font-con.lmt /size: 56 Kb    last modification: 2025-02-21 11:03
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(target,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 <const> = tex.charactertagcodes.abovebaseline
272local belowbaseline_tag <const> = tex.charactertagcodes.belowbaseline
273
274local textcontrolcodes  = tex.textcontrolcodes
275local collapse_hyphens  <const> = textcontrolcodes.collapsehyphens
276local base_ligaturing   <const> = textcontrolcodes.baseligaturing
277local base_kerning      <const> = textcontrolcodes.basekerning
278local none_protected    <const> = textcontrolcodes.noneprotected
279local has_italics       <const> = textcontrolcodes.hasitalics
280local auto_italics      <const> = 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    local hasquality       = parameters.expansion or parameters.protrusion
458    local hasitalics       = properties.hasitalics
459    local useditalicangle  = properties.useditalicangle or properties.italicangle or 0
460    local usedslant        = properties.usedslant       or properties.usedslant   or 0
461 -- local stackmath        = not properties.nostackmath
462    local haskerns         = properties.haskerns     or properties.mode == "base" -- we can have afm in node mode
463    local hasligatures     = properties.hasligatures or properties.mode == "base" -- we can have afm in node mode
464    local writingmode      = properties.writingmode or "horizontal"
465    --
466    targetparameters.useditalicangle = useditalicangle
467    targetproperties.useditalicangle = useditalicangle
468    targetparameters.usedslant       = usedslant
469    targetproperties.usedslant       = usedslant
470    --
471    if changed and not next(changed) then
472        changed = false
473    end
474    --
475    target.writingmode = writingmode == "vertical" and "vertical" or "horizontal"
476    --
477    target.postprocessors = tfmdata.postprocessors
478    --
479    local targetslant        = (parameters.slant        or parameters[1] or 0) * factors.pt -- per point
480    local targetspace        = (parameters.space        or parameters[2] or 0) * hdelta
481    local targetspacestretch = (parameters.spacestretch or parameters[3] or 0) * hdelta
482    local targetspaceshrink  = (parameters.spaceshrink  or parameters[4] or 0) * hdelta
483    local targetxheight      = (parameters.xheight      or parameters[5] or 0) * vdelta
484    local targetquad         = (parameters.quad         or parameters[6] or 0) * hdelta
485    local targetextraspace   = (parameters.extraspace   or parameters[7] or 0) * hdelta
486    --
487    targetparameters.expansion    = parameters.expansion
488    targetparameters.protrusion   = parameters.protrusion
489    --
490    targetparameters.slant        = targetslant -- slantperpoint
491    targetparameters.space        = targetspace
492    targetparameters.spacestretch = targetspacestretch
493    targetparameters.spaceshrink  = targetspaceshrink
494    targetparameters.xheight      = targetxheight
495    targetparameters.quad         = targetquad
496    targetparameters.extraspace   = targetextraspace
497    --
498    local hshift = parameters.hshift
499    if hshift then
500        targetparameters.hshift = delta * hshift
501    end
502    local vshift = parameters.vshift
503    if vshift then
504        targetparameters.vshift = delta * vshift
505    end
506    --
507    local ascender  = parameters.ascender  if ascender  then targetparameters.ascender  = vdelta * ascender  end
508    local descender = parameters.descender if descender then targetparameters.descender = vdelta * descender end
509    local capheight = parameters.capheight if capheight then targetparameters.capheight = vdelta * capheight end
510    local ascent    = parameters.ascent    if ascent    then targetparameters.ascent    = vdelta * ascent    end
511    local descent   = parameters.descent   if descent   then targetparameters.descent   = vdelta * descent   end
512    --
513    constructors.enhanceparameters(targetparameters) -- official copies for us, now virtual
514    --
515    local scaledwidth  = defaultwidth  * hdelta
516    local scaledheight = defaultheight * vdelta
517    local scaleddepth  = defaultdepth  * vdelta
518    --
519    local textcontrol = properties.textcontrol or 0
520    if targetproperties.mode == "base" then
521        textcontrol = textcontrol | base_ligaturing | base_kerning
522        --
523        -- the only drawback is when we add some butthat doesn't happen in base mode so
524        -- we need to test this as there can be a little gain
525        --
526     -- if haskerns then
527     --     textcontrol = textcontrol | base_kerning
528     -- end
529     -- if hasligatures then
530     --     textcontrol = textcontrol | base_ligaturing
531     -- end
532    elseif targetproperties.mode == "none" then
533        textcontrol = textcontrol | none_protected -- or just assign
534    end
535    if hasitalics then
536        textcontrol = textcontrol | has_italics
537    end
538    if useditalicangle ~= 0 or usedslant ~= 0 then
539        textcontrol = textcontrol | auto_italics
540    end
541    targetproperties.textcontrol = textcontrol
542    target.textcontrol = textcontrol
543    --
544    local hasmath = (properties.hasmath or next(mathparameters)) and true
545    --
546    if hasmath then
547        constructors.assignmathparameters(target,tfmdata) -- does scaling and whatever is needed
548
549        properties.hasmath   = true  -- to be sure
550        target.nomath        = false -- hm
551        target.MathConstants = target.mathparameters
552
553        local compactmath    = properties.compactmath
554
555        targetproperties.compactmath = compactmath
556        target.compactmath           = compactmath
557
558        local textscale         = parameters.textscale         -- 1000
559        local scriptscale       = parameters.scriptscale       --  700
560        local scriptscriptscale = parameters.scriptscriptscale --  500
561
562        parameters.textscale         = textscale
563        parameters.scriptscale       = scriptscale
564        parameters.scriptscriptscale = scriptscriptscale
565
566        target.textscale         = textscale
567        target.scriptscale       = scriptscale
568        target.scriptscriptscale = scriptscriptscale
569
570        targetparameters.textscale         = textscale
571        targetparameters.scriptscale       = scriptscale
572        targetparameters.scriptscriptscale = scriptscriptscale
573
574--         local scriptxscale       = parameters.scriptxscale
575--         local scriptyscale       = parameters.scriptyscale
576--         local scriptscriptxscale = parameters.scriptscriptxscale
577--         local scriptscriptyscale = parameters.scriptscriptyscale
578
579--         targetparameters.scriptxscale       = scriptxscale
580--         targetparameters.scriptyscale       = scriptyscale
581--         targetparameters.scriptscriptxscale = scriptscriptxscale
582--         targetparameters.scriptscriptyscale = scriptscriptyscale
583
584--         target.scriptxscale       = scriptxscale
585--         target.scriptyscale       = scriptyscale
586--         target.scriptscriptxscale = scriptscriptxscale
587--         target.scriptscriptyscale = scriptscriptyscale
588
589        local mathcontrol = properties.mathcontrol
590        targetproperties.mathcontrol = mathcontrol
591        target.mathcontrol = mathcontrol
592    else
593        properties.hasmath    = false
594        target.nomath         = true
595        target.mathparameters = nil -- nop
596    end
597    --
598    if trace_defining then
599        report_defining("defining tfm, name %a, fullname %a, filename %a, %spsname %a, hscale %a, vscale %a, math %a, italics %a",
600            name,fullname,filename,psfixed and "(fixed) " or "",psname,hdelta,vdelta,
601            hasmath and "enabled" or "disabled",hasitalics and "enabled" or "disabled")
602    end
603    --
604    constructors.beforecopyingcharacters(target,tfmdata)
605    --
606    local sharedkerns = { }
607    --
608    -- we can have a dumb mode (basemode without math etc) that skips most
609    --
610    for unicode, character in next, characters do
611        local chr, description, index
612        -- todo: add description entry to char entry instead of index ... saves elsewhere too
613        if changed then
614            local c = changed[unicode]
615            if c and c ~= unicode then
616                local cc = changed[c]
617                if cc then
618                    while cc do
619                        c = cc
620                        cc = changed[c]
621                    end
622                end
623                -- check not needed:
624                if c then
625                    description = descriptions[c] or descriptions[unicode] or character
626                    character   = characters[c] or character
627                    index       = description.index or c
628                else
629                    description = descriptions[unicode] or character
630                    index       = description.index or unicode
631                end
632            else
633                description = descriptions[unicode] or character
634                index       = description.index or unicode
635            end
636        else
637            description = descriptions[unicode] or character
638            index       = description.index or unicode
639        end
640        local width     = description.width
641        local height    = description.height
642        local depth     = description.depth
643        local isunicode = description.unicode
644        if width  then width  = hdelta*width  else width  = scaledwidth  end
645        if height then height = vdelta*height else height = scaledheight end
646    --  if depth  then depth  = vdelta*depth  else depth  = scaleddepth  end
647        if depth and depth ~= 0 then
648            depth = vdelta*depth
649            if isunicode then
650                chr = {
651                    index   = index,
652                    height  = height,
653                    depth   = depth,
654                    width   = width,
655                    unicode = isunicode,
656                }
657            else
658                chr = {
659                    index  = index,
660                    height = height,
661                    depth  = depth,
662                    width  = width,
663                }
664            end
665        else
666            if isunicode then
667                chr = {
668                    index   = index,
669                    height  = height,
670                    width   = width,
671                    unicode = isunicode,
672                }
673            else
674                chr = {
675                    index  = index,
676                    height = height,
677                    width  = width,
678                }
679            end
680        end
681        local bb = description.boundingbox
682        if bb then
683            if bb[2] > 0 then
684                chr.tag = abovebaseline_tag
685            elseif bb[4] < 0 then
686                chr.tag = belowbaseline_tag
687            end
688        end
689        if hasquality then
690            -- we could move these calculations elsewhere (saves calculations)
691            local ve = character.expansion
692            if ve then
693                chr.expansion = ve*1000 -- expansionfactor, hm, can happen elsewhere
694            end
695            local vc = character.compression
696            if vc then
697                chr.compression = vc*1000 -- expansionfactor, hm, can happen elsewhere
698            end
699            local vl = character.leftprotrusion
700            if vl then
701                chr.leftprotrusion = width*vl
702            end
703            local vr = character.rightprotrusion
704            if vr then
705                chr.rightprotrusion = width*vr
706            end
707        end
708        --
709        if hasmath then
710            local nxt = character.next
711            if nxt then
712                chr.next = nxt
713            end
714            local parts = character.parts
715            if parts then
716                local orientation = character.partsorientation or "vertical"
717                chr.parts = scaleparts(parts,orientation == "horizontal" and hdelta or vdelta)
718                chr.partsorientation = orientation
719            end
720            local vi = character.partsitalic
721            if vi and vi ~= 0 then
722                chr.partsitalic = vi*hdelta
723            end
724            local va = character.topanchor
725            if va and va ~= 0 then
726             -- chr.topanchor = va*vdelta
727                chr.topanchor = va*hdelta
728            end
729            va = character.bottomanchor
730            if va and va ~= 0 then
731             -- chr.bottomanchor = va*vdelta
732                chr.bottomanchor = va*hdelta
733            end
734            --
735            local mk = character.mathkerns
736            if mk then
737                local tr = mk.topright
738                local tl = mk.topleft
739                local br = mk.bottomright
740                local bl = mk.bottomleft
741                chr.mathkerns = {
742                    topright    = tr and mathkerns(tr,vdelta) or nil,
743                    topleft     = tl and mathkerns(tl,vdelta) or nil,
744                    bottomright = br and mathkerns(br,vdelta) or nil,
745                    bottomleft  = bl and mathkerns(bl,vdelta) or nil,
746                }
747            end
748            --
749            if hasitalics then
750                local vi = character.italic
751                if vi and vi ~= 0 then
752                    chr.italic = vi*hdelta
753                end
754            end
755            --
756            -- These can never happen here as these come from tweaks but I need to check it:
757            --
758         -- local vo = character.topovershoot
759         -- if vo and vo ~= 0 then
760         --     chr.topovershoot = vo*hdelta
761         -- end
762         -- local il = character.innerlocation
763         -- if il then
764         --     chr.innerlocation = il
765         --     chr.innerxoffset  = (character.innerxoffset or 0) * hdelta
766         --     chr.inneryoffset  = (character.inneryoffset or 0) * vdelta
767         -- end
768            --
769         -- if character.extensible then
770         --     chr.extensible = true -- stretch fixed width accent
771         -- end
772         -- --
773         -- local k = character.topleft      if k and k ~= 0 then chr.topleft      = k*hdelta end
774         -- local k = character.topright     if k and k ~= 0 then chr.topright     = k*hdelta end
775         -- local k = character.bottomleft   if k and k ~= 0 then chr.bottomleft   = k*hdelta end
776         -- local k = character.bottomright  if k and k ~= 0 then chr.bottomright  = k*hdelta end
777         -- --
778         -- local m = character.leftmargin   if m and m ~= 0 then chr.leftmargin   = m*hdelta end
779         -- local m = character.rightmargin  if m and m ~= 0 then chr.rightmargin  = m*hdelta end
780         -- local m = character.topmargin    if m and m ~= 0 then chr.topmargin    = m*hdelta end
781         -- local m = character.bottommargin if m and m ~= 0 then chr.bottommargin = m*hdelta end
782            --
783            local sm = character.smaller
784            if sm then
785                chr.smaller = sm
786            end
787         -- local mi = character.mirror
788         -- if mi then
789         --     chr.mirror = mi
790         -- end
791            local fa = character.flataccent -- set here?
792            if fa then
793                chr.flataccent = fa
794            end
795        elseif hasitalics then -- unlikely
796            local vi = character.italic
797            if vi and vi ~= 0 then
798                chr.italic = vi*hdelta
799            end
800        end
801        if haskerns then
802            local vk = character.kerns
803            if vk then
804                local s = sharedkerns[vk]
805                if not s then
806                    s = { }
807                    for k,v in next, vk do s[k] = v*hdelta end
808                    sharedkerns[vk] = s
809                end
810                chr.kerns = s
811            end
812        end
813        if hasligatures then
814            local vl = character.ligatures
815            if vl then
816                if true then
817                    chr.ligatures = vl -- shared
818                else
819                    local tt = { }
820                    for i, l in next, vl do
821                        tt[i] = l
822                    end
823                    chr.ligatures = tt
824                end
825            end
826        end
827        -- only in old school fonts (otherwise we set commands later in context)
828        local vc = character.commands
829        if vc then
830            chr.commands = scalecommands(vc,hdelta,vdelta)
831        end
832        local cb = character.callback
833        if cb then
834            chr.callback = cb
835        end
836        -- we assume that these are done in a manipulator afterwards
837        -- as it makes no sense otherwise
838     -- local vx = character.xoffset
839     -- if vx then
840     --     chr.xoffset = vx*hdelta
841     -- end
842     -- local vy = character.yoffset
843     -- if vy then
844     --     chr.yoffset = vy*vdelta
845     -- end
846     -- local va = character.advance
847     -- if va then
848     --     chr.advance = va*vdelta
849     -- end
850        targetcharacters[unicode] = chr
851    end
852    --
853    properties.setitalics = hasitalics -- for postprocessing
854    --
855 -- if hasmath then properties.mode = "none" end -- possible optimization but it needs testing
856    --
857    constructors.aftercopyingcharacters(target,tfmdata)
858    --
859    constructors.beforepassingfonttotex(target,tfmdata) -- mayeb move into aftercopyingcharacters
860    --
861    constructors.trytosharefont(target,tfmdata)
862    --
863    -- catch inconsistencies (for now)
864    --
865    local vfonts = target.fonts
866    if not vfonts or #vfonts == 0 then
867        target.fonts = { { id = 0 } }
868    end
869    --
870    if trace_defining then
871        report_defining("font %a, size %N, delta %N",name,scaledpoints,delta)
872    end
873    --
874    return target
875end
876
877function constructors.finalize(tfmdata)
878    if tfmdata.properties and tfmdata.properties.finalized then
879        return
880    end
881    --
882    if not tfmdata.characters then
883        return nil
884    end
885    --
886    if not tfmdata.goodies then
887        tfmdata.goodies = { }
888    end
889    --
890    local parameters = tfmdata.parameters
891    local properties = tfmdata.properties
892    if not parameters then
893        return nil
894    end
895    --
896 -- if not parameters.expansion then
897 --     parameters.expansion = {
898 --         stretch = tfmdata.stretch or 0,
899 --         shrink  = tfmdata.shrink  or 0,
900 --         step    = tfmdata.step    or 0,
901 --     }
902 -- end
903    --
904    if not parameters.size then
905        parameters.size = tfmdata.size
906    end
907    --
908    -- In the backend we need to know what effects have been applied. The font
909    -- has been scaled contrary to the effects that we apply per glyph as these
910    -- are delayed. We can actually also use the font specific effects there.
911    --
912    local effect = properties and properties.effect
913    if effect then
914        parameters.mode          = effect.mode
915        parameters.width         = effect.width
916        parameters.slantfactor   = effect.slant
917        parameters.extendfactor  = effect.extend
918        parameters.squeezefactor = effect.squeeze
919    else
920        parameters.mode          = 0
921        parameters.width         = 0
922        parameters.slantfactor   = 0
923        parameters.extendfactor  = 1
924        parameters.squeezefactor = 1
925    end
926    local designsize = parameters.designsize
927    if designsize then
928        parameters.minsize = tfmdata.minsize or designsize
929        parameters.maxsize = tfmdata.maxsize or designsize
930    else
931        designsize = factors.pt * 10
932        parameters.designsize = designsize
933        parameters.minsize    = designsize
934        parameters.maxsize    = designsize
935    end
936    parameters.minsize = tfmdata.minsize or parameters.designsize
937    parameters.maxsize = tfmdata.maxsize or parameters.designsize
938    --
939    if not parameters.units then
940        parameters.units = tfmdata.units or 1000
941    end
942    --
943    if not tfmdata.descriptions then
944        local descriptions = { } -- yes or no
945        setmetatableindex(descriptions, function(t,k) local v = { } t[k] = v return v end)
946        tfmdata.descriptions = descriptions
947    end
948    --
949    local properties = tfmdata.properties
950    if not properties then
951        properties = { }
952        tfmdata.properties = properties
953    end
954    --
955    properties.fontname      = properties.fontname or tfmdata.fontname
956    properties.filename      = properties.filename or tfmdata.filename
957    properties.fullname      = properties.fullname or tfmdata.fullname
958    properties.name          = properties.name     or tfmdata.name
959    properties.psname        = properties.psname   or tfmdata.psname
960    --
961    properties.subfont       = tfmdata.subfont       or nil
962    properties.cidinfo       = tfmdata.cidinfo       or nil
963    properties.format        = tfmdata.format        or "type1"
964    properties.writingmode   = tfmdata.writingmode   or "horizontal"
965    properties.usedbitmap    = tfmdata.usedbitmap
966    --
967    if not tfmdata.resources then
968        tfmdata.resources = { }
969    end
970    if not tfmdata.shared then
971        tfmdata.shared = { }
972    end
973    --
974    -- tfmdata.fonts
975    -- tfmdata.unscaled
976    --
977    if not properties.hasmath then
978        properties.hasmath = not tfmdata.nomath
979    end
980    --
981    tfmdata.MathConstants    = nil
982    tfmdata.postprocessors   = nil
983    --
984    tfmdata.fontname         = nil
985    tfmdata.filename         = nil
986    tfmdata.fullname         = nil
987    tfmdata.name             = nil -- most tricky part
988    tfmdata.psname           = nil
989    --
990    tfmdata.subfont          = nil
991    tfmdata.cidinfo          = nil
992    tfmdata.format           = nil
993    tfmdata.nomath           = nil
994    tfmdata.designsize       = nil
995    --
996    tfmdata.size             = nil
997 -- tfmdata.stretch          = nil
998 -- tfmdata.shrink           = nil
999 -- tfmdata.step             = nil
1000    tfmdata.slant            = nil
1001    tfmdata.extend           = nil
1002    tfmdata.squeeze          = nil
1003    tfmdata.mode             = nil
1004    tfmdata.width            = nil
1005    tfmdata.units            = nil
1006    --
1007    tfmdata.cache            = nil
1008    --
1009    properties.finalized     = true
1010    --
1011    return tfmdata
1012end
1013
1014-- A unique hash value is generated by:
1015
1016local hashmethods        = { }
1017constructors.hashmethods = hashmethods
1018
1019function constructors.hashfeatures(specification) -- will be overloaded
1020    local features = specification.features
1021    if features then
1022        local t, n = { }, 0
1023        for category, list in sortedhash(features) do
1024            if next(list) then
1025                local hasher = hashmethods[category]
1026                if hasher then
1027                    local hash = hasher(list)
1028                    if hash then
1029                        n = n + 1
1030                        t[n] = category .. ":" .. hash
1031                    end
1032                end
1033            end
1034        end
1035        if n > 0 then
1036            return concat(t," & ")
1037        end
1038    end
1039    return "unknown"
1040end
1041
1042hashmethods.normal = function(list)
1043    local s = { }
1044    local n = 0
1045    for k, v in next, list do
1046        if not k then
1047            -- no need to add to hash
1048        elseif k == "number" or k == "features" then
1049            -- no need to add to hash (maybe we need a skip list)
1050        else
1051            n = n + 1
1052            if type(v) == "table" then
1053                -- table.sequenced
1054                local t = { }
1055                local m = 0
1056                for k, v in next, v do
1057                    m = m + 1
1058                    t[m] = k .. '=' .. tostring(v)
1059                end
1060                sort(t)
1061                s[n] = k .. '={' .. concat(t,",") .. "}"
1062            else
1063                s[n] = k .. '=' .. tostring(v)
1064            end
1065        end
1066    end
1067    if n > 0 then
1068        sort(s)
1069        return concat(s,"+")
1070    end
1071end
1072
1073-- In principle we can share tfm tables when we are in need for a font, but then we
1074-- need to define a font switch as an id/attr switch which is no fun, so in that
1075-- case users can best use dynamic features ... so, we will not use that speedup.
1076-- Okay, when we get rid of base mode we can optimize even further by sharing, but
1077-- then we loose our testcases for LuaTeX.
1078
1079function constructors.hashinstance(specification,force)
1080    -- implemented in font-ctx.lmt
1081end
1082
1083function constructors.setname(tfmdata,specification)
1084    -- dummy (still called in generic font-otl)
1085end
1086
1087function constructors.checkedfilename(data)
1088    local foundfilename = data.foundfilename
1089    if not foundfilename then
1090        local askedfilename = data.filename or ""
1091        if askedfilename ~= "" then
1092            askedfilename = resolvers.resolve(askedfilename) -- no shortcut
1093            foundfilename = resolvers.findbinfile(askedfilename,"") or ""
1094            if foundfilename == "" then
1095                report_defining("source file %a is not found",askedfilename)
1096                foundfilename = resolvers.findbinfile(file.basename(askedfilename),"") or ""
1097                if foundfilename ~= "" then
1098                    report_defining("using source file %a due to cache mismatch",foundfilename)
1099                end
1100            end
1101        end
1102        data.foundfilename = foundfilename
1103    end
1104    return foundfilename
1105end
1106
1107local formats = allocate()
1108fonts.formats = formats
1109
1110setmetatableindex(formats, function(t,k)
1111    local l = lower(k)
1112    if rawget(t,k) then
1113        t[k] = l
1114        return l
1115    end
1116    return rawget(t,file.suffix(l))
1117end)
1118
1119do
1120
1121    local function setindeed(mode,source,target,group,name,position)
1122        local action = source[mode]
1123        if not action then
1124            return
1125        end
1126        local t = target[mode]
1127        if not t then
1128            report_defining("fatal error in setting feature %a, group %a, mode %a",name,group,mode)
1129            os.exit()
1130        elseif position then
1131            -- todo: remove existing
1132            insert(t, position, { name = name, action = action })
1133        else
1134            for i=1,#t do
1135                local ti = t[i]
1136                if ti.name == name then
1137                    ti.action = action
1138                    return
1139                end
1140            end
1141            insert(t, { name = name, action = action })
1142        end
1143    end
1144
1145    local function set(group,name,target,source)
1146        target = target[group]
1147        if not target then
1148            report_defining("fatal target error in setting feature %a, group %a",name,group)
1149            os.exit()
1150        end
1151        local source = source[group]
1152        if not source then
1153            report_defining("fatal source error in setting feature %a, group %a",name,group)
1154            os.exit()
1155        end
1156        local position = source.position
1157        setindeed("node",source,target,group,name,position)
1158        setindeed("base",source,target,group,name,position)
1159        setindeed("none",source,target,group,name,position)
1160        setindeed("plug",source,target,group,name,position)
1161    end
1162
1163    local function register(where,specification)
1164        local name = specification.name
1165        if name and name ~= "" then
1166            local default      = specification.default
1167            local description  = specification.description
1168            local initializers = specification.initializers
1169            local processors   = specification.processors
1170            local manipulators = specification.manipulators
1171            local modechecker  = specification.modechecker
1172            if default then
1173                where.defaults[name] = default
1174            end
1175            if description and description ~= "" then
1176                where.descriptions[name] = description
1177            end
1178            if initializers then
1179                set('initializers',name,where,specification)
1180            end
1181            if processors then
1182                set('processors',  name,where,specification)
1183            end
1184            if manipulators then
1185                set('manipulators',name,where,specification)
1186            end
1187            if modechecker then
1188               where.modechecker = modechecker
1189            end
1190        end
1191    end
1192
1193    constructors.registerfeature = register
1194
1195    function constructors.getfeatureaction(what,where,mode,name)
1196        what = handlers[what].features
1197        if what then
1198            where = what[where]
1199            if where then
1200                mode = where[mode]
1201                if mode then
1202                    for i=1,#mode do
1203                        local m = mode[i]
1204                        if m.name == name then
1205                            return m.action
1206                        end
1207                    end
1208                end
1209            end
1210        end
1211    end
1212
1213    local newfeatures        = { }
1214    constructors.newfeatures = newfeatures -- downward compatible
1215    constructors.features    = newfeatures
1216
1217    local function setnewfeatures(what)
1218        local handler  = handlers[what]
1219        local features = handler.features
1220        if not features then
1221            local tables     = handler.tables     -- can be preloaded
1222            local statistics = handler.statistics -- can be preloaded
1223            features = allocate {
1224                defaults     = { },
1225                descriptions = tables and tables.features or { },
1226                used         = statistics and statistics.usedfeatures or { },
1227                initializers = { base = { }, node = { }, none = { }, plug = { } },
1228                processors   = { base = { }, node = { }, none = { }, plug = { } },
1229                manipulators = { base = { }, node = { }, none = { }, plug = { } },
1230                finalizers   = { base = { }, node = { }, none = { }, plug = { } },
1231            }
1232            features.register = function(specification) return register(features,specification) end
1233            handler.features = features -- will also become hidden
1234        end
1235        return features
1236    end
1237
1238    setmetatable(newfeatures, {
1239        __call  = function(t,k) local v = t[k] return v end,
1240        __index = function(t,k) local v = setnewfeatures(k) t[k] = v return v end,
1241    })
1242
1243end
1244
1245do
1246
1247    local newhandler        = { }
1248    constructors.handlers   = newhandler -- downward compatible
1249    constructors.newhandler = newhandler
1250
1251    local function setnewhandler(what) -- could be a metatable newindex
1252        local handler = handlers[what]
1253        if not handler then
1254            handler = { }
1255            handlers[what] = handler
1256        end
1257        return handler
1258    end
1259
1260    setmetatable(newhandler, {
1261        __call  = function(t,k) local v = t[k] return v end,
1262        __index = function(t,k) local v = setnewhandler(k) t[k] = v return v end,
1263    })
1264
1265end
1266
1267do
1268    -- a pitty that we need to be generic as we have nicer mechanisms for this ...
1269
1270    local newenhancer        = { }
1271    constructors.enhancers   = newenhancer
1272    constructors.newenhancer = newenhancer
1273
1274    local function setnewenhancer(format)
1275
1276        local handler   = handlers[format]
1277        local enhancers = handler.enhancers
1278
1279        if not enhancers then
1280
1281            local actions  = allocate() -- no need to allocate thee
1282            local before   = allocate()
1283            local after    = allocate()
1284            local order    = allocate()
1285            local known    = { }
1286            local nofsteps = 0
1287            local patches  = { before = before, after = after }
1288
1289            local trace   = false
1290            local report  = logs.reporter("fonts",format .. " enhancing")
1291
1292            trackers.register(format .. ".loading", function(v) trace = v end)
1293
1294            local function enhance(name,data,filename,raw)
1295                local enhancer = actions[name]
1296                if enhancer then
1297                    if trace then
1298                        report("apply enhancement %a to file %a",name,filename)
1299                        ioflush()
1300                    end
1301                    enhancer(data,filename,raw)
1302                else
1303                    -- no message as we can have private ones
1304                end
1305            end
1306
1307            local function apply(data,filename,raw)
1308                local basename = file.basename(lower(filename))
1309                if trace then
1310                    report("%s enhancing file %a","start",filename)
1311                end
1312                ioflush() -- we want instant messages
1313                for e=1,nofsteps do
1314                    local enhancer = order[e]
1315                    local b = before[enhancer]
1316                    if b then
1317                        for pattern, action in next, b do
1318                            if find(basename,pattern) then
1319                                action(data,filename,raw)
1320                            end
1321                        end
1322                    end
1323                    enhance(enhancer,data,filename,raw) -- we have one installed: check extra features
1324                    local a = after[enhancer]
1325                    if a then
1326                        for pattern, action in next, a do
1327                            if find(basename,pattern) then
1328                                action(data,filename,raw)
1329                            end
1330                        end
1331                    end
1332                    ioflush() -- we want instant messages
1333                end
1334                if trace then
1335                    report("%s enhancing file %a","stop",filename)
1336                end
1337                ioflush() -- we want instant messages
1338            end
1339
1340            local function register(what,action)
1341                if action then
1342                    if actions[what] then
1343                        -- overloading, e.g."check extra features"
1344                    else
1345                        nofsteps        = nofsteps + 1
1346                        order[nofsteps] = what
1347                        known[what]     = nofsteps
1348                    end
1349                    actions[what] = action
1350                else
1351                    report("bad enhancer %a",what)
1352                end
1353            end
1354
1355            -- We used to have a lot of enhancers but no longer with the new font loader. The order of enhancers
1356            -- is the order of definition. The before/after patches are there for old times sake and happen
1357            -- before or after a (named) enhancer. An example of a set enhancer is "check extra features" so one
1358            -- one set patches before or after that is applied. Unknown enhancers are auto-registered. It's a bit
1359            -- messy but we keep it for compatibility reasons.
1360            --
1361            -- fonts.handlers.otf.enhancers.patches.register("before","some patches","somefont",function(data,filename)
1362            --     print("!!!!!!!") -- before | after
1363            -- end)
1364            --
1365            -- fonts.handlers.otf.enhancers.register("more patches",function(data,filename)
1366            --     print("???????") -- enhance
1367            -- end)
1368
1369            local function patch(what,where,pattern,action)
1370                local pw = patches[what]
1371                if pw then
1372                    local ww = pw[where]
1373                    if ww then
1374                        ww[pattern] = action
1375                    else
1376                        pw[where] = { [pattern] = action }
1377                        if not known[where] then
1378                            nofsteps        = nofsteps + 1
1379                            order[nofsteps] = where
1380                            known[where]    = nofsteps
1381                        end
1382                    end
1383                end
1384            end
1385
1386            enhancers = {
1387                register = register,
1388                apply    = apply,
1389                patch    = patch,
1390                report   = report,
1391                patches  = {
1392                    register = patch,
1393                    report   = report,
1394                }, -- for old times sake
1395            }
1396
1397            handler.enhancers = enhancers
1398        end
1399        return enhancers
1400    end
1401
1402    setmetatable(newenhancer, {
1403        __call  = function(t,k) local v = t[k] return v end,
1404        __index = function(t,k) local v = setnewenhancer(k) t[k] = v return v end,
1405    })
1406
1407end
1408
1409-- We need to check for default features. For this we provide a helper function.
1410
1411function constructors.checkedfeatures(what,features)
1412    local defaults = handlers[what].features.defaults
1413    if features and next(features) then
1414        features = fastcopy(features) -- can be inherited (mt) but then no loops possible
1415        for key, value in next, defaults do
1416            if features[key] == nil then
1417                features[key] = value
1418            end
1419        end
1420        return features
1421    else
1422        return fastcopy(defaults) -- we can change features in place
1423    end
1424end
1425
1426-- before scaling
1427
1428function constructors.initializefeatures(what,tfmdata,features,trace,report)
1429    if features and next(features) then
1430        local properties       = tfmdata.properties or { } -- brrr
1431        local whathandler      = handlers[what]
1432        local whatfeatures     = whathandler.features
1433        local whatmodechecker  = whatfeatures.modechecker
1434        -- properties.mode can be enforces (for instance in font-otd)
1435        local mode             = properties.mode or (whatmodechecker and whatmodechecker(tfmdata,features,features.mode)) or features.mode or "base"
1436        properties.mode        = mode -- also status
1437        features.mode          = mode -- both properties.mode or features.mode can be changed
1438        --
1439        local done             = { }
1440        while true do
1441            local redo = false
1442            local initializers = whatfeatures.initializers[mode]
1443            if initializers then
1444                for i=1,#initializers do
1445                    local step = initializers[i]
1446                    local feature = step.name
1447-- we could intercept mode here .. needs a rewrite of this whole loop then but it's cleaner that way
1448                    local value = features[feature]
1449                    if not value then
1450                        -- disabled
1451                    elseif done[feature] then
1452                        -- already done
1453                    else
1454                        local action = step.action
1455                        if trace then
1456                            report("initializing feature %a to %a for mode %a for font %a",feature,
1457                                value,mode,tfmdata.properties.fullname)
1458                        end
1459                        action(tfmdata,value,features) -- can set mode (e.g. goodies) so it can trigger a restart
1460                        if mode ~= properties.mode or mode ~= features.mode then
1461                            if whatmodechecker then
1462                                properties.mode = whatmodechecker(tfmdata,features,properties.mode) -- force checking
1463                                features.mode   = properties.mode
1464                            end
1465                            if mode ~= properties.mode then
1466                                mode = properties.mode
1467                                redo = true
1468                            end
1469                        end
1470                        done[feature] = true
1471                    end
1472                    if redo then
1473                        break
1474                    end
1475                end
1476                if not redo then
1477                    break
1478                end
1479            else
1480                break
1481            end
1482        end
1483        properties.mode = mode -- to be sure
1484        return true
1485    else
1486        return false
1487    end
1488end
1489
1490-- while typesetting
1491
1492function constructors.collectprocessors(what,tfmdata,features,trace,report)
1493    local processes    = { }
1494    local nofprocesses = 0
1495    if features and next(features) then
1496        local properties     = tfmdata.properties
1497        local whathandler    = handlers[what]
1498        local whatfeatures   = whathandler.features
1499        local whatprocessors = whatfeatures.processors
1500        local mode           = properties.mode
1501        local processors     = whatprocessors[mode]
1502        if processors then
1503            for i=1,#processors do
1504                local step = processors[i]
1505                local feature = step.name
1506                if features[feature] then
1507                    local action = step.action
1508                    if trace then
1509                        report("installing feature processor %a for mode %a for font %a",feature,mode,tfmdata.properties.fullname)
1510                    end
1511                    if action then
1512                        nofprocesses = nofprocesses + 1
1513                        processes[nofprocesses] = action
1514                    end
1515                end
1516            end
1517        elseif trace then
1518            report("no feature processors for mode %a for font %a",mode,properties.fullname)
1519        end
1520    end
1521    return processes
1522end
1523
1524-- after scaling
1525
1526local function apply(key,what,tfmdata,features,trace,report)
1527    if features and next(features) then
1528        local properties   = tfmdata.properties
1529        local whathandler  = handlers[what]
1530        local whatfeatures = whathandler.features
1531        local whatactions  = whatfeatures[key]
1532        local mode         = properties.mode
1533        local actions      = whatactions[mode]
1534        if actions then
1535            for i=1,#actions do
1536                local step    = actions[i]
1537                local feature = step.name
1538                local value   = features[feature]
1539                if value then
1540                    local action = step.action
1541                    if trace then
1542                        report("applying feature %s %a for mode %a for font %a",key,feature,mode,properties.fullname)
1543                    end
1544                    if action then
1545                        action(tfmdata,feature,value)
1546                    end
1547                end
1548            end
1549        end
1550    end
1551end
1552
1553function constructors.applymanipulators(what,tfmdata,features,trace,report)
1554    if features and next(features) then
1555        apply("manipulators",what,tfmdata,features,trace,report)
1556    end
1557end
1558
1559function constructors.applyfinalizers(what,tfmdata,features,trace,report)
1560    if features and next(features) then
1561        apply("finalizers",what,tfmdata,features,trace,report)
1562    end
1563end
1564
1565function constructors.addcoreunicodes(unicodes) -- maybe make this a metatable if used at all
1566    if not unicodes then
1567        unicodes = { }
1568    end
1569    unicodes.space  = 0x0020
1570    unicodes.hyphen = 0x002D
1571    unicodes.zwj    = 0x200D
1572    unicodes.zwnj   = 0x200C
1573    return unicodes
1574end
1575