font-oup.lua /size: 125 Kb    last modification: 2023-12-21 09:44
1if not modules then modules = { } end modules ['font-oup'] = {
2    version   = 1.001,
3    comment   = "companion to font-ini.mkiv",
4    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5    copyright = "PRAGMA ADE / ConTeXt Development Team",
6    license   = "see context related readme files"
7}
8
9local next, type = next, type
10local P, R, S = lpeg.P, lpeg.R, lpeg.S
11local lpegmatch = lpeg.match
12local insert, remove, copy, unpack = table.insert, table.remove, table.copy, table.unpack
13local find = string.find
14
15local formatters           = string.formatters
16local sortedkeys           = table.sortedkeys
17local sortedhash           = table.sortedhash
18local tohash               = table.tohash
19local setmetatableindex    = table.setmetatableindex
20
21local report_error         = logs.reporter("otf reader","error")
22local report_markwidth     = logs.reporter("otf reader","markwidth")
23local report_cleanup       = logs.reporter("otf reader","cleanup")
24local report_optimizations = logs.reporter("otf reader","merges")
25local report_unicodes      = logs.reporter("otf reader","unicodes")
26
27local trace_markwidth      = false  trackers.register("otf.markwidth",     function(v) trace_markwidth     = v end)
28local trace_cleanup        = false  trackers.register("otf.cleanups",      function(v) trace_cleanups      = v end)
29local trace_optimizations  = false  trackers.register("otf.optimizations", function(v) trace_optimizations = v end)
30local trace_unicodes       = false  trackers.register("otf.unicodes",      function(v) trace_unicodes      = v end)
31
32local readers              = fonts.handlers.otf.readers
33local privateoffset        = fonts.constructors and fonts.constructors.privateoffset or 0xF0000 -- 0x10FFFF
34
35local f_private            = formatters["P%05X"]
36local f_unicode            = formatters["U%05X"]
37local f_index              = formatters["I%05X"]
38local f_character_y        = formatters["%C"]
39local f_character_n        = formatters["[ %C ]"]
40
41local check_duplicates     = true    -- can become an option (pseudo feature) / always needed anyway
42local check_soft_hyphen    = context -- only in context
43
44directives.register("otf.checksofthyphen",function(v)
45    check_soft_hyphen = v -- only for testing
46end)
47
48-- After (!) the unicodes have been resolved we compact ligature tables so before that happens
49-- we don't need to check for numbers.
50
51local function replaced(list,index,replacement)
52    if type(list) == "number" then
53        return replacement
54    elseif type(replacement) == "table" then
55        local t = { }
56        local n = index-1
57        for i=1,n do
58            t[i] = list[i]
59        end
60        for i=1,#replacement do
61            n = n + 1
62            t[n] = replacement[i]
63        end
64        for i=index+1,#list do
65            n = n + 1
66            t[n] = list[i]
67        end
68    else
69        list[index] = replacement
70        return list
71    end
72end
73
74local function unifyresources(fontdata,indices)
75    local descriptions = fontdata.descriptions
76    local resources    = fontdata.resources
77    if not descriptions or not resources then
78        return
79    end
80    --
81    local nofindices = #indices
82    --
83    local variants = fontdata.resources.variants
84    if variants then
85        for selector, unicodes in next, variants do
86            for unicode, index in next, unicodes do
87                unicodes[unicode] = indices[index]
88            end
89        end
90    end
91    --
92    local function remark(marks)
93        if marks then
94            local newmarks = { }
95            for k, v in next, marks do
96                local u = indices[k]
97                if u then
98                    newmarks[u] = v
99                elseif trace_optimizations then
100                    report_optimizations("discarding mark %i",k)
101                end
102            end
103            return newmarks
104        end
105    end
106    --
107    local marks = resources.marks
108    if marks then
109        resources.marks  = remark(marks)
110    end
111    --
112    local markclasses = resources.markclasses
113    if markclasses then
114        for class, marks in next, markclasses do
115            markclasses[class] = remark(marks)
116        end
117    end
118    --
119    local marksets = resources.marksets
120    if marksets then
121        for class, marks in next, marksets do
122            marksets[class] = remark(marks)
123        end
124    end
125    --
126    local done = { } -- we need to deal with shared !
127    --
128    local duplicates = check_duplicates and resources.duplicates
129    if duplicates and not next(duplicates) then
130        duplicates = false
131    end
132    --
133    local function recover(cover) -- can be packed
134        for i=1,#cover do
135            local c = cover[i]
136            if not done[c] then
137                local t = { }
138                for k, v in next, c do
139                    local ug = indices[k]
140                    if ug then
141                        t[ug] = v
142                    else
143                        report_error("case %i, bad index in unifying %s: %s of %s",1,"coverage",k,nofindices)
144                    end
145                end
146                cover[i] = t
147                done[c]  = d
148            end
149        end
150    end
151    --
152    local function recursed(c,kind) -- ligs are not packed
153        local t = { }
154        for g, d in next, c do
155            if type(d) == "table" then
156                local ug = indices[g]
157                if ug then
158                    t[ug] = recursed(d,kind)
159                else
160                    report_error("case %i, bad index in unifying %s: %s of %s",1,kind,g,nofindices)
161                end
162            else
163                t[g] = indices[d] -- ligature
164            end
165        end
166        return t
167    end
168    --
169    -- the duplicates need checking (probably only in cjk fonts): currently we only check
170    -- gsub_single, gsub_alternate, gsub_multiple, gpos_single and gpos_cursive
171    --
172    local function unifythem(sequences)
173        if not sequences then
174            return
175        end
176        for i=1,#sequences do
177            local sequence  = sequences[i]
178            local kind      = sequence.type
179            local steps     = sequence.steps
180            local features  = sequence.features
181            if steps then
182                for i=1,#steps do
183                    local step = steps[i]
184                    if kind == "gsub_single" then
185                        local c = step.coverage
186                        if c then
187                            local t1 = done[c]
188                            if not t1 then
189                                t1 = { }
190                                if duplicates then
191                                    for g1, d1 in next, c do
192                                        local ug1 = indices[g1]
193                                        if ug1 then
194                                            local ud1 = indices[d1]
195                                            if ud1 then
196                                                t1[ug1] = ud1
197                                                local dg1 = duplicates[ug1]
198                                                if dg1 then
199                                                    for u in next, dg1 do
200                                                        t1[u] = ud1
201                                                    end
202                                                end
203                                            else
204                                                report_error("case %i, bad index in unifying %s: %s of %s",3,kind,d1,nofindices)
205                                            end
206                                        else
207                                            report_error("case %i, bad index in unifying %s: %s of %s",1,kind,g1,nofindices)
208                                        end
209                                    end
210                                else
211                                    for g1, d1 in next, c do
212                                        local ug1 = indices[g1]
213                                        if ug1 then
214                                            t1[ug1] = indices[d1]
215                                        else
216                                            report_error("fuzzy case %i in unifying %s: %i",2,kind,g1)
217                                        end
218                                    end
219                                end
220                                done[c] = t1
221                            end
222                            step.coverage = t1
223                        end
224                    elseif kind == "gpos_pair" then
225                        local c = step.coverage
226                        if c then
227                            local t1 = done[c]
228                            if not t1 then
229                                t1 = { }
230                                for g1, d1 in next, c do
231                                    local ug1 = indices[g1]
232                                    if ug1 then
233                                        local t2 = done[d1]
234                                        if not t2 then
235                                            t2 = { }
236                                            for g2, d2 in next, d1 do
237                                                local ug2 = indices[g2]
238                                                if ug2 then
239                                                    t2[ug2] = d2
240                                                else
241                                                    report_error("case %i, bad index in unifying %s: %s of %s",1,kind,g2,nofindices,nofindices)
242                                                end
243                                            end
244                                            done[d1] = t2
245                                        end
246                                        t1[ug1] = t2
247                                    else
248                                        report_error("case %i, bad index in unifying %s: %s of %s",2,kind,g1,nofindices)
249                                    end
250                                end
251                                done[c] = t1
252                            end
253                            step.coverage = t1
254                        end
255                    elseif kind == "gsub_ligature" then
256                        local c = step.coverage
257                        if c then
258                            step.coverage = recursed(c,kind)
259                        end
260                    elseif kind == "gsub_alternate" or kind == "gsub_multiple" then
261                        local c = step.coverage
262                        if c then
263                            local t1 = done[c]
264                            if not t1 then
265                                t1 = { }
266                                if duplicates then
267                                    for g1, d1 in next, c do
268                                        for i=1,#d1 do
269                                            local d1i = d1[i]
270                                            local d1u = indices[d1i]
271                                            if d1u then
272                                                d1[i] = d1u
273                                            else
274                                                report_error("case %i, bad index in unifying %s: %s of %s",1,kind,i,d1i,nofindices)
275                                            end
276                                        end
277                                        local ug1 = indices[g1]
278                                        if ug1 then
279                                            t1[ug1] = d1
280                                            local dg1 = duplicates[ug1]
281                                            if dg1 then
282                                                for u in next, dg1 do
283                                                    t1[u] = copy(d1)
284                                                end
285                                            end
286                                        else
287                                            report_error("case %i, bad index in unifying %s: %s of %s",2,kind,g1,nofindices)
288                                        end
289                                    end
290                                else
291                                    for g1, d1 in next, c do
292                                        for i=1,#d1 do
293                                            local d1i = d1[i]
294                                            local d1u = indices[d1i]
295                                            if d1u then
296                                                d1[i] = d1u
297                                            else
298                                                report_error("case %i, bad index in unifying %s: %s of %s",2,kind,d1i,nofindices)
299                                            end
300                                        end
301                                        t1[indices[g1]] = d1
302                                    end
303                                end
304                                done[c] = t1
305                            end
306                            step.coverage = t1
307                        end
308                    elseif kind == "gpos_single" then
309                        local c = step.coverage
310                        if c then
311                            local t1 = done[c]
312                            if not t1 then
313                                t1 = { }
314                                if duplicates then
315                                    for g1, d1 in next, c do
316                                        local ug1 = indices[g1]
317                                        if ug1 then
318                                            t1[ug1] = d1
319                                            local dg1 = duplicates[ug1]
320                                            if dg1 then
321                                                for u in next, dg1 do
322                                                    t1[u] = d1
323                                                end
324                                            end
325                                        else
326                                            report_error("case %i, bad index in unifying %s: %s of %s",1,kind,g1,nofindices)
327                                        end
328                                    end
329                                else
330                                    for g1, d1 in next, c do
331                                        local ug1 = indices[g1]
332                                        if ug1 then
333                                            t1[ug1] = d1
334                                        else
335                                            report_error("case %i, bad index in unifying %s: %s of %s",2,kind,g1,nofindices)
336                                        end
337                                    end
338                                end
339                                done[c] = t1
340                            end
341                            step.coverage = t1
342                        end
343                    elseif kind == "gpos_mark2base" or kind == "gpos_mark2mark" or kind == "gpos_mark2ligature" then
344                        local c = step.coverage
345                        if c then
346                            local t1 = done[c]
347                            if not t1 then
348                                t1 = { }
349                                for g1, d1 in next, c do
350                                    local ug1 = indices[g1]
351                                    if ug1 then
352                                        t1[ug1] = d1
353                                    else
354                                        report_error("case %i, bad index in unifying %s: %s of %s",1,kind,g1,nofindices)
355                                    end
356                                end
357                                done[c] = t1
358                            end
359                            step.coverage = t1
360                        end
361                        local c = step.baseclasses
362                        if c then
363                            local t1 = done[c]
364                            if not t1 then
365                                for g1, d1 in next, c do
366                                    local t2 = done[d1]
367                                    if not t2 then
368                                        t2 = { }
369                                        for g2, d2 in next, d1 do
370                                            local ug2 = indices[g2]
371                                            if ug2 then
372                                                t2[ug2] = d2
373                                            else
374                                                report_error("case %i, bad index in unifying %s: %s of %s",2,kind,g2,nofindices)
375                                            end
376                                        end
377                                        done[d1] = t2
378                                    end
379                                    c[g1] = t2
380                                end
381                                done[c] = c
382                            end
383                        end
384                    elseif kind == "gpos_cursive" then
385                        local c = step.coverage
386                        if c then
387                            local t1 = done[c]
388                            if not t1 then
389                                t1 = { }
390                                if duplicates then
391                                    for g1, d1 in next, c do
392                                        local ug1 = indices[g1]
393                                        if ug1 then
394                                            t1[ug1] = d1
395                                            --
396                                            local dg1 = duplicates[ug1]
397                                            if dg1 then
398                                                -- probably needs a bit more
399                                                for u in next, dg1 do
400                                                    t1[u] = copy(d1)
401                                                end
402                                            end
403                                        else
404                                            report_error("case %i, bad index in unifying %s: %s of %s",1,kind,g1,nofindices)
405                                        end
406                                    end
407                                else
408                                    for g1, d1 in next, c do
409                                        local ug1 = indices[g1]
410                                        if ug1 then
411                                            t1[ug1] = d1
412                                        else
413                                            report_error("case %i, bad index in unifying %s: %s of %s",2,kind,g1,nofindices)
414                                        end
415                                    end
416                                end
417                                done[c] = t1
418                            end
419                            step.coverage = t1
420                        end
421                    end
422                    --
423                    local rules = step.rules
424                    if rules then
425                        for i=1,#rules do
426                            local rule = rules[i]
427                            --
428                            local before   = rule.before   if before  then recover(before)  end
429                            local after    = rule.after    if after   then recover(after)   end
430                            local current  = rule.current  if current then recover(current) end
431                            --
432                            local replacements = rule.replacements
433                            if replacements then
434                                if not done[replacements] then
435                                    local r = { }
436                                    for k, v in next, replacements do
437                                        r[indices[k]] = indices[v]
438                                    end
439                                    rule.replacements = r
440                                    done[replacements] = r
441                                end
442                            end
443                        end
444                    end
445                end
446            end
447       end
448    end
449    --
450    unifythem(resources.sequences)
451    unifythem(resources.sublookups)
452end
453
454local function copyduplicates(fontdata)
455    if check_duplicates then
456        local descriptions = fontdata.descriptions
457        local resources    = fontdata.resources
458        local duplicates   = resources.duplicates
459        if check_soft_hyphen then
460         -- ebgaramond has a zero width empty soft hyphen
461         -- antykwatorunska lacks a soft hyphen
462         -- lucidaot has a halfwidth soft hyphen
463
464         -- local dh = descriptions[0x2D]
465         -- if dh then
466         --     descriptions[0xAD] = nil
467         --     local d = duplicates[0x2D]
468         --     if d then
469         --         d[#d+1] = { [0xAD] = true }
470         --     else
471         --         duplicates[0x2D] = { [0xAD] = true }
472         --     end
473         -- end
474
475            local dh = descriptions[0x2D]
476            if dh then
477                local ds = descriptions[0xAD]
478                if not ds or ds.width ~= dh.width then
479                    descriptions[0xAD] = nil
480                    if ds then
481                        if trace_unicodes then
482                            report_unicodes("patching soft hyphen")
483                        end
484                    else
485                        if trace_unicodes then
486                            report_unicodes("adding soft hyphen")
487                        end
488                    end
489                    if not duplicates then
490                        duplicates = { }
491                        resources.duplicates = duplicates
492                    end
493                    local d = duplicates[0x2D]
494                    if d then
495                        d[0xAD] = true
496                    else
497                        duplicates[0x2D] = { [0xAD] = true }
498                    end
499                end
500            end
501
502        end
503        if duplicates then
504           for u, d in next, duplicates do
505                local du = descriptions[u]
506                if du then
507                    local t = { f_character_y(u), "@", f_index(du.index), "->" }
508                    local n = 0
509                    local m = 25
510                    for u in next, d do
511                        if descriptions[u] then
512                            if n < m then
513                                t[n+4] = f_character_n(u)
514                            end
515                        else
516                            local c = copy(du)
517                            c.unicode = u -- better this way
518                            descriptions[u] = c
519                            if n < m then
520                                t[n+4] = f_character_y(u)
521                            end
522                        end
523                        n = n + 1
524                    end
525                    if trace_unicodes then
526                        if n <= m then
527                            report_unicodes("%i : % t",n,t)
528                        else
529                            report_unicodes("%i : % t ...",n,t)
530                        end
531                    end
532                else
533                    -- what a mess
534                end
535            end
536        end
537    end
538end
539
540local ignore = { -- should we fix them?
541    ["notdef"]            = true,
542    [".notdef"]           = true,
543    ["null"]              = true,
544    [".null"]             = true,
545    ["nonmarkingreturn"]  = true,
546}
547
548
549local function checklookups(fontdata,missing,nofmissing)
550    local descriptions = fontdata.descriptions
551    local resources    = fontdata.resources
552    if missing and nofmissing and nofmissing <= 0 then
553        return
554    end
555    --
556    local singles    = { }
557    local alternates = { }
558    local ligatures  = { }
559
560    if not missing then
561        missing    = { }
562        nofmissing = 0
563        for u, d in next, descriptions do
564            if not d.unicode then
565                nofmissing = nofmissing + 1
566                missing[u] = true
567            end
568        end
569    end
570    local function collectthem(sequences)
571        if not sequences then
572            return
573        end
574        for i=1,#sequences do
575            local sequence = sequences[i]
576            local kind     = sequence.type
577            local steps    = sequence.steps
578            if steps then
579                for i=1,#steps do
580                    local step = steps[i]
581                    if kind == "gsub_single" then
582                        local c = step.coverage
583                        if c then
584                            singles[#singles+1] = c
585                        end
586                    elseif kind == "gsub_alternate" then
587                        local c = step.coverage
588                        if c then
589                            alternates[#alternates+1] = c
590                        end
591                    elseif kind == "gsub_ligature" then
592                        local c = step.coverage
593                        if c then
594                            ligatures[#ligatures+1] = c
595                        end
596                    end
597                end
598            end
599        end
600    end
601
602    collectthem(resources.sequences)
603    collectthem(resources.sublookups)
604
605    local loops = 0
606    while true do
607        loops = loops + 1
608        local old = nofmissing
609        for i=1,#singles do
610            local c = singles[i]
611            for g1, g2 in next, c do
612                if missing[g1] then
613                    local u2 = descriptions[g2].unicode
614                    if u2 then
615                        missing[g1] = false
616                        descriptions[g1].unicode = u2
617                        nofmissing = nofmissing - 1
618                    end
619                end
620                if missing[g2] then
621                    local u1 = descriptions[g1].unicode
622                    if u1 then
623                        missing[g2] = false
624                        descriptions[g2].unicode = u1
625                        nofmissing = nofmissing - 1
626                    end
627                end
628            end
629        end
630        for i=1,#alternates do
631            local c = alternates[i]
632            -- maybe first a g1 loop and then a g2
633            for g1, d1 in next, c do
634                if missing[g1] then
635                    for i=1,#d1 do
636                        local g2 = d1[i]
637                        local u2 = descriptions[g2].unicode
638                        if u2 then
639                            missing[g1] = false
640                            descriptions[g1].unicode = u2
641                            nofmissing = nofmissing - 1
642                        end
643                    end
644                end
645                if not missing[g1] then
646                    for i=1,#d1 do
647                        local g2 = d1[i]
648                        if missing[g2] then
649                            local u1 = descriptions[g1].unicode
650                            if u1 then
651                                missing[g2] = false
652                                descriptions[g2].unicode = u1
653                                nofmissing = nofmissing - 1
654                            end
655                        end
656                    end
657                end
658            end
659        end
660        if nofmissing <= 0 then
661            if trace_unicodes then
662                report_unicodes("all missings done in %s loops",loops)
663            end
664            return
665        elseif old == nofmissing then
666            break
667        end
668    end
669
670    local t, n -- no need to insert/remove and allocate many times
671
672    local function recursed(c)
673        for g, d in next, c do
674            if g ~= "ligature" then
675                local u = descriptions[g].unicode
676                if u then
677                    n = n + 1
678                    t[n] = u
679                    recursed(d)
680                    n = n - 1
681                end
682            elseif missing[d] then
683                local l = { }
684                local m = 0
685                for i=1,n do
686                    local u = t[i]
687                    if type(u) == "table" then
688                        for i=1,#u do
689                            m = m + 1
690                            l[m] = u[i]
691                        end
692                    else
693                        m = m + 1
694                        l[m] = u
695                    end
696                end
697                missing[d] = false
698                descriptions[d].unicode = l
699                nofmissing = nofmissing - 1
700            end
701        end
702    end
703
704    if nofmissing > 0 then
705        t = { }
706        n = 0
707        local loops = 0
708        while true do
709            loops = loops + 1
710            local old = nofmissing
711            for i=1,#ligatures do
712                recursed(ligatures[i])
713            end
714            if nofmissing <= 0 then
715                if trace_unicodes then
716                    report_unicodes("all missings done in %s loops",loops)
717                end
718                return
719            elseif old == nofmissing then
720                break
721            end
722        end
723        t = nil
724        n = 0
725    end
726
727    if trace_unicodes and nofmissing > 0 then
728        local done = { }
729        for i, r in next, missing do
730            if r then
731                local data = descriptions[i]
732                local name = data and data.name or f_index(i)
733                if not ignore[name] then
734                    done[name] = true
735                end
736            end
737        end
738        if next(done) then
739            report_unicodes("not unicoded: % t",sortedkeys(done))
740        end
741    end
742end
743
744local firstprivate = fonts.privateoffsets and fonts.privateoffsets.textbase or 0xF0000
745local puafirst     = 0xE000
746local pualast      = 0xF8FF
747
748local function unifymissing(fontdata)
749    if not fonts.mappings then
750        require("font-map")
751        require("font-agl")
752    end
753    local unicodes     = { }
754    local resources    = fontdata.resources
755    resources.unicodes = unicodes
756    for unicode, d in next, fontdata.descriptions do
757        if unicode < privateoffset then
758            if unicode >= puafirst and unicode <= pualast then
759                -- report_unicodes("resolving private unicode %U",unicode)
760            else
761                local name = d.name
762                if name then
763                    unicodes[name] = unicode
764                end
765            end
766        else
767            -- report_unicodes("resolving private unicode %U",unicode)
768        end
769    end
770    fonts.mappings.addtounicode(fontdata,fontdata.filename,checklookups)
771    resources.unicodes = nil
772end
773
774local function unifyglyphs(fontdata,usenames)
775    local private      = fontdata.private or privateoffset
776    local glyphs       = fontdata.glyphs
777    local indices      = { }
778    local descriptions = { }
779    local names        = usenames and { }
780    local resources    = fontdata.resources
781    local zero         = glyphs[0]
782    local zerocode     = zero.unicode
783    local nofglyphs    = #glyphs
784    if not zerocode then
785        zerocode       = private
786        zero.unicode   = zerocode
787        private        = private + 1
788    end
789    descriptions[zerocode] = zero
790    if names then
791        local name  = glyphs[0].name or f_private(zerocode)
792        indices[0]  = name
793        names[name] = zerocode
794    else
795        indices[0] = zerocode
796    end
797    --
798    if names then
799        -- seldom uses, we don't issue message ... this branch might even go away
800        for index=1,nofglyphs do
801            local glyph   = glyphs[index]
802            local unicode = glyph.unicode -- this is the primary one
803            if not unicode then
804                unicode        = private
805                local name     = glyph.name or f_private(unicode)
806                indices[index] = name
807                names[name]    = unicode
808                private        = private + 1
809            elseif unicode >= firstprivate then
810                unicode        = private
811                local name     = glyph.name or f_private(unicode)
812                indices[index] = name
813                names[name]    = unicode
814                private        = private + 1
815            elseif unicode >= puafirst and unicode <= pualast then
816                local name     = glyph.name or f_private(unicode)
817                indices[index] = name
818                names[name]    = unicode
819            elseif descriptions[unicode] then
820                unicode        = private
821                local name     = glyph.name or f_private(unicode)
822                indices[index] = name
823                names[name]    = unicode
824                private        = private + 1
825            else
826                local name     = glyph.name or f_unicode(unicode)
827                indices[index] = name
828                names[name]    = unicode
829            end
830            descriptions[unicode] = glyph
831        end
832    elseif trace_unicodes then
833        for index=1,nofglyphs do
834            local glyph   = glyphs[index]
835            local unicode = glyph.unicode -- this is the primary one
836            if not unicode then
837                unicode        = private
838                indices[index] = unicode
839                private        = private + 1
840            elseif unicode >= firstprivate then
841                local name = glyph.name
842                if name then
843                    report_unicodes("moving glyph %a indexed %05X from private %U to %U ",name,index,unicode,private)
844                else
845                    report_unicodes("moving glyph indexed %05X from private %U to %U ",index,unicode,private)
846                end
847                unicode        = private
848                indices[index] = unicode
849                private        = private + 1
850            elseif unicode >= puafirst and unicode <= pualast then
851                local name = glyph.name
852                if name then
853                    report_unicodes("keeping private unicode %U for glyph %a indexed %05X",unicode,name,index)
854                else
855                    report_unicodes("keeping private unicode %U for glyph indexed %05X",unicode,index)
856                end
857                indices[index] = unicode
858            elseif descriptions[unicode] then
859                local name = glyph.name
860                if name then
861                    report_unicodes("assigning duplicate unicode %U to %U for glyph %a indexed %05X ",unicode,private,name,index)
862                else
863                    report_unicodes("assigning duplicate unicode %U to %U for glyph indexed %05X ",unicode,private,index)
864                end
865                unicode        = private
866                indices[index] = unicode
867                private        = private + 1
868            else
869                indices[index] = unicode
870            end
871            descriptions[unicode] = glyph
872        end
873    else
874        for index=1,nofglyphs do
875            local glyph   = glyphs[index]
876            local unicode = glyph.unicode -- this is the primary one
877            if not unicode then
878                unicode        = private
879                indices[index] = unicode
880                private        = private + 1
881            elseif unicode >= firstprivate then
882                local name = glyph.name
883                unicode        = private
884                indices[index] = unicode
885                private        = private + 1
886            elseif unicode >= puafirst and unicode <= pualast then
887                local name = glyph.name
888                indices[index] = unicode
889            elseif descriptions[unicode] then
890                local name = glyph.name
891                unicode        = private
892                indices[index] = unicode
893                private        = private + 1
894            else
895                indices[index] = unicode
896            end
897            descriptions[unicode] = glyph
898        end
899    end
900    --
901    if LUATEXENGINE == "luametatex" then
902        for index=1,nofglyphs do
903            local math = glyphs[index].math
904            if math then
905                local list = math.parts
906                if list then
907                    for i=1,#list do local l = list[i] l.glyph = indices[l.glyph] end
908                end
909                local list = math.variants
910                if list then
911                    for i=1,#list do list[i] = indices[list[i]] end
912                end
913            end
914        end
915    else
916        for index=1,nofglyphs do
917            local math = glyphs[index].math
918            if math then
919                local list = math.vparts
920                if list then
921                    for i=1,#list do local l = list[i] l.glyph = indices[l.glyph] end
922                end
923                local list = math.hparts
924                if list then
925                    for i=1,#list do local l = list[i] l.glyph = indices[l.glyph] end
926                end
927                local list = math.vvariants
928                if list then
929                 -- for i=1,#list do local l = list[i] l.glyph = indices[l.glyph] end
930                    for i=1,#list do list[i] = indices[list[i]] end
931                end
932                local list = math.hvariants
933                if list then
934                 -- for i=1,#list do local l = list[i] l.glyph = indices[l.glyph] end
935                    for i=1,#list do list[i] = indices[list[i]] end
936                end
937            end
938        end
939    end
940    --
941    local colorpalettes = resources.colorpalettes
942    if colorpalettes then
943        for index=1,nofglyphs do
944            local colors = glyphs[index].colors
945            if colors then
946                for i=1,#colors do
947                    local c = colors[i]
948                    if c then -- safeguard
949                        c.slot = indices[c.slot]
950                    end
951                end
952            end
953        end
954    end
955    --
956    fontdata.private      = private
957    fontdata.glyphs       = nil
958    fontdata.names        = names
959    fontdata.descriptions = descriptions
960    fontdata.hashmethod   = hashmethod
961    fontdata.nofglyphs    = nofglyphs
962    --
963    return indices, names
964end
965
966local stripredundant  do
967
968    local p_hex   = R("af","AF","09")
969    local p_digit = R("09")
970    local p_done  = S("._-")^0 + P(-1)
971 -- local p_style = P(".ss") * p_digit * p_digit * P(-1)
972    local p_style = P(".")
973    local p_alpha = R("az","AZ")
974    local p_ALPHA = R("AZ")
975
976    local p_crappyname = (
977    -- (P("uni") + P("UNI") + P("Uni") + P("U") + P("u"))
978        lpeg.utfchartabletopattern({ "uni", "u" },true)
979      * S("Xx_")^0
980      * p_hex^1
981   -- + (P("identity") + P("Identity") + P("IDENTITY") + P("glyph") + P("jamo"))
982      + lpeg.utfchartabletopattern({ "identity", "glyph", "jamo" },true)
983      * p_hex^1
984   -- + (P("index") + P("Index") + P("INDEX")+ P("afii"))
985      + lpeg.utfchartabletopattern({ "index", "afii" }, true)
986      * p_digit^1
987      -- also happens l
988      + p_digit
989      * p_hex^3
990      + p_alpha
991      * p_digit^1
992      -- sort of special
993      + P("aj")
994      * p_digit^1
995      + P("eh_")
996      * (p_digit^1 + p_ALPHA * p_digit^1)
997      + (1-P("_"))^1
998      * P("_uni")
999      * p_hex^1
1000      + P("_")
1001      * P(1)^1
1002    ) * p_done
1003
1004    -- In context we only keep glyph names because of tracing and access by name
1005    -- so weird names make no sense.
1006
1007    if context then
1008
1009        local forcekeep = false -- only for testing something
1010--         local forcekeep = true
1011
1012        directives.register("otf.keepnames",function(v)
1013            report_cleanup("keeping weird glyph names, expect larger files and more memory usage")
1014            forcekeep = v
1015        end)
1016
1017     -- local p_lesscrappyname =
1018     --     lpeg.utfchartabletopattern({ "uni", "u" },true)
1019     --   * S("Xx")^0
1020     --   * p_hex^1
1021     --   * p_style
1022
1023        local function stripvariants(descriptions,list)
1024            local n = list and #list or 0
1025            if n > 0 then
1026                for i=1,n do
1027                    local g = list[i]
1028                    if g then
1029                        local d = descriptions[g]
1030                        if d and d.name then
1031                            d.name = nil
1032                            n = n + 1
1033                        end
1034                    end
1035                end
1036            end
1037            return n
1038        end
1039
1040        local function stripparts(descriptions,list)
1041            local n = list and #list or 0
1042            if n > 0 then
1043                for i=1,n do
1044                    local g = list[i].glyph
1045                    if g then
1046                        local d = descriptions[g]
1047                        if d and d.name then
1048                            d.name = nil
1049                            n = n + 1
1050                        end
1051                    end
1052                end
1053            end
1054            return n
1055        end
1056
1057     -- local function collectsimple(fontdata)
1058     --     local resources = fontdata.resources
1059     --     local sequences = resources and resources.sequences
1060     --     if sequences then
1061     --         local keeplist = { }
1062     --         for i=1,#sequences do
1063     --             local s = sequences[i]
1064     --             if s.type == "gsub_single" then
1065     --                 -- only simple ones
1066     --                 local features = s.features
1067     --                 local steps    = s.steps
1068     --                 if features and steps then
1069     --                     local okay = false
1070     --                     for k, v in next, features do
1071     --                         if find(k,"^ss%d%d") then
1072     --                             okay = true
1073     --                             break
1074     --                         end
1075     --                     end
1076     --                     if okay then
1077     --                         for i=1,#steps do
1078     --                             local coverage = steps[i].coverage
1079     --                             if coverage then
1080     --                                 for k, v in next, coverage do
1081     --                                     keeplist[k] = v
1082     --                                 end
1083     --                             end
1084     --                         end
1085     --                     end
1086     --                 end
1087     --             end
1088     --         end
1089     --         return next(keeplist) and keeplist or nil
1090     --     end
1091     -- end
1092
1093        local function collectsimple(fontdata)
1094            return nil
1095        end
1096
1097        stripredundant = function(fontdata)
1098            local descriptions = fontdata.descriptions
1099            if descriptions then
1100                local n = 0
1101                local c = 0
1102                for unicode, d in next, descriptions do
1103                    local m = d.math
1104                    if m then
1105                        n = n + stripvariants(descriptions,m.vvariants)
1106                        n = n + stripvariants(descriptions,m.hvariants)
1107                        n = n + stripparts   (descriptions,m.vparts)
1108                        n = n + stripparts   (descriptions,m.hparts)
1109                    end
1110                end
1111                if forcekeep then
1112                    for unicode, d in next, descriptions do
1113                        if d.class == "base" then
1114                            d.class = nil
1115                            c = c + 1
1116                        end
1117                    end
1118                else
1119                    local keeplist = collectsimple(fontdata)
1120                    for unicode, d in next, descriptions do
1121                        local name = d.name
1122                        if name then
1123                         -- if lpegmatch(p_lesscrappyname,name) then
1124                            if keeplist and keeplist[name] then
1125                                -- keep name
1126                            elseif lpegmatch(p_crappyname,name) then
1127                                d.name = nil
1128                                n = n + 1
1129                            end
1130                        end
1131                        if d.class == "base" then
1132                            d.class = nil
1133                            c = c + 1
1134                        end
1135                    end
1136                end
1137                if trace_cleanup then
1138                    if n > 0 then
1139                        report_cleanup("%s bogus names removed (verbose unicode)",n)
1140                    end
1141                    if c > 0 then
1142                        report_cleanup("%s base class tags removed (default is base)",c)
1143                    end
1144                end
1145            end
1146        end
1147
1148    else
1149
1150        stripredundant = function(fontdata)
1151            local descriptions = fontdata.descriptions
1152            if descriptions then
1153                if fonts.privateoffsets.keepnames then
1154                    for unicode, d in next, descriptions do
1155                        if d.class == "base" then
1156                            d.class = nil
1157                        end
1158                    end
1159                else
1160                    for unicode, d in next, descriptions do
1161                        local name = d.name
1162                        if name then
1163                            if lpegmatch(p_crappyname,name) then
1164                                d.name = nil
1165                            end
1166                        end
1167                        if d.class == "base" then
1168                            d.class = nil
1169                        end
1170                    end
1171                end
1172            end
1173        end
1174
1175    end
1176
1177    readers.stripredundant = stripredundant
1178
1179end
1180
1181function readers.getcomponents(fontdata) -- handy for resolving ligatures when names are missing
1182    local resources = fontdata.resources
1183    if resources then
1184        local sequences = resources.sequences
1185        if sequences then
1186            local collected = { }
1187            for i=1,#sequences do
1188                local sequence = sequences[i]
1189                if sequence.type == "gsub_ligature" then
1190                    local steps  = sequence.steps
1191                    if steps then
1192                        local l = { }
1193                        local function traverse(p,k,v)
1194                            if k == "ligature" then
1195                                collected[v] = { unpack(l) }
1196                            elseif tonumber(v) then
1197                                insert(l,k)
1198                                collected[v] = { unpack(l) }
1199                                remove(l)
1200                            else
1201                                insert(l,k)
1202                                for k, vv in next, v do
1203                                    traverse(p,k,vv)
1204                                end
1205                                remove(l)
1206                            end
1207                        end
1208                        for i=1,#steps do
1209                         -- we actually had/have this in base mode
1210                            local c = steps[i].coverage
1211                            if c then
1212                                for k, v in next, c do
1213                                    traverse(k,k,v)
1214                                end
1215                            end
1216                        end
1217                    end
1218                end
1219            end
1220            if next(collected) then
1221                -- remove self referring
1222             -- for k, v in next, collected do
1223             --     for i=1,#v do
1224             --         local vi = v[i]
1225             --         if vi == k then
1226             --          -- report("removing self referring ligature @ slot %5X from collected (1)",k)
1227             --             collected[k] = nil
1228             --         end
1229             --     end
1230             -- end
1231                while true do
1232                    local done = false
1233                    for k, v in next, collected do
1234                        for i=1,#v do
1235                            local vi = v[i]
1236                            if vi == k then
1237                             -- report("removing self referring ligature @ slot %5X from collected (2)",k)
1238                                collected[k] = nil
1239                                break
1240                            else
1241                                local c = collected[vi]
1242                                if c then
1243                                    done = true
1244                                    local t = { }
1245                                    local n = i - 1
1246                                    for j=1,n do
1247                                        t[j] = v[j]
1248                                    end
1249                                    for j=1,#c do
1250                                        n = n + 1
1251                                        t[n] = c[j]
1252                                    end
1253                                    for j=i+1,#v do
1254                                        n = n + 1
1255                                        t[n] = v[j]
1256                                    end
1257                                    collected[k] = t
1258                                    break
1259                                end
1260                            end
1261                        end
1262                    end
1263                    if not done then
1264                        break
1265                    end
1266                end
1267                return collected
1268            end
1269        end
1270    end
1271end
1272
1273readers.unifymissing = unifymissing
1274
1275function readers.rehash(fontdata,hashmethod) -- TODO: combine loops in one
1276    if not (fontdata and fontdata.glyphs) then
1277        return
1278    elseif hashmethod == "indices" then
1279        fontdata.hashmethod = "indices"
1280    elseif hashmethod == "names" then
1281        fontdata.hashmethod = "names"
1282        local indices = unifyglyphs(fontdata,true)
1283        unifyresources(fontdata,indices)
1284        copyduplicates(fontdata)
1285        unifymissing(fontdata)
1286    else
1287        fontdata.hashmethod = "unicodes"
1288        local indices = unifyglyphs(fontdata)
1289        unifyresources(fontdata,indices)
1290        copyduplicates(fontdata)
1291        unifymissing(fontdata)
1292        stripredundant(fontdata)
1293    end
1294    -- maybe here components
1295end
1296
1297function readers.checkhash(fontdata)
1298    local hashmethod = fontdata.hashmethod
1299    if hashmethod == "unicodes" then
1300        fontdata.names = nil -- just to be sure
1301    elseif hashmethod == "names" and fontdata.names then
1302        unifyresources(fontdata,fontdata.names)
1303        copyduplicates(fontdata)
1304        fontdata.hashmethod = "unicodes"
1305        fontdata.names = nil -- no need for it
1306    else
1307        readers.rehash(fontdata,"unicodes")
1308    end
1309end
1310
1311function readers.addunicodetable(fontdata)
1312    local resources = fontdata.resources
1313    local unicodes  = resources.unicodes
1314    if not unicodes then
1315        local descriptions = fontdata.descriptions
1316        if descriptions then
1317            unicodes = { }
1318            resources.unicodes = unicodes
1319            for u, d in next, descriptions do
1320                local n = d.name
1321                if n then
1322                    unicodes[n] = u
1323                end
1324            end
1325        end
1326    end
1327end
1328
1329-- for the moment here:
1330
1331local concat, sort = table.concat, table.sort
1332local next, type, tostring = next, type, tostring
1333
1334local criterium     = 1
1335local threshold     = 0
1336
1337local trace_packing = false  trackers.register("otf.packing", function(v) trace_packing = v end)
1338local trace_loading = false  trackers.register("otf.loading", function(v) trace_loading = v end)
1339
1340local report_otf    = logs.reporter("fonts","otf loading")
1341
1342local function tabstr_normal(t)
1343    local s = { }
1344    local n = 0
1345    for k, v in next, t do
1346        n = n + 1
1347        if type(v) == "table" then
1348            s[n] = k .. ">" .. tabstr_normal(v)
1349        elseif v == true then
1350            s[n] = k .. "+" -- "=true"
1351        elseif v then
1352            s[n] = k .. "=" .. v
1353        else
1354            s[n] = k .. "-" -- "=false"
1355        end
1356    end
1357    if n == 0 then
1358        return ""
1359    elseif n == 1 then
1360        return s[1]
1361    else
1362        sort(s) -- costly but needed (occasional wrong hit otherwise)
1363        return concat(s,",")
1364    end
1365end
1366
1367local function tabstr_flat(t)
1368    local s = { }
1369    local n = 0
1370    for k, v in next, t do
1371        n = n + 1
1372        s[n] = k .. "=" .. v
1373    end
1374    if n == 0 then
1375        return ""
1376    elseif n == 1 then
1377        return s[1]
1378    else
1379        sort(s) -- costly but needed (occasional wrong hit otherwise)
1380        return concat(s,",")
1381    end
1382end
1383
1384local function tabstr_mixed(t) -- indexed
1385    local n = #t
1386    if n == 0 then
1387        return ""
1388    elseif n == 1 then
1389        local k = t[1]
1390        if k == true then
1391            return "++" -- we need to distinguish from "true"
1392        elseif k == false then
1393            return "--" -- we need to distinguish from "false"
1394        else
1395            return tostring(k) -- number or string
1396        end
1397    else
1398        local s = { }
1399        for i=1,n do
1400            local k = t[i]
1401            if k == true then
1402                s[i] = "++" -- we need to distinguish from "true"
1403            elseif k == false then
1404                s[i] = "--" -- we need to distinguish from "false"
1405            else
1406                s[i] = k -- number or string
1407            end
1408        end
1409        return concat(s,",")
1410    end
1411end
1412
1413local function tabstr_boolean(t)
1414    local s = { }
1415    local n = 0
1416    for k, v in next, t do
1417        n = n + 1
1418        if v then
1419            s[n] = k .. "+"
1420        else
1421            s[n] = k .. "-"
1422        end
1423    end
1424    if n == 0 then
1425        return ""
1426    elseif n == 1 then
1427        return s[1]
1428    else
1429        sort(s) -- costly but needed (occasional wrong hit otherwise)
1430        return concat(s,",")
1431    end
1432end
1433
1434-- beware: we cannot unpack and repack the same table because then sharing
1435-- interferes (we could catch this if needed) .. so for now: save, reload
1436-- and repack in such cases (never needed anyway) .. a tricky aspect is that
1437-- we then need to sort more thanks to random hashing
1438
1439function readers.pack(data)
1440
1441    if data then
1442
1443        local h, t, c = { }, { }, { }
1444        local hh, tt, cc = { }, { }, { }
1445        local nt, ntt = 0, 0
1446
1447        local function pack_normal(v)
1448            local tag = tabstr_normal(v)
1449            local ht = h[tag]
1450            if ht then
1451                c[ht] = c[ht] + 1
1452                return ht
1453            else
1454                nt = nt + 1
1455                t[nt] = v
1456                h[tag] = nt
1457                c[nt] = 1
1458                return nt
1459            end
1460        end
1461
1462        local function pack_normal_cc(v)
1463            local tag = tabstr_normal(v)
1464            local ht = h[tag]
1465            if ht then
1466                c[ht] = c[ht] + 1
1467                return ht
1468            else
1469                v[1] = 0
1470                nt = nt + 1
1471                t[nt] = v
1472                h[tag] = nt
1473                c[nt] = 1
1474                return nt
1475            end
1476        end
1477
1478        local function pack_flat(v)
1479            local tag = tabstr_flat(v)
1480            local ht = h[tag]
1481            if ht then
1482                c[ht] = c[ht] + 1
1483                return ht
1484            else
1485                nt = nt + 1
1486                t[nt] = v
1487                h[tag] = nt
1488                c[nt] = 1
1489                return nt
1490            end
1491        end
1492
1493        local function pack_indexed(v)
1494            local tag = concat(v," ")
1495            local ht = h[tag]
1496            if ht then
1497                c[ht] = c[ht] + 1
1498                return ht
1499            else
1500                nt = nt + 1
1501                t[nt] = v
1502                h[tag] = nt
1503                c[nt] = 1
1504                return nt
1505            end
1506        end
1507
1508     -- local function pack_indexed(v) -- less code
1509     --     local tag = concat(v," ")
1510     --     local ht = h[tag]
1511     --     if ht then
1512     --         c[ht] = c[ht] + 1
1513     --     else
1514     --         ht = nt + 1
1515     --         t[ht] = v
1516     --         c[ht] = 1
1517     --         h[tag] = ht
1518     --         nt = ht
1519     --     end
1520     --     return ht
1521     -- end
1522
1523        local function pack_mixed(v)
1524            local tag = tabstr_mixed(v)
1525            local ht = h[tag]
1526            if ht then
1527                c[ht] = c[ht] + 1
1528                return ht
1529            else
1530                nt = nt + 1
1531                t[nt] = v
1532                h[tag] = nt
1533                c[nt] = 1
1534                return nt
1535            end
1536        end
1537
1538        -- saves a lot on noto sans
1539
1540        -- can be made more clever
1541
1542        local function pack_boolean(v)
1543            local tag = tabstr_boolean(v)
1544            local ht = h[tag]
1545            if ht then
1546                c[ht] = c[ht] + 1
1547                return ht
1548            else
1549                nt = nt + 1
1550                t[nt] = v
1551                h[tag] = nt
1552                c[nt] = 1
1553                return nt
1554            end
1555        end
1556
1557        local function pack_final(v)
1558            -- v == number
1559            if c[v] <= criterium then
1560                return t[v]
1561            else
1562                -- compact hash
1563                local hv = hh[v]
1564                if hv then
1565                    return hv
1566                else
1567                    ntt = ntt + 1
1568                    tt[ntt] = t[v]
1569                    hh[v] = ntt
1570                    cc[ntt] = c[v]
1571                    return ntt
1572                end
1573            end
1574        end
1575
1576        local function pack_final_cc(v)
1577            -- v == number
1578            if c[v] <= criterium then
1579                return t[v]
1580            else
1581                -- compact hash
1582                local hv = hh[v]
1583                if hv then
1584                    return hv
1585                else
1586                    ntt = ntt + 1
1587                    tt[ntt] = t[v]
1588                    hh[v] = ntt
1589                    cc[ntt] = c[v]
1590                    return ntt
1591                end
1592            end
1593        end
1594
1595        local function success(stage,pass)
1596            if nt == 0 then
1597                if trace_loading or trace_packing then
1598                    report_otf("pack quality: nothing to pack")
1599                end
1600                return false
1601            elseif nt >= threshold then
1602                local one  = 0
1603                local two  = 0
1604                local rest = 0
1605                if pass == 1 then
1606                    for k,v in next, c do
1607                        if v == 1 then
1608                            one = one + 1
1609                        elseif v == 2 then
1610                            two = two + 1
1611                        else
1612                            rest = rest + 1
1613                        end
1614                    end
1615                else
1616                    for k,v in next, cc do
1617                        if v > 20 then
1618                            rest = rest + 1
1619                        elseif v > 10 then
1620                            two = two + 1
1621                        else
1622                            one = one + 1
1623                        end
1624                    end
1625                    data.tables = tt
1626                end
1627                if trace_loading or trace_packing then
1628                    report_otf("pack quality: stage %s, pass %s, %s packed, 1-10:%s, 11-20:%s, rest:%s (criterium: %s)",
1629                        stage, pass, one+two+rest, one, two, rest, criterium)
1630                end
1631                return true
1632            else
1633                if trace_loading or trace_packing then
1634                    report_otf("pack quality: stage %s, pass %s, %s packed, aborting pack (threshold: %s)",
1635                        stage, pass, nt, threshold)
1636                end
1637                return false
1638            end
1639        end
1640
1641        local function packers(pass)
1642            if pass == 1 then
1643                return pack_normal, pack_indexed, pack_flat, pack_boolean, pack_mixed, pack_normal_cc
1644            else
1645                return pack_final, pack_final, pack_final, pack_final, pack_final, pack_final_cc
1646            end
1647        end
1648
1649        local resources  = data.resources
1650        local sequences  = resources.sequences
1651        local sublookups = resources.sublookups
1652        local features   = resources.features
1653        local palettes   = resources.colorpalettes
1654        local variable   = resources.variabledata
1655
1656        local chardata     = characters and characters.data
1657        local descriptions = data.descriptions or data.glyphs
1658
1659        if not descriptions then
1660            return
1661        end
1662
1663        for pass=1,2 do
1664
1665            if trace_packing then
1666                report_otf("start packing: stage 1, pass %s",pass)
1667            end
1668
1669            local pack_normal, pack_indexed, pack_flat, pack_boolean, pack_mixed, pack_normal_cc = packers(pass)
1670
1671            for unicode, description in next, descriptions do
1672                local boundingbox = description.boundingbox
1673                if boundingbox then
1674                    description.boundingbox = pack_indexed(boundingbox)
1675                end
1676                local math = description.math
1677                if math then
1678                    local kerns = math.kerns
1679                    if kerns then
1680                        for tag, kern in next, kerns do
1681                            kerns[tag] = pack_normal(kern)
1682                        end
1683                    end
1684                end
1685             -- if palettes then
1686             --     local color = description.color
1687             --     if color then
1688             --         for i=1,#color do
1689             --             color[i] = pack_normal(color[i])
1690             --         end
1691             --     end
1692             -- end
1693            end
1694
1695            local function packthem(sequences)
1696                for i=1,#sequences do
1697                    local sequence = sequences[i]
1698                    local kind     = sequence.type
1699                    local steps    = sequence.steps
1700                    local order    = sequence.order
1701                    local features = sequence.features
1702                    local flags    = sequence.flags
1703                    if steps then
1704                        for i=1,#steps do
1705                            local step = steps[i]
1706                            if kind == "gpos_pair" then
1707                                local c = step.coverage
1708                                if c then
1709                                    if step.format ~= "pair" then
1710                                        for g1, d1 in next, c do
1711                                            c[g1] = pack_normal(d1)
1712                                        end
1713                                    elseif step.shared then
1714                                        -- This branch results from classes. We already share at the reader end. Maybe
1715                                        -- the sharing should be moved there altogether but it becomes kind of messy
1716                                        -- then. Here we're still wasting time because in the second pass we serialize
1717                                        -- and hash. So we compromise. We could merge the two passes ...
1718                                        local shared = { }
1719                                        for g1, d1 in next, c do
1720                                            for g2, d2 in next, d1 do
1721                                                if not shared[d2] then
1722                                                    local f = d2[1] if f and f ~= true then d2[1] = pack_indexed(f) end
1723                                                    local s = d2[2] if s and s ~= true then d2[2] = pack_indexed(s) end
1724                                                    shared[d2] = true
1725                                                end
1726                                            end
1727                                        end
1728                                        if pass == 2 then
1729                                            step.shared = nil -- weird, so dups
1730                                        end
1731                                    else
1732                                        for g1, d1 in next, c do
1733                                            for g2, d2 in next, d1 do
1734                                                local f = d2[1] if f and f ~= true then d2[1] = pack_indexed(f) end
1735                                                local s = d2[2] if s and s ~= true then d2[2] = pack_indexed(s) end
1736                                            end
1737                                        end
1738                                    end
1739                                end
1740                            elseif kind == "gpos_single" then
1741                                local c = step.coverage
1742                                if c then
1743                                    if step.format == "single" then
1744                                        for g1, d1 in next, c do
1745                                            if d1 and d1 ~= true then
1746                                                c[g1] = pack_indexed(d1)
1747                                            end
1748                                        end
1749                                    else
1750                                        step.coverage = pack_normal(c)
1751                                    end
1752                                end
1753                            elseif kind == "gpos_cursive" then
1754                                local c = step.coverage
1755                                if c then
1756                                    for g1, d1 in next, c do
1757                                        local f = d1[2] if f then d1[2] = pack_indexed(f) end
1758                                        local s = d1[3] if s then d1[3] = pack_indexed(s) end
1759                                    end
1760                                end
1761                            elseif kind == "gpos_mark2base" or kind == "gpos_mark2mark" then
1762                                local c = step.baseclasses
1763                                if c then
1764                                    for g1, d1 in next, c do
1765                                        for g2, d2 in next, d1 do
1766                                            d1[g2] = pack_indexed(d2)
1767                                        end
1768                                    end
1769                                end
1770                                local c = step.coverage
1771                                if c then
1772                                    for g1, d1 in next, c do
1773                                        d1[2] = pack_indexed(d1[2])
1774                                    end
1775                                end
1776                            elseif kind == "gpos_mark2ligature" then
1777                                local c = step.baseclasses
1778                                if c then
1779                                    for g1, d1 in next, c do
1780                                        for g2, d2 in next, d1 do
1781                                            for g3, d3 in next, d2 do
1782                                                d2[g3] = pack_indexed(d3)
1783                                            end
1784                                        end
1785                                    end
1786                                end
1787                                local c = step.coverage
1788                                if c then
1789                                    for g1, d1 in next, c do
1790                                        d1[2] = pack_indexed(d1[2])
1791                                    end
1792                                end
1793                            end
1794                            -- if ... chain ...
1795                            local rules = step.rules
1796                            if rules then
1797                                for i=1,#rules do
1798                                    local rule = rules[i]
1799                                    local r = rule.before       if r then for i=1,#r do r[i] = pack_boolean(r[i]) end end
1800                                    local r = rule.after        if r then for i=1,#r do r[i] = pack_boolean(r[i]) end end
1801                                    local r = rule.current      if r then for i=1,#r do r[i] = pack_boolean(r[i]) end end
1802                                 -- local r = rule.lookups      if r then rule.lookups       = pack_mixed  (r)    end
1803                                    local r = rule.replacements if r then rule.replacements  = pack_flat   (r)    end
1804                                end
1805                            end
1806                        end
1807                    end
1808                    if order then
1809                        sequence.order = pack_indexed(order)
1810                    end
1811                    if features then
1812                        for script, feature in next, features do
1813                            features[script] = pack_normal(feature)
1814                        end
1815                    end
1816                    if flags then
1817                        sequence.flags = pack_normal(flags)
1818                    end
1819               end
1820            end
1821
1822            if sequences then
1823                packthem(sequences)
1824            end
1825
1826            if sublookups then
1827                packthem(sublookups)
1828            end
1829
1830            if features then
1831                for k, list in next, features do
1832                    for feature, spec in next, list do
1833                        list[feature] = pack_normal(spec)
1834                    end
1835                end
1836            end
1837
1838            if palettes then
1839                for i=1,#palettes do
1840                    local p = palettes[i]
1841                    for j=1,#p do
1842                        p[j] = pack_indexed(p[j])
1843                    end
1844                end
1845
1846            end
1847
1848            if variable then
1849
1850                -- todo: segments
1851
1852                local instances = variable.instances
1853                if instances then
1854                    for i=1,#instances do
1855                        local v = instances[i].values
1856                        for j=1,#v do
1857                            v[j] = pack_normal(v[j])
1858                        end
1859                    end
1860                end
1861
1862                local function packdeltas(main)
1863                    if main then
1864                        local deltas = main.deltas
1865                        if deltas then
1866                            for i=1,#deltas do
1867                                local di = deltas[i]
1868                                local d  = di.deltas
1869                             -- local r  = di.regions
1870                                for j=1,#d do
1871                                    d[j] = pack_indexed(d[j])
1872                                end
1873                                di.regions = pack_indexed(di.regions)
1874                            end
1875                        end
1876                        local regions = main.regions
1877                        if regions then
1878                            for i=1,#regions do
1879                                local r = regions[i]
1880                                for j=1,#r do
1881                                    r[j] = pack_normal(r[j])
1882                                end
1883                            end
1884                        end
1885                    end
1886                end
1887
1888                packdeltas(variable.global)
1889                packdeltas(variable.horizontal)
1890                packdeltas(variable.vertical)
1891                packdeltas(variable.metrics)
1892
1893            end
1894
1895            if not success(1,pass) then
1896                return
1897            end
1898
1899        end
1900
1901        if nt > 0 then
1902
1903            for pass=1,2 do
1904
1905                if trace_packing then
1906                    report_otf("start packing: stage 2, pass %s",pass)
1907                end
1908
1909                local pack_normal, pack_indexed, pack_flat, pack_boolean, pack_mixed, pack_normal_cc = packers(pass)
1910
1911                for unicode, description in next, descriptions do
1912                    local math = description.math
1913                    if math then
1914                        local kerns = math.kerns
1915                        if kerns then
1916                            math.kerns = pack_normal(kerns)
1917                        end
1918                    end
1919                end
1920
1921                local function packthem(sequences)
1922                    for i=1,#sequences do
1923                        local sequence = sequences[i]
1924                        local kind     = sequence.type
1925                        local steps    = sequence.steps
1926                        local features = sequence.features
1927                        if steps then
1928                            for i=1,#steps do
1929                                local step = steps[i]
1930                                if kind == "gpos_pair" then
1931                                    local c = step.coverage
1932                                    if c then
1933                                        if step.format == "pair" then
1934                                            for g1, d1 in next, c do
1935                                                for g2, d2 in next, d1 do
1936                                                    d1[g2] = pack_normal(d2)
1937                                                end
1938                                            end
1939                                        end
1940                                    end
1941                             -- elseif kind == "gpos_cursive" then
1942                             --     local c = step.coverage -- new
1943                             --     if c then
1944                             --         for g1, d1 in next, c do
1945                             --             c[g1] = pack_normal_cc(d1)
1946                             --         end
1947                             --     end
1948                                elseif kind == "gpos_mark2ligature" then
1949                                    local c = step.baseclasses -- new
1950                                    if c then
1951                                        for g1, d1 in next, c do
1952                                            for g2, d2 in next, d1 do
1953                                                d1[g2] = pack_normal(d2)
1954                                            end
1955                                        end
1956                                    end
1957                                end
1958                                local rules = step.rules
1959                                if rules then
1960                                    for i=1,#rules do
1961                                        local rule = rules[i]
1962                                        local r = rule.before  if r then rule.before  = pack_normal(r) end
1963                                        local r = rule.after   if r then rule.after   = pack_normal(r) end
1964                                        local r = rule.current if r then rule.current = pack_normal(r) end
1965                                    end
1966                                end
1967                            end
1968                        end
1969                        if features then
1970                            sequence.features = pack_normal(features)
1971                        end
1972                   end
1973                end
1974                if sequences then
1975                    packthem(sequences)
1976                end
1977                if sublookups then
1978                    packthem(sublookups)
1979                end
1980                if variable then
1981                    local function unpackdeltas(main)
1982                        if main then
1983                            local regions = main.regions
1984                            if regions then
1985                                main.regions = pack_normal(regions)
1986                            end
1987                        end
1988                    end
1989                    unpackdeltas(variable.global)
1990                    unpackdeltas(variable.horizontal)
1991                    unpackdeltas(variable.vertical)
1992                    unpackdeltas(variable.metrics)
1993                end
1994             -- if not success(2,pass) then
1995             --  -- return
1996             -- end
1997            end
1998
1999            for pass=1,2 do
2000                if trace_packing then
2001                    report_otf("start packing: stage 3, pass %s",pass)
2002                end
2003
2004                local pack_normal, pack_indexed, pack_flat, pack_boolean, pack_mixed, pack_normal_cc = packers(pass)
2005
2006                local function packthem(sequences)
2007                    for i=1,#sequences do
2008                        local sequence = sequences[i]
2009                        local kind     = sequence.type
2010                        local steps    = sequence.steps
2011                        local features = sequence.features
2012                        if steps then
2013                            for i=1,#steps do
2014                                local step = steps[i]
2015                                if kind == "gpos_pair" then
2016                                    local c = step.coverage
2017                                    if c then
2018                                        if step.format == "pair" then
2019                                            for g1, d1 in next, c do
2020                                                c[g1] = pack_normal(d1)
2021                                            end
2022                                        end
2023                                    end
2024                                elseif kind == "gpos_cursive" then
2025                                    local c = step.coverage
2026                                    if c then
2027                                        for g1, d1 in next, c do
2028                                            c[g1] = pack_normal_cc(d1)
2029                                        end
2030                                    end
2031                                end
2032                            end
2033                        end
2034                    end
2035                end
2036
2037                if sequences then
2038                    packthem(sequences)
2039                end
2040                if sublookups then
2041                    packthem(sublookups)
2042                end
2043
2044            end
2045
2046        end
2047
2048    end
2049end
2050
2051local unpacked_mt = {
2052    __index =
2053        function(t,k)
2054            t[k] = false
2055            return k -- next time true
2056        end
2057}
2058
2059function readers.unpack(data)
2060
2061    if data then
2062        local tables = data.tables
2063        if tables then
2064            local resources    = data.resources
2065            local descriptions = data.descriptions or data.glyphs
2066            local sequences    = resources.sequences
2067            local sublookups   = resources.sublookups
2068            local features     = resources.features
2069            local palettes     = resources.colorpalettes
2070            local variable     = resources.variabledata
2071            local unpacked     = { }
2072            setmetatable(unpacked,unpacked_mt)
2073            for unicode, description in next, descriptions do
2074                local tv = tables[description.boundingbox]
2075                if tv then
2076                    description.boundingbox = tv
2077                end
2078                local math = description.math
2079                if math then
2080                    local kerns = math.kerns
2081                    if kerns then
2082                        local tm = tables[kerns]
2083                        if tm then
2084                            math.kerns = tm
2085                            kerns = unpacked[tm]
2086                        end
2087                        if kerns then
2088                            for k, kern in next, kerns do
2089                                local tv = tables[kern]
2090                                if tv then
2091                                    kerns[k] = tv
2092                                end
2093                            end
2094                        end
2095                    end
2096                end
2097             -- if palettes then
2098             --     local color = description.color
2099             --     if color then
2100             --         for i=1,#color do
2101             --             local tv = tables[color[i]]
2102             --             if tv then
2103             --                 color[i] = tv
2104             --             end
2105             --         end
2106             --     end
2107             -- end
2108            end
2109
2110         -- local function expandranges(t,ranges)
2111         --     for i=1,#ranges do
2112         --         local r = ranges[i]
2113         --         for k=r[1],r[2] do
2114         --             t[k] = true
2115         --         end
2116         --     end
2117         -- end
2118
2119            local function unpackthem(sequences)
2120                for i=1,#sequences do
2121                    local sequence  = sequences[i]
2122                    local kind      = sequence.type
2123                    local steps     = sequence.steps
2124                    local order     = sequence.order
2125                    local features  = sequence.features
2126                    local flags     = sequence.flags
2127                    local markclass = sequence.markclass
2128                    if features then
2129                        local tv = tables[features]
2130                        if tv then
2131                            sequence.features = tv
2132                            features = tv
2133                        end
2134                        for script, feature in next, features do
2135                            local tv = tables[feature]
2136                            if tv then
2137                                features[script] = tv
2138                            end
2139                        end
2140                    end
2141                    if steps then
2142                        for i=1,#steps do
2143                            local step = steps[i]
2144                            if kind == "gpos_pair" then
2145                                local c = step.coverage
2146                                if c then
2147                                    if step.format == "pair" then
2148                                        for g1, d1 in next, c do
2149                                            local tv = tables[d1]
2150                                            if tv then
2151                                                c[g1] = tv
2152                                                d1 = tv
2153                                            end
2154                                            for g2, d2 in next, d1 do
2155                                                local tv = tables[d2]
2156                                                if tv then
2157                                                    d1[g2] = tv
2158                                                    d2 = tv
2159                                                end
2160                                                local f = tables[d2[1]] if f then d2[1] = f end
2161                                                local s = tables[d2[2]] if s then d2[2] = s end
2162                                            end
2163                                        end
2164                                    else
2165                                        for g1, d1 in next, c do
2166                                            local tv = tables[d1]
2167                                            if tv then
2168                                                c[g1] = tv
2169                                            end
2170                                        end
2171                                    end
2172                                end
2173                            elseif kind == "gpos_single" then
2174                                local c = step.coverage
2175                                if c then
2176                                    if step.format == "single" then
2177                                        for g1, d1 in next, c do
2178                                            local tv = tables[d1]
2179                                            if tv then
2180                                                c[g1] = tv
2181                                            end
2182                                        end
2183                                    else
2184                                        local tv = tables[c]
2185                                        if tv then
2186                                            step.coverage = tv
2187                                        end
2188                                    end
2189                                end
2190                            elseif kind == "gpos_cursive" then
2191                                local c = step.coverage
2192                                if c then
2193                                    for g1, d1 in next, c do
2194                                        local tv = tables[d1]
2195                                        if tv then
2196                                            d1 = tv
2197                                            c[g1] = d1
2198                                        end
2199                                        local f = tables[d1[2]] if f then d1[2] = f end
2200                                        local s = tables[d1[3]] if s then d1[3] = s end
2201                                    end
2202                                end
2203                            elseif kind == "gpos_mark2base" or kind == "gpos_mark2mark" then
2204                                local c = step.baseclasses
2205                                if c then
2206                                    for g1, d1 in next, c do
2207                                        for g2, d2 in next, d1 do
2208                                            local tv = tables[d2]
2209                                            if tv then
2210                                                d1[g2] = tv
2211                                            end
2212                                        end
2213                                    end
2214                                end
2215                                local c = step.coverage
2216                                if c then
2217                                    for g1, d1 in next, c do
2218                                        local tv = tables[d1[2]]
2219                                        if tv then
2220                                            d1[2] = tv
2221                                        end
2222                                    end
2223                                end
2224                            elseif kind == "gpos_mark2ligature" then
2225                                local c = step.baseclasses
2226                                if c then
2227                                    for g1, d1 in next, c do
2228                                        for g2, d2 in next, d1 do
2229                                            local tv = tables[d2] -- new
2230                                            if tv then
2231                                                d2 = tv
2232                                                d1[g2] = d2
2233                                            end
2234                                            for g3, d3 in next, d2 do
2235                                                local tv = tables[d2[g3]]
2236                                                if tv then
2237                                                    d2[g3] = tv
2238                                                end
2239                                            end
2240                                        end
2241                                    end
2242                                end
2243                                local c = step.coverage
2244                                if c then
2245                                    for g1, d1 in next, c do
2246                                        local tv = tables[d1[2]]
2247                                        if tv then
2248                                            d1[2] = tv
2249                                        end
2250                                    end
2251                                end
2252                            end
2253                            local rules = step.rules
2254                            if rules then
2255                                for i=1,#rules do
2256                                    local rule = rules[i]
2257                                    local before = rule.before
2258                                    if before then
2259                                        local tv = tables[before]
2260                                        if tv then
2261                                            rule.before = tv
2262                                            before = tv
2263                                        end
2264                                        for i=1,#before do
2265                                            local tv = tables[before[i]]
2266                                            if tv then
2267                                                before[i] = tv
2268                                            end
2269                                        end
2270                                     -- for i=1,#before do
2271                                     --     local bi = before[i]
2272                                     --     local tv = tables[bi]
2273                                     --     if tv then
2274                                     --         bi = tv
2275                                     --         before[i] = bi
2276                                     --     end
2277                                     --     local ranges = bi.ranges
2278                                     --     if ranges then
2279                                     --         expandranges(bi,ranges)
2280                                     --     end
2281                                     -- end
2282                                    end
2283                                    local after = rule.after
2284                                    if after then
2285                                        local tv = tables[after]
2286                                        if tv then
2287                                            rule.after = tv
2288                                            after = tv
2289                                        end
2290                                        for i=1,#after do
2291                                            local tv = tables[after[i]]
2292                                            if tv then
2293                                                after[i] = tv
2294                                            end
2295                                        end
2296                                     -- for i=1,#after do
2297                                     --     local ai = after[i]
2298                                     --     local tv = tables[ai]
2299                                     --     if tv then
2300                                     --         ai = tv
2301                                     --         after[i] = ai
2302                                     --     end
2303                                     --     local ranges = ai.ranges
2304                                     --     if ranges then
2305                                     --         expandranges(ai,ranges)
2306                                     --     end
2307                                     -- end
2308                                    end
2309                                    local current = rule.current
2310                                    if current then
2311                                        local tv = tables[current]
2312                                        if tv then
2313                                            rule.current = tv
2314                                            current = tv
2315                                        end
2316                                        for i=1,#current do
2317                                            local tv = tables[current[i]]
2318                                            if tv then
2319                                                current[i] = tv
2320                                            end
2321                                        end
2322                                     -- for i=1,#current do
2323                                     --     local ci = current[i]
2324                                     --     local tv = tables[ci]
2325                                     --     if tv then
2326                                     --         ci = tv
2327                                     --         current[i] = ci
2328                                     --     end
2329                                     --     local ranges = ci.ranges
2330                                     --     if ranges then
2331                                     --         expandranges(ci,ranges)
2332                                     --     end
2333                                     -- end
2334                                    end
2335                                 -- local lookups = rule.lookups
2336                                 -- if lookups then
2337                                 --     local tv = tables[lookups]
2338                                 --     if tv then
2339                                 --         rule.lookups = tv
2340                                 --     end
2341                                 -- end
2342                                    local replacements = rule.replacements
2343                                    if replacements then
2344                                        local tv = tables[replacements]
2345                                        if tv then
2346                                            rule.replacements = tv
2347                                        end
2348                                    end
2349                                end
2350                            end
2351                        end
2352                    end
2353                    if order then
2354                        local tv = tables[order]
2355                        if tv then
2356                            sequence.order = tv
2357                        end
2358                    end
2359                    if flags then
2360                        local tv = tables[flags]
2361                        if tv then
2362                            sequence.flags = tv
2363                        end
2364                    end
2365               end
2366            end
2367
2368            if sequences then
2369                unpackthem(sequences)
2370            end
2371
2372            if sublookups then
2373                unpackthem(sublookups)
2374            end
2375
2376            if features then
2377                for k, list in next, features do
2378                    for feature, spec in next, list do
2379                        local tv = tables[spec]
2380                        if tv then
2381                            list[feature] = tv
2382                        end
2383                    end
2384                end
2385            end
2386
2387            if palettes then
2388                for i=1,#palettes do
2389                    local p = palettes[i]
2390                    for j=1,#p do
2391                        local tv = tables[p[j]]
2392                        if tv then
2393                            p[j] = tv
2394                        end
2395                    end
2396                end
2397            end
2398
2399            if variable then
2400
2401                -- todo: segments
2402
2403                local instances = variable.instances
2404                if instances then
2405                    for i=1,#instances do
2406                        local v = instances[i].values
2407                        for j=1,#v do
2408                            local tv = tables[v[j]]
2409                            if tv then
2410                                v[j] = tv
2411                            end
2412                        end
2413                    end
2414                end
2415
2416                local function unpackdeltas(main)
2417                    if main then
2418                        local deltas = main.deltas
2419                        if deltas then
2420                            for i=1,#deltas do
2421                                local di = deltas[i]
2422                                local d  = di.deltas
2423                                local r  = di.regions
2424                                for j=1,#d do
2425                                    local tv = tables[d[j]]
2426                                    if tv then
2427                                        d[j] = tv
2428                                    end
2429                                end
2430                                local tv = di.regions
2431                                if tv then
2432                                    di.regions = tv
2433                                end
2434                            end
2435                        end
2436                        local regions = main.regions
2437                        if regions then
2438                            local tv = tables[regions]
2439                            if tv then
2440                                main.regions = tv
2441                                regions = tv
2442                            end
2443                            for i=1,#regions do
2444                                local r = regions[i]
2445                                for j=1,#r do
2446                                    local tv = tables[r[j]]
2447                                    if tv then
2448                                        r[j] = tv
2449                                    end
2450                                end
2451                            end
2452                        end
2453                    end
2454                end
2455
2456                unpackdeltas(variable.global)
2457                unpackdeltas(variable.horizontal)
2458                unpackdeltas(variable.vertical)
2459                unpackdeltas(variable.metrics)
2460
2461            end
2462
2463            data.tables = nil
2464        end
2465    end
2466end
2467
2468local mt = {
2469    __index = function(t,k) -- maybe set it
2470        if k == "height" then
2471            local ht = t.boundingbox[4]
2472            return ht < 0 and 0 or ht
2473        elseif k == "depth" then
2474            local dp = -t.boundingbox[2]
2475            return dp < 0 and 0 or dp
2476        elseif k == "width" then
2477            return 0
2478        elseif k == "name" then -- or maybe uni*
2479            return forcenotdef and ".notdef"
2480        end
2481    end
2482}
2483
2484local function sameformat(sequence,steps,first,nofsteps,kind)
2485    return true
2486end
2487
2488local function mergesteps_1(lookup,strict)
2489    local steps    = lookup.steps
2490    local nofsteps = lookup.nofsteps
2491    local first    = steps[1]
2492    if strict then
2493        local f = first.format
2494        for i=2,nofsteps do
2495            if steps[i].format ~= f then
2496                if trace_optimizations then
2497                    report_optimizations("not merging %a steps of %a lookup %a, different formats",nofsteps,lookup.type,lookup.name)
2498                end
2499                return 0
2500            end
2501        end
2502    end
2503    if trace_optimizations then
2504        report_optimizations("merging %a steps of %a lookup %a",nofsteps,lookup.type,lookup.name)
2505    end
2506    local target = first.coverage
2507    for i=2,nofsteps do
2508        local c = steps[i].coverage
2509        if c then
2510            for k, v in next, c do
2511                if not target[k] then
2512                    target[k] = v
2513                end
2514            end
2515        end
2516    end
2517    lookup.nofsteps = 1
2518    lookup.merged   = true
2519    lookup.steps    = { first }
2520    return nofsteps - 1
2521end
2522
2523local function mergesteps_2(lookup) -- pairs
2524    -- this can be tricky as we can have a match on a mark with no marks skip flag
2525    -- in which case with multiple steps a hit can prevent a next step while in the
2526    -- merged case we can hit differently (a messy font then anyway)
2527    local steps    = lookup.steps
2528    local nofsteps = lookup.nofsteps
2529    local first    = steps[1]
2530    if strict then
2531        local f = first.format
2532        for i=2,nofsteps do
2533            if steps[i].format ~= f then
2534                if trace_optimizations then
2535                    report_optimizations("not merging %a steps of %a lookup %a, different formats",nofsteps,lookup.type,lookup.name)
2536                end
2537                return 0
2538            end
2539        end
2540    end
2541    if trace_optimizations then
2542        report_optimizations("merging %a steps of %a lookup %a",nofsteps,lookup.type,lookup.name)
2543    end
2544    local target = first.coverage
2545    for i=2,nofsteps do
2546        local c = steps[i].coverage
2547        if c then
2548            for k, v in next, c do
2549                local tk = target[k]
2550                if tk then
2551                    for kk, vv in next, v do
2552                        if tk[kk] == nil then
2553                            tk[kk] = vv
2554                        end
2555                    end
2556                else
2557                    target[k] = v
2558                end
2559            end
2560        end
2561    end
2562    lookup.nofsteps = 1
2563    lookup.merged   = true
2564    lookup.steps    = { first }
2565    return nofsteps - 1
2566end
2567
2568-- we could have a coverage[first][second] = { } already here (because eventually
2569-- we also have something like that after loading)
2570
2571local function mergesteps_3(lookup,strict) -- marks
2572    local steps    = lookup.steps
2573    local nofsteps = lookup.nofsteps
2574    if trace_optimizations then
2575        report_optimizations("merging %a steps of %a lookup %a",nofsteps,lookup.type,lookup.name)
2576    end
2577    -- check first
2578    local coverage = { }
2579    for i=1,nofsteps do
2580        local c = steps[i].coverage
2581        if c then
2582            for k, v in next, c do
2583                local tk = coverage[k] -- { class, { x, y } }
2584                if tk then
2585                    if trace_optimizations then
2586                        report_optimizations("quitting merge due to multiple checks")
2587                    end
2588                    return nofsteps
2589                else
2590                    coverage[k] = v
2591                end
2592            end
2593        end
2594    end
2595    -- merge indeed
2596    local first       = steps[1]
2597    local baseclasses = { } -- let's assume sparse step.baseclasses
2598    for i=1,nofsteps do
2599        local offset = i*10  -- we assume max 10 classes per step
2600        local step   = steps[i]
2601        for k, v in sortedhash(step.baseclasses) do
2602            baseclasses[offset+k] = v
2603        end
2604        for k, v in next, step.coverage do
2605            v[1] = offset + v[1]
2606        end
2607    end
2608    first.baseclasses = baseclasses
2609    first.coverage    = coverage
2610    lookup.nofsteps   = 1
2611    lookup.merged     = true
2612    lookup.steps      = { first }
2613    return nofsteps - 1
2614end
2615
2616local function nested(old,new)
2617    for k, v in next, old do
2618        if k == "ligature" then
2619            if not new.ligature then
2620                new.ligature = v
2621            end
2622        else
2623            local n = new[k]
2624            if n then
2625                nested(v,n)
2626            else
2627                new[k] = v
2628            end
2629        end
2630    end
2631end
2632
2633local function mergesteps_4(lookup) -- ligatures
2634    local steps    = lookup.steps
2635    local nofsteps = lookup.nofsteps
2636    local first    = steps[1]
2637    if trace_optimizations then
2638        report_optimizations("merging %a steps of %a lookup %a",nofsteps,lookup.type,lookup.name)
2639    end
2640    local target = first.coverage
2641    for i=2,nofsteps do
2642        local c = steps[i].coverage
2643        if c then
2644            for k, v in next, c do
2645                local tk = target[k]
2646                if tk then
2647                    nested(v,tk)
2648                else
2649                    target[k] = v
2650                end
2651            end
2652        end
2653    end
2654    lookup.nofsteps = 1
2655    lookup.steps = { first }
2656    return nofsteps - 1
2657end
2658
2659-- so we assume only one cursive entry and exit and even then the first one seems
2660-- to win anyway: no exit or entry quite the lookup match and then we take the
2661-- next step; this means that we can as well merge them
2662
2663local function mergesteps_5(lookup) -- cursive
2664    local steps    = lookup.steps
2665    local nofsteps = lookup.nofsteps
2666    local first    = steps[1]
2667    if trace_optimizations then
2668        report_optimizations("merging %a steps of %a lookup %a",nofsteps,lookup.type,lookup.name)
2669    end
2670    local target = first.coverage
2671    local hash   = nil
2672    for k, v in next, target do
2673        hash = v[1]
2674        break
2675    end
2676    for i=2,nofsteps do
2677        local c = steps[i].coverage
2678        if c then
2679            for k, v in next, c do
2680                local tk = target[k]
2681                if tk then
2682                    if not tk[2] then
2683                        tk[2] = v[2]
2684                    end
2685                    if not tk[3] then
2686                        tk[3] = v[3]
2687                    end
2688                else
2689                    target[k] = v
2690                    v[1] = hash
2691                end
2692            end
2693        end
2694    end
2695    lookup.nofsteps = 1
2696    lookup.merged   = true
2697    lookup.steps    = { first }
2698    return nofsteps - 1
2699end
2700
2701local function checkkerns(lookup)
2702    local steps    = lookup.steps
2703    local nofsteps = lookup.nofsteps
2704    local kerned   = 0
2705    for i=1,nofsteps do
2706        local step = steps[i]
2707        if step.format == "pair" then
2708            local coverage = step.coverage
2709            local kerns    = true
2710            for g1, d1 in next, coverage do
2711                if d1 == true then
2712                    -- all zero
2713                elseif not d1 then
2714                    -- null
2715                elseif d1[1] ~= 0 or d1[2] ~= 0 or d1[4] ~= 0 then
2716                    kerns = false
2717                    break
2718                end
2719            end
2720            if kerns then
2721                if trace_optimizations then
2722                    report_optimizations("turning pairs of step %a of %a lookup %a into kerns",i,lookup.type,lookup.name)
2723                end
2724                local c = { }
2725                for g1, d1 in next, coverage do
2726                    if d1 and d1 ~= true then
2727                        c[g1] = d1[3]
2728                    end
2729                end
2730                step.coverage = c
2731                step.format = "move"
2732                kerned = kerned + 1
2733            end
2734        end
2735    end
2736    return kerned
2737end
2738
2739-- There are several options to optimize but we have this somewhat fuzzy aspect of
2740-- advancing (depending on the second of a pair) so we need to retain that information.
2741--
2742-- We can have:
2743--
2744--     true, nil|false
2745--
2746-- which effectively means: nothing to be done and advance to next (so not next of
2747-- next) and because coverage should be not overlapping we can wipe these. However,
2748-- checking for (true,nil) (false,nil) and omitting them doesn't gain much.
2749
2750-- Because we pack we cannot mix tables and numbers so we can only turn a whole set in
2751-- format kern instead of pair.
2752
2753local strip_pairs         = true
2754
2755local compact_pairs       = true
2756local compact_singles     = true
2757
2758local merge_pairs         = true
2759local merge_singles       = true
2760local merge_substitutions = true
2761local merge_alternates    = true
2762local merge_multiples     = true
2763local merge_ligatures     = true
2764local merge_cursives      = true
2765local merge_marks         = true
2766
2767directives.register("otf.strip.pairs",         function(v) strip_pairs     = v end)
2768
2769directives.register("otf.compact.pairs",       function(v) compact_pairs   = v end)
2770directives.register("otf.compact.singles",     function(v) compact_singles = v end)
2771
2772directives.register("otf.merge.pairs",         function(v) merge_pairs         = v end)
2773directives.register("otf.merge.singles",       function(v) merge_singles       = v end)
2774directives.register("otf.merge.substitutions", function(v) merge_substitutions = v end)
2775directives.register("otf.merge.alternates",    function(v) merge_alternates    = v end)
2776directives.register("otf.merge.multiples",     function(v) merge_multiples     = v end)
2777directives.register("otf.merge.ligatures",     function(v) merge_ligatures     = v end)
2778directives.register("otf.merge.cursives",      function(v) merge_cursives      = v end)
2779directives.register("otf.merge.marks",         function(v) merge_marks         = v end)
2780
2781local function checkpairs(lookup)
2782    local steps    = lookup.steps
2783    local nofsteps = lookup.nofsteps
2784    local kerned   = 0
2785
2786    local function onlykerns(step)
2787        local coverage = step.coverage
2788        for g1, d1 in next, coverage do
2789            for g2, d2 in next, d1 do
2790                if d2[2] then
2791                    --- true or { a, b, c, d }
2792                    return false
2793                else
2794                    local v = d2[1]
2795                    if v == true then
2796                        -- all zero
2797                    elseif v and (v[1] ~= 0 or v[2] ~= 0 or v[4] ~= 0) then
2798                        -- complex kerns
2799                        return false
2800                    end
2801                end
2802            end
2803        end
2804        return coverage
2805    end
2806
2807    for i=1,nofsteps do
2808        local step = steps[i]
2809        if step.format == "pair" then
2810            local coverage = onlykerns(step)
2811            if coverage then
2812                if trace_optimizations then
2813                    report_optimizations("turning pairs of step %a of %a lookup %a into kerns",i,lookup.type,lookup.name)
2814                end
2815                for g1, d1 in next, coverage do
2816                    local d = { }
2817                    for g2, d2 in next, d1 do
2818                        local v = d2[1]
2819                        if v == true then
2820                            -- ignore    -- d1[g2] = nil
2821                        elseif v then
2822                            d[g2] = v[3] -- d1[g2] = v[3]
2823                        end
2824                    end
2825                    coverage[g1] = d
2826                end
2827                step.format = "move"
2828                kerned = kerned + 1
2829            end
2830        end
2831    end
2832    return kerned
2833end
2834
2835local function strippairs(lookup)
2836    local steps    = lookup.steps
2837    local nofsteps = lookup.nofsteps
2838    local stripped = 0
2839
2840    for i=1,nofsteps do
2841        local step = steps[i]
2842        if step.format == "pair" then
2843            local coverage = step.coverage
2844            for g1, d1 in next, coverage do
2845                for g2, d2 in next, d1 do
2846                    if d2[2] then
2847                        --- true or { a, b, c, d }
2848                 -- else
2849                 --     local v = d2[1]
2850                 --     if v == true then
2851                 --         d1[g2] = nil
2852                 --         stripped = stripped + 1
2853                 --     elseif v and (v[1] == 0 and v[2] == 0 and v[4] == 0) then -- vkrn can have v[3] ~= 0
2854                 --         d1[g2] = nil
2855                 --         stripped = stripped + 1
2856                 --     end
2857                 -- end
2858                    elseif d2[1] == true then
2859                        d1[g2] = nil
2860                        stripped = stripped + 1
2861                    end
2862                end
2863            end
2864        end
2865    end
2866    return stripped
2867end
2868
2869function readers.compact(data)
2870    if not data or data.compacted then
2871        return
2872    else
2873        data.compacted = true
2874    end
2875    local resources = data.resources
2876    local stripped  = 0
2877    local merged    = 0
2878    local kerned    = 0
2879    local allsteps  = 0
2880    local function compact(what)
2881        local lookups = resources[what]
2882        if lookups then
2883            for i=1,#lookups do
2884                local lookup   = lookups[i]
2885                local nofsteps = lookup.nofsteps
2886                local kind     = lookup.type
2887                allsteps = allsteps + nofsteps
2888                if nofsteps > 1 then
2889                    local merg = merged
2890                    if kind == "gsub_single" then
2891                        if merge_substitutions then
2892                            merged = merged + mergesteps_1(lookup)
2893                        end
2894                    elseif kind == "gsub_alternate" then
2895                        if merge_alternates then
2896                            merged = merged + mergesteps_1(lookup)
2897                        end
2898                    elseif kind == "gsub_multiple" then
2899                        if merge_multiples then
2900                            merged = merged + mergesteps_1(lookup)
2901                        end
2902                    elseif kind == "gsub_ligature" then
2903                        if merge_ligatures then
2904                            merged = merged + mergesteps_4(lookup)
2905                        end
2906                    elseif kind == "gpos_single" then
2907                        -- maybe also strip zeros here
2908                        if merge_singles then
2909                            merged = merged + mergesteps_1(lookup,true)
2910                        end
2911                        if compact_singles then
2912                            kerned = kerned + checkkerns(lookup)
2913                        end
2914                    elseif kind == "gpos_pair" then
2915                        if strip_pairs then
2916                            stripped = stripped + strippairs(lookup) -- noto cjk from 24M -> 8 M
2917                        end
2918                        if merge_pairs then
2919                            merged = merged + mergesteps_2(lookup)
2920                        end
2921                        if compact_pairs then
2922                            kerned = kerned + checkpairs(lookup)
2923                        end
2924                    elseif kind == "gpos_cursive" then
2925                        if merge_cursives then
2926                            merged = merged + mergesteps_5(lookup)
2927                        end
2928                    elseif kind == "gpos_mark2mark" or kind == "gpos_mark2base" or kind == "gpos_mark2ligature" then
2929                        if merge_marks then
2930                            merged = merged + mergesteps_3(lookup)
2931                        end
2932                    end
2933                    if merg ~= merged then
2934                        lookup.merged = true
2935                    end
2936                elseif nofsteps == 1 then
2937                    local kern = kerned
2938                    if kind == "gpos_single" then
2939                        if compact_singles then
2940                            kerned = kerned + checkkerns(lookup)
2941                        end
2942                    elseif kind == "gpos_pair" then
2943                        if compact_pairs then
2944                            kerned = kerned + checkpairs(lookup)
2945                        end
2946                    end
2947                    if kern ~= kerned then
2948                     -- lookup.kerned = true
2949                    end
2950                end
2951            end
2952        elseif trace_optimizations then
2953            report_optimizations("no lookups in %a",what)
2954        end
2955    end
2956    compact("sequences")
2957    compact("sublookups")
2958    if trace_optimizations then
2959        if stripped > 0 then
2960            report_optimizations("%i zero positions stripped before merging",stripped)
2961        end
2962        if merged > 0 then
2963            report_optimizations("%i steps of %i removed due to merging",merged,allsteps)
2964        end
2965        if kerned > 0 then
2966            report_optimizations("%i steps of %i steps turned from pairs into kerns",kerned,allsteps)
2967        end
2968    end
2969end
2970
2971if CONTEXTLMTXMODE and CONTEXTLMTXMODE > 0 then
2972
2973    local done = 0
2974
2975    local function condense_1(k,v,t)
2976        if type(v) == "table" then
2977            local u = false
2978            local l = false
2979            for k, v in next, v do
2980                if k == "ligature" then
2981                    l = v
2982                    if u then
2983                        break
2984                    end
2985                elseif u then
2986                    break
2987                else
2988                    u = true
2989                end
2990            end
2991            if l and not u then
2992                t[k] = l
2993                done = done + 1
2994            end
2995            if u then
2996                for k, vv in next, v do
2997                    if k ~= "ligature" then
2998                        condense_1(k,vv,v)
2999                    end
3000                end
3001            end
3002        end
3003    end
3004
3005    local function condensesteps_1(lookup)
3006        done = 0
3007        if lookup.type == "gsub_ligature" then
3008            local steps = lookup.steps
3009            if steps then
3010                for i=1,#steps do
3011                    local step     = steps[i]
3012                    local coverage = step.coverage
3013                    if coverage then
3014                        for k, v in next, coverage do
3015                            if condense_1(k,v,coverage) then
3016                                coverage[k] = v.ligature
3017                                done = done + 1
3018                            end
3019                        end
3020                    end
3021                end
3022            end
3023        end
3024        return done
3025    end
3026
3027    function readers.condense(data)
3028        if not data or data.condensed then
3029            return
3030        else
3031            data.condensed = true
3032        end
3033        local resources = data.resources
3034        local condensed = 0
3035        local function condense(what)
3036            local lookups = resources[what]
3037            if lookups then
3038                for i=1,#lookups do
3039                    condensed = condensed + condensesteps_1(lookups[i])
3040                end
3041            elseif trace_optimizations then
3042                report_optimizations("no lookups in %a",what)
3043            end
3044        end
3045        condense("sequences")
3046        condense("sublookups")
3047        if trace_optimizations then
3048            if condensed > 0 then
3049                report_optimizations("%i ligatures condensed",condensed)
3050            end
3051        end
3052    end
3053
3054end
3055
3056local function mergesteps(t,k)
3057    if k == "merged" then
3058        local merged = { }
3059        for i=1,#t do
3060            local step     = t[i]
3061            local coverage = step.coverage
3062            for k in next, coverage do
3063                local m = merged[k]
3064                if m then
3065                    m[2] = i
3066                 -- m[#m+1] = step
3067                else
3068                    merged[k] = { i, i }
3069                 -- merged[k] = { step }
3070                end
3071            end
3072        end
3073        t.merged = merged
3074        return merged
3075    end
3076end
3077
3078local function checkmerge(sequence)
3079    local steps = sequence.steps
3080    if steps then
3081        setmetatableindex(steps,mergesteps)
3082    end
3083end
3084
3085local function checkflags(sequence,resources)
3086    if not sequence.skiphash then
3087        local flags = sequence.flags
3088        if flags then
3089            local skipmark     = flags[1]
3090            local skipligature = flags[2]
3091            local skipbase     = flags[3]
3092            local markclass    = sequence.markclass
3093            local skipsome     = skipmark or skipligature or skipbase or markclass or false
3094            if skipsome then
3095                sequence.skiphash = setmetatableindex(function(t,k)
3096                    local c = resources.classes[k] -- delayed table
3097                    local v = c == skipmark
3098                           or (markclass and c == "mark" and not markclass[k])
3099                           or c == skipligature
3100                           or c == skipbase
3101                           or false
3102                    t[k] = v
3103                    return v
3104                end)
3105            else
3106                sequence.skiphash = false
3107            end
3108        else
3109            sequence.skiphash = false
3110        end
3111    end
3112end
3113
3114local function checksteps(sequence)
3115    local steps = sequence.steps
3116    if steps then
3117        for i=1,#steps do
3118            steps[i].index = i
3119        end
3120    end
3121end
3122
3123if fonts.helpers then
3124    fonts.helpers.checkmerge = checkmerge
3125    fonts.helpers.checkflags = checkflags
3126    fonts.helpers.checksteps = checksteps -- has to happen last
3127end
3128
3129function readers.expand(data)
3130    if not data or data.expanded then
3131        return
3132    else
3133        data.expanded = true
3134    end
3135    local resources    = data.resources
3136    local sublookups   = resources.sublookups
3137    local sequences    = resources.sequences -- were one level up
3138    local markclasses  = resources.markclasses
3139    local descriptions = data.descriptions
3140    if descriptions then
3141        local defaultwidth  = resources.defaultwidth  or 0
3142        local defaultheight = resources.defaultheight or 0
3143        local defaultdepth  = resources.defaultdepth  or 0
3144        local basename      = trace_markwidth and file.basename(resources.filename)
3145        for u, d in next, descriptions do
3146            local bb = d.boundingbox
3147            local wd = d.width
3148            if not wd then
3149                -- or bb?
3150                d.width = defaultwidth
3151            elseif trace_markwidth and wd ~= 0 and d.class == "mark" then
3152                report_markwidth("mark %a with width %b found in %a",d.name or "<noname>",wd,basename)
3153            end
3154            if bb then
3155                local ht =  bb[4]
3156                local dp = -bb[2]
3157                if ht == 0 or ht < 0 then
3158                    -- not set
3159                else
3160                    d.height = ht
3161                end
3162                if dp == 0 or dp < 0 then
3163                    -- not set
3164                else
3165                    d.depth  = dp
3166                end
3167            end
3168        end
3169    end
3170
3171    -- using a merged combined hash as first test saves some 30% on ebgaramond and
3172    -- about 15% on arabtype .. then moving the a test also saves a bit (even when
3173    -- often a is not set at all so that one is a bit debatable
3174
3175    local function expandlookups(sequences,whatever)
3176        if sequences then
3177            -- we also need to do sublookups
3178            for i=1,#sequences do
3179                local sequence = sequences[i]
3180                local steps    = sequence.steps
3181                if steps then
3182                    local nofsteps = sequence.nofsteps
3183
3184                    local kind = sequence.type
3185                    local markclass = sequence.markclass
3186                    if markclass then
3187                        if not markclasses then
3188                            report_warning("missing markclasses")
3189                            sequence.markclass = false
3190                        else
3191                            sequence.markclass = markclasses[markclass]
3192                        end
3193                    end
3194
3195                    for i=1,nofsteps do
3196                        local step = steps[i]
3197                        local baseclasses = step.baseclasses
3198                        if baseclasses then
3199                            local coverage = step.coverage
3200                            for k, v in next, coverage do
3201                                v[1] = baseclasses[v[1]] -- slot 1 is a placeholder
3202                            end
3203                        elseif kind == "gpos_cursive" then
3204                            local coverage = step.coverage
3205                            for k, v in next, coverage do
3206                                v[1] = coverage -- slot 1 is a placeholder
3207                            end
3208                        end
3209                        local rules = step.rules
3210                        if rules then
3211                            local rulehash   = { n = 0 } -- is contexts in font-ots
3212                            local rulesize   = 0
3213                            local coverage   = { }
3214                            local lookuptype = sequence.type
3215                            local nofrules   = #rules
3216                            step.coverage    = coverage -- combined hits
3217                            for currentrule=1,nofrules do
3218                                local rule         = rules[currentrule]
3219                                local current      = rule.current
3220                                local before       = rule.before
3221                                local after        = rule.after
3222                                local replacements = rule.replacements or false
3223                                local sequence     = { }
3224                                local nofsequences = 0
3225                                if before then
3226                                    for n=1,#before do
3227                                        nofsequences = nofsequences + 1
3228                                        sequence[nofsequences] = before[n]
3229                                    end
3230                                end
3231                                local start = nofsequences + 1
3232                                for n=1,#current do
3233                                    nofsequences = nofsequences + 1
3234                                    sequence[nofsequences] = current[n]
3235                                end
3236                                local stop = nofsequences
3237                                if after then
3238                                    for n=1,#after do
3239                                        nofsequences = nofsequences + 1
3240                                        sequence[nofsequences] = after[n]
3241                                    end
3242                                end
3243                                local lookups = rule.lookups or false
3244                                local subtype = nil
3245                                if lookups then
3246                                    for i=1,#lookups do
3247                                        local lookups = lookups[i]
3248                                        if lookups then
3249                                            for k, v in next, lookups do -- actually this one is indexed
3250                                                local lookup = sublookups[v]
3251if not lookup and whatever then
3252    lookup = whatever[v]
3253end
3254                                                if lookup then
3255                                                    lookups[k] = lookup
3256                                                    if not subtype then
3257                                                        subtype = lookup.type
3258                                                    end
3259                                                else
3260                                                    -- already expanded
3261                                                end
3262                                            end
3263                                        end
3264                                    end
3265                                end
3266                                if sequence[1] then -- we merge coverage into one
3267                                    sequence.n = #sequence -- tiny speedup
3268                                    local ruledata = {
3269                                        currentrule,  -- 1 -- original rule number, only use this for tracing!
3270                                        lookuptype,   -- 2
3271                                        sequence,     -- 3
3272                                        start,        -- 4
3273                                        stop,         -- 5
3274                                        lookups,      -- 6 (6/7 also signal of what to do)
3275                                        replacements, -- 7
3276                                        subtype,      -- 8
3277                                    }
3278                                    --
3279                                    -- possible optimization: per [unic] a rulehash, but beware:
3280                                    -- contexts have unique coverage and chains can have multiple
3281                                    -- hits (rules) per coverage entry
3282                                    --
3283                                    -- so: we can combine multiple steps as well as multiple rules
3284                                    -- but that takes careful checking, in which case we can go the
3285                                    -- step list approach and turn contexts into steps .. in fact,
3286                                    -- if we turn multiple contexts into steps we're already ok as
3287                                    -- steps gets a coverage hash by metatable
3288                                    --
3289                                    rulesize = rulesize + 1
3290                                    rulehash[rulesize] = ruledata
3291                                    rulehash.n = rulesize -- tiny speedup
3292                                    --
3293                                    if true then -- nofrules > 1
3294
3295                                        for unic in next, sequence[start] do
3296                                            local cu = coverage[unic]
3297                                            if cu then
3298                                                local n = #cu+1
3299                                                cu[n] = ruledata
3300                                                cu.n = n
3301                                            else
3302                                                coverage[unic] = { ruledata, n = 1 }
3303                                            end
3304                                        end
3305
3306                                    else
3307
3308                                        for unic in next, sequence[start] do
3309                                            local cu = coverage[unic]
3310                                            if cu then
3311                                                -- we can have a contextchains with many matches which we
3312                                                -- can actually optimize
3313                                            else
3314                                                coverage[unic] = rulehash
3315                                            end
3316                                        end
3317
3318                                    end
3319                                end
3320                            end
3321                        end
3322                    end
3323
3324                    checkmerge(sequence)
3325                    checkflags(sequence,resources)
3326                    checksteps(sequence)
3327
3328                end
3329            end
3330        end
3331    end
3332
3333    expandlookups(sequences)
3334    expandlookups(sublookups,sequences)
3335end
3336