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