summaryrefslogtreecommitdiff
path: root/otfl-font-con.lua
diff options
context:
space:
mode:
Diffstat (limited to 'otfl-font-con.lua')
-rw-r--r--otfl-font-con.lua1157
1 files changed, 1157 insertions, 0 deletions
diff --git a/otfl-font-con.lua b/otfl-font-con.lua
new file mode 100644
index 0000000..f6b415b
--- /dev/null
+++ b/otfl-font-con.lua
@@ -0,0 +1,1157 @@
+if not modules then modules = { } end modules ['font-con'] = {
+ version = 1.001,
+ comment = "companion to font-ini.mkiv",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+
+local utf = unicode.utf8
+
+local next, tostring, rawget = next, tostring, rawget
+local format, match, lower, gsub = string.format, string.match, string.lower, string.gsub
+local utfbyte = utf.byte
+local sort, insert, concat, sortedkeys, serialize, fastcopy = table.sort, table.insert, table.concat, table.sortedkeys, table.serialize, table.fastcopy
+local derivetable = table.derive
+
+local trace_defining = false trackers.register("fonts.defining", function(v) trace_defining = v end)
+local trace_scaling = false trackers.register("fonts.scaling" , function(v) trace_scaling = v end)
+
+local report_defining = logs.reporter("fonts","defining")
+
+-- watch out: no negative depths and negative eights permitted in regular fonts
+
+--[[ldx--
+<p>Here we only implement a few helper functions.</p>
+--ldx]]--
+
+local fonts = fonts
+local constructors = { }
+fonts.constructors = constructors
+local handlers = { }
+fonts.handlers = handlers
+
+local specifiers = fonts.specifiers
+local contextsetups = specifiers.contextsetups
+local contextnumbers = specifiers.contextnumbers
+
+local allocate = utilities.storage.allocate
+local setmetatableindex = table.setmetatableindex
+
+-- will be directives
+
+constructors.dontembed = allocate()
+constructors.mathactions = { }
+constructors.autocleanup = true
+constructors.namemode = "fullpath" -- will be a function
+
+constructors.version = 1.01
+constructors.cache = containers.define("fonts", "constructors", constructors.version, false)
+
+constructors.privateoffset = 0xF0000 -- 0x10FFFF
+
+-- This might become an interface;
+
+local designsizes = allocate()
+constructors.designsizes = designsizes
+local loadedfonts = allocate()
+constructors.loadedfonts = loadedfonts
+
+--[[ldx--
+<p>We need to normalize the scale factor (in scaled points). This has to
+do with the fact that <l n='tex'/> uses a negative multiple of 1000 as
+a signal for a font scaled based on the design size.</p>
+--ldx]]--
+
+local factors = {
+ pt = 65536.0,
+ bp = 65781.8,
+}
+
+function constructors.setfactor(f)
+ constructors.factor = factors[f or 'pt'] or factors.pt
+end
+
+constructors.setfactor()
+
+function constructors.scaled(scaledpoints, designsize) -- handles designsize in sp as well
+ if scaledpoints < 0 then
+ if designsize then
+ local factor = constructors.factor
+ if designsize > factor then -- or just 1000 / when? mp?
+ return (- scaledpoints/1000) * designsize -- sp's
+ else
+ return (- scaledpoints/1000) * designsize * factor
+ end
+ else
+ return (- scaledpoints/1000) * 10 * factor
+ end
+ else
+ return scaledpoints
+ end
+end
+
+--[[ldx--
+<p>Beware, the boundingbox is passed as reference so we may not overwrite it
+in the process; numbers are of course copies. Here 65536 equals 1pt. (Due to
+excessive memory usage in CJK fonts, we no longer pass the boundingbox.)</p>
+--ldx]]--
+
+-- The scaler is only used for otf and afm and virtual fonts. If
+-- a virtual font has italic correction make sure to set the
+-- italic_correction flag. Some more flags will be added in
+-- the future.
+
+--[[ldx--
+<p>The reason why the scaler was originally split, is that for a while we experimented
+with a helper function. However, in practice the <l n='api'/> calls are too slow to
+make this profitable and the <l n='lua'/> based variant was just faster. A days
+wasted day but an experience richer.</p>
+--ldx]]--
+
+-- we can get rid of the tfm instance when we have fast access to the
+-- scaled character dimensions at the tex end, e.g. a fontobject.width
+-- actually we already have soem of that now as virtual keys in glyphs
+--
+-- flushing the kern and ligature tables from memory saves a lot (only
+-- base mode) but it complicates vf building where the new characters
+-- demand this data .. solution: functions that access them
+
+function constructors.cleanuptable(tfmdata)
+ if constructors.autocleanup and tfmdata.properties.virtualized then
+ for k, v in next, tfmdata.characters do
+ if v.commands then v.commands = nil end
+ -- if v.kerns then v.kerns = nil end
+ end
+ end
+end
+
+-- experimental, sharing kerns (unscaled and scaled) saves memory
+-- local sharedkerns, basekerns = constructors.check_base_kerns(tfmdata)
+-- loop over descriptions (afm and otf have descriptions, tfm not)
+-- there is no need (yet) to assign a value to chr.tonunicode
+
+-- constructors.prepare_base_kerns(tfmdata) -- optimalization
+
+-- we have target.name=metricfile and target.fullname=RealName and target.filename=diskfilename
+-- when collapsing fonts, luatex looks as both target.name and target.fullname as ttc files
+-- can have multiple subfonts
+
+function constructors.calculatescale(tfmdata,scaledpoints)
+ local parameters = tfmdata.parameters
+ if scaledpoints < 0 then
+ scaledpoints = (- scaledpoints/1000) * (tfmdata.designsize or parameters.designsize) -- already in sp
+ end
+ return scaledpoints, scaledpoints / (parameters.units or 1000) -- delta
+end
+
+function constructors.assignmathparameters(target,original) -- dumb version, not used in context
+ -- when a tfm file is loaded, it has already been scaled
+ -- and it never enters the scaled so this is otf only and
+ -- even then we do some extra in the context math plugins
+ local mathparameters = original.mathparameters
+ if mathparameters and next(mathparameters) then
+ local targetparameters = target.parameters
+ local targetproperties = target.properties
+ local targetmathparameters = { }
+ local factor = targetproperties.math_is_scaled and 1 or targetparameters.factor
+ for name, value in next, mathparameters do
+ if name == "RadicalDegreeBottomRaisePercent" then
+ targetmathparameters[name] = value
+ else
+ targetmathparameters[name] = value * factor
+ end
+ end
+ -- if not targetmathparameters.FractionDelimiterSize then
+ -- targetmathparameters.FractionDelimiterSize = 0
+ -- end
+ -- if not mathparameters.FractionDelimiterDisplayStyleSize then
+ -- targetmathparameters.FractionDelimiterDisplayStyleSize = 0
+ -- end
+ target.mathparameters = targetmathparameters
+ end
+end
+
+function constructors.scale(tfmdata,specification)
+ local target = { } -- the new table
+ --
+ if tonumber(specification) then
+ specification = { size = specification }
+ end
+ --
+ local scaledpoints = specification.size
+ local relativeid = specification.relativeid
+ --
+ local properties = tfmdata.properties or { }
+ local goodies = tfmdata.goodies or { }
+ local resources = tfmdata.resources or { }
+ local descriptions = tfmdata.descriptions or { } -- bad news if empty
+ local characters = tfmdata.characters or { } -- bad news if empty
+ local changed = tfmdata.changed or { } -- for base mode
+ local shared = tfmdata.shared or { }
+ local parameters = tfmdata.parameters or { }
+ local mathparameters = tfmdata.mathparameters or { }
+ --
+ local targetcharacters = { }
+ local targetdescriptions = derivetable(descriptions)
+ local targetparameters = derivetable(parameters)
+ local targetproperties = derivetable(properties)
+ local targetgoodies = goodies -- we need to loop so no metatable
+ target.characters = targetcharacters
+ target.descriptions = targetdescriptions
+ target.parameters = targetparameters
+ -- target.mathparameters = targetmathparameters -- happens elsewhere
+ target.properties = targetproperties
+ target.goodies = targetgoodies
+ target.shared = shared
+ target.resources = resources
+ target.unscaled = tfmdata -- the original unscaled one
+ --
+ -- specification.mathsize : 1=text 2=script 3=scriptscript
+ -- specification.textsize : natural (text)size
+ -- parameters.mathsize : 1=text 2=script 3=scriptscript >1000 enforced size (feature value other than yes)
+ --
+ local mathsize = tonumber(specification.mathsize) or 0
+ local textsize = tonumber(specification.textsize) or scaledpoints
+ local forcedsize = tonumber(parameters.mathsize ) or 0
+ local extrafactor = tonumber(specification.factor ) or 1
+ if (mathsize == 2 or forcedsize == 2) and parameters.scriptpercentage then
+ scaledpoints = parameters.scriptpercentage * textsize / 100
+ elseif (mathsize == 3 or forcedsize == 3) and parameters.scriptscriptpercentage then
+ scaledpoints = parameters.scriptscriptpercentage * textsize / 100
+ elseif forcedsize > 1000 then -- safeguard
+ scaledpoints = forcedsize
+ end
+ --
+ local tounicode = resources.tounicode
+ local defaultwidth = resources.defaultwidth or 0
+ local defaultheight = resources.defaultheight or 0
+ local defaultdepth = resources.defaultdepth or 0
+ local units = parameters.units or 1000
+ --
+ if target.fonts then
+ target.fonts = fastcopy(target.fonts) -- maybe we virtualize more afterwards
+ end
+ --
+ -- boundary keys are no longer needed as we now have a string 'right_boundary'
+ -- that can be used in relevant tables (kerns and ligatures) ... not that I ever
+ -- used them
+ --
+ -- boundarychar_label = 0, -- not needed
+ -- boundarychar = 65536, -- there is now a string 'right_boundary'
+ -- false_boundarychar = 65536, -- produces invalid tfm in luatex
+ --
+ targetproperties.language = properties.language or "dflt" -- inherited
+ targetproperties.script = properties.script or "dflt" -- inherited
+ targetproperties.mode = properties.mode or "base" -- inherited
+ --
+ local askedscaledpoints = scaledpoints
+ local scaledpoints, delta = constructors.calculatescale(tfmdata,scaledpoints) -- no shortcut, dan be redefined
+ --
+ local hdelta = delta
+ local vdelta = delta
+ --
+ target.designsize = parameters.designsize -- not really needed so it muight become obsolete
+ target.units_per_em = units -- just a trigger for the backend (does luatex use this? if not it will go)
+ --
+ local direction = properties.direction or tfmdata.direction or 0 -- pointless, as we don't use omf fonts at all
+ target.direction = direction
+ properties.direction = direction
+ --
+ target.size = scaledpoints
+ --
+ target.encodingbytes = properties.encodingbytes or 1
+ target.embedding = properties.embedding or "subset"
+ target.tounicode = 1
+ target.cidinfo = properties.cidinfo
+ target.format = properties.format
+ --
+ local fontname = properties.fontname or tfmdata.fontname -- for the moment we fall back on
+ local fullname = properties.fullname or tfmdata.fullname -- names in the tfmdata although
+ local filename = properties.filename or tfmdata.filename -- that is not the right place to
+ local psname = properties.psname or tfmdata.psname -- pass them
+ local name = properties.name or tfmdata.name
+ --
+ if not psname or psname == "" then
+ -- name used in pdf file as well as for selecting subfont in ttc/dfont
+ psname = fontname or (fullname and fonts.names.cleanname(fullname))
+ end
+ target.fontname = fontname
+ target.fullname = fullname
+ target.filename = filename
+ target.psname = psname
+ target.name = name
+ --
+ properties.fontname = fontname
+ properties.fullname = fullname
+ properties.filename = filename
+ properties.psname = psname
+ properties.name = name
+ -- expansion (hz)
+ local expansion = parameters.expansion
+ if expansion then
+ target.stretch = expansion.stretch
+ target.shrink = expansion.shrink
+ target.step = expansion.step
+ target.auto_expand = expansion.auto
+ end
+ -- protrusion
+ local protrusion = parameters.protrusion
+ if protrusion then
+ target.auto_protrude = protrusion.auto
+ end
+ -- widening
+ local extend_factor = parameters.extend_factor or 0
+ if extend_factor ~= 0 and extend_factor ~= 1 then
+ hdelta = hdelta * extend_factor
+ target.extend = extend_factor * 1000 -- extent ?
+ else
+ target.extend = 1000 -- extent ?
+ end
+ -- slanting
+ local slant_factor = parameters.slant_factor or 0
+ if slant_factor ~= 0 then
+ target.slant = slant_factor * 1000
+ else
+ target.slant = 0
+ end
+ --
+ targetparameters.factor = delta
+ targetparameters.hfactor = hdelta
+ targetparameters.vfactor = vdelta
+ targetparameters.size = scaledpoints
+ targetparameters.units = units
+ targetparameters.scaledpoints = askedscaledpoints
+ --
+ local isvirtual = properties.virtualized or tfmdata.type == "virtual"
+ local hasquality = target.auto_expand or target.auto_protrude
+ local hasitalic = properties.italic_correction
+ local autoitalic = properties.auto_italic_correction
+ local stackmath = not properties.no_stackmath
+ local nonames = properties.noglyphnames
+ local nodemode = properties.mode == "node"
+ --
+ if changed and not next(changed) then
+ changed = false
+ end
+ --
+ target.type = isvirtual and "virtual" or "real"
+ -- this will move to some subtable so that it is copied at once
+ target.postprocessors = tfmdata.postprocessors
+ --
+ local targetslant = (parameters.slant or parameters[1] or 0)
+ local targetspace = (parameters.space or parameters[2] or 0)*hdelta
+ local targetspace_stretch = (parameters.space_stretch or parameters[3] or 0)*hdelta
+ local targetspace_shrink = (parameters.space_shrink or parameters[4] or 0)*hdelta
+ local targetx_height = (parameters.x_height or parameters[5] or 0)*vdelta
+ local targetquad = (parameters.quad or parameters[6] or 0)*hdelta
+ local targetextra_space = (parameters.extra_space or parameters[7] or 0)*hdelta
+ --
+ targetparameters.slant = targetslant
+ targetparameters.space = targetspace
+ targetparameters.space_stretch = targetspace_stretch
+ targetparameters.space_shrink = targetspace_shrink
+ targetparameters.x_height = targetx_height
+ targetparameters.quad = targetquad
+ targetparameters.extra_space = targetextra_space
+ --
+ local ascender = parameters.ascender
+ if ascender then
+ targetparameters.ascender = delta * ascender
+ end
+ local descender = parameters.descender
+ if descender then
+ targetparameters.descender = delta * descender
+ end
+ -- copies, might disappear
+ targetparameters.xheight = targetparameters.xheight or targetparameters.x_height or parameters.x_height
+ targetparameters.extraspace = targetparameters.extraspace or targetparameters.extra_space or parameters.extra_space
+ targetparameters.spacestretch = targetparameters.spacestretch or targetparameters.space_stretch or parameters.space_stretch
+ targetparameters.spaceshrink = targetparameters.spaceshrink or targetparameters.space_shrink or parameters.space_shrink
+ --
+ local protrusionfactor = (targetquad ~= 0 and 1000/targetquad) or 0
+ local scaledwidth = defaultwidth * hdelta
+ local scaledheight = defaultheight * vdelta
+ local scaleddepth = defaultdepth * vdelta
+ --
+ if trace_defining then
+ report_defining("scaling by (%s,%s): name '%s', fullname: '%s', filename: '%s'",
+ hdelta,vdelta,name or "noname",fullname or "nofullname",filename or "nofilename")
+ end
+ --
+ local hasmath = (properties.has_math or next(mathparameters)) and true
+ if hasmath then
+ if trace_defining then
+ report_defining("math enabled for: name '%s', fullname: '%s', filename: '%s'",
+ name or "noname",fullname or "nofullname",filename or "nofilename")
+ end
+ constructors.assignmathparameters(target,tfmdata) -- does scaling and whatever is needed
+ properties.has_math = true
+ target.nomath = false
+ target.MathConstants = target.mathparameters
+ else
+ if trace_defining then
+ report_defining("math disabled for: name '%s', fullname: '%s', filename: '%s'",
+ name or "noname",fullname or "nofullname",filename or "nofilename")
+ end
+ properties.has_math = false
+ target.nomath = true
+ target.mathparameters = nil -- nop
+ end
+ --
+ local sharedkerns = { }
+ --
+ for unicode, character in next, characters do
+ local chr, description, index, touni
+ if changed then
+ -- basemode hack (we try to catch missing tounicodes, e.g. needed for ssty in math cambria)
+ local c = changed[unicode]
+ if c then
+ description = descriptions[c] or descriptions[unicode] or character
+ character = characters[c] or character
+ index = description.index or c
+ if tounicode then
+ touni = tounicode[index] -- nb: index!
+ if not touni then -- goodie
+ local d = descriptions[unicode] or characters[unicode]
+ local i = d.index or unicode
+ touni = tounicode[i] -- nb: index!
+ end
+ end
+ else
+ description = descriptions[unicode] or character
+ index = description.index or unicode
+ if tounicode then
+ touni = tounicode[index] -- nb: index!
+ end
+ end
+ else
+ description = descriptions[unicode] or character
+ index = description.index or unicode
+ if tounicode then
+ touni = tounicode[index] -- nb: index!
+ end
+ end
+ local width = description.width
+ local height = description.height
+ local depth = description.depth
+ if width then width = hdelta*width else width = scaledwidth end
+ if height then height = vdelta*height else height = scaledheight end
+ -- if depth then depth = vdelta*depth else depth = scaleddepth end
+ if depth and depth ~= 0 then
+ depth = delta*depth
+ if nonames then
+ chr = {
+ index = index,
+ height = height,
+ depth = depth,
+ width = width,
+ }
+ else
+ chr = {
+ name = description.name,
+ index = index,
+ height = height,
+ depth = depth,
+ width = width,
+ }
+ end
+ else
+ -- this saves a little bit of memory time and memory, esp for big cjk fonts
+ if nonames then
+ chr = {
+ index = index,
+ height = height,
+ width = width,
+ }
+ else
+ chr = {
+ name = description.name,
+ index = index,
+ height = height,
+ width = width,
+ }
+ end
+ end
+ if touni then
+ chr.tounicode = touni
+ end
+ -- if trace_scaling then
+ -- report_defining("t=%s, u=%s, i=%s, n=%s c=%s",k,chr.tounicode or "",index or 0,description.name or '-',description.class or '-')
+ -- end
+ if hasquality then
+ -- we could move these calculations elsewhere (saves calculations)
+ local ve = character.expansion_factor
+ if ve then
+ chr.expansion_factor = ve*1000 -- expansionfactor, hm, can happen elsewhere
+ end
+ local vl = character.left_protruding
+ if vl then
+ chr.left_protruding = protrusionfactor*width*vl
+ end
+ local vr = character.right_protruding
+ if vr then
+ chr.right_protruding = protrusionfactor*width*vr
+ end
+ end
+ -- todo: hasitalic
+ if autoitalic then
+ local vi = description.italic or (description.boundingbox[3] - description.width + autoitalic)
+ if vi and vi ~= 0 then
+ chr.italic = vi*hdelta
+ end
+ elseif hasitalic then
+ local vi = description.italic or character.italic -- why character
+ if vi and vi ~= 0 then
+ chr.italic = vi*hdelta
+ end
+ end
+ -- to be tested
+ if hasmath then
+ -- todo, just operate on descriptions.math
+ local vn = character.next
+ if vn then
+ chr.next = vn
+ -- if character.vert_variants or character.horiz_variants then
+ -- report_defining("glyph U+%05X has combination of next, vert_variants and horiz_variants",index)
+ -- end
+ else
+ local vv = character.vert_variants
+ if vv then
+ local t = { }
+ for i=1,#vv do
+ local vvi = vv[i]
+ t[i] = {
+ ["start"] = (vvi["start"] or 0)*vdelta,
+ ["end"] = (vvi["end"] or 0)*vdelta,
+ ["advance"] = (vvi["advance"] or 0)*vdelta,
+ ["extender"] = vvi["extender"],
+ ["glyph"] = vvi["glyph"],
+ }
+ end
+ chr.vert_variants = t
+ else
+ local hv = character.horiz_variants
+ if hv then
+ local t = { }
+ for i=1,#hv do
+ local hvi = hv[i]
+ t[i] = {
+ ["start"] = (hvi["start"] or 0)*hdelta,
+ ["end"] = (hvi["end"] or 0)*hdelta,
+ ["advance"] = (hvi["advance"] or 0)*hdelta,
+ ["extender"] = hvi["extender"],
+ ["glyph"] = hvi["glyph"],
+ }
+ end
+ chr.horiz_variants = t
+ end
+ end
+ end
+ local va = character.top_accent
+ if va then
+ chr.top_accent = vdelta*va
+ end
+ if stackmath then
+ local mk = character.mathkerns -- not in math ?
+ if mk then
+ local kerns = { }
+ local v = mk.top_right if v then local k = { } for i=1,#v do local vi = v[i]
+ k[i] = { height = vdelta*vi.height, kern = vdelta*vi.kern }
+ end kerns.top_right = k end
+ local v = mk.top_left if v then local k = { } for i=1,#v do local vi = v[i]
+ k[i] = { height = vdelta*vi.height, kern = vdelta*vi.kern }
+ end kerns.top_left = k end
+ local v = mk.bottom_left if v then local k = { } for i=1,#v do local vi = v[i]
+ k[i] = { height = vdelta*vi.height, kern = vdelta*vi.kern }
+ end kerns.bottom_left = k end
+ local v = mk.bottom_right if v then local k = { } for i=1,#v do local vi = v[i]
+ k[i] = { height = vdelta*vi.height, kern = vdelta*vi.kern }
+ end kerns.bottom_right = k end
+ chr.mathkern = kerns -- singular -> should be patched in luatex !
+ end
+ end
+ end
+ if not nodemode then
+ local vk = character.kerns
+ if vk then
+ local s = sharedkerns[vk]
+ if not s then
+ s = { }
+ for k,v in next, vk do s[k] = v*hdelta end
+ sharedkerns[vk] = s
+ end
+ chr.kerns = s
+ end
+ local vl = character.ligatures
+ if vl then
+ if true then
+ chr.ligatures = vl -- shared
+ else
+ local tt = { }
+ for i,l in next, vl do
+ tt[i] = l
+ end
+ chr.ligatures = tt
+ end
+ end
+ end
+ if isvirtual then
+ local vc = character.commands
+ if vc then
+ -- we assume non scaled commands here
+ -- tricky .. we need to scale pseudo math glyphs too
+ -- which is why we deal with rules too
+ local ok = false
+ for i=1,#vc do
+ local key = vc[i][1]
+ if key == "right" or key == "down" then
+ ok = true
+ break
+ end
+ end
+ if ok then
+ local tt = { }
+ for i=1,#vc do
+ local ivc = vc[i]
+ local key = ivc[1]
+ if key == "right" then
+ tt[i] = { key, ivc[2]*hdelta }
+ elseif key == "down" then
+ tt[i] = { key, ivc[2]*vdelta }
+ elseif key == "rule" then
+ tt[i] = { key, ivc[2]*vdelta, ivc[3]*hdelta }
+ else -- not comment
+ tt[i] = ivc -- shared since in cache and untouched
+ end
+ end
+ chr.commands = tt
+ else
+ chr.commands = vc
+ end
+ chr.index = nil
+ end
+ end
+ targetcharacters[unicode] = chr
+ end
+ return target
+end
+
+function constructors.finalize(tfmdata)
+ if tfmdata.properties and tfmdata.properties.finalized then
+ return
+ end
+ --
+ if not tfmdata.characters then
+ return nil
+ end
+ --
+ if not tfmdata.goodies then
+ tfmdata.goodies = { } -- context specific
+ end
+ --
+ local parameters = tfmdata.parameters
+ if not parameters then
+ return nil
+ end
+ --
+ if not parameters.expansion then
+ parameters.expansion = {
+ stretch = tfmdata.stretch or 0,
+ shrink = tfmdata.shrink or 0,
+ step = tfmdata.step or 0,
+ auto = tfmdata.auto_expand or false,
+ }
+ end
+ --
+ if not parameters.protrusion then
+ parameters.protrusion = {
+ auto = auto_protrude
+ }
+ end
+ --
+ if not parameters.size then
+ parameters.size = tfmdata.size
+ end
+ --
+ if not parameters.extend_factor then
+ parameters.extend_factor = tfmdata.extend or 0
+ end
+ --
+ if not parameters.slant_factor then
+ parameters.slant_factor = tfmdata.slant or 0
+ end
+ --
+ if not parameters.designsize then
+ parameters.designsize = tfmdata.designsize or 655360
+ end
+ --
+ if not parameters.units then
+ parameters.units = tfmdata.units_per_em or 1000
+ end
+ --
+ if not tfmdata.descriptions then
+ local descriptions = { } -- yes or no
+ setmetatableindex(descriptions, function(t,k) local v = { } t[k] = v return v end)
+ tfmdata.descriptions = descriptions
+ end
+ --
+ local properties = tfmdata.properties
+ if not properties then
+ properties = { }
+ tfmdata.properties = properties
+ end
+ --
+ if not properties.virtualized then
+ properties.virtualized = tfmdata.type == "virtual"
+ end
+ --
+ if not tfmdata.properties then
+ tfmdata.properties = {
+ fontname = tfmdata.fontname,
+ filename = tfmdata.filename,
+ fullname = tfmdata.fullname,
+ name = tfmdata.name,
+ psname = tfmdata.psname,
+ --
+ encodingbytes = tfmdata.encodingbytes or 1,
+ embedding = tfmdata.embedding or "subset",
+ tounicode = tfmdata.tounicode or 1,
+ cidinfo = tfmdata.cidinfo or nil,
+ format = tfmdata.format or "type1",
+ direction = tfmdata.direction or 0,
+ }
+ end
+ if not tfmdata.resources then
+ tfmdata.resources = { }
+ end
+ if not tfmdata.shared then
+ tfmdata.shared = { }
+ end
+ --
+ -- tfmdata.fonts
+ -- tfmdata.unscaled
+ --
+ if not properties.has_math then
+ properties.has_math = not tfmdata.nomath
+ end
+ --
+ tfmdata.MathConstants = nil
+ tfmdata.postprocessors = nil
+ --
+ tfmdata.fontname = nil
+ tfmdata.filename = nil
+ tfmdata.fullname = nil
+ tfmdata.name = nil -- most tricky part
+ tfmdata.psname = nil
+ --
+ tfmdata.encodingbytes = nil
+ tfmdata.embedding = nil
+ tfmdata.tounicode = nil
+ tfmdata.cidinfo = nil
+ tfmdata.format = nil
+ tfmdata.direction = nil
+ tfmdata.type = nil
+ tfmdata.nomath = nil
+ tfmdata.designsize = nil
+ --
+ tfmdata.size = nil
+ tfmdata.stretch = nil
+ tfmdata.shrink = nil
+ tfmdata.step = nil
+ tfmdata.auto_expand = nil
+ tfmdata.auto_protrude = nil
+ tfmdata.extend = nil
+ tfmdata.slant = nil
+ tfmdata.units_per_em = nil
+ --
+ properties.finalized = true
+ --
+ return tfmdata
+end
+
+--[[ldx--
+<p>A unique hash value is generated by:</p>
+--ldx]]--
+
+local hashmethods = { }
+constructors.hashmethods = hashmethods
+
+function constructors.hashfeatures(specification) -- will be overloaded
+ local features = specification.features
+ if features then
+ local t, tn = { }, 0
+ for category, list in next, features do
+ if next(list) then
+ local hasher = hashmethods[category]
+ if hasher then
+ local hash = hasher(list)
+ if hash then
+ tn = tn + 1
+ t[tn] = category .. ":" .. hash
+ end
+ end
+ end
+ end
+ if tn > 0 then
+ return concat(t," & ")
+ end
+ end
+ return "unknown"
+end
+
+hashmethods.normal = function(list)
+ local s = { }
+ local n = 0
+ for k, v in next, list do
+ if k ~= "number" and k ~= "features" then -- I need to figure this out, features
+ n = n + 1
+ s[n] = k
+ end
+ end
+ if n > 0 then
+ sort(s)
+ for i=1,n do
+ local k = s[i]
+ s[i] = k .. '=' .. tostring(list[k])
+ end
+ return concat(s,"+")
+ end
+end
+
+--[[ldx--
+<p>In principle we can share tfm tables when we are in node for a font, but then
+we need to define a font switch as an id/attr switch which is no fun, so in that
+case users can best use dynamic features ... so, we will not use that speedup. Okay,
+when we get rid of base mode we can optimize even further by sharing, but then we
+loose our testcases for <l n='luatex'/>.</p>
+--ldx]]--
+
+function constructors.hashinstance(specification,force)
+ local hash, size, fallbacks = specification.hash, specification.size, specification.fallbacks
+ if force or not hash then
+ hash = constructors.hashfeatures(specification)
+ specification.hash = hash
+ end
+ if size < 1000 and designsizes[hash] then
+ size = math.round(constructors.scaled(size,designsizes[hash]))
+ specification.size = size
+ end
+ -- local mathsize = specification.mathsize or 0
+ -- if mathsize > 0 then
+ -- local textsize = specification.textsize
+ -- if fallbacks then
+ -- return hash .. ' @ ' .. tostring(size) .. ' [ ' .. tostring(mathsize) .. ' : ' .. tostring(textsize) .. ' ] @ ' .. fallbacks
+ -- else
+ -- return hash .. ' @ ' .. tostring(size) .. ' [ ' .. tostring(mathsize) .. ' : ' .. tostring(textsize) .. ' ]'
+ -- end
+ -- else
+ if fallbacks then
+ return hash .. ' @ ' .. tostring(size) .. ' @ ' .. fallbacks
+ else
+ return hash .. ' @ ' .. tostring(size)
+ end
+ -- end
+end
+
+function constructors.setname(tfmdata,specification) -- todo: get specification from tfmdata
+ if constructors.namemode == "specification" then
+ -- not to be used in context !
+ local specname = specification.specification
+ if specname then
+ tfmdata.properties.name = specname
+ if trace_defining then
+ report_otf("overloaded fontname: '%s'",specname)
+ end
+ end
+ end
+end
+
+function constructors.checkedfilename(data)
+ local foundfilename = data.foundfilename
+ if not foundfilename then
+ local askedfilename = data.filename or ""
+ if askedfilename ~= "" then
+ askedfilename = resolvers.resolve(askedfilename) -- no shortcut
+ foundfilename = resolvers.findbinfile(askedfilename,"") or ""
+ if foundfilename == "" then
+ report_defining("source file '%s' is not found",askedfilename)
+ foundfilename = resolvers.findbinfile(file.basename(askedfilename),"") or ""
+ if foundfilename ~= "" then
+ report_defining("using source file '%s' (cache mismatch)",foundfilename)
+ end
+ end
+ end
+ data.foundfilename = foundfilename
+ end
+ return foundfilename
+end
+
+local formats = allocate()
+fonts.formats = formats
+
+setmetatableindex(formats, function(t,k)
+ local l = lower(k)
+ if rawget(t,k) then
+ t[k] = l
+ return l
+ end
+ return rawget(t,file.extname(l))
+end)
+
+local locations = { }
+
+local function setindeed(mode,target,group,name,action,position)
+ local t = target[mode]
+ if not t then
+ report_defining("fatal error in setting feature '%s', group '%s', mode '%s'",name or "?",group or "?",mode)
+ os.exit()
+ elseif position then
+ -- todo: remove existing
+ insert(t, position, { name = name, action = action })
+ else
+ for i=1,#t do
+ local ti = t[i]
+ if ti.name == name then
+ ti.action = action
+ return
+ end
+ end
+ insert(t, { name = name, action = action })
+ end
+end
+
+local function set(group,name,target,source)
+ target = target[group]
+ if not target then
+ report_defining("fatal target error in setting feature '%s', group '%s'",name or "?",group or "?")
+ os.exit()
+ end
+ local source = source[group]
+ if not source then
+ report_defining("fatal source error in setting feature '%s', group '%s'",name or "?",group or "?")
+ os.exit()
+ end
+ local node = source.node
+ local base = source.base
+ local position = source.position
+ if node then
+ setindeed("node",target,group,name,node,position)
+ end
+ if base then
+ setindeed("base",target,group,name,base,position)
+ end
+end
+
+local function register(where,specification)
+ local name = specification.name
+ if name and name ~= "" then
+ local default = specification.default
+ local description = specification.description
+ local initializers = specification.initializers
+ local processors = specification.processors
+ local manipulators = specification.manipulators
+ local modechecker = specification.modechecker
+ if default then
+ where.defaults[name] = default
+ end
+ if description and description ~= "" then
+ where.descriptions[name] = description
+ end
+ if initializers then
+ set('initializers',name,where,specification)
+ end
+ if processors then
+ set('processors', name,where,specification)
+ end
+ if manipulators then
+ set('manipulators',name,where,specification)
+ end
+ if modechecker then
+ where.modechecker = modechecker
+ end
+ end
+end
+
+constructors.registerfeature = register
+
+function constructors.getfeatureaction(what,where,mode,name)
+ what = handlers[what].features
+ if what then
+ where = what[where]
+ if where then
+ mode = where[mode]
+ if mode then
+ for i=1,#mode do
+ local m = mode[i]
+ if m.name == name then
+ return m.action
+ end
+ end
+ end
+ end
+ end
+end
+
+function constructors.newfeatures(what)
+ local features = handlers[what].features
+ if not features then
+ local tables = handlers[what].tables -- can be preloaded
+ features = allocate {
+ defaults = { },
+ descriptions = tables and tables.features or { },
+ initializers = { base = { }, node = { } },
+ processors = { base = { }, node = { } },
+ manipulators = { base = { }, node = { } },
+ }
+ features.register = function(specification) return register(features,specification) end
+ handlers[what].features = features -- will also become hidden
+ end
+ return features
+end
+
+--[[ldx--
+<p>We need to check for default features. For this we provide
+a helper function.</p>
+--ldx]]--
+
+function constructors.checkedfeatures(what,features)
+ local defaults = handlers[what].features.defaults
+ if features and next(features) then
+ features = fastcopy(features) -- can be inherited
+ for key, value in next, defaults do
+ if features[key] == nil then
+ features[key] = value
+ end
+ end
+ return features
+ else
+ return fastcopy(defaults) -- we can change features in place
+ end
+end
+
+-- before scaling
+
+function constructors.initializefeatures(what,tfmdata,features,trace,report)
+ if features and next(features) then
+ local properties = tfmdata.properties or { } -- brrr
+ local whathandler = handlers[what]
+ local whatfeatures = whathandler.features
+ local whatinitializers = whatfeatures.initializers
+ local whatmodechecker = whatfeatures.modechecker
+ -- properties.mode can be enforces (for instance in font-otd)
+ local mode = properties.mode or (whatmodechecker and whatmodechecker(tfmdata,features,features.mode)) or features.mode or "base"
+ properties.mode = mode -- also status
+ features.mode = mode -- both properties.mode or features.mode can be changed
+ --
+ local done = { }
+ while true do
+ local redo = false
+ local initializers = whatfeatures.initializers[mode]
+ if initializers then
+ for i=1,#initializers do
+ local step = initializers[i]
+ local feature = step.name
+-- we could intercept mode here .. needs a rewrite of this whole loop then but it's cleaner that way
+ local value = features[feature]
+ if not value then
+ -- disabled
+ elseif done[feature] then
+ -- already done
+ else
+ local action = step.action
+ if trace then
+ report("initializing feature %s to %s for mode %s for font %s",feature,
+ tostring(value),mode or 'unknown', tfmdata.properties.fullname or 'unknown')
+ end
+ action(tfmdata,value,features) -- can set mode (e.g. goodies) so it can trigger a restart
+ if mode ~= properties.mode or mode ~= features.mode then
+ if whatmodechecker then
+ properties.mode = whatmodechecker(tfmdata,features,properties.mode) -- force checking
+ features.mode = properties.mode
+ end
+ if mode ~= properties.mode then
+ mode = properties.mode
+ redo = true
+ end
+ end
+ done[feature] = true
+ end
+ if redo then
+ break
+ end
+ end
+ if not redo then
+ break
+ end
+ else
+ break
+ end
+ end
+ properties.mode = mode -- to be sure
+ return true
+ else
+ return false
+ end
+end
+
+-- while typesetting
+
+function constructors.collectprocessors(what,tfmdata,features,trace,report)
+ local processes, nofprocesses = { }, 0
+ if features and next(features) then
+ local properties = tfmdata.properties
+ local whathandler = handlers[what]
+ local whatfeatures = whathandler.features
+ local whatprocessors = whatfeatures.processors
+ local processors = whatprocessors[properties.mode]
+ if processors then
+ for i=1,#processors do
+ local step = processors[i]
+ local feature = step.name
+ if features[feature] then
+ local action = step.action
+ if trace then
+ report("installing feature processor %s for mode %s for font %s",feature,
+ mode or 'unknown', tfmdata.properties.fullname or 'unknown')
+ end
+ if action then
+ nofprocesses = nofprocesses + 1
+ processes[nofprocesses] = action
+ end
+ end
+ end
+ end
+ end
+ return processes
+end
+
+-- after scaling
+
+function constructors.applymanipulators(what,tfmdata,features,trace,report)
+ if features and next(features) then
+ local properties = tfmdata.properties
+ local whathandler = handlers[what]
+ local whatfeatures = whathandler.features
+ local whatmanipulators = whatfeatures.manipulators
+ local manipulators = whatmanipulators[properties.mode]
+ if manipulators then
+ for i=1,#manipulators do
+ local step = manipulators[i]
+ local feature = step.name
+ local value = features[feature]
+ if value then
+ local action = step.action
+ if trace then
+ report("applying feature manipulator %s for mode %s for font %s",feature,
+ mode or 'unknown', tfmdata.properties.fullname or 'unknown')
+ end
+ if action then
+ action(tfmdata,feature,value)
+ end
+ end
+ end
+ end
+ end
+end