font-oup.lmt /size: 126 Kb    last modification: 2025-02-21 11:03
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
742
743    for k, v in next, descriptions do
744        local math = v.math
745        if math then
746            local variants = math.variants
747            local parts    = math.parts
748            local unicode  = v.unicode
749            if variants then
750                if unicode then
751                    for i=1,#variants do
752                        local v = descriptions[variants[i]]
753                        if not v then
754                            -- error
755                        elseif v.unicode then
756                            -- error
757                        else
758                            v.unicode = unicode
759                        end
760                    end
761                end
762            end
763            if parts then
764                parts[#parts//2+1].unicode = unicode
765            end
766        end
767    end
768
769end
770
771local firstprivate <const> = fonts.privateoffsets and fonts.privateoffsets.textbase or 0xF0000
772local puafirst     <const> = 0xE000
773local pualast      <const> = 0xF8FF
774
775local function unifymissing(fontdata)
776    if not fonts.mappings then
777        require("font-map")
778        require("font-agl")
779    end
780    local unicodes     = { }
781    local resources    = fontdata.resources
782    resources.unicodes = unicodes
783    for unicode, d in next, fontdata.descriptions do
784        if unicode < privateoffset then
785            if unicode >= puafirst and unicode <= pualast then
786                -- report_unicodes("resolving private unicode %U",unicode)
787            else
788                local name = d.name
789                if name then
790                    unicodes[name] = unicode
791                end
792            end
793        else
794            -- report_unicodes("resolving private unicode %U",unicode)
795        end
796    end
797    fonts.mappings.addtounicode(fontdata,fontdata.filename,checklookups)
798    resources.unicodes = nil
799end
800
801local function unifyglyphs(fontdata,usenames)
802    local private      = fontdata.private or privateoffset
803    local glyphs       = fontdata.glyphs
804    local indices      = { }
805    local descriptions = { }
806    local names        = usenames and { }
807    local resources    = fontdata.resources
808    local zero         = glyphs[0]
809    local zerocode     = zero.unicode
810    local nofglyphs    = #glyphs
811    if not zerocode then
812        zerocode       = private
813        zero.unicode   = zerocode
814        private        = private + 1
815    end
816    descriptions[zerocode] = zero
817    if names then
818        local name  = glyphs[0].name or f_private(zerocode)
819        indices[0]  = name
820        names[name] = zerocode
821    else
822        indices[0] = zerocode
823    end
824    --
825    if names then
826        -- seldom uses, we don't issue message ... this branch might even go away
827        for index=1,nofglyphs do
828            local glyph   = glyphs[index]
829            local unicode = glyph.unicode -- this is the primary one
830            if not unicode then
831                unicode        = private
832                local name     = glyph.name or f_private(unicode)
833                indices[index] = name
834                names[name]    = unicode
835                private        = private + 1
836            elseif unicode >= firstprivate then
837                unicode        = private
838                local name     = glyph.name or f_private(unicode)
839                indices[index] = name
840                names[name]    = unicode
841                private        = private + 1
842            elseif unicode >= puafirst and unicode <= pualast then
843                local name     = glyph.name or f_private(unicode)
844                indices[index] = name
845                names[name]    = unicode
846            elseif descriptions[unicode] then
847                unicode        = private
848                local name     = glyph.name or f_private(unicode)
849                indices[index] = name
850                names[name]    = unicode
851                private        = private + 1
852            else
853                local name     = glyph.name or f_unicode(unicode)
854                indices[index] = name
855                names[name]    = unicode
856            end
857            descriptions[unicode] = glyph
858        end
859    elseif trace_unicodes then
860        for index=1,nofglyphs do
861            local glyph   = glyphs[index]
862            local unicode = glyph.unicode -- this is the primary one
863            if not unicode then
864                unicode        = private
865                indices[index] = unicode
866                private        = private + 1
867            elseif unicode >= firstprivate then
868                local name = glyph.name
869                if name then
870                    report_unicodes("moving glyph %a indexed %05X from private %U to %U ",name,index,unicode,private)
871                else
872                    report_unicodes("moving glyph indexed %05X from private %U to %U ",index,unicode,private)
873                end
874                unicode        = private
875                indices[index] = unicode
876                private        = private + 1
877            elseif unicode >= puafirst and unicode <= pualast then
878                local name = glyph.name
879                if name then
880                    report_unicodes("keeping private unicode %U for glyph %a indexed %05X",unicode,name,index)
881                else
882                    report_unicodes("keeping private unicode %U for glyph indexed %05X",unicode,index)
883                end
884                indices[index] = unicode
885            elseif descriptions[unicode] then
886                local name = glyph.name
887                if name then
888                    report_unicodes("assigning duplicate unicode %U to %U for glyph %a indexed %05X ",unicode,private,name,index)
889                else
890                    report_unicodes("assigning duplicate unicode %U to %U for glyph indexed %05X ",unicode,private,index)
891                end
892                unicode        = private
893                indices[index] = unicode
894                private        = private + 1
895            else
896                indices[index] = unicode
897            end
898            descriptions[unicode] = glyph
899        end
900    else
901        for index=1,nofglyphs do
902            local glyph   = glyphs[index]
903            local unicode = glyph.unicode -- this is the primary one
904            if not unicode then
905                unicode        = private
906                indices[index] = unicode
907                private        = private + 1
908            elseif unicode >= firstprivate then
909                local name = glyph.name
910                unicode        = private
911                indices[index] = unicode
912                private        = private + 1
913            elseif unicode >= puafirst and unicode <= pualast then
914                local name = glyph.name
915                indices[index] = unicode
916            elseif descriptions[unicode] then
917                local name = glyph.name
918                unicode        = private
919                indices[index] = unicode
920                private        = private + 1
921            else
922                indices[index] = unicode
923            end
924            descriptions[unicode] = glyph
925        end
926    end
927    --
928    if LUATEXENGINE == "luametatex" then
929        for index=1,nofglyphs do
930            local math = glyphs[index].math
931            if math then
932                local list = math.parts
933                if list then
934                    for i=1,#list do local l = list[i] l.glyph = indices[l.glyph] end
935                end
936                local list = math.variants
937                if list then
938                    for i=1,#list do list[i] = indices[list[i]] end
939                end
940            end
941        end
942    else
943        for index=1,nofglyphs do
944            local math = glyphs[index].math
945            if math then
946                local list = math.vparts
947                if list then
948                    for i=1,#list do local l = list[i] l.glyph = indices[l.glyph] end
949                end
950                local list = math.hparts
951                if list then
952                    for i=1,#list do local l = list[i] l.glyph = indices[l.glyph] end
953                end
954                local list = math.vvariants
955                if list then
956                 -- for i=1,#list do local l = list[i] l.glyph = indices[l.glyph] end
957                    for i=1,#list do list[i] = indices[list[i]] end
958                end
959                local list = math.hvariants
960                if list then
961                 -- for i=1,#list do local l = list[i] l.glyph = indices[l.glyph] end
962                    for i=1,#list do list[i] = indices[list[i]] end
963                end
964            end
965        end
966    end
967    --
968    local colorpalettes = resources.colorpalettes
969    if colorpalettes then
970        for index=1,nofglyphs do
971            local colors = glyphs[index].colors
972            if colors then
973                for i=1,#colors do
974                    local c = colors[i]
975                    if c then -- safeguard
976                        c.slot = indices[c.slot]
977                    end
978                end
979            end
980        end
981    end
982    --
983    fontdata.private      = private
984    fontdata.glyphs       = nil
985    fontdata.names        = names
986    fontdata.descriptions = descriptions
987    fontdata.hashmethod   = hashmethod
988    fontdata.nofglyphs    = nofglyphs
989    --
990    return indices, names
991end
992
993local stripredundant  do
994
995    local p_hex   = R("af","AF","09")
996    local p_digit = R("09")
997    local p_done  = S("._-")^0 + P(-1)
998 -- local p_style = P(".ss") * p_digit * p_digit * P(-1)
999    local p_style = P(".")
1000    local p_alpha = R("az","AZ")
1001    local p_ALPHA = R("AZ")
1002
1003    local p_crappyname = (
1004    -- (P("uni") + P("UNI") + P("Uni") + P("U") + P("u"))
1005        lpeg.utfchartabletopattern({ "uni", "u" },true)
1006      * S("Xx_")^0
1007      * p_hex^1
1008   -- + (P("identity") + P("Identity") + P("IDENTITY") + P("glyph") + P("jamo"))
1009      + lpeg.utfchartabletopattern({ "identity", "glyph", "jamo" },true)
1010      * p_hex^1
1011   -- + (P("index") + P("Index") + P("INDEX")+ P("afii"))
1012      + lpeg.utfchartabletopattern({ "index", "afii" }, true)
1013      * p_digit^1
1014      -- also happens l
1015      + p_digit
1016      * p_hex^3
1017      + p_alpha
1018      * p_digit^1
1019      -- sort of special
1020      + P("aj")
1021      * p_digit^1
1022      + P("eh_")
1023      * (p_digit^1 + p_ALPHA * p_digit^1)
1024      + (1-P("_"))^1
1025      * P("_uni")
1026      * p_hex^1
1027      + P("_")
1028      * P(1)^1
1029    ) * p_done
1030
1031    -- In context we only keep glyph names because of tracing and access by name
1032    -- so weird names make no sense.
1033
1034    if context then
1035
1036        local forcekeep = false -- only for testing something
1037--         local forcekeep = true
1038
1039        directives.register("otf.keepnames",function(v)
1040            report_cleanup("keeping weird glyph names, expect larger files and more memory usage")
1041            forcekeep = v
1042        end)
1043
1044     -- local p_lesscrappyname =
1045     --     lpeg.utfchartabletopattern({ "uni", "u" },true)
1046     --   * S("Xx")^0
1047     --   * p_hex^1
1048     --   * p_style
1049
1050        local function stripvariants(descriptions,list)
1051            local n = list and #list or 0
1052            if n > 0 then
1053                for i=1,n do
1054                    local g = list[i]
1055                    if g then
1056                        local d = descriptions[g]
1057                        if d and d.name then
1058                            d.name = nil
1059                            n = n + 1
1060                        end
1061                    end
1062                end
1063            end
1064            return n
1065        end
1066
1067        local function stripparts(descriptions,list)
1068            local n = list and #list or 0
1069            if n > 0 then
1070                for i=1,n do
1071                    local g = list[i].glyph
1072                    if g then
1073                        local d = descriptions[g]
1074                        if d and d.name then
1075                            d.name = nil
1076                            n = n + 1
1077                        end
1078                    end
1079                end
1080            end
1081            return n
1082        end
1083
1084     -- local function collectsimple(fontdata)
1085     --     local resources = fontdata.resources
1086     --     local sequences = resources and resources.sequences
1087     --     if sequences then
1088     --         local keeplist = { }
1089     --         for i=1,#sequences do
1090     --             local s = sequences[i]
1091     --             if s.type == "gsub_single" then
1092     --                 -- only simple ones
1093     --                 local features = s.features
1094     --                 local steps    = s.steps
1095     --                 if features and steps then
1096     --                     local okay = false
1097     --                     for k, v in next, features do
1098     --                         if find(k,"^ss%d%d") then
1099     --                             okay = true
1100     --                             break
1101     --                         end
1102     --                     end
1103     --                     if okay then
1104     --                         for i=1,#steps do
1105     --                             local coverage = steps[i].coverage
1106     --                             if coverage then
1107     --                                 for k, v in next, coverage do
1108     --                                     keeplist[k] = v
1109     --                                 end
1110     --                             end
1111     --                         end
1112     --                     end
1113     --                 end
1114     --             end
1115     --         end
1116     --         return next(keeplist) and keeplist or nil
1117     --     end
1118     -- end
1119
1120        local function collectsimple(fontdata)
1121            return nil
1122        end
1123
1124        stripredundant = function(fontdata)
1125            local descriptions = fontdata.descriptions
1126            if descriptions then
1127                local n = 0
1128                local c = 0
1129                for unicode, d in next, descriptions do
1130                    local m = d.math
1131                    if m then
1132                        n = n + stripvariants(descriptions,m.vvariants)
1133                        n = n + stripvariants(descriptions,m.hvariants)
1134                        n = n + stripparts   (descriptions,m.vparts)
1135                        n = n + stripparts   (descriptions,m.hparts)
1136                    end
1137                end
1138                if forcekeep then
1139                    for unicode, d in next, descriptions do
1140                        if d.class == "base" then
1141                            d.class = nil
1142                            c = c + 1
1143                        end
1144                    end
1145                else
1146                    local keeplist = collectsimple(fontdata)
1147                    for unicode, d in next, descriptions do
1148                        local name = d.name
1149                        if name then
1150                         -- if lpegmatch(p_lesscrappyname,name) then
1151                            if keeplist and keeplist[name] then
1152                                -- keep name
1153                            elseif lpegmatch(p_crappyname,name) then
1154                                d.name = nil
1155                                n = n + 1
1156                            end
1157                        end
1158                        if d.class == "base" then
1159                            d.class = nil
1160                            c = c + 1
1161                        end
1162                    end
1163                end
1164                if trace_cleanup then
1165                    if n > 0 then
1166                        report_cleanup("%s bogus names removed (verbose unicode)",n)
1167                    end
1168                    if c > 0 then
1169                        report_cleanup("%s base class tags removed (default is base)",c)
1170                    end
1171                end
1172            end
1173        end
1174
1175    else
1176
1177        stripredundant = function(fontdata)
1178            local descriptions = fontdata.descriptions
1179            if descriptions then
1180                if fonts.privateoffsets.keepnames then
1181                    for unicode, d in next, descriptions do
1182                        if d.class == "base" then
1183                            d.class = nil
1184                        end
1185                    end
1186                else
1187                    for unicode, d in next, descriptions do
1188                        local name = d.name
1189                        if name then
1190                            if lpegmatch(p_crappyname,name) then
1191                                d.name = nil
1192                            end
1193                        end
1194                        if d.class == "base" then
1195                            d.class = nil
1196                        end
1197                    end
1198                end
1199            end
1200        end
1201
1202    end
1203
1204    readers.stripredundant = stripredundant
1205
1206end
1207
1208function readers.getcomponents(fontdata) -- handy for resolving ligatures when names are missing
1209    local resources = fontdata.resources
1210    if resources then
1211        local sequences = resources.sequences
1212        if sequences then
1213            local collected = { }
1214            for i=1,#sequences do
1215                local sequence = sequences[i]
1216                if sequence.type == "gsub_ligature" then
1217                    local steps  = sequence.steps
1218                    if steps then
1219                        local l = { }
1220                        local function traverse(p,k,v)
1221                            if k == "ligature" then
1222                                collected[v] = { unpack(l) }
1223                            elseif tonumber(v) then
1224                                insert(l,k)
1225                                collected[v] = { unpack(l) }
1226                                remove(l)
1227                            else
1228                                insert(l,k)
1229                                for k, vv in next, v do
1230                                    traverse(p,k,vv)
1231                                end
1232                                remove(l)
1233                            end
1234                        end
1235                        for i=1,#steps do
1236                         -- we actually had/have this in base mode
1237                            local c = steps[i].coverage
1238                            if c then
1239                                for k, v in next, c do
1240                                    traverse(k,k,v)
1241                                end
1242                            end
1243                        end
1244                    end
1245                end
1246            end
1247            if next(collected) then
1248                -- remove self referring
1249             -- for k, v in next, collected do
1250             --     for i=1,#v do
1251             --         local vi = v[i]
1252             --         if vi == k then
1253             --          -- report("removing self referring ligature @ slot %5X from collected (1)",k)
1254             --             collected[k] = nil
1255             --         end
1256             --     end
1257             -- end
1258                while true do
1259                    local done = false
1260                    for k, v in next, collected do
1261                        for i=1,#v do
1262                            local vi = v[i]
1263                            if vi == k then
1264                             -- report("removing self referring ligature @ slot %5X from collected (2)",k)
1265                                collected[k] = nil
1266                                break
1267                            else
1268                                local c = collected[vi]
1269                                if c then
1270                                    done = true
1271                                    local t = { }
1272                                    local n = i - 1
1273                                    for j=1,n do
1274                                        t[j] = v[j]
1275                                    end
1276                                    for j=1,#c do
1277                                        n = n + 1
1278                                        t[n] = c[j]
1279                                    end
1280                                    for j=i+1,#v do
1281                                        n = n + 1
1282                                        t[n] = v[j]
1283                                    end
1284                                    collected[k] = t
1285                                    break
1286                                end
1287                            end
1288                        end
1289                    end
1290                    if not done then
1291                        break
1292                    end
1293                end
1294                return collected
1295            end
1296        end
1297    end
1298end
1299
1300readers.unifymissing = unifymissing
1301
1302function readers.rehash(fontdata,hashmethod) -- TODO: combine loops in one
1303    if not (fontdata and fontdata.glyphs) then
1304        return
1305    elseif hashmethod == "indices" then
1306        fontdata.hashmethod = "indices"
1307    elseif hashmethod == "names" then
1308        fontdata.hashmethod = "names"
1309        local indices = unifyglyphs(fontdata,true)
1310        unifyresources(fontdata,indices)
1311        copyduplicates(fontdata)
1312        unifymissing(fontdata)
1313    else
1314        fontdata.hashmethod = "unicodes"
1315        local indices = unifyglyphs(fontdata)
1316        unifyresources(fontdata,indices)
1317        copyduplicates(fontdata)
1318        unifymissing(fontdata)
1319        stripredundant(fontdata)
1320    end
1321    -- maybe here components
1322end
1323
1324function readers.checkhash(fontdata)
1325    local hashmethod = fontdata.hashmethod
1326    if hashmethod == "unicodes" then
1327        fontdata.names = nil -- just to be sure
1328    elseif hashmethod == "names" and fontdata.names then
1329        unifyresources(fontdata,fontdata.names)
1330        copyduplicates(fontdata)
1331        fontdata.hashmethod = "unicodes"
1332        fontdata.names = nil -- no need for it
1333    else
1334        readers.rehash(fontdata,"unicodes")
1335    end
1336end
1337
1338function readers.addunicodetable(fontdata)
1339    local resources = fontdata.resources
1340    local unicodes  = resources.unicodes
1341    if not unicodes then
1342        local descriptions = fontdata.descriptions
1343        if descriptions then
1344            unicodes = { }
1345            resources.unicodes = unicodes
1346            for u, d in next, descriptions do
1347                local n = d.name
1348                if n then
1349                    unicodes[n] = u
1350                end
1351            end
1352        end
1353    end
1354end
1355
1356-- for the moment here:
1357
1358local concat, sort = table.concat, table.sort
1359local next, type, tostring = next, type, tostring
1360
1361local criterium     <const> = 1
1362local threshold     <const> = 0
1363
1364local trace_packing = false  trackers.register("otf.packing", function(v) trace_packing = v end)
1365local trace_loading = false  trackers.register("otf.loading", function(v) trace_loading = v end)
1366
1367local report_otf    = logs.reporter("fonts","otf loading")
1368
1369local function tabstr_normal(t)
1370    local s = { }
1371    local n = 0
1372    for k, v in next, t do
1373        n = n + 1
1374        if type(v) == "table" then
1375            s[n] = k .. ">" .. tabstr_normal(v)
1376        elseif v == true then
1377            s[n] = k .. "+" -- "=true"
1378        elseif v then
1379            s[n] = k .. "=" .. v
1380        else
1381            s[n] = k .. "-" -- "=false"
1382        end
1383    end
1384    if n == 0 then
1385        return ""
1386    elseif n == 1 then
1387        return s[1]
1388    else
1389        sort(s) -- costly but needed (occasional wrong hit otherwise)
1390        return concat(s,",")
1391    end
1392end
1393
1394local function tabstr_flat(t)
1395    local s = { }
1396    local n = 0
1397    for k, v in next, t do
1398        n = n + 1
1399        s[n] = k .. "=" .. v
1400    end
1401    if n == 0 then
1402        return ""
1403    elseif n == 1 then
1404        return s[1]
1405    else
1406        sort(s) -- costly but needed (occasional wrong hit otherwise)
1407        return concat(s,",")
1408    end
1409end
1410
1411local function tabstr_mixed(t) -- indexed
1412    local n = #t
1413    if n == 0 then
1414        return ""
1415    elseif n == 1 then
1416        local k = t[1]
1417        if k == true then
1418            return "++" -- we need to distinguish from "true"
1419        elseif k == false then
1420            return "--" -- we need to distinguish from "false"
1421        else
1422            return tostring(k) -- number or string
1423        end
1424    else
1425        local s = { }
1426        for i=1,n do
1427            local k = t[i]
1428            if k == true then
1429                s[i] = "++" -- we need to distinguish from "true"
1430            elseif k == false then
1431                s[i] = "--" -- we need to distinguish from "false"
1432            else
1433                s[i] = k -- number or string
1434            end
1435        end
1436        return concat(s,",")
1437    end
1438end
1439
1440local function tabstr_boolean(t)
1441    local s = { }
1442    local n = 0
1443    for k, v in next, t do
1444        n = n + 1
1445        if v then
1446            s[n] = k .. "+"
1447        else
1448            s[n] = k .. "-"
1449        end
1450    end
1451    if n == 0 then
1452        return ""
1453    elseif n == 1 then
1454        return s[1]
1455    else
1456        sort(s) -- costly but needed (occasional wrong hit otherwise)
1457        return concat(s,",")
1458    end
1459end
1460
1461-- beware: we cannot unpack and repack the same table because then sharing
1462-- interferes (we could catch this if needed) .. so for now: save, reload
1463-- and repack in such cases (never needed anyway) .. a tricky aspect is that
1464-- we then need to sort more thanks to random hashing
1465
1466function readers.pack(data)
1467
1468    if data then
1469
1470        local h, t, c = { }, { }, { }
1471        local hh, tt, cc = { }, { }, { }
1472        local nt, ntt = 0, 0
1473
1474        local function pack_normal(v)
1475            local tag = tabstr_normal(v)
1476            local ht = h[tag]
1477            if ht then
1478                c[ht] = c[ht] + 1
1479                return ht
1480            else
1481                nt = nt + 1
1482                t[nt] = v
1483                h[tag] = nt
1484                c[nt] = 1
1485                return nt
1486            end
1487        end
1488
1489        local function pack_normal_cc(v)
1490            local tag = tabstr_normal(v)
1491            local ht = h[tag]
1492            if ht then
1493                c[ht] = c[ht] + 1
1494                return ht
1495            else
1496                v[1] = 0
1497                nt = nt + 1
1498                t[nt] = v
1499                h[tag] = nt
1500                c[nt] = 1
1501                return nt
1502            end
1503        end
1504
1505        local function pack_flat(v)
1506            local tag = tabstr_flat(v)
1507            local ht = h[tag]
1508            if ht then
1509                c[ht] = c[ht] + 1
1510                return ht
1511            else
1512                nt = nt + 1
1513                t[nt] = v
1514                h[tag] = nt
1515                c[nt] = 1
1516                return nt
1517            end
1518        end
1519
1520        local function pack_indexed(v)
1521            local tag = concat(v," ")
1522            local ht = h[tag]
1523            if ht then
1524                c[ht] = c[ht] + 1
1525                return ht
1526            else
1527                nt = nt + 1
1528                t[nt] = v
1529                h[tag] = nt
1530                c[nt] = 1
1531                return nt
1532            end
1533        end
1534
1535     -- local function pack_indexed(v) -- less code
1536     --     local tag = concat(v," ")
1537     --     local ht = h[tag]
1538     --     if ht then
1539     --         c[ht] = c[ht] + 1
1540     --     else
1541     --         ht = nt + 1
1542     --         t[ht] = v
1543     --         c[ht] = 1
1544     --         h[tag] = ht
1545     --         nt = ht
1546     --     end
1547     --     return ht
1548     -- end
1549
1550        local function pack_mixed(v)
1551            local tag = tabstr_mixed(v)
1552            local ht = h[tag]
1553            if ht then
1554                c[ht] = c[ht] + 1
1555                return ht
1556            else
1557                nt = nt + 1
1558                t[nt] = v
1559                h[tag] = nt
1560                c[nt] = 1
1561                return nt
1562            end
1563        end
1564
1565        -- saves a lot on noto sans
1566
1567        -- can be made more clever
1568
1569        local function pack_boolean(v)
1570            local tag = tabstr_boolean(v)
1571            local ht = h[tag]
1572            if ht then
1573                c[ht] = c[ht] + 1
1574                return ht
1575            else
1576                nt = nt + 1
1577                t[nt] = v
1578                h[tag] = nt
1579                c[nt] = 1
1580                return nt
1581            end
1582        end
1583
1584        local function pack_final(v)
1585            -- v == number
1586            if c[v] <= criterium then
1587                return t[v]
1588            else
1589                -- compact hash
1590                local hv = hh[v]
1591                if hv then
1592                    return hv
1593                else
1594                    ntt = ntt + 1
1595                    tt[ntt] = t[v]
1596                    hh[v] = ntt
1597                    cc[ntt] = c[v]
1598                    return ntt
1599                end
1600            end
1601        end
1602
1603        local function pack_final_cc(v)
1604            -- v == number
1605            if c[v] <= criterium then
1606                return t[v]
1607            else
1608                -- compact hash
1609                local hv = hh[v]
1610                if hv then
1611                    return hv
1612                else
1613                    ntt = ntt + 1
1614                    tt[ntt] = t[v]
1615                    hh[v] = ntt
1616                    cc[ntt] = c[v]
1617                    return ntt
1618                end
1619            end
1620        end
1621
1622        local function success(stage,pass)
1623            if nt == 0 then
1624                if trace_loading or trace_packing then
1625                    report_otf("pack quality: nothing to pack")
1626                end
1627                return false
1628            elseif nt >= threshold then
1629                local one  = 0
1630                local two  = 0
1631                local rest = 0
1632                if pass == 1 then
1633                    for k,v in next, c do
1634                        if v == 1 then
1635                            one = one + 1
1636                        elseif v == 2 then
1637                            two = two + 1
1638                        else
1639                            rest = rest + 1
1640                        end
1641                    end
1642                else
1643                    for k,v in next, cc do
1644                        if v > 20 then
1645                            rest = rest + 1
1646                        elseif v > 10 then
1647                            two = two + 1
1648                        else
1649                            one = one + 1
1650                        end
1651                    end
1652                    data.tables = tt
1653                end
1654                if trace_loading or trace_packing then
1655                    report_otf("pack quality: stage %s, pass %s, %s packed, 1-10:%s, 11-20:%s, rest:%s (criterium: %s)",
1656                        stage, pass, one+two+rest, one, two, rest, criterium)
1657                end
1658                return true
1659            else
1660                if trace_loading or trace_packing then
1661                    report_otf("pack quality: stage %s, pass %s, %s packed, aborting pack (threshold: %s)",
1662                        stage, pass, nt, threshold)
1663                end
1664                return false
1665            end
1666        end
1667
1668        local function packers(pass)
1669            if pass == 1 then
1670                return pack_normal, pack_indexed, pack_flat, pack_boolean, pack_mixed, pack_normal_cc
1671            else
1672                return pack_final, pack_final, pack_final, pack_final, pack_final, pack_final_cc
1673            end
1674        end
1675
1676        local resources  = data.resources
1677        local sequences  = resources.sequences
1678        local sublookups = resources.sublookups
1679        local features   = resources.features
1680        local palettes   = resources.colorpalettes
1681        local variable   = resources.variabledata
1682
1683        local chardata     = characters and characters.data
1684        local descriptions = data.descriptions or data.glyphs
1685
1686        if not descriptions then
1687            return
1688        end
1689
1690        for pass=1,2 do
1691
1692            if trace_packing then
1693                report_otf("start packing: stage 1, pass %s",pass)
1694            end
1695
1696            local pack_normal, pack_indexed, pack_flat, pack_boolean, pack_mixed, pack_normal_cc = packers(pass)
1697
1698            for unicode, description in next, descriptions do
1699                local boundingbox = description.boundingbox
1700                if boundingbox then
1701                    description.boundingbox = pack_indexed(boundingbox)
1702                end
1703                local math = description.math
1704                if math then
1705                    local kerns = math.kerns
1706                    if kerns then
1707                        for tag, kern in next, kerns do
1708                            kerns[tag] = pack_normal(kern)
1709                        end
1710                    end
1711                end
1712             -- if palettes then
1713             --     local color = description.color
1714             --     if color then
1715             --         for i=1,#color do
1716             --             color[i] = pack_normal(color[i])
1717             --         end
1718             --     end
1719             -- end
1720            end
1721
1722            local function packthem(sequences)
1723                for i=1,#sequences do
1724                    local sequence = sequences[i]
1725                    local kind     = sequence.type
1726                    local steps    = sequence.steps
1727                    local order    = sequence.order
1728                    local features = sequence.features
1729                    local flags    = sequence.flags
1730                    if steps then
1731                        for i=1,#steps do
1732                            local step = steps[i]
1733                            if kind == "gpos_pair" then
1734                                local c = step.coverage
1735                                if c then
1736                                    if step.format ~= "pair" then
1737                                        for g1, d1 in next, c do
1738                                            c[g1] = pack_normal(d1)
1739                                        end
1740                                    elseif step.shared then
1741                                        -- This branch results from classes. We already share at the reader end. Maybe
1742                                        -- the sharing should be moved there altogether but it becomes kind of messy
1743                                        -- then. Here we're still wasting time because in the second pass we serialize
1744                                        -- and hash. So we compromise. We could merge the two passes ...
1745                                        local shared = { }
1746                                        for g1, d1 in next, c do
1747                                            for g2, d2 in next, d1 do
1748                                                if not shared[d2] then
1749                                                    local f = d2[1] if f and f ~= true then d2[1] = pack_indexed(f) end
1750                                                    local s = d2[2] if s and s ~= true then d2[2] = pack_indexed(s) end
1751                                                    shared[d2] = true
1752                                                end
1753                                            end
1754                                        end
1755                                        if pass == 2 then
1756                                            step.shared = nil -- weird, so dups
1757                                        end
1758                                    else
1759                                        for g1, d1 in next, c do
1760                                            for g2, d2 in next, d1 do
1761                                                local f = d2[1] if f and f ~= true then d2[1] = pack_indexed(f) end
1762                                                local s = d2[2] if s and s ~= true then d2[2] = pack_indexed(s) end
1763                                            end
1764                                        end
1765                                    end
1766                                end
1767                            elseif kind == "gpos_single" then
1768                                local c = step.coverage
1769                                if c then
1770                                    if step.format == "single" then
1771                                        for g1, d1 in next, c do
1772                                            if d1 and d1 ~= true then
1773                                                c[g1] = pack_indexed(d1)
1774                                            end
1775                                        end
1776                                    else
1777                                        step.coverage = pack_normal(c)
1778                                    end
1779                                end
1780                            elseif kind == "gpos_cursive" then
1781                                local c = step.coverage
1782                                if c then
1783                                    for g1, d1 in next, c do
1784                                        local f = d1[2] if f then d1[2] = pack_indexed(f) end
1785                                        local s = d1[3] if s then d1[3] = pack_indexed(s) end
1786                                    end
1787                                end
1788                            elseif kind == "gpos_mark2base" or kind == "gpos_mark2mark" then
1789                                local c = step.baseclasses
1790                                if c then
1791                                    for g1, d1 in next, c do
1792                                        for g2, d2 in next, d1 do
1793                                            d1[g2] = pack_indexed(d2)
1794                                        end
1795                                    end
1796                                end
1797                                local c = step.coverage
1798                                if c then
1799                                    for g1, d1 in next, c do
1800                                        d1[2] = pack_indexed(d1[2])
1801                                    end
1802                                end
1803                            elseif kind == "gpos_mark2ligature" then
1804                                local c = step.baseclasses
1805                                if c then
1806                                    for g1, d1 in next, c do
1807                                        for g2, d2 in next, d1 do
1808                                            for g3, d3 in next, d2 do
1809                                                d2[g3] = pack_indexed(d3)
1810                                            end
1811                                        end
1812                                    end
1813                                end
1814                                local c = step.coverage
1815                                if c then
1816                                    for g1, d1 in next, c do
1817                                        d1[2] = pack_indexed(d1[2])
1818                                    end
1819                                end
1820                            end
1821                            -- if ... chain ...
1822                            local rules = step.rules
1823                            if rules then
1824                                for i=1,#rules do
1825                                    local rule = rules[i]
1826                                    local r = rule.before       if r then for i=1,#r do r[i] = pack_boolean(r[i]) end end
1827                                    local r = rule.after        if r then for i=1,#r do r[i] = pack_boolean(r[i]) end end
1828                                    local r = rule.current      if r then for i=1,#r do r[i] = pack_boolean(r[i]) end end
1829                                 -- local r = rule.lookups      if r then rule.lookups       = pack_mixed  (r)    end
1830                                    local r = rule.replacements if r then rule.replacements  = pack_flat   (r)    end
1831                                end
1832                            end
1833                        end
1834                    end
1835                    if order then
1836                        sequence.order = pack_indexed(order)
1837                    end
1838                    if features then
1839                        for script, feature in next, features do
1840                            features[script] = pack_normal(feature)
1841                        end
1842                    end
1843                    if flags then
1844                        sequence.flags = pack_normal(flags)
1845                    end
1846               end
1847            end
1848
1849            if sequences then
1850                packthem(sequences)
1851            end
1852
1853            if sublookups then
1854                packthem(sublookups)
1855            end
1856
1857            if features then
1858                for k, list in next, features do
1859                    for feature, spec in next, list do
1860                        list[feature] = pack_normal(spec)
1861                    end
1862                end
1863            end
1864
1865            if palettes then
1866                for i=1,#palettes do
1867                    local p = palettes[i]
1868                    for j=1,#p do
1869                        p[j] = pack_indexed(p[j])
1870                    end
1871                end
1872
1873            end
1874
1875            if variable then
1876
1877                -- todo: segments
1878
1879                local instances = variable.instances
1880                if instances then
1881                    for i=1,#instances do
1882                        local v = instances[i].values
1883                        for j=1,#v do
1884                            v[j] = pack_normal(v[j])
1885                        end
1886                    end
1887                end
1888
1889                local function packdeltas(main)
1890                    if main then
1891                        local deltas = main.deltas
1892                        if deltas then
1893                            for i=1,#deltas do
1894                                local di = deltas[i]
1895                                local d  = di.deltas
1896                             -- local r  = di.regions
1897                                for j=1,#d do
1898                                    d[j] = pack_indexed(d[j])
1899                                end
1900                                di.regions = pack_indexed(di.regions)
1901                            end
1902                        end
1903                        local regions = main.regions
1904                        if regions then
1905                            for i=1,#regions do
1906                                local r = regions[i]
1907                                for j=1,#r do
1908                                    r[j] = pack_normal(r[j])
1909                                end
1910                            end
1911                        end
1912                    end
1913                end
1914
1915                packdeltas(variable.global)
1916                packdeltas(variable.horizontal)
1917                packdeltas(variable.vertical)
1918                packdeltas(variable.metrics)
1919
1920            end
1921
1922            if not success(1,pass) then
1923                return
1924            end
1925
1926        end
1927
1928        if nt > 0 then
1929
1930            for pass=1,2 do
1931
1932                if trace_packing then
1933                    report_otf("start packing: stage 2, pass %s",pass)
1934                end
1935
1936                local pack_normal, pack_indexed, pack_flat, pack_boolean, pack_mixed, pack_normal_cc = packers(pass)
1937
1938                for unicode, description in next, descriptions do
1939                    local math = description.math
1940                    if math then
1941                        local kerns = math.kerns
1942                        if kerns then
1943                            math.kerns = pack_normal(kerns)
1944                        end
1945                    end
1946                end
1947
1948                local function packthem(sequences)
1949                    for i=1,#sequences do
1950                        local sequence = sequences[i]
1951                        local kind     = sequence.type
1952                        local steps    = sequence.steps
1953                        local features = sequence.features
1954                        if steps then
1955                            for i=1,#steps do
1956                                local step = steps[i]
1957                                if kind == "gpos_pair" then
1958                                    local c = step.coverage
1959                                    if c then
1960                                        if step.format == "pair" then
1961                                            for g1, d1 in next, c do
1962                                                for g2, d2 in next, d1 do
1963                                                    d1[g2] = pack_normal(d2)
1964                                                end
1965                                            end
1966                                        end
1967                                    end
1968                             -- elseif kind == "gpos_cursive" then
1969                             --     local c = step.coverage -- new
1970                             --     if c then
1971                             --         for g1, d1 in next, c do
1972                             --             c[g1] = pack_normal_cc(d1)
1973                             --         end
1974                             --     end
1975                                elseif kind == "gpos_mark2ligature" then
1976                                    local c = step.baseclasses -- new
1977                                    if c then
1978                                        for g1, d1 in next, c do
1979                                            for g2, d2 in next, d1 do
1980                                                d1[g2] = pack_normal(d2)
1981                                            end
1982                                        end
1983                                    end
1984                                end
1985                                local rules = step.rules
1986                                if rules then
1987                                    for i=1,#rules do
1988                                        local rule = rules[i]
1989                                        local r = rule.before  if r then rule.before  = pack_normal(r) end
1990                                        local r = rule.after   if r then rule.after   = pack_normal(r) end
1991                                        local r = rule.current if r then rule.current = pack_normal(r) end
1992                                    end
1993                                end
1994                            end
1995                        end
1996                        if features then
1997                            sequence.features = pack_normal(features)
1998                        end
1999                   end
2000                end
2001                if sequences then
2002                    packthem(sequences)
2003                end
2004                if sublookups then
2005                    packthem(sublookups)
2006                end
2007                if variable then
2008                    local function unpackdeltas(main)
2009                        if main then
2010                            local regions = main.regions
2011                            if regions then
2012                                main.regions = pack_normal(regions)
2013                            end
2014                        end
2015                    end
2016                    unpackdeltas(variable.global)
2017                    unpackdeltas(variable.horizontal)
2018                    unpackdeltas(variable.vertical)
2019                    unpackdeltas(variable.metrics)
2020                end
2021             -- if not success(2,pass) then
2022             --  -- return
2023             -- end
2024            end
2025
2026            for pass=1,2 do
2027                if trace_packing then
2028                    report_otf("start packing: stage 3, pass %s",pass)
2029                end
2030
2031                local pack_normal, pack_indexed, pack_flat, pack_boolean, pack_mixed, pack_normal_cc = packers(pass)
2032
2033                local function packthem(sequences)
2034                    for i=1,#sequences do
2035                        local sequence = sequences[i]
2036                        local kind     = sequence.type
2037                        local steps    = sequence.steps
2038                        local features = sequence.features
2039                        if steps then
2040                            for i=1,#steps do
2041                                local step = steps[i]
2042                                if kind == "gpos_pair" then
2043                                    local c = step.coverage
2044                                    if c then
2045                                        if step.format == "pair" then
2046                                            for g1, d1 in next, c do
2047                                                c[g1] = pack_normal(d1)
2048                                            end
2049                                        end
2050                                    end
2051                                elseif kind == "gpos_cursive" then
2052                                    local c = step.coverage
2053                                    if c then
2054                                        for g1, d1 in next, c do
2055                                            c[g1] = pack_normal_cc(d1)
2056                                        end
2057                                    end
2058                                end
2059                            end
2060                        end
2061                    end
2062                end
2063
2064                if sequences then
2065                    packthem(sequences)
2066                end
2067                if sublookups then
2068                    packthem(sublookups)
2069                end
2070
2071            end
2072
2073        end
2074
2075    end
2076end
2077
2078local unpacked_mt = {
2079    __index =
2080        function(t,k)
2081            t[k] = false
2082            return k -- next time true
2083        end
2084}
2085
2086function readers.unpack(data)
2087
2088    if data then
2089        local tables = data.tables
2090        if tables then
2091            local resources    = data.resources
2092            local descriptions = data.descriptions or data.glyphs
2093            local sequences    = resources.sequences
2094            local sublookups   = resources.sublookups
2095            local features     = resources.features
2096            local palettes     = resources.colorpalettes
2097            local variable     = resources.variabledata
2098            local unpacked     = { }
2099            setmetatable(unpacked,unpacked_mt)
2100            for unicode, description in next, descriptions do
2101                local tv = tables[description.boundingbox]
2102                if tv then
2103                    description.boundingbox = tv
2104                end
2105                local math = description.math
2106                if math then
2107                    local kerns = math.kerns
2108                    if kerns then
2109                        local tm = tables[kerns]
2110                        if tm then
2111                            math.kerns = tm
2112                            kerns = unpacked[tm]
2113                        end
2114                        if kerns then
2115                            for k, kern in next, kerns do
2116                                local tv = tables[kern]
2117                                if tv then
2118                                    kerns[k] = tv
2119                                end
2120                            end
2121                        end
2122                    end
2123                end
2124             -- if palettes then
2125             --     local color = description.color
2126             --     if color then
2127             --         for i=1,#color do
2128             --             local tv = tables[color[i]]
2129             --             if tv then
2130             --                 color[i] = tv
2131             --             end
2132             --         end
2133             --     end
2134             -- end
2135            end
2136
2137         -- local function expandranges(t,ranges)
2138         --     for i=1,#ranges do
2139         --         local r = ranges[i]
2140         --         for k=r[1],r[2] do
2141         --             t[k] = true
2142         --         end
2143         --     end
2144         -- end
2145
2146            local function unpackthem(sequences)
2147                for i=1,#sequences do
2148                    local sequence  = sequences[i]
2149                    local kind      = sequence.type
2150                    local steps     = sequence.steps
2151                    local order     = sequence.order
2152                    local features  = sequence.features
2153                    local flags     = sequence.flags
2154                    local markclass = sequence.markclass
2155                    if features then
2156                        local tv = tables[features]
2157                        if tv then
2158                            sequence.features = tv
2159                            features = tv
2160                        end
2161                        for script, feature in next, features do
2162                            local tv = tables[feature]
2163                            if tv then
2164                                features[script] = tv
2165                            end
2166                        end
2167                    end
2168                    if steps then
2169                        for i=1,#steps do
2170                            local step = steps[i]
2171                            if kind == "gpos_pair" then
2172                                local c = step.coverage
2173                                if c then
2174                                    if step.format == "pair" then
2175                                        for g1, d1 in next, c do
2176                                            local tv = tables[d1]
2177                                            if tv then
2178                                                c[g1] = tv
2179                                                d1 = tv
2180                                            end
2181                                            for g2, d2 in next, d1 do
2182                                                local tv = tables[d2]
2183                                                if tv then
2184                                                    d1[g2] = tv
2185                                                    d2 = tv
2186                                                end
2187                                                local f = tables[d2[1]] if f then d2[1] = f end
2188                                                local s = tables[d2[2]] if s then d2[2] = s end
2189                                            end
2190                                        end
2191                                    else
2192                                        for g1, d1 in next, c do
2193                                            local tv = tables[d1]
2194                                            if tv then
2195                                                c[g1] = tv
2196                                            end
2197                                        end
2198                                    end
2199                                end
2200                            elseif kind == "gpos_single" then
2201                                local c = step.coverage
2202                                if c then
2203                                    if step.format == "single" then
2204                                        for g1, d1 in next, c do
2205                                            local tv = tables[d1]
2206                                            if tv then
2207                                                c[g1] = tv
2208                                            end
2209                                        end
2210                                    else
2211                                        local tv = tables[c]
2212                                        if tv then
2213                                            step.coverage = tv
2214                                        end
2215                                    end
2216                                end
2217                            elseif kind == "gpos_cursive" then
2218                                local c = step.coverage
2219                                if c then
2220                                    for g1, d1 in next, c do
2221                                        local tv = tables[d1]
2222                                        if tv then
2223                                            d1 = tv
2224                                            c[g1] = d1
2225                                        end
2226                                        local f = tables[d1[2]] if f then d1[2] = f end
2227                                        local s = tables[d1[3]] if s then d1[3] = s end
2228                                    end
2229                                end
2230                            elseif kind == "gpos_mark2base" or kind == "gpos_mark2mark" then
2231                                local c = step.baseclasses
2232                                if c then
2233                                    for g1, d1 in next, c do
2234                                        for g2, d2 in next, d1 do
2235                                            local tv = tables[d2]
2236                                            if tv then
2237                                                d1[g2] = tv
2238                                            end
2239                                        end
2240                                    end
2241                                end
2242                                local c = step.coverage
2243                                if c then
2244                                    for g1, d1 in next, c do
2245                                        local tv = tables[d1[2]]
2246                                        if tv then
2247                                            d1[2] = tv
2248                                        end
2249                                    end
2250                                end
2251                            elseif kind == "gpos_mark2ligature" then
2252                                local c = step.baseclasses
2253                                if c then
2254                                    for g1, d1 in next, c do
2255                                        for g2, d2 in next, d1 do
2256                                            local tv = tables[d2] -- new
2257                                            if tv then
2258                                                d2 = tv
2259                                                d1[g2] = d2
2260                                            end
2261                                            for g3, d3 in next, d2 do
2262                                                local tv = tables[d2[g3]]
2263                                                if tv then
2264                                                    d2[g3] = tv
2265                                                end
2266                                            end
2267                                        end
2268                                    end
2269                                end
2270                                local c = step.coverage
2271                                if c then
2272                                    for g1, d1 in next, c do
2273                                        local tv = tables[d1[2]]
2274                                        if tv then
2275                                            d1[2] = tv
2276                                        end
2277                                    end
2278                                end
2279                            end
2280                            local rules = step.rules
2281                            if rules then
2282                                for i=1,#rules do
2283                                    local rule = rules[i]
2284                                    local before = rule.before
2285                                    if before then
2286                                        local tv = tables[before]
2287                                        if tv then
2288                                            rule.before = tv
2289                                            before = tv
2290                                        end
2291                                        for i=1,#before do
2292                                            local tv = tables[before[i]]
2293                                            if tv then
2294                                                before[i] = tv
2295                                            end
2296                                        end
2297                                     -- for i=1,#before do
2298                                     --     local bi = before[i]
2299                                     --     local tv = tables[bi]
2300                                     --     if tv then
2301                                     --         bi = tv
2302                                     --         before[i] = bi
2303                                     --     end
2304                                     --     local ranges = bi.ranges
2305                                     --     if ranges then
2306                                     --         expandranges(bi,ranges)
2307                                     --     end
2308                                     -- end
2309                                    end
2310                                    local after = rule.after
2311                                    if after then
2312                                        local tv = tables[after]
2313                                        if tv then
2314                                            rule.after = tv
2315                                            after = tv
2316                                        end
2317                                        for i=1,#after do
2318                                            local tv = tables[after[i]]
2319                                            if tv then
2320                                                after[i] = tv
2321                                            end
2322                                        end
2323                                     -- for i=1,#after do
2324                                     --     local ai = after[i]
2325                                     --     local tv = tables[ai]
2326                                     --     if tv then
2327                                     --         ai = tv
2328                                     --         after[i] = ai
2329                                     --     end
2330                                     --     local ranges = ai.ranges
2331                                     --     if ranges then
2332                                     --         expandranges(ai,ranges)
2333                                     --     end
2334                                     -- end
2335                                    end
2336                                    local current = rule.current
2337                                    if current then
2338                                        local tv = tables[current]
2339                                        if tv then
2340                                            rule.current = tv
2341                                            current = tv
2342                                        end
2343                                        for i=1,#current do
2344                                            local tv = tables[current[i]]
2345                                            if tv then
2346                                                current[i] = tv
2347                                            end
2348                                        end
2349                                     -- for i=1,#current do
2350                                     --     local ci = current[i]
2351                                     --     local tv = tables[ci]
2352                                     --     if tv then
2353                                     --         ci = tv
2354                                     --         current[i] = ci
2355                                     --     end
2356                                     --     local ranges = ci.ranges
2357                                     --     if ranges then
2358                                     --         expandranges(ci,ranges)
2359                                     --     end
2360                                     -- end
2361                                    end
2362                                 -- local lookups = rule.lookups
2363                                 -- if lookups then
2364                                 --     local tv = tables[lookups]
2365                                 --     if tv then
2366                                 --         rule.lookups = tv
2367                                 --     end
2368                                 -- end
2369                                    local replacements = rule.replacements
2370                                    if replacements then
2371                                        local tv = tables[replacements]
2372                                        if tv then
2373                                            rule.replacements = tv
2374                                        end
2375                                    end
2376                                end
2377                            end
2378                        end
2379                    end
2380                    if order then
2381                        local tv = tables[order]
2382                        if tv then
2383                            sequence.order = tv
2384                        end
2385                    end
2386                    if flags then
2387                        local tv = tables[flags]
2388                        if tv then
2389                            sequence.flags = tv
2390                        end
2391                    end
2392               end
2393            end
2394
2395            if sequences then
2396                unpackthem(sequences)
2397            end
2398
2399            if sublookups then
2400                unpackthem(sublookups)
2401            end
2402
2403            if features then
2404                for k, list in next, features do
2405                    for feature, spec in next, list do
2406                        local tv = tables[spec]
2407                        if tv then
2408                            list[feature] = tv
2409                        end
2410                    end
2411                end
2412            end
2413
2414            if palettes then
2415                for i=1,#palettes do
2416                    local p = palettes[i]
2417                    for j=1,#p do
2418                        local tv = tables[p[j]]
2419                        if tv then
2420                            p[j] = tv
2421                        end
2422                    end
2423                end
2424            end
2425
2426            if variable then
2427
2428                -- todo: segments
2429
2430                local instances = variable.instances
2431                if instances then
2432                    for i=1,#instances do
2433                        local v = instances[i].values
2434                        for j=1,#v do
2435                            local tv = tables[v[j]]
2436                            if tv then
2437                                v[j] = tv
2438                            end
2439                        end
2440                    end
2441                end
2442
2443                local function unpackdeltas(main)
2444                    if main then
2445                        local deltas = main.deltas
2446                        if deltas then
2447                            for i=1,#deltas do
2448                                local di = deltas[i]
2449                                local d  = di.deltas
2450                                local r  = di.regions
2451                                for j=1,#d do
2452                                    local tv = tables[d[j]]
2453                                    if tv then
2454                                        d[j] = tv
2455                                    end
2456                                end
2457                                local tv = di.regions
2458                                if tv then
2459                                    di.regions = tv
2460                                end
2461                            end
2462                        end
2463                        local regions = main.regions
2464                        if regions then
2465                            local tv = tables[regions]
2466                            if tv then
2467                                main.regions = tv
2468                                regions = tv
2469                            end
2470                            for i=1,#regions do
2471                                local r = regions[i]
2472                                for j=1,#r do
2473                                    local tv = tables[r[j]]
2474                                    if tv then
2475                                        r[j] = tv
2476                                    end
2477                                end
2478                            end
2479                        end
2480                    end
2481                end
2482
2483                unpackdeltas(variable.global)
2484                unpackdeltas(variable.horizontal)
2485                unpackdeltas(variable.vertical)
2486                unpackdeltas(variable.metrics)
2487
2488            end
2489
2490            data.tables = nil
2491        end
2492    end
2493end
2494
2495local mt = {
2496    __index = function(t,k) -- maybe set it
2497        if k == "height" then
2498            local ht = t.boundingbox[4]
2499            return ht < 0 and 0 or ht
2500        elseif k == "depth" then
2501            local dp = -t.boundingbox[2]
2502            return dp < 0 and 0 or dp
2503        elseif k == "width" then
2504            return 0
2505        elseif k == "name" then -- or maybe uni*
2506            return forcenotdef and ".notdef"
2507        end
2508    end
2509}
2510
2511local function sameformat(sequence,steps,first,nofsteps,kind)
2512    return true
2513end
2514
2515local function mergesteps_1(lookup,strict)
2516    local steps    = lookup.steps
2517    local nofsteps = lookup.nofsteps
2518    local first    = steps[1]
2519    if strict then
2520        local f = first.format
2521        for i=2,nofsteps do
2522            if steps[i].format ~= f then
2523                if trace_optimizations then
2524                    report_optimizations("not merging %a steps of %a lookup %a, different formats",nofsteps,lookup.type,lookup.name)
2525                end
2526                return 0
2527            end
2528        end
2529    end
2530    if trace_optimizations then
2531        report_optimizations("merging %a steps of %a lookup %a",nofsteps,lookup.type,lookup.name)
2532    end
2533    local target = first.coverage
2534    for i=2,nofsteps do
2535        local c = steps[i].coverage
2536        if c then
2537            for k, v in next, c do
2538                if not target[k] then
2539                    target[k] = v
2540                end
2541            end
2542        end
2543    end
2544    lookup.nofsteps = 1
2545    lookup.merged   = true
2546    lookup.steps    = { first }
2547    return nofsteps - 1
2548end
2549
2550local function mergesteps_2(lookup) -- pairs
2551    -- this can be tricky as we can have a match on a mark with no marks skip flag
2552    -- in which case with multiple steps a hit can prevent a next step while in the
2553    -- merged case we can hit differently (a messy font then anyway)
2554    local steps    = lookup.steps
2555    local nofsteps = lookup.nofsteps
2556    local first    = steps[1]
2557    if strict then
2558        local f = first.format
2559        for i=2,nofsteps do
2560            if steps[i].format ~= f then
2561                if trace_optimizations then
2562                    report_optimizations("not merging %a steps of %a lookup %a, different formats",nofsteps,lookup.type,lookup.name)
2563                end
2564                return 0
2565            end
2566        end
2567    end
2568    if trace_optimizations then
2569        report_optimizations("merging %a steps of %a lookup %a",nofsteps,lookup.type,lookup.name)
2570    end
2571    local target = first.coverage
2572    for i=2,nofsteps do
2573        local c = steps[i].coverage
2574        if c then
2575            for k, v in next, c do
2576                local tk = target[k]
2577                if tk then
2578                    for kk, vv in next, v do
2579                        if tk[kk] == nil then
2580                            tk[kk] = vv
2581                        end
2582                    end
2583                else
2584                    target[k] = v
2585                end
2586            end
2587        end
2588    end
2589    lookup.nofsteps = 1
2590    lookup.merged   = true
2591    lookup.steps    = { first }
2592    return nofsteps - 1
2593end
2594
2595-- we could have a coverage[first][second] = { } already here (because eventually
2596-- we also have something like that after loading)
2597
2598local function mergesteps_3(lookup,strict) -- marks
2599    local steps    = lookup.steps
2600    local nofsteps = lookup.nofsteps
2601    if trace_optimizations then
2602        report_optimizations("merging %a steps of %a lookup %a",nofsteps,lookup.type,lookup.name)
2603    end
2604    -- check first
2605    local coverage = { }
2606    for i=1,nofsteps do
2607        local c = steps[i].coverage
2608        if c then
2609            for k, v in next, c do
2610                local tk = coverage[k] -- { class, { x, y } }
2611                if tk then
2612                    if trace_optimizations then
2613                        report_optimizations("quitting merge due to multiple checks")
2614                    end
2615                    return nofsteps
2616                else
2617                    coverage[k] = v
2618                end
2619            end
2620        end
2621    end
2622    -- merge indeed
2623    local first       = steps[1]
2624    local baseclasses = { } -- let's assume sparse step.baseclasses
2625    for i=1,nofsteps do
2626        local offset = i*10  -- we assume max 10 classes per step
2627        local step   = steps[i]
2628        for k, v in sortedhash(step.baseclasses) do
2629            baseclasses[offset+k] = v
2630        end
2631        for k, v in next, step.coverage do
2632            v[1] = offset + v[1]
2633        end
2634    end
2635    first.baseclasses = baseclasses
2636    first.coverage    = coverage
2637    lookup.nofsteps   = 1
2638    lookup.merged     = true
2639    lookup.steps      = { first }
2640    return nofsteps - 1
2641end
2642
2643local function nested(old,new)
2644    for k, v in next, old do
2645        if k == "ligature" then
2646            if not new.ligature then
2647                new.ligature = v
2648            end
2649        else
2650            local n = new[k]
2651            if n then
2652                nested(v,n)
2653            else
2654                new[k] = v
2655            end
2656        end
2657    end
2658end
2659
2660local function mergesteps_4(lookup) -- ligatures
2661    local steps    = lookup.steps
2662    local nofsteps = lookup.nofsteps
2663    local first    = steps[1]
2664    if trace_optimizations then
2665        report_optimizations("merging %a steps of %a lookup %a",nofsteps,lookup.type,lookup.name)
2666    end
2667    local target = first.coverage
2668    for i=2,nofsteps do
2669        local c = steps[i].coverage
2670        if c then
2671            for k, v in next, c do
2672                local tk = target[k]
2673                if tk then
2674                    nested(v,tk)
2675                else
2676                    target[k] = v
2677                end
2678            end
2679        end
2680    end
2681    lookup.nofsteps = 1
2682    lookup.steps = { first }
2683    return nofsteps - 1
2684end
2685
2686-- so we assume only one cursive entry and exit and even then the first one seems
2687-- to win anyway: no exit or entry quite the lookup match and then we take the
2688-- next step; this means that we can as well merge them
2689
2690local function mergesteps_5(lookup) -- cursive
2691    local steps    = lookup.steps
2692    local nofsteps = lookup.nofsteps
2693    local first    = steps[1]
2694    if trace_optimizations then
2695        report_optimizations("merging %a steps of %a lookup %a",nofsteps,lookup.type,lookup.name)
2696    end
2697    local target = first.coverage
2698    local hash   = nil
2699    for k, v in next, target do
2700        hash = v[1]
2701        break
2702    end
2703    for i=2,nofsteps do
2704        local c = steps[i].coverage
2705        if c then
2706            for k, v in next, c do
2707                local tk = target[k]
2708                if tk then
2709                    if not tk[2] then
2710                        tk[2] = v[2]
2711                    end
2712                    if not tk[3] then
2713                        tk[3] = v[3]
2714                    end
2715                else
2716                    target[k] = v
2717                    v[1] = hash
2718                end
2719            end
2720        end
2721    end
2722    lookup.nofsteps = 1
2723    lookup.merged   = true
2724    lookup.steps    = { first }
2725    return nofsteps - 1
2726end
2727
2728local function checkkerns(lookup)
2729    local steps    = lookup.steps
2730    local nofsteps = lookup.nofsteps
2731    local kerned   = 0
2732    for i=1,nofsteps do
2733        local step = steps[i]
2734        if step.format == "pair" then
2735            local coverage = step.coverage
2736            local kerns    = true
2737            for g1, d1 in next, coverage do
2738                if d1 == true then
2739                    -- all zero
2740                elseif not d1 then
2741                    -- null
2742                elseif d1[1] ~= 0 or d1[2] ~= 0 or d1[4] ~= 0 then
2743                    kerns = false
2744                    break
2745                end
2746            end
2747            if kerns then
2748                if trace_optimizations then
2749                    report_optimizations("turning pairs of step %a of %a lookup %a into kerns",i,lookup.type,lookup.name)
2750                end
2751                local c = { }
2752                for g1, d1 in next, coverage do
2753                    if d1 and d1 ~= true then
2754                        c[g1] = d1[3]
2755                    end
2756                end
2757                step.coverage = c
2758                step.format = "move"
2759                kerned = kerned + 1
2760            end
2761        end
2762    end
2763    return kerned
2764end
2765
2766-- There are several options to optimize but we have this somewhat fuzzy aspect of
2767-- advancing (depending on the second of a pair) so we need to retain that information.
2768--
2769-- We can have:
2770--
2771--     true, nil|false
2772--
2773-- which effectively means: nothing to be done and advance to next (so not next of
2774-- next) and because coverage should be not overlapping we can wipe these. However,
2775-- checking for (true,nil) (false,nil) and omitting them doesn't gain much.
2776
2777-- Because we pack we cannot mix tables and numbers so we can only turn a whole set in
2778-- format kern instead of pair.
2779
2780local strip_pairs         = true
2781
2782local compact_pairs       = true
2783local compact_singles     = true
2784
2785local merge_pairs         = true
2786local merge_singles       = true
2787local merge_substitutions = true
2788local merge_alternates    = true
2789local merge_multiples     = true
2790local merge_ligatures     = true
2791local merge_cursives      = true
2792local merge_marks         = true
2793
2794directives.register("otf.strip.pairs",         function(v) strip_pairs     = v end)
2795
2796directives.register("otf.compact.pairs",       function(v) compact_pairs   = v end)
2797directives.register("otf.compact.singles",     function(v) compact_singles = v end)
2798
2799directives.register("otf.merge.pairs",         function(v) merge_pairs         = v end)
2800directives.register("otf.merge.singles",       function(v) merge_singles       = v end)
2801directives.register("otf.merge.substitutions", function(v) merge_substitutions = v end)
2802directives.register("otf.merge.alternates",    function(v) merge_alternates    = v end)
2803directives.register("otf.merge.multiples",     function(v) merge_multiples     = v end)
2804directives.register("otf.merge.ligatures",     function(v) merge_ligatures     = v end)
2805directives.register("otf.merge.cursives",      function(v) merge_cursives      = v end)
2806directives.register("otf.merge.marks",         function(v) merge_marks         = v end)
2807
2808local function checkpairs(lookup)
2809    local steps    = lookup.steps
2810    local nofsteps = lookup.nofsteps
2811    local kerned   = 0
2812
2813    local function onlykerns(step)
2814        local coverage = step.coverage
2815        for g1, d1 in next, coverage do
2816            for g2, d2 in next, d1 do
2817                if d2[2] then
2818                    --- true or { a, b, c, d }
2819                    return false
2820                else
2821                    local v = d2[1]
2822                    if v == true then
2823                        -- all zero
2824                    elseif v and (v[1] ~= 0 or v[2] ~= 0 or v[4] ~= 0) then
2825                        -- complex kerns
2826                        return false
2827                    end
2828                end
2829            end
2830        end
2831        return coverage
2832    end
2833
2834    for i=1,nofsteps do
2835        local step = steps[i]
2836        if step.format == "pair" then
2837            local coverage = onlykerns(step)
2838            if coverage then
2839                if trace_optimizations then
2840                    report_optimizations("turning pairs of step %a of %a lookup %a into kerns",i,lookup.type,lookup.name)
2841                end
2842                for g1, d1 in next, coverage do
2843                    local d = { }
2844                    for g2, d2 in next, d1 do
2845                        local v = d2[1]
2846                        if v == true then
2847                            -- ignore    -- d1[g2] = nil
2848                        elseif v then
2849                            d[g2] = v[3] -- d1[g2] = v[3]
2850                        end
2851                    end
2852                    coverage[g1] = d
2853                end
2854                step.format = "move"
2855                kerned = kerned + 1
2856            end
2857        end
2858    end
2859    return kerned
2860end
2861
2862local function strippairs(lookup)
2863    local steps    = lookup.steps
2864    local nofsteps = lookup.nofsteps
2865    local stripped = 0
2866
2867    for i=1,nofsteps do
2868        local step = steps[i]
2869        if step.format == "pair" then
2870            local coverage = step.coverage
2871            for g1, d1 in next, coverage do
2872                for g2, d2 in next, d1 do
2873                    if d2[2] then
2874                        --- true or { a, b, c, d }
2875                 -- else
2876                 --     local v = d2[1]
2877                 --     if v == true then
2878                 --         d1[g2] = nil
2879                 --         stripped = stripped + 1
2880                 --     elseif v and (v[1] == 0 and v[2] == 0 and v[4] == 0) then -- vkrn can have v[3] ~= 0
2881                 --         d1[g2] = nil
2882                 --         stripped = stripped + 1
2883                 --     end
2884                 -- end
2885                    elseif d2[1] == true then
2886                        d1[g2] = nil
2887                        stripped = stripped + 1
2888                    end
2889                end
2890            end
2891        end
2892    end
2893    return stripped
2894end
2895
2896function readers.compact(data)
2897    if not data or data.compacted then
2898        return
2899    else
2900        data.compacted = true
2901    end
2902    local resources = data.resources
2903    local stripped  = 0
2904    local merged    = 0
2905    local kerned    = 0
2906    local allsteps  = 0
2907    local function compact(what)
2908        local lookups = resources[what]
2909        if lookups then
2910            for i=1,#lookups do
2911                local lookup   = lookups[i]
2912                local nofsteps = lookup.nofsteps
2913                local kind     = lookup.type
2914                allsteps = allsteps + nofsteps
2915                if nofsteps > 1 then
2916                    local merg = merged
2917                    if kind == "gsub_single" then
2918                        if merge_substitutions then
2919                            merged = merged + mergesteps_1(lookup)
2920                        end
2921                    elseif kind == "gsub_alternate" then
2922                        if merge_alternates then
2923                            merged = merged + mergesteps_1(lookup)
2924                        end
2925                    elseif kind == "gsub_multiple" then
2926                        if merge_multiples then
2927                            merged = merged + mergesteps_1(lookup)
2928                        end
2929                    elseif kind == "gsub_ligature" then
2930                        if merge_ligatures then
2931                            merged = merged + mergesteps_4(lookup)
2932                        end
2933                    elseif kind == "gpos_single" then
2934                        -- maybe also strip zeros here
2935                        if merge_singles then
2936                            merged = merged + mergesteps_1(lookup,true)
2937                        end
2938                        if compact_singles then
2939                            kerned = kerned + checkkerns(lookup)
2940                        end
2941                    elseif kind == "gpos_pair" then
2942                        if strip_pairs then
2943                            stripped = stripped + strippairs(lookup) -- noto cjk from 24M -> 8 M
2944                        end
2945                        if merge_pairs then
2946                            merged = merged + mergesteps_2(lookup)
2947                        end
2948                        if compact_pairs then
2949                            kerned = kerned + checkpairs(lookup)
2950                        end
2951                    elseif kind == "gpos_cursive" then
2952                        if merge_cursives then
2953                            merged = merged + mergesteps_5(lookup)
2954                        end
2955                    elseif kind == "gpos_mark2mark" or kind == "gpos_mark2base" or kind == "gpos_mark2ligature" then
2956                        if merge_marks then
2957                            merged = merged + mergesteps_3(lookup)
2958                        end
2959                    end
2960                    if merg ~= merged then
2961                        lookup.merged = true
2962                    end
2963                elseif nofsteps == 1 then
2964                    local kern = kerned
2965                    if kind == "gpos_single" then
2966                        if compact_singles then
2967                            kerned = kerned + checkkerns(lookup)
2968                        end
2969                    elseif kind == "gpos_pair" then
2970                        if compact_pairs then
2971                            kerned = kerned + checkpairs(lookup)
2972                        end
2973                    end
2974                    if kern ~= kerned then
2975                     -- lookup.kerned = true
2976                    end
2977                end
2978            end
2979        elseif trace_optimizations then
2980            report_optimizations("no lookups in %a",what)
2981        end
2982    end
2983    compact("sequences")
2984    compact("sublookups")
2985    if trace_optimizations then
2986        if stripped > 0 then
2987            report_optimizations("%i zero positions stripped before merging",stripped)
2988        end
2989        if merged > 0 then
2990            report_optimizations("%i steps of %i removed due to merging",merged,allsteps)
2991        end
2992        if kerned > 0 then
2993            report_optimizations("%i steps of %i steps turned from pairs into kerns",kerned,allsteps)
2994        end
2995    end
2996end
2997
2998if CONTEXTLMTXMODE and CONTEXTLMTXMODE > 0 then
2999
3000    local done = 0
3001
3002    local function condense_1(k,v,t)
3003        if type(v) == "table" then
3004            local u = false
3005            local l = false
3006            for k, v in next, v do
3007                if k == "ligature" then
3008                    l = v
3009                    if u then
3010                        break
3011                    end
3012                elseif u then
3013                    break
3014                else
3015                    u = true
3016                end
3017            end
3018            if l and not u then
3019                t[k] = l
3020                done = done + 1
3021            end
3022            if u then
3023                for k, vv in next, v do
3024                    if k ~= "ligature" then
3025                        condense_1(k,vv,v)
3026                    end
3027                end
3028            end
3029        end
3030    end
3031
3032    local function condensesteps_1(lookup)
3033        done = 0
3034        if lookup.type == "gsub_ligature" then
3035            local steps = lookup.steps
3036            if steps then
3037                for i=1,#steps do
3038                    local step     = steps[i]
3039                    local coverage = step.coverage
3040                    if coverage then
3041                        for k, v in next, coverage do
3042                            if condense_1(k,v,coverage) then
3043                                coverage[k] = v.ligature
3044                                done = done + 1
3045                            end
3046                        end
3047                    end
3048                end
3049            end
3050        end
3051        return done
3052    end
3053
3054    function readers.condense(data)
3055        if not data or data.condensed then
3056            return
3057        else
3058            data.condensed = true
3059        end
3060        local resources = data.resources
3061        local condensed = 0
3062        local function condense(what)
3063            local lookups = resources[what]
3064            if lookups then
3065                for i=1,#lookups do
3066                    condensed = condensed + condensesteps_1(lookups[i])
3067                end
3068            elseif trace_optimizations then
3069                report_optimizations("no lookups in %a",what)
3070            end
3071        end
3072        condense("sequences")
3073        condense("sublookups")
3074        if trace_optimizations then
3075            if condensed > 0 then
3076                report_optimizations("%i ligatures condensed",condensed)
3077            end
3078        end
3079    end
3080
3081end
3082
3083local function mergesteps(t,k)
3084    if k == "merged" then
3085        local merged = { }
3086        for i=1,#t do
3087            local step     = t[i]
3088            local coverage = step.coverage
3089            for k in next, coverage do
3090                local m = merged[k]
3091                if m then
3092                    m[2] = i
3093                 -- m[#m+1] = step
3094                else
3095                    merged[k] = { i, i }
3096                 -- merged[k] = { step }
3097                end
3098            end
3099        end
3100        t.merged = merged
3101        return merged
3102    end
3103end
3104
3105local function checkmerge(sequence)
3106    local steps = sequence.steps
3107    if steps then
3108        setmetatableindex(steps,mergesteps)
3109    end
3110end
3111
3112local function checkflags(sequence,resources)
3113    if not sequence.skiphash then
3114        local flags = sequence.flags
3115        if flags then
3116            local skipmark     = flags[1]
3117            local skipligature = flags[2]
3118            local skipbase     = flags[3]
3119            local markclass    = sequence.markclass
3120            local skipsome     = skipmark or skipligature or skipbase or markclass or false
3121            if skipsome then
3122                sequence.skiphash = setmetatableindex(function(t,k)
3123                    local c = resources.classes[k] -- delayed table
3124                    local v = c == skipmark
3125                           or (markclass and c == "mark" and not markclass[k])
3126                           or c == skipligature
3127                           or c == skipbase
3128                           or false
3129                    t[k] = v
3130                    return v
3131                end)
3132            else
3133                sequence.skiphash = false
3134            end
3135        else
3136            sequence.skiphash = false
3137        end
3138    end
3139end
3140
3141local function checksteps(sequence)
3142    local steps = sequence.steps
3143    if steps then
3144        for i=1,#steps do
3145            steps[i].index = i
3146        end
3147    end
3148end
3149
3150if fonts.helpers then
3151    fonts.helpers.checkmerge = checkmerge
3152    fonts.helpers.checkflags = checkflags
3153    fonts.helpers.checksteps = checksteps -- has to happen last
3154end
3155
3156function readers.expand(data)
3157    if not data or data.expanded then
3158        return
3159    else
3160        data.expanded = true
3161    end
3162    local resources    = data.resources
3163    local sublookups   = resources.sublookups
3164    local sequences    = resources.sequences -- were one level up
3165    local markclasses  = resources.markclasses
3166    local descriptions = data.descriptions
3167    if descriptions then
3168        local defaultwidth  = resources.defaultwidth  or 0
3169     -- local defaultheight = resources.defaultheight or 0
3170     -- local defaultdepth  = resources.defaultdepth  or 0
3171        local basename      = trace_markwidth and file.basename(resources.filename)
3172        for u, d in next, descriptions do
3173            local bb = d.boundingbox
3174            local wd = d.width
3175            if d.class == "mark" then
3176                if trace_markwidth and wd ~= 0 then
3177                    report_markwidth("mark %a with width %p found in %a",d.name or "<noname>",wd,basename)
3178                end
3179                d.width = 0
3180            elseif not wd then
3181                d.width = defaultwidth
3182            end
3183            if bb then
3184                local ht =  bb[4]
3185                local dp = -bb[2]
3186                if ht == 0 or ht < 0 then
3187                    -- not set
3188                else
3189                    d.height = ht
3190                end
3191                if dp == 0 or dp < 0 then
3192                    -- not set
3193                else
3194                    d.depth  = dp
3195                end
3196            end
3197        end
3198    end
3199
3200    -- using a merged combined hash as first test saves some 30% on ebgaramond and
3201    -- about 15% on arabtype .. then moving the a test also saves a bit (even when
3202    -- often a is not set at all so that one is a bit debatable
3203
3204    local function expandlookups(sequences,whatever)
3205        if sequences then
3206            -- we also need to do sublookups
3207            for i=1,#sequences do
3208                local sequence = sequences[i]
3209                local steps    = sequence.steps
3210                if steps then
3211                    local nofsteps = sequence.nofsteps
3212
3213                    local kind = sequence.type
3214                    local markclass = sequence.markclass
3215                    if markclass then
3216                        if not markclasses then
3217                            report_warning("missing markclasses")
3218                            sequence.markclass = false
3219                        else
3220                            sequence.markclass = markclasses[markclass]
3221                        end
3222                    end
3223
3224                    for i=1,nofsteps do
3225                        local step = steps[i]
3226                        local baseclasses = step.baseclasses
3227                        if baseclasses then
3228                            local coverage = step.coverage
3229                            for k, v in next, coverage do
3230                                v[1] = baseclasses[v[1]] -- slot 1 is a placeholder
3231                            end
3232                        elseif kind == "gpos_cursive" then
3233                            local coverage = step.coverage
3234                            for k, v in next, coverage do
3235                                v[1] = coverage -- slot 1 is a placeholder
3236                            end
3237                        end
3238                        local rules = step.rules
3239                        if rules then
3240                            local rulehash   = { n = 0 } -- is contexts in font-ots
3241                            local rulesize   = 0
3242                            local coverage   = { }
3243                            local lookuptype = sequence.type
3244                            local nofrules   = #rules
3245                            step.coverage    = coverage -- combined hits
3246                            for currentrule=1,nofrules do
3247                                local rule         = rules[currentrule]
3248                                local current      = rule.current
3249                                local before       = rule.before
3250                                local after        = rule.after
3251                                local replacements = rule.replacements or false
3252                                local sequence     = { }
3253                                local nofsequences = 0
3254                                if before then
3255                                    for n=1,#before do
3256                                        nofsequences = nofsequences + 1
3257                                        sequence[nofsequences] = before[n]
3258                                    end
3259                                end
3260                                local start = nofsequences + 1
3261                                for n=1,#current do
3262                                    nofsequences = nofsequences + 1
3263                                    sequence[nofsequences] = current[n]
3264                                end
3265                                local stop = nofsequences
3266                                if after then
3267                                    for n=1,#after do
3268                                        nofsequences = nofsequences + 1
3269                                        sequence[nofsequences] = after[n]
3270                                    end
3271                                end
3272                                local lookups = rule.lookups or false
3273                                local subtype = nil
3274                                if lookups then
3275                                    for i=1,#lookups do
3276                                        local lookups = lookups[i]
3277                                        if lookups then
3278                                            for k, v in next, lookups do -- actually this one is indexed
3279                                                local lookup = sublookups[v]
3280if not lookup and whatever then
3281    lookup = whatever[v]
3282end
3283                                                if lookup then
3284                                                    lookups[k] = lookup
3285                                                    if not subtype then
3286                                                        subtype = lookup.type
3287                                                    end
3288                                                else
3289                                                    -- already expanded
3290                                                end
3291                                            end
3292                                        end
3293                                    end
3294                                end
3295                                if sequence[1] then -- we merge coverage into one
3296                                    sequence.n = #sequence -- tiny speedup
3297                                    local ruledata = {
3298                                        currentrule,  -- 1 -- original rule number, only use this for tracing!
3299                                        lookuptype,   -- 2
3300                                        sequence,     -- 3
3301                                        start,        -- 4
3302                                        stop,         -- 5
3303                                        lookups,      -- 6 (6/7 also signal of what to do)
3304                                        replacements, -- 7
3305                                        subtype,      -- 8
3306                                    }
3307                                    --
3308                                    -- possible optimization: per [unic] a rulehash, but beware:
3309                                    -- contexts have unique coverage and chains can have multiple
3310                                    -- hits (rules) per coverage entry
3311                                    --
3312                                    -- so: we can combine multiple steps as well as multiple rules
3313                                    -- but that takes careful checking, in which case we can go the
3314                                    -- step list approach and turn contexts into steps .. in fact,
3315                                    -- if we turn multiple contexts into steps we're already ok as
3316                                    -- steps gets a coverage hash by metatable
3317                                    --
3318                                    rulesize = rulesize + 1
3319                                    rulehash[rulesize] = ruledata
3320                                    rulehash.n = rulesize -- tiny speedup
3321                                    --
3322                                    if true then -- nofrules > 1
3323
3324                                        for unic in next, sequence[start] do
3325                                            local cu = coverage[unic]
3326                                            if cu then
3327                                                local n = #cu+1
3328                                                cu[n] = ruledata
3329                                                cu.n = n
3330                                            else
3331                                                coverage[unic] = { ruledata, n = 1 }
3332                                            end
3333                                        end
3334
3335                                    else
3336
3337                                        for unic in next, sequence[start] do
3338                                            local cu = coverage[unic]
3339                                            if cu then
3340                                                -- we can have a contextchains with many matches which we
3341                                                -- can actually optimize
3342                                            else
3343                                                coverage[unic] = rulehash
3344                                            end
3345                                        end
3346
3347                                    end
3348                                end
3349                            end
3350                        end
3351                    end
3352
3353                    checkmerge(sequence)
3354                    checkflags(sequence,resources)
3355                    checksteps(sequence)
3356
3357                end
3358            end
3359        end
3360    end
3361
3362    expandlookups(sequences)
3363    expandlookups(sublookups,sequences)
3364end
3365