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