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--

Here we only implement a few helper functions.

--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--

We need to normalize the scale factor (in scaled points). This has to do with the fact that uses a negative multiple of 1000 as a signal for a font scaled based on the design size.

--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--

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.)

--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--

The reason why the scaler was originally split, is that for a while we experimented with a helper function. However, in practice the calls are too slow to make this profitable and the based variant was just faster. A days wasted day but an experience richer.

--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 local unscaled = { ScriptPercentScaleDown = true, ScriptScriptPercentScaleDown = true, RadicalDegreeBottomRaisePercent = true } function constructors.assignmathparameters(target,original) -- simple variant, 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 unscaled[name] then targetmathparameters[name] = value else targetmathparameters[name] = value * factor end end if not targetmathparameters.FractionDelimiterSize then targetmathparameters.FractionDelimiterSize = 1.01 * targetparameters.size end if not mathparameters.FractionDelimiterDisplayStyleSize then targetmathparameters.FractionDelimiterDisplayStyleSize = 2.40 * targetparameters.size 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--

A unique hash value is generated by:

--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--

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 .

--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--

We need to check for default features. For this we provide a helper function.

--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 else report("no feature processors for mode %s for font %s", mode or 'unknown', tfmdata.properties.fullname or 'unknown') 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