diff options
Diffstat (limited to 'Master/texmf-dist/tex/context/base/mkxl/font-ctx.lmt')
-rw-r--r-- | Master/texmf-dist/tex/context/base/mkxl/font-ctx.lmt | 3231 |
1 files changed, 3231 insertions, 0 deletions
diff --git a/Master/texmf-dist/tex/context/base/mkxl/font-ctx.lmt b/Master/texmf-dist/tex/context/base/mkxl/font-ctx.lmt new file mode 100644 index 00000000000..b735e3d2f91 --- /dev/null +++ b/Master/texmf-dist/tex/context/base/mkxl/font-ctx.lmt @@ -0,0 +1,3231 @@ +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. +-- +-- Olders code can be found in the mkiv lua variant. + +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, R, S, C, Cc, Cf, Cg, Ct, Cs, lpegmatch = lpeg.P, lpeg.R, lpeg.S, lpeg.C, lpeg.Cc, lpeg.Cf, lpeg.Cg, lpeg.Ct, lpeg.Cs, 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) + +setmetatableindex(setups, environment.initex and + function(t,k) + return type(k) == "number" and rawget(t,numbers[k]) or nil + end +or + function(t,k) + local v = type(k) == "number" and rawget(t,numbers[k]) + if v then + t[k] = v + return v + 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) +constructors.sharefonts = true -- experimental +constructors.nofsharedfonts = 0 +constructors.nofsharedhashes = 0 +constructors.nofsharedvectors = 0 +constructors.noffontsloaded = 0 + +-- 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 + +do + + -- Does this still make sense? + + 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 to 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 + +-- This is some initialization code and we don't want (tracing) clutter in lmtx +-- which is why we follow a different route there. I admit that is sounds freaky. +-- Watch out: in lmtx the font dimen array is no longer resized automatically. + +implement { + name = "resetnullfont", + onlyonce = true, + permanent = false, + actions = function() + for i=1,7 do + font.setfontdimen(0,i,0) + 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 + +-- We only set the checker and leave other settings of the mode feature as +-- they are. + +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 { + name = "mode", + modechecker = modechecker, +} + +do + + -- copying will end up here too + + 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-- +<p>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:</p> +situations:</p> + +<code> +name:xetex like specs +name@virtual font spec +name*context specification +</code> +--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 + return loadfont(specification,size,id) + 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 + +-- See for old code in the lua file. We now share code. + +local function contextnumber(name) + 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 + 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 + 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 + 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 + +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 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 + -- We can have: "a=yes,b=yes" "a,b" "a" "a=yes" etc. + 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 + + -- todo: use lpeg.patterns + + ----- space = P(" ") + ----- spaces = space^0 + local digit = R('09') + local period = P(".") + 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 dimension = Cs((digit+period)^1 * (spaces/"") * (P(1)-digit)^0) +-- local scaler = C((digit+period)^1) + local scaler = lpeg.patterns.unsigned/function(s) return round(tonumber(s)*1000) end + + C(lpeg.patterns.cardinal) + 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) * (spaces * scaler)^0 + scale_none) + splitpattern = spaces * value * spaces * rest + +end + +function helpers.splitfontpattern(str) + local name, size = lpegmatch(splitpattern,str) + local kind, size = lpegmatch(sizepattern,size) -- sx, sy + return name, kind, size -- sx, sy +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 + + local starttiming = statistics.starttiming + local stoptiming = statistics.stoptiming + + local setmacro = tokens.setters.macro + local ctxcatcodes = tex.ctxcatcodes + local texconditionals = tex.conditionals + + local reported = setmetatableindex(function(t,k) + local v = setmetatableindex(function(t,k) + t[k] = true + return false + end) + t[k] = v + return v + end) + + implement { + name = "definefont_one", + arguments = "string", + actions = function(str) + 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, sx, sy = lpegmatch(sizepattern,size) + if mode and size and size ~= "" then + texsetcount("scaledfontmode",mode) + -- ctx_setsomefontsize(size) + -- We use a catcodetable, just in case it's 1.2\exheight (a corner case that showed + -- up in the lmtx manual: marking that changed size in the mids of some verbatim). + setmacro(ctxcatcodes,"somefontsize",size) + if sx then + setmacro("somefontsizex",sx) + end + if sy then + setmacro("somefontsizey",sy) + end + 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) +-- specification.original = str + 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) + + implement { + name = "definefont_two", + arguments = { + -- "boolean", "string", "string", "dimension", "integer", "string", + -- "string", "string", "string", "integer", "dimension", "string", + -- "string", "string", "string", "integer", + "boolean", "argument", "argument", "dimension", "integer", + "argument", "argument", "argument", "argument", "integer", + "dimension", "argument", "argument", "argument", "argument", + "integer", + }, + actions = function ( + global, -- \ifx\fontclass\empty\s!false\else\s!true\fi + cs, -- {#csname}% + str, -- \somefontfile + size, -- \d_font_scaled_font_size + inheritancemode, -- \c_font_feature_inheritance_mode + classfeatures, -- \m_font_class_features + fontfeatures, -- \m_font_features + classfallbacks, -- \m_font_class_fallbacks + fontfallbacks, -- \m_font_fallbacks + mathsize, -- \fontface + textsize, -- \d_font_scaled_text_face + classgoodies, -- \m_font_class_goodies + goodies, -- \m_font_goodies + classdesignsize, -- \m_font_class_designsize + fontdesignsize, -- \m_font_designsize + scaledfontmode -- \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 + if texconditionals["c_font_compact"] then + size = 655360 + 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() + -- We're doing math fallbacks. + busy = false + mathematics.finishfallbacks(tfmdata,specification,fallbacks) + tfmdata.original = specification.specification + local id = definefont(tfmdata,properties.id) + csnames[id] = specification.cs + properties.id = id -- already set + definers.register(tfmdata,id) -- to be sure, normally already done + texdefinefont(global,cs,id) + -- texdefinefont(cs,id,global and "global") + 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 + tfmdata.original = specification.specification + local id = definefont(tfmdata,properties.id) + csnames[id] = specification.cs + properties.id = id -- already set + definers.register(tfmdata,id) -- to be sure, normally already done + texdefinefont(global,cs,id) + -- texdefinefont(cs,id,global and "global") + 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) + -- texdefinefont(cs,tfmdata,global and "global") + -- 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 + local nice = nice_cs(cs) + if not reported[name][nice] then + report_defining("unable to define %a as %a",name,nice) + end + 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 + } + + implement { + name = "specifiedfontspec", + arguments = "integer", + actions = function(id) + local f = specifiers[id] + if f then + context(f[1]) + end + end + } + + implement { + name = "specifiedfontsize", + arguments = "integer", + actions = function(id) + local f = specifiers[id] + if f then + context(f[2]) + end + end + } + + implement { + name = "specifiedfont", + arguments = { "integer", "number" }, + actions = function(id,size) + local f = specifiers[id] + if f and size then + context("%s at %0.2p",f[1],size * 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) + -- texdefinefont(cs,tfmdata,specification.global and "global") + 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) + -- texdefinefont(cs,id,specification.global and "global") + csnames[id] = cs + end + 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 function read(name,size) + return (define { name = name, size = size } or 0) + 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 + +-- 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. + +function constructors.calculatescale(tfmdata,scaledpoints,relativeid,specification) + local parameters = tfmdata.parameters + local units = parameters.units or 1000 + if specification then + local scalemode = specification.scalemode + local special = scalemode and specialscale[scalemode] + if special == "ht" then + local height = parameters.ascender / units + scaledpoints = scaledpoints / height + elseif special == "cp" then + local glyph = tfmdata.descriptions[utfbyte("X")] + local height = (glyph and glyph.height or parameters.ascender) / units + scaledpoints = scaledpoints / height + end + end + if scaledpoints < 0 then + scaledpoints = (- scaledpoints/1000) * (tfmdata.designsize or parameters.designsize) -- already in sp + end + -- a temp hack till we have upgraded all mechanisms + local delta = round(scaledpoints/units) + local size = round(scaledpoints) + texsetcount("c_font_scaled_points",size) + -- + return size, 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 + +-- 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.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-- +<p>Before a font is passed to <l n='tex'/> we scale it. Here we also need +to scale virtual characters.</p> +--ldx]]-- + +do + + -- can become luat-tex.lua + + local texsetglyphdata = tex.setglyphdata + local texgetglyphdata = tex.getglyphdata + + if not texsetglyphdata then + + local texsetattribute = tex.setattribute + local texgetattribute = tex.getattribute + + texsetglyphdata = function(n) return texsetattribute(0,n) end + texgetglyphdata = function() return texgetattribute(0) end + + tex.setglyphdata = texsetglyphdata + tex.getglyphdata = texgetglyphdata + + end + + -- till here + + local setmacro = tokens.setters.macro + + function constructors.currentfonthasfeature(n) + local f = fontdata[currentfont()] + if not f then return end f = f.shared + if not f then return end f = f.rawdata + if not f then return end f = f.resources + if not f then return end f = f.features + return f and (f.gpos[n] or f.gsub[n]) + end + + local ctx_doifelse = commands.doifelse + local ctx_doif = commands.doif + + implement { + name = "doifelsecurrentfonthasfeature", + actions = { constructors.currentfonthasfeature, ctx_doifelse }, + arguments = "string" + } + + local f_strip = formatters["%0.2fpt"] -- normally this value is changed only once + local stripper = lpeg.patterns.stripzeros + + local cache = { } + + local hows = { + ["+"] = "add", + ["-"] = "subtract", + ["="] = "replace", + } + + local function setfeature(how,parent,name,font) -- 0/1 test temporary for testing + if not how or how == 0 then + if trace_features and texgetglyphdata() ~= 0 then + report_cummulative("font %!font:name!, reset",fontdata[font or true]) + end + texsetglyphdata(0) + elseif how == true or how == 1 then + local hash = "feature > " .. parent + local done = cache[hash] + if trace_features and done then + report_cummulative("font %!font:name!, revive %a : %!font:features!",fontdata[font or true],parent,setups[numbers[done]]) + end + texsetglyphdata(done or 0) + else + local full = parent .. how .. name + local hash = "feature > " .. full + local done = cache[hash] + if not done then + local n = setups[full] + if n then + -- already defined + else + n = mergecontextfeatures(parent,name,how,full) + end + done = registercontextfeature(hash,full,how) + cache[hash] = done + if trace_features then + report_cummulative("font %!font:name!, %s %a : %!font:features!",fontdata[font or true],hows[how],full,setups[numbers[done]]) + end + end + texsetglyphdata(done) + end + end + + local function resetfeature() + if trace_features and texgetglyphdata() ~= 0 then + report_cummulative("font %!font:name!, reset",fontdata[true]) + end + texsetglyphdata(0) + end + + local function setfontfeature(tag) + texsetglyphdata(contextnumber(tag)) + end + + local function resetfontfeature() + texsetglyphdata(0) + end + + implement { + name = "nbfs", + arguments = "dimen", + actions = function(d) + context(lpegmatch(stripper,f_strip(d/65536))) + end + } + + implement { + name = "featureattribute", + arguments = "string", + actions = { contextnumber, context } + } + + implement { + name = "setfontfeature", + arguments = "string", + actions = setfontfeature, + } + + implement { + name = "resetfontfeature", + -- arguments = { 0, 0 }, + actions = resetfontfeature, + } + + implement { + name = "setfontofid", + arguments = "integer", + actions = function(id) + ctx_getvalue(csnames[id]) + end + } + + implement { + name = "definefontfeature", + arguments = "3 strings", + actions = presetcontext, + } + + implement { + name = "doifelsefontfeature", + arguments = "string", + actions = function(name) ctx_doifelse(contextnumber(name) > 1) end, + } + + implement { + name = "doifunknownfontfeature", + arguments = "string", + actions = function(name) ctx_doif(contextnumber(name) == 0) end, + } + + implement { + name = "adaptfontfeature", + arguments = "2 strings", + actions = adaptcontext + } + + local function registerlanguagefeatures() + local specifications = languages.data.specifications + for i=1,#specifications do + local specification = specifications[i] + local language = specification.opentype + if language then + local script = specification.opentypescript or specification.script + if script then + local context = specification.context + if type(context) == "table" then + for i=1,#context do + definecontext(context[i], { language = language, script = script}) + end + elseif type(context) == "string" then + definecontext(context, { language = language, script = script}) + end + end + end + end + end + + constructors.setfeature = setfeature + constructors.resetfeature = resetfeature + + implement { name = "resetfeature", actions = resetfeature } + implement { name = "addfeature", actions = setfeature, arguments = { "'+'", "string", "string" } } + implement { name = "subtractfeature", actions = setfeature, arguments = { "'-'", "string", "string" } } + implement { name = "replacefeature", actions = setfeature, arguments = { "'='", "string", "string" } } + implement { name = "revivefeature", actions = setfeature, arguments = { true, "string" } } + + implement { + name = "featurelist", + actions = { fonts.specifiers.contexttostring, context }, + arguments = { "string", "'otf'", "string", "'yes'", "'no'", true } + } + + implement { + name = "registerlanguagefeatures", + actions = registerlanguagefeatures, + } + +end + +-- a fontkern plug: + +-- nodes.injections.installnewkern(nuts.pool.fontkern) + +do + + local report = logs.reporter("otf","variants") + + local function replace(tfmdata,feature,value) + local characters = tfmdata.characters + local variants = tfmdata.resources.variants + if variants then + local t = { } + for k, v in sortedhash(variants) do + t[#t+1] = formatters["0x%X (%i)"](k,k) + end + value = tonumber(value) or 0xFE00 -- 917762 + report("fontname : %s",tfmdata.properties.fontname) + report("available: % t",t) + local v = variants[value] + if v then + report("using : %X (%i)",value,value) + for k, v in next, v do + local c = characters[v] + if c then + characters[k] = c + end + end + else + report("unknown : %X (%i)",value,value) + end + end + end + + registerotffeature { + name = 'variant', + description = 'unicode variant', + manipulators = { + base = replace, + node = replace, + } + } + +end + +-- here (todo: closure) + +-- make a closure (200 limit): + +do + + local trace_analyzing = false trackers.register("otf.analyzing", function(v) trace_analyzing = v end) + + local analyzers = fonts.analyzers + local methods = analyzers.methods + + local unsetvalue = attributes.unsetvalue + + local a_color = attributes.private('color') + local a_colormodel = attributes.private('colormodel') + local m_color = attributes.list[a_color] or { } + + local glyph_code = nodes.nodecodes.glyph + + local states = analyzers.states + + local colornames = { + [states.init] = "font:1", + [states.medi] = "font:2", + [states.fina] = "font:3", + [states.isol] = "font:4", + [states.mark] = "font:5", + [states.rest] = "font:6", + [states.rphf] = "font:1", + [states.half] = "font:2", + [states.pref] = "font:3", + [states.blwf] = "font:4", + [states.pstf] = "font:5", + } + + -- todo: traversers + -- todo: check attr_list so that we can use the same .. helper: setcolorattr + + local function markstates(head) + if head then + head = tonut(head) + local model = getattr(head,a_colormodel) or 1 + for glyph in nextchar, head do + local a = getstate(glyph) + if a then + local name = colornames[a] + if name then + local color = m_color[name] + if color then + setattr(glyph,a_colormodel,model) + setattr(glyph,a_color,color) + end + end + end + end + end + end + + local function analyzeprocessor(head,font,attr) + local tfmdata = fontdata[font] + local script, language = otf.scriptandlanguage(tfmdata,attr) + local action = methods[script] + if not action then + return head, false + end + if type(action) == "function" then + local head, done = action(head,font,attr) + if done and trace_analyzing then + markstates(head) + end + return head, done + end + action = action[language] + if action then + local head, done = action(head,font,attr) + if done and trace_analyzing then + markstates(head) + end + return head, done + else + return head, false + end + end + + registerotffeature { -- adapts + name = "analyze", + processors = { + node = analyzeprocessor, + } + } + + + function methods.nocolor(head,font,attr) + for n, c, f in nextchar, head do + if not font or f == font then + setattr(n,a_color,unsetvalue) + end + end + return head, true + end + +end + + +local function purefontname(name) + if type(name) == "number" then + name = getfontname(name) + end + if type(name) == "string" then + return basename(name) + end +end + +implement { + name = "purefontname", + actions = { purefontname, context }, + arguments = "string", +} + +local sharedstorage = storage.shared + +local list = sharedstorage.bodyfontsizes or { } +local unknown = sharedstorage.unknownbodyfontsizes or { } + +sharedstorage.bodyfontsizes = list +sharedstorage.unknownbodyfontsizes = unknown + +implement { + name = "registerbodyfontsize", + arguments = "string", + actions = function(size) + list[size] = true + end +} + +interfaces.implement { + name = "registerunknownbodysize", + arguments = "string", + actions = function(size) + if not unknown[size] then + interfaces.showmessage("fonts",14,size) + end + unknown[size] = true + end, +} + +implement { + name = "getbodyfontsizes", + arguments = "string", + actions = function(separator) + context(concat(sortedkeys(list),separator)) + end +} + +implement { + name = "processbodyfontsizes", + arguments = "string", + actions = function(command) + local keys = sortedkeys(list) + if command then + local action = context[command] + for i=1,#keys do + action(keys[i]) + end + else + context(concat(keys,",")) + end + end +} + +implement { + name = "cleanfontname", + actions = { cleanname, context }, + arguments = "string" +} + +implement { + name = "fontlookupinitialize", + actions = names.lookup, + arguments = "string", +} + +implement { + name = "fontlookupnoffound", + actions = { names.noflookups, context }, +} + +implement { + name = "fontlookupgetkeyofindex", + actions = { names.getlookupkey, context }, + arguments = { "string", "integer"} +} + +implement { + name = "fontlookupgetkey", + actions = { names.getlookupkey, context }, + arguments = "string" +} + +-- this might move to a runtime module: + +function commands.showchardata(n) + local tfmdata = fontdata[currentfont()] + if tfmdata then + if type(n) == "string" then + n = utfbyte(n) + end + local chr = tfmdata.characters[n] + if chr then + report_status("%s @ %s => %U => %c => %s",tfmdata.properties.fullname,tfmdata.parameters.size,n,n,serialize(chr,false)) + end + end +end + +function commands.showfontparameters(tfmdata) + -- this will become more clever + local tfmdata = tfmdata or fontdata[currentfont()] + if tfmdata then + local parameters = tfmdata.parameters + local mathparameters = tfmdata.mathparameters + local properties = tfmdata.properties + local hasparameters = parameters and next(parameters) + local hasmathparameters = mathparameters and next(mathparameters) + if hasparameters then + report_status("%s @ %s => text parameters => %s",properties.fullname,parameters.size,serialize(parameters,false)) + end + if hasmathparameters then + report_status("%s @ %s => math parameters => %s",properties.fullname,parameters.size,serialize(mathparameters,false)) + end + if not hasparameters and not hasmathparameters then + report_status("%s @ %s => no text parameters and/or math parameters",properties.fullname,parameters.size) + end + end +end + +implement { + name = "currentdesignsize", + actions = function() + context(parameters[currentfont()].designsize) + end +} + +implement { + name = "doifelsefontpresent", + actions = { names.exists, commands.doifelse }, + arguments = "string" +} + +-- we use 0xFE000+ and 0xFF000+ in math and for runtime (text) extensions we +-- use 0xFD000+ + +constructors.privateslots = constructors.privateslots or { } + +storage.register("fonts/constructors/privateslots", constructors.privateslots, "fonts.constructors.privateslots") + +do + + local privateslots = constructors.privateslots + local lastprivateslot = 0xFD000 + + constructors.privateslots = setmetatableindex(privateslots,function(t,k) + local v = lastprivateslot + lastprivateslot = lastprivateslot + 1 + t[k] = v + return v + end) + + implement { + name = "getprivateglyphslot", + actions = function(name) context(privateslots[name]) end, + arguments = "string", + } + +end + +-- an extra helper + +function helpers.getcoloredglyphs(tfmdata) + if type(tfmdata) == "number" then + tfmdata = fontdata[tfmdata] + end + if not tfmdata then + tfmdata = fontdata[true] + end + local characters = tfmdata.characters + local descriptions = tfmdata.descriptions + local collected = { } + for unicode, character in next, characters do + local description = descriptions[unicode] + if description and (description.colors or character.svg) then + collected[#collected+1] = unicode + end + end + table.sort(collected) + return collected +end + +-- for the font manual + +statistics.register("body font sizes", function() + if next(unknown) then + return formatters["defined: % t, undefined: % t"](sortedkeys(list),sortedkeys(unknown)) + end +end) + +statistics.register("used fonts",function() + if trace_usage then + local filename = file.nameonly(environment.jobname) .. "-fonts-usage.lua" + if next(fontdata) then + local files = { } + local list = { } + for id, tfmdata in sortedhash(fontdata) do + local filename = tfmdata.properties.filename + if filename then + local filedata = files[filename] + if filedata then + filedata.instances = filedata.instances + 1 + else + local rawdata = tfmdata.shared and tfmdata.shared.rawdata + local metadata = rawdata and rawdata.metadata + files[filename] = { + instances = 1, + filename = filename, + version = metadata and metadata.version, + size = rawdata and rawdata.size, + } + end + else + -- what to do + end + end + for k, v in sortedhash(files) do + list[#list+1] = v + end + table.save(filename,list) + else + os.remove(filename) + end + end +end) + +-- new + +do + + local settings_to_array = utilities.parsers.settings_to_array + + implement { + name = "definefontcolorpalette", + arguments = "2 strings", + actions = function(name,set) + otf.registerpalette(name,settings_to_array(set)) + end + } + +end + +do + + local pattern = C((1-S("* "))^1) -- strips all after * or ' at' + + implement { + name = "truefontname", + arguments = "string", + actions = function(s) + -- context(match(s,"[^* ]+") or s) + context(lpegmatch(pattern,s) or s) + end + } + +end + +do + + local function getinstancespec(id) + local data = fontdata[id or true] + local shared = data.shared + local resources = shared and shared.rawdata.resources + if resources then + local instancespec = data.properties.instance + if instancespec then + local variabledata = resources.variabledata + if variabledata then + local instances = variabledata.instances + if instances then + for i=1,#instances do + local instance = instances[i] + if cleanname(instance.subfamily)== instancespec then + local values = table.copy(instance.values) + local axis = variabledata.axis + for i=1,#values do + for j=1,#axis do + if values[i].axis == axis[j].tag then + values[i].name = axis[j].name + break + end + end + end + return values + end + end + end + end + end + end + end + + helpers.getinstancespec = getinstancespec + + -- expandable + + implement { + name = "currentfontinstancespec", + public = true, + actions = function() + local t = getinstancespec() -- current font + if t then + for i=1,#t do + if i > 1 then + context.space() + end + local ti = t[i] + context("%s=%s",ti.name,ti.value) + end + end + end + } + +end + +-- for the moment here (and not in font-con.lua): + +do + + local identical = table.identical + local copy = table.copy + local fontdata = fonts.hashes.identifiers + local addcharacters = font.addcharacters + + -- This helper is mostly meant to add last-resort (virtual) characters + -- or runtime generated fonts (so we forget about features and such). It + -- will probably take a while before it get used. + + local trace_adding = false + local report_adding = logs.reporter("fonts","add characters") + + trackers.register("fonts.addcharacters",function(v) trace_adding = v end) + + function constructors.addcharacters(id,list) + local newchar = list.characters + if newchar then + local data = fontdata[id] + local newfont = list.fonts + local oldchar = data.characters + local oldfont = data.fonts + addcharacters(id, { + characters = newchar, + fonts = newfont, + nomath = not data.properties.hasmath, + }) + -- this is just for tracing, as the assignment only uses the fonts list + -- and doesn't store it otherwise + if newfont then + if oldfont then + local oldn = #oldfont + local newn = #newfont + for n=1,newn do + local ok = false + local nf = newfont[n] + for o=1,oldn do + if identical(nf,oldfont[o]) then + ok = true + break + end + end + if not ok then + oldn = oldn + 1 + oldfont[oldn] = newfont[i] + end + end + else + data.fonts = newfont + end + end + -- this is because we need to know what goes on and also might + -- want to access character data + for u, c in next, newchar do + if trace_adding then + report_adding("adding character %U to font %!font:name!",u,id) + end + oldchar[u] = c + end + end + end + + implement { + name = "addfontpath", + arguments = "string", + actions = function(list) + names.addruntimepath(settings_to_array(list)) + end + } + +end + +-- moved here + +do + + local getfontoffamily = font.getfontoffamily + local new_glyph = nodes.pool.glyph + local fontproperties = fonts.hashes.properties + + local function getprivateslot(id,name) + if not name then + name = id + id = currentfont() + end + local properties = fontproperties[id] + local privates = properties and properties.privates + return privates and privates[name] + end + + local function getprivatenode(tfmdata,name) + if type(tfmdata) == "number" then + tfmdata = fontdata[tfmdata] + end + local properties = tfmdata.properties + local font = properties.id + local slot = getprivateslot(font,name) + if slot then + -- todo: set current attribibutes + local char = tfmdata.characters[slot] + local tonode = char.tonode + if tonode then + return tonode(font,char) + else + return new_glyph(font,slot) + end + end + end + + local function getprivatecharornode(tfmdata,name) + if type(tfmdata) == "number" then + tfmdata = fontdata[tfmdata] + end + local properties = tfmdata.properties + local font = properties.id + local slot = getprivateslot(font,name) + if slot then + -- todo: set current attributes + local char = tfmdata.characters[slot] + local tonode = char.tonode + if tonode then + return "node", tonode(tfmdata,char) + else + return "char", slot + end + end + end + + helpers.getprivateslot = getprivateslot + helpers.getprivatenode = getprivatenode + helpers.getprivatecharornode = getprivatecharornode + + implement { + name = "getprivatechar", + arguments = "string", + actions = function(name) + local p = getprivateslot(name) + if p then + context(utfchar(p)) + end + end + } + + implement { + name = "getprivatemathchar", + arguments = "string", + actions = function(name) + local p = getprivateslot(getfontoffamily(0),name) + if p then + context(utfchar(p)) + end + end + } + + implement { + name = "getprivateslot", + arguments = "string", + actions = function(name) + local p = getprivateslot(name) + if p then + context(p) + end + end + } + +end + +-- handy, for now here: + +function fonts.helpers.collectanchors(tfmdata) + + local resources = tfmdata.resources -- todo: use shared + + if not resources or resources.anchors then + return resources.anchors + end + + local anchors = { } + + local function set(unicode,target,class,anchor) + local a = anchors[unicode] + if not a then + anchors[unicode] = { [target] = { anchor } } + return + end + local t = a[target] + if not t then + a[target] = { anchor } + return + end + local x = anchor[1] + local y = anchor[2] + for k, v in next, t do + if v[1] == x and v[2] == y then + return + end + end + t[#t+1] = anchor + end + + local function getanchors(steps,target) + for i=1,#steps do + local step = steps[i] + local coverage = step.coverage + for unicode, data in next, coverage do + local class = data[1] + local anchor = data[2] + if anchor[1] ~= 0 or anchor[2] ~= 0 then + set(unicode,target,class,anchor) + end + end + end + end + + local function getcursives(steps) + for i=1,#steps do + local step = steps[i] + local coverage = step.coverage + for unicode, data in next, coverage do + local class = data[1] + local en = data[2] + local ex = data[3] + if en then + set(unicode,"entry",class,en) + end + if ex then + set(unicode,"exit", class,ex) + end + end + end + end + + local function collect(list) + if list then + for i=1,#list do + local entry = list[i] + local steps = entry.steps + local kind = entry.type + if kind == "gpos_mark2mark" then + getanchors(steps,"mark") + elseif kind == "gpos_mark2base" then + getanchors(steps,"base") + elseif kind == "gpos_mark2ligature" then + getanchors(steps,"ligature") + elseif kind == "gpos_cursive" then + getcursives(steps) + end + end + end + end + + collect(resources.sequences) + collect(resources.sublookups) + + local function sorter(a,b) + if a[1] == b[1] then + return a[2] < b[2] + else + return a[1] < b[1] + end + end + + for unicode, old in next, anchors do + for target, list in next, old do + sort(list,sorter) + end + end + + resources.anchors = anchors + + return anchors + +end |