font-con.lmt /size: 53 Kb    last modification: 2021-10-28 13:51
1if not modules then modules = { } end modules ['font-con'] = {
2    version   = 1.001,
3    comment   = "companion to font-ini.mkiv",
4    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5    copyright = "PRAGMA ADE / ConTeXt Development Team",
6    license   = "see context related readme files"
7}
8
9-- some names of table entries will be changed (no _)
10
11local next, tostring, tonumber, rawget = next, tostring, tonumber, rawget
12local format, match, lower, gsub, find = string.format, string.match, string.lower, string.gsub, string.find
13local sort, insert, concat = table.sort, table.insert, table.concat
14local sortedkeys, sortedhash, serialize, fastcopy = table.sortedkeys, table.sortedhash, table.serialize, table.fastcopy
15local derivetable = table.derive
16local ioflush = io.flush
17local round = math.round
18local setmetatable, getmetatable, rawget, rawset = setmetatable, getmetatable, rawget, rawset
19
20local trace_defining  = false  trackers.register("fonts.defining",  function(v) trace_defining = v end)
21local trace_scaling   = false  trackers.register("fonts.scaling",   function(v) trace_scaling  = v end)
22
23local report_defining = logs.reporter("fonts","defining")
24
25-- watch out: no negative depths and negative eights permitted in regular fonts
26
27--[[ldx--
28<p>Here we only implement a few helper functions.</p>
29--ldx]]--
30
31local fonts                  = fonts
32local constructors           = fonts.constructors or { }
33fonts.constructors           = constructors
34local handlers               = fonts.handlers or { } -- can have preloaded tables
35fonts.handlers               = handlers
36
37local allocate               = utilities.storage.allocate
38local setmetatableindex      = table.setmetatableindex
39
40-- will be directives
41
42constructors.version         = 1.01
43constructors.cache           = containers.define("fonts", "constructors", constructors.version, false)
44
45constructors.privateoffset   = fonts.privateoffsets.textbase or 0xF0000
46
47-- This might become an interface:
48
49local designsizes           = allocate()
50constructors.designsizes    = designsizes
51local loadedfonts           = allocate()
52constructors.loadedfonts    = loadedfonts
53
54----- scalecommands         = fonts.helpers.scalecommands
55
56--[[ldx--
57<p>We need to normalize the scale factor (in scaled points). This has to
58do with the fact that <l n='tex'/> uses a negative multiple of 1000 as
59a signal for a font scaled based on the design size.</p>
60--ldx]]--
61
62local factors = {
63    pt = 65536.0,
64    bp = 65781.8,
65}
66
67function constructors.setfactor(f)
68    constructors.factor = factors[f or 'pt'] or factors.pt
69end
70
71constructors.setfactor()
72
73function constructors.scaled(scaledpoints, designsize) -- handles designsize in sp as well
74    if scaledpoints < 0 then
75        local factor = constructors.factor
76        if designsize then
77            if designsize > factor then -- or just 1000 / when? mp?
78                return (- scaledpoints/1000) * designsize -- sp's
79            else
80                return (- scaledpoints/1000) * designsize * factor
81            end
82        else
83            return (- scaledpoints/1000) * 10 * factor
84        end
85    else
86        return scaledpoints
87    end
88end
89
90function constructors.getprivate(tfmdata)
91    local properties = tfmdata.properties
92    local private = properties.private
93    properties.private = private + 1
94    return private
95end
96
97function constructors.setmathparameter(tfmdata,name,value)
98    local m = tfmdata.mathparameters
99    local c = tfmdata.MathConstants
100    if m then
101        m[name] = value
102    end
103    if c and c ~= m then
104        c[name] = value
105    end
106end
107
108function constructors.getmathparameter(tfmdata,name)
109    local p = tfmdata.mathparameters or tfmdata.MathConstants
110    if p then
111        return p[name]
112    end
113end
114
115--[[ldx--
116<p>Beware, the boundingbox is passed as reference so we may not overwrite it
117in the process; numbers are of course copies. Here 65536 equals 1pt. (Due to
118excessive memory usage in CJK fonts, we no longer pass the boundingbox.)</p>
119--ldx]]--
120
121-- The scaler is only used for otf and afm and virtual fonts. If a virtual font has italic
122-- correction make sure to set the hasitalics flag. Some more flags will be added in the
123-- future.
124
125--[[ldx--
126<p>The reason why the scaler was originally split, is that for a while we experimented
127with a helper function. However, in practice the <l n='api'/> calls are too slow to
128make this profitable and the <l n='lua'/> based variant was just faster. A days
129wasted day but an experience richer.</p>
130--ldx]]--
131
132-- experimental, sharing kerns (unscaled and scaled) saves memory
133-- local sharedkerns, basekerns = constructors.check_base_kerns(tfmdata)
134-- loop over descriptions (afm and otf have descriptions, tfm not)
135-- there is no need (yet) to assign a value to chr.tonunicode
136
137-- constructors.prepare_base_kerns(tfmdata) -- optimalization
138
139-- we have target.name=metricfile and target.fullname=RealName and target.filename=diskfilename
140-- when collapsing fonts, luatex looks as both target.name and target.fullname as ttc files
141-- can have multiple subfonts
142
143function constructors.calculatescale(tfmdata,scaledpoints)
144    -- implemented in font-ctx.lmt
145end
146
147function constructors.assignmathparameters(target,original)
148    -- implemented in math-act.lua
149end
150
151function constructors.beforecopyingcharacters(target,original)
152    -- can be used for additional tweaking
153end
154
155function constructors.aftercopyingcharacters(target,original)
156    -- can be used for additional tweaking
157end
158
159function constructors.trytosharefont(target,tfmdata)
160    -- implemented in font-def.lmt
161end
162
163local synonyms = {
164    exheight      = "x_height",
165    xheight       = "x_height",
166    ex            = "x_height",
167    emwidth       = "quad",
168    em            = "quad",
169    spacestretch  = "space_stretch",
170    stretch       = "space_stretch",
171    spaceshrink   = "space_shrink",
172    shrink        = "space_shrink",
173    extraspace    = "extra_space",
174    xspace        = "extra_space",
175    slantperpoint = "slant",
176}
177
178function constructors.enhanceparameters(parameters)
179    local mt = getmetatable(parameters)
180    local getter = function(t,k)
181        if not k then
182            return nil
183        end
184        local s = synonyms[k]
185        if s then
186            return rawget(t,s) or (mt and mt[s]) or nil
187        end
188        if k == "spacing" then
189            return {
190                width   = t.space,
191                stretch = t.space_stretch,
192                shrink  = t.space_shrink,
193                extra   = t.extra_space,
194            }
195        end
196        return mt and mt[k] or nil
197    end
198    local setter = function(t,k,v)
199        if not k then
200            return 0
201        end
202        local s = synonyms[k]
203        if s then
204            rawset(t,s,v)
205        elseif k == "spacing" then
206            if type(v) == "table" then
207                rawset(t,"space",v.width or 0)
208                rawset(t,"space_stretch",v.stretch or 0)
209                rawset(t,"space_shrink",v.shrink or 0)
210                rawset(t,"extra_space",v.extra or 0)
211            end
212        else
213            rawset(t,k,v)
214        end
215    end
216    setmetatable(parameters, {
217        __index    = getter,
218        __newindex = setter,
219    })
220end
221
222local function mathkerns(v,vdelta)
223    local k = { }
224    for i=1,#v do
225        local entry  = v[i]
226        local height = entry.height
227        local kern   = entry.kern
228        k[i] = {
229            height = height and vdelta*height or 0,
230            kern   = kern   and vdelta*kern   or 0,
231        }
232    end
233    return k
234end
235
236local psfake = 0
237
238local function fixedpsname(psname,fallback)
239    local usedname = psname
240    if psname and psname ~= "" then
241        if find(psname," ",1,true) then
242            usedname = gsub(psname,"[%s]+","-")
243        else
244            -- we assume that the name is sane enough (we might sanitize completely some day)
245        end
246    elseif not fallback or fallback == "" then
247        psfake = psfake + 1
248        psname = "fakename-" .. psfake
249    else
250        -- filenames can be a mess so we do a drastic cleanup
251        psname   = fallback
252        usedname = gsub(psname,"[^a-zA-Z0-9]+","-")
253    end
254    return usedname, psname ~= usedname
255end
256
257function constructors.scale(tfmdata,specification)
258    local target         = { } -- the new table
259    --
260    if tonumber(specification) then
261        specification    = { size = specification }
262    end
263    target.specification = specification
264    --
265    local scalecommands  = fonts.helpers.scalecommands -- defined later
266    --
267    local scaledpoints   = specification.size
268    local relativeid     = specification.relativeid
269    --
270    local properties     = tfmdata.properties     or { }
271    local goodies        = tfmdata.goodies        or { }
272    local resources      = tfmdata.resources      or { }
273    local descriptions   = tfmdata.descriptions   or { } -- bad news if empty
274    local characters     = tfmdata.characters     or { } -- bad news if empty
275    local changed        = tfmdata.changed        or { } -- for base mode
276    local shared         = tfmdata.shared         or { }
277    local parameters     = tfmdata.parameters     or { }
278    local mathparameters = tfmdata.mathparameters or { }
279    --
280    local targetcharacters     = { }
281    local targetdescriptions   = derivetable(descriptions)
282    local targetparameters     = derivetable(parameters)
283    local targetproperties     = derivetable(properties)
284    local targetgoodies        = goodies                        -- we need to loop so no metatable
285    target.characters          = targetcharacters
286    target.descriptions        = targetdescriptions
287    target.parameters          = targetparameters
288 -- target.mathparameters      = targetmathparameters           -- happens elsewhere
289    target.properties          = targetproperties
290    target.goodies             = targetgoodies
291    target.shared              = shared
292    target.resources           = resources
293    target.unscaled            = tfmdata                        -- the original unscaled one
294    --
295    -- specification.mathsize : 1=text 2=script 3=scriptscript
296    -- specification.textsize : natural (text)size
297    -- parameters.mathsize    : 1=text 2=script 3=scriptscript >1000 enforced size (feature value other than yes)
298    --
299    local mathsize    = tonumber(specification.mathsize) or 0
300    local textsize    = tonumber(specification.textsize) or scaledpoints
301    local forcedsize  = tonumber(parameters.mathsize   ) or 0 -- can be set by the feature "mathsize"
302    local extrafactor = tonumber(specification.factor  ) or 1
303    if (mathsize == 2 or forcedsize == 2) and parameters.scriptpercentage then
304        scaledpoints = parameters.scriptpercentage * textsize / 100
305    elseif (mathsize == 3 or forcedsize == 3) and parameters.scriptscriptpercentage then
306        scaledpoints = parameters.scriptscriptpercentage * textsize / 100
307    elseif forcedsize > 1000 then -- safeguard
308        scaledpoints = forcedsize
309    else
310        -- in context x and xx also use mathsize
311    end
312    targetparameters.mathsize    = mathsize
313    targetparameters.textsize    = textsize
314    targetparameters.forcedsize  = forcedsize
315    targetparameters.extrafactor = extrafactor
316    --
317    local defaultwidth  = resources.defaultwidth  or 0
318    local defaultheight = resources.defaultheight or 0
319    local defaultdepth  = resources.defaultdepth or 0
320    local units         = parameters.units or 1000
321    --
322    -- boundary keys are no longer needed as we now have a string 'right_boundary'
323    -- that can be used in relevant tables (kerns and ligatures) ... not that I ever
324    -- used them
325    --
326 -- boundarychar_label = 0,     -- not needed
327 -- boundarychar       = 65536, -- there is now a string 'right_boundary'
328 -- false_boundarychar = 65536, -- produces invalid tfm in luatex
329    --
330    targetproperties.language   = properties.language or "dflt" -- inherited
331    targetproperties.script     = properties.script   or "dflt" -- inherited
332    targetproperties.mode       = properties.mode     or "base" -- inherited
333    targetproperties.method     = properties.method
334    --
335    local askedscaledpoints   = scaledpoints
336    local scaledpoints, delta = constructors.calculatescale(tfmdata,scaledpoints,nil,specification) -- no shortcut, dan be redefined
337    --
338    local hdelta      = delta
339    local vdelta      = delta
340    --
341    target.designsize = parameters.designsize -- not really needed so it might become obsolete
342    target.units      = units
343    --
344    target.size       = scaledpoints
345    --
346    target.subfont    = properties.subfont
347    target.cidinfo    = properties.cidinfo
348    target.format     = properties.format
349    --
350    local original = properties.original or tfmdata.original
351    local fontname = properties.fontname or tfmdata.fontname
352    local fullname = properties.fullname or tfmdata.fullname
353    local filename = properties.filename or tfmdata.filename
354    local psname   = properties.psname   or tfmdata.psname
355    local name     = properties.name     or tfmdata.name
356    --
357    -- The psname used in pdf file as well as for selecting subfont in ttc although
358    -- we don't need that subfont look up here (mapfile stuff).
359    --
360    local psname, psfixed = fixedpsname(psname,fontname or fullname or file.nameonly(filename))
361    --
362    target.original = original
363    target.fontname = fontname
364    target.fullname = fullname
365    target.filename = filename
366    target.psname   = psname
367    target.name     = name
368    --
369    properties.fontname = fontname
370    properties.fullname = fullname
371    properties.filename = filename
372    properties.psname   = psname
373    properties.name     = name
374    -- expansion (hz)
375    local expansion = parameters.expansion
376    if expansion then
377        target.stretch = expansion.stretch
378        target.shrink  = expansion.shrink
379        target.step    = expansion.step
380    end
381    -- slanting
382    local slantfactor = parameters.slantfactor or 0
383    if slantfactor ~= 0 then
384        target.slant = slantfactor * 1000
385    else
386        target.slant = 0
387    end
388    -- widening
389    local extendfactor = parameters.extendfactor or 0
390    if extendfactor ~= 0 and extendfactor ~= 1 then
391        hdelta = hdelta * extendfactor
392        target.extend = extendfactor * 1000
393    else
394        target.extend = 1000 -- extent ?
395    end
396    -- squeezing
397    local squeezefactor = parameters.squeezefactor or 0
398    if squeezefactor ~= 0 and squeezefactor ~= 1 then
399        vdelta = vdelta * squeezefactor
400        target.squeeze = squeezefactor * 1000
401    else
402        target.squeeze = 1000 -- extent ?
403    end
404    -- effects
405    local mode = parameters.mode or 0
406    if mode ~= 0 then
407        target.mode = mode
408    end
409    local width = parameters.width or 0
410    if width ~= 0 then
411        target.width = width * delta * 1000 / 655360
412    end
413    --
414    targetparameters.factor       = delta
415    targetparameters.hfactor      = hdelta
416    targetparameters.vfactor      = vdelta
417    targetparameters.size         = scaledpoints
418    targetparameters.units        = units
419    targetparameters.scaledpoints = askedscaledpoints
420    targetparameters.mode         = mode
421    targetparameters.width        = width
422    --
423    local hasquality       = parameters.expansion or parameters.protrusion
424    local hasitalics       = properties.hasitalics
425    local autoitalicamount = properties.autoitalicamount
426    local stackmath        = not properties.nostackmath
427    local haskerns         = properties.haskerns     or properties.mode == "base" -- we can have afm in node mode
428    local hasligatures     = properties.hasligatures or properties.mode == "base" -- we can have afm in node mode
429    local realdimensions   = properties.realdimensions
430    local writingmode      = properties.writingmode or "horizontal"
431    --
432    if changed and not next(changed) then
433        changed = false
434    end
435    --
436    target.writingmode = writingmode == "vertical" and "vertical" or "horizontal"
437    --
438    target.postprocessors = tfmdata.postprocessors
439    --
440    local targetslant         = (parameters.slant         or parameters[1] or 0) * factors.pt -- per point
441    local targetspace         = (parameters.space         or parameters[2] or 0) * hdelta
442    local targetspace_stretch = (parameters.space_stretch or parameters[3] or 0) * hdelta
443    local targetspace_shrink  = (parameters.space_shrink  or parameters[4] or 0) * hdelta
444    local targetx_height      = (parameters.x_height      or parameters[5] or 0) * vdelta
445    local targetquad          = (parameters.quad          or parameters[6] or 0) * hdelta
446    local targetextra_space   = (parameters.extra_space   or parameters[7] or 0) * hdelta
447    --
448    targetparameters.slant         = targetslant -- slantperpoint
449    targetparameters.space         = targetspace
450    targetparameters.space_stretch = targetspace_stretch
451    targetparameters.space_shrink  = targetspace_shrink
452    targetparameters.x_height      = targetx_height
453    targetparameters.quad          = targetquad
454    targetparameters.extra_space   = targetextra_space
455    --
456    local hshift = parameters.hshift
457    if hshift then
458        targetparameters.hshift = delta * hshift
459    end
460    local vshift = parameters.vshift
461    if vshift then
462        targetparameters.vshift = delta * vshift
463    end
464    --
465    local ascender = parameters.ascender
466    if ascender then
467        targetparameters.ascender  = delta * ascender
468    end
469    local descender = parameters.descender
470    if descender then
471        targetparameters.descender = delta * descender
472    end
473    --
474    constructors.enhanceparameters(targetparameters) -- official copies for us, now virtual
475    --
476    local scaledwidth      = defaultwidth  * hdelta
477    local scaledheight     = defaultheight * vdelta
478    local scaleddepth      = defaultdepth  * vdelta
479    --
480    local textcontrol = properties.textcontrol
481    targetproperties.textcontrol = textcontrol
482    target.textcontrol = textcontrol
483    --
484    local hasmath = (properties.hasmath or next(mathparameters)) and true
485    --
486    if hasmath then
487        constructors.assignmathparameters(target,tfmdata) -- does scaling and whatever is needed
488
489        properties.hasmath   = true
490        target.nomath        = false
491        target.MathConstants = target.mathparameters
492
493        properties.compactmath = true
494        target.compactmath     = true
495
496        local textscale         = parameters.textscale         -- or 1000
497        local scriptscale       = parameters.scriptscale       -- or  700
498        local scriptscriptscale = parameters.scriptscriptscale -- or  500
499
500        parameters.textscale         = textscale
501        parameters.scriptscale       = scriptscale
502        parameters.scriptscriptscale = scriptscriptscale
503
504        target.textscale         = textscale
505        target.scriptscale       = scriptscale
506        target.scriptscriptscale = scriptscriptscale
507
508        targetparameters.textscale         = textscale
509        targetparameters.scriptscale       = scriptscale
510        targetparameters.scriptscriptscale = scriptscriptscale
511
512        -- todo: maybe retrofit to font-con.lua
513
514        local oldmath = properties.oldmath
515        targetproperties.oldmath = oldmath
516        target.oldmath = oldmath
517
518        local mathcontrol = properties.mathcontrol
519        targetproperties.mathcontrol = mathcontrol
520        target.mathcontrol = mathcontrol
521
522    else
523        properties.hasmath    = false
524        target.nomath         = true
525        target.mathparameters = nil -- nop
526    end
527    --
528    -- During the transition to opentype the engine had troubles with italics so we had some
529    -- additional code for fixing that. In node mode (text) we don't care much if italics gets
530    -- passed because the engine does nothing with them then.
531    --
532    if hasmath then
533        local mathitalics = properties.mathitalics
534        if mathitalics == false then
535            if trace_defining then
536                report_defining("%s italics %s for font %a, fullname %a, filename %a","math",hasitalics and "ignored" or "disabled",name,fullname,filename)
537            end
538            hasitalics       = false
539            autoitalicamount = false
540        end
541    else
542        local textitalics = properties.textitalics
543        if textitalics == false then
544            if trace_defining then
545                report_defining("%s italics %s for font %a, fullname %a, filename %a","text",hasitalics and "ignored" or "disabled",name,fullname,filename)
546            end
547            hasitalics       = false
548            autoitalicamount = false
549        end
550    end
551    --
552    -- end of context specific trickery
553    --
554    if trace_defining then
555        report_defining("defining tfm, name %a, fullname %a, filename %a, %spsname %a, hscale %a, vscale %a, math %a, italics %a",
556            name,fullname,filename,psfixed and "(fixed) " or "",psname,hdelta,vdelta,
557            hasmath and "enabled" or "disabled",hasitalics and "enabled" or "disabled")
558    end
559    --
560    constructors.beforecopyingcharacters(target,tfmdata)
561    --
562    local sharedkerns = { }
563    --
564    -- we can have a dumb mode (basemode without math etc) that skips most
565    --
566    for unicode, character in next, characters do
567        local chr, description, index
568
569        -- todo: add description entry to char entry instead of index ... saves elsewhere too
570
571        if changed then
572            local c = changed[unicode]
573            if c and c ~= unicode then
574                if c then
575                    description = descriptions[c] or descriptions[unicode] or character
576                    character   = characters[c] or character
577                    index       = description.index or c
578                else
579                    description = descriptions[unicode] or character
580                    index       = description.index or unicode
581                end
582            else
583                description = descriptions[unicode] or character
584                index       = description.index or unicode
585            end
586        else
587            description = descriptions[unicode] or character
588            index       = description.index or unicode
589        end
590        local width     = description.width
591        local height    = description.height
592        local depth     = description.depth
593        local isunicode = description.unicode
594        if realdimensions then
595            -- this is mostly for checking issues
596            if not height or height == 0 then
597                local bb = description.boundingbox
598                local ht =  bb[4]
599                if ht ~= 0 then
600                    height = ht
601                end
602                if not depth or depth == 0 then
603                    local dp = -bb[2]
604                    if dp ~= 0 then
605                        depth = dp
606                    end
607                end
608            elseif not depth or depth == 0 then
609                local dp = -description.boundingbox[2]
610                if dp ~= 0 then
611                    depth = dp
612                end
613            end
614        end
615        if width  then width  = hdelta*width  else width  = scaledwidth  end
616        if height then height = vdelta*height else height = scaledheight end
617    --  if depth  then depth  = vdelta*depth  else depth  = scaleddepth  end
618        if depth and depth ~= 0 then
619            depth = delta*depth
620            if isunicode then
621                chr = {
622                    index   = index,
623                    height  = height,
624                    depth   = depth,
625                    width   = width,
626                    unicode = isunicode,
627                }
628            else
629                chr = {
630                    index  = index,
631                    height = height,
632                    depth  = depth,
633                    width  = width,
634                }
635            end
636        else
637            if isunicode then
638                chr = {
639                    index   = index,
640                    height  = height,
641                    width   = width,
642                    unicode = isunicode,
643                }
644            else
645                chr = {
646                    index  = index,
647                    height = height,
648                    width  = width,
649                }
650            end
651        end
652        if hasquality then
653            -- we could move these calculations elsewhere (saves calculations)
654            local ve = character.expansion_factor
655            if ve then
656                chr.expansion_factor = ve*1000 -- expansionfactor, hm, can happen elsewhere
657            end
658            local vl = character.left_protruding
659            if vl then
660                chr.left_protruding  = width*vl
661            end
662            local vr = character.right_protruding
663            if vr then
664                chr.right_protruding  = width*vr
665            end
666        end
667        --
668        if hasmath then
669            --
670            -- todo, just operate on descriptions.math
671            local vn = character.next
672            if vn then
673                chr.next = vn
674            else
675                local vv = character.vert_variants
676                if vv then
677                    local t = { }
678                    for i=1,#vv do
679                        local vvi = vv[i]
680                        local s = vvi["start"]   or 0
681                        local e = vvi["end"]     or 0
682                        local a = vvi["advance"] or 0
683                        t[i] = { -- zero check nicer for 5.3
684                            ["start"]    = s == 0 and 0 or s * vdelta,
685                            ["end"]      = e == 0 and 0 or e * vdelta,
686                            ["advance"]  = a == 0 and 0 or a * vdelta,
687                            ["extender"] = vvi["extender"],
688                            ["glyph"]    = vvi["glyph"],
689                        }
690                    end
691                    chr.vert_variants = t
692                else
693                    local hv = character.horiz_variants
694                    if hv then
695                        local t = { }
696                        for i=1,#hv do
697                            local hvi = hv[i]
698                            local s = hvi["start"]   or 0
699                            local e = hvi["end"]     or 0
700                            local a = hvi["advance"] or 0
701                            t[i] = { -- zero check nicer for 5.3
702                                ["start"]    = s == 0 and 0 or s * hdelta,
703                                ["end"]      = e == 0 and 0 or e * hdelta,
704                                ["advance"]  = a == 0 and 0 or a * hdelta,
705                                ["extender"] = hvi["extender"],
706                                ["glyph"]    = hvi["glyph"],
707                            }
708                        end
709                        chr.horiz_variants = t
710                    end
711                end
712                -- todo also check mathitalics (or that one can go away)
713            end
714            local vi = character.vert_italic
715            if vi and vi ~= 0 then
716                chr.vert_italic = vi*hdelta
717            end
718            local va = character.accent
719            if va then
720                chr.top_accent = vdelta*va
721            end
722            if stackmath then
723                local mk = character.mathkerns
724                if mk then
725                    local tr = mk.topright
726                    local tl = mk.topleft
727                    local br = mk.bottomright
728                    local bl = mk.bottomleft
729                    chr.mathkern = { -- singular -> should be patched in luatex !
730                        top_right    = tr and mathkerns(tr,vdelta) or nil,
731                        top_left     = tl and mathkerns(tl,vdelta) or nil,
732                        bottom_right = br and mathkerns(br,vdelta) or nil,
733                        bottom_left  = bl and mathkerns(bl,vdelta) or nil,
734                    }
735                end
736            end
737            if hasitalics then
738                local vi = character.italic
739                if vi and vi ~= 0 then
740                    chr.italic = vi*hdelta
741                end
742            end
743        elseif autoitalicamount then -- itlc feature
744            local vi = description.italic
745            if not vi then
746                local bb = description.boundingbox
747                if bb then
748                    local vi = bb[3] - description.width + autoitalicamount
749                    if vi > 0 then -- < 0 indicates no overshoot or a very small auto italic
750                        chr.italic = vi*hdelta
751                    end
752                else
753                 -- report_defining("no boundingbox for character %C in font %a, fullname %a, filename %a",unicode,name,fullname,filename)
754                end
755            elseif vi ~= 0 then
756                chr.italic = vi*hdelta
757            end
758        elseif hasitalics then -- unlikely
759            local vi = character.italic
760            if vi and vi ~= 0 then
761                chr.italic = vi*hdelta
762            end
763        end
764        if haskerns then
765            local vk = character.kerns
766            if vk then
767                local s = sharedkerns[vk]
768                if not s then
769                    s = { }
770                    for k,v in next, vk do s[k] = v*hdelta end
771                    sharedkerns[vk] = s
772                end
773                chr.kerns = s
774            end
775        end
776        if hasligatures then
777            local vl = character.ligatures
778            if vl then
779                if true then
780                    chr.ligatures = vl -- shared
781                else
782                    local tt = { }
783                    for i, l in next, vl do
784                        tt[i] = l
785                    end
786                    chr.ligatures = tt
787                end
788            end
789        end
790        local vc = character.commands
791        if vc then
792            chr.commands = scalecommands(vc,hdelta,vdelta)
793        end
794        local vx = characters.xoffset
795        if vx then
796            chr.vx = vx*hdelta
797        end
798        local vy = characters.yoffset
799        if vy then
800            chr.vy = vy*hdelta
801        end
802        targetcharacters[unicode] = chr
803    end
804    --
805    properties.setitalics = hasitalics -- for postprocessing
806    --
807 -- if hasmath then properties.mode = "none" end -- possible optimization but it needs testing
808    --
809    constructors.aftercopyingcharacters(target,tfmdata)
810    --
811    constructors.trytosharefont(target,tfmdata)
812    --
813    -- catch inconsistencies (for now)
814    --
815    local vfonts = target.fonts
816    if not vfonts or #vfonts == 0 then
817        target.fonts = { { id = 0 } }
818    end
819    --
820    return target
821end
822
823function constructors.finalize(tfmdata)
824    if tfmdata.properties and tfmdata.properties.finalized then
825        return
826    end
827    --
828    if not tfmdata.characters then
829        return nil
830    end
831    --
832    if not tfmdata.goodies then
833        tfmdata.goodies = { }
834    end
835    --
836    local parameters = tfmdata.parameters
837    if not parameters then
838        return nil
839    end
840    --
841    if not parameters.expansion then
842        parameters.expansion = {
843            stretch = tfmdata.stretch or 0,
844            shrink  = tfmdata.shrink  or 0,
845            step    = tfmdata.step    or 0,
846        }
847    end
848    --
849    if not parameters.size then
850        parameters.size = tfmdata.size
851    end
852    --
853    if not parameters.mode then
854        parameters.mode = 0
855    end
856    --
857    if not parameters.width then
858        parameters.width = 0
859    end
860    --
861    if not parameters.slantfactor then
862        parameters.slantfactor = (tfmdata.slant or 0)/1000
863    end
864    --
865    if not parameters.extendfactor then
866        parameters.extendfactor = (tfmdata.extend or 1000)/1000
867    end
868    --
869    if not parameters.squeezefactor then
870        parameters.squeezefactor = (tfmdata.squeeze or 1000)/1000
871    end
872    --
873    local designsize = parameters.designsize
874    if designsize then
875        parameters.minsize = tfmdata.minsize or designsize
876        parameters.maxsize = tfmdata.maxsize or designsize
877    else
878        designsize = factors.pt * 10
879        parameters.designsize = designsize
880        parameters.minsize    = designsize
881        parameters.maxsize    = designsize
882    end
883    parameters.minsize = tfmdata.minsize or parameters.designsize
884    parameters.maxsize = tfmdata.maxsize or parameters.designsize
885    --
886    if not parameters.units then
887        parameters.units = tfmdata.units or 1000
888    end
889    --
890    if not tfmdata.descriptions then
891        local descriptions = { } -- yes or no
892        setmetatableindex(descriptions, function(t,k) local v = { } t[k] = v return v end)
893        tfmdata.descriptions = descriptions
894    end
895    --
896    local properties = tfmdata.properties
897    if not properties then
898        properties = { }
899        tfmdata.properties = properties
900    end
901    --
902    properties.fontname      = properties.fontname or tfmdata.fontname
903    properties.filename      = properties.filename or tfmdata.filename
904    properties.fullname      = properties.fullname or tfmdata.fullname
905    properties.name          = properties.name     or tfmdata.name
906    properties.psname        = properties.psname   or tfmdata.psname
907    --
908    properties.subfont       = tfmdata.subfont       or nil
909    properties.cidinfo       = tfmdata.cidinfo       or nil
910    properties.format        = tfmdata.format        or "type1"
911    properties.writingmode   = tfmdata.writingmode   or "horizontal"
912    properties.usedbitmap    = tfmdata.usedbitmap
913    --
914    if not tfmdata.resources then
915        tfmdata.resources = { }
916    end
917    if not tfmdata.shared then
918        tfmdata.shared = { }
919    end
920    --
921    -- tfmdata.fonts
922    -- tfmdata.unscaled
923    --
924    if not properties.hasmath then
925        properties.hasmath = not tfmdata.nomath
926    end
927    --
928    tfmdata.MathConstants    = nil
929    tfmdata.postprocessors   = nil
930    --
931    tfmdata.fontname         = nil
932    tfmdata.filename         = nil
933    tfmdata.fullname         = nil
934    tfmdata.name             = nil -- most tricky part
935    tfmdata.psname           = nil
936    --
937    tfmdata.subfont          = nil
938    tfmdata.cidinfo          = nil
939    tfmdata.format           = nil
940    tfmdata.nomath           = nil
941    tfmdata.designsize       = nil
942    --
943    tfmdata.size             = nil
944    tfmdata.stretch          = nil
945    tfmdata.shrink           = nil
946    tfmdata.step             = nil
947    tfmdata.slant            = nil
948    tfmdata.extend           = nil
949    tfmdata.squeeze          = nil
950    tfmdata.mode             = nil
951    tfmdata.width            = nil
952    tfmdata.units            = nil
953    --
954    tfmdata.cache            = nil
955    --
956    properties.finalized     = true
957    --
958    return tfmdata
959end
960
961--[[ldx--
962<p>A unique hash value is generated by:</p>
963--ldx]]--
964
965local hashmethods        = { }
966constructors.hashmethods = hashmethods
967
968function constructors.hashfeatures(specification) -- will be overloaded
969    local features = specification.features
970    if features then
971        local t, n = { }, 0
972        for category, list in sortedhash(features) do
973            if next(list) then
974                local hasher = hashmethods[category]
975                if hasher then
976                    local hash = hasher(list)
977                    if hash then
978                        n = n + 1
979                        t[n] = category .. ":" .. hash
980                    end
981                end
982            end
983        end
984        if n > 0 then
985            return concat(t," & ")
986        end
987    end
988    return "unknown"
989end
990
991hashmethods.normal = function(list)
992    local s = { }
993    local n = 0
994    for k, v in next, list do
995        if not k then
996            -- no need to add to hash
997        elseif k == "number" or k == "features" then
998            -- no need to add to hash (maybe we need a skip list)
999        else
1000            n = n + 1
1001            if type(v) == "table" then
1002                -- table.sequenced
1003                local t = { }
1004                local m = 0
1005                for k, v in next, v do
1006                    m = m + 1
1007                    t[m] = k .. '=' .. tostring(v)
1008                end
1009                sort(t)
1010                s[n] = k .. '={' .. concat(t,",") .. "}"
1011            else
1012                s[n] = k .. '=' .. tostring(v)
1013            end
1014        end
1015    end
1016    if n > 0 then
1017        sort(s)
1018        return concat(s,"+")
1019    end
1020end
1021
1022--[[ldx--
1023<p>In principle we can share tfm tables when we are in need for a font, but then
1024we need to define a font switch as an id/attr switch which is no fun, so in that
1025case users can best use dynamic features ... so, we will not use that speedup. Okay,
1026when we get rid of base mode we can optimize even further by sharing, but then we
1027loose our testcases for <l n='luatex'/>.</p>
1028--ldx]]--
1029
1030function constructors.hashinstance(specification,force)
1031    -- implemented in font-ctx.lmt
1032end
1033
1034function constructors.setname(tfmdata,specification)
1035    -- dummy (still called in generic font-otl)
1036end
1037
1038function constructors.checkedfilename(data)
1039    local foundfilename = data.foundfilename
1040    if not foundfilename then
1041        local askedfilename = data.filename or ""
1042        if askedfilename ~= "" then
1043            askedfilename = resolvers.resolve(askedfilename) -- no shortcut
1044            foundfilename = resolvers.findbinfile(askedfilename,"") or ""
1045            if foundfilename == "" then
1046                report_defining("source file %a is not found",askedfilename)
1047                foundfilename = resolvers.findbinfile(file.basename(askedfilename),"") or ""
1048                if foundfilename ~= "" then
1049                    report_defining("using source file %a due to cache mismatch",foundfilename)
1050                end
1051            end
1052        end
1053        data.foundfilename = foundfilename
1054    end
1055    return foundfilename
1056end
1057
1058local formats = allocate()
1059fonts.formats = formats
1060
1061setmetatableindex(formats, function(t,k)
1062    local l = lower(k)
1063    if rawget(t,k) then
1064        t[k] = l
1065        return l
1066    end
1067    return rawget(t,file.suffix(l))
1068end)
1069
1070do
1071
1072    local function setindeed(mode,source,target,group,name,position)
1073        local action = source[mode]
1074        if not action then
1075            return
1076        end
1077        local t = target[mode]
1078        if not t then
1079            report_defining("fatal error in setting feature %a, group %a, mode %a",name,group,mode)
1080            os.exit()
1081        elseif position then
1082            -- todo: remove existing
1083            insert(t, position, { name = name, action = action })
1084        else
1085            for i=1,#t do
1086                local ti = t[i]
1087                if ti.name == name then
1088                    ti.action = action
1089                    return
1090                end
1091            end
1092            insert(t, { name = name, action = action })
1093        end
1094    end
1095
1096    local function set(group,name,target,source)
1097        target = target[group]
1098        if not target then
1099            report_defining("fatal target error in setting feature %a, group %a",name,group)
1100            os.exit()
1101        end
1102        local source = source[group]
1103        if not source then
1104            report_defining("fatal source error in setting feature %a, group %a",name,group)
1105            os.exit()
1106        end
1107        local position = source.position
1108        setindeed("node",source,target,group,name,position)
1109        setindeed("base",source,target,group,name,position)
1110        setindeed("none",source,target,group,name,position)
1111        setindeed("plug",source,target,group,name,position)
1112    end
1113
1114    local function register(where,specification)
1115        local name = specification.name
1116        if name and name ~= "" then
1117            local default      = specification.default
1118            local description  = specification.description
1119            local initializers = specification.initializers
1120            local processors   = specification.processors
1121            local manipulators = specification.manipulators
1122            local modechecker  = specification.modechecker
1123            if default then
1124                where.defaults[name] = default
1125            end
1126            if description and description ~= "" then
1127                where.descriptions[name] = description
1128            end
1129            if initializers then
1130                set('initializers',name,where,specification)
1131            end
1132            if processors then
1133                set('processors',  name,where,specification)
1134            end
1135            if manipulators then
1136                set('manipulators',name,where,specification)
1137            end
1138            if modechecker then
1139               where.modechecker = modechecker
1140            end
1141        end
1142    end
1143
1144    constructors.registerfeature = register
1145
1146    function constructors.getfeatureaction(what,where,mode,name)
1147        what = handlers[what].features
1148        if what then
1149            where = what[where]
1150            if where then
1151                mode = where[mode]
1152                if mode then
1153                    for i=1,#mode do
1154                        local m = mode[i]
1155                        if m.name == name then
1156                            return m.action
1157                        end
1158                    end
1159                end
1160            end
1161        end
1162    end
1163
1164    local newfeatures        = { }
1165    constructors.newfeatures = newfeatures -- downward compatible
1166    constructors.features    = newfeatures
1167
1168    local function setnewfeatures(what)
1169        local handler  = handlers[what]
1170        local features = handler.features
1171        if not features then
1172            local tables     = handler.tables     -- can be preloaded
1173            local statistics = handler.statistics -- can be preloaded
1174            features = allocate {
1175                defaults     = { },
1176                descriptions = tables and tables.features or { },
1177                used         = statistics and statistics.usedfeatures or { },
1178                initializers = { base = { }, node = { }, none = { }, plug = { } },
1179                processors   = { base = { }, node = { }, none = { }, plug = { } },
1180                manipulators = { base = { }, node = { }, none = { }, plug = { } },
1181                finalizers   = { base = { }, node = { }, none = { }, plug = { } },
1182            }
1183            features.register = function(specification) return register(features,specification) end
1184            handler.features = features -- will also become hidden
1185        end
1186        return features
1187    end
1188
1189    setmetatable(newfeatures, {
1190        __call  = function(t,k) local v = t[k] return v end,
1191        __index = function(t,k) local v = setnewfeatures(k) t[k] = v return v end,
1192    })
1193
1194end
1195
1196do
1197
1198    local newhandler        = { }
1199    constructors.handlers   = newhandler -- downward compatible
1200    constructors.newhandler = newhandler
1201
1202    local function setnewhandler(what) -- could be a metatable newindex
1203        local handler = handlers[what]
1204        if not handler then
1205            handler = { }
1206            handlers[what] = handler
1207        end
1208        return handler
1209    end
1210
1211    setmetatable(newhandler, {
1212        __call  = function(t,k) local v = t[k] return v end,
1213        __index = function(t,k) local v = setnewhandler(k) t[k] = v return v end,
1214    })
1215
1216end
1217
1218do
1219    -- a pitty that we need to be generic as we have nicer mechanisms for this ...
1220
1221    local newenhancer        = { }
1222    constructors.enhancers   = newenhancer
1223    constructors.newenhancer = newenhancer
1224
1225    local function setnewenhancer(format)
1226
1227        local handler   = handlers[format]
1228        local enhancers = handler.enhancers
1229
1230        if not enhancers then
1231
1232            local actions  = allocate() -- no need to allocate thee
1233            local before   = allocate()
1234            local after    = allocate()
1235            local order    = allocate()
1236            local known    = { }
1237            local nofsteps = 0
1238            local patches  = { before = before, after = after }
1239
1240            local trace   = false
1241            local report  = logs.reporter("fonts",format .. " enhancing")
1242
1243            trackers.register(format .. ".loading", function(v) trace = v end)
1244
1245            local function enhance(name,data,filename,raw)
1246                local enhancer = actions[name]
1247                if enhancer then
1248                    if trace then
1249                        report("apply enhancement %a to file %a",name,filename)
1250                        ioflush()
1251                    end
1252                    enhancer(data,filename,raw)
1253                else
1254                    -- no message as we can have private ones
1255                end
1256            end
1257
1258            local function apply(data,filename,raw)
1259                local basename = file.basename(lower(filename))
1260                if trace then
1261                    report("%s enhancing file %a","start",filename)
1262                end
1263                ioflush() -- we want instant messages
1264                for e=1,nofsteps do
1265                    local enhancer = order[e]
1266                    local b = before[enhancer]
1267                    if b then
1268                        for pattern, action in next, b do
1269                            if find(basename,pattern) then
1270                                action(data,filename,raw)
1271                            end
1272                        end
1273                    end
1274                    enhance(enhancer,data,filename,raw) -- we have one installed: check extra features
1275                    local a = after[enhancer]
1276                    if a then
1277                        for pattern, action in next, a do
1278                            if find(basename,pattern) then
1279                                action(data,filename,raw)
1280                            end
1281                        end
1282                    end
1283                    ioflush() -- we want instant messages
1284                end
1285                if trace then
1286                    report("%s enhancing file %a","stop",filename)
1287                end
1288                ioflush() -- we want instant messages
1289            end
1290
1291            local function register(what,action)
1292                if action then
1293                    if actions[what] then
1294                        -- overloading, e.g."check extra features"
1295                    else
1296                        nofsteps        = nofsteps + 1
1297                        order[nofsteps] = what
1298                        known[what]     = nofsteps
1299                    end
1300                    actions[what] = action
1301                else
1302                    report("bad enhancer %a",what)
1303                end
1304            end
1305
1306            -- We used to have a lot of enhancers but no longer with the new font loader. The order of enhancers
1307            -- is the order of definition. The before/after patches are there for old times sake and happen
1308            -- before or after a (named) enhancer. An example of a set enhancer is "check extra features" so one
1309            -- one set patches before or after that is applied. Unknown enhancers are auto-registered. It's a bit
1310            -- messy but we keep it for compatibility reasons.
1311            --
1312            -- fonts.handlers.otf.enhancers.patches.register("before","some patches","somefont",function(data,filename)
1313            --     print("!!!!!!!") -- before | after
1314            -- end)
1315            --
1316            -- fonts.handlers.otf.enhancers.register("more patches",function(data,filename)
1317            --     print("???????") -- enhance
1318            -- end)
1319
1320            local function patch(what,where,pattern,action)
1321                local pw = patches[what]
1322                if pw then
1323                    local ww = pw[where]
1324                    if ww then
1325                        ww[pattern] = action
1326                    else
1327                        pw[where] = { [pattern] = action }
1328                        if not known[where] then
1329                            nofsteps        = nofsteps + 1
1330                            order[nofsteps] = where
1331                            known[where]    = nofsteps
1332                        end
1333                    end
1334                end
1335            end
1336
1337            enhancers = {
1338                register = register,
1339                apply    = apply,
1340                patch    = patch,
1341                report   = report,
1342                patches  = {
1343                    register = patch,
1344                    report   = report,
1345                }, -- for old times sake
1346            }
1347
1348            handler.enhancers = enhancers
1349        end
1350        return enhancers
1351    end
1352
1353    setmetatable(newenhancer, {
1354        __call  = function(t,k) local v = t[k] return v end,
1355        __index = function(t,k) local v = setnewenhancer(k) t[k] = v return v end,
1356    })
1357
1358end
1359
1360--[[ldx--
1361<p>We need to check for default features. For this we provide
1362a helper function.</p>
1363--ldx]]--
1364
1365function constructors.checkedfeatures(what,features)
1366    local defaults = handlers[what].features.defaults
1367    if features and next(features) then
1368        features = fastcopy(features) -- can be inherited (mt) but then no loops possible
1369        for key, value in next, defaults do
1370            if features[key] == nil then
1371                features[key] = value
1372            end
1373        end
1374        return features
1375    else
1376        return fastcopy(defaults) -- we can change features in place
1377    end
1378end
1379
1380-- before scaling
1381
1382function constructors.initializefeatures(what,tfmdata,features,trace,report)
1383    if features and next(features) then
1384        local properties       = tfmdata.properties or { } -- brrr
1385        local whathandler      = handlers[what]
1386        local whatfeatures     = whathandler.features
1387        local whatmodechecker  = whatfeatures.modechecker
1388        -- properties.mode can be enforces (for instance in font-otd)
1389        local mode             = properties.mode or (whatmodechecker and whatmodechecker(tfmdata,features,features.mode)) or features.mode or "base"
1390        properties.mode        = mode -- also status
1391        features.mode          = mode -- both properties.mode or features.mode can be changed
1392        --
1393        local done             = { }
1394        while true do
1395            local redo = false
1396            local initializers = whatfeatures.initializers[mode]
1397            if initializers then
1398                for i=1,#initializers do
1399                    local step = initializers[i]
1400                    local feature = step.name
1401-- we could intercept mode here .. needs a rewrite of this whole loop then but it's cleaner that way
1402                    local value = features[feature]
1403                    if not value then
1404                        -- disabled
1405                    elseif done[feature] then
1406                        -- already done
1407                    else
1408                        local action = step.action
1409                        if trace then
1410                            report("initializing feature %a to %a for mode %a for font %a",feature,
1411                                value,mode,tfmdata.properties.fullname)
1412                        end
1413                        action(tfmdata,value,features) -- can set mode (e.g. goodies) so it can trigger a restart
1414                        if mode ~= properties.mode or mode ~= features.mode then
1415                            if whatmodechecker then
1416                                properties.mode = whatmodechecker(tfmdata,features,properties.mode) -- force checking
1417                                features.mode   = properties.mode
1418                            end
1419                            if mode ~= properties.mode then
1420                                mode = properties.mode
1421                                redo = true
1422                            end
1423                        end
1424                        done[feature] = true
1425                    end
1426                    if redo then
1427                        break
1428                    end
1429                end
1430                if not redo then
1431                    break
1432                end
1433            else
1434                break
1435            end
1436        end
1437        properties.mode = mode -- to be sure
1438        return true
1439    else
1440        return false
1441    end
1442end
1443
1444-- while typesetting
1445
1446function constructors.collectprocessors(what,tfmdata,features,trace,report)
1447    local processes    = { }
1448    local nofprocesses = 0
1449    if features and next(features) then
1450        local properties     = tfmdata.properties
1451        local whathandler    = handlers[what]
1452        local whatfeatures   = whathandler.features
1453        local whatprocessors = whatfeatures.processors
1454        local mode           = properties.mode
1455        local processors     = whatprocessors[mode]
1456        if processors then
1457            for i=1,#processors do
1458                local step = processors[i]
1459                local feature = step.name
1460                if features[feature] then
1461                    local action = step.action
1462                    if trace then
1463                        report("installing feature processor %a for mode %a for font %a",feature,mode,tfmdata.properties.fullname)
1464                    end
1465                    if action then
1466                        nofprocesses = nofprocesses + 1
1467                        processes[nofprocesses] = action
1468                    end
1469                end
1470            end
1471        elseif trace then
1472            report("no feature processors for mode %a for font %a",mode,properties.fullname)
1473        end
1474    end
1475    return processes
1476end
1477
1478-- after scaling
1479
1480local function apply(key,what,tfmdata,features,trace,report)
1481    if features and next(features) then
1482        local properties   = tfmdata.properties
1483        local whathandler  = handlers[what]
1484        local whatfeatures = whathandler.features
1485        local whatactions  = whatfeatures[key]
1486        local mode         = properties.mode
1487        local actions      = whatactions[mode]
1488        if actions then
1489            for i=1,#actions do
1490                local step    = actions[i]
1491                local feature = step.name
1492                local value   = features[feature]
1493                if value then
1494                    local action = step.action
1495                    if trace then
1496                        report("applying feature %s %a for mode %a for font %a",key,feature,mode,properties.fullname)
1497                    end
1498                    if action then
1499                        action(tfmdata,feature,value)
1500                    end
1501                end
1502            end
1503        end
1504    end
1505end
1506
1507function constructors.applymanipulators(what,tfmdata,features,trace,report)
1508    if features and next(features) then
1509        apply("manipulators",what,tfmdata,features,trace,report)
1510    end
1511end
1512
1513function constructors.applyfinalizers(what,tfmdata,features,trace,report)
1514    if features and next(features) then
1515        apply("finalizers",what,tfmdata,features,trace,report)
1516    end
1517end
1518
1519function constructors.addcoreunicodes(unicodes) -- maybe make this a metatable if used at all
1520    if not unicodes then
1521        unicodes = { }
1522    end
1523    unicodes.space  = 0x0020
1524    unicodes.hyphen = 0x002D
1525    unicodes.zwj    = 0x200D
1526    unicodes.zwnj   = 0x200C
1527    return unicodes
1528end
1529