font-dsp.lua /size: 152 Kb    last modification: 2023-12-21 09:44
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        local before     = readarray(f)
1187        local current    = readarray(f)
1188        local after      = readarray(f)
1189        local noflookups = readushort(f)
1190        local lookups    = readlookuparray(f,noflookups,#current)
1191        before  = readcoveragearray(f,tableoffset,before,true)
1192        current = readcoveragearray(f,tableoffset,current,true)
1193        after   = readcoveragearray(f,tableoffset,after,true)
1194        return {
1195            format = "coverage",
1196            rules  = {
1197                {
1198                    before  = before,
1199                    current = current,
1200                    after   = after,
1201                    lookups = lookups,
1202                }
1203            }
1204        }
1205    else
1206        report("unsupported subtype %a in %a %s",subtype,"chainedcontext",what)
1207    end
1208end
1209
1210local function extension(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs,types,handlers,what)
1211    local tableoffset = lookupoffset + offset
1212    setposition(f,tableoffset)
1213    local subtype = readushort(f)
1214    if subtype == 1 then
1215        local lookuptype = types[readushort(f)]
1216        local faroffset  = readulong(f)
1217        local handler    = handlers[lookuptype]
1218        if handler then
1219            -- maybe we can just pass one offset (or tableoffset first)
1220            return handler(f,fontdata,lookupid,tableoffset + faroffset,0,glyphs,nofglyphs), lookuptype
1221        else
1222            report("no handler for lookuptype %a subtype %a in %s %s",lookuptype,subtype,what,"extension")
1223        end
1224    else
1225        report("unsupported subtype %a in %s %s",subtype,what,"extension")
1226    end
1227end
1228
1229-- gsub handlers
1230
1231function gsubhandlers.single(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs)
1232    local tableoffset = lookupoffset + offset
1233    setposition(f,tableoffset)
1234    local subtype = readushort(f)
1235    if subtype == 1 then
1236        local coverage = readushort(f)
1237        local delta    = readshort(f) -- can be negative
1238        local coverage = readcoverage(f,tableoffset+coverage) -- not simple as we need to set key/value anyway
1239        for index in next, coverage do
1240            local newindex = (index + delta) % 65536 -- modulo is new in 1.8.3
1241            if index > nofglyphs or newindex > nofglyphs then
1242                report("invalid index in %s format %i: %i -> %i (max %i)","single",subtype,index,newindex,nofglyphs)
1243                coverage[index] = nil
1244            else
1245                coverage[index] = newindex
1246            end
1247        end
1248        return {
1249            coverage = coverage
1250        }
1251    elseif subtype == 2 then -- in streamreader a seek and fetch is faster than a temp table
1252        local coverage        = readushort(f)
1253        local nofreplacements = readushort(f)
1254        local replacements    = readcardinaltable(f,nofreplacements,ushort)
1255        local coverage = readcoverage(f,tableoffset + coverage) -- not simple as we need to set key/value anyway
1256        for index, newindex in next, coverage do
1257            newindex = newindex + 1
1258            if index > nofglyphs or newindex > nofglyphs then
1259                report("invalid index in %s format %i: %i -> %i (max %i)","single",subtype,index,newindex,nofglyphs)
1260                coverage[index] = nil
1261            else
1262                coverage[index] = replacements[newindex]
1263            end
1264        end
1265        return {
1266            coverage = coverage
1267        }
1268    else
1269        report("unsupported subtype %a in %a substitution",subtype,"single")
1270    end
1271end
1272
1273-- we see coverage format 0x300 in some old ms fonts
1274
1275local function sethandler(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs,what)
1276    local tableoffset = lookupoffset + offset
1277    setposition(f,tableoffset)
1278    local subtype = readushort(f)
1279    if subtype == 1 then
1280        local coverage    = readushort(f)
1281        local nofsequence = readushort(f)
1282        local sequences   = readcardinaltable(f,nofsequence,ushort)
1283        for i=1,nofsequence do
1284            setposition(f,tableoffset + sequences[i])
1285            sequences[i] = readcardinaltable(f,readushort(f),ushort)
1286        end
1287        local coverage = readcoverage(f,tableoffset + coverage)
1288        for index, newindex in next, coverage do
1289            newindex = newindex + 1
1290            if index > nofglyphs or newindex > nofglyphs then
1291                report("invalid index in %s format %i: %i -> %i (max %i)",what,subtype,index,newindex,nofglyphs)
1292                coverage[index] = nil
1293            else
1294                coverage[index] = sequences[newindex]
1295            end
1296        end
1297        return {
1298            coverage = coverage
1299        }
1300    else
1301        report("unsupported subtype %a in %a substitution",subtype,what)
1302    end
1303end
1304
1305function gsubhandlers.multiple(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs)
1306    return sethandler(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs,"multiple")
1307end
1308
1309function gsubhandlers.alternate(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs)
1310    return sethandler(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs,"alternate")
1311end
1312
1313function gsubhandlers.ligature(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs)
1314    local tableoffset = lookupoffset + offset
1315    setposition(f,tableoffset)
1316    local subtype = readushort(f)
1317    if subtype == 1 then
1318        local coverage  = readushort(f)
1319        local nofsets   = readushort(f)
1320        local ligatures = readcardinaltable(f,nofsets,ushort)
1321        for i=1,nofsets do
1322            local offset = lookupoffset + offset + ligatures[i]
1323            setposition(f,offset)
1324            local n = readushort(f)
1325            if n == 1 then
1326                ligatures[i] = { offset + readushort(f) }
1327            else
1328                local l = { }
1329                for i=1,n do
1330                    l[i] = offset + readushort(f)
1331                end
1332                ligatures[i] = l
1333            end
1334        end
1335        local coverage = readcoverage(f,tableoffset + coverage)
1336        for index, newindex in next, coverage do
1337            local hash = { }
1338            local ligatures = ligatures[newindex+1]
1339            for i=1,#ligatures do
1340                local offset = ligatures[i]
1341                setposition(f,offset)
1342                local lig = readushort(f)
1343                local cnt = readushort(f)
1344                local hsh = hash
1345                for i=2,cnt do
1346                    local c = readushort(f)
1347                    local h = hsh[c]
1348                    if not h then
1349                        h = { }
1350                        hsh[c] = h
1351                    end
1352                    hsh =  h
1353                end
1354                hsh.ligature = lig
1355            end
1356            coverage[index] = hash
1357        end
1358        return {
1359            coverage = coverage
1360        }
1361    else
1362        report("unsupported subtype %a in %a substitution",subtype,"ligature")
1363    end
1364end
1365
1366function gsubhandlers.context(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs)
1367    return unchainedcontext(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs,"substitution"), "context"
1368end
1369
1370function gsubhandlers.chainedcontext(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs)
1371    return chainedcontext(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs,"substitution"), "chainedcontext"
1372end
1373
1374function gsubhandlers.extension(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs)
1375    return extension(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs,gsubtypes,gsubhandlers,"substitution")
1376end
1377
1378function gsubhandlers.reversechainedcontextsingle(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs)
1379    local tableoffset = lookupoffset + offset
1380    setposition(f,tableoffset)
1381    local subtype = readushort(f)
1382    if subtype == 1 then -- NEEDS CHECKING
1383        local current      = readfirst(f)
1384        local before       = readarray(f)
1385        local after        = readarray(f)
1386        local replacements = readarray(f)
1387        current = readcoveragearray(f,tableoffset,current,true)
1388        before  = readcoveragearray(f,tableoffset,before,true)
1389        after   = readcoveragearray(f,tableoffset,after,true)
1390        return {
1391            format = "reversecoverage", -- reversesub
1392            rules  = {
1393                {
1394                    before       = before,
1395                    current      = current,
1396                    after        = after,
1397                    replacements = replacements,
1398                }
1399            }
1400        }, "reversechainedcontextsingle"
1401    else
1402        report("unsupported subtype %a in %a substitution",subtype,"reversechainedcontextsingle")
1403    end
1404end
1405
1406-- gpos handlers
1407
1408local function readpairsets(f,tableoffset,sets,format1,format2,mainoffset,getdelta)
1409    local done = { }
1410    for i=1,#sets do
1411        local offset = sets[i]
1412        local reused = done[offset]
1413        if not reused then
1414            offset = tableoffset + offset
1415            setposition(f,offset)
1416            local n = readushort(f)
1417            reused = { }
1418            for i=1,n do
1419                reused[i] = {
1420                    readushort(f), -- second glyph id
1421                    readposition(f,format1,offset,getdelta),
1422                    readposition(f,format2,offset,getdelta),
1423                }
1424            end
1425            done[offset] = reused
1426        end
1427        sets[i] = reused
1428    end
1429    return sets
1430end
1431
1432local function readpairclasssets(f,nofclasses1,nofclasses2,format1,format2,mainoffset,getdelta)
1433    local classlist1  = { }
1434    for i=1,nofclasses1 do
1435        local classlist2 = { }
1436        classlist1[i] = classlist2
1437        for j=1,nofclasses2 do
1438            local one = readposition(f,format1,mainoffset,getdelta)
1439            local two = readposition(f,format2,mainoffset,getdelta)
1440            if one or two then
1441                classlist2[j] = { one, two }
1442            else
1443                classlist2[j] = false
1444            end
1445        end
1446    end
1447    return classlist1
1448end
1449
1450-- no real gain in kerns as we pack
1451
1452function gposhandlers.single(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs)
1453    local tableoffset = lookupoffset + offset
1454    setposition(f,tableoffset)
1455    local subtype  = readushort(f)
1456    local getdelta = fontdata.temporary.getdelta
1457    if subtype == 1 then
1458        local coverage = readushort(f)
1459        local format   = readushort(f)
1460        local value    = readposition(f,format,tableoffset,getdelta)
1461        local coverage = readcoverage(f,tableoffset+coverage)
1462        for index, newindex in next, coverage do
1463            coverage[index] = value -- will be packed and shared anyway
1464        end
1465        return {
1466            format   = "single",
1467            coverage = coverage,
1468        }
1469    elseif subtype == 2 then
1470        local coverage  = readushort(f)
1471        local format    = readushort(f)
1472        local nofvalues = readushort(f)
1473        local values    = { }
1474        for i=1,nofvalues do
1475            values[i] = readposition(f,format,tableoffset,getdelta)
1476        end
1477        local coverage = readcoverage(f,tableoffset+coverage)
1478        for index, newindex in next, coverage do
1479            coverage[index] = values[newindex+1]
1480        end
1481        return {
1482            format   = "single",
1483            coverage = coverage,
1484        }
1485    else
1486        report("unsupported subtype %a in %a positioning",subtype,"single")
1487    end
1488end
1489
1490-- this needs checking! if no second pair then another advance over the list
1491
1492-- 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.
1493-- 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.
1494
1495-- local simple = {
1496--     [true]  = { [true] = { true,  true }, [false] = { true  } },
1497--     [false] = { [true] = { false, true }, [false] = { false } },
1498-- }
1499
1500-- function gposhandlers.pair(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs)
1501--     local tableoffset = lookupoffset + offset
1502--     setposition(f,tableoffset)
1503--     local subtype  = readushort(f)
1504--     local getdelta = fontdata.temporary.getdelta
1505--     if subtype == 1 then
1506--         local coverage = readushort(f)
1507--         local format1  = readushort(f)
1508--         local format2  = readushort(f)
1509--         local sets     = readarray(f)
1510--               sets     = readpairsets(f,tableoffset,sets,format1,format2,mainoffset,getdelta)
1511--               coverage = readcoverage(f,tableoffset + coverage)
1512--         local shared   = { } -- partial sparse, when set also needs to be handled in the packer
1513--         for index, newindex in next, coverage do
1514--             local set  = sets[newindex+1]
1515--             local hash = { }
1516--             for i=1,#set do
1517--                 local value = set[i]
1518--                 if value then
1519--                     local other  = value[1]
1520--                     if shared then
1521--                         local s = shared[value]
1522--                         if s == nil then
1523--                             local first  = value[2]
1524--                             local second = value[3]
1525--                             if first or second then
1526--                                 s = { first, second or nil } -- needs checking
1527--                             else
1528--                                 s = false
1529--                             end
1530--                             shared[value] = s
1531--                         end
1532--                         hash[other] = s or nil
1533--                     else
1534--                         local first  = value[2]
1535--                         local second = value[3]
1536--                         if first or second then
1537--                             hash[other] = { first, second or nil } -- needs checking
1538--                         else
1539--                             hash[other] = nil -- what if set, maybe warning
1540--                         end
1541--                     end
1542--                 end
1543--             end
1544--             coverage[index] = hash
1545--         end
1546--         return {
1547--             shared   = shared and true or nil,
1548--             format   = "pair",
1549--             coverage = coverage,
1550--         }
1551--     elseif subtype == 2 then
1552--         local coverage     = readushort(f)
1553--         local format1      = readushort(f)
1554--         local format2      = readushort(f)
1555--         local classdef1    = readushort(f)
1556--         local classdef2    = readushort(f)
1557--         local nofclasses1  = readushort(f) -- incl class 0
1558--         local nofclasses2  = readushort(f) -- incl class 0
1559--         local classlist    = readpairclasssets(f,nofclasses1,nofclasses2,format1,format2,tableoffset,getdelta)
1560--               coverage     = readcoverage(f,tableoffset+coverage)
1561--               classdef1    = readclassdef(f,tableoffset+classdef1,coverage)
1562--               classdef2    = readclassdef(f,tableoffset+classdef2,nofglyphs)
1563--         local usedcoverage = { }
1564--         local shared       = { } -- partial sparse, when set also needs to be handled in the packer
1565--         for g1, c1 in next, classdef1 do
1566--             if coverage[g1] then
1567--                 local l1 = classlist[c1]
1568--                 if l1 then
1569--                     local hash = { }
1570--                     for paired, class in next, classdef2 do
1571--                         local offsets = l1[class]
1572--                         if offsets then
1573--                             local first  = offsets[1]
1574--                             local second = offsets[2]
1575--                             if first or second then
1576--                                 if shared then
1577--                                     local s1 = shared[first]
1578--                                     if s1 == nil then
1579--                                         s1 = { }
1580--                                         shared[first] = s1
1581--                                     end
1582--                                     local s2 = s1[second]
1583--                                     if s2 == nil then
1584--                                         s2 = { first, second or nil }
1585--                                         s1[second] = s2
1586--                                     end
1587--                                     hash[paired] = s2
1588--                                 else
1589--                                     hash[paired] = { first, second or nil }
1590--                                 end
1591--                             else
1592--                                 -- upto the next lookup for this combination
1593--                             end
1594--                         end
1595--                     end
1596--                     usedcoverage[g1] = hash
1597--                 end
1598--             end
1599--         end
1600--         return {
1601--             shared   = shared and true or nil,
1602--             format   = "pair",
1603--             coverage = usedcoverage,
1604--         }
1605--     elseif subtype == 3 then
1606--         report("yet unsupported subtype %a in %a positioning",subtype,"pair")
1607--     else
1608--         report("unsupported subtype %a in %a positioning",subtype,"pair")
1609--     end
1610-- end
1611
1612function gposhandlers.pair(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs)
1613    local tableoffset = lookupoffset + offset
1614    setposition(f,tableoffset)
1615    local subtype  = readushort(f)
1616    local getdelta = fontdata.temporary.getdelta
1617    if subtype == 1 then
1618        local coverage = readushort(f)
1619        local format1  = readushort(f)
1620        local format2  = readushort(f)
1621        local sets     = readarray(f)
1622              sets     = readpairsets(f,tableoffset,sets,format1,format2,mainoffset,getdelta)
1623              coverage = readcoverage(f,tableoffset + coverage)
1624        local shared   = { } -- partial sparse, when set also needs to be handled in the packer
1625        for index, newindex in next, coverage do
1626            local set  = sets[newindex+1]
1627            local hash = { }
1628            for i=1,#set do
1629                local value = set[i]
1630                if value then
1631                    local other = value[1]
1632                    local share = shared[value]
1633                    if share == nil then
1634                        local first  = value[2]
1635                        local second = value[3]
1636                        if first or second then
1637                            share = { first, second or nil } -- needs checking
1638                        else
1639                            share = false
1640                        end
1641                        shared[value] = share
1642                    end
1643                    hash[other] = share or nil -- really overload ?
1644                end
1645            end
1646            coverage[index] = hash
1647        end
1648        return {
1649            shared   = shared and true or nil,
1650            format   = "pair",
1651            coverage = coverage,
1652        }
1653    elseif subtype == 2 then
1654        local coverage     = readushort(f)
1655        local format1      = readushort(f)
1656        local format2      = readushort(f)
1657        local classdef1    = readushort(f)
1658        local classdef2    = readushort(f)
1659        local nofclasses1  = readushort(f) -- incl class 0
1660        local nofclasses2  = readushort(f) -- incl class 0
1661        local classlist    = readpairclasssets(f,nofclasses1,nofclasses2,format1,format2,tableoffset,getdelta)
1662              coverage     = readcoverage(f,tableoffset+coverage)
1663              classdef1    = readclassdef(f,tableoffset+classdef1,coverage)
1664              classdef2    = readclassdef(f,tableoffset+classdef2,nofglyphs)
1665        local usedcoverage = { }
1666        local shared       = { } -- partial sparse, when set also needs to be handled in the packer
1667        for g1, c1 in next, classdef1 do
1668            if coverage[g1] then
1669                local l1 = classlist[c1]
1670                if l1 then
1671                    local hash = { }
1672                    for paired, class in next, classdef2 do
1673                        local offsets = l1[class]
1674                        if offsets then
1675                            local first  = offsets[1]
1676                            local second = offsets[2]
1677                            if first or second then
1678                                local s1 = shared[first]
1679                                if s1 == nil then
1680                                    s1 = { }
1681                                    shared[first] = s1
1682                                end
1683                                local s2 = s1[second]
1684                                if s2 == nil then
1685                                    s2 = { first, second or nil }
1686                                    s1[second] = s2
1687                                end
1688                                hash[paired] = s2
1689                            end
1690                        end
1691                    end
1692                    usedcoverage[g1] = hash
1693                end
1694            end
1695        end
1696        return {
1697            shared   = shared and true or nil,
1698            format   = "pair",
1699            coverage = usedcoverage,
1700        }
1701    elseif subtype == 3 then
1702        report("yet unsupported subtype %a in %a positioning",subtype,"pair")
1703    else
1704        report("unsupported subtype %a in %a positioning",subtype,"pair")
1705    end
1706end
1707
1708function gposhandlers.cursive(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs)
1709    local tableoffset = lookupoffset + offset
1710    setposition(f,tableoffset)
1711    local subtype  = readushort(f)
1712    local getdelta = fontdata.temporary.getdelta
1713    if subtype == 1 then
1714        local coverage   = tableoffset + readushort(f)
1715        local nofrecords = readushort(f)
1716        local records    = { }
1717        for i=1,nofrecords do
1718            local entry = readushort(f)
1719            local exit  = readushort(f)
1720            records[i] = {
1721             -- entry = entry ~= 0 and (tableoffset + entry) or false,
1722             -- exit  = exit  ~= 0 and (tableoffset + exit ) or nil,
1723                entry ~= 0 and (tableoffset + entry) or false,
1724                exit  ~= 0 and (tableoffset + exit ) or nil,
1725            }
1726        end
1727        -- slot 1 will become hash after loading and it must be unique because we
1728        -- pack the tables (packed we turn the cc-* into a zero)
1729        local cc = (fontdata.temporary.cursivecount or 0) + 1
1730        fontdata.temporary.cursivecount = cc
1731        cc = "cc-" .. cc
1732        coverage = readcoverage(f,coverage)
1733        for i=1,nofrecords do
1734            local r = records[i]
1735            records[i] = {
1736             -- 1,
1737                cc,
1738             -- readanchor(f,r.entry,getdelta) or false,
1739             -- readanchor(f,r.exit, getdelta) or nil,
1740                readanchor(f,r[1],getdelta) or false,
1741                readanchor(f,r[2],getdelta) or nil,
1742            }
1743        end
1744        for index, newindex in next, coverage do
1745            coverage[index] = records[newindex+1]
1746        end
1747        return {
1748            coverage = coverage,
1749        }
1750    else
1751        report("unsupported subtype %a in %a positioning",subtype,"cursive")
1752    end
1753end
1754
1755local function handlemark(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs,ligature)
1756    local tableoffset = lookupoffset + offset
1757    setposition(f,tableoffset)
1758    local subtype  = readushort(f)
1759    local getdelta = fontdata.temporary.getdelta
1760    if subtype == 1 then
1761        -- we are one based, not zero
1762        local markcoverage = tableoffset + readushort(f)
1763        local basecoverage = tableoffset + readushort(f)
1764        local nofclasses   = readushort(f)
1765        local markoffset   = tableoffset + readushort(f)
1766        local baseoffset   = tableoffset + readushort(f)
1767        --
1768        local markcoverage = readcoverage(f,markcoverage)
1769        local basecoverage = readcoverage(f,basecoverage,true) -- TO BE CHECKED: true
1770        --
1771        setposition(f,markoffset)
1772        local markclasses    = { }
1773        local nofmarkclasses = readushort(f)
1774        --
1775        local lastanchor  = fontdata.lastanchor or 0
1776        local usedanchors = { }
1777        --
1778        for i=1,nofmarkclasses do
1779            local class  = readushort(f) + 1
1780            local offset = readushort(f)
1781            if offset == 0 then
1782                markclasses[i] = false
1783            else
1784                markclasses[i] = { class, markoffset + offset }
1785            end
1786            usedanchors[class] = true
1787        end
1788        for i=1,nofmarkclasses do
1789            local mc = markclasses[i]
1790            if mc then
1791                mc[2] = readanchor(f,mc[2],getdelta)
1792            end
1793        end
1794        --
1795        setposition(f,baseoffset)
1796        local nofbaserecords = readushort(f)
1797        local baserecords    = { }
1798        --
1799        if ligature then
1800            -- 3 components
1801            -- 1 : class .. nofclasses -- NULL when empty
1802            -- 2 : class .. nofclasses -- NULL when empty
1803            -- 3 : class .. nofclasses -- NULL when empty
1804            for i=1,nofbaserecords do -- here i is the class
1805                local offset = readushort(f)
1806                if offset == 0 then
1807                    baserecords[i] = false
1808                else
1809                    baserecords[i] = baseoffset + offset
1810                end
1811            end
1812            for i=1,nofbaserecords do
1813                local recordoffset = baserecords[i]
1814                if recordoffset then
1815                    setposition(f,recordoffset)
1816                    local nofcomponents = readushort(f)
1817                    local components = { }
1818                    for i=1,nofcomponents do
1819                        local classes = { }
1820                        for i=1,nofclasses do
1821                            local offset = readushort(f)
1822                            if offset ~= 0 then
1823                                classes[i] = recordoffset + offset
1824                            else
1825                                classes[i] = false
1826                            end
1827                        end
1828                        components[i] = classes
1829                    end
1830                    baserecords[i] = components
1831                end
1832            end
1833            local baseclasses = { } -- setmetatableindex("table")
1834            for i=1,nofclasses do
1835                baseclasses[i] = { }
1836            end
1837            for i=1,nofbaserecords do
1838                local components = baserecords[i]
1839                if components then
1840                    local b = basecoverage[i]
1841                    for c=1,#components do
1842                        local classes = components[c]
1843                        if classes then
1844                            for i=1,nofclasses do
1845                                local anchor = readanchor(f,classes[i],getdelta)
1846                                local bclass = baseclasses[i]
1847                                local bentry = bclass[b]
1848                                if bentry then
1849                                    bentry[c] = anchor
1850                                else
1851                                    bclass[b]= { [c] = anchor }
1852                                end
1853                            end
1854                        end
1855                    end
1856                end
1857            end
1858            for index, newindex in next, markcoverage do
1859                markcoverage[index] = markclasses[newindex+1] or nil
1860            end
1861            return {
1862                format      = "ligature",
1863                baseclasses = baseclasses,
1864                coverage    = markcoverage,
1865            }
1866        else
1867            for i=1,nofbaserecords do
1868                local r = { }
1869                for j=1,nofclasses do
1870                    local offset = readushort(f)
1871                    if offset == 0 then
1872                        r[j] = false
1873                    else
1874                        r[j] = baseoffset + offset
1875                    end
1876                end
1877                baserecords[i] = r
1878            end
1879            local baseclasses = { } -- setmetatableindex("table")
1880            for i=1,nofclasses do
1881                baseclasses[i] = { }
1882            end
1883            for i=1,nofbaserecords do
1884                local r = baserecords[i]
1885                local b = basecoverage[i]
1886                for j=1,nofclasses do
1887                    baseclasses[j][b] = readanchor(f,r[j],getdelta)
1888                end
1889            end
1890            for index, newindex in next, markcoverage do
1891                markcoverage[index] = markclasses[newindex+1] or nil
1892            end
1893            -- we could actually already calculate the displacement if we want
1894            return {
1895                format      = "base",
1896                baseclasses = baseclasses,
1897                coverage    = markcoverage,
1898            }
1899        end
1900    else
1901        report("unsupported subtype %a in",subtype)
1902    end
1903
1904end
1905
1906function gposhandlers.marktobase(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs)
1907    return handlemark(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs)
1908end
1909
1910function gposhandlers.marktoligature(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs)
1911    return handlemark(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs,true)
1912end
1913
1914function gposhandlers.marktomark(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs)
1915    return handlemark(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs)
1916end
1917
1918function gposhandlers.context(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs)
1919    return unchainedcontext(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs,"positioning"), "context"
1920end
1921
1922function gposhandlers.chainedcontext(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs)
1923    return chainedcontext(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs,"positioning"), "chainedcontext"
1924end
1925
1926function gposhandlers.extension(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs)
1927    return extension(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs,gpostypes,gposhandlers,"positioning")
1928end
1929
1930-- main loader
1931
1932do
1933
1934    local plugins = { }
1935
1936    function plugins.size(f,fontdata,tableoffset,feature)
1937        if fontdata.designsize then
1938            -- yes, there are fonts with multiple size entries ... it probably relates
1939            -- to the other two fields (menu entries in some language)
1940        else
1941            local function check(offset)
1942                setposition(f,offset)
1943                local designsize = readushort(f)
1944                if designsize > 0 then -- we could also have a threshold
1945                    local fontstyleid = readushort(f)
1946                    local guimenuid   = readushort(f)
1947                    local minsize     = readushort(f)
1948                    local maxsize     = readushort(f)
1949                    if minsize == 0 and maxsize == 0 and fontstyleid == 0 and guimenuid == 0 then
1950                        minsize = designsize
1951                        maxsize = designsize
1952                    end
1953                    if designsize >= minsize and designsize <= maxsize then
1954                        return minsize, maxsize, designsize
1955                    end
1956                end
1957            end
1958            local minsize, maxsize, designsize = check(tableoffset+feature.offset+feature.parameters)
1959            if not designsize then
1960                -- some old adobe fonts have: tableoffset+feature.parameters and we could
1961                -- use some heuristic but why bother ... this extra check will be removed
1962                -- some day and/or when we run into an issue
1963                minsize, maxsize, designsize = check(tableoffset+feature.parameters)
1964                if designsize then
1965                    report("bad size feature in %a, falling back to wrong offset",fontdata.filename or "?")
1966                else
1967                    report("bad size feature in %a,",fontdata.filename or "?")
1968                end
1969            end
1970            if designsize then
1971                fontdata.minsize    = minsize
1972                fontdata.maxsize    = maxsize
1973                fontdata.designsize = designsize
1974            end
1975        end
1976    end
1977
1978 -- function plugins.rvrn(f,fontdata,tableoffset,feature)
1979 --     -- todo, at least a message
1980 -- end
1981
1982    -- feature order needs checking ... as we loop over a hash ... however, in the file
1983    -- they are sorted so order is not that relevant
1984
1985    local function reorderfeatures(fontdata,scripts,features)
1986        local scriptlangs  = { }
1987        local featurehash  = { }
1988        local featureorder = { }
1989        for script, languages in next, scripts do
1990            for language, record in next, languages do
1991                local hash = { }
1992                local list = record.featureindices
1993                for k=1,#list do
1994                    local index   = list[k]
1995                    local feature = features[index]
1996                    local lookups = feature.lookups
1997                    local tag     = feature.tag
1998                    if tag then
1999                        hash[tag] = true
2000                    end
2001                    if lookups then
2002                        for i=1,#lookups do
2003                            local lookup = lookups[i]
2004                            local o = featureorder[lookup]
2005                            if o then
2006                                local okay = true
2007                                for i=1,#o do
2008                                    if o[i] == tag then
2009                                        okay = false
2010                                        break
2011                                    end
2012                                end
2013                                if okay then
2014                                    o[#o+1] = tag
2015                                end
2016                            else
2017                                featureorder[lookup] = { tag }
2018                            end
2019                            local f = featurehash[lookup]
2020                            if f then
2021                                local h = f[tag]
2022                                if h then
2023                                    local s = h[script]
2024                                    if s then
2025                                        s[language] = true
2026                                    else
2027                                        h[script] = { [language] = true }
2028                                    end
2029                                else
2030                                    f[tag] = { [script] = { [language] = true } }
2031                                end
2032                            else
2033                                featurehash[lookup] = { [tag] = { [script] = { [language] = true } } }
2034                            end
2035                            --
2036                            local h = scriptlangs[tag]
2037                            if h then
2038                                local s = h[script]
2039                                if s then
2040                                    s[language] = true
2041                                else
2042                                    h[script] = { [language] = true }
2043                                end
2044                            else
2045                                scriptlangs[tag] = { [script] = { [language] = true } }
2046                            end
2047                        end
2048                    end
2049                end
2050            end
2051        end
2052        return scriptlangs, featurehash, featureorder
2053    end
2054
2055    local function readscriplan(f,fontdata,scriptoffset)
2056        setposition(f,scriptoffset)
2057        local nofscripts = readushort(f)
2058        local scripts    = { }
2059        for i=1,nofscripts do
2060            scripts[readtag(f)] = scriptoffset + readushort(f)
2061        end
2062        -- script list -> language system info
2063        local languagesystems = setmetatableindex("table")
2064        for script, offset in next, scripts do
2065            setposition(f,offset)
2066            local defaultoffset = readushort(f)
2067            local noflanguages  = readushort(f)
2068            local languages     = { }
2069            if defaultoffset > 0 then
2070                languages.dflt = languagesystems[offset + defaultoffset]
2071            end
2072            for i=1,noflanguages do
2073                local language      = readtag(f)
2074                local offset        = offset + readushort(f)
2075                languages[language] = languagesystems[offset]
2076            end
2077            scripts[script] = languages
2078        end
2079        -- script list -> language system info -> feature list
2080        for offset, usedfeatures in next, languagesystems do
2081            if offset > 0 then
2082                setposition(f,offset)
2083                local featureindices        = { }
2084                usedfeatures.featureindices = featureindices
2085                usedfeatures.lookuporder    = readushort(f) -- reserved, not used (yet)
2086                usedfeatures.requiredindex  = readushort(f) -- relates to required (can be 0xFFFF)
2087                local noffeatures           = readushort(f)
2088                for i=1,noffeatures do
2089                    featureindices[i] = readushort(f) + 1
2090                end
2091            end
2092        end
2093        return scripts
2094    end
2095
2096    local function readfeatures(f,fontdata,featureoffset)
2097        setposition(f,featureoffset)
2098        local features    = { }
2099        local noffeatures = readushort(f)
2100        for i=1,noffeatures do
2101            -- also shared?
2102            features[i] = {
2103                tag    = readtag(f),
2104                offset = readushort(f)
2105            }
2106        end
2107        --
2108        for i=1,noffeatures do
2109            local feature = features[i]
2110            local offset  = featureoffset+feature.offset
2111            setposition(f,offset)
2112            local parameters = readushort(f) -- feature.parameters
2113            local noflookups = readushort(f)
2114            if noflookups > 0 then
2115--                 local lookups   = { }
2116--                 feature.lookups = lookups
2117--                 for j=1,noflookups do
2118--                     lookups[j] = readushort(f) + 1
2119--                 end
2120                local lookups   = readcardinaltable(f,noflookups,ushort)
2121                feature.lookups = lookups
2122                for j=1,noflookups do
2123                    lookups[j] = lookups[j] + 1
2124                end
2125            end
2126            if parameters > 0 then
2127                feature.parameters = parameters
2128                local plugin = plugins[feature.tag]
2129                if plugin then
2130                    plugin(f,fontdata,featureoffset,feature)
2131                end
2132            end
2133        end
2134        return features
2135    end
2136
2137    local function readlookups(f,lookupoffset,lookuptypes,featurehash,featureorder,nofmarkclasses)
2138        setposition(f,lookupoffset)
2139        local noflookups = readushort(f)
2140        local lookups    = readcardinaltable(f,noflookups,ushort)
2141        for lookupid=1,noflookups do
2142            local offset = lookups[lookupid]
2143            setposition(f,lookupoffset+offset)
2144            local subtables    = { }
2145            local typebits     = readushort(f)
2146            local flagbits     = readushort(f)
2147            local lookuptype   = lookuptypes[typebits]
2148            local lookupflags  = lookupflags[flagbits]
2149            local nofsubtables = readushort(f)
2150            for j=1,nofsubtables do
2151                subtables[j] = offset + readushort(f) -- we can probably put lookupoffset here
2152            end
2153            -- which one wins?
2154            local markclass = band(flagbits,0x0010) ~= 0 -- usemarkfilteringset
2155            local markset   = rshift(flagbits,8)
2156            if markclass then
2157                markclass = readushort(f) -- + 1
2158            end
2159            if markset > 0 then
2160                markclass = nofmarkclasses + markset
2161            end
2162            lookups[lookupid] = {
2163                type      = lookuptype,
2164             -- chain     = chaindirections[lookuptype] or nil,
2165                flags     = lookupflags,
2166                name      = lookupid,
2167                subtables = subtables,
2168                markclass = markclass,
2169                features  = featurehash[lookupid], -- not if extension
2170                order     = featureorder[lookupid],
2171            }
2172        end
2173        return lookups
2174    end
2175
2176    local f_lookupname = formatters["%s_%s_%s"]
2177
2178    local function resolvelookups(f,lookupoffset,fontdata,lookups,lookuptypes,lookuphandlers,what,tableoffset)
2179
2180        local sequences      = fontdata.sequences  or { }
2181        local sublookuplist  = fontdata.sublookups or { }
2182        fontdata.sequences   = sequences
2183        fontdata.sublookups  = sublookuplist
2184        local nofsublookups  = #sublookuplist
2185        local nofsequences   = #sequences -- 0
2186        local lastsublookup  = nofsublookups
2187        local lastsequence   = nofsequences
2188        local lookupnames    = lookupnames[what]
2189        local sublookuphash  = { }
2190        local sublookupcheck = { }
2191        local glyphs         = fontdata.glyphs
2192        local nofglyphs      = fontdata.nofglyphs or #glyphs
2193        local noflookups     = #lookups
2194        local lookupprefix   = sub(what,2,2) -- g[s|p][ub|os]
2195        --
2196        local usedlookups    = false -- setmetatableindex("number")
2197        --
2198        local allsteps = { } -- new per 2022-09-25
2199
2200        for lookupid=1,noflookups do
2201            local lookup     = lookups[lookupid]
2202            local lookuptype = lookup.type
2203            local subtables  = lookup.subtables
2204            local features   = lookup.features
2205            local handler    = lookuphandlers[lookuptype]
2206            if handler then
2207                local nofsubtables = #subtables
2208                local order        = lookup.order
2209                local flags        = lookup.flags
2210                -- this is expected in the font handler (faster checking)
2211                if flags[1] then flags[1] = "mark" end
2212                if flags[2] then flags[2] = "ligature" end
2213                if flags[3] then flags[3] = "base" end
2214                --
2215                local markclass    = lookup.markclass
2216             -- local chain        = lookup.chain
2217                if nofsubtables > 0 then
2218                    local steps     = { }
2219                    local nofsteps  = 0
2220                    local oldtype   = nil
2221                    for s=1,nofsubtables do
2222                        local step, lt = handler(f,fontdata,lookupid,lookupoffset,subtables[s],glyphs,nofglyphs)
2223                        if lt then
2224                            lookuptype = lt
2225                            if oldtype and lt ~= oldtype then
2226                                report("messy %s lookup type %a and %a",what,lookuptype,oldtype)
2227                            end
2228                            oldtype = lookuptype
2229                        end
2230                        if not step then
2231                            report("unsupported %s lookup type %a",what,lookuptype)
2232                        else
2233                            nofsteps = nofsteps + 1
2234                            steps[nofsteps] = step
2235                            local rules = step.rules
2236                            if rules then
2237                                allsteps[#allsteps+1] = step -- new per 2022-09-25
2238                                for i=1,#rules do
2239                                    local rule         = rules[i]
2240                                    local before       = rule.before
2241                                    local current      = rule.current
2242                                    local after        = rule.after
2243                                    local replacements = rule.replacements
2244                                    if before then
2245                                        for i=1,#before do
2246                                            before[i] = tohash(before[i])
2247                                        end
2248                                        -- as with original ctx ff loader
2249                                        rule.before = reversed(before)
2250                                    end
2251                                    if current then
2252                                        if replacements then
2253                                            -- We have a reverse lookup and therefore only one current entry. We might need
2254                                            -- to reverse the order in the before and after lists so that needs checking.
2255                                            local first = current[1]
2256                                            local hash  = { }
2257                                            local repl  = { }
2258                                            for i=1,#first do
2259                                                local c = first[i]
2260                                                hash[c] = true
2261                                                repl[c] = replacements[i]
2262                                            end
2263                                            rule.current      = { hash }
2264                                            rule.replacements = repl
2265                                        else
2266                                            for i=1,#current do
2267                                                current[i] = tohash(current[i])
2268                                            end
2269                                        end
2270                                    else
2271                                        -- weird lookup
2272                                    end
2273                                    if after then
2274                                        for i=1,#after do
2275                                            after[i] = tohash(after[i])
2276                                        end
2277                                    end
2278                                    if usedlookups then
2279                                        local lookups = rule.lookups
2280                                        if lookups then
2281                                            for k, v in next, lookups do
2282                                                if v then
2283                                                    for k, v in next, v do
2284                                                        usedlookups[v] = usedlookups[v] + 1
2285                                                    end
2286                                                end
2287                                            end
2288                                        end
2289                                    end
2290                                end
2291                            end
2292                        end
2293                    end
2294                    if nofsteps ~= nofsubtables then
2295                        report("bogus subtables removed in %s lookup type %a",what,lookuptype)
2296                    end
2297                    lookuptype = lookupnames[lookuptype] or lookuptype
2298                    if features then
2299                        nofsequences = nofsequences + 1
2300                     -- report("registering %i as sequence step %i",lookupid,nofsequences)
2301                        local l = {
2302                            index     = nofsequences,
2303                            name      = f_lookupname(lookupprefix,"s",lookupid+lookupidoffset),
2304                            steps     = steps,
2305                            nofsteps  = nofsteps,
2306                            type      = lookuptype,
2307                            markclass = markclass or nil,
2308                            flags     = flags,
2309                         -- chain     = chain,
2310                            order     = order,
2311                            features  = features,
2312                        }
2313                        sequences[nofsequences] = l
2314                        lookup.done = l
2315                    else
2316                        nofsublookups = nofsublookups + 1
2317                     -- report("registering %i as sublookup %i",lookupid,nofsublookups)
2318                        local l = {
2319                            index     = nofsublookups,
2320                            name      = f_lookupname(lookupprefix,"l",lookupid+lookupidoffset),
2321                            steps     = steps,
2322                            nofsteps  = nofsteps,
2323                            type      = lookuptype,
2324                            markclass = markclass or nil,
2325                            flags     = flags,
2326                         -- chain     = chain,
2327                        }
2328                        sublookuplist[nofsublookups] = l
2329                        sublookuphash[lookupid] = nofsublookups
2330                        sublookupcheck[lookupid] = 0
2331                        lookup.done = l
2332                    end
2333                else
2334                    report("no subtables for lookup %a",lookupid)
2335                end
2336            else
2337                report("no handler for lookup %a with type %a",lookupid,lookuptype)
2338            end
2339        end
2340
2341        if usedlookups then
2342            report("used %s lookups: % t",what,sortedkeys(usedlookups))
2343        end
2344
2345        -- When we have a context, we have sublookups that resolve into lookups for which we need to
2346        -- know the type. We split the main lookuptable in two parts: sequences (the main lookups)
2347        -- and subtable lookups (simple specs with no features). We could keep them merged and might do
2348        -- that once we only use this loader. Then we can also move the simple specs into the sequence.
2349        -- After all, we pack afterwards.
2350
2351        local reported = { }
2352
2353        local function report_issue(i,what,step,kind)
2354--             if not reported[step] then
2355                report("rule %i in step %i of %s has %s lookups",i,step,what,kind)
2356--                 reported[name] = true
2357--             end
2358        end
2359
2360     -- for i=lastsequence+1,nofsequences do
2361     --     local sequence = sequences[i]
2362     --     local steps    = sequence.steps
2363     --     for i=1,#steps do
2364     --         local step  = steps[i]
2365
2366            for s=1,#allsteps do          -- new per 2022-09-25
2367                local step  = allsteps[s] -- new per 2022-09-25
2368                local rules = step.rules
2369                if rules then
2370                    for i=1,#rules do
2371                        local rule     = rules[i]
2372                        local rlookups = rule.lookups
2373                        if not rlookups then
2374                            report_issue(i,what,s,"no")
2375                        elseif not next(rlookups) then
2376                            -- can be ok as it aborts a chain sequence
2377                         -- report_issue(i,what,s,"empty")
2378                            rule.lookups = nil
2379                        else
2380                            -- we can have holes in rlookups flagged false and we can have multiple lookups
2381                            -- applied (first time seen in seguemj)
2382                            local length = #rlookups
2383                            for index=1,length do
2384                                local lookuplist = rlookups[index]
2385                                if lookuplist then
2386                                    local length   = #lookuplist
2387                                    local found    = { }
2388                                    local noffound = 0
2389                                    for index=1,length do
2390                                        local lookupid = lookuplist[index]
2391                                        if lookupid then
2392                                            local h = sublookuphash[lookupid]
2393                                            if not h then
2394                                                -- here we have a lookup that is used independent as well
2395                                                -- as in another one
2396                                                local lookup = lookups[lookupid]
2397                                                if lookup then
2398                                                    local d = lookup.done
2399                                                    if d then
2400                                                        nofsublookups = nofsublookups + 1
2401                                                     -- report("registering %i as sublookup %i",lookupid,nofsublookups)
2402                                                        local l = {
2403                                                            index     = nofsublookups, -- handy for tracing
2404                                                            name      = f_lookupname(lookupprefix,"d",lookupid+lookupidoffset),
2405                                                            derived   = true,          -- handy for tracing
2406                                                            steps     = d.steps,
2407                                                            nofsteps  = d.nofsteps,
2408                                                            type      = d.lookuptype or "gsub_single", -- todo: check type
2409                                                            markclass = d.markclass or nil,
2410                                                            flags     = d.flags,
2411                                                         -- chain     = d.chain,
2412                                                        }
2413                                                        sublookuplist[nofsublookups] = copy(l) -- we repack later
2414                                                        sublookuphash[lookupid] = nofsublookups
2415                                                        sublookupcheck[lookupid] = 1
2416                                                        h = nofsublookups
2417                                                    else
2418                                                        report_issue(i,what,s,"missing")
2419                                                        rule.lookups = nil
2420                                                        break
2421                                                    end
2422                                                else
2423                                                    report_issue(i,what,s,"bad")
2424                                                    rule.lookups = nil
2425                                                    break
2426                                                end
2427                                            else
2428                                                sublookupcheck[lookupid] = sublookupcheck[lookupid] + 1
2429                                            end
2430                                            if h then
2431                                                noffound = noffound + 1
2432                                                found[noffound] = h
2433                                            end
2434                                        end
2435                                    end
2436                                    rlookups[index] = noffound > 0 and found or false
2437                                else
2438                                    rlookups[index] = false
2439                                end
2440                            end
2441                        end
2442                    end
2443                end
2444            end
2445     -- end -- new per 2022-09-25
2446
2447        for i, n in sortedhash(sublookupcheck) do
2448            local l = lookups[i]
2449            local t = l.type
2450            if n == 0 and t ~= "extension" then
2451                local d = l.done
2452                report("%s lookup %s of type %a is not used",what,d and d.name or l.name,t)
2453            end
2454        end
2455
2456    end
2457
2458    local function loadvariations(f,fontdata,variationsoffset,lookuptypes,featurehash,featureorder)
2459        setposition(f,variationsoffset)
2460        local version    = readulong(f) -- two times readushort
2461        local nofrecords = readulong(f)
2462        local records    = { }
2463        for i=1,nofrecords do
2464            records[i] = {
2465                conditions    = readulong(f),
2466                substitutions = readulong(f),
2467            }
2468        end
2469        for i=1,nofrecords do
2470            local record = records[i]
2471            local offset = record.conditions
2472            if offset == 0 then
2473                record.condition = nil
2474                record.matchtype = "always"
2475            else
2476                local offset = variationsoffset+offset
2477                setposition(f,offset)
2478                local nofconditions = readushort(f)
2479                local conditions    = { }
2480                for i=1,nofconditions do
2481                    conditions[i] = offset + readulong(f)
2482                end
2483                record.conditions = conditions
2484                record.matchtype  = "condition"
2485            end
2486        end
2487        for i=1,nofrecords do
2488            local record = records[i]
2489            if record.matchtype == "condition" then
2490                local conditions = record.conditions
2491                for i=1,#conditions do
2492                    setposition(f,conditions[i])
2493                    conditions[i] = {
2494                        format   = readushort(f),
2495                        axis     = readushort(f),
2496                        minvalue = read2dot14(f),
2497                        maxvalue = read2dot14(f),
2498                    }
2499                end
2500            end
2501        end
2502
2503        for i=1,nofrecords do
2504            local record = records[i]
2505            local offset = record.substitutions
2506            if offset == 0 then
2507                record.substitutions = { }
2508            else
2509                setposition(f,variationsoffset + offset)
2510                local version          = readulong(f)
2511                local nofsubstitutions = readushort(f)
2512                local substitutions    = { }
2513                for i=1,nofsubstitutions do
2514                    substitutions[readushort(f)] = readulong(f)
2515                end
2516                for index, alternates in sortedhash(substitutions) do
2517                    if index == 0 then
2518                        record.substitutions = false
2519                    else
2520                        local tableoffset = variationsoffset + offset + alternates
2521                        setposition(f,tableoffset)
2522                        local parameters = readulong(f) -- feature parameters
2523                        local noflookups = readushort(f)
2524                        local lookups    = readcardinaltable(f,noflookups,ushort) -- not sure what to do with these
2525                        -- todo : resolve to proper lookups
2526                        record.substitutions = lookups
2527                    end
2528                end
2529            end
2530        end
2531        setvariabledata(fontdata,"features",records)
2532    end
2533
2534    local function readscripts(f,fontdata,what,lookuptypes,lookuphandlers,lookupstoo)
2535        local tableoffset = gotodatatable(f,fontdata,what,true)
2536        if tableoffset then
2537            local version          = readulong(f)
2538            local scriptoffset     = tableoffset + readushort(f)
2539            local featureoffset    = tableoffset + readushort(f)
2540            local lookupoffset     = tableoffset + readushort(f)
2541            -- MFK : Rubik-Regular.ttf : we need to delay adding the offset
2542         -- local variationsoffset = version > 0x00010000 and (tableoffset + readulong(f)) or 0
2543            local variationsoffset = version > 0x00010000 and readulong(f) or 0
2544            if not scriptoffset then
2545                return
2546            end
2547            local scripts  = readscriplan(f,fontdata,scriptoffset)
2548            local features = readfeatures(f,fontdata,featureoffset)
2549            --
2550            local scriptlangs, featurehash, featureorder = reorderfeatures(fontdata,scripts,features)
2551            --
2552            if fontdata.features then
2553                fontdata.features[what] = scriptlangs
2554            else
2555                fontdata.features = { [what] = scriptlangs }
2556            end
2557            --
2558            if not lookupstoo then
2559                return
2560            end
2561            --
2562            local nofmarkclasses = (fontdata.markclasses and #fontdata.markclasses or 0) - (fontdata.marksets and #fontdata.marksets or 0)
2563            local lookups = readlookups(f,lookupoffset,lookuptypes,featurehash,featureorder,nofmarkclasses)
2564            --
2565            if lookups then
2566                resolvelookups(f,lookupoffset,fontdata,lookups,lookuptypes,lookuphandlers,what,tableoffset)
2567            end
2568            --
2569            if variationsoffset > 0 then
2570             -- loadvariations(f,fontdata,variationsoffset,lookuptypes,featurehash,featureorder)
2571                loadvariations(f,fontdata,tableoffset + variationsoffset,lookuptypes,featurehash,featureorder)
2572            end
2573        end
2574    end
2575
2576    local function checkkerns(f,fontdata,specification)
2577        local datatable = fontdata.tables.kern
2578        if not datatable then
2579            return -- no kerns
2580        end
2581        local features     = fontdata.features
2582        local gposfeatures = features and features.gpos
2583        local name
2584        if not gposfeatures or not gposfeatures.kern then
2585            name = "kern"
2586        elseif specification.globalkerns then
2587            name = "globalkern"
2588        else
2589            report("ignoring global kern table, using gpos kern feature")
2590            return
2591        end
2592        setposition(f,datatable.offset)
2593        local version   = readushort(f)
2594        local noftables = readushort(f)
2595        if noftables > 1 then
2596            report("adding global kern table as gpos feature %a",name)
2597            local kerns = setmetatableindex("table")
2598            for i=1,noftables do
2599                local version  = readushort(f)
2600                local length   = readushort(f)
2601                local coverage = readushort(f)
2602                -- bit 8-15 of coverage: format 0 or 2
2603                local format   = rshift(coverage,8) -- is this ok
2604                if format == 0 then
2605                    local nofpairs      = readushort(f)
2606                    local searchrange   = readushort(f)
2607                    local entryselector = readushort(f)
2608                    local rangeshift    = readushort(f)
2609                    for i=1,nofpairs do
2610                        kerns[readushort(f)][readushort(f)] = readfword(f)
2611                    end
2612                elseif format == 2 then
2613                    -- apple specific so let's ignore it
2614                else
2615                    -- not supported by ms
2616                end
2617            end
2618            local feature = { dflt = { dflt = true } }
2619            if not features then
2620                fontdata.features = { gpos = { [name] = feature } }
2621            elseif not gposfeatures then
2622                fontdata.features.gpos = { [name] = feature }
2623            else
2624                gposfeatures[name] = feature
2625            end
2626            local sequences = fontdata.sequences
2627            if not sequences then
2628                sequences = { }
2629                fontdata.sequences = sequences
2630            end
2631            local nofsequences = #sequences + 1
2632            sequences[nofsequences] = {
2633                index     = nofsequences,
2634                name      = name,
2635                steps     = {
2636                    {
2637                        coverage = kerns,
2638                        format   = "kern",
2639                    },
2640                },
2641                nofsteps  = 1,
2642                type      = "gpos_pair",
2643                flags     = { false, false, false, false },
2644                order     = { name },
2645                features  = { [name] = feature },
2646            }
2647        else
2648            report("ignoring empty kern table of feature %a",name)
2649        end
2650    end
2651
2652    function readers.gsub(f,fontdata,specification)
2653        if specification.details then
2654            readscripts(f,fontdata,"gsub",gsubtypes,gsubhandlers,specification.lookups)
2655        end
2656    end
2657
2658    function readers.gpos(f,fontdata,specification)
2659        if specification.details then
2660            readscripts(f,fontdata,"gpos",gpostypes,gposhandlers,specification.lookups)
2661            if specification.lookups then
2662                checkkerns(f,fontdata,specification)
2663            end
2664        end
2665    end
2666
2667end
2668
2669function readers.gdef(f,fontdata,specification)
2670    if not specification.glyphs then
2671        return
2672    end
2673    local datatable = fontdata.tables.gdef
2674    if datatable then
2675        local tableoffset = datatable.offset
2676        setposition(f,tableoffset)
2677        local version          = readulong(f)
2678        local classoffset      = readushort(f)
2679        local attachmentoffset = readushort(f) -- used for bitmaps
2680        local ligaturecarets   = readushort(f) -- used in editors (maybe nice for tracing)
2681        local markclassoffset  = readushort(f)
2682        local marksetsoffset   = version >= 0x00010002 and readushort(f) or 0
2683        local varsetsoffset    = version >= 0x00010003 and readulong(f) or 0
2684        local glyphs           = fontdata.glyphs
2685        local marks            = { }
2686        local markclasses      = setmetatableindex("table")
2687        local marksets         = setmetatableindex("table")
2688        fontdata.marks         = marks
2689        fontdata.markclasses   = markclasses
2690        fontdata.marksets      = marksets
2691        -- class definitions
2692        if classoffset ~= 0 then
2693            setposition(f,tableoffset + classoffset)
2694            local classformat = readushort(f)
2695            if classformat == 1 then
2696                local firstindex = readushort(f)
2697                local lastindex  = firstindex + readushort(f) - 1
2698                for index=firstindex,lastindex do
2699                    local class = classes[readushort(f)]
2700                    if class == "mark" then
2701                        marks[index] = true
2702                    end
2703                    glyphs[index].class = class
2704                end
2705            elseif classformat == 2 then
2706                local nofranges = readushort(f)
2707                for i=1,nofranges do
2708                    local firstindex = readushort(f)
2709                    local lastindex  = readushort(f)
2710                    local class      = classes[readushort(f)]
2711                    if class then
2712                        for index=firstindex,lastindex do
2713                            glyphs[index].class = class
2714                            if class == "mark" then
2715                                marks[index] = true
2716                            end
2717                        end
2718                    end
2719                end
2720            end
2721        end
2722        -- mark classes
2723        if markclassoffset ~= 0 then
2724            setposition(f,tableoffset + markclassoffset)
2725            local classformat = readushort(f)
2726            if classformat == 1 then
2727                local firstindex = readushort(f)
2728                local lastindex  = firstindex + readushort(f) - 1
2729                for index=firstindex,lastindex do
2730                    markclasses[readushort(f)][index] = true
2731                end
2732            elseif classformat == 2 then
2733                local nofranges = readushort(f)
2734                for i=1,nofranges do
2735                    local firstindex = readushort(f)
2736                    local lastindex  = readushort(f)
2737                    local class      = markclasses[readushort(f)]
2738                    for index=firstindex,lastindex do
2739                        class[index] = true
2740                    end
2741                end
2742            end
2743        end
2744        -- mark sets
2745        if marksetsoffset ~= 0 then
2746            local nofmarkclasses = fontdata.markclasses and #fontdata.markclasses or 0
2747            marksetsoffset = tableoffset + marksetsoffset
2748            setposition(f,marksetsoffset)
2749            local format = readushort(f)
2750            if format == 1 then
2751                local nofsets = readushort(f)
2752                local sets    = readcardinaltable(f,nofsets,ulong)
2753                for i=1,nofsets do
2754                    local offset = sets[i]
2755                    if offset ~= 0 then
2756                        markclasses[nofmarkclasses + i] = readcoverage(f,marksetsoffset+offset)
2757                        marksets[i] = { }
2758                    end
2759                end
2760            end
2761        end
2762
2763        local factors = specification.factors
2764
2765        if (specification.variable or factors) and varsetsoffset ~= 0 then
2766
2767            local regions, deltas = readvariationdata(f,tableoffset+varsetsoffset,factors)
2768
2769         -- setvariabledata(fontdata,"gregions",regions)
2770
2771            if factors then
2772                fontdata.temporary.getdelta = function(outer,inner)
2773                    local delta = deltas[outer+1]
2774                    if delta then
2775                        local d = delta.deltas[inner+1]
2776                        if d then
2777                            local scales = delta.scales
2778                            local dd = 0
2779                            for i=1,#scales do
2780                                local di = d[i]
2781                                if di then
2782                                    dd = dd + scales[i] * di
2783                                else
2784                                    break
2785                                end
2786                            end
2787                            return round(dd)
2788                        end
2789                    end
2790                    return 0
2791                end
2792            end
2793
2794        end
2795    end
2796end
2797
2798-- We keep this code here instead of font-otm.lua because we need coverage
2799-- helpers. Okay, these helpers could go to the main reader file some day.
2800
2801local function readmathvalue(f)
2802    local v = readshort(f)
2803    skipshort(f,1) -- offset to device table
2804    return v
2805end
2806
2807local function readmathconstants(f,fontdata,offset)
2808    setposition(f,offset)
2809    fontdata.mathconstants = {
2810        ScriptPercentScaleDown                   = readshort(f),
2811        ScriptScriptPercentScaleDown             = readshort(f),
2812        DelimitedSubFormulaMinHeight             = readushort(f),
2813        DisplayOperatorMinHeight                 = readushort(f),
2814        MathLeading                              = readmathvalue(f),
2815        AxisHeight                               = readmathvalue(f),
2816        AccentBaseHeight                         = readmathvalue(f),
2817        FlattenedAccentBaseHeight                = readmathvalue(f),
2818        SubscriptShiftDown                       = readmathvalue(f),
2819        SubscriptTopMax                          = readmathvalue(f),
2820        SubscriptBaselineDropMin                 = readmathvalue(f),
2821        SuperscriptShiftUp                       = readmathvalue(f),
2822        SuperscriptShiftUpCramped                = readmathvalue(f),
2823        SuperscriptBottomMin                     = readmathvalue(f),
2824        SuperscriptBaselineDropMax               = readmathvalue(f),
2825        SubSuperscriptGapMin                     = readmathvalue(f),
2826        SuperscriptBottomMaxWithSubscript        = readmathvalue(f),
2827        SpaceAfterScript                         = readmathvalue(f),
2828        UpperLimitGapMin                         = readmathvalue(f),
2829        UpperLimitBaselineRiseMin                = readmathvalue(f),
2830        LowerLimitGapMin                         = readmathvalue(f),
2831        LowerLimitBaselineDropMin                = readmathvalue(f),
2832        StackTopShiftUp                          = readmathvalue(f),
2833        StackTopDisplayStyleShiftUp              = readmathvalue(f),
2834        StackBottomShiftDown                     = readmathvalue(f),
2835        StackBottomDisplayStyleShiftDown         = readmathvalue(f),
2836        StackGapMin                              = readmathvalue(f),
2837        StackDisplayStyleGapMin                  = readmathvalue(f),
2838        StretchStackTopShiftUp                   = readmathvalue(f),
2839        StretchStackBottomShiftDown              = readmathvalue(f),
2840        StretchStackGapAboveMin                  = readmathvalue(f),
2841        StretchStackGapBelowMin                  = readmathvalue(f),
2842        FractionNumeratorShiftUp                 = readmathvalue(f),
2843        FractionNumeratorDisplayStyleShiftUp     = readmathvalue(f),
2844        FractionDenominatorShiftDown             = readmathvalue(f),
2845        FractionDenominatorDisplayStyleShiftDown = readmathvalue(f),
2846        FractionNumeratorGapMin                  = readmathvalue(f),
2847        FractionNumeratorDisplayStyleGapMin      = readmathvalue(f),
2848        FractionRuleThickness                    = readmathvalue(f),
2849        FractionDenominatorGapMin                = readmathvalue(f),
2850        FractionDenominatorDisplayStyleGapMin    = readmathvalue(f),
2851        SkewedFractionHorizontalGap              = readmathvalue(f),
2852        SkewedFractionVerticalGap                = readmathvalue(f),
2853        OverbarVerticalGap                       = readmathvalue(f),
2854        OverbarRuleThickness                     = readmathvalue(f),
2855        OverbarExtraAscender                     = readmathvalue(f),
2856        UnderbarVerticalGap                      = readmathvalue(f),
2857        UnderbarRuleThickness                    = readmathvalue(f),
2858        UnderbarExtraDescender                   = readmathvalue(f),
2859        RadicalVerticalGap                       = readmathvalue(f),
2860        RadicalDisplayStyleVerticalGap           = readmathvalue(f),
2861        RadicalRuleThickness                     = readmathvalue(f),
2862        RadicalExtraAscender                     = readmathvalue(f),
2863        RadicalKernBeforeDegree                  = readmathvalue(f),
2864        RadicalKernAfterDegree                   = readmathvalue(f),
2865        RadicalDegreeBottomRaisePercent          = readshort(f),
2866    }
2867end
2868
2869local function readmathglyphinfo(f,fontdata,offset)
2870    setposition(f,offset)
2871    local italics    = readushort(f)
2872    local accents    = readushort(f)
2873    local extensions = readushort(f)
2874    local kerns      = readushort(f)
2875    local glyphs     = fontdata.glyphs
2876    if italics ~= 0 then
2877        setposition(f,offset+italics)
2878        local coverage  = readushort(f)
2879        local nofglyphs = readushort(f)
2880        coverage = readcoverage(f,offset+italics+coverage,true)
2881        setposition(f,offset+italics+4)
2882        for i=1,nofglyphs do
2883            local italic = readmathvalue(f)
2884            if italic ~= 0 then
2885                local glyph = glyphs[coverage[i]]
2886                local math  = glyph.math
2887                if not math then
2888                    glyph.math = { italic = italic }
2889                else
2890                    math.italic = italic
2891                end
2892            end
2893        end
2894        fontdata.hasitalics = true
2895    end
2896    if accents ~= 0 then
2897        setposition(f,offset+accents)
2898        local coverage  = readushort(f)
2899        local nofglyphs = readushort(f)
2900        coverage = readcoverage(f,offset+accents+coverage,true)
2901        setposition(f,offset+accents+4)
2902        for i=1,nofglyphs do
2903            local accent = readmathvalue(f)
2904            if accent ~= 0 then
2905                local glyph = glyphs[coverage[i]]
2906                local math  = glyph.math
2907                if not math then
2908                    glyph.math = { accent = accent }
2909                else
2910                    math.accent = accent -- will become math.topanchor
2911                end
2912            end
2913        end
2914    end
2915    if extensions ~= 0 then
2916        setposition(f,offset+extensions)
2917    end
2918    if kerns ~= 0 then
2919        local kernoffset = offset + kerns
2920        setposition(f,kernoffset)
2921        local coverage  = readushort(f)
2922        local nofglyphs = readushort(f)
2923        if nofglyphs > 0 then
2924            local function get(offset)
2925                setposition(f,kernoffset+offset)
2926                local n = readushort(f)
2927                if n == 0 then
2928                    local k = readmathvalue(f)
2929                    if k == 0 then
2930                        -- no need for it (happens sometimes)
2931                    else
2932                        return { { kern = k } }
2933                    end
2934                else
2935                    local l = { }
2936                    for i=1,n do
2937                        l[i] = { height = readmathvalue(f) }
2938                    end
2939                    for i=1,n do
2940                        l[i].kern = readmathvalue(f)
2941                    end
2942                    l[n+1] = { kern = readmathvalue(f) }
2943                    return l
2944                end
2945            end
2946            local kernsets = { }
2947            for i=1,nofglyphs do
2948                local topright    = readushort(f)
2949                local topleft     = readushort(f)
2950                local bottomright = readushort(f)
2951                local bottomleft  = readushort(f)
2952                kernsets[i] = {
2953                    topright    = topright    ~= 0 and topright    or nil,
2954                    topleft     = topleft     ~= 0 and topleft     or nil,
2955                    bottomright = bottomright ~= 0 and bottomright or nil,
2956                    bottomleft  = bottomleft  ~= 0 and bottomleft  or nil,
2957                }
2958            end
2959            coverage = readcoverage(f,kernoffset+coverage,true)
2960            for i=1,nofglyphs do
2961                local kernset = kernsets[i]
2962                if next(kernset) then
2963                    local k = kernset.topright    if k then kernset.topright    = get(k) end
2964                    local k = kernset.topleft     if k then kernset.topleft     = get(k) end
2965                    local k = kernset.bottomright if k then kernset.bottomright = get(k) end
2966                    local k = kernset.bottomleft  if k then kernset.bottomleft  = get(k) end
2967                    if next(kernset) then
2968                        local glyph = glyphs[coverage[i]]
2969                        local math  = glyph.math
2970                        if math then
2971                            math.kerns = kernset
2972                        else
2973                            glyph.math = { kerns = kernset }
2974                        end
2975                    end
2976                end
2977            end
2978        end
2979    end
2980end
2981
2982local function readmathvariants(f,fontdata,offset)
2983    setposition(f,offset)
2984    local glyphs        = fontdata.glyphs
2985    local minoverlap    = readushort(f)
2986    local vcoverage     = readushort(f)
2987    local hcoverage     = readushort(f)
2988    local vnofglyphs    = readushort(f)
2989    local hnofglyphs    = readushort(f)
2990    local vconstruction = readcardinaltable(f,vnofglyphs,ushort)
2991    local hconstruction = readcardinaltable(f,hnofglyphs,ushort)
2992
2993    fontdata.mathconstants.MinConnectorOverlap = minoverlap
2994
2995    -- variants[i] = {
2996    --     glyph   = readushort(f),
2997    --     advance = readushort(f),
2998    -- }
2999
3000    local function get(offset,coverage,nofglyphs,construction,kvariants,kparts,kitalic,korientation,orientation)
3001        if coverage ~= 0 and nofglyphs > 0 then
3002            local coverage = readcoverage(f,offset+coverage,true)
3003            for i=1,nofglyphs do
3004                local c = construction[i]
3005                if c ~= 0 then
3006                    local index = coverage[i]
3007                    local glyph = glyphs[index]
3008                    local math  = glyph.math
3009                    setposition(f,offset+c)
3010                    local assembly    = readushort(f)
3011                    local nofvariants = readushort(f)
3012                    if nofvariants > 0 then
3013                        local variants, v = nil, 0
3014                        for i=1,nofvariants do
3015                            local variant = readushort(f)
3016                            if variant == index then
3017                                -- ignore
3018                            elseif variants then
3019                                v = v + 1
3020                                variants[v] = variant
3021                            else
3022                                v = 1
3023                                variants = { variant }
3024                            end
3025                            skipshort(f)
3026                        end
3027                        if not variants then
3028                            -- only self
3029                        elseif not math then
3030                            math = { [kvariants] = variants }
3031                            glyph.math = math
3032                        else
3033                            math[kvariants] = variants
3034                        end
3035                    end
3036                    if assembly ~= 0 then
3037                        setposition(f,offset + c + assembly)
3038                        local italic   = readmathvalue(f)
3039                        local nofparts = readushort(f)
3040                        local parts    = { }
3041                        for i=1,nofparts do
3042                            local p = {
3043                                glyph   = readushort(f),
3044                                start   = readushort(f),
3045                                ["end"] = readushort(f),
3046                                advance = readushort(f),
3047                            }
3048                            local flags = readushort(f)
3049                            if band(flags,0x0001) ~= 0 then
3050                                p.extender = 1 -- true
3051                            end
3052                            parts[i] = p
3053                        end
3054                        if not math then
3055                            math = {
3056                                [kparts] = parts
3057                            }
3058                            glyph.math = math
3059                        else
3060                            math[kparts] = parts
3061                        end
3062                        if italic and italic ~= 0 then
3063                            math[kitalic] = italic
3064                        end
3065                        if orientation then
3066                            math[korientation] = orientation
3067                        end
3068                    end
3069                end
3070            end
3071        end
3072    end
3073
3074 -- if LUATEXENGINE == "luametatex" then
3075    if CONTEXTLMTXMODE and CONTEXTLMTXMODE > 0 then
3076        get(offset,hcoverage,hnofglyphs,hconstruction,"variants","parts","partsitalic","partsorientation","horizontal")
3077        get(offset,vcoverage,vnofglyphs,vconstruction,"variants","parts","partsitalic","partsorientation","vertical")
3078    else
3079        get(offset,vcoverage,vnofglyphs,vconstruction,"vvariants","vparts","vitalic")
3080        get(offset,hcoverage,hnofglyphs,hconstruction,"hvariants","hparts","hitalic")
3081    end
3082end
3083
3084function readers.math(f,fontdata,specification)
3085    local tableoffset = gotodatatable(f,fontdata,"math",specification.glyphs)
3086    if tableoffset then
3087        local version = readulong(f)
3088     -- if version ~= 0x00010000 then
3089     --     report("table version %a of %a is not supported (yet), maybe font %s is bad",version,"math",fontdata.filename)
3090     --     return
3091     -- end
3092        local constants = readushort(f)
3093        local glyphinfo = readushort(f)
3094        local variants  = readushort(f)
3095        if constants == 0 then
3096            report("the math table of %a has no constants",fontdata.filename)
3097        else
3098            readmathconstants(f,fontdata,tableoffset+constants)
3099        end
3100        if glyphinfo ~= 0 then
3101            readmathglyphinfo(f,fontdata,tableoffset+glyphinfo)
3102        end
3103        if variants ~= 0 then
3104            readmathvariants(f,fontdata,tableoffset+variants)
3105        end
3106    end
3107end
3108
3109function readers.colr(f,fontdata,specification)
3110    local tableoffset = gotodatatable(f,fontdata,"colr",specification.glyphs)
3111    if tableoffset then
3112        local version = readushort(f)
3113        if version == 0 or version == 1 then
3114            report("table version %a of %a is not supported (yet), maybe font %s is bad",version,"colr",fontdata.filename)
3115            return
3116        else
3117            -- both versions have this in common
3118        end
3119        if not fontdata.tables.cpal then
3120            report("color table %a in font %a has no mandate %a table","colr",fontdata.filename,"cpal")
3121            fontdata.colorpalettes = { }
3122        end
3123        local glyphs       = fontdata.glyphs
3124        local nofglyphs    = readushort(f)
3125        local baseoffset   = readulong(f)
3126        local layeroffset  = readulong(f)
3127        local noflayers    = readushort(f)
3128        local layerrecords = { }
3129        local maxclass     = 0
3130        -- The special value 0xFFFF is foreground (but we index from 1). It
3131        -- more looks like indices into a palette so 'class' is a better name
3132        -- than 'palette'.
3133        setposition(f,tableoffset + layeroffset)
3134        for i=1,noflayers do
3135            local slot  = readushort(f)
3136            local class = readushort(f)
3137            if class < 0xFFFF then
3138                class = class + 1
3139                if class > maxclass then
3140                    maxclass = class
3141                end
3142            end
3143            layerrecords[i] = {
3144                slot  = slot,
3145                class = class,
3146            }
3147        end
3148        fontdata.maxcolorclass = maxclass
3149        setposition(f,tableoffset + baseoffset)
3150        for i=0,nofglyphs-1 do
3151            local glyphindex = readushort(f)
3152            local firstlayer = readushort(f)
3153            local noflayers  = readushort(f)
3154            local t = { }
3155            for i=1,noflayers do
3156                t[i] = layerrecords[firstlayer+i]
3157            end
3158            glyphs[glyphindex].colors = t
3159        end
3160    end
3161    fontdata.hascolor = true
3162end
3163
3164function readers.cpal(f,fontdata,specification)
3165    local tableoffset = gotodatatable(f,fontdata,"cpal",specification.glyphs)
3166    if tableoffset then
3167        local version = readushort(f)
3168     -- if version > 1 then
3169     --     report("table version %a of %a is not supported (yet), maybe font %s is bad",version,"cpal",fontdata.filename)
3170     --     return
3171     -- end
3172        local nofpaletteentries  = readushort(f)
3173        local nofpalettes        = readushort(f)
3174        local nofcolorrecords    = readushort(f)
3175        local firstcoloroffset   = readulong(f)
3176        local colorrecords       = { }
3177        local palettes           = readcardinaltable(f,nofpalettes,ushort)
3178        if version == 1 then
3179            -- used for guis
3180            local palettettypesoffset = readulong(f)
3181            local palettelabelsoffset = readulong(f)
3182            local paletteentryoffset  = readulong(f)
3183        end
3184        setposition(f,tableoffset+firstcoloroffset)
3185        for i=1,nofcolorrecords do
3186            local b, g, r, a = readbytes(f,4)
3187            colorrecords[i] = {
3188                r, g, b, a ~= 255 and a or nil,
3189            }
3190        end
3191        for i=1,nofpalettes do
3192            local p = { }
3193            local o = palettes[i]
3194            for j=1,nofpaletteentries do
3195                p[j] = colorrecords[o+j]
3196            end
3197            palettes[i] = p
3198        end
3199        fontdata.colorpalettes = palettes
3200    end
3201end
3202
3203local compress   = gzip and gzip.compress
3204local compressed = compress and gzip.compressed
3205
3206-- At some point I will delay loading and only store the offsets (in context lmtx
3207-- only).
3208
3209-- compressed = false
3210
3211function readers.svg(f,fontdata,specification)
3212    local tableoffset = gotodatatable(f,fontdata,"svg",specification.glyphs)
3213    if tableoffset then
3214        local version = readushort(f)
3215     -- if version ~= 0 then
3216     --     report("table version %a of %a is not supported (yet), maybe font %s is bad",version,"svg",fontdata.filename)
3217     --     return
3218     -- end
3219        local glyphs      = fontdata.glyphs
3220        local indexoffset = tableoffset + readulong(f)
3221        local reserved    = readulong(f)
3222        setposition(f,indexoffset)
3223        local nofentries  = readushort(f)
3224        local entries     = { }
3225        for i=1,nofentries do
3226            entries[i] = {
3227                first  = readushort(f),
3228                last   = readushort(f),
3229                offset = indexoffset + readulong(f),
3230                length = readulong(f),
3231            }
3232        end
3233        for i=1,nofentries do
3234            local entry = entries[i]
3235            setposition(f,entry.offset)
3236            local data = readstring(f,entry.length)
3237            if compressed and not compressed(data) then
3238                data = compress(data)
3239            end
3240            entries[i] = {
3241                first = entry.first,
3242                last  = entry.last,
3243                data  = data
3244            }
3245        end
3246        fontdata.svgshapes = entries
3247    end
3248    fontdata.hascolor = true
3249end
3250
3251function readers.sbix(f,fontdata,specification)
3252    local tableoffset = gotodatatable(f,fontdata,"sbix",specification.glyphs)
3253    if tableoffset then
3254        local version    = readushort(f)
3255        local flags      = readushort(f)
3256        local nofstrikes = readulong(f)
3257        local strikes    = { }
3258        local nofglyphs  = fontdata.nofglyphs
3259        for i=1,nofstrikes do
3260            strikes[i] = readulong(f)
3261        end
3262        local shapes = { }
3263        local done   = 0
3264        for i=1,nofstrikes do
3265            local strikeoffset = strikes[i] + tableoffset
3266            setposition(f,strikeoffset)
3267            strikes[i] = {
3268                ppem   = readushort(f),
3269                ppi    = readushort(f),
3270                offset = strikeoffset
3271            }
3272        end
3273        -- highest first
3274        sort(strikes,function(a,b)
3275            if b.ppem == a.ppem then
3276                return b.ppi < a.ppi
3277            else
3278                return b.ppem < a.ppem
3279            end
3280        end)
3281        local glyphs  = { }
3282        local delayed = CONTEXTLMTXMODE and CONTEXTLMTXMODE > 0 or fonts.handlers.typethree
3283        for i=1,nofstrikes do
3284            local strike       = strikes[i]
3285            local strikeppem   = strike.ppem
3286            local strikeppi    = strike.ppi
3287            local strikeoffset = strike.offset
3288            setposition(f,strikeoffset)
3289            for i=0,nofglyphs do
3290                glyphs[i] = readulong(f)
3291            end
3292            local glyphoffset = glyphs[0]
3293            for i=0,nofglyphs-1 do
3294                local nextoffset = glyphs[i+1]
3295                if not shapes[i] then
3296                    local datasize = nextoffset - glyphoffset
3297                    if datasize > 0 then
3298                        setposition(f,strikeoffset + glyphoffset)
3299                        local x      = readshort(f)
3300                        local y      = readshort(f)
3301                        local tag    = readtag(f) -- or just skip, we never needed it till now
3302                        local size   = datasize - 8
3303                        local data   = nil
3304                        local offset = nil
3305                        if delayed then
3306                            offset = getposition(f)
3307                            data   = nil
3308                        else
3309                            data   = readstring(f,size)
3310                            size   = nil
3311                        end
3312                        shapes[i] = {
3313                            x    = x,
3314                            y    = y,
3315                            o    = offset,
3316                            s    = size,
3317                            data = data,
3318                         -- tag  = tag, -- maybe for tracing
3319                         -- ppem = strikeppem, -- not used, for tracing
3320                         -- ppi  = strikeppi,  -- not used, for tracing
3321                        }
3322                        done = done + 1
3323                        if done == nofglyphs then
3324                            break
3325                        end
3326                    end
3327                end
3328                glyphoffset = nextoffset
3329            end
3330        end
3331        fontdata.pngshapes = shapes
3332    end
3333end
3334
3335-- Another bitmap (so not that useful) format. But Luigi found a font that
3336-- has them , so ...
3337
3338do
3339
3340    local function getmetrics(f)
3341        return {
3342            ascender              = readinteger(f),
3343            descender             = readinteger(f),
3344            widthmax              = readuinteger(f),
3345            caretslopedumerator   = readinteger(f),
3346            caretslopedenominator = readinteger(f),
3347            caretoffset           = readinteger(f),
3348            minorigin             = readinteger(f),
3349            minadvance            = readinteger(f),
3350            maxbefore             = readinteger(f),
3351            minafter              = readinteger(f),
3352            pad1                  = readinteger(f),
3353            pad2                  = readinteger(f),
3354        }
3355    end
3356
3357    -- bad names
3358
3359    local function getbigmetrics(f)
3360        -- bigmetrics, maybe just skip 9 bytes
3361        return {
3362            height       = readuinteger(f),
3363            width        = readuinteger(f),
3364            horiBearingX = readinteger(f),
3365            horiBearingY = readinteger(f),
3366            horiAdvance  = readuinteger(f),
3367            vertBearingX = readinteger(f),
3368            vertBearingY = readinteger(f),
3369            vertAdvance  = readuinteger(f),
3370        }
3371    end
3372
3373    local function getsmallmetrics(f)
3374        -- smallmetrics, maybe just skip 5 bytes
3375        return {
3376            height   = readuinteger(f),
3377            width    = readuinteger(f),
3378            bearingX = readinteger(f),
3379            bearingY = readinteger(f),
3380            advance  = readuinteger(f),
3381        }
3382    end
3383
3384    function readers.cblc(f,fontdata,specification)
3385        -- should we delay this ?
3386        local ctdttableoffset = gotodatatable(f,fontdata,"cbdt",specification.glyphs)
3387        if not ctdttableoffset then
3388            return
3389        end
3390        local cblctableoffset = gotodatatable(f,fontdata,"cblc",specification.glyphs)
3391        if cblctableoffset then
3392            local majorversion  = readushort(f)
3393            local minorversion  = readushort(f)
3394            local nofsizetables = readulong(f)
3395            local sizetables    = { }
3396            local shapes        = { }
3397            local subtables     = { }
3398            for i=1,nofsizetables do
3399                sizetables[i] = {
3400                    subtables    = readulong(f),
3401                    indexsize    = readulong(f),
3402                    nofsubtables = readulong(f),
3403                    colorref     = readulong(f),
3404                    hormetrics   = getmetrics(f),
3405                    vermetrics   = getmetrics(f),
3406                    firstindex   = readushort(f),
3407                    lastindex    = readushort(f),
3408                    ppemx        = readbyte(f),
3409                    ppemy        = readbyte(f),
3410                    bitdepth     = readbyte(f),
3411                    flags        = readbyte(f),
3412                }
3413            end
3414            sort(sizetables,function(a,b)
3415                if b.ppemx == a.ppemx then
3416                    return b.bitdepth < a.bitdepth
3417                else
3418                    return b.ppemx < a.ppemx
3419                end
3420            end)
3421            for i=1,nofsizetables do
3422                local s = sizetables[i]
3423                local d = false
3424                for j=s.firstindex,s.lastindex do
3425                    if not shapes[j] then
3426                        shapes[j] = i
3427                        d = true
3428                    end
3429                end
3430                if d then
3431                    s.used = true
3432                end
3433            end
3434            for i=1,nofsizetables do
3435                local s = sizetables[i]
3436                if s.used then
3437                    local offset = s.subtables
3438                    setposition(f,cblctableoffset+offset)
3439                    for j=1,s.nofsubtables do
3440                        local firstindex  = readushort(f)
3441                        local lastindex   = readushort(f)
3442                        local tableoffset = readulong(f) + offset
3443                        for k=firstindex,lastindex do
3444                            if shapes[k] == i then
3445                                local s = subtables[tableoffset]
3446                                if not s then
3447                                    s = {
3448                                        firstindex = firstindex,
3449                                        lastindex  = lastindex,
3450                                    }
3451                                    subtables[tableoffset] = s
3452                                end
3453                                shapes[k] = s
3454                            end
3455                        end
3456                    end
3457                end
3458            end
3459
3460            -- there is no need to sort in string stream but we have a nicer trace
3461            -- if needed
3462
3463            for offset, subtable in sortedhash(subtables) do
3464                local tabletype  = readushort(f)
3465                subtable.format  = readushort(f)
3466                local baseoffset = readulong(f) + ctdttableoffset
3467                local offsets    = { }
3468                local metrics    = nil
3469                if tabletype == 1 then
3470                    -- we have the usual one more to get the size
3471                    for i=subtable.firstindex,subtable.lastindex do
3472                        offsets[i] = readulong(f) + baseoffset
3473                    end
3474                    skipbytes(f,4)
3475                elseif tabletype == 2 then
3476                    local size = readulong(f)
3477                    local done = baseoffset
3478                    metrics = getbigmetrics(f)
3479                    for i=subtable.firstindex,subtable.lastindex do
3480                        offsets[i] = done
3481                        done = done + size
3482                    end
3483                elseif tabletype == 3 then
3484                    -- we have the usual one more to get the size
3485                    local n = subtable.lastindex - subtable.firstindex + 2
3486                    for i=subtable.firstindex,subtable.lastindex do
3487                        offsets[i] = readushort(f) + baseoffset
3488                    end
3489                    if math.odd(n) then
3490                        skipbytes(f,4)
3491                    else
3492                        skipbytes(f,2)
3493                    end
3494                elseif tabletype == 4 then
3495                    for i=1,readulong(f) do
3496                        offsets[readushort(f)] = readushort(f) + baseoffset
3497                    end
3498                elseif tabletype == 5 then
3499                    local size = readulong(f)
3500                    local done = baseoffset
3501                    metrics = getbigmetrics(f)
3502                    local n = readulong(f)
3503                    for i=1,n do
3504                        offsets[readushort(f)] = done
3505                        done = done + size
3506                    end
3507                    if math.odd(n) then
3508                        skipbytes(f,2)
3509                    end
3510                else
3511                    return -- unsupported format
3512                end
3513                subtable.offsets = offsets
3514                subtable.metrics = metrics
3515            end
3516
3517            -- we only support a few sensible types ... there are hardly any fonts so
3518            -- why are there so many variants ... not the best spec
3519
3520            local default = { width = 0, height = 0 }
3521            local glyphs  = fontdata.glyphs
3522            local delayed = CONTEXTLMTXMODE and CONTEXTLMTXMODE > 0 or fonts.handlers.typethree
3523
3524            for index, subtable in sortedhash(shapes) do
3525                if type(subtable) == "table" then
3526                    local data    = nil
3527                    local size    = nil
3528                    local metrics = default
3529                    local format  = subtable.format
3530                    local offset  = subtable.offsets[index]
3531                    setposition(f,offset)
3532                    if format == 17 then
3533                        metrics = getsmallmetrics(f)
3534                        size    = true
3535                    elseif format == 18 then
3536                        metrics = getbigmetrics(f)
3537                        size    = true
3538                    elseif format == 19 then
3539                        metrics = subtable.metrics
3540                        size    = true
3541                    else
3542                        -- forget about it
3543                    end
3544                    if size then
3545                        size = readulong(f)
3546                        if delayed then
3547                            offset = getposition(f)
3548                            data   = nil
3549                        else
3550                            offset = nil
3551                            data   = readstring(f,size)
3552                            size   = nil
3553                        end
3554                    else
3555                        offset = nil
3556                    end
3557                    local x = metrics.width
3558                    local y = metrics.height
3559                    shapes[index] = {
3560                        x    = x,
3561                        y    = y,
3562                        o    = offset,
3563                        s    = size,
3564                        data = data,
3565                    }
3566                    -- I'll look into this in more details when needed
3567                    -- as we can use the bearings to get better boxes.
3568                    local glyph = glyphs[index]
3569                    if not glyph.boundingbox then
3570                        local width  = glyph.width
3571                        local height = width * y/x
3572                        glyph.boundingbox = { 0, 0, width, height }
3573                    end
3574                else
3575                    shapes[index] = {
3576                        x    = 0,
3577                        y    = 0,
3578                        data = "", -- or just nil
3579                    }
3580                end
3581            end
3582
3583            fontdata.pngshapes = shapes -- we cheat
3584        end
3585    end
3586
3587    function readers.cbdt(f,fontdata,specification)
3588     -- local tableoffset = gotodatatable(f,fontdata,"ctdt",specification.glyphs)
3589     -- if tableoffset then
3590     --     local majorversion = readushort(f)
3591     --     local minorversion = readushort(f)
3592     -- end
3593    end
3594
3595    -- function readers.ebdt(f,fontdata,specification)
3596    --     if specification.glyphs then
3597    --     end
3598    -- end
3599
3600    -- function readers.ebsc(f,fontdata,specification)
3601    --     if specification.glyphs then
3602    --     end
3603    -- end
3604
3605    -- function readers.eblc(f,fontdata,specification)
3606    --     if specification.glyphs then
3607    --     end
3608    -- end
3609
3610end
3611
3612-- + AVAR : optional
3613-- + CFF2 : otf outlines
3614-- - CVAR : ttf hinting, not needed
3615-- + FVAR : the variations
3616-- + GVAR : ttf outline changes
3617-- + HVAR : horizontal changes
3618-- + MVAR : metric changes
3619-- + STAT : relations within fonts
3620-- * VVAR : vertical changes
3621--
3622-- * BASE : extra baseline adjustments
3623-- - GASP : not needed
3624-- + GDEF : not needed (carets)
3625-- + GPOS : adapted device tables (needed?)
3626-- + GSUB : new table
3627-- + NAME : 25 added
3628
3629function readers.stat(f,fontdata,specification)
3630    local tableoffset = gotodatatable(f,fontdata,"stat",true) -- specification.variable
3631    if tableoffset then
3632        local extras       = fontdata.extras
3633        local version      = readulong(f) -- 0x00010000
3634        local axissize     = readushort(f)
3635        local nofaxis      = readushort(f)
3636        local axisoffset   = readulong(f)
3637        local nofvalues    = readushort(f)
3638        local valuesoffset = readulong(f)
3639        local fallbackname = extras[readushort(f)] -- beta fonts mess up
3640        local axis         = { }
3641        local values       = { }
3642        setposition(f,tableoffset+axisoffset)
3643        for i=1,nofaxis do
3644            local tag = readtag(f)
3645            axis[i] = {
3646                tag      = tag,
3647                name     = lower(extras[readushort(f)] or tag),
3648                ordering = readushort(f), -- maybe gaps
3649                variants = { }
3650            }
3651        end
3652        -- flags:
3653        --
3654        -- 0x0001 : OlderSiblingFontAttribute
3655        -- 0x0002 : ElidableAxisValueName
3656        -- 0xFFFC : reservedFlags
3657        --
3658        setposition(f,tableoffset+valuesoffset)
3659        for i=1,nofvalues do
3660            values[i] = readushort(f)
3661        end
3662        for i=1,nofvalues do
3663            setposition(f,tableoffset + valuesoffset + values[i])
3664            local format  = readushort(f)
3665            local index   = readushort(f) + 1
3666            local flags   = readushort(f)
3667            local name    = lower(extras[readushort(f)] or "no name")
3668            local value   = readfixed(f)
3669            local variant
3670            if format == 1 then
3671                variant = {
3672                    flags = flags,
3673                    name  = name,
3674                    value = value,
3675                }
3676            elseif format == 2 then
3677                variant = {
3678                    flags   = flags,
3679                    name    = name,
3680                    value   = value,
3681                    minimum = readfixed(f),
3682                    maximum = readfixed(f),
3683                }
3684            elseif format == 3 then
3685                variant = {
3686                    flags = flags,
3687                    name  = name,
3688                    value = value,
3689                    link  = readfixed(f),
3690                }
3691            end
3692            insert(axis[index].variants,variant)
3693        end
3694        sort(axis,function(a,b)
3695            return a.ordering < b.ordering
3696        end)
3697        for i=1,#axis do
3698            local a = axis[i]
3699            sort(a.variants,function(a,b)
3700                return a.name < b.name
3701            end)
3702            a.ordering = nil
3703        end
3704        setvariabledata(fontdata,"designaxis",axis)
3705        setvariabledata(fontdata,"fallbackname",fallbackname)
3706    end
3707end
3708
3709-- The avar table is optional and used in combination with fvar. Given the
3710-- detailed explanation about bad values we expect the worst and do some
3711-- checking.
3712
3713function readers.avar(f,fontdata,specification)
3714    local tableoffset = gotodatatable(f,fontdata,"avar",true) -- specification.variable
3715    if tableoffset then
3716
3717        local function collect()
3718            local nofvalues = readushort(f)
3719            local values    = { }
3720            local lastfrom  = false
3721            local lastto    = false
3722            for i=1,nofvalues do
3723                local from = read2dot14(f)
3724                local to   = read2dot14(f)
3725                if lastfrom and from <= lastfrom then
3726                    -- ignore
3727                elseif lastto and to >= lastto then
3728                    -- ignore
3729                else
3730                    values[#values+1] = { from, to }
3731                    lastfrom, lastto = from, to
3732                end
3733            end
3734            nofvalues = #values
3735            if nofvalues > 2 then
3736                local some = values[1]
3737                if some[1] == -1 and some[2] == -1 then
3738                    some = values[nofvalues]
3739                    if some[1] == 1 and some[2] == 1 then
3740                        for i=2,nofvalues-1 do
3741                            some = values[i]
3742                            if some[1] == 0 and some[2] == 0 then
3743                                return values
3744                            end
3745                        end
3746                    end
3747                end
3748            end
3749            return false
3750        end
3751
3752        local version  = readulong(f) -- 0x00010000
3753        local reserved = readushort(f)
3754        local nofaxis  = readushort(f)
3755        local segments = { }
3756        for i=1,nofaxis do
3757            segments[i] = collect()
3758        end
3759        setvariabledata(fontdata,"segments",segments)
3760    end
3761end
3762
3763function readers.fvar(f,fontdata,specification)
3764    local tableoffset = gotodatatable(f,fontdata,"fvar",true) -- specification.variable or specification.instancenames
3765    if tableoffset then
3766        local version         = readulong(f) -- 0x00010000
3767        local offsettoaxis    = tableoffset + readushort(f)
3768        local reserved        = skipshort(f)
3769        -- pair 1
3770        local nofaxis         = readushort(f)
3771        local sizeofaxis      = readushort(f)
3772        -- pair 2
3773        local nofinstances    = readushort(f)
3774        local sizeofinstances = readushort(f)
3775        --
3776        local extras    = fontdata.extras
3777        local axis      = { }
3778        local instances = { }
3779        --
3780        setposition(f,offsettoaxis)
3781        --
3782        for i=1,nofaxis do
3783            axis[i] = {
3784                tag     = readtag(f),   -- ital opsz slnt wdth wght
3785                minimum = readfixed(f),
3786                default = readfixed(f),
3787                maximum = readfixed(f),
3788                flags   = readushort(f),
3789                name    = lower(extras[readushort(f)] or "bad name"),
3790            }
3791            local n = sizeofaxis - 20
3792            if n > 0 then
3793                skipbytes(f,n)
3794            elseif n < 0 then
3795                -- error
3796            end
3797        end
3798        --
3799        local nofbytes   = 2 + 2 + 2 + nofaxis * 4
3800        local readpsname = nofbytes <= sizeofinstances
3801        local skippable  = sizeofinstances - nofbytes
3802        for i=1,nofinstances do
3803            local subfamid = readushort(f)
3804            local flags    = readushort(f) -- 0, not used yet
3805            local values   = { }
3806            for i=1,nofaxis do
3807                values[i] = {
3808                    axis  = axis[i].tag,
3809                    value = readfixed(f),
3810                }
3811            end
3812            local psnameid = readpsname and readushort(f) or 0xFFFF
3813            if subfamid == 2 or subfamid == 17 then
3814                -- okay
3815            elseif subfamid == 0xFFFF then
3816                subfamid = nil
3817            elseif subfamid <= 256 or subfamid >= 32768 then
3818                subfamid = nil -- actually an error
3819            end
3820            if psnameid == 6 then
3821                -- okay
3822            elseif psnameid == 0xFFFF then
3823                psnameid = nil
3824            elseif psnameid <= 256 or psnameid >= 32768 then
3825                psnameid = nil -- actually an error
3826            end
3827            instances[i] = {
3828             -- flags     = flags,
3829                subfamily = extras[subfamid],
3830                psname    = psnameid and extras[psnameid] or nil,
3831                values    = values,
3832            }
3833            if skippable > 0 then
3834                skipbytes(f,skippable)
3835            end
3836        end
3837        setvariabledata(fontdata,"axis",axis)
3838        setvariabledata(fontdata,"instances",instances)
3839    end
3840end
3841
3842function readers.hvar(f,fontdata,specification)
3843    local factors = specification.factors
3844    if not factors then
3845        return
3846    end
3847    local tableoffset = gotodatatable(f,fontdata,"hvar",specification.variable)
3848    if not tableoffset then
3849     -- report("no hvar table, expect problems due to messy widths")
3850        return
3851    end
3852
3853    local version         = readulong(f) -- 0x00010000
3854    local variationoffset = tableoffset + readulong(f) -- the store
3855    local advanceoffset   = tableoffset + readulong(f)
3856    local lsboffset       = tableoffset + readulong(f)
3857    local rsboffset       = tableoffset + readulong(f)
3858
3859    local regions    = { }
3860    local variations = { }
3861    local innerindex = { } -- size is mapcount
3862    local outerindex = { } -- size is mapcount
3863    local deltas     = { }
3864
3865    if variationoffset > 0 then
3866        regions, deltas = readvariationdata(f,variationoffset,factors)
3867    end
3868    if not regions then
3869        -- for now .. what to do ?
3870        return
3871    end
3872
3873    if advanceoffset > 0 then
3874        --
3875        -- innerIndexBitCountMask = 0x000F
3876        -- mapEntrySizeMask       = 0x0030
3877        -- reservedFlags          = 0xFFC0
3878        --
3879        -- outerIndex = entry >>       ((entryFormat & innerIndexBitCountMask) + 1)
3880        -- innerIndex = entry & ((1 << ((entryFormat & innerIndexBitCountMask) + 1)) - 1)
3881        --
3882        setposition(f,advanceoffset)
3883        local format       = readushort(f) -- todo: check
3884        local mapcount     = readushort(f)
3885        local entrysize    = rshift(band(format,0x0030),4) + 1
3886        local nofinnerbits = band(format,0x000F) + 1 -- n of inner bits
3887        local innermask    = lshift(1,nofinnerbits) - 1
3888        local readcardinal = read_cardinal[entrysize] -- 1 upto 4 bytes
3889        for i=0,mapcount-1 do
3890            local mapdata = readcardinal(f)
3891            outerindex[i] = rshift(mapdata,nofinnerbits)
3892            innerindex[i] = band(mapdata,innermask)
3893        end
3894        -- use last entry when no match i
3895        setvariabledata(fontdata,"hvarwidths",true)
3896        local glyphs = fontdata.glyphs
3897        for i=0,fontdata.nofglyphs-1 do
3898            local glyph = glyphs[i]
3899            local width = glyph.width
3900            if width then
3901                local outer = outerindex[i] or 0
3902                local inner = innerindex[i] or i
3903                if outer and inner then -- not needed
3904                    local delta = deltas[outer+1]
3905                    if delta then
3906                        local d = delta.deltas[inner+1]
3907                        if d then
3908                            local scales = delta.scales
3909                            local deltaw = 0
3910                            for i=1,#scales do
3911                                local di = d[i]
3912                                if di then
3913                                    deltaw = deltaw + scales[i] * di
3914                                else
3915                                    break -- can't happen
3916                                end
3917                            end
3918-- report("index: %i, outer: %i, inner: %i, deltas: %|t, scales: %|t, width: %i, delta %i",
3919--     i,outer,inner,d,scales,width,round(deltaw))
3920                            glyph.width = width + round(deltaw)
3921                        end
3922                    end
3923                end
3924            end
3925        end
3926
3927    end
3928
3929 -- if lsboffset > 0 then
3930 --     -- we don't use left side bearings
3931 -- end
3932
3933 -- if rsboffset > 0 then
3934 --     -- we don't use right side bearings
3935 -- end
3936
3937 -- setvariabledata(fontdata,"hregions",regions)
3938
3939end
3940
3941function readers.vvar(f,fontdata,specification)
3942    if not specification.variable then
3943        return
3944    end
3945end
3946
3947function readers.mvar(f,fontdata,specification)
3948    local tableoffset = gotodatatable(f,fontdata,"mvar",specification.variable)
3949    if tableoffset then
3950        local version       = readulong(f) -- 0x00010000
3951        local reserved      = skipshort(f,1)
3952        local recordsize    = readushort(f)
3953        local nofrecords    = readushort(f)
3954        local offsettostore = tableoffset + readushort(f)
3955        local dimensions    = { }
3956        local factors       = specification.factors
3957        if factors then
3958            local regions, deltas = readvariationdata(f,offsettostore,factors)
3959            for i=1,nofrecords do
3960                local tag = readtag(f)
3961                local var = variabletags[tag]
3962                if var then
3963                    local outer = readushort(f)
3964                    local inner = readushort(f)
3965                    local delta = deltas[outer+1]
3966                    if delta then
3967                        local d = delta.deltas[inner+1]
3968                        if d then
3969                            local scales = delta.scales
3970                            local dd = 0
3971                            for i=1,#scales do
3972                                dd = dd + scales[i] * d[i]
3973                            end
3974                            var(fontdata,round(dd))
3975                        end
3976                    end
3977                else
3978                    skipshort(f,2)
3979                end
3980                if recordsize > 8 then -- 4 + 2 + 2
3981                    skipbytes(recordsize-8)
3982                end
3983            end
3984        end
3985     -- setvariabledata(fontdata,"mregions",regions)
3986    end
3987end
3988