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