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" } -- some names of table entries will be changed (no _) local next, tostring, tonumber, rawget = next, tostring, tonumber, rawget local format, match, lower, gsub, find = string.format, string.match, string.lower, string.gsub, string.find local sort, insert, concat = table.sort, table.insert, table.concat local sortedkeys, sortedhash, serialize, fastcopy = table.sortedkeys, table.sortedhash, table.serialize, table.fastcopy local derivetable = table.derive local ioflush = io.flush local round = math.round local setmetatable, getmetatable, rawget, rawset = setmetatable, getmetatable, rawget, rawset 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 or { } fonts.constructors = constructors local handlers = fonts.handlers or { } -- can have preloaded tables fonts.handlers = handlers local allocate = utilities.storage.allocate local setmetatableindex = table.setmetatableindex -- will be directives constructors.version = 1.01 constructors.cache = containers.define("fonts", "constructors", constructors.version, false) constructors.privateoffset = fonts.privateoffsets.textbase or 0xF0000 -- This might become an interface: local designsizes = allocate() constructors.designsizes = designsizes local loadedfonts = allocate() constructors.loadedfonts = loadedfonts ----- scalecommands = fonts.helpers.scalecommands --[[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 local factor = constructors.factor if designsize then 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 function constructors.getprivate(tfmdata) local properties = tfmdata.properties local private = properties.private properties.private = private + 1 return private end function constructors.setmathparameter(tfmdata,name,value) local m = tfmdata.mathparameters local c = tfmdata.MathConstants if m then m[name] = value end if c and c ~= m then c[name] = value end end function constructors.getmathparameter(tfmdata,name) local p = tfmdata.mathparameters or tfmdata.MathConstants if p then return p[name] 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 hasitalics 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]]-- -- 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) -- implemented in font-ctx.lmt end function constructors.assignmathparameters(target,original) -- implemented in math-act.lua end function constructors.beforecopyingcharacters(target,original) -- can be used for additional tweaking end function constructors.aftercopyingcharacters(target,original) -- can be used for additional tweaking end function constructors.beforepassingfonttotex(tfmdata) -- can be used for additional tweaking end function constructors.trytosharefont(target,tfmdata) -- implemented in font-def.lmt end local synonyms = { exheight = "xheight", xheight = "xheight", ex = "xheight", emwidth = "quad", em = "quad", spacestretch = "spacestretch", stretch = "spacestretch", spaceshrink = "spaceshrink", shrink = "spaceshrink", extraspace = "extraspace", xspace = "extraspace", slantperpoint = "slant", } function constructors.enhanceparameters(parameters) local mt = getmetatable(parameters) local getter = function(t,k) if not k then return nil end local s = synonyms[k] if s then return rawget(t,s) or (mt and mt[s]) or nil end if k == "spacing" then return { width = t.space, stretch = t.spacestretch, shrink = t.spaceshrink, extra = t.extraspace, } end return mt and mt[k] or nil end local setter = function(t,k,v) if not k then return 0 end local s = synonyms[k] if s then rawset(t,s,v) elseif k == "spacing" then if type(v) == "table" then rawset(t,"space",v.width or 0) rawset(t,"spacestretch",v.stretch or 0) rawset(t,"spaceshrink",v.shrink or 0) rawset(t,"extraspace",v.extra or 0) end else rawset(t,k,v) end end setmetatable(parameters, { __index = getter, __newindex = setter, }) end local function mathkerns(v,vdelta) local k = { } for i=1,#v do local entry = v[i] local height = entry.height local kern = entry.kern k[i] = { height = height and vdelta*height or 0, kern = kern and vdelta*kern or 0, } end return k end local psfake = 0 local function fixedpsname(psname,fallback) local usedname = psname if psname and psname ~= "" then if find(psname," ",1,true) then usedname = gsub(psname,"[%s]+","-") else -- we assume that the name is sane enough (we might sanitize completely some day) end elseif not fallback or fallback == "" then psfake = psfake + 1 psname = "fakename-" .. psfake else -- filenames can be a mess so we do a drastic cleanup psname = fallback usedname = gsub(psname,"[^a-zA-Z0-9]+","-") end return usedname, psname ~= usedname end local function scaleparts(parts,delta) local t = { } for i=1,#parts do local p = parts[i] local s = p["start"] or 0 local e = p["end"] or 0 local a = p["advance"] or 0 t[i] = { ["start"] = s == 0 and 0 or s * delta, ["end"] = e == 0 and 0 or e * delta, ["advance"] = a == 0 and 0 or a * delta, ["extender"] = p["extender"], ["glyph"] = p["glyph"], } end return t end -- getcharactertagvalues function constructors.scale(tfmdata,specification) local target = { } -- the new table -- if tonumber(specification) then specification = { size = specification } end target.specification = specification -- local scalecommands = fonts.helpers.scalecommands -- defined later -- 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 -- can be set by the feature "mathsize" 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 -- else -- -- in context x and xx also use mathsize -- end targetparameters.mathsize = mathsize targetparameters.textsize = textsize -- targetparameters.forcedsize = forcedsize targetparameters.extrafactor = extrafactor -- local defaultwidth = resources.defaultwidth or 0 local defaultheight = resources.defaultheight or 0 local defaultdepth = resources.defaultdepth or 0 local units = parameters.units or 1000 -- -- 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 targetproperties.method = properties.method -- local askedscaledpoints = scaledpoints local scaledpoints, delta = constructors.calculatescale(tfmdata,scaledpoints,nil,specification) -- no shortcut, dan be redefined -- print("B",mathsize,askedscaledpoints,scaledpoints,delta) -- local hdelta = delta local vdelta = delta -- target.designsize = parameters.designsize -- not really needed so it might become obsolete target.units = units -- target.size = scaledpoints -- target.subfont = properties.subfont target.cidinfo = properties.cidinfo target.format = properties.format -- local original = properties.original or tfmdata.original local fontname = properties.fontname or tfmdata.fontname local fullname = properties.fullname or tfmdata.fullname local filename = properties.filename or tfmdata.filename local psname = properties.psname or tfmdata.psname local name = properties.name or tfmdata.name -- -- The psname used in pdf file as well as for selecting subfont in ttc although -- we don't need that subfont look up here (mapfile stuff). -- local psname, psfixed = fixedpsname(psname,fontname or fullname or file.nameonly(filename)) -- target.original = original 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 end -- slanting local slantfactor = parameters.slantfactor or 0 if slantfactor ~= 0 then target.slant = slantfactor * 1000 else target.slant = 0 end -- widening local extendfactor = parameters.extendfactor or 0 if extendfactor ~= 0 and extendfactor ~= 1 then hdelta = hdelta * extendfactor target.extend = extendfactor * 1000 -- target.extend = round(target.extend) else target.extend = 1000 -- extent ? end -- squeezing local squeezefactor = parameters.squeezefactor or 0 if squeezefactor ~= 0 and squeezefactor ~= 1 then vdelta = vdelta * squeezefactor target.squeeze = squeezefactor * 1000 -- target.squeeze = round(target.squeeze) else target.squeeze = 1000 -- extent ? end -- effects local mode = parameters.mode or 0 if mode ~= 0 then target.mode = mode end local width = parameters.width or 0 if width ~= 0 then target.width = width * delta * 1000 / 655360 end -- targetparameters.factor = delta targetparameters.hfactor = hdelta targetparameters.vfactor = vdelta targetparameters.size = scaledpoints targetparameters.units = units targetparameters.scaledpoints = askedscaledpoints targetparameters.mode = mode targetparameters.width = width -- local hasquality = parameters.expansion or parameters.protrusion local hasitalics = properties.hasitalics local autoitalicamount = properties.autoitalicamount -- local stackmath = not properties.nostackmath local haskerns = properties.haskerns or properties.mode == "base" -- we can have afm in node mode local hasligatures = properties.hasligatures or properties.mode == "base" -- we can have afm in node mode local realdimensions = properties.realdimensions local writingmode = properties.writingmode or "horizontal" -- if changed and not next(changed) then changed = false end -- target.writingmode = writingmode == "vertical" and "vertical" or "horizontal" -- target.postprocessors = tfmdata.postprocessors -- local targetslant = (parameters.slant or parameters[1] or 0) * factors.pt -- per point local targetspace = (parameters.space or parameters[2] or 0) * hdelta local targetspacestretch = (parameters.spacestretch or parameters[3] or 0) * hdelta local targetspaceshrink = (parameters.spaceshrink or parameters[4] or 0) * hdelta local targetxheight = (parameters.xheight or parameters[5] or 0) * vdelta local targetquad = (parameters.quad or parameters[6] or 0) * hdelta local targetextraspace = (parameters.extraspace or parameters[7] or 0) * hdelta -- targetparameters.slant = targetslant -- slantperpoint targetparameters.space = targetspace targetparameters.spacestretch = targetspacestretch targetparameters.spaceshrink = targetspaceshrink targetparameters.xheight = targetxheight targetparameters.quad = targetquad targetparameters.extraspace = targetextraspace -- local hshift = parameters.hshift if hshift then targetparameters.hshift = delta * hshift end local vshift = parameters.vshift if vshift then targetparameters.vshift = delta * vshift end -- local ascender = parameters.ascender if ascender then targetparameters.ascender = delta * ascender end local descender = parameters.descender if descender then targetparameters.descender = delta * descender end -- constructors.enhanceparameters(targetparameters) -- official copies for us, now virtual -- local scaledwidth = defaultwidth * hdelta local scaledheight = defaultheight * vdelta local scaleddepth = defaultdepth * vdelta -- local textcontrol = properties.textcontrol or 0 if targetproperties.mode == "base" then textcontrol = textcontrol | 0x02 | 0x04 -- todo symbolic elseif targetproperties.mode == "none" then textcontrol = textcontrol | 0x08 -- todo symbolic end targetproperties.textcontrol = textcontrol target.textcontrol = textcontrol -- local hasmath = (properties.hasmath or next(mathparameters)) and true -- if hasmath then constructors.assignmathparameters(target,tfmdata) -- does scaling and whatever is needed properties.hasmath = true -- to be sure target.nomath = false -- hm target.MathConstants = target.mathparameters local compactmath = properties.compactmath targetproperties.compactmath = compactmath target.compactmath = compactmath local textscale = parameters.textscale -- or 1000 local scriptscale = parameters.scriptscale -- or 700 local scriptscriptscale = parameters.scriptscriptscale -- or 500 parameters.textscale = textscale parameters.scriptscale = scriptscale parameters.scriptscriptscale = scriptscriptscale target.textscale = textscale target.scriptscale = scriptscale target.scriptscriptscale = scriptscriptscale targetparameters.textscale = textscale targetparameters.scriptscale = scriptscale targetparameters.scriptscriptscale = scriptscriptscale local mathcontrol = properties.mathcontrol targetproperties.mathcontrol = mathcontrol target.mathcontrol = mathcontrol else properties.hasmath = false target.nomath = true target.mathparameters = nil -- nop end -- -- During the transition to opentype the engine had troubles with italics so we had some -- additional code for fixing that. In node mode (text) we don't care much if italics gets -- passed because the engine does nothing with them then. -- if hasmath then local mathitalics = properties.mathitalics if mathitalics == false then if trace_defining then report_defining("%s italics %s for font %a, fullname %a, filename %a","math",hasitalics and "ignored" or "disabled",name,fullname,filename) end hasitalics = false autoitalicamount = false end else local textitalics = properties.textitalics if textitalics == false then if trace_defining then report_defining("%s italics %s for font %a, fullname %a, filename %a","text",hasitalics and "ignored" or "disabled",name,fullname,filename) end hasitalics = false autoitalicamount = false end end -- -- end of context specific trickery -- if trace_defining then report_defining("defining tfm, name %a, fullname %a, filename %a, %spsname %a, hscale %a, vscale %a, math %a, italics %a", name,fullname,filename,psfixed and "(fixed) " or "",psname,hdelta,vdelta, hasmath and "enabled" or "disabled",hasitalics and "enabled" or "disabled") end -- constructors.beforecopyingcharacters(target,tfmdata) -- local sharedkerns = { } -- -- we can have a dumb mode (basemode without math etc) that skips most -- for unicode, character in next, characters do local chr, description, index -- todo: add description entry to char entry instead of index ... saves elsewhere too if changed then local c = changed[unicode] if c and c ~= unicode then if c then description = descriptions[c] or descriptions[unicode] or character character = characters[c] or character index = description.index or c else description = descriptions[unicode] or character index = description.index or unicode end else description = descriptions[unicode] or character index = description.index or unicode end else description = descriptions[unicode] or character index = description.index or unicode end local width = description.width local height = description.height local depth = description.depth local isunicode = description.unicode if realdimensions then -- this is mostly for checking issues if not height or height == 0 then local bb = description.boundingbox local ht = bb[4] if ht ~= 0 then height = ht end if not depth or depth == 0 then local dp = -bb[2] if dp ~= 0 then depth = dp end end elseif not depth or depth == 0 then local dp = -description.boundingbox[2] if dp ~= 0 then depth = dp end end end 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 isunicode then chr = { index = index, height = height, depth = depth, width = width, unicode = isunicode, } else chr = { index = index, height = height, depth = depth, width = width, } end else if isunicode then chr = { index = index, height = height, width = width, unicode = isunicode, } else chr = { index = index, height = height, width = width, } end end if hasquality then -- we could move these calculations elsewhere (saves calculations) local ve = character.expansion if ve then chr.expansion = ve*1000 -- expansionfactor, hm, can happen elsewhere end local vc = character.compression if vc then chr.compression = vc*1000 -- expansionfactor, hm, can happen elsewhere end local vl = character.leftprotrusion if vl then chr.leftprotrusion = width*vl end local vr = character.rightprotrusion if vr then chr.rightprotrusion = width*vr end end -- if hasmath then local nxt = character.next if nxt then chr.next = nxt end local parts = character.parts if parts then local orientation = character.partsorientation or "vertical" chr.parts = scaleparts(parts,orientation == "horizontal" and hdelta or vdelta) chr.partsorientation = orientation end local vi = character.partsitalic if vi and vi ~= 0 then chr.partsitalic = vi*hdelta end local va = character.topanchor if va and va ~= 0 then chr.topanchor = va*vdelta end va = character.bottomanchor if va and va ~= 0 then chr.bottomanchor = va*vdelta end -- local mk = character.mathkerns if mk then local tr = mk.topright local tl = mk.topleft local br = mk.bottomright local bl = mk.bottomleft chr.mathkerns = { topright = tr and mathkerns(tr,vdelta) or nil, topleft = tl and mathkerns(tl,vdelta) or nil, bottomright = br and mathkerns(br,vdelta) or nil, bottomleft = bl and mathkerns(bl,vdelta) or nil, } end -- if hasitalics then local vi = character.italic if vi and vi ~= 0 then chr.italic = vi*hdelta end end -- -- These can never happen here as these come from tweaks but I need to check it: -- -- local vo = character.topovershoot -- if vo and vo ~= 0 then -- chr.topovershoot = vo*hdelta -- end -- local il = character.innerlocation -- if il then -- chr.innerlocation = il -- chr.innerxoffset = (character.innerxoffset or 0) * hdelta -- chr.inneryoffset = (character.inneryoffset or 0) * vdelta -- end -- -- if character.extensible then -- chr.extensible = true -- stretch fixed width accent -- end -- -- -- local k = character.topleft if k and k ~= 0 then chr.topleft = k*hdelta end -- local k = character.topright if k and k ~= 0 then chr.topright = k*hdelta end -- local k = character.bottomleft if k and k ~= 0 then chr.bottomleft = k*hdelta end -- local k = character.bottomright if k and k ~= 0 then chr.bottomright = k*hdelta end -- -- -- local m = character.leftmargin if m and m ~= 0 then chr.leftmargin = m*hdelta end -- local m = character.rightmargin if m and m ~= 0 then chr.rightmargin = m*hdelta end -- local m = character.topmargin if m and m ~= 0 then chr.topmargin = m*hdelta end -- local m = character.bottommargin if m and m ~= 0 then chr.bottommargin = m*hdelta end -- local sm = character.smaller if sm then chr.smaller = sm end -- local mi = character.mirror -- if mi then -- chr.mirror = mi -- end local fa = character.flataccent -- set here? if fa then chr.flataccent = fa end elseif autoitalicamount then -- itlc feature local vi = description.italic if not vi then local bb = description.boundingbox if bb then local vi = bb[3] - description.width + autoitalicamount if vi > 0 then -- < 0 indicates no overshoot or a very small auto italic chr.italic = vi*hdelta end else -- report_defining("no boundingbox for character %C in font %a, fullname %a, filename %a",unicode,name,fullname,filename) end elseif vi ~= 0 then chr.italic = vi*hdelta end elseif hasitalics then -- unlikely local vi = character.italic if vi and vi ~= 0 then chr.italic = vi*hdelta end end if haskerns 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 end if hasligatures then 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 -- only in old school fonts (otherwise we set commands later in context) local vc = character.commands if vc then chr.commands = scalecommands(vc,hdelta,vdelta) end local cb = character.callback if cb then chr.callback = cb end -- we assume that these are done in a manipulator afterwards -- as it makes no sense otherwise -- local vx = character.xoffset -- if vx then -- chr.xoffset = vx*hdelta -- end -- local vy = character.yoffset -- if vy then -- chr.yoffset = vy*vdelta -- end -- local va = character.advance -- if va then -- chr.advance = va*vdelta -- end targetcharacters[unicode] = chr end -- properties.setitalics = hasitalics -- for postprocessing -- -- if hasmath then properties.mode = "none" end -- possible optimization but it needs testing -- constructors.aftercopyingcharacters(target,tfmdata) -- constructors.trytosharefont(target,tfmdata) -- -- catch inconsistencies (for now) -- local vfonts = target.fonts if not vfonts or #vfonts == 0 then target.fonts = { { id = 0 } } 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 = { } 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, } end -- if not parameters.size then parameters.size = tfmdata.size end -- if not parameters.mode then parameters.mode = 0 end -- if not parameters.width then parameters.width = 0 end -- if not parameters.slantfactor then parameters.slantfactor = (tfmdata.slant or 0)/1000 end -- if not parameters.extendfactor then parameters.extendfactor = (tfmdata.extend or 1000)/1000 end -- if not parameters.squeezefactor then parameters.squeezefactor = (tfmdata.squeeze or 1000)/1000 end -- local designsize = parameters.designsize if designsize then parameters.minsize = tfmdata.minsize or designsize parameters.maxsize = tfmdata.maxsize or designsize else designsize = factors.pt * 10 parameters.designsize = designsize parameters.minsize = designsize parameters.maxsize = designsize end parameters.minsize = tfmdata.minsize or parameters.designsize parameters.maxsize = tfmdata.maxsize or parameters.designsize -- if not parameters.units then parameters.units = tfmdata.units 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 -- properties.fontname = properties.fontname or tfmdata.fontname properties.filename = properties.filename or tfmdata.filename properties.fullname = properties.fullname or tfmdata.fullname properties.name = properties.name or tfmdata.name properties.psname = properties.psname or tfmdata.psname -- properties.subfont = tfmdata.subfont or nil properties.cidinfo = tfmdata.cidinfo or nil properties.format = tfmdata.format or "type1" properties.writingmode = tfmdata.writingmode or "horizontal" properties.usedbitmap = tfmdata.usedbitmap -- if not tfmdata.resources then tfmdata.resources = { } end if not tfmdata.shared then tfmdata.shared = { } end -- -- tfmdata.fonts -- tfmdata.unscaled -- if not properties.hasmath then properties.hasmath = 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.subfont = nil tfmdata.cidinfo = nil tfmdata.format = nil tfmdata.nomath = nil tfmdata.designsize = nil -- tfmdata.size = nil tfmdata.stretch = nil tfmdata.shrink = nil tfmdata.step = nil tfmdata.slant = nil tfmdata.extend = nil tfmdata.squeeze = nil tfmdata.mode = nil tfmdata.width = nil tfmdata.units = nil -- tfmdata.cache = 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, n = { }, 0 for category, list in sortedhash(features) do if next(list) then local hasher = hashmethods[category] if hasher then local hash = hasher(list) if hash then n = n + 1 t[n] = category .. ":" .. hash end end end end if n > 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 not k then -- no need to add to hash elseif k == "number" or k == "features" then -- no need to add to hash (maybe we need a skip list) else n = n + 1 if type(v) == "table" then -- table.sequenced local t = { } local m = 0 for k, v in next, v do m = m + 1 t[m] = k .. '=' .. tostring(v) end sort(t) s[n] = k .. '={' .. concat(t,",") .. "}" else s[n] = k .. '=' .. tostring(v) end end end if n > 0 then sort(s) return concat(s,"+") end end --[[ldx--

In principle we can share tfm tables when we are in need 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) -- implemented in font-ctx.lmt end function constructors.setname(tfmdata,specification) -- dummy (still called in generic font-otl) 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 %a is not found",askedfilename) foundfilename = resolvers.findbinfile(file.basename(askedfilename),"") or "" if foundfilename ~= "" then report_defining("using source file %a due to 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.suffix(l)) end) do local function setindeed(mode,source,target,group,name,position) local action = source[mode] if not action then return end local t = target[mode] if not t then report_defining("fatal error in setting feature %a, group %a, mode %a",name,group,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 %a, group %a",name,group) os.exit() end local source = source[group] if not source then report_defining("fatal source error in setting feature %a, group %a",name,group) os.exit() end local position = source.position setindeed("node",source,target,group,name,position) setindeed("base",source,target,group,name,position) setindeed("none",source,target,group,name,position) setindeed("plug",source,target,group,name,position) 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 local newfeatures = { } constructors.newfeatures = newfeatures -- downward compatible constructors.features = newfeatures local function setnewfeatures(what) local handler = handlers[what] local features = handler.features if not features then local tables = handler.tables -- can be preloaded local statistics = handler.statistics -- can be preloaded features = allocate { defaults = { }, descriptions = tables and tables.features or { }, used = statistics and statistics.usedfeatures or { }, initializers = { base = { }, node = { }, none = { }, plug = { } }, processors = { base = { }, node = { }, none = { }, plug = { } }, manipulators = { base = { }, node = { }, none = { }, plug = { } }, finalizers = { base = { }, node = { }, none = { }, plug = { } }, } features.register = function(specification) return register(features,specification) end handler.features = features -- will also become hidden end return features end setmetatable(newfeatures, { __call = function(t,k) local v = t[k] return v end, __index = function(t,k) local v = setnewfeatures(k) t[k] = v return v end, }) end do local newhandler = { } constructors.handlers = newhandler -- downward compatible constructors.newhandler = newhandler local function setnewhandler(what) -- could be a metatable newindex local handler = handlers[what] if not handler then handler = { } handlers[what] = handler end return handler end setmetatable(newhandler, { __call = function(t,k) local v = t[k] return v end, __index = function(t,k) local v = setnewhandler(k) t[k] = v return v end, }) end do -- a pitty that we need to be generic as we have nicer mechanisms for this ... local newenhancer = { } constructors.enhancers = newenhancer constructors.newenhancer = newenhancer local function setnewenhancer(format) local handler = handlers[format] local enhancers = handler.enhancers if not enhancers then local actions = allocate() -- no need to allocate thee local before = allocate() local after = allocate() local order = allocate() local known = { } local nofsteps = 0 local patches = { before = before, after = after } local trace = false local report = logs.reporter("fonts",format .. " enhancing") trackers.register(format .. ".loading", function(v) trace = v end) local function enhance(name,data,filename,raw) local enhancer = actions[name] if enhancer then if trace then report("apply enhancement %a to file %a",name,filename) ioflush() end enhancer(data,filename,raw) else -- no message as we can have private ones end end local function apply(data,filename,raw) local basename = file.basename(lower(filename)) if trace then report("%s enhancing file %a","start",filename) end ioflush() -- we want instant messages for e=1,nofsteps do local enhancer = order[e] local b = before[enhancer] if b then for pattern, action in next, b do if find(basename,pattern) then action(data,filename,raw) end end end enhance(enhancer,data,filename,raw) -- we have one installed: check extra features local a = after[enhancer] if a then for pattern, action in next, a do if find(basename,pattern) then action(data,filename,raw) end end end ioflush() -- we want instant messages end if trace then report("%s enhancing file %a","stop",filename) end ioflush() -- we want instant messages end local function register(what,action) if action then if actions[what] then -- overloading, e.g."check extra features" else nofsteps = nofsteps + 1 order[nofsteps] = what known[what] = nofsteps end actions[what] = action else report("bad enhancer %a",what) end end -- We used to have a lot of enhancers but no longer with the new font loader. The order of enhancers -- is the order of definition. The before/after patches are there for old times sake and happen -- before or after a (named) enhancer. An example of a set enhancer is "check extra features" so one -- one set patches before or after that is applied. Unknown enhancers are auto-registered. It's a bit -- messy but we keep it for compatibility reasons. -- -- fonts.handlers.otf.enhancers.patches.register("before","some patches","somefont",function(data,filename) -- print("!!!!!!!") -- before | after -- end) -- -- fonts.handlers.otf.enhancers.register("more patches",function(data,filename) -- print("???????") -- enhance -- end) local function patch(what,where,pattern,action) local pw = patches[what] if pw then local ww = pw[where] if ww then ww[pattern] = action else pw[where] = { [pattern] = action } if not known[where] then nofsteps = nofsteps + 1 order[nofsteps] = where known[where] = nofsteps end end end end enhancers = { register = register, apply = apply, patch = patch, report = report, patches = { register = patch, report = report, }, -- for old times sake } handler.enhancers = enhancers end return enhancers end setmetatable(newenhancer, { __call = function(t,k) local v = t[k] return v end, __index = function(t,k) local v = setnewenhancer(k) t[k] = v return v end, }) 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 (mt) but then no loops possible 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 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 %a to %a for mode %a for font %a",feature, value,mode,tfmdata.properties.fullname) 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 = { } local 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 mode = properties.mode local processors = whatprocessors[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 %a for mode %a for font %a",feature,mode,tfmdata.properties.fullname) end if action then nofprocesses = nofprocesses + 1 processes[nofprocesses] = action end end end elseif trace then report("no feature processors for mode %a for font %a",mode,properties.fullname) end end return processes end -- after scaling local function apply(key,what,tfmdata,features,trace,report) if features and next(features) then local properties = tfmdata.properties local whathandler = handlers[what] local whatfeatures = whathandler.features local whatactions = whatfeatures[key] local mode = properties.mode local actions = whatactions[mode] if actions then for i=1,#actions do local step = actions[i] local feature = step.name local value = features[feature] if value then local action = step.action if trace then report("applying feature %s %a for mode %a for font %a",key,feature,mode,properties.fullname) end if action then action(tfmdata,feature,value) end end end end end end function constructors.applymanipulators(what,tfmdata,features,trace,report) if features and next(features) then apply("manipulators",what,tfmdata,features,trace,report) end end function constructors.applyfinalizers(what,tfmdata,features,trace,report) if features and next(features) then apply("finalizers",what,tfmdata,features,trace,report) end end function constructors.addcoreunicodes(unicodes) -- maybe make this a metatable if used at all if not unicodes then unicodes = { } end unicodes.space = 0x0020 unicodes.hyphen = 0x002D unicodes.zwj = 0x200D unicodes.zwnj = 0x200C return unicodes end