summaryrefslogtreecommitdiff
path: root/Master/texmf-dist/tex/context/base/mkxl/font-ctx.lmt
diff options
context:
space:
mode:
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.lmt3231
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