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" } -- Todo: Enable fixes from the lmt to here. Also in font-oto.lua wrt the changed -- assignments. (Around texlive 2024 in order not to disturb generic.) -- 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.dontembed = allocate() constructors.namemode = "fullpath" -- will be a function constructors.version = 1.01 constructors.cache = containers.define("fonts", "constructors", constructors.version, false) constructors.privateoffset = fonts.privateoffsets.textbase or 0xF0000 constructors.cacheintex = true -- so we see the original table in fonts.font constructors.addtounicode = true constructors.fixprotrusion = true -- This might become an interface: local designsizes = allocate() constructors.designsizes = designsizes local loadedfonts = allocate() constructors.loadedfonts = loadedfonts -- 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. function constructors.cleanuptable(tfmdata) -- This no longer makes sense because the addition of font.getcopy and its -- possible usage in generic implicates that we need to return the whole -- lot now. 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, NoLimitSupFactor = true, NoLimitSubFactor = 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 if not targetmathparameters.SpaceBeforeScript then targetmathparameters.SpaceBeforeScript = targetmathparameters.SpaceAfterScript end target.mathparameters = targetmathparameters end 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 -- It's probably ok to hash just the indices because there is not that much -- chance that one will shift slots and leave the others unset then. Anyway, -- there is of course some overhead here, but it might as well get compensated -- by less time spent on including the font resource twice. For the moment -- we default to false, so a macro package has to enable it explicitly. In -- LuaTeX the fullname is used to identify a font as being unique. local nofinstances = 0 local instances = setmetatableindex(function(t,k) nofinstances = nofinstances + 1 t[k] = nofinstances return nofinstances end) function constructors.trytosharefont(target,tfmdata) local properties = target.properties local instance = properties.instance if instance then local fullname = target.fullname local fontname = target.fontname local psname = target.psname local format = tfmdata.properties.format if format == "opentype" then target.streamprovider = 1 elseif format == "truetype" then target.streamprovider = 2 else target.streamprovider = 0 end if target.streamprovider > 0 then if fullname then fullname = fullname .. ":" .. instances[instance] target.fullname = fullname end if fontname then fontname = fontname .. ":" .. instances[instance] target.fontname = fontname end if psname then psname = psname .. ":" .. instances[instance] target.psname = psname end end end end local synonyms = { exheight = "x_height", xheight = "x_height", ex = "x_height", emwidth = "quad", em = "quad", spacestretch = "space_stretch", stretch = "space_stretch", spaceshrink = "space_shrink", shrink = "space_shrink", extraspace = "extra_space", xspace = "extra_space", 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.space_stretch, shrink = t.space_shrink, extra = t.extra_space, } 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,"space_stretch",v.stretch or 0) rawset(t,"space_shrink",v.shrink or 0) rawset(t,"extra_space",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 function constructors.scale(tfmdata,specification) local target = { } -- the new table -- if tonumber(specification) then specification = { size = specification } end target.specification = specification -- 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 context then -- -- do nothing, as we moved this upstream -- elseif (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 targetparameters.mathsize = mathsize -- context specific targetparameters.textsize = textsize -- context specific -- targetparameters.forcedsize = forcedsize -- context specific targetparameters.extrafactor = extrafactor -- context specific -- local addtounicode = constructors.addtounicode -- local tounicode = fonts.mappings.tounicode local unknowncode = tounicode(0xFFFD) -- 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 -- local hdelta = delta local vdelta = delta -- target.designsize = parameters.designsize -- not really needed so it might become obsolete target.units = units target.units_per_em = units -- just a trigger for the backend -- 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.subfont = properties.subfont target.embedding = properties.embedding or "subset" target.tounicode = 1 target.cidinfo = properties.cidinfo target.format = properties.format target.cache = constructors.cacheintex and "yes" or "renew" -- 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 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 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 isvirtual = properties.virtualized or tfmdata.type == "virtual" 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" local identity = properties.identity or "horizontal" local vfonts = target.fonts if vfonts and #vfonts > 0 then target.fonts = fastcopy(vfonts) -- maybe we virtualize more afterwards elseif isvirtual then target.fonts = { { id = 0 } } -- catch error end -- if changed and not next(changed) then changed = false end -- target.type = isvirtual and "virtual" or "real" target.writingmode = writingmode == "vertical" and "vertical" or "horizontal" target.identity = identity == "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 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 -- slantperpoint 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 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 -- -- I need to fix this in luatex ... get rid of quad there so that we can omit this here. -- local protrusionfactor = constructors.fixprotrusion and ((targetquad ~= 0 and 1000/targetquad) or 1) or 1 -- local scaledwidth = defaultwidth * hdelta local scaledheight = defaultheight * vdelta local scaleddepth = defaultdepth * vdelta -- 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 target.nomath = false target.MathConstants = target.mathparameters -- local oldmath = properties.oldmath targetproperties.oldmath = oldmath target.oldmath = oldmath else properties.hasmath = false target.nomath = true target.mathparameters = nil -- nop end -- -- Here we support some context specific trickery (this might move to a plugin). 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 nothign 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 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 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 addtounicode then chr.tounicode = isunicode and tounicode(isunicode) or unknowncode 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 -- if hasmath then -- -- todo, just operate on descriptions.math local vn = character.next if vn then chr.next = vn else local vv = character.vert_variants if vv then local t = { } for i=1,#vv do local vvi = vv[i] local s = vvi["start"] or 0 local e = vvi["end"] or 0 local a = vvi["advance"] or 0 t[i] = { -- zero check nicer for 5.3 ["start"] = s == 0 and 0 or s * vdelta, ["end"] = e == 0 and 0 or e * vdelta, ["advance"] = a == 0 and 0 or a * 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] local s = hvi["start"] or 0 local e = hvi["end"] or 0 local a = hvi["advance"] or 0 t[i] = { -- zero check nicer for 5.3 ["start"] = s == 0 and 0 or s * hdelta, ["end"] = e == 0 and 0 or e * hdelta, ["advance"] = a == 0 and 0 or a * hdelta, ["extender"] = hvi["extender"], ["glyph"] = hvi["glyph"], } end chr.horiz_variants = t end end -- todo also check mathitalics (or that one can go away) end local vi = character.vert_italic if vi and vi ~= 0 then chr.vert_italic = vi*hdelta end local va = character.accent if va then chr.top_accent = vdelta*va end if stackmath then local mk = character.mathkerns if mk then local tr = mk.topright local tl = mk.topleft local br = mk.bottomright local bl = mk.bottomleft chr.mathkern = { -- singular -> should be patched in luatex ! top_right = tr and mathkerns(tr,vdelta) or nil, top_left = tl and mathkerns(tl,vdelta) or nil, bottom_right = br and mathkerns(br,vdelta) or nil, bottom_left = bl and mathkerns(bl,vdelta) or nil, } end end if hasitalics then local vi = character.italic if vi and vi ~= 0 then chr.italic = vi*hdelta end 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 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" or key == "rule" 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 -- properties.setitalics = hasitalics -- for postprocessing -- constructors.aftercopyingcharacters(target,tfmdata) -- constructors.trytosharefont(target,tfmdata) -- -- catch inconsistencies -- local vfonts = target.fonts if isvirtual or target.type == "virtual" or properties.virtualized then properties.virtualized = true target.type = "virtual" if not vfonts or #vfonts == 0 then target.fonts = { { id = 0 } } end elseif vfonts then properties.virtualized = true target.type = "virtual" if #vfonts == 0 then target.fonts = { { id = 0 } } end 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, } 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 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 -- 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.encodingbytes = tfmdata.encodingbytes or 1 properties.subfont = tfmdata.subfont or nil properties.embedding = tfmdata.embedding or "subset" properties.tounicode = tfmdata.tounicode or 1 properties.cidinfo = tfmdata.cidinfo or nil properties.format = tfmdata.format or "type1" properties.direction = tfmdata.direction or 0 properties.writingmode = tfmdata.writingmode or "horizontal" properties.identity = tfmdata.identity 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.encodingbytes = nil tfmdata.subfont = 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.slant = nil tfmdata.extend = nil tfmdata.squeeze = nil tfmdata.mode = nil tfmdata.width = nil tfmdata.units = nil tfmdata.units_per_em = 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) local hash = specification.hash local size = specification.size local fallbacks = specification.fallbacks if force or not hash then hash = constructors.hashfeatures(specification) specification.hash = hash end if size < 1000 and designsizes[hash] then size = round(constructors.scaled(size,designsizes[hash])) else size = round(size) end specification.size = size if fallbacks then return hash .. ' @ ' .. size .. ' @ ' .. fallbacks else return hash .. ' @ ' .. size 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 %a",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 %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("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 = { }, plug = { } }, processors = { base = { }, node = { }, plug = { } }, manipulators = { base = { }, node = { }, 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 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 mode = properties.mode local manipulators = whatmanipulators[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 %a for mode %a for font %a",feature,mode,properties.fullname) end if action then action(tfmdata,feature,value) end end end end 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 -- -- keep for a while: old tounicode code -- -- 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 -- -- local ligatures = character.ligatures -- the original ligatures (as we cannot rely on remapping) -- 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 -- -- if ligatures and not character.ligatures then -- -- character.ligatures = ligatures -- the original targets (for now at least.. see libertine smallcaps) -- -- 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