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 heights are permitted in regular -- fonts. Also, the code in LMTX is a bit different. Here we only implement a -- few helper functions. 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 -- We need to normalize the scale factor (in scaled points). This has to do with the -- fact that TeX uses a negative multiple of 1000 as a signal for a font scaled -- based on the design size. 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 -- 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.) -- -- 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. -- -- The reason why the scaler was originally split, is that for a while we -- experimented with a helper function. However, in practice the API calls are too -- slow to make this profitable and the Lua based variant was just faster. A days -- wasted day but an experience richer. -- 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 at both -- target.name and target.fullname because 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 local abovebaseline_tag = tex.charactertagcodes.abovebaseline local belowbaseline_tag = tex.charactertagcodes.belowbaseline local textcontrolcodes = tex.textcontrolcodes local collapse_hyphens = textcontrolcodes.collapsehyphens local base_ligaturing = textcontrolcodes.baseligaturing local base_kerning = textcontrolcodes.basekerning local none_protected = textcontrolcodes.noneprotected local has_italics = textcontrolcodes.hasitalics local auto_italics = textcontrolcodes.autoitalics 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) -- -- delta = round(delta) -- TODO -- 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 -- The official entries are in the effects subtable. Later we will set the -- values in the parameters table because the backend needs these. local effect = properties.effect if effect then -- units ? local slant = effect.slant or 0 if slant ~= 0 then target.slant = slant * 1000 -- italic : new wd = oldwd + slant * ht else target.slant = 0 end local extend = effect.extend or 1 if extend ~= 0 and extend ~= 1 then hdelta = hdelta * extend target.extend = extend * 1000 -- target.extend = round(target.extend) else target.extend = 1000 end local squeeze = effect.squeeze or 1 if squeeze ~= 0 and squeeze ~= 1 then vdelta = vdelta * squeeze target.squeeze = squeeze * 1000 -- target.squeeze = round(target.squeeze) else target.squeeze = 1000 end local mode = effect.mode or 0 if mode ~= 0 then target.mode = mode end -- target height ! local width = effect.width or 0 if width ~= 0 then target.width = width * 1000 end end -- targetproperties.effect = effect 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 useditalicangle = properties.useditalicangle or properties.italicangle or 0 local usedslant = properties.usedslant or properties.usedslant or 0 -- 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 writingmode = properties.writingmode or "horizontal" -- targetparameters.useditalicangle = useditalicangle targetproperties.useditalicangle = useditalicangle targetparameters.usedslant = usedslant targetproperties.usedslant = usedslant -- 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.expansion = parameters.expansion targetparameters.protrusion = parameters.protrusion -- 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 = vdelta * ascender end local descender = parameters.descender if descender then targetparameters.descender = vdelta * descender end local capheight = parameters.capheight if capheight then targetparameters.capheight = vdelta * capheight end local ascent = parameters.ascent if ascent then targetparameters.ascent = vdelta * ascent end local descent = parameters.descent if descent then targetparameters.descent = vdelta * descent 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 | base_ligaturing | base_kerning -- -- the only drawback is when we add some butthat doesn't happen in base mode so -- we need to test this as there can be a little gain -- -- if haskerns then -- textcontrol = textcontrol | base_kerning -- end -- if hasligatures then -- textcontrol = textcontrol | base_ligaturing -- end elseif targetproperties.mode == "none" then textcontrol = textcontrol | none_protected -- or just assign end if hasitalics then textcontrol = textcontrol | has_italics end if useditalicangle ~= 0 or usedslant ~= 0 then textcontrol = textcontrol | auto_italics 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 -- 1000 local scriptscale = parameters.scriptscale -- 700 local scriptscriptscale = parameters.scriptscriptscale -- 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 -- 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 local cc = changed[c] if cc then while cc do c = cc cc = changed[c] end end -- check not needed: 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 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 = vdelta*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 local bb = description.boundingbox if bb then if bb[2] > 0 then chr.tag = abovebaseline_tag elseif bb[4] < 0 then chr.tag = belowbaseline_tag 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 chr.topanchor = va*hdelta end va = character.bottomanchor if va and va ~= 0 then -- chr.bottomanchor = va*vdelta chr.bottomanchor = va*hdelta 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 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 -- if trace_defining then report_defining("font %a, size %N, delta %N",name,scaledpoints,delta) 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 local properties = tfmdata.properties 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 -- -- In the backend we need to know what effects have been applied. The font -- has been scaled contrary to the effects that we apply per glyph as these -- are delayed. We can actually also use the font specific effects there. -- local effect = properties and properties.effect if effect then parameters.mode = effect.mode parameters.width = effect.width parameters.slantfactor = effect.slant parameters.extendfactor = effect.extend parameters.squeezefactor = effect.squeeze else parameters.mode = 0 parameters.width = 0 parameters.slantfactor = 0 parameters.extendfactor = 1 parameters.squeezefactor = 1 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 -- A unique hash value is generated by: 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 -- 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 LuaTeX. 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 -- We need to check for default features. For this we provide a helper function. 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