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