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