font-dsp.lua /size: 154 Kb    last modification: 2025-02-21 11:03
1if not modules then modules = { } end modules ['font-dsp'] = {
2    version   = 1.001,
3    optimize  = true,
4    comment   = "companion to font-ini.mkiv",
5    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
6    copyright = "PRAGMA ADE / ConTeXt Development Team",
7    license   = "see context related readme files"
8}
9
10-- many 0,0 entry/exit
11
12-- This loader went through a few iterations. First I made a ff compatible one so
13-- that we could do some basic checking. Also some verbosity was added (named
14-- glyphs). Eventually all that was dropped for a context friendly format, simply
15-- because keeping the different table models in sync too to much time. I have the
16-- old file somewhere. A positive side effect is that we get an (upto) much smaller
17-- smaller tma/tmc file. In the end the loader will be not much slower than the
18-- c based ff one.
19
20-- Being binary encoded, an opentype is rather compact. When expanded into a Lua table
21-- quite some memory can be used. This is very noticeable in the ff loader, which for
22-- a good reason uses a verbose format. However, when we use that data we create a couple
23-- of hashes. In the Lua loader we create these hashes directly, which save quite some
24-- memory.
25--
26-- We convert a font file only once and then cache it. Before creating the cached instance
27-- packing takes place: common tables get shared. After (re)loading and unpacking we then
28-- get a rather efficient internal representation of the font. In the new loader there is a
29-- pitfall. Because we use some common coverage magic we put a bit more information in
30-- the mark and cursive coverage tables than strickly needed: a reference to the coverage
31-- itself. This permits a fast lookup of the second glyph involved. In the marks we
32-- expand the class indicator to a class hash, in the cursive we use a placeholder that gets
33-- a self reference. This means that we cannot pack these subtables unless we add a unique
34-- id per entry (the same one per coverage) and that makes the tables larger. Because only a
35-- few fonts benefit from this, I decided to not do this. Experiments demonstrated that it
36-- only gives a few percent gain (on for instance husayni we can go from 845K to 828K
37-- bytecode). Better stay conceptually clean than messy compact.
38
39-- When we can reduce all basic lookups to one step we might safe a bit in the processing
40-- so then only chains are multiple.
41
42-- I used to flatten kerns here but that has been moved elsewhere because it polutes the code
43-- here and can be done fast afterwards. One can even wonder if it makes sense to do it as we
44-- pack anyway. In a similar fashion the unique placeholders in anchors in marks have been
45-- removed because packing doesn't save much there anyway.
46
47-- Although we have a bit more efficient tables in the cached files, the internals are still
48-- pretty similar. And although we have a slightly more direct coverage access the processing
49-- of node lists is not noticeable faster for latin texts, but for arabic we gain some 10%
50-- (and could probably gain a bit more).
51
52-- All this packing in the otf format is somewhat obsessive as nowadays 4K resolution
53-- multi-gig videos pass through our networks and storage and memory is abundant.
54
55-- Although we use a few table readers there i sno real gain in there (apart from having
56-- less code. After all there are often not that many demanding features.
57
58local next, type, tonumber = next, type, tonumber
59local band = bit32.band
60local extract = bit32.extract
61local bor = bit32.bor
62local lshift = bit32.lshift
63local rshift = bit32.rshift
64local gsub = string.gsub
65local lower = string.lower
66local sub = string.sub
67local strip = string.strip
68local tohash = table.tohash
69local concat = table.concat
70local copy = table.copy
71local reversed = table.reversed
72local sort = table.sort
73local insert = table.insert
74local round = math.round
75
76local settings_to_hash  = utilities.parsers.settings_to_hash_colon_too
77local setmetatableindex = table.setmetatableindex
78local formatters        = string.formatters
79local sortedkeys        = table.sortedkeys
80local sortedhash        = table.sortedhash
81local sequenced         = table.sequenced
82
83local report            = logs.reporter("otf reader")
84
85local readers           = fonts.handlers.otf.readers
86local streamreader      = readers.streamreader
87
88local setposition       = streamreader.setposition
89local getposition       = streamreader.getposition
90local readuinteger      = streamreader.readcardinal1
91local readushort        = streamreader.readcardinal2
92local readulong         = streamreader.readcardinal4
93local readinteger       = streamreader.readinteger1
94local readshort         = streamreader.readinteger2
95local readstring        = streamreader.readstring
96local readtag           = streamreader.readtag
97local readbytes         = streamreader.readbytes
98local readfixed         = streamreader.readfixed4
99local read2dot14        = streamreader.read2dot14
100local skipshort         = streamreader.skipshort
101local skipbytes         = streamreader.skip
102local readbytetable     = streamreader.readbytetable
103local readbyte          = streamreader.readbyte
104local readcardinaltable = streamreader.readcardinaltable
105local readintegertable  = streamreader.readintegertable
106local readfword         = readshort
107
108local short  = 2
109local ushort = 2
110local ulong  = 4
111
112directives.register("fonts.streamreader",function()
113
114    streamreader      = utilities.streams
115
116    setposition       = streamreader.setposition
117    getposition       = streamreader.getposition
118    readuinteger      = streamreader.readcardinal1
119    readushort        = streamreader.readcardinal2
120    readulong         = streamreader.readcardinal4
121    readinteger       = streamreader.readinteger1
122    readshort         = streamreader.readinteger2
123    readstring        = streamreader.readstring
124    readtag           = streamreader.readtag
125    readbytes         = streamreader.readbytes
126    readfixed         = streamreader.readfixed4
127    read2dot14        = streamreader.read2dot14
128    skipshort         = streamreader.skipshort
129    skipbytes         = streamreader.skip
130    readbytetable     = streamreader.readbytetable
131    readbyte          = streamreader.readbyte
132    readcardinaltable = streamreader.readcardinaltable
133    readintegertable  = streamreader.readintegertable
134    readfword         = readshort
135
136end)
137
138local gsubhandlers      = { }
139local gposhandlers      = { }
140
141readers.gsubhandlers    = gsubhandlers
142readers.gposhandlers    = gposhandlers
143
144local helpers           = readers.helpers
145local gotodatatable     = helpers.gotodatatable
146local setvariabledata   = helpers.setvariabledata
147
148local lookupidoffset    = -1    -- will become 1 when we migrate (only -1 for comparign with old)
149
150local classes = {
151    "base",
152    "ligature",
153    "mark",
154    "component",
155}
156
157local gsubtypes = {
158    "single",
159    "multiple",
160    "alternate",
161    "ligature",
162    "context",
163    "chainedcontext",
164    "extension",
165    "reversechainedcontextsingle",
166}
167
168local gpostypes = {
169    "single",
170    "pair",
171    "cursive",
172    "marktobase",
173    "marktoligature",
174    "marktomark",
175    "context",
176    "chainedcontext",
177    "extension",
178}
179
180local chaindirections = {
181    context                     =  0,
182    chainedcontext              =  1,
183    reversechainedcontextsingle = -1,
184}
185
186local function setmetrics(data,where,tag,d)
187    local w = data[where]
188    if w then
189        local v = w[tag]
190        if v then
191            -- it looks like some fonts set the value and not the delta
192         -- report("adding %s to %s.%s value %s",d,where,tag,v)
193            w[tag] = v + d
194        end
195    end
196end
197
198local variabletags = {
199    hasc = function(data,d) setmetrics(data,"windowsmetrics","typoascender",d) end,
200    hdsc = function(data,d) setmetrics(data,"windowsmetrics","typodescender",d) end,
201    hlgp = function(data,d) setmetrics(data,"windowsmetrics","typolinegap",d) end,
202    hcla = function(data,d) setmetrics(data,"windowsmetrics","winascent",d) end,
203    hcld = function(data,d) setmetrics(data,"windowsmetrics","windescent",d) end,
204    vasc = function(data,d) setmetrics(data,"vhea not done","ascent",d) end,
205    vdsc = function(data,d) setmetrics(data,"vhea not done","descent",d) end,
206    vlgp = function(data,d) setmetrics(data,"vhea not done","linegap",d) end,
207    xhgt = function(data,d) setmetrics(data,"windowsmetrics","xheight",d) end,
208    cpht = function(data,d) setmetrics(data,"windowsmetrics","capheight",d) end,
209    sbxs = function(data,d) setmetrics(data,"windowsmetrics","subscriptxsize",d) end,
210    sbys = function(data,d) setmetrics(data,"windowsmetrics","subscriptysize",d) end,
211    sbxo = function(data,d) setmetrics(data,"windowsmetrics","subscriptxoffset",d) end,
212    sbyo = function(data,d) setmetrics(data,"windowsmetrics","subscriptyoffset",d) end,
213    spxs = function(data,d) setmetrics(data,"windowsmetrics","superscriptxsize",d) end,
214    spys = function(data,d) setmetrics(data,"windowsmetrics","superscriptysize",d) end,
215    spxo = function(data,d) setmetrics(data,"windowsmetrics","superscriptxoffset",d) end,
216    spyo = function(data,d) setmetrics(data,"windowsmetrics","superscriptyoffset",d) end,
217    strs = function(data,d) setmetrics(data,"windowsmetrics","strikeoutsize",d) end,
218    stro = function(data,d) setmetrics(data,"windowsmetrics","strikeoutpos",d) end,
219    unds = function(data,d) setmetrics(data,"postscript","underlineposition",d) end,
220    undo = function(data,d) setmetrics(data,"postscript","underlinethickness",d) end,
221}
222
223local read_cardinal = {
224    streamreader.readcardinal1,
225    streamreader.readcardinal2,
226    streamreader.readcardinal3,
227    streamreader.readcardinal4,
228}
229
230local read_integer = {
231    streamreader.readinteger1,
232    streamreader.readinteger2,
233    streamreader.readinteger3,
234    streamreader.readinteger4,
235}
236
237directives.register("fonts.streamreader",function()
238
239    read_cardinal = {
240        streamreader.readcardinal1,
241        streamreader.readcardinal2,
242        streamreader.readcardinal3,
243        streamreader.readcardinal4,
244    }
245
246    read_integer = {
247        streamreader.readinteger1,
248        streamreader.readinteger2,
249        streamreader.readinteger3,
250        streamreader.readinteger4,
251    }
252
253end)
254
255-- Traditionally we use these unique names (so that we can flatten the lookup list
256-- (we create subsets runtime) but I will adapt the old code to newer names.
257
258-- chainsub
259-- reversesub
260
261local lookupnames = {
262    gsub = {
263        single                      = "gsub_single",
264        multiple                    = "gsub_multiple",
265        alternate                   = "gsub_alternate",
266        ligature                    = "gsub_ligature",
267        context                     = "gsub_context",
268        chainedcontext              = "gsub_contextchain",
269        reversechainedcontextsingle = "gsub_reversecontextchain", -- reversesub
270    },
271    gpos = {
272        single                      = "gpos_single",
273        pair                        = "gpos_pair",
274        cursive                     = "gpos_cursive",
275        marktobase                  = "gpos_mark2base",
276        marktoligature              = "gpos_mark2ligature",
277        marktomark                  = "gpos_mark2mark",
278        context                     = "gpos_context",
279        chainedcontext              = "gpos_contextchain",
280    }
281}
282
283-- keep this as reference:
284--
285-- local lookupbits = {
286--     [0x0001] = "righttoleft",
287--     [0x0002] = "ignorebaseglyphs",
288--     [0x0004] = "ignoreligatures",
289--     [0x0008] = "ignoremarks",
290--     [0x0010] = "usemarkfilteringset",
291--     [0x00E0] = "reserved",
292--     [0xFF00] = "markattachmenttype",
293-- }
294--
295-- local lookupstate = setmetatableindex(function(t,k)
296--     local v = { }
297--     for kk, vv in next, lookupbits do
298--         if band(k,kk) ~= 0 then
299--             v[vv] = true
300--         end
301--     end
302--     t[k] = v
303--     return v
304-- end)
305
306local lookupflags = setmetatableindex(function(t,k)
307    local v = {
308        band(k,0x0008) ~= 0 and true or false, -- ignoremarks
309        band(k,0x0004) ~= 0 and true or false, -- ignoreligatures
310        band(k,0x0002) ~= 0 and true or false, -- ignorebaseglyphs
311        band(k,0x0001) ~= 0 and true or false, -- r2l
312    }
313    t[k] = v
314    return v
315end)
316
317-- Variation stores: it's not entirely clear if the regions are a shared
318-- resource (it looks like they are). Anyway, we play safe and use a
319-- share.
320
321-- values can be anything the min/max permits so we can either think of
322-- real values of a fraction along the axis (probably easier)
323
324-- wght=400,wdth=100,ital=1
325
326local function axistofactors(str)
327    local t = settings_to_hash(str)
328    for k, v in next, t do
329        t[k] = tonumber(v) or v -- this also normalizes numbers itself
330    end
331    return t
332end
333
334local hash = table.setmetatableindex(function(t,k)
335    local v = sequenced(axistofactors(k),",")
336    t[k] = v
337    return v
338end)
339
340helpers.normalizedaxishash = hash
341
342local cleanname = fonts.names and fonts.names.cleanname or function(name)
343    return name and (gsub(lower(name),"[^%a%d]","")) or nil
344end
345
346helpers.cleanname = cleanname
347
348function helpers.normalizedaxis(str)
349    return hash[str] or str
350end
351
352-- contradicting spec ... (signs) so i'll check it and fix it once we have
353-- proper fonts
354
355local function getaxisscale(segments,minimum,default,maximum,user)
356    --
357    -- returns the right values cf example in standard
358    --
359    if not minimum or not default or not maximum then
360        return false
361    end
362    if user < minimum then
363        user = minimum
364    elseif user > maximum then
365        user = maximum
366    end
367    if user < default then
368        default = - (default - user) / (default - minimum)
369    elseif user > default then
370        default = (user - default) / (maximum - default)
371    else
372        default = 0
373    end
374    if not segments then
375        return default
376    end
377    local e
378    for i=1,#segments do
379        local s = segments[i]
380        if type(s) ~= "number" then
381         -- report("using default axis scale")
382            return default
383        elseif s[1] >= default then
384            if s[2] == default then
385                return default
386            else
387                e = i
388                break
389            end
390        end
391    end
392    if e then
393        local b = segments[e-1]
394        local e = segments[e]
395        return b[2] + (e[2] - b[2]) * (default - b[1]) / (e[1] - b[1])
396    else
397        return false
398    end
399end
400
401local function getfactors(data,instancespec)
402    if instancespec == true then
403        -- take default
404    elseif type(instancespec) ~= "string" or instancespec == "" then
405        return
406    end
407    local variabledata = data.variabledata
408    if not variabledata then
409        return
410    end
411    local instances = variabledata.instances
412    local axis      = variabledata.axis
413    local segments  = variabledata.segments
414    if instances and axis then
415        local values
416        if instancespec == true then
417            -- first instance:
418         -- values = instances[1].values
419            -- axis defaults:
420            values = { }
421            for i=1,#axis do
422                values[i] = {
423                 -- axis  = axis[i].tag,
424                    value = axis[i].default,
425                }
426            end
427
428        else
429            for i=1,#instances do
430                local instance = instances[i]
431                if cleanname(instance.subfamily) == instancespec then
432                    values = instance.values
433                    break
434                end
435            end
436        end
437        if values then
438            local factors = { }
439            for i=1,#axis do
440                local a = axis[i]
441                factors[i] = getaxisscale(segments,a.minimum,a.default,a.maximum,values[i].value)
442            end
443            return factors
444        end
445        local values = axistofactors(hash[instancespec] or instancespec)
446        if values then
447            local factors = { }
448            for i=1,#axis do
449                local a = axis[i]
450                local d = a.default
451                factors[i] = getaxisscale(segments,a.minimum,d,a.maximum,values[a.name or a.tag] or d)
452            end
453            return factors
454        end
455    end
456end
457
458local function getscales(regions,factors)
459    local scales = { }
460    for i=1,#regions do
461        local region = regions[i]
462        local s = 1
463        for j=1,#region do
464            local axis  = region[j]
465            local f     = factors[j]
466            local start = axis.start
467            local peak  = axis.peak
468            local stop  = axis.stop
469            -- get rid of these tests, false flag
470            if start > peak or peak > stop then
471                -- * 1
472            elseif start < 0 and stop > 0 and peak ~= 0 then
473                -- * 1
474            elseif peak == 0 then
475                -- * 1
476            elseif f < start or f > stop then
477                -- * 0
478                s = 0
479                break
480            elseif f < peak then
481             -- s = - s * (f - start) / (peak - start)
482                s = s * (f - start) / (peak - start)
483            elseif f > peak then
484                s = s * (stop - f) / (stop - peak)
485            else
486                -- * 1
487            end
488        end
489        scales[i] = s
490    end
491    return scales
492end
493
494helpers.getaxisscale  = getaxisscale
495helpers.getfactors    = getfactors
496helpers.getscales     = getscales
497helpers.axistofactors = axistofactors
498
499local function readvariationdata(f,storeoffset,factors) -- store
500    local position = getposition(f)
501    setposition(f,storeoffset)
502    -- header
503    local format       = readushort(f)
504    local regionoffset = storeoffset + readulong(f)
505    local nofdeltadata = readushort(f)
506    local deltadata    = readcardinaltable(f,nofdeltadata,ulong)
507    -- regions
508    setposition(f,regionoffset)
509    local nofaxis    = readushort(f)
510    local nofregions = readushort(f)
511    local regions    = { }
512    for i=1,nofregions do -- 0
513        local t = { }
514        for i=1,nofaxis do
515            t[i] = { -- maybe no keys, just 1..3
516                start = read2dot14(f),
517                peak  = read2dot14(f),
518                stop  = read2dot14(f),
519            }
520        end
521        regions[i] = t
522    end
523    -- deltas
524 -- if factors then
525        for i=1,nofdeltadata do
526            setposition(f,storeoffset+deltadata[i])
527            local nofdeltasets = readushort(f)
528            local nofshorts    = readushort(f)
529            local nofregions   = readushort(f)
530            local usedregions  = { }
531            local deltas       = { }
532            for i=1,nofregions do
533                usedregions[i] = regions[readushort(f)+1]
534            end
535            -- we could test before and save a for
536            for i=1,nofdeltasets do
537                local t = readintegertable(f,nofshorts,short)
538                for i=nofshorts+1,nofregions do
539                    t[i] = readinteger(f)
540                end
541                deltas[i] = t
542            end
543            deltadata[i] = {
544                regions = usedregions,
545                deltas  = deltas,
546                scales  = factors and getscales(usedregions,factors) or nil,
547            }
548        end
549 -- end
550    setposition(f,position)
551    return regions, deltadata
552end
553
554helpers.readvariationdata = readvariationdata
555
556-- Beware: only use the simple variant if we don't set keys/values (otherwise too many entries). We
557-- could also have a variant that applies a function but there is no real benefit in this.
558
559local function readcoverage(f,offset,simple)
560    setposition(f,offset)
561    local coverageformat = readushort(f)
562    if coverageformat == 1 then
563        local nofcoverage = readushort(f)
564        if simple then
565            -- often 1 or 2
566            if nofcoverage == 1 then
567                return { readushort(f) }
568            elseif nofcoverage == 2 then
569                return { readushort(f), readushort(f) }
570            else
571                return readcardinaltable(f,nofcoverage,ushort)
572            end
573        elseif nofcoverage == 1 then
574            return { [readushort(f)] = 0 }
575        elseif nofcoverage == 2 then
576            return { [readushort(f)] = 0, [readushort(f)] = 1 }
577        else
578            local coverage = { }
579            for i=0,nofcoverage-1 do
580                coverage[readushort(f)] = i -- index in record
581            end
582            return coverage
583        end
584    elseif coverageformat == 2 then
585        local nofranges = readushort(f)
586        local coverage  = { }
587        local n         = simple and 1 or 0 -- needs checking
588        for i=1,nofranges do
589            local firstindex = readushort(f)
590            local lastindex  = readushort(f)
591            local coverindex = readushort(f)
592            if simple then
593                for i=firstindex,lastindex do
594                    coverage[n] = i
595                    n = n + 1
596                end
597            else
598                for i=firstindex,lastindex do
599                    coverage[i] = n
600                    n = n + 1
601                end
602            end
603        end
604        return coverage
605    else
606        report("unknown coverage format %a ",coverageformat)
607        return { }
608    end
609end
610
611local function readclassdef(f,offset,preset)
612    setposition(f,offset)
613    local classdefformat = readushort(f)
614    local classdef = { }
615    if type(preset) == "number" then
616        for k=0,preset-1 do
617            classdef[k] = 1
618        end
619    end
620    if classdefformat == 1 then
621        local index       = readushort(f)
622        local nofclassdef = readushort(f)
623        for i=1,nofclassdef do
624            classdef[index] = readushort(f) + 1
625            index = index + 1
626        end
627    elseif classdefformat == 2 then
628        local nofranges = readushort(f)
629        local n = 0
630        for i=1,nofranges do
631            local firstindex = readushort(f)
632            local lastindex  = readushort(f)
633            local class      = readushort(f) + 1
634            for i=firstindex,lastindex do
635                classdef[i] = class
636            end
637        end
638    else
639        report("unknown classdef format %a ",classdefformat)
640    end
641    if type(preset) == "table" then
642        for k in next, preset do
643            if not classdef[k] then
644                classdef[k] = 1
645            end
646        end
647    end
648    return classdef
649end
650
651local function classtocoverage(defs)
652    if defs then
653        local list = { }
654        for index, class in next, defs do
655            local c = list[class]
656            if c then
657                c[#c+1] = index
658            else
659                list[class] = { index }
660            end
661        end
662        return list
663    end
664end
665
666-- extra readers
667
668local skips = { [0] =
669    0, -- ----
670    1, -- ---x
671    1, -- --y-
672    2, -- --yx
673    1, -- -h--
674    2, -- -h-x
675    2, -- -hy-
676    3, -- -hyx
677    2, -- v--x
678    2, -- v-y-
679    3, -- v-yx
680    2, -- vh--
681    3, -- vh-x
682    3, -- vhy-
683    4, -- vhyx
684}
685
686-- We can assume that 0 is nothing and in fact we can start at 1 as
687-- usual in Lua to make sure of that.
688
689local function readvariation(f,offset)
690    local p = getposition(f)
691    setposition(f,offset)
692    local outer  = readushort(f)
693    local inner  = readushort(f)
694    local format = readushort(f)
695    setposition(f,p)
696    if format == 0x8000 then
697        return outer, inner
698    end
699end
700
701local function readposition(f,format,mainoffset,getdelta)
702    if format == 0 then
703        return false
704    end
705    -- a few happen often
706    if format == 0x04 then
707        local h = readshort(f)
708        if h == 0 then
709            return true -- all zero
710        else
711            return { 0, 0, h, 0 }
712        end
713    end
714    if format == 0x05 then
715        local x = readshort(f)
716        local h = readshort(f)
717        if x == 0 and h == 0 then
718            return true -- all zero
719        else
720            return { x, 0, h, 0 }
721        end
722    end
723    if format == 0x44 then
724        local h = readshort(f)
725        if getdelta then
726            local d = readshort(f) -- short or ushort
727            if d > 0 then
728                local outer, inner = readvariation(f,mainoffset+d)
729                if outer then
730                    h = h + getdelta(outer,inner)
731                end
732            end
733        else
734            skipshort(f,1)
735        end
736        if h == 0 then
737            return true -- all zero
738        else
739            return { 0, 0, h, 0 }
740        end
741    end
742    --
743    -- todo:
744    --
745    -- if format == 0x55 then
746    --     local x = readshort(f)
747    --     local h = readshort(f)
748    --     ....
749    -- end
750    --
751    local x = band(format,0x1) ~= 0 and readshort(f) or 0 -- x placement
752    local y = band(format,0x2) ~= 0 and readshort(f) or 0 -- y placement
753    local h = band(format,0x4) ~= 0 and readshort(f) or 0 -- h advance
754    local v = band(format,0x8) ~= 0 and readshort(f) or 0 -- v advance
755    if format >= 0x10 then
756        local X = band(format,0x10) ~= 0 and skipshort(f) or 0
757        local Y = band(format,0x20) ~= 0 and skipshort(f) or 0
758        local H = band(format,0x40) ~= 0 and skipshort(f) or 0
759        local V = band(format,0x80) ~= 0 and skipshort(f) or 0
760        local s = skips[extract(format,4,4)]
761        if s > 0 then
762            skipshort(f,s)
763        end
764        if getdelta then
765            if X > 0 then
766                local outer, inner = readvariation(f,mainoffset+X)
767                if outer then
768                    x = x + getdelta(outer,inner)
769                end
770            end
771            if Y > 0 then
772                local outer, inner = readvariation(f,mainoffset+Y)
773                if outer then
774                    y = y + getdelta(outer,inner)
775                end
776            end
777            if H > 0 then
778                local outer, inner = readvariation(f,mainoffset+H)
779                if outer then
780                    h = h + getdelta(outer,inner)
781                end
782            end
783            if V > 0 then
784                local outer, inner = readvariation(f,mainoffset+V)
785                if outer then
786                    v = v + getdelta(outer,inner)
787                end
788            end
789        end
790        return { x, y, h, v }
791    elseif x == 0 and y == 0 and h == 0 and v == 0 then
792        return true -- all zero
793    else
794        return { x, y, h, v }
795    end
796end
797
798local function readanchor(f,offset,getdelta) -- maybe also ignore 0's as in pos
799    if not offset or offset == 0 then
800        return nil -- false
801    end
802    setposition(f,offset)
803    -- no need to skip as we position each
804    local format = readshort(f) -- 1: x y 2: x y index 3 x y X Y
805    local x = readshort(f)
806    local y = readshort(f)
807    if format == 3 then
808        if getdelta then
809            local X = readshort(f)
810            local Y = readshort(f)
811            if X > 0 then
812                local outer, inner = readvariation(f,offset+X)
813                if outer then
814                    x = x + getdelta(outer,inner)
815                end
816            end
817            if Y > 0 then
818                local outer, inner = readvariation(f,offset+Y)
819                if outer then
820                    y = y + getdelta(outer,inner)
821                end
822            end
823        else
824            skipshort(f,2)
825        end
826        return { x, y } -- , { xindex, yindex }
827    else
828        return { x, y }
829    end
830end
831
832-- common handlers: inlining can be faster but we cache anyway
833-- so we don't bother too much about speed here
834
835local function readfirst(f,offset)
836    if offset then
837        setposition(f,offset)
838    end
839    return { readushort(f) }
840end
841
842-- quite often 0, 1, 2
843
844local function readarray(f,offset)
845    if offset then
846        setposition(f,offset)
847    end
848    local n = readushort(f)
849    if n == 1 then
850        return { readushort(f) }, 1
851    elseif n > 0 then
852        return readcardinaltable(f,n,ushort), n
853    end
854end
855
856local function readcoveragearray(f,offset,t,simple)
857    if not t then
858        return nil
859    end
860    local n = #t
861    if n == 0 then
862        return nil
863    end
864    for i=1,n do
865        t[i] = readcoverage(f,offset+t[i],simple)
866    end
867    return t
868end
869
870local function covered(subset,all)
871    local used, u
872    for i=1,#subset do
873        local s = subset[i]
874        if all[s] then
875            if used then
876                u = u + 1
877                used[u] = s
878            else
879                u = 1
880                used = { s }
881            end
882        end
883    end
884    return used
885end
886
887-- We generalize the chained lookups so that we can do with only one handler
888-- when processing them.
889
890-- pruned
891
892local function readlookuparray(f,noflookups,nofcurrent)
893    local lookups = { }
894    if noflookups > 0 then
895        local length = 0
896        for i=1,noflookups do
897            local index = readushort(f) + 1
898            if index > length then
899                length = index
900            end
901            local lookup = readushort(f) + 1
902            local list   = lookups[index]
903            if list then
904                list[#list+1] = lookup
905            else
906                lookups[index] = { lookup }
907            end
908        end
909        for index=1,length do
910            if not lookups[index] then
911                lookups[index] = false
912            end
913        end
914     -- if length > nofcurrent then
915     --     report("more lookups than currently matched characters")
916     -- end
917    end
918    return lookups
919end
920
921-- not pruned
922--
923-- local function readlookuparray(f,noflookups,nofcurrent)
924--     local lookups = { }
925--     for i=1,nofcurrent do
926--         lookups[i] = false
927--     end
928--     for i=1,noflookups do
929--         local index = readushort(f) + 1
930--         if index > nofcurrent then
931--             report("more lookups than currently matched characters")
932--             for i=nofcurrent+1,index-1 do
933--                 lookups[i] = false
934--             end
935--             nofcurrent = index
936--         end
937--         lookups[index] = readushort(f) + 1
938--     end
939--     return lookups
940-- end
941
942local function unchainedcontext(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs,what)
943    local tableoffset = lookupoffset + offset
944    setposition(f,tableoffset)
945    local subtype = readushort(f)
946    if subtype == 1 then
947        local coverage     = readushort(f)
948        local subclasssets = readarray(f)
949        local rules        = { }
950        if subclasssets then
951            coverage = readcoverage(f,tableoffset+coverage,true)
952            for i=1,#subclasssets do
953                local offset = subclasssets[i]
954                if offset > 0 then
955                    local firstcoverage = coverage[i]
956                    local rulesoffset   = tableoffset + offset
957                    local subclassrules = readarray(f,rulesoffset)
958                    for rule=1,#subclassrules do
959                        setposition(f,rulesoffset + subclassrules[rule])
960                        local nofcurrent = readushort(f)
961                        local noflookups = readushort(f)
962                        local current    = { { firstcoverage } }
963                        for i=2,nofcurrent do
964                            current[i] = { readushort(f) }
965                        end
966                        local lookups = readlookuparray(f,noflookups,nofcurrent)
967                        rules[#rules+1] = {
968                            current = current,
969                            lookups = lookups
970                        }
971                    end
972                end
973            end
974        else
975            report("empty subclassset in %a subtype %i","unchainedcontext",subtype)
976        end
977        return {
978            format = "glyphs",
979            rules  = rules,
980        }
981    elseif subtype == 2 then
982        -- We expand the classes as later on we do a pack over the whole table so then we get
983        -- back efficiency. This way we can also apply the coverage to the first current.
984        local coverage        = readushort(f)
985        local currentclassdef = readushort(f)
986        local subclasssets    = readarray(f)
987        local rules           = { }
988        if subclasssets then
989            coverage             = readcoverage(f,tableoffset + coverage)
990            currentclassdef      = readclassdef(f,tableoffset + currentclassdef,coverage)
991            local currentclasses = classtocoverage(currentclassdef,fontdata.glyphs)
992            for class=1,#subclasssets do
993                local offset = subclasssets[class]
994                if offset > 0 then
995                    local firstcoverage = currentclasses[class]
996                    if firstcoverage then
997                        firstcoverage = covered(firstcoverage,coverage) -- bonus
998                        if firstcoverage then
999                            local rulesoffset   = tableoffset + offset
1000                            local subclassrules = readarray(f,rulesoffset)
1001                            for rule=1,#subclassrules do
1002                                setposition(f,rulesoffset + subclassrules[rule])
1003                                local nofcurrent = readushort(f)
1004                                local noflookups = readushort(f)
1005                                local current    = { firstcoverage }
1006                                for i=2,nofcurrent do
1007                                    current[i] = currentclasses[readushort(f) + 1]
1008                                end
1009                                local lookups = readlookuparray(f,noflookups,nofcurrent)
1010                                rules[#rules+1] = {
1011                                    current = current,
1012                                    lookups = lookups
1013                                }
1014                            end
1015                        else
1016                            report("no coverage")
1017                        end
1018                    else
1019                        report("no coverage class")
1020                    end
1021                end
1022            end
1023        else
1024            report("empty subclassset in %a subtype %i","unchainedcontext",subtype)
1025        end
1026        return {
1027            format = "class",
1028            rules  = rules,
1029        }
1030    elseif subtype == 3 then
1031        local nofglyphs  = readushort(f)
1032        local noflookups = readushort(f)
1033        local current    = readcardinaltable(f,nofglyphs,ushort)
1034        local lookups    = readlookuparray(f,noflookups,#current)
1035        current = readcoveragearray(f,tableoffset,current,true)
1036        return {
1037            format = "coverage",
1038            rules  = {
1039                {
1040                    current = current,
1041                    lookups = lookups,
1042                }
1043            }
1044        }
1045    else
1046        report("unsupported subtype %a in %a %s",subtype,"unchainedcontext",what)
1047    end
1048end
1049
1050-- todo: optimize for n=1 ?
1051
1052-- class index needs checking, probably no need for +1
1053
1054local function chainedcontext(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs,what)
1055    local tableoffset = lookupoffset + offset
1056    setposition(f,tableoffset)
1057    local subtype = readushort(f)
1058    if subtype == 1 then
1059        local coverage     = readushort(f)
1060        local subclasssets = readarray(f)
1061        local rules        = { }
1062        if subclasssets then
1063            coverage = readcoverage(f,tableoffset+coverage,true)
1064            for i=1,#subclasssets do
1065                local offset = subclasssets[i]
1066                if offset > 0 then
1067                    local firstcoverage = coverage[i]
1068                    local rulesoffset   = tableoffset + offset
1069                    local subclassrules = readarray(f,rulesoffset)
1070                    for rule=1,#subclassrules do
1071                        setposition(f,rulesoffset + subclassrules[rule])
1072                        local nofbefore = readushort(f)
1073                        local before
1074                        if nofbefore > 0 then
1075                            before = { }
1076                            for i=1,nofbefore do
1077                                before[i] = { readushort(f) }
1078                            end
1079                        end
1080                        local nofcurrent = readushort(f)
1081                        local current    = { { firstcoverage } }
1082                        for i=2,nofcurrent do
1083                            current[i] = { readushort(f) }
1084                        end
1085                        local nofafter = readushort(f)
1086                        local after
1087                        if nofafter > 0 then
1088                            after = { }
1089                            for i=1,nofafter do
1090                                after[i] = { readushort(f) }
1091                            end
1092                        end
1093                        local noflookups = readushort(f)
1094                        local lookups    = readlookuparray(f,noflookups,nofcurrent)
1095                        rules[#rules+1] = {
1096                            before  = before,
1097                            current = current,
1098                            after   = after,
1099                            lookups = lookups,
1100                        }
1101                    end
1102                end
1103            end
1104        else
1105            report("empty subclassset in %a subtype %i","chainedcontext",subtype)
1106        end
1107        return {
1108            format = "glyphs",
1109            rules  = rules,
1110        }
1111    elseif subtype == 2 then
1112        local coverage        = readushort(f)
1113        local beforeclassdef  = readushort(f)
1114        local currentclassdef = readushort(f)
1115        local afterclassdef   = readushort(f)
1116        local subclasssets    = readarray(f)
1117        local rules           = { }
1118        if subclasssets then
1119            local coverage        = readcoverage(f,tableoffset + coverage)
1120            local beforeclassdef  = readclassdef(f,tableoffset + beforeclassdef,nofglyphs)
1121            local currentclassdef = readclassdef(f,tableoffset + currentclassdef,coverage)
1122            local afterclassdef   = readclassdef(f,tableoffset + afterclassdef,nofglyphs)
1123            local beforeclasses   = classtocoverage(beforeclassdef,fontdata.glyphs)
1124            local currentclasses  = classtocoverage(currentclassdef,fontdata.glyphs)
1125            local afterclasses    = classtocoverage(afterclassdef,fontdata.glyphs)
1126            for class=1,#subclasssets do
1127                local offset = subclasssets[class]
1128                if offset > 0 then
1129                    local firstcoverage = currentclasses[class]
1130                    if firstcoverage then
1131                        firstcoverage = covered(firstcoverage,coverage) -- bonus
1132                        if firstcoverage then
1133                            local rulesoffset   = tableoffset + offset
1134                            local subclassrules = readarray(f,rulesoffset)
1135                            for rule=1,#subclassrules do
1136                                -- watch out, in context we first get the counts and then the arrays while
1137                                -- here we get them mixed
1138                                setposition(f,rulesoffset + subclassrules[rule])
1139                                local nofbefore = readushort(f)
1140                                local before
1141                                if nofbefore > 0 then
1142                                    before = { }
1143                                    for i=1,nofbefore do
1144                                        before[i] = beforeclasses[readushort(f) + 1]
1145                                    end
1146                                end
1147                                local nofcurrent = readushort(f)
1148                                local current    = { firstcoverage }
1149                                for i=2,nofcurrent do
1150                                    current[i] = currentclasses[readushort(f)+ 1]
1151                                end
1152                                local nofafter = readushort(f)
1153                                local after
1154                                if nofafter > 0 then
1155                                    after = { }
1156                                    for i=1,nofafter do
1157                                        after[i] = afterclasses[readushort(f) + 1]
1158                                    end
1159                                end
1160                                -- no sequence index here (so why in context as it saves nothing)
1161                                local noflookups = readushort(f)
1162                                local lookups    = readlookuparray(f,noflookups,nofcurrent)
1163                                rules[#rules+1] = {
1164                                    before  = before,
1165                                    current = current,
1166                                    after   = after,
1167                                    lookups = lookups,
1168                                }
1169                            end
1170                        else
1171                            report("no coverage")
1172                        end
1173                    else
1174                        report("class is not covered")
1175                    end
1176                end
1177            end
1178        else
1179            report("empty subclassset in %a subtype %i","chainedcontext",subtype)
1180        end
1181        return {
1182            format = "class",
1183            rules  = rules,
1184        }
1185    elseif subtype == 3 then
1186        -- Maybe this one needs checking. Anyway zero current is bad.
1187        local before     = readarray(f)
1188        local current    = readarray(f)
1189        local after      = readarray(f)
1190        local noflookups = readushort(f)
1191        local lookups    = current and readlookuparray(f,noflookups,#current)
1192        if lookups then
1193            before  = readcoveragearray(f,tableoffset,before,true)
1194            current = readcoveragearray(f,tableoffset,current,true)
1195            after   = readcoveragearray(f,tableoffset,after,true)
1196            return {
1197                format = "coverage",
1198                rules  = {
1199                    {
1200                        before  = before,
1201                        current = current,
1202                        after   = after,
1203                        lookups = lookups,
1204                    }
1205                }
1206            }
1207        else
1208            report("confusing subtype %a in %a %s",subtype,"chainedcontext",what)
1209        end
1210    else
1211        report("unsupported subtype %a in %a %s",subtype,"chainedcontext",what)
1212    end
1213end
1214
1215local function extension(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs,types,handlers,what)
1216    local tableoffset = lookupoffset + offset
1217    setposition(f,tableoffset)
1218    local subtype = readushort(f)
1219    if subtype == 1 then
1220        local lookuptype = types[readushort(f)]
1221        local faroffset  = readulong(f)
1222        local handler    = handlers[lookuptype]
1223        if handler then
1224            -- maybe we can just pass one offset (or tableoffset first)
1225            return handler(f,fontdata,lookupid,tableoffset + faroffset,0,glyphs,nofglyphs), lookuptype
1226        else
1227            report("no handler for lookuptype %a subtype %a in %s %s",lookuptype,subtype,what,"extension")
1228        end
1229    else
1230        report("unsupported subtype %a in %s %s",subtype,what,"extension")
1231    end
1232end
1233
1234-- gsub handlers
1235
1236function gsubhandlers.single(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs)
1237    local tableoffset = lookupoffset + offset
1238    setposition(f,tableoffset)
1239    local subtype = readushort(f)
1240    if subtype == 1 then
1241        local coverage = readushort(f)
1242        local delta    = readshort(f) -- can be negative
1243        local coverage = readcoverage(f,tableoffset+coverage) -- not simple as we need to set key/value anyway
1244        for index in next, coverage do
1245            local newindex = (index + delta) % 65536 -- modulo is new in 1.8.3
1246            if index > nofglyphs or newindex > nofglyphs then
1247                report("invalid index in %s format %i: %i -> %i (max %i)","single",subtype,index,newindex,nofglyphs)
1248                coverage[index] = nil
1249            else
1250                coverage[index] = newindex
1251            end
1252        end
1253        return {
1254            coverage = coverage
1255        }
1256    elseif subtype == 2 then -- in streamreader a seek and fetch is faster than a temp table
1257        local coverage        = readushort(f)
1258        local nofreplacements = readushort(f)
1259        local replacements    = readcardinaltable(f,nofreplacements,ushort)
1260        local coverage = readcoverage(f,tableoffset + coverage) -- not simple as we need to set key/value anyway
1261        for index, newindex in next, coverage do
1262            newindex = newindex + 1
1263            if index > nofglyphs or newindex > nofglyphs then
1264                report("invalid index in %s format %i: %i -> %i (max %i)","single",subtype,index,newindex,nofglyphs)
1265                coverage[index] = nil
1266            else
1267                coverage[index] = replacements[newindex]
1268            end
1269        end
1270        return {
1271            coverage = coverage
1272        }
1273    else
1274        report("unsupported subtype %a in %a substitution",subtype,"single")
1275    end
1276end
1277
1278-- we see coverage format 0x300 in some old ms fonts
1279
1280local function sethandler(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs,what)
1281    local tableoffset = lookupoffset + offset
1282    setposition(f,tableoffset)
1283    local subtype = readushort(f)
1284    if subtype == 1 then
1285        local coverage    = readushort(f)
1286        local nofsequence = readushort(f)
1287        local sequences   = readcardinaltable(f,nofsequence,ushort)
1288        for i=1,nofsequence do
1289            setposition(f,tableoffset + sequences[i])
1290            sequences[i] = readcardinaltable(f,readushort(f),ushort)
1291        end
1292        local coverage = readcoverage(f,tableoffset + coverage)
1293        for index, newindex in next, coverage do
1294            newindex = newindex + 1
1295            if index > nofglyphs or newindex > nofglyphs then
1296                report("invalid index in %s format %i: %i -> %i (max %i)",what,subtype,index,newindex,nofglyphs)
1297                coverage[index] = nil
1298            else
1299                coverage[index] = sequences[newindex]
1300            end
1301        end
1302        return {
1303            coverage = coverage
1304        }
1305    else
1306        report("unsupported subtype %a in %a substitution",subtype,what)
1307    end
1308end
1309
1310function gsubhandlers.multiple(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs)
1311    return sethandler(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs,"multiple")
1312end
1313
1314function gsubhandlers.alternate(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs)
1315    return sethandler(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs,"alternate")
1316end
1317
1318function gsubhandlers.ligature(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs)
1319    local tableoffset = lookupoffset + offset
1320    setposition(f,tableoffset)
1321    local subtype = readushort(f)
1322    if subtype == 1 then
1323        local coverage  = readushort(f)
1324        local nofsets   = readushort(f)
1325        local ligatures = readcardinaltable(f,nofsets,ushort)
1326        for i=1,nofsets do
1327            local offset = lookupoffset + offset + ligatures[i]
1328            setposition(f,offset)
1329            local n = readushort(f)
1330            if n == 1 then
1331                ligatures[i] = { offset + readushort(f) }
1332            else
1333                local l = { }
1334                for i=1,n do
1335                    l[i] = offset + readushort(f)
1336                end
1337                ligatures[i] = l
1338            end
1339        end
1340        local coverage = readcoverage(f,tableoffset + coverage)
1341        for index, newindex in next, coverage do
1342            local hash = { }
1343            local ligatures = ligatures[newindex+1]
1344            for i=1,#ligatures do
1345                local offset = ligatures[i]
1346                setposition(f,offset)
1347                local lig = readushort(f)
1348                local cnt = readushort(f)
1349                local hsh = hash
1350                for i=2,cnt do
1351                    local c = readushort(f)
1352                    local h = hsh[c]
1353                    if not h then
1354                        h = { }
1355                        hsh[c] = h
1356                    end
1357                    hsh =  h
1358                end
1359                hsh.ligature = lig
1360            end
1361            coverage[index] = hash
1362        end
1363        return {
1364            coverage = coverage
1365        }
1366    else
1367        report("unsupported subtype %a in %a substitution",subtype,"ligature")
1368    end
1369end
1370
1371function gsubhandlers.context(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs)
1372    return unchainedcontext(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs,"substitution"), "context"
1373end
1374
1375function gsubhandlers.chainedcontext(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs)
1376    return chainedcontext(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs,"substitution"), "chainedcontext"
1377end
1378
1379function gsubhandlers.extension(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs)
1380    return extension(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs,gsubtypes,gsubhandlers,"substitution")
1381end
1382
1383function gsubhandlers.reversechainedcontextsingle(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs)
1384    local tableoffset = lookupoffset + offset
1385    setposition(f,tableoffset)
1386    local subtype = readushort(f)
1387    if subtype == 1 then -- NEEDS CHECKING
1388        local current      = readfirst(f)
1389        local before       = readarray(f)
1390        local after        = readarray(f)
1391        local replacements = readarray(f)
1392        current = readcoveragearray(f,tableoffset,current,true)
1393        before  = readcoveragearray(f,tableoffset,before,true)
1394        after   = readcoveragearray(f,tableoffset,after,true)
1395        return {
1396            format = "reversecoverage", -- reversesub
1397            rules  = {
1398                {
1399                    before       = before,
1400                    current      = current,
1401                    after        = after,
1402                    replacements = replacements,
1403                }
1404            }
1405        }, "reversechainedcontextsingle"
1406    else
1407        report("unsupported subtype %a in %a substitution",subtype,"reversechainedcontextsingle")
1408    end
1409end
1410
1411-- gpos handlers
1412
1413local function readpairsets(f,tableoffset,sets,format1,format2,mainoffset,getdelta)
1414    local done = { }
1415    for i=1,#sets do
1416        local offset = sets[i]
1417        local reused = done[offset]
1418        if not reused then
1419            offset = tableoffset + offset
1420            setposition(f,offset)
1421            local n = readushort(f)
1422            reused = { }
1423            for i=1,n do
1424                reused[i] = {
1425                    readushort(f), -- second glyph id
1426                    readposition(f,format1,offset,getdelta),
1427                    readposition(f,format2,offset,getdelta),
1428                }
1429            end
1430            done[offset] = reused
1431        end
1432        sets[i] = reused
1433    end
1434    return sets
1435end
1436
1437local function readpairclasssets(f,nofclasses1,nofclasses2,format1,format2,mainoffset,getdelta)
1438    local classlist1  = { }
1439    for i=1,nofclasses1 do
1440        local classlist2 = { }
1441        classlist1[i] = classlist2
1442        for j=1,nofclasses2 do
1443            local one = readposition(f,format1,mainoffset,getdelta)
1444            local two = readposition(f,format2,mainoffset,getdelta)
1445            if one or two then
1446                classlist2[j] = { one, two }
1447            else
1448                classlist2[j] = false
1449            end
1450        end
1451    end
1452    return classlist1
1453end
1454
1455-- no real gain in kerns as we pack
1456
1457function gposhandlers.single(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs)
1458    local tableoffset = lookupoffset + offset
1459    setposition(f,tableoffset)
1460    local subtype  = readushort(f)
1461    local getdelta = fontdata.temporary.getdelta
1462    if subtype == 1 then
1463        local coverage = readushort(f)
1464        local format   = readushort(f)
1465        local value    = readposition(f,format,tableoffset,getdelta)
1466        local coverage = readcoverage(f,tableoffset+coverage)
1467        for index, newindex in next, coverage do
1468            coverage[index] = value -- will be packed and shared anyway
1469        end
1470        return {
1471            format   = "single",
1472            coverage = coverage,
1473        }
1474    elseif subtype == 2 then
1475        local coverage  = readushort(f)
1476        local format    = readushort(f)
1477        local nofvalues = readushort(f)
1478        local values    = { }
1479        for i=1,nofvalues do
1480            values[i] = readposition(f,format,tableoffset,getdelta)
1481        end
1482        local coverage = readcoverage(f,tableoffset+coverage)
1483        for index, newindex in next, coverage do
1484            coverage[index] = values[newindex+1]
1485        end
1486        return {
1487            format   = "single",
1488            coverage = coverage,
1489        }
1490    else
1491        report("unsupported subtype %a in %a positioning",subtype,"single")
1492    end
1493end
1494
1495-- this needs checking! if no second pair then another advance over the list
1496
1497-- ValueFormat1 applies to the ValueRecord of the first glyph in each pair. ValueRecords for all first glyphs must use ValueFormat1. If ValueFormat1 is set to zero (0), the corresponding glyph has no ValueRecord and, therefore, should not be repositioned.
1498-- ValueFormat2 applies to the ValueRecord of the second glyph in each pair. ValueRecords for all second glyphs must use ValueFormat2. If ValueFormat2 is set to null, then the second glyph of the pair is the “next” glyph for which a lookup should be performed.
1499
1500-- local simple = {
1501--     [true]  = { [true] = { true,  true }, [false] = { true  } },
1502--     [false] = { [true] = { false, true }, [false] = { false } },
1503-- }
1504
1505-- function gposhandlers.pair(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs)
1506--     local tableoffset = lookupoffset + offset
1507--     setposition(f,tableoffset)
1508--     local subtype  = readushort(f)
1509--     local getdelta = fontdata.temporary.getdelta
1510--     if subtype == 1 then
1511--         local coverage = readushort(f)
1512--         local format1  = readushort(f)
1513--         local format2  = readushort(f)
1514--         local sets     = readarray(f)
1515--               sets     = readpairsets(f,tableoffset,sets,format1,format2,mainoffset,getdelta)
1516--               coverage = readcoverage(f,tableoffset + coverage)
1517--         local shared   = { } -- partial sparse, when set also needs to be handled in the packer
1518--         for index, newindex in next, coverage do
1519--             local set  = sets[newindex+1]
1520--             local hash = { }
1521--             for i=1,#set do
1522--                 local value = set[i]
1523--                 if value then
1524--                     local other  = value[1]
1525--                     if shared then
1526--                         local s = shared[value]
1527--                         if s == nil then
1528--                             local first  = value[2]
1529--                             local second = value[3]
1530--                             if first or second then
1531--                                 s = { first, second or nil } -- needs checking
1532--                             else
1533--                                 s = false
1534--                             end
1535--                             shared[value] = s
1536--                         end
1537--                         hash[other] = s or nil
1538--                     else
1539--                         local first  = value[2]
1540--                         local second = value[3]
1541--                         if first or second then
1542--                             hash[other] = { first, second or nil } -- needs checking
1543--                         else
1544--                             hash[other] = nil -- what if set, maybe warning
1545--                         end
1546--                     end
1547--                 end
1548--             end
1549--             coverage[index] = hash
1550--         end
1551--         return {
1552--             shared   = shared and true or nil,
1553--             format   = "pair",
1554--             coverage = coverage,
1555--         }
1556--     elseif subtype == 2 then
1557--         local coverage     = readushort(f)
1558--         local format1      = readushort(f)
1559--         local format2      = readushort(f)
1560--         local classdef1    = readushort(f)
1561--         local classdef2    = readushort(f)
1562--         local nofclasses1  = readushort(f) -- incl class 0
1563--         local nofclasses2  = readushort(f) -- incl class 0
1564--         local classlist    = readpairclasssets(f,nofclasses1,nofclasses2,format1,format2,tableoffset,getdelta)
1565--               coverage     = readcoverage(f,tableoffset+coverage)
1566--               classdef1    = readclassdef(f,tableoffset+classdef1,coverage)
1567--               classdef2    = readclassdef(f,tableoffset+classdef2,nofglyphs)
1568--         local usedcoverage = { }
1569--         local shared       = { } -- partial sparse, when set also needs to be handled in the packer
1570--         for g1, c1 in next, classdef1 do
1571--             if coverage[g1] then
1572--                 local l1 = classlist[c1]
1573--                 if l1 then
1574--                     local hash = { }
1575--                     for paired, class in next, classdef2 do
1576--                         local offsets = l1[class]
1577--                         if offsets then
1578--                             local first  = offsets[1]
1579--                             local second = offsets[2]
1580--                             if first or second then
1581--                                 if shared then
1582--                                     local s1 = shared[first]
1583--                                     if s1 == nil then
1584--                                         s1 = { }
1585--                                         shared[first] = s1
1586--                                     end
1587--                                     local s2 = s1[second]
1588--                                     if s2 == nil then
1589--                                         s2 = { first, second or nil }
1590--                                         s1[second] = s2
1591--                                     end
1592--                                     hash[paired] = s2
1593--                                 else
1594--                                     hash[paired] = { first, second or nil }
1595--                                 end
1596--                             else
1597--                                 -- upto the next lookup for this combination
1598--                             end
1599--                         end
1600--                     end
1601--                     usedcoverage[g1] = hash
1602--                 end
1603--             end
1604--         end
1605--         return {
1606--             shared   = shared and true or nil,
1607--             format   = "pair",
1608--             coverage = usedcoverage,
1609--         }
1610--     elseif subtype == 3 then
1611--         report("yet unsupported subtype %a in %a positioning",subtype,"pair")
1612--     else
1613--         report("unsupported subtype %a in %a positioning",subtype,"pair")
1614--     end
1615-- end
1616
1617function gposhandlers.pair(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs)
1618    local tableoffset = lookupoffset + offset
1619    setposition(f,tableoffset)
1620    local subtype  = readushort(f)
1621    local getdelta = fontdata.temporary.getdelta
1622    if subtype == 1 then
1623        local coverage = readushort(f)
1624        local format1  = readushort(f)
1625        local format2  = readushort(f)
1626        local sets     = readarray(f)
1627              sets     = readpairsets(f,tableoffset,sets,format1,format2,mainoffset,getdelta)
1628              coverage = readcoverage(f,tableoffset + coverage)
1629        local shared   = { } -- partial sparse, when set also needs to be handled in the packer
1630        for index, newindex in next, coverage do
1631            local set  = sets[newindex+1]
1632            local hash = { }
1633            for i=1,#set do
1634                local value = set[i]
1635                if value then
1636                    local other = value[1]
1637                    local share = shared[value]
1638                    if share == nil then
1639                        local first  = value[2]
1640                        local second = value[3]
1641                        if first or second then
1642                            share = { first, second or nil } -- needs checking
1643                        else
1644                            share = false
1645                        end
1646                        shared[value] = share
1647                    end
1648                    hash[other] = share or nil -- really overload ?
1649                end
1650            end
1651            coverage[index] = hash
1652        end
1653        return {
1654            shared   = shared and true or nil,
1655            format   = "pair",
1656            coverage = coverage,
1657        }
1658    elseif subtype == 2 then
1659        local coverage     = readushort(f)
1660        local format1      = readushort(f)
1661        local format2      = readushort(f)
1662        local classdef1    = readushort(f)
1663        local classdef2    = readushort(f)
1664        local nofclasses1  = readushort(f) -- incl class 0
1665        local nofclasses2  = readushort(f) -- incl class 0
1666        local classlist    = readpairclasssets(f,nofclasses1,nofclasses2,format1,format2,tableoffset,getdelta)
1667              coverage     = readcoverage(f,tableoffset+coverage)
1668              classdef1    = readclassdef(f,tableoffset+classdef1,coverage)
1669              classdef2    = readclassdef(f,tableoffset+classdef2,nofglyphs)
1670        local usedcoverage = { }
1671        local shared       = { } -- partial sparse, when set also needs to be handled in the packer
1672        for g1, c1 in next, classdef1 do
1673            if coverage[g1] then
1674                local l1 = classlist[c1]
1675                if l1 then
1676                    local hash = { }
1677                    for paired, class in next, classdef2 do
1678                        local offsets = l1[class]
1679                        if offsets then
1680                            local first  = offsets[1]
1681                            local second = offsets[2]
1682                            if first or second then
1683                                local s1 = shared[first]
1684                                if s1 == nil then
1685                                    s1 = { }
1686                                    shared[first] = s1
1687                                end
1688                                local s2 = s1[second]
1689                                if s2 == nil then
1690                                    s2 = { first, second or nil }
1691                                    s1[second] = s2
1692                                end
1693                                hash[paired] = s2
1694                            end
1695                        end
1696                    end
1697                    usedcoverage[g1] = hash
1698                end
1699            end
1700        end
1701        return {
1702            shared   = shared and true or nil,
1703            format   = "pair",
1704            coverage = usedcoverage,
1705        }
1706    elseif subtype == 3 then
1707        report("yet unsupported subtype %a in %a positioning",subtype,"pair")
1708    else
1709        report("unsupported subtype %a in %a positioning",subtype,"pair")
1710    end
1711end
1712
1713function gposhandlers.cursive(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs)
1714    local tableoffset = lookupoffset + offset
1715    setposition(f,tableoffset)
1716    local subtype  = readushort(f)
1717    local getdelta = fontdata.temporary.getdelta
1718    if subtype == 1 then
1719        local coverage   = tableoffset + readushort(f)
1720        local nofrecords = readushort(f)
1721        local records    = { }
1722        for i=1,nofrecords do
1723            local entry = readushort(f)
1724            local exit  = readushort(f)
1725            records[i] = {
1726             -- entry = entry ~= 0 and (tableoffset + entry) or false,
1727             -- exit  = exit  ~= 0 and (tableoffset + exit ) or nil,
1728                entry ~= 0 and (tableoffset + entry) or false,
1729                exit  ~= 0 and (tableoffset + exit ) or nil,
1730            }
1731        end
1732        -- slot 1 will become hash after loading and it must be unique because we
1733        -- pack the tables (packed we turn the cc-* into a zero)
1734        local cc = (fontdata.temporary.cursivecount or 0) + 1
1735        fontdata.temporary.cursivecount = cc
1736        cc = "cc-" .. cc
1737        coverage = readcoverage(f,coverage)
1738        for i=1,nofrecords do
1739            local r = records[i]
1740            records[i] = {
1741             -- 1,
1742                cc,
1743             -- readanchor(f,r.entry,getdelta) or false,
1744             -- readanchor(f,r.exit, getdelta) or nil,
1745                readanchor(f,r[1],getdelta) or false,
1746                readanchor(f,r[2],getdelta) or nil,
1747            }
1748        end
1749        for index, newindex in next, coverage do
1750            coverage[index] = records[newindex+1]
1751        end
1752        return {
1753            coverage = coverage,
1754        }
1755    else
1756        report("unsupported subtype %a in %a positioning",subtype,"cursive")
1757    end
1758end
1759
1760local function handlemark(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs,ligature)
1761    local tableoffset = lookupoffset + offset
1762    setposition(f,tableoffset)
1763    local subtype  = readushort(f)
1764    local getdelta = fontdata.temporary.getdelta
1765    if subtype == 1 then
1766        -- we are one based, not zero
1767        local markcoverage = tableoffset + readushort(f)
1768        local basecoverage = tableoffset + readushort(f)
1769        local nofclasses   = readushort(f)
1770        local markoffset   = tableoffset + readushort(f)
1771        local baseoffset   = tableoffset + readushort(f)
1772        --
1773        local markcoverage = readcoverage(f,markcoverage)
1774        local basecoverage = readcoverage(f,basecoverage,true) -- TO BE CHECKED: true
1775        --
1776        setposition(f,markoffset)
1777        local markclasses    = { }
1778        local nofmarkclasses = readushort(f)
1779        --
1780        local lastanchor  = fontdata.lastanchor or 0
1781        local usedanchors = { }
1782        --
1783        for i=1,nofmarkclasses do
1784            local class  = readushort(f) + 1
1785            local offset = readushort(f)
1786            if offset == 0 then
1787                markclasses[i] = false
1788            else
1789                markclasses[i] = { class, markoffset + offset }
1790            end
1791            usedanchors[class] = true
1792        end
1793        for i=1,nofmarkclasses do
1794            local mc = markclasses[i]
1795            if mc then
1796                mc[2] = readanchor(f,mc[2],getdelta)
1797            end
1798        end
1799        --
1800        setposition(f,baseoffset)
1801        local nofbaserecords = readushort(f)
1802        local baserecords    = { }
1803        --
1804        if ligature then
1805            -- 3 components
1806            -- 1 : class .. nofclasses -- NULL when empty
1807            -- 2 : class .. nofclasses -- NULL when empty
1808            -- 3 : class .. nofclasses -- NULL when empty
1809            for i=1,nofbaserecords do -- here i is the class
1810                local offset = readushort(f)
1811                if offset == 0 then
1812                    baserecords[i] = false
1813                else
1814                    baserecords[i] = baseoffset + offset
1815                end
1816            end
1817            for i=1,nofbaserecords do
1818                local recordoffset = baserecords[i]
1819                if recordoffset then
1820                    setposition(f,recordoffset)
1821                    local nofcomponents = readushort(f)
1822                    local components = { }
1823                    for i=1,nofcomponents do
1824                        local classes = { }
1825                        for i=1,nofclasses do
1826                            local offset = readushort(f)
1827                            if offset ~= 0 then
1828                                classes[i] = recordoffset + offset
1829                            else
1830                                classes[i] = false
1831                            end
1832                        end
1833                        components[i] = classes
1834                    end
1835                    baserecords[i] = components
1836                end
1837            end
1838            local baseclasses = { } -- setmetatableindex("table")
1839            for i=1,nofclasses do
1840                baseclasses[i] = { }
1841            end
1842            for i=1,nofbaserecords do
1843                local components = baserecords[i]
1844                if components then
1845                    local b = basecoverage[i]
1846                    for c=1,#components do
1847                        local classes = components[c]
1848                        if classes then
1849                            for i=1,nofclasses do
1850                                local anchor = readanchor(f,classes[i],getdelta)
1851                                local bclass = baseclasses[i]
1852                                local bentry = bclass[b]
1853                                if bentry then
1854                                    bentry[c] = anchor
1855                                else
1856                                    bclass[b]= { [c] = anchor }
1857                                end
1858                            end
1859                        end
1860                    end
1861                end
1862            end
1863            for index, newindex in next, markcoverage do
1864                markcoverage[index] = markclasses[newindex+1] or nil
1865            end
1866            return {
1867                format      = "ligature",
1868                baseclasses = baseclasses,
1869                coverage    = markcoverage,
1870            }
1871        else
1872            for i=1,nofbaserecords do
1873                local r = { }
1874                for j=1,nofclasses do
1875                    local offset = readushort(f)
1876                    if offset == 0 then
1877                        r[j] = false
1878                    else
1879                        r[j] = baseoffset + offset
1880                    end
1881                end
1882                baserecords[i] = r
1883            end
1884            local baseclasses = { } -- setmetatableindex("table")
1885            for i=1,nofclasses do
1886                baseclasses[i] = { }
1887            end
1888            for i=1,nofbaserecords do
1889                local r = baserecords[i]
1890                local b = basecoverage[i]
1891                for j=1,nofclasses do
1892                    baseclasses[j][b] = readanchor(f,r[j],getdelta)
1893                end
1894            end
1895            for index, newindex in next, markcoverage do
1896                markcoverage[index] = markclasses[newindex+1] or nil
1897            end
1898            -- we could actually already calculate the displacement if we want
1899            return {
1900                format      = "base",
1901                baseclasses = baseclasses,
1902                coverage    = markcoverage,
1903            }
1904        end
1905    else
1906        report("unsupported subtype %a in",subtype)
1907    end
1908
1909end
1910
1911function gposhandlers.marktobase(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs)
1912    return handlemark(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs)
1913end
1914
1915function gposhandlers.marktoligature(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs)
1916    return handlemark(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs,true)
1917end
1918
1919function gposhandlers.marktomark(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs)
1920    return handlemark(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs)
1921end
1922
1923function gposhandlers.context(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs)
1924    return unchainedcontext(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs,"positioning"), "context"
1925end
1926
1927function gposhandlers.chainedcontext(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs)
1928    return chainedcontext(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs,"positioning"), "chainedcontext"
1929end
1930
1931function gposhandlers.extension(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs)
1932    return extension(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs,gpostypes,gposhandlers,"positioning")
1933end
1934
1935-- main loader
1936
1937do
1938
1939    local plugins = { }
1940
1941    function plugins.size(f,fontdata,tableoffset,feature)
1942        if fontdata.designsize then
1943            -- yes, there are fonts with multiple size entries ... it probably relates
1944            -- to the other two fields (menu entries in some language)
1945        else
1946            local function check(offset)
1947                setposition(f,offset)
1948                local designsize = readushort(f)
1949                if designsize > 0 then -- we could also have a threshold
1950                    local fontstyleid = readushort(f)
1951                    local guimenuid   = readushort(f)
1952                    local minsize     = readushort(f)
1953                    local maxsize     = readushort(f)
1954                    if minsize == 0 and maxsize == 0 and fontstyleid == 0 and guimenuid == 0 then
1955                        minsize = designsize
1956                        maxsize = designsize
1957                    end
1958                    if designsize >= minsize and designsize <= maxsize then
1959                        return minsize, maxsize, designsize
1960                    end
1961                end
1962            end
1963            local minsize, maxsize, designsize = check(tableoffset+feature.offset+feature.parameters)
1964            if not designsize then
1965                -- some old adobe fonts have: tableoffset+feature.parameters and we could
1966                -- use some heuristic but why bother ... this extra check will be removed
1967                -- some day and/or when we run into an issue
1968                minsize, maxsize, designsize = check(tableoffset+feature.parameters)
1969                if designsize then
1970                    report("bad size feature in %a, falling back to wrong offset",fontdata.filename or "?")
1971                else
1972                    report("bad size feature in %a,",fontdata.filename or "?")
1973                end
1974            end
1975            if designsize then
1976                fontdata.minsize    = minsize
1977                fontdata.maxsize    = maxsize
1978                fontdata.designsize = designsize
1979            end
1980        end
1981    end
1982
1983 -- function plugins.rvrn(f,fontdata,tableoffset,feature)
1984 --     -- todo, at least a message
1985 -- end
1986
1987    -- feature order needs checking ... as we loop over a hash ... however, in the file
1988    -- they are sorted so order is not that relevant
1989
1990    local function reorderfeatures(fontdata,scripts,features)
1991        local scriptlangs  = { }
1992        local featurehash  = { }
1993        local featureorder = { }
1994        for script, languages in next, scripts do
1995            for language, record in next, languages do
1996                local hash = { }
1997                local list = record.featureindices
1998                for k=1,#list do
1999                    local index   = list[k]
2000                    local feature = features[index]
2001                    local lookups = feature.lookups
2002                    local tag     = feature.tag
2003                    if tag then
2004                        hash[tag] = true
2005                    end
2006                    if lookups then
2007                        for i=1,#lookups do
2008                            local lookup = lookups[i]
2009                            local o = featureorder[lookup]
2010                            if o then
2011                                local okay = true
2012                                for i=1,#o do
2013                                    if o[i] == tag then
2014                                        okay = false
2015                                        break
2016                                    end
2017                                end
2018                                if okay then
2019                                    o[#o+1] = tag
2020                                end
2021                            else
2022                                featureorder[lookup] = { tag }
2023                            end
2024                            local f = featurehash[lookup]
2025                            if f then
2026                                local h = f[tag]
2027                                if h then
2028                                    local s = h[script]
2029                                    if s then
2030                                        s[language] = true
2031                                    else
2032                                        h[script] = { [language] = true }
2033                                    end
2034                                else
2035                                    f[tag] = { [script] = { [language] = true } }
2036                                end
2037                            else
2038                                featurehash[lookup] = { [tag] = { [script] = { [language] = true } } }
2039                            end
2040                            --
2041                            local h = scriptlangs[tag]
2042                            if h then
2043                                local s = h[script]
2044                                if s then
2045                                    s[language] = true
2046                                else
2047                                    h[script] = { [language] = true }
2048                                end
2049                            else
2050                                scriptlangs[tag] = { [script] = { [language] = true } }
2051                            end
2052                        end
2053                    end
2054                end
2055            end
2056        end
2057        return scriptlangs, featurehash, featureorder
2058    end
2059
2060    local function readscriplan(f,fontdata,scriptoffset)
2061        setposition(f,scriptoffset)
2062        local nofscripts = readushort(f)
2063        local scripts    = { }
2064        for i=1,nofscripts do
2065            scripts[readtag(f)] = scriptoffset + readushort(f)
2066        end
2067        -- script list -> language system info
2068        local languagesystems = setmetatableindex("table")
2069        for script, offset in next, scripts do
2070            setposition(f,offset)
2071            local defaultoffset = readushort(f)
2072            local noflanguages  = readushort(f)
2073            local languages     = { }
2074            if defaultoffset > 0 then
2075                languages.dflt = languagesystems[offset + defaultoffset]
2076            end
2077            for i=1,noflanguages do
2078                local language      = readtag(f)
2079                local offset        = offset + readushort(f)
2080                languages[language] = languagesystems[offset]
2081            end
2082            scripts[script] = languages
2083        end
2084        -- script list -> language system info -> feature list
2085        for offset, usedfeatures in next, languagesystems do
2086            if offset > 0 then
2087                setposition(f,offset)
2088                local featureindices        = { }
2089                usedfeatures.featureindices = featureindices
2090                usedfeatures.lookuporder    = readushort(f) -- reserved, not used (yet)
2091                usedfeatures.requiredindex  = readushort(f) -- relates to required (can be 0xFFFF)
2092                local noffeatures           = readushort(f)
2093                for i=1,noffeatures do
2094                    featureindices[i] = readushort(f) + 1
2095                end
2096            end
2097        end
2098        return scripts
2099    end
2100
2101    local function readfeatures(f,fontdata,featureoffset)
2102        setposition(f,featureoffset)
2103        local features    = { }
2104        local noffeatures = readushort(f)
2105        for i=1,noffeatures do
2106            -- also shared?
2107            features[i] = {
2108                tag    = readtag(f),
2109                offset = readushort(f)
2110            }
2111        end
2112        --
2113        for i=1,noffeatures do
2114            local feature = features[i]
2115            local offset  = featureoffset+feature.offset
2116            setposition(f,offset)
2117            local parameters = readushort(f) -- feature.parameters
2118            local noflookups = readushort(f)
2119            if noflookups > 0 then
2120--                 local lookups   = { }
2121--                 feature.lookups = lookups
2122--                 for j=1,noflookups do
2123--                     lookups[j] = readushort(f) + 1
2124--                 end
2125                local lookups   = readcardinaltable(f,noflookups,ushort)
2126                feature.lookups = lookups
2127                for j=1,noflookups do
2128                    lookups[j] = lookups[j] + 1
2129                end
2130            end
2131            if parameters > 0 then
2132                feature.parameters = parameters
2133                local plugin = plugins[feature.tag]
2134                if plugin then
2135                    plugin(f,fontdata,featureoffset,feature)
2136                end
2137            end
2138        end
2139        return features
2140    end
2141
2142    local function readlookups(f,lookupoffset,lookuptypes,featurehash,featureorder,nofmarkclasses)
2143        setposition(f,lookupoffset)
2144        local noflookups = readushort(f)
2145        local lookups    = readcardinaltable(f,noflookups,ushort)
2146        for lookupid=1,noflookups do
2147            local offset = lookups[lookupid]
2148            setposition(f,lookupoffset+offset)
2149            local subtables    = { }
2150            local typebits     = readushort(f)
2151            local flagbits     = readushort(f)
2152            local lookuptype   = lookuptypes[typebits]
2153            local lookupflags  = lookupflags[flagbits]
2154            local nofsubtables = readushort(f)
2155            for j=1,nofsubtables do
2156                subtables[j] = offset + readushort(f) -- we can probably put lookupoffset here
2157            end
2158            -- which one wins?
2159            local markclass = band(flagbits,0x0010) ~= 0 -- usemarkfilteringset
2160            local markset   = rshift(flagbits,8)
2161            if markclass then
2162                markclass = readushort(f) -- + 1
2163            end
2164            if markset > 0 then
2165                markclass = nofmarkclasses + markset
2166            end
2167            lookups[lookupid] = {
2168                type      = lookuptype,
2169             -- chain     = chaindirections[lookuptype] or nil,
2170                flags     = lookupflags,
2171                name      = lookupid,
2172                subtables = subtables,
2173                markclass = markclass,
2174                features  = featurehash[lookupid], -- not if extension
2175                order     = featureorder[lookupid],
2176            }
2177        end
2178        return lookups
2179    end
2180
2181    local f_lookupname = formatters["%s_%s_%s"]
2182
2183    local function resolvelookups(f,lookupoffset,fontdata,lookups,lookuptypes,lookuphandlers,what,tableoffset)
2184
2185        local sequences      = fontdata.sequences  or { }
2186        local sublookuplist  = fontdata.sublookups or { }
2187        fontdata.sequences   = sequences
2188        fontdata.sublookups  = sublookuplist
2189        local nofsublookups  = #sublookuplist
2190        local nofsequences   = #sequences -- 0
2191        local lastsublookup  = nofsublookups
2192        local lastsequence   = nofsequences
2193        local lookupnames    = lookupnames[what]
2194        local sublookuphash  = { }
2195        local sublookupcheck = { }
2196        local glyphs         = fontdata.glyphs
2197        local nofglyphs      = fontdata.nofglyphs or #glyphs
2198        local noflookups     = #lookups
2199        local lookupprefix   = sub(what,2,2) -- g[s|p][ub|os]
2200        --
2201        local usedlookups    = false -- setmetatableindex("number")
2202        --
2203        local allsteps = { } -- new per 2022-09-25
2204
2205        for lookupid=1,noflookups do
2206            local lookup     = lookups[lookupid]
2207            local lookuptype = lookup.type
2208            local subtables  = lookup.subtables
2209            local features   = lookup.features
2210            local handler    = lookuphandlers[lookuptype]
2211            if handler then
2212                local nofsubtables = #subtables
2213                local order        = lookup.order
2214                local flags        = lookup.flags
2215                -- this is expected in the font handler (faster checking)
2216                if flags[1] then flags[1] = "mark" end
2217                if flags[2] then flags[2] = "ligature" end
2218                if flags[3] then flags[3] = "base" end
2219                --
2220                local markclass    = lookup.markclass
2221             -- local chain        = lookup.chain
2222                if nofsubtables > 0 then
2223                    local steps     = { }
2224                    local nofsteps  = 0
2225                    local oldtype   = nil
2226                    for s=1,nofsubtables do
2227                        local step, lt = handler(f,fontdata,lookupid,lookupoffset,subtables[s],glyphs,nofglyphs)
2228                        if lt then
2229                            lookuptype = lt
2230                            if oldtype and lt ~= oldtype then
2231                                report("messy %s lookup type %a and %a",what,lookuptype,oldtype)
2232                            end
2233                            oldtype = lookuptype
2234                        end
2235                        if not step then
2236                            report("unsupported %s lookup type %a",what,lookuptype)
2237                        else
2238                            nofsteps = nofsteps + 1
2239                            steps[nofsteps] = step
2240                            local rules = step.rules
2241                            if rules then
2242                                allsteps[#allsteps+1] = step -- new per 2022-09-25
2243                                for i=1,#rules do
2244                                    local rule         = rules[i]
2245                                    local before       = rule.before
2246                                    local current      = rule.current
2247                                    local after        = rule.after
2248                                    local replacements = rule.replacements
2249                                    if before then
2250                                        for i=1,#before do
2251                                            before[i] = tohash(before[i])
2252                                        end
2253                                        -- as with original ctx ff loader
2254                                        rule.before = reversed(before)
2255                                    end
2256                                    if current then
2257                                        if replacements then
2258                                            -- We have a reverse lookup and therefore only one current entry. We might need
2259                                            -- to reverse the order in the before and after lists so that needs checking.
2260                                            local first = current[1]
2261                                            local hash  = { }
2262                                            local repl  = { }
2263                                            for i=1,#first do
2264                                                local c = first[i]
2265                                                hash[c] = true
2266                                                repl[c] = replacements[i]
2267                                            end
2268                                            rule.current      = { hash }
2269                                            rule.replacements = repl
2270                                        else
2271                                            for i=1,#current do
2272                                                current[i] = tohash(current[i])
2273                                            end
2274                                        end
2275                                    else
2276                                        -- weird lookup
2277                                    end
2278                                    if after then
2279                                        for i=1,#after do
2280                                            after[i] = tohash(after[i])
2281                                        end
2282                                    end
2283                                    if usedlookups then
2284                                        local lookups = rule.lookups
2285                                        if lookups then
2286                                            for k, v in next, lookups do
2287                                                if v then
2288                                                    for k, v in next, v do
2289                                                        usedlookups[v] = usedlookups[v] + 1
2290                                                    end
2291                                                end
2292                                            end
2293                                        end
2294                                    end
2295                                end
2296                            end
2297                        end
2298                    end
2299                    if nofsteps ~= nofsubtables then
2300                        report("bogus subtables removed in %s lookup type %a",what,lookuptype)
2301                    end
2302                    lookuptype = lookupnames[lookuptype] or lookuptype
2303                    if features then
2304                        nofsequences = nofsequences + 1
2305                     -- report("registering %i as sequence step %i",lookupid,nofsequences)
2306                        local l = {
2307                            index     = nofsequences,
2308                            name      = f_lookupname(lookupprefix,"s",lookupid+lookupidoffset),
2309                            steps     = steps,
2310                            nofsteps  = nofsteps,
2311                            type      = lookuptype,
2312                            markclass = markclass or nil,
2313                            flags     = flags,
2314                         -- chain     = chain,
2315                            order     = order,
2316                            features  = features,
2317                        }
2318                        sequences[nofsequences] = l
2319                        lookup.done = l
2320                    else
2321                        nofsublookups = nofsublookups + 1
2322                     -- report("registering %i as sublookup %i",lookupid,nofsublookups)
2323                        local l = {
2324                            index     = nofsublookups,
2325                            name      = f_lookupname(lookupprefix,"l",lookupid+lookupidoffset),
2326                            steps     = steps,
2327                            nofsteps  = nofsteps,
2328                            type      = lookuptype,
2329                            markclass = markclass or nil,
2330                            flags     = flags,
2331                         -- chain     = chain,
2332                        }
2333                        sublookuplist[nofsublookups] = l
2334                        sublookuphash[lookupid] = nofsublookups
2335                        sublookupcheck[lookupid] = 0
2336                        lookup.done = l
2337                    end
2338                else
2339                    report("no subtables for lookup %a",lookupid)
2340                end
2341            else
2342                report("no handler for lookup %a with type %a",lookupid,lookuptype)
2343            end
2344        end
2345
2346        if usedlookups then
2347            report("used %s lookups: % t",what,sortedkeys(usedlookups))
2348        end
2349
2350        -- When we have a context, we have sublookups that resolve into lookups for which we need to
2351        -- know the type. We split the main lookuptable in two parts: sequences (the main lookups)
2352        -- and subtable lookups (simple specs with no features). We could keep them merged and might do
2353        -- that once we only use this loader. Then we can also move the simple specs into the sequence.
2354        -- After all, we pack afterwards.
2355
2356        local reported = { }
2357
2358        local function report_issue(i,what,step,kind)
2359--             if not reported[step] then
2360                report("rule %i in step %i of %s has %s lookups",i,step,what,kind)
2361--                 reported[name] = true
2362--             end
2363        end
2364
2365     -- for i=lastsequence+1,nofsequences do
2366     --     local sequence = sequences[i]
2367     --     local steps    = sequence.steps
2368     --     for i=1,#steps do
2369     --         local step  = steps[i]
2370
2371            for s=1,#allsteps do          -- new per 2022-09-25
2372                local step  = allsteps[s] -- new per 2022-09-25
2373                local rules = step.rules
2374                if rules then
2375                    for i=1,#rules do
2376                        local rule     = rules[i]
2377                        local rlookups = rule.lookups
2378                        if not rlookups then
2379                            report_issue(i,what,s,"no")
2380                        elseif not next(rlookups) then
2381                            -- can be ok as it aborts a chain sequence
2382                         -- report_issue(i,what,s,"empty")
2383                            rule.lookups = nil
2384                        else
2385                            -- we can have holes in rlookups flagged false and we can have multiple lookups
2386                            -- applied (first time seen in seguemj)
2387                            local length = #rlookups
2388                            for index=1,length do
2389                                local lookuplist = rlookups[index]
2390                                if lookuplist then
2391                                    local length   = #lookuplist
2392                                    local found    = { }
2393                                    local noffound = 0
2394                                    for index=1,length do
2395                                        local lookupid = lookuplist[index]
2396                                        if lookupid then
2397                                            local h = sublookuphash[lookupid]
2398                                            if not h then
2399                                                -- here we have a lookup that is used independent as well
2400                                                -- as in another one
2401                                                local lookup = lookups[lookupid]
2402                                                if lookup then
2403                                                    local d = lookup.done
2404                                                    if d then
2405                                                        nofsublookups = nofsublookups + 1
2406                                                     -- report("registering %i as sublookup %i",lookupid,nofsublookups)
2407                                                        local l = {
2408                                                            index     = nofsublookups, -- handy for tracing
2409                                                            name      = f_lookupname(lookupprefix,"d",lookupid+lookupidoffset),
2410                                                            derived   = true,          -- handy for tracing
2411                                                            steps     = d.steps,
2412                                                            nofsteps  = d.nofsteps,
2413                                                            type      = d.lookuptype or "gsub_single", -- todo: check type
2414                                                            markclass = d.markclass or nil,
2415                                                            flags     = d.flags,
2416                                                         -- chain     = d.chain,
2417                                                        }
2418                                                        sublookuplist[nofsublookups] = copy(l) -- we repack later
2419                                                        sublookuphash[lookupid] = nofsublookups
2420                                                        sublookupcheck[lookupid] = 1
2421                                                        h = nofsublookups
2422                                                    else
2423                                                        report_issue(i,what,s,"missing")
2424                                                        rule.lookups = nil
2425                                                        break
2426                                                    end
2427                                                else
2428                                                    report_issue(i,what,s,"bad")
2429                                                    rule.lookups = nil
2430                                                    break
2431                                                end
2432                                            else
2433                                                sublookupcheck[lookupid] = sublookupcheck[lookupid] + 1
2434                                            end
2435                                            if h then
2436                                                noffound = noffound + 1
2437                                                found[noffound] = h
2438                                            end
2439                                        end
2440                                    end
2441                                    rlookups[index] = noffound > 0 and found or false
2442                                else
2443                                    rlookups[index] = false
2444                                end
2445                            end
2446                        end
2447                    end
2448                end
2449            end
2450     -- end -- new per 2022-09-25
2451
2452        for i, n in sortedhash(sublookupcheck) do
2453            local l = lookups[i]
2454            local t = l.type
2455            if n == 0 and t ~= "extension" then
2456                local d = l.done
2457                report("%s lookup %s of type %a is not used",what,d and d.name or l.name,t)
2458            end
2459        end
2460
2461    end
2462
2463    local function loadvariations(f,fontdata,variationsoffset,lookuptypes,featurehash,featureorder)
2464        setposition(f,variationsoffset)
2465        local version    = readulong(f) -- two times readushort
2466        local nofrecords = readulong(f)
2467        local records    = { }
2468        for i=1,nofrecords do
2469            records[i] = {
2470                conditions    = readulong(f),
2471                substitutions = readulong(f),
2472            }
2473        end
2474        for i=1,nofrecords do
2475            local record = records[i]
2476            local offset = record.conditions
2477            if offset == 0 then
2478                record.condition = nil
2479                record.matchtype = "always"
2480            else
2481                local offset = variationsoffset+offset
2482                setposition(f,offset)
2483                local nofconditions = readushort(f)
2484                local conditions    = { }
2485                for i=1,nofconditions do
2486                    conditions[i] = offset + readulong(f)
2487                end
2488                record.conditions = conditions
2489                record.matchtype  = "condition"
2490            end
2491        end
2492        for i=1,nofrecords do
2493            local record = records[i]
2494            if record.matchtype == "condition" then
2495                local conditions = record.conditions
2496                for i=1,#conditions do
2497                    setposition(f,conditions[i])
2498                    conditions[i] = {
2499                        format   = readushort(f),
2500                        axis     = readushort(f),
2501                        minvalue = read2dot14(f),
2502                        maxvalue = read2dot14(f),
2503                    }
2504                end
2505            end
2506        end
2507
2508        for i=1,nofrecords do
2509            local record = records[i]
2510            local offset = record.substitutions
2511            if offset == 0 then
2512                record.substitutions = { }
2513            else
2514                setposition(f,variationsoffset + offset)
2515                local version          = readulong(f)
2516                local nofsubstitutions = readushort(f)
2517                local substitutions    = { }
2518                for i=1,nofsubstitutions do
2519                    substitutions[readushort(f)] = readulong(f)
2520                end
2521                for index, alternates in sortedhash(substitutions) do
2522                    if index == 0 then
2523                        record.substitutions = false
2524                    else
2525                        local tableoffset = variationsoffset + offset + alternates
2526                        setposition(f,tableoffset)
2527                        local parameters = readulong(f) -- feature parameters
2528                        local noflookups = readushort(f)
2529                        local lookups    = readcardinaltable(f,noflookups,ushort) -- not sure what to do with these
2530                        -- todo : resolve to proper lookups
2531                        record.substitutions = lookups
2532                    end
2533                end
2534            end
2535        end
2536        setvariabledata(fontdata,"features",records)
2537    end
2538
2539    local function readscripts(f,fontdata,what,lookuptypes,lookuphandlers,lookupstoo)
2540        local tableoffset = gotodatatable(f,fontdata,what,true)
2541        if tableoffset then
2542            local version          = readulong(f)
2543            local scriptoffset     = tableoffset + readushort(f)
2544            local featureoffset    = tableoffset + readushort(f)
2545            local lookupoffset     = tableoffset + readushort(f)
2546            -- MFK : Rubik-Regular.ttf : we need to delay adding the offset
2547         -- local variationsoffset = version > 0x00010000 and (tableoffset + readulong(f)) or 0
2548            local variationsoffset = version > 0x00010000 and readulong(f) or 0
2549            if not scriptoffset then
2550                return
2551            end
2552            local scripts  = readscriplan(f,fontdata,scriptoffset)
2553            local features = readfeatures(f,fontdata,featureoffset)
2554            --
2555            local scriptlangs, featurehash, featureorder = reorderfeatures(fontdata,scripts,features)
2556            --
2557            if fontdata.features then
2558                fontdata.features[what] = scriptlangs
2559            else
2560                fontdata.features = { [what] = scriptlangs }
2561            end
2562            --
2563            if not lookupstoo then
2564                return
2565            end
2566            --
2567            local markclasses    = fontdata.markclasses
2568            local marksets       = fontdata.marksets
2569            local nofmarkclasses = (markclasses and #markclasses or 0) - (marksets and #marksets or 0)
2570            local lookups        = readlookups(f,lookupoffset,lookuptypes,featurehash,featureorder,nofmarkclasses)
2571            --
2572            if lookups then
2573                resolvelookups(f,lookupoffset,fontdata,lookups,lookuptypes,lookuphandlers,what,tableoffset)
2574            end
2575            --
2576            if variationsoffset > 0 then
2577             -- loadvariations(f,fontdata,variationsoffset,lookuptypes,featurehash,featureorder)
2578                loadvariations(f,fontdata,tableoffset + variationsoffset,lookuptypes,featurehash,featureorder)
2579            end
2580        end
2581    end
2582
2583    local function checkkerns(f,fontdata,specification)
2584        local datatable = fontdata.tables.kern
2585        if not datatable then
2586            return -- no kerns
2587        end
2588        local features     = fontdata.features
2589        local gposfeatures = features and features.gpos
2590        local name
2591        if not gposfeatures or not gposfeatures.kern then
2592            name = "kern"
2593        elseif specification.globalkerns then
2594            name = "globalkern"
2595        else
2596            report("ignoring global kern table, using gpos kern feature")
2597            return
2598        end
2599        setposition(f,datatable.offset)
2600        local version   = readushort(f)
2601        local noftables = readushort(f)
2602        if noftables > 1 then
2603            report("adding global kern table as gpos feature %a",name)
2604            local kerns = setmetatableindex("table")
2605            for i=1,noftables do
2606                local version  = readushort(f)
2607                local length   = readushort(f)
2608                local coverage = readushort(f)
2609                -- bit 8-15 of coverage: format 0 or 2
2610                local format   = rshift(coverage,8) -- is this ok
2611                if format == 0 then
2612                    local nofpairs      = readushort(f)
2613                    local searchrange   = readushort(f)
2614                    local entryselector = readushort(f)
2615                    local rangeshift    = readushort(f)
2616                    for i=1,nofpairs do
2617                        kerns[readushort(f)][readushort(f)] = readfword(f)
2618                    end
2619                elseif format == 2 then
2620                    -- apple specific so let's ignore it
2621                else
2622                    -- not supported by ms
2623                end
2624            end
2625            local feature = { dflt = { dflt = true } }
2626            if not features then
2627                fontdata.features = { gpos = { [name] = feature } }
2628            elseif not gposfeatures then
2629                fontdata.features.gpos = { [name] = feature }
2630            else
2631                gposfeatures[name] = feature
2632            end
2633            local sequences = fontdata.sequences
2634            if not sequences then
2635                sequences = { }
2636                fontdata.sequences = sequences
2637            end
2638            local nofsequences = #sequences + 1
2639            sequences[nofsequences] = {
2640                index     = nofsequences,
2641                name      = name,
2642                steps     = {
2643                    {
2644                        coverage = kerns,
2645                        format   = "kern",
2646                    },
2647                },
2648                nofsteps  = 1,
2649                type      = "gpos_pair",
2650                flags     = { false, false, false, false },
2651                order     = { name },
2652                features  = { [name] = feature },
2653            }
2654        else
2655            report("ignoring empty kern table of feature %a",name)
2656        end
2657    end
2658
2659    function readers.gsub(f,fontdata,specification)
2660        if specification.details then
2661            readscripts(f,fontdata,"gsub",gsubtypes,gsubhandlers,specification.lookups)
2662        end
2663    end
2664
2665    function readers.gpos(f,fontdata,specification)
2666        if specification.details then
2667            readscripts(f,fontdata,"gpos",gpostypes,gposhandlers,specification.lookups)
2668            if specification.lookups then
2669                checkkerns(f,fontdata,specification)
2670            end
2671        end
2672    end
2673
2674end
2675
2676function readers.gdef(f,fontdata,specification)
2677    if not specification.glyphs then
2678        return
2679    end
2680    local datatable = fontdata.tables.gdef
2681    if datatable then
2682        local tableoffset = datatable.offset
2683        setposition(f,tableoffset)
2684        local version          = readulong(f)
2685        local classoffset      = readushort(f)
2686        local attachmentoffset = readushort(f) -- used for bitmaps
2687        local ligaturecarets   = readushort(f) -- used in editors (maybe nice for tracing)
2688        local markclassoffset  = readushort(f)
2689        local marksetsoffset   = version >= 0x00010002 and readushort(f) or 0
2690        local varsetsoffset    = version >= 0x00010003 and readulong(f) or 0
2691        local glyphs           = fontdata.glyphs
2692        local marks            = { }
2693        local markclasses      = setmetatableindex("table")
2694        local marksets         = setmetatableindex("table")
2695        fontdata.marks         = marks
2696        fontdata.markclasses   = markclasses
2697        fontdata.marksets      = marksets
2698        -- class definitions
2699        if classoffset ~= 0 then
2700            setposition(f,tableoffset + classoffset)
2701            local classformat = readushort(f)
2702            if classformat == 1 then
2703                local firstindex = readushort(f)
2704                local lastindex  = firstindex + readushort(f) - 1
2705                for index=firstindex,lastindex do
2706                    local class = classes[readushort(f)]
2707                    if class == "mark" then
2708                        marks[index] = true
2709                    end
2710                    glyphs[index].class = class
2711                end
2712            elseif classformat == 2 then
2713                local nofranges = readushort(f)
2714                for i=1,nofranges do
2715                    local firstindex = readushort(f)
2716                    local lastindex  = readushort(f)
2717                    local class      = classes[readushort(f)]
2718                    if class then
2719                        for index=firstindex,lastindex do
2720                            glyphs[index].class = class
2721                            if class == "mark" then
2722                                marks[index] = true
2723                            end
2724                        end
2725                    end
2726                end
2727            end
2728        end
2729        -- mark classes
2730        if markclassoffset ~= 0 then
2731            setposition(f,tableoffset + markclassoffset)
2732            local classformat = readushort(f)
2733            if classformat == 1 then
2734                local firstindex = readushort(f)
2735                local lastindex  = firstindex + readushort(f) - 1
2736                for index=firstindex,lastindex do
2737                    markclasses[readushort(f)][index] = true
2738                end
2739            elseif classformat == 2 then
2740                local nofranges = readushort(f)
2741                for i=1,nofranges do
2742                    local firstindex = readushort(f)
2743                    local lastindex  = readushort(f)
2744                    local class      = markclasses[readushort(f)]
2745                    for index=firstindex,lastindex do
2746                        class[index] = true
2747                    end
2748                end
2749            end
2750        end
2751        -- mark sets
2752        if marksetsoffset ~= 0 then
2753            local nofmarkclasses = fontdata.markclasses and #fontdata.markclasses or 0
2754            marksetsoffset = tableoffset + marksetsoffset
2755            setposition(f,marksetsoffset)
2756            local format = readushort(f)
2757            if format == 1 then
2758                local nofsets = readushort(f)
2759                local sets    = readcardinaltable(f,nofsets,ulong)
2760                for i=1,nofsets do
2761                    local offset = sets[i]
2762                    if offset ~= 0 then
2763                        markclasses[nofmarkclasses + i] = readcoverage(f,marksetsoffset+offset)
2764                        marksets[i] = { }
2765                    end
2766                end
2767            end
2768        end
2769
2770        local factors = specification.factors
2771
2772        if (specification.variable or factors) and varsetsoffset ~= 0 then
2773
2774            local regions, deltas = readvariationdata(f,tableoffset+varsetsoffset,factors)
2775
2776         -- setvariabledata(fontdata,"gregions",regions)
2777
2778            if factors then
2779                fontdata.temporary.getdelta = function(outer,inner)
2780                    local delta = deltas[outer+1]
2781                    if delta then
2782                        local d = delta.deltas[inner+1]
2783                        if d then
2784                            local scales = delta.scales
2785                            local dd = 0
2786                            for i=1,#scales do
2787                                local di = d[i]
2788                                if di then
2789                                    dd = dd + scales[i] * di
2790                                else
2791                                    break
2792                                end
2793                            end
2794                            return round(dd)
2795                        end
2796                    end
2797                    return 0
2798                end
2799            end
2800
2801        end
2802    end
2803end
2804
2805-- We keep this code here instead of font-otm.lua because we need coverage
2806-- helpers. Okay, these helpers could go to the main reader file some day.
2807
2808local function readmathvalue(f)
2809    local v = readshort(f)
2810    skipshort(f,1) -- offset to device table
2811    return v
2812end
2813
2814local function readmathconstants(f,fontdata,offset)
2815    setposition(f,offset)
2816    fontdata.mathconstants = {
2817        ScriptPercentScaleDown                   = readshort(f),
2818        ScriptScriptPercentScaleDown             = readshort(f),
2819        DelimitedSubFormulaMinHeight             = readushort(f),
2820        DisplayOperatorMinHeight                 = readushort(f),
2821        MathLeading                              = readmathvalue(f),
2822        AxisHeight                               = readmathvalue(f),
2823        AccentBaseHeight                         = readmathvalue(f),
2824        FlattenedAccentBaseHeight                = readmathvalue(f),
2825        SubscriptShiftDown                       = readmathvalue(f),
2826        SubscriptTopMax                          = readmathvalue(f),
2827        SubscriptBaselineDropMin                 = readmathvalue(f),
2828        SuperscriptShiftUp                       = readmathvalue(f),
2829        SuperscriptShiftUpCramped                = readmathvalue(f),
2830        SuperscriptBottomMin                     = readmathvalue(f),
2831        SuperscriptBaselineDropMax               = readmathvalue(f),
2832        SubSuperscriptGapMin                     = readmathvalue(f),
2833        SuperscriptBottomMaxWithSubscript        = readmathvalue(f),
2834        SpaceAfterScript                         = readmathvalue(f),
2835        UpperLimitGapMin                         = readmathvalue(f),
2836        UpperLimitBaselineRiseMin                = readmathvalue(f),
2837        LowerLimitGapMin                         = readmathvalue(f),
2838        LowerLimitBaselineDropMin                = readmathvalue(f),
2839        StackTopShiftUp                          = readmathvalue(f),
2840        StackTopDisplayStyleShiftUp              = readmathvalue(f),
2841        StackBottomShiftDown                     = readmathvalue(f),
2842        StackBottomDisplayStyleShiftDown         = readmathvalue(f),
2843        StackGapMin                              = readmathvalue(f),
2844        StackDisplayStyleGapMin                  = readmathvalue(f),
2845        StretchStackTopShiftUp                   = readmathvalue(f),
2846        StretchStackBottomShiftDown              = readmathvalue(f),
2847        StretchStackGapAboveMin                  = readmathvalue(f),
2848        StretchStackGapBelowMin                  = readmathvalue(f),
2849        FractionNumeratorShiftUp                 = readmathvalue(f),
2850        FractionNumeratorDisplayStyleShiftUp     = readmathvalue(f),
2851        FractionDenominatorShiftDown             = readmathvalue(f),
2852        FractionDenominatorDisplayStyleShiftDown = readmathvalue(f),
2853        FractionNumeratorGapMin                  = readmathvalue(f),
2854        FractionNumeratorDisplayStyleGapMin      = readmathvalue(f),
2855        FractionRuleThickness                    = readmathvalue(f),
2856        FractionDenominatorGapMin                = readmathvalue(f),
2857        FractionDenominatorDisplayStyleGapMin    = readmathvalue(f),
2858        SkewedFractionHorizontalGap              = readmathvalue(f),
2859        SkewedFractionVerticalGap                = readmathvalue(f),
2860        OverbarVerticalGap                       = readmathvalue(f),
2861        OverbarRuleThickness                     = readmathvalue(f),
2862        OverbarExtraAscender                     = readmathvalue(f),
2863        UnderbarVerticalGap                      = readmathvalue(f),
2864        UnderbarRuleThickness                    = readmathvalue(f),
2865        UnderbarExtraDescender                   = readmathvalue(f),
2866        RadicalVerticalGap                       = readmathvalue(f),
2867        RadicalDisplayStyleVerticalGap           = readmathvalue(f),
2868        RadicalRuleThickness                     = readmathvalue(f),
2869        RadicalExtraAscender                     = readmathvalue(f),
2870        RadicalKernBeforeDegree                  = readmathvalue(f),
2871        RadicalKernAfterDegree                   = readmathvalue(f),
2872        RadicalDegreeBottomRaisePercent          = readshort(f),
2873    }
2874end
2875
2876local function readmathglyphinfo(f,fontdata,offset)
2877    setposition(f,offset)
2878    local italics    = readushort(f)
2879    local accents    = readushort(f)
2880    local extensions = readushort(f)
2881    local kerns      = readushort(f)
2882    local glyphs     = fontdata.glyphs
2883    if italics ~= 0 then
2884        setposition(f,offset+italics)
2885        local coverage  = readushort(f)
2886        local nofglyphs = readushort(f)
2887        coverage = readcoverage(f,offset+italics+coverage,true)
2888        setposition(f,offset+italics+4)
2889        for i=1,nofglyphs do
2890            local italic = readmathvalue(f)
2891            if italic ~= 0 then
2892                local glyph = glyphs[coverage[i]]
2893                local math  = glyph.math
2894                if not math then
2895                    glyph.math = { italic = italic }
2896                else
2897                    math.italic = italic
2898                end
2899            end
2900        end
2901        fontdata.hasitalics = true
2902    end
2903    if accents ~= 0 then
2904        setposition(f,offset+accents)
2905        local coverage  = readushort(f)
2906        local nofglyphs = readushort(f)
2907        coverage = readcoverage(f,offset+accents+coverage,true)
2908        setposition(f,offset+accents+4)
2909        for i=1,nofglyphs do
2910            local accent = readmathvalue(f)
2911            if accent ~= 0 then
2912                local glyph = glyphs[coverage[i]]
2913                local math  = glyph.math
2914                if not math then
2915                    glyph.math = { accent = accent }
2916                else
2917                    math.accent = accent -- will become math.topanchor
2918                end
2919            end
2920        end
2921    end
2922    if extensions ~= 0 then
2923        setposition(f,offset+extensions)
2924    end
2925    if kerns ~= 0 then
2926        local kernoffset = offset + kerns
2927        setposition(f,kernoffset)
2928        local coverage  = readushort(f)
2929        local nofglyphs = readushort(f)
2930        if nofglyphs > 0 then
2931            local function get(offset)
2932                setposition(f,kernoffset+offset)
2933                local n = readushort(f)
2934                if n == 0 then
2935                    local k = readmathvalue(f)
2936                    if k == 0 then
2937                        -- no need for it (happens sometimes)
2938                    else
2939                        return { { kern = k } }
2940                    end
2941                else
2942                    local l = { }
2943                    for i=1,n do
2944                        l[i] = { height = readmathvalue(f) }
2945                    end
2946                    for i=1,n do
2947                        l[i].kern = readmathvalue(f)
2948                    end
2949                    l[n+1] = { kern = readmathvalue(f) }
2950                    return l
2951                end
2952            end
2953            local kernsets = { }
2954            for i=1,nofglyphs do
2955                local topright    = readushort(f)
2956                local topleft     = readushort(f)
2957                local bottomright = readushort(f)
2958                local bottomleft  = readushort(f)
2959                kernsets[i] = {
2960                    topright    = topright    ~= 0 and topright    or nil,
2961                    topleft     = topleft     ~= 0 and topleft     or nil,
2962                    bottomright = bottomright ~= 0 and bottomright or nil,
2963                    bottomleft  = bottomleft  ~= 0 and bottomleft  or nil,
2964                }
2965            end
2966            coverage = readcoverage(f,kernoffset+coverage,true)
2967            for i=1,nofglyphs do
2968                local kernset = kernsets[i]
2969                if next(kernset) then
2970                    local k = kernset.topright    if k then kernset.topright    = get(k) end
2971                    local k = kernset.topleft     if k then kernset.topleft     = get(k) end
2972                    local k = kernset.bottomright if k then kernset.bottomright = get(k) end
2973                    local k = kernset.bottomleft  if k then kernset.bottomleft  = get(k) end
2974                    if next(kernset) then
2975                        local glyph = glyphs[coverage[i]]
2976                        local math  = glyph.math
2977                        if math then
2978                            math.kerns = kernset
2979                        else
2980                            glyph.math = { kerns = kernset }
2981                        end
2982                    end
2983                end
2984            end
2985        end
2986    end
2987end
2988
2989local function readmathvariants(f,fontdata,offset)
2990    setposition(f,offset)
2991    local glyphs        = fontdata.glyphs
2992    local minoverlap    = readushort(f)
2993    local vcoverage     = readushort(f)
2994    local hcoverage     = readushort(f)
2995    local vnofglyphs    = readushort(f)
2996    local hnofglyphs    = readushort(f)
2997    local vconstruction = readcardinaltable(f,vnofglyphs,ushort)
2998    local hconstruction = readcardinaltable(f,hnofglyphs,ushort)
2999
3000    fontdata.mathconstants.MinConnectorOverlap = minoverlap
3001
3002    -- variants[i] = {
3003    --     glyph   = readushort(f),
3004    --     advance = readushort(f),
3005    -- }
3006
3007    local function get(offset,coverage,nofglyphs,construction,kvariants,kparts,kitalic,korientation,orientation)
3008        if coverage ~= 0 and nofglyphs > 0 then
3009            local coverage = readcoverage(f,offset+coverage,true)
3010            local n = 0
3011            for i=1,nofglyphs do
3012                local c = construction[i]
3013                if c ~= 0 then
3014                    local index = coverage[i]
3015                    local glyph = glyphs[index]
3016                    local math  = glyph.math
3017                    setposition(f,offset+c)
3018                    local assembly    = readushort(f)
3019                    local nofvariants = readushort(f)
3020                    if nofvariants > 0 then
3021                        local variants, v = nil, 0
3022                        for i=1,nofvariants do
3023                            local variant = readushort(f)
3024                            if variant == index then
3025                             -- report("discarding %s variant %04X for %04X, %C","self referencing",variant,index,tonumber(glyph.unicode) or 0xFFFD)
3026                                n = n + 1
3027                            elseif variants then
3028                                v = v + 1
3029                                variants[v] = variant
3030                            else
3031                                v = 1
3032                                variants = { variant }
3033                            end
3034                            skipshort(f)
3035                        end
3036                        if not variants or not next(variants) then
3037                            -- only self
3038                        elseif not math then
3039                            math = { [kvariants] = variants }
3040                            glyph.math = math
3041                        else
3042                            math[kvariants] = variants
3043                        end
3044                    end
3045                    if assembly ~= 0 then
3046                        setposition(f,offset + c + assembly)
3047                        local italic   = readmathvalue(f)
3048                        local nofparts = readushort(f)
3049                        local parts    = { }
3050                        for i=1,nofparts do
3051                            local p = {
3052                                glyph   = readushort(f),
3053                                start   = readushort(f),
3054                                ["end"] = readushort(f),
3055                                advance = readushort(f),
3056                            }
3057                            local flags = readushort(f)
3058                            if band(flags,0x0001) ~= 0 then
3059                                p.extender = 1 -- true
3060                            end
3061                            parts[i] = p
3062                        end
3063                        if not math then
3064                            math = {
3065                                [kparts] = parts
3066                            }
3067                            glyph.math = math
3068                        else
3069                            math[kparts] = parts
3070                        end
3071                        if italic and italic ~= 0 then
3072                            math[kitalic] = italic
3073                        end
3074                        if korientation and orientation then
3075                            math[korientation] = orientation
3076                        end
3077                    end
3078                end
3079            end
3080            -- There are quite some fonts out there (noto anno 2024) with these so we only report the
3081            -- number. It's often the first entry then.
3082            if n > 0 then
3083                report("discarding %i self referencing %s variant entries",n,orientation)
3084            end
3085            -- There are a few fonts out there (noto anno 2024) that have a variant list with entries
3086            -- that themselve have variants and that gives circular references in a 'next' chain.
3087            for index=1,#glyphs do
3088                local g = glyphs[index]
3089                local m = g.math
3090                if m then
3091                    local v = m[kvariants]
3092                    if v then
3093                        local done = { [index] = true }
3094                        local size = #v
3095                        local i    = 1
3096                        while i <= size do
3097                            local vi = v[i]
3098                            if done[vi] then
3099                                report("discarding %sdirect circular %s variant index 0x%04X for index 0x%04X, %C","",orientation,vi,index,tonumber(g.unicode) or 0xFFFD)
3100                                table.remove(v,i)
3101                                size = size - 1
3102                                goto NEXT
3103                            else
3104                                local gg = glyphs[vi]
3105                                if gg then
3106                                    local mm = gg.math
3107                                    if mm then
3108                                        local vv = mm[kvariants]
3109                                        if vv then
3110                                            report("discarding %sdirect circular %s variant index 0x%04X for index 0x%04X, %C","in",orientation,vi,index,tonumber(g.unicode) or 0xFFFD)
3111                                            table.remove(v,i)
3112                                            size = size - 1
3113                                            goto NEXT
3114                                        end
3115                                    end
3116                                end
3117                                done[vi] = true
3118                            end
3119                            i = i + 1
3120                          ::NEXT::
3121                        end
3122                    end
3123                end
3124            end
3125        end
3126    end
3127    get(offset,hcoverage,hnofglyphs,hconstruction,"hvariants","hparts","hitalic",nil,"horizontal")
3128    get(offset,vcoverage,vnofglyphs,vconstruction,"vvariants","vparts","vitalic",nil,"vertical")
3129end
3130
3131function readers.math(f,fontdata,specification)
3132    local tableoffset = gotodatatable(f,fontdata,"math",specification.glyphs)
3133    if tableoffset then
3134        local version = readulong(f)
3135     -- if version ~= 0x00010000 then
3136     --     report("table version %a of %a is not supported (yet), maybe font %s is bad",version,"math",fontdata.filename)
3137     --     return
3138     -- end
3139        local constants = readushort(f)
3140        local glyphinfo = readushort(f)
3141        local variants  = readushort(f)
3142        if constants == 0 then
3143            report("the math table of %a has no constants",fontdata.filename)
3144        else
3145            readmathconstants(f,fontdata,tableoffset+constants)
3146        end
3147        if glyphinfo ~= 0 then
3148            readmathglyphinfo(f,fontdata,tableoffset+glyphinfo)
3149        end
3150        if variants ~= 0 then
3151            readmathvariants(f,fontdata,tableoffset+variants)
3152        end
3153    end
3154end
3155
3156function readers.colr(f,fontdata,specification)
3157    local tableoffset = gotodatatable(f,fontdata,"colr",specification.glyphs)
3158    if tableoffset then
3159        local version = readushort(f)
3160		if version == 0 then
3161			-- we're okay
3162		elseif version == 1 then
3163			report("table version %a of %a is %s supported for font %s",version,"colr","partially",fontdata.filename)
3164		else
3165			report("table version %a of %a is %s supported for font %s",version,"colr","not",fontdata.filename)
3166			return
3167		end
3168        if not fontdata.tables.cpal then
3169            report("color table %a in font %a has no mandate %a table","colr",fontdata.filename,"cpal")
3170            fontdata.colorpalettes = { }
3171        end
3172        local glyphs       = fontdata.glyphs
3173        local nofglyphs    = readushort(f)
3174        local baseoffset   = readulong(f)
3175        local layeroffset  = readulong(f)
3176        local noflayers    = readushort(f)
3177        local layerrecords = { }
3178        local maxclass     = 0
3179        -- The special value 0xFFFF is foreground (but we index from 1). It
3180        -- more looks like indices into a palette so 'class' is a better name
3181        -- than 'palette'.
3182        setposition(f,tableoffset + layeroffset)
3183        for i=1,noflayers do
3184            local slot  = readushort(f)
3185            local class = readushort(f)
3186            if class < 0xFFFF then
3187                class = class + 1
3188                if class > maxclass then
3189                    maxclass = class
3190                end
3191            end
3192            layerrecords[i] = {
3193                slot  = slot,
3194                class = class,
3195            }
3196        end
3197        fontdata.maxcolorclass = maxclass
3198        setposition(f,tableoffset + baseoffset)
3199        for i=0,nofglyphs-1 do
3200            local glyphindex = readushort(f)
3201            local firstlayer = readushort(f)
3202            local noflayers  = readushort(f)
3203            local t = { }
3204            for i=1,noflayers do
3205                t[i] = layerrecords[firstlayer+i]
3206            end
3207            glyphs[glyphindex].colors = t
3208        end
3209    end
3210    fontdata.hascolor = true
3211end
3212
3213function readers.cpal(f,fontdata,specification)
3214    local tableoffset = gotodatatable(f,fontdata,"cpal",specification.glyphs)
3215    if tableoffset then
3216        local version = readushort(f)
3217     -- if version > 1 then
3218     --     report("table version %a of %a is not supported (yet), maybe font %s is bad",version,"cpal",fontdata.filename)
3219     --     return
3220     -- end
3221        local nofpaletteentries  = readushort(f)
3222        local nofpalettes        = readushort(f)
3223        local nofcolorrecords    = readushort(f)
3224        local firstcoloroffset   = readulong(f)
3225        local colorrecords       = { }
3226        local palettes           = readcardinaltable(f,nofpalettes,ushort)
3227        if version == 1 then
3228            -- used for guis
3229            local palettettypesoffset = readulong(f)
3230            local palettelabelsoffset = readulong(f)
3231            local paletteentryoffset  = readulong(f)
3232        end
3233        setposition(f,tableoffset+firstcoloroffset)
3234        for i=1,nofcolorrecords do
3235            local b, g, r, a = readbytes(f,4)
3236            colorrecords[i] = {
3237                r, g, b, a ~= 255 and a or nil,
3238            }
3239        end
3240        for i=1,nofpalettes do
3241            local p = { }
3242            local o = palettes[i]
3243            for j=1,nofpaletteentries do
3244                p[j] = colorrecords[o+j]
3245            end
3246            palettes[i] = p
3247        end
3248        fontdata.colorpalettes = palettes
3249    end
3250end
3251
3252local compress   = gzip and gzip.compress
3253local compressed = compress and gzip.compressed
3254
3255-- compressed = false
3256
3257function readers.svg(f,fontdata,specification)
3258    local tableoffset = gotodatatable(f,fontdata,"svg",specification.glyphs)
3259    if tableoffset then
3260        local version = readushort(f)
3261     -- if version ~= 0 then
3262     --     report("table version %a of %a is not supported (yet), maybe font %s is bad",version,"svg",fontdata.filename)
3263     --     return
3264     -- end
3265        local glyphs      = fontdata.glyphs
3266        local indexoffset = tableoffset + readulong(f)
3267        local reserved    = readulong(f)
3268        setposition(f,indexoffset)
3269        local nofentries  = readushort(f)
3270        local entries     = { }
3271        for i=1,nofentries do
3272            entries[i] = {
3273                first  = readushort(f),
3274                last   = readushort(f),
3275                offset = indexoffset + readulong(f),
3276                length = readulong(f),
3277            }
3278        end
3279        for i=1,nofentries do
3280            local entry = entries[i]
3281            setposition(f,entry.offset)
3282            local data = readstring(f,entry.length)
3283            if compressed and not compressed(data) then
3284                data = compress(data)
3285            end
3286            entries[i] = {
3287                first = entry.first,
3288                last  = entry.last,
3289                data  = data
3290            }
3291        end
3292        fontdata.svgshapes = entries
3293    end
3294    fontdata.hascolor = true
3295end
3296
3297function readers.sbix(f,fontdata,specification)
3298    local tableoffset = gotodatatable(f,fontdata,"sbix",specification.glyphs)
3299    if tableoffset then
3300        local version    = readushort(f)
3301        local flags      = readushort(f)
3302        local nofstrikes = readulong(f)
3303        local strikes    = { }
3304        local nofglyphs  = fontdata.nofglyphs
3305        for i=1,nofstrikes do
3306            strikes[i] = readulong(f)
3307        end
3308        local shapes = { }
3309        local done   = 0
3310        for i=1,nofstrikes do
3311            local strikeoffset = strikes[i] + tableoffset
3312            setposition(f,strikeoffset)
3313            strikes[i] = {
3314                ppem   = readushort(f),
3315                ppi    = readushort(f),
3316                offset = strikeoffset
3317            }
3318        end
3319        -- highest first
3320        sort(strikes,function(a,b)
3321            if b.ppem == a.ppem then
3322                return b.ppi < a.ppi
3323            else
3324                return b.ppem < a.ppem
3325            end
3326        end)
3327        local glyphs  = { }
3328        local delayed = CONTEXTLMTXMODE and CONTEXTLMTXMODE > 0 or fonts.handlers.typethree
3329        for i=1,nofstrikes do
3330            local strike       = strikes[i]
3331            local strikeppem   = strike.ppem
3332            local strikeppi    = strike.ppi
3333            local strikeoffset = strike.offset
3334            setposition(f,strikeoffset)
3335            for i=0,nofglyphs do
3336                glyphs[i] = readulong(f)
3337            end
3338            local glyphoffset = glyphs[0]
3339            for i=0,nofglyphs-1 do
3340                local nextoffset = glyphs[i+1]
3341                if not shapes[i] then
3342                    local datasize = nextoffset - glyphoffset
3343                    if datasize > 0 then
3344                        setposition(f,strikeoffset + glyphoffset)
3345                        local x      = readshort(f)
3346                        local y      = readshort(f)
3347                        local tag    = readtag(f) -- or just skip, we never needed it till now
3348                        local size   = datasize - 8
3349                        local data   = nil
3350                        local offset = nil
3351                        if delayed then
3352                            offset = getposition(f)
3353                            data   = nil
3354                        else
3355                            data   = readstring(f,size)
3356                            size   = nil
3357                        end
3358                        shapes[i] = {
3359                            x    = x,
3360                            y    = y,
3361                            o    = offset,
3362                            s    = size,
3363                            data = data,
3364                         -- tag  = tag, -- maybe for tracing
3365                         -- ppem = strikeppem, -- not used, for tracing
3366                         -- ppi  = strikeppi,  -- not used, for tracing
3367                        }
3368                        done = done + 1
3369                        if done == nofglyphs then
3370                            break
3371                        end
3372                    end
3373                end
3374                glyphoffset = nextoffset
3375            end
3376        end
3377        fontdata.pngshapes = shapes
3378    end
3379end
3380
3381-- Another bitmap (so not that useful) format. But Luigi found a font that
3382-- has them , so ...
3383
3384do
3385
3386    local function getmetrics(f)
3387        return {
3388            ascender              = readinteger(f),
3389            descender             = readinteger(f),
3390            widthmax              = readuinteger(f),
3391            caretslopedumerator   = readinteger(f),
3392            caretslopedenominator = readinteger(f),
3393            caretoffset           = readinteger(f),
3394            minorigin             = readinteger(f),
3395            minadvance            = readinteger(f),
3396            maxbefore             = readinteger(f),
3397            minafter              = readinteger(f),
3398            pad1                  = readinteger(f),
3399            pad2                  = readinteger(f),
3400        }
3401    end
3402
3403    -- bad names
3404
3405    local function getbigmetrics(f)
3406        -- bigmetrics, maybe just skip 9 bytes
3407        return {
3408            height       = readuinteger(f),
3409            width        = readuinteger(f),
3410            horiBearingX = readinteger(f),
3411            horiBearingY = readinteger(f),
3412            horiAdvance  = readuinteger(f),
3413            vertBearingX = readinteger(f),
3414            vertBearingY = readinteger(f),
3415            vertAdvance  = readuinteger(f),
3416        }
3417    end
3418
3419    local function getsmallmetrics(f)
3420        -- smallmetrics, maybe just skip 5 bytes
3421        return {
3422            height   = readuinteger(f),
3423            width    = readuinteger(f),
3424            bearingX = readinteger(f),
3425            bearingY = readinteger(f),
3426            advance  = readuinteger(f),
3427        }
3428    end
3429
3430    function readers.cblc(f,fontdata,specification)
3431        -- should we delay this ?
3432        local ctdttableoffset = gotodatatable(f,fontdata,"cbdt",specification.glyphs)
3433        if not ctdttableoffset then
3434            return
3435        end
3436        local cblctableoffset = gotodatatable(f,fontdata,"cblc",specification.glyphs)
3437        if cblctableoffset then
3438            local majorversion  = readushort(f)
3439            local minorversion  = readushort(f)
3440            local nofsizetables = readulong(f)
3441            local sizetables    = { }
3442            local shapes        = { }
3443            local subtables     = { }
3444            for i=1,nofsizetables do
3445                sizetables[i] = {
3446                    subtables    = readulong(f),
3447                    indexsize    = readulong(f),
3448                    nofsubtables = readulong(f),
3449                    colorref     = readulong(f),
3450                    hormetrics   = getmetrics(f),
3451                    vermetrics   = getmetrics(f),
3452                    firstindex   = readushort(f),
3453                    lastindex    = readushort(f),
3454                    ppemx        = readbyte(f),
3455                    ppemy        = readbyte(f),
3456                    bitdepth     = readbyte(f),
3457                    flags        = readbyte(f),
3458                }
3459            end
3460            sort(sizetables,function(a,b)
3461                if b.ppemx == a.ppemx then
3462                    return b.bitdepth < a.bitdepth
3463                else
3464                    return b.ppemx < a.ppemx
3465                end
3466            end)
3467            for i=1,nofsizetables do
3468                local s = sizetables[i]
3469                local d = false
3470                for j=s.firstindex,s.lastindex do
3471                    if not shapes[j] then
3472                        shapes[j] = i
3473                        d = true
3474                    end
3475                end
3476                if d then
3477                    s.used = true
3478                end
3479            end
3480            for i=1,nofsizetables do
3481                local s = sizetables[i]
3482                if s.used then
3483                    local offset = s.subtables
3484                    setposition(f,cblctableoffset+offset)
3485                    for j=1,s.nofsubtables do
3486                        local firstindex  = readushort(f)
3487                        local lastindex   = readushort(f)
3488                        local tableoffset = readulong(f) + offset
3489                        for k=firstindex,lastindex do
3490                            if shapes[k] == i then
3491                                local s = subtables[tableoffset]
3492                                if not s then
3493                                    s = {
3494                                        firstindex = firstindex,
3495                                        lastindex  = lastindex,
3496                                    }
3497                                    subtables[tableoffset] = s
3498                                end
3499                                shapes[k] = s
3500                            end
3501                        end
3502                    end
3503                end
3504            end
3505
3506            -- there is no need to sort in string stream but we have a nicer trace
3507            -- if needed
3508
3509            for offset, subtable in sortedhash(subtables) do
3510                local tabletype  = readushort(f)
3511                subtable.format  = readushort(f)
3512                local baseoffset = readulong(f) + ctdttableoffset
3513                local offsets    = { }
3514                local metrics    = nil
3515                if tabletype == 1 then
3516                    -- we have the usual one more to get the size
3517                    for i=subtable.firstindex,subtable.lastindex do
3518                        offsets[i] = readulong(f) + baseoffset
3519                    end
3520                    skipbytes(f,4)
3521                elseif tabletype == 2 then
3522                    local size = readulong(f)
3523                    local done = baseoffset
3524                    metrics = getbigmetrics(f)
3525                    for i=subtable.firstindex,subtable.lastindex do
3526                        offsets[i] = done
3527                        done = done + size
3528                    end
3529                elseif tabletype == 3 then
3530                    -- we have the usual one more to get the size
3531                    local n = subtable.lastindex - subtable.firstindex + 2
3532                    for i=subtable.firstindex,subtable.lastindex do
3533                        offsets[i] = readushort(f) + baseoffset
3534                    end
3535                    if math.odd(n) then
3536                        skipbytes(f,4)
3537                    else
3538                        skipbytes(f,2)
3539                    end
3540                elseif tabletype == 4 then
3541                    for i=1,readulong(f) do
3542                        offsets[readushort(f)] = readushort(f) + baseoffset
3543                    end
3544                elseif tabletype == 5 then
3545                    local size = readulong(f)
3546                    local done = baseoffset
3547                    metrics = getbigmetrics(f)
3548                    local n = readulong(f)
3549                    for i=1,n do
3550                        offsets[readushort(f)] = done
3551                        done = done + size
3552                    end
3553                    if math.odd(n) then
3554                        skipbytes(f,2)
3555                    end
3556                else
3557                    return -- unsupported format
3558                end
3559                subtable.offsets = offsets
3560                subtable.metrics = metrics
3561            end
3562
3563            -- we only support a few sensible types ... there are hardly any fonts so
3564            -- why are there so many variants ... not the best spec
3565
3566            local default = { width = 0, height = 0 }
3567            local glyphs  = fontdata.glyphs
3568            local delayed = CONTEXTLMTXMODE and CONTEXTLMTXMODE > 0 or fonts.handlers.typethree
3569
3570            for index, subtable in sortedhash(shapes) do
3571                if type(subtable) == "table" then
3572                    local data    = nil
3573                    local size    = nil
3574                    local metrics = default
3575                    local format  = subtable.format
3576                    local offset  = subtable.offsets[index]
3577                    setposition(f,offset)
3578                    if format == 17 then
3579                        metrics = getsmallmetrics(f)
3580                        size    = true
3581                    elseif format == 18 then
3582                        metrics = getbigmetrics(f)
3583                        size    = true
3584                    elseif format == 19 then
3585                        metrics = subtable.metrics
3586                        size    = true
3587                    else
3588                        -- forget about it
3589                    end
3590                    if size then
3591                        size = readulong(f)
3592                        if delayed then
3593                            offset = getposition(f)
3594                            data   = nil
3595                        else
3596                            offset = nil
3597                            data   = readstring(f,size)
3598                            size   = nil
3599                        end
3600                    else
3601                        offset = nil
3602                    end
3603                    local x = metrics.width
3604                    local y = metrics.height
3605                    shapes[index] = {
3606                        x    = x,
3607                        y    = y,
3608                        o    = offset,
3609                        s    = size,
3610                        data = data,
3611                    }
3612                    -- I'll look into this in more details when needed
3613                    -- as we can use the bearings to get better boxes.
3614                    local glyph = glyphs[index]
3615                    if not glyph.boundingbox then
3616                        local width  = glyph.width
3617                        local height = width * y/x
3618                        glyph.boundingbox = { 0, 0, width, height }
3619                    end
3620                else
3621                    shapes[index] = {
3622                        x    = 0,
3623                        y    = 0,
3624                        data = "", -- or just nil
3625                    }
3626                end
3627            end
3628
3629            fontdata.pngshapes = shapes -- we cheat
3630        end
3631    end
3632
3633    function readers.cbdt(f,fontdata,specification)
3634     -- local tableoffset = gotodatatable(f,fontdata,"ctdt",specification.glyphs)
3635     -- if tableoffset then
3636     --     local majorversion = readushort(f)
3637     --     local minorversion = readushort(f)
3638     -- end
3639    end
3640
3641    -- function readers.ebdt(f,fontdata,specification)
3642    --     if specification.glyphs then
3643    --     end
3644    -- end
3645
3646    -- function readers.ebsc(f,fontdata,specification)
3647    --     if specification.glyphs then
3648    --     end
3649    -- end
3650
3651    -- function readers.eblc(f,fontdata,specification)
3652    --     if specification.glyphs then
3653    --     end
3654    -- end
3655
3656end
3657
3658-- + AVAR : optional
3659-- + CFF2 : otf outlines
3660-- - CVAR : ttf hinting, not needed
3661-- + FVAR : the variations
3662-- + GVAR : ttf outline changes
3663-- + HVAR : horizontal changes
3664-- + MVAR : metric changes
3665-- + STAT : relations within fonts
3666-- * VVAR : vertical changes
3667--
3668-- * BASE : extra baseline adjustments
3669-- - GASP : not needed
3670-- + GDEF : not needed (carets)
3671-- + GPOS : adapted device tables (needed?)
3672-- + GSUB : new table
3673-- + NAME : 25 added
3674
3675function readers.stat(f,fontdata,specification)
3676    local tableoffset = gotodatatable(f,fontdata,"stat",true) -- specification.variable
3677    if tableoffset then
3678        local extras       = fontdata.extras
3679        local version      = readulong(f) -- 0x00010000
3680        local axissize     = readushort(f)
3681        local nofaxis      = readushort(f)
3682        local axisoffset   = readulong(f)
3683        local nofvalues    = readushort(f)
3684        local valuesoffset = readulong(f)
3685        local fallbackname = extras[readushort(f)] -- beta fonts mess up
3686        local axis         = { }
3687        local values       = { }
3688        setposition(f,tableoffset+axisoffset)
3689        for i=1,nofaxis do
3690            local tag = readtag(f)
3691            axis[i] = {
3692                tag      = tag,
3693                name     = lower(extras[readushort(f)] or tag),
3694                ordering = readushort(f), -- maybe gaps
3695                variants = { }
3696            }
3697        end
3698        -- flags:
3699        --
3700        -- 0x0001 : OlderSiblingFontAttribute
3701        -- 0x0002 : ElidableAxisValueName
3702        -- 0xFFFC : reservedFlags
3703        --
3704        setposition(f,tableoffset+valuesoffset)
3705        for i=1,nofvalues do
3706            values[i] = readushort(f)
3707        end
3708        for i=1,nofvalues do
3709            setposition(f,tableoffset + valuesoffset + values[i])
3710            local format  = readushort(f)
3711            local index   = readushort(f) + 1
3712            local flags   = readushort(f)
3713            local name    = lower(extras[readushort(f)] or "no name")
3714            local value   = readfixed(f)
3715            local variant
3716            if format == 1 then
3717                variant = {
3718                    flags = flags,
3719                    name  = name,
3720                    value = value,
3721                }
3722            elseif format == 2 then
3723                variant = {
3724                    flags   = flags,
3725                    name    = name,
3726                    value   = value,
3727                    minimum = readfixed(f),
3728                    maximum = readfixed(f),
3729                }
3730            elseif format == 3 then
3731                variant = {
3732                    flags = flags,
3733                    name  = name,
3734                    value = value,
3735                    link  = readfixed(f),
3736                }
3737            end
3738            insert(axis[index].variants,variant)
3739        end
3740        sort(axis,function(a,b)
3741            return a.ordering < b.ordering
3742        end)
3743        for i=1,#axis do
3744            local a = axis[i]
3745            sort(a.variants,function(a,b)
3746                return a.name < b.name
3747            end)
3748            a.ordering = nil
3749        end
3750        setvariabledata(fontdata,"designaxis",axis)
3751        setvariabledata(fontdata,"fallbackname",fallbackname)
3752    end
3753end
3754
3755-- The avar table is optional and used in combination with fvar. Given the
3756-- detailed explanation about bad values we expect the worst and do some
3757-- checking.
3758
3759function readers.avar(f,fontdata,specification)
3760    local tableoffset = gotodatatable(f,fontdata,"avar",true) -- specification.variable
3761    if tableoffset then
3762
3763        local function collect()
3764            local nofvalues = readushort(f)
3765            local values    = { }
3766            local lastfrom  = false
3767            local lastto    = false
3768            for i=1,nofvalues do
3769                local from = read2dot14(f)
3770                local to   = read2dot14(f)
3771                if lastfrom and from <= lastfrom then
3772                    -- ignore
3773                elseif lastto and to >= lastto then
3774                    -- ignore
3775                else
3776                    values[#values+1] = { from, to }
3777                    lastfrom, lastto = from, to
3778                end
3779            end
3780            nofvalues = #values
3781            if nofvalues > 2 then
3782                local some = values[1]
3783                if some[1] == -1 and some[2] == -1 then
3784                    some = values[nofvalues]
3785                    if some[1] == 1 and some[2] == 1 then
3786                        for i=2,nofvalues-1 do
3787                            some = values[i]
3788                            if some[1] == 0 and some[2] == 0 then
3789                                return values
3790                            end
3791                        end
3792                    end
3793                end
3794            end
3795            return false
3796        end
3797
3798        local version  = readulong(f) -- 0x00010000
3799        local reserved = readushort(f)
3800        local nofaxis  = readushort(f)
3801        local segments = { }
3802        for i=1,nofaxis do
3803            segments[i] = collect()
3804        end
3805        setvariabledata(fontdata,"segments",segments)
3806    end
3807end
3808
3809function readers.fvar(f,fontdata,specification)
3810    local tableoffset = gotodatatable(f,fontdata,"fvar",true) -- specification.variable or specification.instancenames
3811    if tableoffset then
3812        local version         = readulong(f) -- 0x00010000
3813        local offsettoaxis    = tableoffset + readushort(f)
3814        local reserved        = skipshort(f)
3815        -- pair 1
3816        local nofaxis         = readushort(f)
3817        local sizeofaxis      = readushort(f)
3818        -- pair 2
3819        local nofinstances    = readushort(f)
3820        local sizeofinstances = readushort(f)
3821        --
3822        local extras    = fontdata.extras
3823        local axis      = { }
3824        local instances = { }
3825        --
3826        setposition(f,offsettoaxis)
3827        --
3828        for i=1,nofaxis do
3829            axis[i] = {
3830                tag     = readtag(f),   -- ital opsz slnt wdth wght
3831                minimum = readfixed(f),
3832                default = readfixed(f),
3833                maximum = readfixed(f),
3834                flags   = readushort(f),
3835                name    = lower(extras[readushort(f)] or "bad name"),
3836            }
3837            local n = sizeofaxis - 20
3838            if n > 0 then
3839                skipbytes(f,n)
3840            elseif n < 0 then
3841                -- error
3842            end
3843        end
3844        --
3845        local nofbytes   = 2 + 2 + 2 + nofaxis * 4
3846        local readpsname = nofbytes <= sizeofinstances
3847        local skippable  = sizeofinstances - nofbytes
3848        for i=1,nofinstances do
3849            local subfamid = readushort(f)
3850            local flags    = readushort(f) -- 0, not used yet
3851            local values   = { }
3852            for i=1,nofaxis do
3853                values[i] = {
3854                    axis  = axis[i].tag,
3855                    value = readfixed(f),
3856                }
3857            end
3858            local psnameid = readpsname and readushort(f) or 0xFFFF
3859            if subfamid == 2 or subfamid == 17 then
3860                -- okay
3861            elseif subfamid == 0xFFFF then
3862                subfamid = nil
3863            elseif subfamid <= 256 or subfamid >= 32768 then
3864                subfamid = nil -- actually an error
3865            end
3866            if psnameid == 6 then
3867                -- okay
3868            elseif psnameid == 0xFFFF then
3869                psnameid = nil
3870            elseif psnameid <= 256 or psnameid >= 32768 then
3871                psnameid = nil -- actually an error
3872            end
3873            instances[i] = {
3874             -- flags     = flags,
3875                subfamily = extras[subfamid],
3876                psname    = psnameid and extras[psnameid] or nil,
3877                values    = values,
3878            }
3879            if skippable > 0 then
3880                skipbytes(f,skippable)
3881            end
3882        end
3883        setvariabledata(fontdata,"axis",axis)
3884        setvariabledata(fontdata,"instances",instances)
3885    end
3886end
3887
3888function readers.hvar(f,fontdata,specification)
3889    local factors = specification.factors
3890    if not factors then
3891        return
3892    end
3893    local tableoffset = gotodatatable(f,fontdata,"hvar",specification.variable)
3894    if not tableoffset then
3895     -- report("no hvar table, expect problems due to messy widths")
3896        return
3897    end
3898
3899    local version         = readulong(f) -- 0x00010000
3900    local variationoffset = tableoffset + readulong(f) -- the store
3901    local advanceoffset   = tableoffset + readulong(f)
3902    local lsboffset       = tableoffset + readulong(f)
3903    local rsboffset       = tableoffset + readulong(f)
3904
3905    local regions    = { }
3906    local variations = { }
3907    local innerindex = { } -- size is mapcount
3908    local outerindex = { } -- size is mapcount
3909    local deltas     = { }
3910
3911    if variationoffset > 0 then
3912        regions, deltas = readvariationdata(f,variationoffset,factors)
3913    end
3914    if not regions then
3915        -- for now .. what to do ?
3916        return
3917    end
3918
3919    if advanceoffset > 0 then
3920        --
3921        -- innerIndexBitCountMask = 0x000F
3922        -- mapEntrySizeMask       = 0x0030
3923        -- reservedFlags          = 0xFFC0
3924        --
3925        -- outerIndex = entry >>       ((entryFormat & innerIndexBitCountMask) + 1)
3926        -- innerIndex = entry & ((1 << ((entryFormat & innerIndexBitCountMask) + 1)) - 1)
3927        --
3928        setposition(f,advanceoffset)
3929        local format       = readushort(f) -- todo: check
3930        local mapcount     = readushort(f)
3931        local entrysize    = rshift(band(format,0x0030),4) + 1
3932        local nofinnerbits = band(format,0x000F) + 1 -- n of inner bits
3933        local innermask    = lshift(1,nofinnerbits) - 1
3934        local readcardinal = read_cardinal[entrysize] -- 1 upto 4 bytes
3935        for i=0,mapcount-1 do
3936            local mapdata = readcardinal(f)
3937            outerindex[i] = rshift(mapdata,nofinnerbits)
3938            innerindex[i] = band(mapdata,innermask)
3939        end
3940        -- use last entry when no match i
3941        setvariabledata(fontdata,"hvarwidths",true)
3942        local glyphs = fontdata.glyphs
3943        for i=0,fontdata.nofglyphs-1 do
3944            local glyph = glyphs[i]
3945            local width = glyph.width
3946            if width then
3947                local outer = outerindex[i] or 0
3948                local inner = innerindex[i] or i
3949                if outer and inner then -- not needed
3950                    local delta = deltas[outer+1]
3951                    if delta then
3952                        local d = delta.deltas[inner+1]
3953                        if d then
3954                            local scales = delta.scales
3955                            local deltaw = 0
3956                            for i=1,#scales do
3957                                local di = d[i]
3958                                if di then
3959                                    deltaw = deltaw + scales[i] * di
3960                                else
3961                                    break -- can't happen
3962                                end
3963                            end
3964-- report("index: %i, outer: %i, inner: %i, deltas: %|t, scales: %|t, width: %i, delta %i",
3965--     i,outer,inner,d,scales,width,round(deltaw))
3966                            glyph.width = width + round(deltaw)
3967                        end
3968                    end
3969                end
3970            end
3971        end
3972
3973    end
3974
3975 -- if lsboffset > 0 then
3976 --     -- we don't use left side bearings
3977 -- end
3978
3979 -- if rsboffset > 0 then
3980 --     -- we don't use right side bearings
3981 -- end
3982
3983 -- setvariabledata(fontdata,"hregions",regions)
3984
3985end
3986
3987function readers.vvar(f,fontdata,specification)
3988    if not specification.variable then
3989        return
3990    end
3991end
3992
3993function readers.mvar(f,fontdata,specification)
3994    local tableoffset = gotodatatable(f,fontdata,"mvar",specification.variable)
3995    if tableoffset then
3996        local version       = readulong(f) -- 0x00010000
3997        local reserved      = skipshort(f,1)
3998        local recordsize    = readushort(f)
3999        local nofrecords    = readushort(f)
4000        local offsettostore = tableoffset + readushort(f)
4001        local dimensions    = { }
4002        local factors       = specification.factors
4003        if factors then
4004            local regions, deltas = readvariationdata(f,offsettostore,factors)
4005            for i=1,nofrecords do
4006                local tag = readtag(f)
4007                local var = variabletags[tag]
4008                if var then
4009                    local outer = readushort(f)
4010                    local inner = readushort(f)
4011                    local delta = deltas[outer+1]
4012                    if delta then
4013                        local d = delta.deltas[inner+1]
4014                        if d then
4015                            local scales = delta.scales
4016                            local dd = 0
4017                            for i=1,#scales do
4018                                dd = dd + scales[i] * d[i]
4019                            end
4020                            var(fontdata,round(dd))
4021                        end
4022                    end
4023                else
4024                    skipshort(f,2)
4025                end
4026                if recordsize > 8 then -- 4 + 2 + 2
4027                    skipbytes(recordsize-8)
4028                end
4029            end
4030        end
4031     -- setvariabledata(fontdata,"mregions",regions)
4032    end
4033end
4034
4035function readers.dsig(f,fontdata,specification)
4036    -- We're not going to deal with this security crap.
4037end
4038