if not modules then modules = { } end modules ['font-ctx'] = { 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" } -- At some point I will clean up the code here so that at the tex end -- the table interface is used. -- -- Todo: make a proper 'next id' mechanism (register etc) or wait till 'true' -- in virtual fonts indices is implemented. local tostring, next, type, rawget, tonumber = tostring, next, type, rawget, tonumber local format, gmatch, match, find, lower, upper, gsub, byte, topattern = string.format, string.gmatch, string.match, string.find, string.lower, string.upper, string.gsub, string.byte, string.topattern local concat, serialize, sort, fastcopy, mergedtable = table.concat, table.serialize, table.sort, table.fastcopy, table.merged local sortedhash, sortedkeys, sequenced = table.sortedhash, table.sortedkeys, table.sequenced local parsers = utilities.parsers local settings_to_hash, hash_to_string, settings_to_array = parsers.settings_to_hash, parsers.hash_to_string, parsers.settings_to_array local formatcolumns = utilities.formatters.formatcolumns local mergehashes = utilities.parsers.mergehashes local formatters = string.formatters local basename = file.basename local utfchar, utfbyte = utf.char, utf.byte local round = math.round local context, commands = context, commands local P, S, C, Cc, Cf, Cg, Ct, lpegmatch = lpeg.P, lpeg.S, lpeg.C, lpeg.Cc, lpeg.Cf, lpeg.Cg, lpeg.Ct, lpeg.match local trace_features = false trackers.register("fonts.features", function(v) trace_features = v end) local trace_defining = false trackers.register("fonts.defining", function(v) trace_defining = v end) local trace_designsize = false trackers.register("fonts.designsize", function(v) trace_designsize = v end) local trace_usage = false trackers.register("fonts.usage", function(v) trace_usage = v end) local trace_mapfiles = false trackers.register("fonts.mapfiles", function(v) trace_mapfiles = v end) local trace_automode = false trackers.register("fonts.automode", function(v) trace_automode = v end) local trace_merge = false trackers.register("fonts.merge", function(v) trace_merge = v end) local report = logs.reporter("fonts") local report_features = logs.reporter("fonts","features") local report_cummulative = logs.reporter("fonts","cummulative") local report_defining = logs.reporter("fonts","defining") local report_status = logs.reporter("fonts","status") local report_mapfiles = logs.reporter("fonts","mapfiles") local setmetatableindex = table.setmetatableindex local implement = interfaces.implement local chardata = characters.data local fonts = fonts local handlers = fonts.handlers local otf = handlers.otf -- brrr local afm = handlers.afm -- brrr local tfm = handlers.tfm -- brrr local names = fonts.names local definers = fonts.definers local specifiers = fonts.specifiers local constructors = fonts.constructors local loggers = fonts.loggers local fontgoodies = fonts.goodies local helpers = fonts.helpers local hashes = fonts.hashes local currentfont = font.current local definefont = font.define local getprivateslot = helpers.getprivateslot local cleanname = names.cleanname local encodings = fonts.encodings ----- aglunicodes = encodings.agl.unicodes local aglunicodes = nil -- delayed loading local nuts = nodes.nuts local tonut = nuts.tonut local nextchar = nuts.traversers.char local getattr = nuts.getattr local setattr = nuts.setattr local getstate = nuts.getstate local setsubtype = nuts.setsubtype local texgetdimen = tex.getdimen local texsetcount = tex.setcount local texget = tex.get local texdefinefont = tex.definefont local texsp = tex.sp local fontdata = hashes.identifiers local characters = hashes.characters local descriptions = hashes.descriptions local properties = hashes.properties local resources = hashes.resources local unicodes = hashes.unicodes local csnames = hashes.csnames local lastmathids = hashes.lastmathids local exheights = hashes.exheights local emwidths = hashes.emwidths local parameters = hashes.parameters local designsizefilename = fontgoodies.designsizes.filename local ctx_char = context.char local ctx_safechar = context.safechar local ctx_getvalue = context.getvalue local otffeatures = otf.features local otftables = otf.tables local registerotffeature = otffeatures.register local sequencers = utilities.sequencers local appendgroup = sequencers.appendgroup local appendaction = sequencers.appendaction specifiers.contextsetups = specifiers.contextsetups or { } specifiers.contextnumbers = specifiers.contextnumbers or { } specifiers.contextmerged = specifiers.contextmerged or { } specifiers.synonyms = specifiers.synonyms or { } local setups = specifiers.contextsetups local numbers = specifiers.contextnumbers local merged = specifiers.contextmerged local synonyms = specifiers.synonyms storage.register("fonts/setups" , setups , "fonts.specifiers.contextsetups" ) storage.register("fonts/numbers", numbers, "fonts.specifiers.contextnumbers") storage.register("fonts/merged", merged, "fonts.specifiers.contextmerged") storage.register("fonts/synonyms", synonyms, "fonts.specifiers.synonyms") -- inspect(setups) if environment.initex then setmetatableindex(setups,function(t,k) return type(k) == "number" and rawget(t,numbers[k]) or nil end) else setmetatableindex(setups,function(t,k) local v = type(k) == "number" and rawget(t,numbers[k]) if v then t[k] = v return v end end) end -- this will move elsewhere ... local function getfontname(tfmdata) return basename(type(tfmdata) == "number" and properties[tfmdata].name or tfmdata.properties.name) end helpers.name = getfontname local addformatter = utilities.strings.formatters.add addformatter(formatters,"font:name", [["'"..fontname(%s).."'"]], { fontname = helpers.name }) addformatter(formatters,"font:features",[["'"..sequenced(%s," ",true).."'"]],{ sequenced = table.sequenced }) -- ... like font-sfm or so constructors.resolvevirtualtoo = true -- context specific (due to resolver) if CONTEXTLMTXMODE and CONTEXTLMTXMODE > 0 then constructors.fixprotrusion = false end constructors.sharefonts = true -- experimental constructors.nofsharedhashes = 0 constructors.nofsharedvectors = 0 constructors.noffontsloaded = 0 constructors.autocleanup = true -- we can get rid of the tfm instance when we have fast access to the -- scaled character dimensions at the tex end, e.g. a fontobject.width -- actually we already have some of that now as virtual keys in glyphs -- -- flushing the kern and ligature tables from memory saves a lot (only -- base mode) but it complicates vf building where the new characters -- demand this data .. solution: functions that access them -- font.getcopy = font.getfont -- we always want the table that context uses function constructors.cleanuptable(tfmdata) if constructors.autocleanup and tfmdata.properties.virtualized then for k, v in next, tfmdata.characters do if v.commands then v.commands = nil end -- if v.kerns then v.kerns = nil end end end end do local shares = { } local hashes = { } local nofinstances = 0 local instances = setmetatableindex(function(t,k) nofinstances = nofinstances + 1 t[k] = nofinstances return nofinstances end) function constructors.trytosharefont(target,tfmdata) constructors.noffontsloaded = constructors.noffontsloaded + 1 if constructors.sharefonts then local fonthash = target.specification.hash if fonthash then local properties = target.properties local fullname = target.fullname local fontname = target.fontname local psname = target.psname -- for the moment here: local instance = properties.instance if instance then 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 -- this one is used for the funny prefix in font names in pdf -- so it has ot be kind of unique in order to avoid subset prefix -- clashes being reported psname = psname .. ":" .. instances[instance] target.psname = psname end end end -- local sharedname = hashes[fonthash] if sharedname then -- this is ok for context as we know that only features can mess with font definitions -- so a similar hash means that the fonts are similar too if trace_defining then report_defining("font %a uses backend resources of font %a (%s)",target.fullname,sharedname,"common hash") end target.fullname = sharedname properties.sharedwith = sharedname constructors.nofsharedfonts = constructors.nofsharedfonts + 1 constructors.nofsharedhashes = constructors.nofsharedhashes + 1 else -- the one takes more time (in the worst case of many cjk fonts) but it also saves -- embedding time .. haha, this is interesting: when i got a clash on subset tag -- collision i saw in the source that these tags are also using a hash like below -- so maybe we should have an option to pass it from lua local characters = target.characters local n = 1 local t = { target.psname } -- for the moment here: if instance then n = n + 1 t[n] = instance end -- local u = sortedkeys(characters) for i=1,#u do local k = u[i] n = n + 1 ; t[n] = k n = n + 1 ; t[n] = characters[k].index or k end local checksum = md5.HEX(concat(t," ")) local sharedname = shares[checksum] local fullname = target.fullname if sharedname then if trace_defining then report_defining("font %a uses backend resources of font %a (%s)",fullname,sharedname,"common vector") end fullname = sharedname properties.sharedwith= sharedname constructors.nofsharedfonts = constructors.nofsharedfonts + 1 constructors.nofsharedvectors = constructors.nofsharedvectors + 1 else shares[checksum] = fullname end target.fullname = fullname hashes[fonthash] = fullname end end end end end directives.register("fonts.checksharing",function(v) if not v then report_defining("font sharing in backend is disabled") end constructors.sharefonts = v end) function definers.resetnullfont() -- resetting is needed because tikz misuses nullfont local parameters = fonts.nulldata.parameters -- parameters.slant = 0 -- 1 parameters.space = 0 -- 2 parameters.space_stretch = 0 -- 3 parameters.space_shrink = 0 -- 4 parameters.x_height = 0 -- 5 parameters.quad = 0 -- 6 parameters.extra_space = 0 -- 7 parameters.designsize = 655360 -- constructors.enhanceparameters(parameters) -- official copies for us -- definers.resetnullfont = function() end end implement { name = "resetnullfont", onlyonce = true, actions = function() for i=1,7 do -- we have no direct method yet context([[\fontdimen%s\nullfont\zeropoint]],i) end definers.resetnullfont() end } -- this cannot be a feature initializer as there is no auto namespace -- so we never enter the loop then; we can store the defaults in the tma -- file (features.gpos.mkmk = 1 etc) local needsnodemode = { -- we will have node mode by default anyway -- gsub_single = true, gsub_multiple = true, -- gsub_alternate = true, -- gsub_ligature = true, gsub_context = true, gsub_contextchain = true, gsub_reversecontextchain = true, -- chainsub = true, -- reversesub = true, gpos_mark2base = true, gpos_mark2ligature = true, gpos_mark2mark = true, gpos_cursive = true, -- gpos_single = true, -- gpos_pair = true, gpos_context = true, gpos_contextchain = true, } otftables.scripts.auto = "automatic fallback to latn when no dflt present" -- setmetatableindex(otffeatures.descriptions,otftables.features) local function checkedscript(tfmdata,resources,features) local latn = false local script = false if resources.features then for g, list in next, resources.features do for f, scripts in next, list do if scripts.dflt then script = "dflt" break elseif scripts.latn then latn = true end end end end if not script then script = latn and "latn" or "dflt" end if trace_automode then report_defining("auto script mode, using script %a in font %!font:name!",script,tfmdata) end features.script = script return script end -- basemode combined with dynamics is somewhat tricky local function checkedmode(tfmdata,resources,features) local sequences = resources.sequences if sequences and #sequences > 0 then local script = features.script or "dflt" local language = features.language or "dflt" for feature, value in next, features do if value then local found = false for i=1,#sequences do local sequence = sequences[i] local features = sequence.features if features then local scripts = features[feature] if scripts then local languages = scripts[script] if languages and languages[language] then if found then -- more than one lookup if trace_automode then report_defining("forcing mode %a, font %!font:name!, feature %a, script %a, language %a, %s", "node",tfmdata,feature,script,language,"multiple lookups") end features.mode = "node" return "node" elseif needsnodemode[sequence.type] then if trace_automode then report_defining("forcing mode %a, font %!font:name!, feature %a, script %a, language %a, %s", "node",tfmdata,feature,script,language,"no base support") end features.mode = "node" return "node" else -- at least one lookup found = true end end end end end end end end if trace_automode then report_defining("forcing mode base, font %!font:name!",tfmdata) end features.mode = "base" -- new, or is this wrong? return "base" end definers.checkedscript = checkedscript definers.checkedmode = checkedmode local function modechecker(tfmdata,features,mode) -- we cannot adapt features as they are shared! if trace_features then report_features("fontname %!font:name!, features %!font:features!",tfmdata,features) end local rawdata = tfmdata.shared.rawdata local resources = rawdata and rawdata.resources local script = features.script if resources then if script == "auto" then script = checkedscript(tfmdata,resources,features) end if mode == "auto" then mode = checkedmode(tfmdata,resources,features) end else report_features("missing resources for font %!font:name!",tfmdata) end return mode end registerotffeature { -- we only set the checker and leave other settings of the mode -- feature as they are name = "mode", modechecker = modechecker, } -- -- default = true anyway -- -- local normalinitializer = constructors.getfeatureaction("otf","initializers","node","analyze") -- -- local function analyzeinitializer(tfmdata,value,features) -- attr -- if value == "auto" and features then -- value = features.init or features.medi or features.fina or features.isol or false -- end -- return normalinitializer(tfmdata,value,features) -- end -- -- registerotffeature { -- name = "analyze", -- initializers = { -- node = analyzeinitializer, -- }, -- } do local beforecopyingcharacters = sequencers.new { name = "beforecopyingcharacters", arguments = "target,original", } appendgroup(beforecopyingcharacters,"before") -- user appendgroup(beforecopyingcharacters,"system") -- private appendgroup(beforecopyingcharacters,"after" ) -- user function constructors.beforecopyingcharacters(original,target) local runner = beforecopyingcharacters.runner if runner then runner(original,target) end end local aftercopyingcharacters = sequencers.new { name = "aftercopyingcharacters", arguments = "target,original", } appendgroup(aftercopyingcharacters,"before") -- user appendgroup(aftercopyingcharacters,"system") -- private appendgroup(aftercopyingcharacters,"after" ) -- user function constructors.aftercopyingcharacters(original,target) local runner = aftercopyingcharacters.runner if runner then runner(original,target) end end end --[[ldx--
So far we haven't really dealt with features (or whatever we want to pass along with the font definition. We distinguish the following situations:
situations:
name:xetex like specs
name@virtual font spec
name*context specification
--ldx]]--
-- currently fonts are scaled while constructing the font, so we
-- have to do scaling of commands in the vf at that point using e.g.
-- "local scale = g.parameters.factor or 1" after all, we need to
-- work with copies anyway and scaling needs to be done at some point;
-- however, when virtual tricks are used as feature (makes more
-- sense) we scale the commands in fonts.constructors.scale (and set the
-- factor there)
local loadfont = definers.loadfont
function definers.loadfont(specification,size,id) -- overloads the one in font-def
local variants = definers.methods.variants
local virtualfeatures = specification.features.virtual
if virtualfeatures and virtualfeatures.preset then
local variant = variants[virtualfeatures.preset]
if variant then
return variant(specification,size,id)
end
else
local tfmdata = loadfont(specification,size,id)
-- constructors.checkvirtualid(tfmdata,id)
return tfmdata
end
end
local function predefined(specification)
local variants = definers.methods.variants
local detail = specification.detail
if detail ~= "" and variants[detail] then
specification.features.virtual = { preset = detail }
end
return specification
end
definers.registersplit("@", predefined,"virtual")
local normalize_features = otffeatures.normalize -- should be general
local function definecontext(name,t) -- can be shared
local number = setups[name] and setups[name].number or 0 -- hm, numbers[name]
if number == 0 then
number = #numbers + 1
numbers[number] = name
end
t.number = number
setups[name] = t
return number, t
end
-- {a,b,c} as table (so we don' need to parse again when it gets applied)
-- we will update this ... when we have foo={a,b,c} then we can keep the table
-- \definefontfeature[demo][a={b,c}]
-- \definefontfeature[demo][a={b=12,c={34,35}}]
local h = setmetatableindex(function(t,k)
local v = "," .. k .. ","
t[k] = v
return v
end)
-- local function removefromhash(hash,key)
-- local pattern = h[key]
-- for k in next, hash do
-- if k ~= key and find(h[k],pattern) then -- if find(k,",") and ...
-- hash[k] = nil
-- end
-- end
-- end
local function presetcontext(name,parent,features) -- will go to con and shared
if features == "" and find(parent,"=",1,true) then
features = parent
parent = ""
end
if not features or features == "" then
features = { }
elseif type(features) == "string" then
features = normalize_features(settings_to_hash(features))
-- if type(value) == "string" and find(value,"[=:]") then
-- local t = settings_to_hash_colon_too(value) -- clashes with foo=file:bar
for key, value in next, features do
if type(value) == "string" and find(value,"[=]") then
local t = settings_to_hash(value)
if next(t) then
features[key] = sequenced(normalize_features(t,true),",")
end
end
end
else
features = normalize_features(features)
end
-- todo: synonyms, and not otf bound
if parent ~= "" then
for p in gmatch(parent,"[^, ]+") do
local s = setups[p]
if s then
for k, v in next, s do
-- no, as then we cannot overload: e.g. math,mathextra
-- reverted, so we only take from parent when not set
if features[k] == nil then
features[k] = v
end
end
else
-- just ignore an undefined one .. i.e. we can refer to not yet defined
end
end
end
-- these are auto set so in order to prevent redundant definitions
-- we need to preset them (we hash the features and adding a default
-- setting during initialization may result in a different hash)
--
-- for k,v in next, triggers do
-- if features[v] == nil then -- not false !
-- local vv = default_features[v]
-- if vv then features[v] = vv end
-- end
-- end
--
for feature,value in next, features do
if value == nil then -- not false !
local default = default_features[feature]
if default ~= nil then
features[feature] = default
end
end
end
-- sparse 'm so that we get a better hash and less test (experimental
-- optimization)
local t = { } -- can we avoid t ?
for k,v in next, features do
-- if v then t[k] = v end
t[k] = v
end
-- the number is needed for dynamic features; maybe number should always be
-- renewed as we can redefine features ... i need a test
local number = setups[name] and setups[name].number or 0
if number == 0 then
number = #numbers + 1
numbers[number] = name
end
--
t.number = number
-- there is the special case of combined features as we have in math but maybe
-- this has to change some day ... otherwise we mess up dynamics (ok, we could
-- impose a limit there: no combined features)
--
-- done elsewhere (!)
--
-- removefromhash(setups,name) -- can have changed (like foo,extramath)
--
setups[name] = t
return number, t
end
local function adaptcontext(pattern,features)
local pattern = topattern(pattern,false,true)
for name in next, setups do
if find(name,pattern) then
presetcontext(name,name,features)
end
end
end
-- local function contextnumber(name) -- will be replaced
-- local t = setups[name]
-- if not t then
-- return 0
-- elseif t.auto then -- check where used, autolanguage / autoscript?
-- local lng = tonumber(tex.language)
-- local tag = name .. ":" .. lng
-- local s = setups[tag]
-- if s then
-- return s.number or 0
-- else
-- local script, language = languages.association(lng)
-- if t.script ~= script or t.language ~= language then
-- local s = fastcopy(t)
-- local n = #numbers + 1
-- setups[tag] = s
-- numbers[n] = tag
-- s.number = n
-- s.script = script
-- s.language = language
-- return n
-- else
-- setups[tag] = t
-- return t.number or 0
-- end
-- end
-- else
-- return t.number or 0
-- end
-- end
local function contextnumber(name) -- will be replaced
local t = setups[name]
return t and t.number or 0
end
local function mergecontext(currentnumber,extraname,option) -- number string number (used in scrp-ini
local extra = setups[extraname]
if extra then
local current = setups[numbers[currentnumber]]
local mergedfeatures = { }
local mergedname = nil
if option < 0 then
if current then
for k, v in next, current do
if not extra[k] then
mergedfeatures[k] = v
end
end
end
mergedname = currentnumber .. "-" .. extraname
else
if current then
for k, v in next, current do
mergedfeatures[k] = v
end
end
for k, v in next, extra do
mergedfeatures[k] = v
end
mergedname = currentnumber .. "+" .. extraname
end
local number = #numbers + 1
mergedfeatures.number = number
numbers[number] = mergedname
merged[number] = option
setups[mergedname] = mergedfeatures
return number -- contextnumber(mergedname)
else
return currentnumber
end
end
local extrasets = { }
setmetatableindex(extrasets,function(t,k)
local v = mergehashes(setups,k)
t[k] = v
return v
end)
local function mergecontextfeatures(currentname,extraname,how,mergedname) -- string string
local extra = setups[extraname] or extrasets[extraname]
if extra then
local current = setups[currentname]
local mergedfeatures = { }
if how == "+" then
if current then
for k, v in next, current do
mergedfeatures[k] = v
end
end
for k, v in next, extra do
mergedfeatures[k] = v
end
if trace_merge then
report_features("merge %a, method %a, current %|T, extra %|T, result %|T",mergedname,"add",current or { },extra,mergedfeatures)
end
elseif how == "-" then
if current then
for k, v in next, current do
mergedfeatures[k] = v
end
end
for k, v in next, extra do
-- only boolean features
if v == true then
mergedfeatures[k] = false
end
end
if trace_merge then
report_features("merge %a, method %a, current %|T, extra %|T, result %|T",mergedname,"subtract",current or { },extra,mergedfeatures)
end
else -- =
for k, v in next, extra do
mergedfeatures[k] = v
end
if trace_merge then
report_features("merge %a, method %a, result %|T",mergedname,"replace",mergedfeatures)
end
end
local number = #numbers + 1
mergedfeatures.number = number
numbers[number] = mergedname
merged[number] = option
setups[mergedname] = mergedfeatures
return number
else
return numbers[currentname] or 0
end
end
local function registercontext(fontnumber,extraname,option)
local extra = setups[extraname]
if extra then
local mergedfeatures = { }
local mergedname = nil
if option < 0 then
mergedname = fontnumber .. "-" .. extraname
else
mergedname = fontnumber .. "+" .. extraname
end
for k, v in next, extra do
mergedfeatures[k] = v
end
local number = #numbers + 1
mergedfeatures.number = number
numbers[number] = mergedname
merged[number] = option
setups[mergedname] = mergedfeatures
return number -- contextnumber(mergedname)
else
return 0
end
end
local function registercontextfeature(mergedname,extraname,how)
local extra = setups[extraname]
if extra then
local mergedfeatures = { }
for k, v in next, extra do
mergedfeatures[k] = v
end
local number = #numbers + 1 -- we somehow end up with steps of 2
mergedfeatures.number = number
numbers[number] = mergedname
merged[number] = how == "=" and 1 or 2 -- 1=replace, 2=combine
setups[mergedname] = mergedfeatures
return number -- contextnumber(mergedname)
else
report_features("unknown feature %a cannot be merged into %a using method %a",extraname,mergedname,how)
return 0
end
end
specifiers.presetcontext = presetcontext
specifiers.contextnumber = contextnumber
specifiers.mergecontext = mergecontext
specifiers.registercontext = registercontext
specifiers.definecontext = definecontext
-- we extend the hasher:
constructors.hashmethods.virtual = function(list)
local s = { }
local n = 0
for k, v in next, list do
n = n + 1
-- if v == true then
-- s[n] = k .. '=true'
-- elseif v == false then
-- s[n] = k .. '=false'
-- else
-- s[n] = k .. "=" .. v
-- end
s[n] = k .. "=" .. tostring(v)
end
if n > 0 then
sort(s)
return concat(s,"+")
end
end
if not JITSUPPORTED then
constructors.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] = format("%q=%q",k,v)
end
sort(t)
s[n] = format("%q={%s}",k,concat(t,","))
else
s[n] = format("%q=%q",k,v)
end
end
end
if n > 0 then
sort(s)
return concat(s,"+")
end
end
constructors.hashmethods.virtual = function(list)
local s = { }
local n = 0
for k, v in next, list do
n = n + 1
s[n] = format("%q=%q",k,v)
end
if n > 0 then
sort(s)
return concat(s,"+")
end
end
end
-- end of redefine
-- local withcache = { } -- concat might be less efficient than nested tables
--
-- local function withset(name,what)
-- local zero = texgetattribute(0)
-- local hash = zero .. "+" .. name .. "*" .. what
-- local done = withcache[hash]
-- if not done then
-- done = mergecontext(zero,name,what)
-- withcache[hash] = done
-- end
-- texsetattribute(0,done)
-- end
--
-- local function withfnt(name,what,font)
-- local font = font or currentfont()
-- local hash = font .. "*" .. name .. "*" .. what
-- local done = withcache[hash]
-- if not done then
-- done = registercontext(font,name,what)
-- withcache[hash] = done
-- end
-- texsetattribute(0,done)
-- end
function specifiers.showcontext(name)
return setups[name] or setups[numbers[name]] or setups[numbers[tonumber(name)]] or { }
end
-- we need a copy as we will add (fontclass) goodies to the features and
-- that is bad for a shared table
-- local function splitcontext(features) -- presetcontext creates dummy here
-- return fastcopy(setups[features] or (presetcontext(features,"","") and setups[features]))
-- end
-- local function splitcontext(features) -- presetcontext creates dummy here
-- local sf = setups[features]
-- if not sf then
-- local n -- number
-- if find(features,",") then
-- -- let's assume a combination which is not yet defined but just specified (as in math)
-- n, sf = presetcontext(features,features,"")
-- else
-- -- we've run into an unknown feature and or a direct spec so we create a dummy
-- n, sf = presetcontext(features,"","")
-- end
-- end
-- return fastcopy(sf)
-- end
local function splitcontext(features) -- presetcontext creates dummy here
local n, sf
if find(features,",") then
--
-- from elsewhere (!)
--
-- this will become:
--
-- if find(features,"^reset," then
setups[features] = nil
-- end
-- let's assume a combination which is not yet defined but just specified (as in math)
n, sf = presetcontext(features,features,"")
else
sf = setups[features]
if not sf then
-- we've run into an unknown feature and or a direct spec so we create a dummy
n, sf = presetcontext(features,"","")
end
end
return fastcopy(sf)
end
-- local splitter = lpeg.splitat("=")
--
-- local function splitcontext(features)
-- local setup = setups[features]
-- if setup then
-- return setup
-- elseif find(features,",",1,true) then
-- -- This is not that efficient but handy anyway for quick and dirty tests
-- -- beware, due to the way of caching setups you can get the wrong results
-- -- when components change. A safeguard is to nil the cache.
-- local merge = nil
-- for feature in gmatch(features,"[^, ]+") do
-- if find(feature,"=",1,true) then
-- local k, v = lpegmatch(splitter,feature)
-- if k and v then
-- if not merge then
-- merge = { k = v }
-- else
-- merge[k] = v
-- end
-- end
-- else
-- local s = setups[feature]
-- if not s then
-- -- skip
-- elseif not merge then
-- merge = s
-- else
-- for k, v in next, s do
-- merge[k] = v
-- end
-- end
-- end
-- end
-- setup = merge and presetcontext(features,"",merge) and setups[features]
-- -- actually we have to nil setups[features] in order to permit redefinitions
-- setups[features] = nil
-- end
-- return setup or (presetcontext(features,"","") and setups[features]) -- creates dummy
-- end
specifiers.splitcontext = splitcontext
function specifiers.contexttostring(name,kind,separator,yes,no,strict,omit) -- not used
return hash_to_string(
mergedtable(handlers[kind].features.defaults or {},setups[name] or {}),
separator, yes, no, strict, omit or { "number" }
)
end
local function starred(features) -- no longer fallbacks here
local detail = features.detail
if detail and detail ~= "" then
features.features.normal = splitcontext(detail)
else
features.features.normal = { }
end
return features
end
definers.registersplit('*',starred,"featureset")
-- sort of xetex mode, but without [] and / as we have file: and name: etc
local space = P(" ")
local spaces = space^0
local separator = S(";,")
local equal = P("=")
local sometext = C((1-equal-space-separator)^1)
local truevalue = P("+") * spaces * sometext * Cc(true)
local falsevalue = P("-") * spaces * sometext * Cc(false)
local somevalue = sometext * spaces * Cc(true)
local keyvalue = sometext * spaces * equal * spaces * sometext
local pattern = Cf(Ct("") * (space + separator + Cg(falsevalue + truevalue + keyvalue + somevalue))^0, rawset)
local function colonized(specification)
specification.features.normal = normalize_features(lpegmatch(pattern,specification.detail))
return specification
end
definers.registersplit(":",colonized,"direct")
-- define (two steps)
local sizepattern, splitpattern, specialscale do
----- space = P(" ")
----- spaces = space^0
local leftparent = (P"(")
local rightparent = (P")")
local leftbrace = (P"{")
local rightbrace = (P"}")
local withinparents = leftparent * (1-rightparent)^0 * rightparent
local withinbraces = leftbrace * (1-rightbrace )^0 * rightbrace
local value = C((withinparents + withinbraces + (1-space))^1)
local dimension = C((space/"" + P(1))^1)
local rest = C(P(1)^0)
local scale_none = Cc(0)
local scale_at = (P("at") +P("@")) * Cc(1) * spaces * dimension -- dimension
local scale_sa = P("sa") * Cc(2) * spaces * dimension -- number
local scale_mo = P("mo") * Cc(3) * spaces * dimension -- number
local scale_scaled = P("scaled") * Cc(4) * spaces * dimension -- number
local scale_ht = P("ht") * Cc(5) * spaces * dimension -- dimension
local scale_cp = P("cp") * Cc(6) * spaces * dimension -- dimension
specialscale = { [5] = "ht", [6] = "cp" }
sizepattern = spaces * (scale_at + scale_sa + scale_mo + scale_ht + scale_cp + scale_scaled + scale_none)
splitpattern = spaces * value * spaces * rest
end
function helpers.splitfontpattern(str)
local name, size = lpegmatch(splitpattern,str)
local kind, size = lpegmatch(sizepattern,size)
return name, kind, size
end
function helpers.fontpatternhassize(str)
local name, size = lpegmatch(splitpattern,str)
local kind, size = lpegmatch(sizepattern,size)
return size or false
end
local specification -- still needed as local ?
local getspecification = definers.getspecification
-- we can make helper macros which saves parsing (but normaly not
-- that many calls, e.g. in mk a couple of 100 and in metafun 3500)
local specifiers = { }
do -- else too many locals
----- ctx_setdefaultfontname = context.fntsetdefname
----- ctx_setsomefontname = context.fntsetsomename
----- ctx_setemptyfontsize = context.fntsetnopsize
----- ctx_setsomefontsize = context.fntsetsomesize
----- ctx_letvaluerelax = context.letvaluerelax
local starttiming = statistics.starttiming
local stoptiming = statistics.stoptiming
local scanners = tokens.scanners
local scanstring = scanners.string
local scaninteger = scanners.integer
local scannumber = scanners.number
local scanboolean = scanners.boolean
local setmacro = tokens.setters.macro
local scanners = interfaces.scanners
-- function commands.definefont_one(str)
scanners.definefont_one = function()
local str = scanstring()
starttiming(fonts)
if trace_defining then
report_defining("memory usage before: %s",statistics.memused())
report_defining("start stage one: %s",str)
end
local fullname, size = lpegmatch(splitpattern,str)
local lookup, name, sub, method, detail = getspecification(fullname)
if not name then
report_defining("strange definition %a",str)
-- ctx_setdefaultfontname()
elseif name == "unknown" then
-- ctx_setdefaultfontname()
else
-- ctx_setsomefontname(name)
setmacro("somefontname",name,"global")
end
-- we can also use a count for the size
if size and size ~= "" then
local mode, size = lpegmatch(sizepattern,size)
if size and mode then
texsetcount("scaledfontmode",mode)
-- ctx_setsomefontsize(size)
setmacro("somefontsize",size)
else
texsetcount("scaledfontmode",0)
-- ctx_setemptyfontsize()
end
elseif true then
-- so we don't need to check in tex
texsetcount("scaledfontmode",2)
-- ctx_setemptyfontsize()
else
texsetcount("scaledfontmode",0)
-- ctx_setemptyfontsize()
end
specification = definers.makespecification(str,lookup,name,sub,method,detail,size)
if trace_defining then
report_defining("stop stage one")
end
end
local function nice_cs(cs)
return (gsub(cs,".->", ""))
end
local n = 0
local busy = false
local combinefeatures = false
directives.register("fonts.features.combine",function(v)
combinefeatures = v
end)
scanners.definefont_two = function()
local global = scanboolean() -- \ifx\fontclass\empty\s!false\else\s!true\fi
local cs = scanstring () -- {#csname}%
local str = scanstring () -- \somefontfile
local size = scaninteger() -- \d_font_scaled_font_size
local inheritancemode = scaninteger() -- \c_font_feature_inheritance_mode
local classfeatures = scanstring () -- \m_font_class_features
local fontfeatures = scanstring () -- \m_font_features
local classfallbacks = scanstring () -- \m_font_class_fallbacks
local fontfallbacks = scanstring () -- \m_font_fallbacks
local mathsize = scaninteger() -- \fontface
local textsize = scaninteger() -- \d_font_scaled_text_face
local relativeid = scaninteger() -- \relativefontid
local classgoodies = scanstring () -- \m_font_class_goodies
local goodies = scanstring () -- \m_font_goodies
local classdesignsize = scanstring () -- \m_font_class_designsize
local fontdesignsize = scanstring () -- \m_font_designsize
local scaledfontmode = scaninteger() -- \scaledfontmode
if trace_defining then
report_defining("start stage two: %s, size %s, features %a & %a, mode %a",str,size,classfeatures,fontfeatures,inheritancemode)
end
-- name is now resolved and size is scaled cf sa/mo
local lookup, name, sub, method, detail = getspecification(str or "")
-- new (todo: inheritancemode)
local designsize = fontdesignsize ~= "" and fontdesignsize or classdesignsize or ""
local designname = designsizefilename(name,designsize,size)
if designname and designname ~= "" then
if trace_defining or trace_designsize then
report_defining("remapping name %a, specification %a, size %a, designsize %a",name,designsize,size,designname)
end
-- we don't catch detail here
local o_lookup, o_name, o_sub, o_method, o_detail = getspecification(designname)
if o_lookup and o_lookup ~= "" then lookup = o_lookup end
if o_method and o_method ~= "" then method = o_method end
if o_detail and o_detail ~= "" then detail = o_detail end
name = o_name
sub = o_sub
end
-- so far
-- some settings can have been overloaded
if lookup and lookup ~= "" then
specification.lookup = lookup
end
if relativeid and relativeid ~= "" then -- experimental hook
local id = tonumber(relativeid) or 0
specification.relativeid = id > 0 and id
end
--
specification.name = name
specification.size = size
specification.sub = (sub and sub ~= "" and sub) or specification.sub
specification.mathsize = mathsize
specification.textsize = textsize
specification.goodies = goodies
specification.cs = cs
specification.global = global
specification.scalemode = scaledfontmode -- context specific
if detail and detail ~= "" then
specification.method = method or "*"
specification.detail = detail
elseif specification.detail and specification.detail ~= "" then
-- already set
elseif inheritancemode == 0 then
-- nothing
elseif inheritancemode == 1 then
-- fontonly
if fontfeatures and fontfeatures ~= "" then
specification.method = "*"
specification.detail = fontfeatures
end
if fontfallbacks and fontfallbacks ~= "" then
specification.fallbacks = fontfallbacks
end
elseif inheritancemode == 2 then
-- classonly
if classfeatures and classfeatures ~= "" then
specification.method = "*"
specification.detail = classfeatures
end
if classfallbacks and classfallbacks ~= "" then
specification.fallbacks = classfallbacks
end
elseif inheritancemode == 3 then
-- fontfirst
if combinefeatures then
if classfeatures and classfeatures ~= "" then
specification.method = "*"
if fontfeatures and fontfeatures ~= "" and fontfeatures ~= classfeatures then
specification.detail = classfeatures .. "," .. fontfeatures
else
specification.detail = classfeatures
end
elseif fontfeatures and fontfeatures ~= "" then
specification.method = "*"
specification.detail = fontfeatures
end
else
if fontfeatures and fontfeatures ~= "" then
specification.method = "*"
specification.detail = fontfeatures
elseif classfeatures and classfeatures ~= "" then
specification.method = "*"
specification.detail = classfeatures
end
end
if fontfallbacks and fontfallbacks ~= "" then
specification.fallbacks = fontfallbacks
elseif classfallbacks and classfallbacks ~= "" then
specification.fallbacks = classfallbacks
end
elseif inheritancemode == 4 then
-- classfirst
if combinefeatures then
if fontfeatures and fontfeatures ~= "" then
specification.method = "*"
if classfeatures and classfeatures ~= "" and classfeatures ~= fontfeatures then
specification.detail = fontfeatures .. "," .. classfeatures
else
specification.detail = fontfeatures
end
elseif classfeatures and classfeatures ~= "" then
specification.method = "*"
specification.detail = classfeatures
end
else
if classfeatures and classfeatures ~= "" then
specification.method = "*"
specification.detail = classfeatures
elseif fontfeatures and fontfeatures ~= "" then
specification.method = "*"
specification.detail = fontfeatures
end
end
if classfallbacks and classfallbacks ~= "" then
specification.fallbacks = classfallbacks
elseif fontfallbacks and fontfallbacks ~= "" then
specification.fallbacks = fontfallbacks
end
end
--
local tfmdata = definers.read(specification,size) -- id not yet known (size in spec?)
--
local lastfontid = 0
local tfmtype = type(tfmdata)
if tfmtype == "table" then
-- setting the extra characters will move elsewhere
local characters = tfmdata.characters
local parameters = tfmdata.parameters
local properties = tfmdata.properties
-- we use char0 as signal; cf the spec pdf can handle this (no char in slot)
characters[0] = nil
-- characters[0x00A0] = { width = parameters.space }
-- characters[0x2007] = { width = characters[0x0030] and characters[0x0030].width or parameters.space } -- figure
-- characters[0x2008] = { width = characters[0x002E] and characters[0x002E].width or parameters.space } -- period
--
local fallbacks = specification.fallbacks or ""
local mathsize = (mathsize == 1 or mathsize == 2 or mathsize == 3) and mathsize or nil -- can be unset so we test 1 2 3
if fallbacks ~= "" and mathsize and not busy then
busy = true
-- We need this ugly hack in order to resolve fontnames (at the \TEX end). Originally
-- math was done in Lua after loading (plugged into aftercopying).
--
-- After tl 2017 I'll also do text fallbacks this way (although backups there are done
-- in a completely different way.)
if trace_defining then
report_defining("defining %a, id %a, target %a, features %a / %a, fallbacks %a / %a, step %a",
name,id,nice_cs(cs),classfeatures,fontfeatures,classfallbacks,fontfallbacks,1)
end
mathematics.resolvefallbacks(tfmdata,specification,fallbacks)
context(function()
busy = false
mathematics.finishfallbacks(tfmdata,specification,fallbacks)
local id = definefont(tfmdata)
csnames[id] = specification.cs
properties.id = id
definers.register(tfmdata,id) -- to be sure, normally already done
texdefinefont(global,cs,id)
constructors.cleanuptable(tfmdata)
constructors.finalize(tfmdata)
if trace_defining then
report_defining("defining %a, id %a, target %a, features %a / %a, fallbacks %a / %a, step %a",
name,id,nice_cs(cs),classfeatures,fontfeatures,classfallbacks,fontfallbacks,2)
end
-- resolved (when designsize is used):
local size = round(tfmdata.parameters.size or 655360)
setmacro("somefontsize",size.."sp")
-- ctx_setsomefontsize(size .. "sp")
texsetcount("scaledfontsize",size)
lastfontid = id
--
if trace_defining then
report_defining("memory usage after: %s",statistics.memused())
report_defining("stop stage two")
end
--
texsetcount("global","lastfontid",lastfontid)
specifiers[lastfontid] = { str, size }
if not mathsize then
-- forget about it (can't happen here)
elseif mathsize == 0 then
-- can't happen (here)
else
-- maybe only 1 2 3 (we already test for this)
lastmathids[mathsize] = lastfontid
end
stoptiming(fonts)
end)
return
else
local id = definefont(tfmdata)
csnames[id] = specification.cs
properties.id = id
definers.register(tfmdata,id) -- to be sure, normally already done
texdefinefont(global,cs,id)
constructors.cleanuptable(tfmdata)
constructors.finalize(tfmdata)
if trace_defining then
report_defining("defining %a, id %a, target %a, features %a / %a, fallbacks %a / %a, step %a",
name,id,nice_cs(cs),classfeatures,fontfeatures,classfallbacks,fontfallbacks,"-")
end
-- resolved (when designsize is used):
local size = round(tfmdata.parameters.size or 655360)
setmacro("somefontsize",size.."sp")
-- ctx_setsomefontsize(size .. "sp")
texsetcount("scaledfontsize",size)
lastfontid = id
end
elseif tfmtype == "number" then
if trace_defining then
report_defining("reusing %s, id %a, target %a, features %a / %a, fallbacks %a / %a, goodies %a / %a, designsize %a / %a",
name,tfmdata,nice_cs(cs),classfeatures,fontfeatures,classfallbacks,fontfallbacks,classgoodies,goodies,classdesignsize,fontdesignsize)
end
csnames[tfmdata] = specification.cs
texdefinefont(global,cs,tfmdata)
-- resolved (when designsize is used):
local size = round(fontdata[tfmdata].parameters.size or 0)
-- ctx_setsomefontsize(size .. "sp")
setmacro("somefontsize",size.."sp")
texsetcount("scaledfontsize",size)
lastfontid = tfmdata
else
report_defining("unable to define %a as %a",name,nice_cs(cs))
lastfontid = -1
texsetcount("scaledfontsize",0)
-- ctx_letvaluerelax(cs) -- otherwise the current definition takes the previous one
end
if trace_defining then
report_defining("memory usage after: %s",statistics.memused())
report_defining("stop stage two")
end
--
texsetcount("global","lastfontid",lastfontid)
specifiers[lastfontid] = { str, size }
if not mathsize then
-- forget about it
elseif mathsize == 0 then
-- can't happen (here)
else
-- maybe only 1 2 3
lastmathids[mathsize] = lastfontid
end
--
stoptiming(fonts)
end
function scanners.specifiedfontspec()
local f = specifiers[scaninteger()]
if f then
context(f[1])
end
end
function scanners.specifiedfontsize()
local f = specifiers[scaninteger()]
if f then
context(f[2])
end
end
function scanners.specifiedfont()
local f = specifiers[scaninteger()]
local s = scannumber()
if f and s then
context("%s at %0.2p",f[1],s * f[2]) -- we round to 2 decimals (as at the tex end)
end
end
--
local function define(specification)
--
local name = specification.name
if not name or name == "" then
return -1
else
starttiming(fonts)
--
-- following calls expect a few properties to be set:
--
local lookup, name, sub, method, detail = getspecification(name or "")
--
specification.name = (name ~= "" and name) or specification.name
--
specification.lookup = specification.lookup or (lookup ~= "" and lookup) or "file"
specification.size = specification.size or 655260
specification.sub = specification.sub or (sub ~= "" and sub) or ""
specification.method = specification.method or (method ~= "" and method) or "*"
specification.detail = specification.detail or (detail ~= "" and detail) or ""
--
if type(specification.size) == "string" then
specification.size = texsp(specification.size) or 655260
end
--
specification.specification = "" -- not used
specification.resolved = ""
specification.forced = ""
specification.features = { } -- via detail, maybe some day
--
-- we don't care about mathsize textsize goodies fallbacks
--
local cs = specification.cs
if cs == "" then
cs = nil
specification.cs = nil
specification.global = false
elseif specification.global == nil then
specification.global = false
end
--
local tfmdata = definers.read(specification,specification.size)
if not tfmdata then
return -1, nil
elseif type(tfmdata) == "number" then
if cs then
texdefinefont(specification.global,cs,tfmdata)
csnames[tfmdata] = cs
end
stoptiming(fonts)
return tfmdata, fontdata[tfmdata]
else
local id = definefont(tfmdata)
tfmdata.properties.id = id
definers.register(tfmdata,id)
if cs then
texdefinefont(specification.global,cs,id)
csnames[id] = cs
end
constructors.cleanuptable(tfmdata)
constructors.finalize(tfmdata)
stoptiming(fonts)
return id, tfmdata
end
end
end
definers.define = define
-- local id, cs = fonts.definers.internal { }
-- local id, cs = fonts.definers.internal { number = 2 }
-- local id, cs = fonts.definers.internal { name = "dejavusans" }
local n = 0
function definers.internal(specification,cs)
specification = specification or { }
local name = specification.name
local size = tonumber(specification.size)
local number = tonumber(specification.number)
local id = nil
if not size then
size = texgetdimen("bodyfontsize")
end
if number then
id = number
elseif name and name ~= "" then
local cs = cs or specification.cs
if not cs then
n = n + 1 -- beware ... there can be many and they are often used once
-- cs = formatters["internal font %s"](n)
cs = "internal font " .. n
else
specification.cs = cs
end
id = define {
name = name,
size = size,
cs = cs,
}
end
if not id then
id = currentfont()
end
return id, csnames[id]
end
local read
if CONTEXTLMTXMODE and CONTEXTLMTXMODE > 0 then -- maybe always
read = function(name,size)
return (define { name = name, size = size } or 0)
end
else
read = definers.read
end
callbacks.register('define_font', read, "definition of fonts (tfmdata preparation)")
-- here
local infofont = 0
function fonts.infofont()
if infofont == 0 then
infofont = define { name = "dejavusansmono", size = texsp("6pt") }
end
return infofont
end
-- abstract interfacing
implement { name = "tf", actions = function() setmacro("fontalternative","tf") end }
implement { name = "bf", actions = function() setmacro("fontalternative","bf") end }
implement { name = "it", actions = function() setmacro("fontalternative","it") end }
implement { name = "sl", actions = function() setmacro("fontalternative","sl") end }
implement { name = "bi", actions = function() setmacro("fontalternative","bi") end }
implement { name = "bs", actions = function() setmacro("fontalternative","bs") end }
end
local enable_auto_r_scale = false
experiments.register("fonts.autorscale", function(v)
enable_auto_r_scale = v
end)
-- Not ok, we can best use a database for this. The problem is that we
-- have delayed definitions and so we never know what style is taken
-- as start.
local calculatescale = constructors.calculatescale
function constructors.calculatescale(tfmdata,scaledpoints,relativeid,specification)
if specification then
local scalemode = specification.scalemode
local special = scalemode and specialscale[scalemode]
if special then
-- we also have available specification.textsize
local parameters = tfmdata.parameters
-- local designsize = parameters.designsize
if special == "ht" then
local height = parameters.ascender / parameters.units
scaledpoints = scaledpoints / height
elseif special == "cp" then
local glyph = tfmdata.descriptions[utfbyte("X")]
local height = (glyph and glyph.height or parameters.ascender) / parameters.units
scaledpoints = scaledpoints / height
end
end
end
local scaledpoints, delta = calculatescale(tfmdata,scaledpoints)
-- if enable_auto_r_scale and relativeid then -- for the moment this is rather context specific (we need to hash rscale then)
-- local relativedata = fontdata[relativeid]
-- local rfmdata = relativedata and relativedata.unscaled and relativedata.unscaled -- just use metadata instead
-- local id_x_height = rfmdata and rfmdata.parameters and rfmdata.parameters.x_height
-- local tf_x_height = tfmdata and tfmdata.parameters and tfmdata.parameters.x_height
-- if id_x_height and tf_x_height then
-- local rscale = id_x_height/tf_x_height
-- delta = rscale * delta
-- scaledpoints = rscale * scaledpoints
-- end
-- end
return round(scaledpoints), round(delta)
end
local designsizes = constructors.designsizes
-- called quite often when in mp labels
-- otf.normalizedaxis
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
local scalemode = specification.scalemode
local special = scalemode and specialscale[scalemode]
if special then
return hash .. ' @ ' .. size .. ' @ ' .. special
else
return hash .. ' @ ' .. size
end
end
end
-- We overload the (generic) resolver:
local resolvers = definers.resolvers
local hashfeatures = constructors.hashfeatures
function definers.resolve(specification) -- overload function in font-con.lua
if not specification.resolved or specification.resolved == "" then -- resolved itself not per se in mapping hash
local r = resolvers[specification.lookup]
if r then
r(specification)
end
end
if specification.forced == "" then
specification.forced = nil
else
specification.forced = specification.forced
end
-- goodies are a context specific thing and are not always defined
-- as feature, so we need to make sure we add them here before
-- hashing because otherwise we get funny goodies applied
local goodies = specification.goodies
if goodies and goodies ~= "" then
-- this adapts the features table so it has best be a copy
local normal = specification.features.normal
if not normal then
specification.features.normal = { goodies = goodies }
elseif not normal.goodies then
local g = normal.goodies
if g and g ~= "" then
normal.goodies = formatters["%s,%s"](g,goodies)
else
normal.goodies = goodies
end
end
end
-- so far for goodie hacks
local hash = hashfeatures(specification)
local name = specification.name or "badfont"
local sub = specification.sub
if sub and sub ~= "" then
specification.hash = lower(name .. " @ " .. sub .. ' @ ' .. hash)
else
specification.hash = lower(name .. " @ " .. ' @ ' .. hash)
end
--
return specification
end
-- soon to be obsolete:
local mappings = fonts.mappings
local loaded = { -- prevent loading (happens in cont-sys files)
-- ["original-base.map" ] = true,
-- ["original-ams-base.map" ] = true,
-- ["original-ams-euler.map"] = true,
-- ["original-public-lm.map"] = true,
}
function mappings.loadfile(name)
name = file.addsuffix(name,"map")
if not loaded[name] then
if trace_mapfiles then
report_mapfiles("loading map file %a",name)
end
lpdf.setmapfile(name)
loaded[name] = true
end
end
local loaded = { -- prevent double loading
}
function mappings.loadline(how,line)
if line then
how = how .. " " .. line
elseif how == "" then
how = "= " .. line
end
if not loaded[how] then
if trace_mapfiles then
report_mapfiles("processing map line %a",line)
end
lpdf.setmapline(how)
loaded[how] = true
end
end
function mappings.reset()
lpdf.setmapfile("") -- tricky ... backend related
end
implement {
name = "loadmapfile",
actions = mappings.loadfile,
arguments = "string"
}
implement {
name = "loadmapline",
actions = mappings.loadline,
arguments = "string"
}
implement {
name = "resetmapfiles",
actions = mappings.reset,
arguments = "string"
}
-- we need an 'do after the banner hook'
-- => commands
local pattern = P("P")
* (lpeg.patterns.hexdigit^4 / function(s) return tonumber(s,16) end)
* P(-1)
local function nametoslot(name) -- also supports PXXXXX (4+ positions)
local t = type(name)
if t == "string" then
local unic = unicodes[true]
local slot = unic[name]
if slot then
return slot
end
--
local slot = unic[gsub(name,"_"," ")] or unic[gsub(name,"_","-")] or
unic[gsub(name,"-"," ")] or unic[gsub(name,"-","_")] or
unic[gsub(name," ","_")] or unic[gsub(name," ","-")]
if slot then
return slot
end
--
if not aglunicodes then
aglunicodes = encodings.agl.unicodes
end
local char = characters[true]
local slot = aglunicodes[name]
if slot and char[slot] then
return slot
end
local slot = lpegmatch(pattern,name)
if slot and char[slot] then
return slot
end
-- not in font
elseif t == "number" then
if characters[true][name] then
return slot
else
-- not in font
end
end
end
local found = { }
local function descriptiontoslot(name)
local t = type(name)
if t == "string" then
-- slow
local list = sortedkeys(chardata) -- can be a cache with weak tables
local slot = found[name]
local char = characters[true]
if slot then
return char[slot] and slot or nil
end
local NAME = upper(name)
for i=1,#list do
slot = list[i]
local c = chardata[slot]
local d = c.description
if d == NAME then
found[name] = slot
return char[slot] and slot or nil
end
end
for i=1,#list do
slot = list[i]
local c = chardata[slot]
local s = c.synonyms
if s then
for i=1,#s do
local si = s[i]
if si == name then
found[name] = si
return char[slot] and slot or nil
end
end
end
end
for i=1,#list do
slot = list[i]
local c = chardata[slot]
local d = c.description
if d and find(d,NAME) then
found[name] = slot
return char[slot] and slot or nil
end
end
for i=1,#list do
slot = list[i]
local c = chardata[slot]
local s = c.synonyms
if s then
for i=1,#s do
local si = s[i]
if find(s[i],name) then
found[name] = si
return char[slot] and slot or nil
end
end
end
end
-- not in font
elseif t == "number" then
if characters[true][name] then
return slot
else
-- not in font
end
end
end
local function indextoslot(font,index)
if not index then
index = font
font = true
end
local r = resources[font]
if r then
local indices = r.indices
if not indices then
indices = { }
local c = characters[font]
for unicode, data in next, c do
local di = data.index
if di then
indices[di] = unicode
end
end
r.indices = indices
end
return indices[tonumber(index)]
end
end
do -- else too many locals
local entities = characters.entities
local lowered = { } -- delayed initialization
setmetatableindex(lowered,function(t,k)
for k, v in next, entities do
local l = lower(k)
if not entities[l] then
lowered[l] = v
end
end
setmetatableindex(lowered,nil)
return lowered[k]
end)
local methods = {
-- entity
e = function(name)
return entities[name] or lowered[name] or name
end,
-- hexadecimal unicode
x = function(name)
local n = tonumber(name,16)
return n and utfchar(n) or name
end,
-- decimal unicode
d = function(name)
local n = tonumber(name)
return n and utfchar(n) or name
end,
-- hexadecimal index (slot)
s = function(name)
local n = tonumber(name,16)
local n = n and indextoslot(n)
return n and utfchar(n) or name
end,
-- decimal index
i = function(name)
local n = tonumber(name)
local n = n and indextoslot(n)
return n and utfchar(n) or name
end,
-- name
n = function(name)
local n = nametoslot(name)
return n and utfchar(n) or name
end,
-- unicode description (synonym)
u = function(name)
local n = descriptiontoslot(name,false)
return n and utfchar(n) or name
end,
-- all
a = function(name)
local n = nametoslot(name) or descriptiontoslot(name)
return n and utfchar(n) or name
end,
-- char
c = function(name)
return name
end,
}
-- -- nicer:
--
-- setmetatableindex(methods,function(t,k) return methods.c end)
--
-- local splitter = (C(1) * P(":") + Cc("c")) * C(P(1)^1) / function(method,name)
-- return methods[method](name)
-- end
--
-- -- more efficient:
local splitter = C(1) * P(":") * C(P(1)^1) / function(method,name)
local action = methods[method]
return action and action(name) or name
end
local function tochar(str)
local t = type(str)
if t == "number" then
return utfchar(str)
elseif t == "string" then
return lpegmatch(splitter,str) or str
else
return str
end
end
helpers.nametoslot = nametoslot
helpers.descriptiontoslot = descriptiontoslot
helpers.indextoslot = indextoslot
helpers.tochar = tochar
-- interfaces:
implement {
name = "fontchar",
actions = { nametoslot, ctx_char },
arguments = "string",
}
implement {
name = "fontcharbyindex",
actions = { indextoslot, ctx_char },
arguments = "integer",
}
implement {
name = "tochar",
actions = { tochar, ctx_safechar },
arguments = "string",
}
end
-- this will change ...
function loggers.reportdefinedfonts()
if trace_usage then
local t, tn = { }, 0
for id, data in sortedhash(fontdata) do
local properties = data.properties or { }
local parameters = data.parameters or { }
tn = tn + 1
t[tn] = {
formatters["%03i"](id or 0),
formatters["%p" ](parameters.size or 0),
properties.type or "real",
properties.format or "unknown",
properties.name or "",
properties.psname or "",
properties.fullname or "",
properties.sharedwith or "",
}
end
formatcolumns(t," ")
--
logs.startfilelogging(report,"defined fonts")
for k=1,tn do
report(t[k])
end
logs.stopfilelogging()
end
end
logs.registerfinalactions(loggers.reportdefinedfonts)
function loggers.reportusedfeatures()
-- numbers, setups, merged
if trace_usage then
local t, n = { }, #numbers
for i=1,n do
local name = numbers[i]
local setup = setups[name]
local n = setup.number
setup.number = nil -- we have no reason to show this
t[i] = { i, name, sequenced(setup,false,true) } -- simple mode
setup.number = n -- restore it (normally not needed as we're done anyway)
end
formatcolumns(t," ")
logs.startfilelogging(report,"defined featuresets")
for k=1,n do
report(t[k])
end
logs.stopfilelogging()
end
end
logs.registerfinalactions(loggers.reportusedfeatures)
-- maybe move this to font-log.lua:
statistics.register("font engine", function()
local elapsed = statistics.elapsedseconds(fonts)
local nofshared = constructors.nofsharedfonts or 0
local nofloaded = constructors.noffontsloaded or 0
if nofshared > 0 then
return format("otf %0.3f, afm %0.3f, tfm %0.3f, %s instances, %s shared in backend, %s common vectors, %s common hashes, load time %s",
otf.version,afm.version,tfm.version,nofloaded,
nofshared,constructors.nofsharedvectors,constructors.nofsharedhashes,
elapsed)
elseif nofloaded > 0 and elapsed then
return format("otf %0.3f, afm %0.3f, tfm %0.3f, %s instances, load time %s",
otf.version,afm.version,tfm.version,nofloaded,
elapsed)
else
return format("otf %0.3f, afm %0.3f, tfm %0.3f",
otf.version,afm.version,tfm.version)
end
end)
-- experimental mechanism for Mojca:
--
-- fonts.definetypeface {
-- name = "mainbodyfont-light",
-- preset = "antykwapoltawskiego-light",
-- }
--
-- fonts.definetypeface {
-- name = "mojcasfavourite",
-- preset = "antykwapoltawskiego",
-- normalweight = "light",
-- boldweight = "bold",
-- width = "condensed",
-- }
local Shapes = {
serif = "Serif",
sans = "Sans",
mono = "Mono",
}
local ctx_startfontclass = context.startfontclass
local ctx_stopfontclass = context.stopfontclass
local ctx_definefontsynonym = context.definefontsynonym
local ctx_dofastdefinetypeface = context.dofastdefinetypeface
function fonts.definetypeface(name,t)
if type(name) == "table" then
-- {name=abc,k=v,...}
t = name
elseif t then
if type(t) == "string" then
-- "abc", "k=v,..."
t = settings_to_hash(name)
else
-- "abc", {k=v,...}
end
t.name = t.name or name
else
-- "name=abc,k=v,..."
t = settings_to_hash(name)
end
local p = t.preset and fonts.typefaces[t.preset] or { }
local name = t.name or "unknowntypeface"
local shortcut = t.shortcut or p.shortcut or "rm"
local size = t.size or p.size or "default"
local shape = t.shape or p.shape or "serif"
local fontname = t.fontname or p.fontname or "unknown"
local normalweight = t.normalweight or t.weight or p.normalweight or p.weight or "normal"
local boldweight = t.boldweight or t.weight or p.boldweight or p.weight or "normal"
local normalwidth = t.normalwidth or t.width or p.normalwidth or p.width or "normal"
local boldwidth = t.boldwidth or t.width or p.boldwidth or p.width or "normal"
Shape = Shapes[shape] or "Serif"
ctx_startfontclass { name }
ctx_definefontsynonym( { formatters["%s"] (Shape) }, { formatters["spec:%s-%s-regular-%s"] (fontname, normalweight, normalwidth) } )
ctx_definefontsynonym( { formatters["%sBold"] (Shape) }, { formatters["spec:%s-%s-regular-%s"] (fontname, boldweight, boldwidth ) } )
ctx_definefontsynonym( { formatters["%sBoldItalic"](Shape) }, { formatters["spec:%s-%s-italic-%s"] (fontname, boldweight, boldwidth ) } )
ctx_definefontsynonym( { formatters["%sItalic"] (Shape) }, { formatters["spec:%s-%s-italic-%s"] (fontname, normalweight, normalwidth) } )
ctx_stopfontclass()
local settings = sequenced({ features= t.features },",")
ctx_dofastdefinetypeface(name, shortcut, shape, size, settings)
end
implement {
name = "definetypeface",
actions = fonts.definetypeface,
arguments = "2 strings"
}
function fonts.current() -- todo: also handle name
return fontdata[currentfont()] or fontdata[0]
end
function fonts.currentid()
return currentfont() or 0
end
-- for the moment here, this will become a chain of extras that is
-- hooked into the ctx registration (or scaler or ...)
local dimenfactors = number.dimenfactors
function helpers.dimenfactor(unit,id)
if unit == "ex" then
return id and exheights[id] or 282460 -- lm 10pt
elseif unit == "em" then
return id and emwidths [id] or 655360 -- lm 10pt
else
local du = dimenfactors[unit]
return du and 1/du or tonumber(unit) or 1
end
end
local function digitwidth(font) -- max(quad/2,wd(0..9))
local tfmdata = fontdata[font]
local parameters = tfmdata.parameters
local width = parameters.digitwidth
if not width then
width = round(parameters.quad/2) -- maybe tex.scale
local characters = tfmdata.characters
for i=48,57 do
local wd = round(characters[i].width)
if wd > width then
width = wd
end
end
parameters.digitwidth = width
end
return width
end
helpers.getdigitwidth = digitwidth
helpers.setdigitwidth = digitwidth
--
function helpers.getparameters(tfmdata)
local p = { }
local m = p
local parameters = tfmdata.parameters
while true do
for k, v in next, parameters do
m[k] = v
end
parameters = getmetatable(parameters)
parameters = parameters and parameters.__index
if type(parameters) == "table" then
m = { }
p.metatable = m
else
break
end
end
return p
end
if environment.initex then
local function names(t)
local nt = #t
if nt > 0 then
local n = { }
for i=1,nt do
n[i] = t[i].name
end
return concat(n," ")
else
return "-"
end
end
statistics.register("font processing", function()
local l = { }
for what, handler in table.sortedpairs(handlers) do
local features = handler and handler.features
if features then
l[#l+1] = format("[%s (base initializers: %s) (base processors: %s) (base manipulators: %s) (node initializers: %s) (node processors: %s) (node manipulators: %s)]",
what,
names(features.initializers.base),
names(features.processors .base),
names(features.manipulators.base),
names(features.initializers.node),
names(features.processors .node),
names(features.manipulators.node)
)
end
end
return concat(l, " | ")
end)
end
-- redefinition
-- local hashes = fonts.hashes
-- local emwidths = hashes.emwidths
-- local exheights = hashes.exheights
setmetatableindex(dimenfactors, function(t,k)
if k == "ex" then
return 1/exheights[currentfont()]
elseif k == "em" then
return 1/emwidths[currentfont()]
elseif k == "pct" or k == "%" then
return 1/(texget("hsize")/100)
else
-- error("wrong dimension: " .. (s or "?")) -- better a message
return false
end
end)
dimenfactors.ex = nil
dimenfactors.em = nil
dimenfactors["%"] = nil
dimenfactors.pct = nil
--[[ldx--
Before a font is passed to