From 9a10021cd4cb23995ad3ffa915fc5b7f6890aaf8 Mon Sep 17 00:00:00 2001 From: Hans Hagen Date: Sun, 19 May 2013 19:27:00 +0200 Subject: beta 2013.05.19 19:27 --- tex/context/base/font-ctx.lua | 3638 ++++++++++++++++++++--------------------- 1 file changed, 1819 insertions(+), 1819 deletions(-) (limited to 'tex/context/base/font-ctx.lua') diff --git a/tex/context/base/font-ctx.lua b/tex/context/base/font-ctx.lua index 2583c6520..965542f0a 100644 --- a/tex/context/base/font-ctx.lua +++ b/tex/context/base/font-ctx.lua @@ -1,1819 +1,1819 @@ -if not modules then modules = { } end modules ['font-ctx'] = { - version = 1.001, - comment = "companion to font-ini.mkiv", - author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", - copyright = "PRAGMA ADE / ConTeXt Development Team", - license = "see context related readme files" -} - --- At some point I will clean up the code here so that at the tex end --- the table interface is used. --- --- Todo: make a proper 'next id' mechanism (register etc) or wait till 'true' --- in virtual fonts indices is implemented. - -local context, commands = context, commands - -local texcount, texsetcount = tex.count, tex.setcount -local format, gmatch, match, find, lower, gsub, byte = string.format, string.gmatch, string.match, string.find, string.lower, string.gsub, string.byte -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 settings_to_hash, hash_to_string = utilities.parsers.settings_to_hash, utilities.parsers.hash_to_string -local formatcolumns = utilities.formatters.formatcolumns -local mergehashes = utilities.parsers.mergehashes -local formatters = string.formatters - -local tostring, next, type, rawget, tonumber = tostring, next, type, rawget, tonumber -local utfchar, utfbyte = utf.char, utf.byte -local round = math.round - -local P, S, C, Cc, Cf, Cg, Ct, lpegmatch = lpeg.P, lpeg.S, lpeg.C, lpeg.Cc, lpeg.Cf, lpeg.Cg, lpeg.Ct, lpeg.match - -local trace_features = false trackers.register("fonts.features", function(v) trace_features = v end) -local trace_defining = false trackers.register("fonts.defining", function(v) trace_defining = v end) -local trace_designsize = false trackers.register("fonts.designsize", function(v) trace_designsize = v end) -local trace_usage = false trackers.register("fonts.usage", function(v) trace_usage = v end) -local trace_mapfiles = false trackers.register("fonts.mapfiles", function(v) trace_mapfiles = v end) -local trace_automode = false trackers.register("fonts.automode", function(v) trace_automode = v end) - -local 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 fonts = fonts -local handlers = fonts.handlers -local otf = handlers.otf -- 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 texattribute = tex.attribute -local texdimen = tex.dimen - -local fontdata = hashes.identifiers -local characters = hashes.chardata -local descriptions = hashes.descriptions -local properties = hashes.properties -local resources = hashes.resources -local csnames = hashes.csnames -local marks = hashes.markdata -local lastmathids = hashes.lastmathids - -local designsizefilename = fontgoodies.designsizes.filename - -local otffeatures = otf.features -local otftables = otf.tables - -local registerotffeature = otffeatures.register -local baseprocessors = otffeatures.processors.base -local baseinitializers = otffeatures.initializers.base - -local sequencers = utilities.sequencers -local appendgroup = sequencers.appendgroup -local appendaction = sequencers.appendaction - -specifiers.contextsetups = specifiers.contextsetups or { } -specifiers.contextnumbers = specifiers.contextnumbers or { } -specifiers.contextmerged = specifiers.contextmerged or { } -specifiers.synonyms = specifiers.synonyms or { } - -local setups = specifiers.contextsetups -local numbers = specifiers.contextnumbers -local merged = specifiers.contextmerged -local synonyms = specifiers.synonyms - -storage.register("fonts/setups" , setups , "fonts.specifiers.contextsetups" ) -storage.register("fonts/numbers", numbers, "fonts.specifiers.contextnumbers") -storage.register("fonts/merged", merged, "fonts.specifiers.contextmerged") -storage.register("fonts/synonyms", synonyms, "fonts.specifiers.synonyms") - --- inspect(setups) - -if environment.initex then - setmetatableindex(setups,function(t,k) - return type(k) == "number" and rawget(t,numbers[k]) or nil - end) -else - setmetatableindex(setups,function(t,k) - local v = type(k) == "number" and rawget(t,numbers[k]) - if v then - t[k] = v - return v - end - end) -end - --- this will move elsewhere ... - -utilities.strings.formatters.add(formatters,"font:name", [["'"..file.basename(%s.properties.name).."'"]]) -utilities.strings.formatters.add(formatters,"font:features",[["'"..table.sequenced(%s," ",true).."'"]]) - --- ... like font-sfm or so - -constructors.resolvevirtualtoo = true -- context specific (due to resolver) - -local limited = false - -directives.register("system.inputmode", function(v) - if not limited then - local i_limiter = io.i_limiter(v) - if i_limiter then - fontloader.open = i_limiter.protect(fontloader.open) - fontloader.info = i_limiter.protect(fontloader.info) - limited = true - end - end -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 - -- - constructors.enhanceparameters(parameters) -- official copies for us - -- - definers.resetnullfont = function() end -end - -commands.resetnullfont = definers.resetnullfont - --- 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 = { - gpos_mark2mark = true, - gpos_mark2base = true, - gpos_mark2ligature = true, -} - -otftables.scripts.auto = "automatic fallback to latn when no dflt present" - --- setmetatableindex(otffeatures.descriptions,otftables.features) - -local privatefeatures = { - tlig = true, - trep = true, - anum = true, -} - -local function checkedscript(tfmdata,resources,features) - local latn = false - local script = false - for g, list in next, resources.features do - for f, scripts in next, list do - if privatefeatures[f] then - -- skip - elseif scripts.dflt then - script = "dflt" - break - elseif scripts.latn then - latn = true - 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 - -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 - features.mode = "base" -- new, or is this wrong? - return "base" -end - -definers.checkedscript = checkedscript -definers.checkedmode = checkedmode - -local function modechecker(tfmdata,features,mode) -- we cannot adapt features as they are shared! - if trace_features then - report_features("fontname %!font:name!, features %!font:features!",tfmdata,features) - end - local rawdata = tfmdata.shared.rawdata - local resources = rawdata and rawdata.resources - local script = features.script - if resources then - if script == "auto" then - script = checkedscript(tfmdata,resources,features) - end - if mode == "auto" then - mode = checkedmode(tfmdata,resources,features) - end - else - report_features("missing resources for font %!font:name!",tfmdata) - end - return mode -end - -registerotffeature { - -- we only set the checker and leave other settings of the mode - -- feature as they are - name = "mode", - modechecker = modechecker, -} - --- -- default = true anyway --- --- local normalinitializer = constructors.getfeatureaction("otf","initializers","node","analyze") --- --- local function analyzeinitializer(tfmdata,value,features) -- attr --- if value == "auto" and features then --- value = features.init or features.medi or features.fina or features.isol or false --- end --- return normalinitializer(tfmdata,value,features) --- end --- --- registerotffeature { --- name = "analyze", --- initializers = { --- node = analyzeinitializer, --- }, --- } - -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 - ---[[ldx-- -

So far we haven't really dealt with features (or whatever we want -to pass along with the font definition. We distinguish the following -situations:

-situations:

- - -name:xetex like specs -name@virtual font spec -name*context specification - ---ldx]]-- - --- currently fonts are scaled while constructing the font, so we --- have to do scaling of commands in the vf at that point using e.g. --- "local scale = g.parameters.factor or 1" after all, we need to --- work with copies anyway and scaling needs to be done at some point; --- however, when virtual tricks are used as feature (makes more --- sense) we scale the commands in fonts.constructors.scale (and set the --- factor there) - -local loadfont = definers.loadfont - -function definers.loadfont(specification,size,id) -- overloads the one in font-def - local variants = definers.methods.variants - local virtualfeatures = specification.features.virtual - if virtualfeatures and virtualfeatures.preset then - local variant = variants[virtualfeatures.preset] - if variant then - return variant(specification,size,id) - end - else - local tfmdata = loadfont(specification,size,id) - -- constructors.checkvirtualid(tfmdata,id) - return tfmdata - end -end - -local function predefined(specification) - local variants = definers.methods.variants - local detail = specification.detail - if detail ~= "" and variants[detail] then - specification.features.virtual = { preset = detail } - end - return specification -end - -definers.registersplit("@", predefined,"virtual") - -local normalize_features = otffeatures.normalize -- should be general - -local function definecontext(name,t) -- can be shared - local number = setups[name] and setups[name].number or 0 -- hm, numbers[name] - if number == 0 then - number = #numbers + 1 - numbers[number] = name - end - t.number = number - setups[name] = t - return number, t -end - -local function presetcontext(name,parent,features) -- will go to con and shared - if features == "" and find(parent,"=") then - features = parent - parent = "" - end - if not features or features == "" then - features = { } - elseif type(features) == "string" then - features = normalize_features(settings_to_hash(features)) - 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 - 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 - -- needed for dynamic features - -- maybe number should always be renewed as we can redefine features - 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 - -local function contextnumber(name) -- will be replaced - local t = setups[name] - if not t then - return 0 - elseif t.auto then - local lng = tonumber(tex.language) - local tag = name .. ":" .. lng - local s = setups[tag] - if s then - return s.number or 0 - else - local script, language = languages.association(lng) - if t.script ~= script or t.language ~= language then - local s = fastcopy(t) - local n = #numbers + 1 - setups[tag] = s - numbers[n] = tag - s.number = n - s.script = script - s.language = language - return n - else - setups[tag] = t - return t.number or 0 - end - end - else - return t.number or 0 - end -end - -local function 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, mergedname = { }, nil - if option < 0 then - if current then - for k, v in next, current do - if not extra[k] then - mergedfeatures[k] = v - end - end - end - mergedname = currentnumber .. "-" .. extraname - else - if current then - for k, v in next, current do - mergedfeatures[k] = v - end - end - for k, v in next, extra do - mergedfeatures[k] = v - end - mergedname = currentnumber .. "+" .. extraname - end - local number = #numbers + 1 - mergedfeatures.number = number - numbers[number] = mergedname - merged[number] = option - setups[mergedname] = mergedfeatures - return number -- contextnumber(mergedname) - else - return currentnumber - end -end - -local extrasets = { } - -setmetatableindex(extrasets,function(t,k) - local v = mergehashes(setups,k) - t[k] = v - return v -end) - -local function mergecontextfeatures(currentname,extraname,how,mergedname) -- string string - local extra = setups[extraname] or extrasets[extraname] - if extra then - local current = setups[currentname] - local mergedfeatures = { } - if how == "+" then - if current then - for k, v in next, current do - mergedfeatures[k] = v - end - end - for k, v in next, extra do - mergedfeatures[k] = v - end - 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 - else -- = - for k, v in next, extra do - mergedfeatures[k] = v - 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, mergedname = { }, nil - if option < 0 then - mergedname = fontnumber .. "-" .. extraname - else - mergedname = fontnumber .. "+" .. extraname - end - for k, v in next, extra do - mergedfeatures[k] = v - end - local number = #numbers + 1 - mergedfeatures.number = number - numbers[number] = mergedname - merged[number] = option - setups[mergedname] = mergedfeatures - return number -- contextnumber(mergedname) - else - return 0 - end -end - -local function registercontextfeature(mergedname,extraname,how) - local extra = setups[extraname] - if extra then - local mergedfeatures = { } - for k, v in next, extra do - mergedfeatures[k] = v - end - local number = #numbers + 1 - mergedfeatures.number = number - numbers[number] = mergedname - merged[number] = how == "=" and 1 or 2 -- 1=replace, 2=combine - setups[mergedname] = mergedfeatures - return number -- contextnumber(mergedname) - else - return 0 - end -end - -specifiers.presetcontext = presetcontext -specifiers.contextnumber = contextnumber -specifiers.mergecontext = mergecontext -specifiers.registercontext = registercontext -specifiers.definecontext = definecontext - --- we extend the hasher: - -constructors.hashmethods.virtual = function(list) - local s = { } - local n = 0 - for k, v in next, list do - n = n + 1 - s[n] = k -- no checking on k - end - if n > 0 then - sort(s) - for i=1,n do - local k = s[i] - s[i] = k .. '=' .. tostring(list[k]) - end - 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 = texattribute[0] --- local hash = zero .. "+" .. name .. "*" .. what --- local done = withcache[hash] --- if not done then --- done = mergecontext(zero,name,what) --- withcache[hash] = done --- end --- texattribute[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 --- texattribute[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 splitter = lpeg.splitat("=") --- --- local function splitcontext(features) --- local setup = setups[features] --- if setup then --- return setup --- elseif find(features,",") 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,"=") 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) -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 separator = S(";,") -local equal = P("=") -local spaces = space^0 -local sometext = C((1-equal-space-separator)^1) -local truevalue = P("+") * spaces * sometext * Cc(true) -- "yes" -local falsevalue = P("-") * spaces * sometext * Cc(false) -- "no" -local keyvalue = sometext * spaces * equal * spaces * sometext -local somevalue = sometext * spaces * Cc(true) -- "yes" -local pattern = Cf(Ct("") * (space + separator + Cg(keyvalue + falsevalue + truevalue + 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 space = P(" ") -local spaces = space^0 -local leftparent = (P"(") -local rightparent = (P")") -local value = C((leftparent * (1-rightparent)^0 * rightparent + (1-space))^1) -local dimension = C((space/"" + P(1))^1) -local rest = C(P(1)^0) -local scale_none = Cc(0) -local scale_at = P("at") * Cc(1) * spaces * dimension -- value -local scale_sa = P("sa") * Cc(2) * spaces * dimension -- value -local scale_mo = P("mo") * Cc(3) * spaces * dimension -- value -local scale_scaled = P("scaled") * Cc(4) * spaces * dimension -- value - -local sizepattern = spaces * (scale_at + scale_sa + scale_mo + scale_scaled + scale_none) -local splitpattern = spaces * value * spaces * rest - -function helpers.splitfontpattern(str) - local name, size = lpegmatch(splitpattern,str) - local kind, size = lpegmatch(sizepattern,size) - return name, kind, size -end - -function helpers.fontpatternhassize(str) - local name, size = lpegmatch(splitpattern,str) - local kind, size = lpegmatch(sizepattern,size) - return size or false -end - -local specification -- still needed as local ? - -local getspecification = definers.getspecification - --- we can make helper macros which saves parsing (but normaly not --- that many calls, e.g. in mk a couple of 100 and in metafun 3500) - -local setdefaultfontname = context.fntsetdefname -local setsomefontname = context.fntsetsomename -local setemptyfontsize = context.fntsetnopsize -local setsomefontsize = context.fntsetsomesize -local letvaluerelax = context.letvaluerelax - -function commands.definefont_one(str) - statistics.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) - setdefaultfontname() - elseif name == "unknown" then - setdefaultfontname() - else - setsomefontname(name) - end - -- we can also use a count for the size - if size and size ~= "" then - local mode, size = lpegmatch(sizepattern,size) - if size and mode then - texcount.scaledfontmode = mode - setsomefontsize(size) - else - texcount.scaledfontmode = 0 - setemptyfontsize() - end - elseif true then - -- so we don't need to check in tex - texcount.scaledfontmode = 2 - setemptyfontsize() - else - texcount.scaledfontmode = 0 - setemptyfontsize() - end - specification = definers.makespecification(str,lookup,name,sub,method,detail,size) - if trace_defining then - report_defining("stop stage one") - end -end - -local n = 0 - --- we can also move rscale to here (more consistent) --- the argument list will become a table - -local function nice_cs(cs) - return (gsub(cs,".->", "")) -end - -function commands.definefont_two(global,cs,str,size,inheritancemode,classfeatures,fontfeatures,classfallbacks,fontfallbacks, - mathsize,textsize,relativeid,classgoodies,goodies,classdesignsize,fontdesignsize) - if trace_defining then - report_defining("start stage two: %s (size %s)",str,size) - end - -- name is now resolved and size is scaled cf sa/mo - local lookup, name, sub, method, detail = getspecification(str or "") - -- new (todo: inheritancemode) - local designsize = fontdesignsize ~= "" and fontdesignsize or classdesignsize or "" - local designname = designsizefilename(name,designsize,size) - if designname and designname ~= "" then - if trace_defining or trace_designsize then - report_defining("remapping name %a, specification %a, size %a, designsize %a",name,designsize,size,designname) - end - -- we don't catch detail here - local o_lookup, o_name, o_sub, o_method, o_detail = getspecification(designname) - if o_lookup and o_lookup ~= "" then lookup = o_lookup end - if o_method and o_method ~= "" then method = o_method end - if o_detail and o_detail ~= "" then detail = o_detail end - name = o_name - sub = o_sub - end - -- so far - -- some settings can have been overloaded - if lookup and lookup ~= "" then - specification.lookup = lookup - end - if relativeid and relativeid ~= "" then -- experimental hook - local id = tonumber(relativeid) or 0 - specification.relativeid = id > 0 and id - end - specification.name = name - specification.size = size - specification.sub = (sub and sub ~= "" and sub) or specification.sub - specification.mathsize = mathsize - specification.textsize = textsize - specification.goodies = goodies - specification.cs = cs - specification.global = global - 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 fontfeatures and fontfeatures ~= "" then - specification.method = "*" - specification.detail = fontfeatures - elseif classfeatures and classfeatures ~= "" then - specification.method = "*" - specification.detail = classfeatures - end - if fontfallbacks and fontfallbacks ~= "" then - specification.fallbacks = fontfallbacks - elseif classfallbacks and classfallbacks ~= "" then - specification.fallbacks = classfallbacks - end - elseif inheritancemode == 4 then - -- classfirst - if classfeatures and classfeatures ~= "" then - specification.method = "*" - specification.detail = classfeatures - elseif fontfeatures and fontfeatures ~= "" then - specification.method = "*" - specification.detail = fontfeatures - 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 - if not tfmdata then - report_defining("unable to define %a as %a",name,nice_cs(cs)) - lastfontid = -1 - letvaluerelax(cs) -- otherwise the current definition takes the previous one - elseif type(tfmdata) == "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 - tex.definefont(global,cs,tfmdata) - -- resolved (when designsize is used): - setsomefontsize((fontdata[tfmdata].parameters.size or 0) .. "sp") - lastfontid = tfmdata - else - -- setting the extra characters will move elsewhere - local characters = tfmdata.characters - local parameters = tfmdata.parameters - -- 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 id = font.define(tfmdata) - csnames[id] = specification.cs - tfmdata.properties.id = id - definers.register(tfmdata,id) -- to be sure, normally already done - tex.definefont(global,cs,id) - constructors.cleanuptable(tfmdata) - constructors.finalize(tfmdata) - if trace_defining then - report_defining("defining %a, id %a, target %a, features %a / %a, fallbacks %a / %a", - name,id,nice_cs(cs),classfeatures,fontfeatures,classfallbacks,fontfallbacks) - end - -- resolved (when designsize is used): - setsomefontsize((tfmdata.parameters.size or 655360) .. "sp") - lastfontid = id - end - if trace_defining then - report_defining("memory usage after: %s",statistics.memused()) - report_defining("stop stage two") - end - -- - texsetcount("global","lastfontid",lastfontid) - if not mathsize then - -- forget about it - elseif mathsize == 0 then - lastmathids[1] = lastfontid - else - lastmathids[mathsize] = lastfontid - end - -- - statistics.stoptiming(fonts) -end - -function definers.define(specification) - -- - local name = specification.name - if not name or name == "" then - return -1 - else - statistics.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 = tex.sp(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 - tex.definefont(specification.global,cs,tfmdata) - csnames[tfmdata] = cs - end - return tfmdata, fontdata[tfmdata] - else - local id = font.define(tfmdata) - tfmdata.properties.id = id - definers.register(tfmdata,id) - if cs then - tex.definefont(specification.global,cs,id) - csnames[id] = cs - end - constructors.cleanuptable(tfmdata) - constructors.finalize(tfmdata) - return id, tfmdata - end - statistics.stoptiming(fonts) - end -end - --- 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 = specification.size and number.todimen(specification.size) or texdimen.bodyfontsize - local number = tonumber(specification.number) - local id = nil - 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 = definers.define { - name = name, - size = size, - cs = cs, - } - end - if not id then - id = currentfont() - end - return id, csnames[id] -end - -local enable_auto_r_scale = false - -experiments.register("fonts.autorscale", function(v) - enable_auto_r_scale = v -end) - --- Not ok, we can best use a database for this. The problem is that we --- have delayed definitions and so we never know what style is taken --- as start. - -local calculatescale = constructors.calculatescale - -function constructors.calculatescale(tfmdata,scaledpoints,relativeid) - local scaledpoints, delta = calculatescale(tfmdata,scaledpoints) - -- if enable_auto_r_scale and relativeid then -- for the moment this is rather context specific - -- local relativedata = fontdata[relativeid] - -- local rfmdata = relativedata and relativedata.unscaled and relativedata.unscaled - -- local id_x_height = rfmdata and rfmdata.parameters and rfmdata.parameters.x_height - -- local tf_x_height = tfmdata and tfmdata.parameters and tfmdata.parameters.x_height - -- if id_x_height and tf_x_height then - -- local rscale = id_x_height/tf_x_height - -- delta = rscale * delta - -- scaledpoints = rscale * scaledpoints - -- end - -- end - return scaledpoints, delta -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 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 - specification.hash = lower(specification.name .. ' @ ' .. hashfeatures(specification)) - if specification.sub and specification.sub ~= "" then - specification.hash = specification.sub .. ' @ ' .. specification.hash - end - return specification -end - - --- soon to be obsolete: - -local mappings = fonts.mappings - -local loaded = { -- prevent loading (happens in cont-sys files) - ["original-base.map" ] = true, - ["original-ams-base.map" ] = true, - ["original-ams-euler.map"] = true, - ["original-public-lm.map"] = true, -} - -function mappings.loadfile(name) - name = file.addsuffix(name,"map") - if not loaded[name] then - if trace_mapfiles then - report_mapfiles("loading map file %a",name) - end - pdf.mapfile(name) - loaded[name] = true - end -end - -local loaded = { -- prevent double loading -} - -function mappings.loadline(how,line) - if line then - how = how .. " " .. line - elseif how == "" then - how = "= " .. line - end - if not loaded[how] then - if trace_mapfiles then - report_mapfiles("processing map line %a",line) - end - pdf.mapline(how) - loaded[how] = true - end -end - -function mappings.reset() - pdf.mapfile("") -end - -mappings.reset() -- resets the default file - --- we need an 'do after the banner hook' - --- => commands - -local function nametoslot(name) - local t = type(name) - if t == "string" then - return resources[true].unicodes[name] - elseif t == "number" then - return n - end -end - -helpers.nametoslot = nametoslot - --- 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] = { - format("%03i",id or 0), - format("%09i",parameters.size or 0), - properties.type or "real", - properties.format or "unknown", - properties.name or "", - properties.psname or "", - properties.fullname or "", - } - report_status("%s: % t",properties.name,sortedkeys(data)) - end - formatcolumns(t," ") - report_status() - report_status("defined fonts:") - report_status() - for k=1,tn do - report_status(t[k]) - end - end -end - -luatex.registerstopactions(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," ") - report_status() - report_status("defined featuresets:") - report_status() - for k=1,n do - report_status(t[k]) - end - end -end - -luatex.registerstopactions(loggers.reportusedfeatures) - -statistics.register("fonts load time", function() - return statistics.elapsedseconds(fonts) -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", -} - -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" - context.startfontclass { name } - context.definefontsynonym( { format("%s", Shape) }, { format("spec:%s-%s-regular-%s", fontname, normalweight, normalwidth) } ) - context.definefontsynonym( { format("%sBold", Shape) }, { format("spec:%s-%s-regular-%s", fontname, boldweight, boldwidth ) } ) - context.definefontsynonym( { format("%sBoldItalic", Shape) }, { format("spec:%s-%s-italic-%s", fontname, boldweight, boldwidth ) } ) - context.definefontsynonym( { format("%sItalic", Shape) }, { format("spec:%s-%s-italic-%s", fontname, normalweight, normalwidth) } ) - context.stopfontclass() - local settings = sequenced({ features= t.features },",") - context.dofastdefinetypeface(name, shortcut, shape, size, settings) -end - -function fonts.current() -- todo: also handle name - return fontdata[currentfont()] or fontdata[0] -end - -function fonts.currentid() - return currentfont() or 0 -end - --- interfaces - -function commands.fontchar(n) - n = nametoslot(n) - if n then - context.char(n) - end -end - -function commands.doifelsecurrentfonthasfeature(name) -- can be made faster with a supportedfeatures hash - local f = fontdata[currentfont()] - f = f and f.shared - f = f and f.rawdata - f = f and f.resources - f = f and f.features - commands.doifelse(f and (f.gpos[name] or f.gsub[name])) -end - -local p, f = 1, formatters["%0.1fpt"] -- normally this value is changed only once - -local stripper = lpeg.patterns.stripzeros - -function commands.nbfs(amount,precision) - if precision ~= p then - p = precision - f = formatters["%0." .. p .. "fpt"] - end - context(lpegmatch(stripper,f(amount/65536))) -end - -function commands.featureattribute(tag) - context(contextnumber(tag)) -end - -function commands.setfontfeature(tag) - texattribute[0] = contextnumber(tag) -end - -function commands.resetfontfeature() - texattribute[0] = 0 -end - --- function commands.addfs(tag) withset(tag, 1) end --- function commands.subfs(tag) withset(tag,-1) end --- function commands.addff(tag) withfnt(tag, 2) end -- on top of font features --- function commands.subff(tag) withfnt(tag,-2) end -- on top of font features - -function commands.cleanfontname (name) context(names.cleanname(name)) end - -function commands.fontlookupinitialize (name) names.lookup(name) end -function commands.fontlookupnoffound () context(names.noflookups()) end -function commands.fontlookupgetkeyofindex(key,index) context(names.getlookupkey(key,index)) end -function commands.fontlookupgetkey (key) context(names.getlookupkey(key)) end - --- 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 - --- 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,tfmdata) -- could be a method of a font instance - if unit == "ex" then - return (tfmdata and tfmdata.parameters.x_height) or 655360 - elseif unit == "em" then - return (tfmdata and tfmdata.parameters.em_width) or 655360 - 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.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 quads = hashes.quads -local xheights = hashes.xheights - -setmetatableindex(number.dimenfactors, function(t,k) - if k == "ex" then - return xheigths[currentfont()] - elseif k == "em" then - return quads[currentfont()] - elseif k == "%" then - return dimen.hsize/100 - else - -- error("wrong dimension: " .. (s or "?")) -- better a message - return false - end -end) - ---[[ldx-- -

Before a font is passed to we scale it. Here we also need -to scale virtual characters.

---ldx]]-- - -function constructors.checkvirtualids(tfmdata) - -- begin of experiment: we can use { "slot", 0, number } in virtual fonts - local fonts = tfmdata.fonts - local selfid = font.nextid() - if fonts and #fonts > 0 then - for i=1,#fonts do - if fonts[i][2] == 0 then - fonts[i][2] = selfid - end - end - else - -- tfmdata.fonts = { "id", selfid } -- conflicts with other next id's (vf math), too late anyway - end - -- end of experiment -end - --- function constructors.getvirtualid(tfmdata) --- -- since we don't know the id yet, we use 0 as signal --- local tf = tfmdata.fonts --- if not tf then --- local properties = tfmdata.properties --- if properties then --- properties.virtualized = true --- else --- tfmdata.properties = { virtualized = true } --- end --- tf = { } --- tfmdata.fonts = tf --- end --- local ntf = #tf + 1 --- tf[ntf] = { id = 0 } --- return ntf --- end --- --- function constructors.checkvirtualid(tfmdata, id) -- will go --- local properties = tfmdata.properties --- if tfmdata and tfmdata.type == "virtual" or (properties and properties.virtualized) then --- local vfonts = tfmdata.fonts --- if not vffonts or #vfonts == 0 then --- if properties then --- properties.virtualized = false --- end --- tfmdata.fonts = nil --- else --- for f=1,#vfonts do --- local fnt = vfonts[f] --- if fnt.id and fnt.id == 0 then --- fnt.id = id --- end --- end --- end --- end --- end - -function commands.setfontofid(id) - context.getvalue(csnames[id]) -end - --- more interfacing: - -commands.definefontfeature = presetcontext - -local cache = { } - -local hows = { - ["+"] = "add", - ["-"] = "subtract", - ["="] = "replace", -} - -function commands.feature(how,parent,name,font) - if not how then - if trace_features and texattribute[0] ~= 0 then - report_cummulative("font %!font:name!, reset",fontdata[font or true]) - end - texattribute[0] = 0 - elseif how == true 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 - texattribute[0] = 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 - texattribute[0] = done - end -end - -function commands.featurelist(...) - context(fonts.specifiers.contexttostring(...)) -end - -function commands.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 - --- a fontkern plug: - -local copy_node = node.copy -local kern = nodes.pool.register(nodes.pool.kern()) - -node.set_attribute(kern,attributes.private('fontkern'),1) -- we can have several, attributes are shared - -nodes.injections.installnewkern(function(k) - local c = copy_node(kern) - c.kern = k - return c -end) - -directives.register("nodes.injections.fontkern", function(v) kern.subtype = v and 0 or 1 end) - --- here - -local trace_analyzing = false trackers.register("otf.analyzing", function(v) trace_analyzing = v end) - -local otffeatures = fonts.constructors.newfeatures("otf") -local registerotffeature = otffeatures.register - -local analyzers = fonts.analyzers -local methods = analyzers.methods - -local unsetvalue = attributes.unsetvalue - -local traverse_by_id = node.traverse_id - -local a_color = attributes.private('color') -local a_colormodel = attributes.private('colormodel') -local a_state = attributes.private('state') -local m_color = attributes.list[a_color] or { } - -local glyph_code = nodes.nodecodes.glyph - -local states = analyzers.states - -local names = { - [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", -} - -local function markstates(head) - if head then - local model = head[a_colormodel] or 1 - for glyph in traverse_by_id(glyph_code,head) do - local a = glyph[a_state] - if a then - local name = names[a] - if name then - local color = m_color[name] - if color then - glyph[a_colormodel] = model - 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 in traverse_by_id(glyph_code,head) do - if not font or n.font == font then - n[a_color] = unsetvalue - end - end - return head, true -end +if not modules then modules = { } end modules ['font-ctx'] = { + version = 1.001, + comment = "companion to font-ini.mkiv", + author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright = "PRAGMA ADE / ConTeXt Development Team", + license = "see context related readme files" +} + +-- At some point I will clean up the code here so that at the tex end +-- the table interface is used. +-- +-- Todo: make a proper 'next id' mechanism (register etc) or wait till 'true' +-- in virtual fonts indices is implemented. + +local context, commands = context, commands + +local texcount, texsetcount = tex.count, tex.setcount +local format, gmatch, match, find, lower, gsub, byte = string.format, string.gmatch, string.match, string.find, string.lower, string.gsub, string.byte +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 settings_to_hash, hash_to_string = utilities.parsers.settings_to_hash, utilities.parsers.hash_to_string +local formatcolumns = utilities.formatters.formatcolumns +local mergehashes = utilities.parsers.mergehashes +local formatters = string.formatters + +local tostring, next, type, rawget, tonumber = tostring, next, type, rawget, tonumber +local utfchar, utfbyte = utf.char, utf.byte +local round = math.round + +local P, S, C, Cc, Cf, Cg, Ct, lpegmatch = lpeg.P, lpeg.S, lpeg.C, lpeg.Cc, lpeg.Cf, lpeg.Cg, lpeg.Ct, lpeg.match + +local trace_features = false trackers.register("fonts.features", function(v) trace_features = v end) +local trace_defining = false trackers.register("fonts.defining", function(v) trace_defining = v end) +local trace_designsize = false trackers.register("fonts.designsize", function(v) trace_designsize = v end) +local trace_usage = false trackers.register("fonts.usage", function(v) trace_usage = v end) +local trace_mapfiles = false trackers.register("fonts.mapfiles", function(v) trace_mapfiles = v end) +local trace_automode = false trackers.register("fonts.automode", function(v) trace_automode = v end) + +local 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 fonts = fonts +local handlers = fonts.handlers +local otf = handlers.otf -- 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 texattribute = tex.attribute +local texdimen = tex.dimen + +local fontdata = hashes.identifiers +local characters = hashes.chardata +local descriptions = hashes.descriptions +local properties = hashes.properties +local resources = hashes.resources +local csnames = hashes.csnames +local marks = hashes.markdata +local lastmathids = hashes.lastmathids + +local designsizefilename = fontgoodies.designsizes.filename + +local otffeatures = otf.features +local otftables = otf.tables + +local registerotffeature = otffeatures.register +local baseprocessors = otffeatures.processors.base +local baseinitializers = otffeatures.initializers.base + +local sequencers = utilities.sequencers +local appendgroup = sequencers.appendgroup +local appendaction = sequencers.appendaction + +specifiers.contextsetups = specifiers.contextsetups or { } +specifiers.contextnumbers = specifiers.contextnumbers or { } +specifiers.contextmerged = specifiers.contextmerged or { } +specifiers.synonyms = specifiers.synonyms or { } + +local setups = specifiers.contextsetups +local numbers = specifiers.contextnumbers +local merged = specifiers.contextmerged +local synonyms = specifiers.synonyms + +storage.register("fonts/setups" , setups , "fonts.specifiers.contextsetups" ) +storage.register("fonts/numbers", numbers, "fonts.specifiers.contextnumbers") +storage.register("fonts/merged", merged, "fonts.specifiers.contextmerged") +storage.register("fonts/synonyms", synonyms, "fonts.specifiers.synonyms") + +-- inspect(setups) + +if environment.initex then + setmetatableindex(setups,function(t,k) + return type(k) == "number" and rawget(t,numbers[k]) or nil + end) +else + setmetatableindex(setups,function(t,k) + local v = type(k) == "number" and rawget(t,numbers[k]) + if v then + t[k] = v + return v + end + end) +end + +-- this will move elsewhere ... + +utilities.strings.formatters.add(formatters,"font:name", [["'"..file.basename(%s.properties.name).."'"]]) +utilities.strings.formatters.add(formatters,"font:features",[["'"..table.sequenced(%s," ",true).."'"]]) + +-- ... like font-sfm or so + +constructors.resolvevirtualtoo = true -- context specific (due to resolver) + +local limited = false + +directives.register("system.inputmode", function(v) + if not limited then + local i_limiter = io.i_limiter(v) + if i_limiter then + fontloader.open = i_limiter.protect(fontloader.open) + fontloader.info = i_limiter.protect(fontloader.info) + limited = true + end + end +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 + -- + constructors.enhanceparameters(parameters) -- official copies for us + -- + definers.resetnullfont = function() end +end + +commands.resetnullfont = definers.resetnullfont + +-- 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 = { + gpos_mark2mark = true, + gpos_mark2base = true, + gpos_mark2ligature = true, +} + +otftables.scripts.auto = "automatic fallback to latn when no dflt present" + +-- setmetatableindex(otffeatures.descriptions,otftables.features) + +local privatefeatures = { + tlig = true, + trep = true, + anum = true, +} + +local function checkedscript(tfmdata,resources,features) + local latn = false + local script = false + for g, list in next, resources.features do + for f, scripts in next, list do + if privatefeatures[f] then + -- skip + elseif scripts.dflt then + script = "dflt" + break + elseif scripts.latn then + latn = true + 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 + +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 + features.mode = "base" -- new, or is this wrong? + return "base" +end + +definers.checkedscript = checkedscript +definers.checkedmode = checkedmode + +local function modechecker(tfmdata,features,mode) -- we cannot adapt features as they are shared! + if trace_features then + report_features("fontname %!font:name!, features %!font:features!",tfmdata,features) + end + local rawdata = tfmdata.shared.rawdata + local resources = rawdata and rawdata.resources + local script = features.script + if resources then + if script == "auto" then + script = checkedscript(tfmdata,resources,features) + end + if mode == "auto" then + mode = checkedmode(tfmdata,resources,features) + end + else + report_features("missing resources for font %!font:name!",tfmdata) + end + return mode +end + +registerotffeature { + -- we only set the checker and leave other settings of the mode + -- feature as they are + name = "mode", + modechecker = modechecker, +} + +-- -- default = true anyway +-- +-- local normalinitializer = constructors.getfeatureaction("otf","initializers","node","analyze") +-- +-- local function analyzeinitializer(tfmdata,value,features) -- attr +-- if value == "auto" and features then +-- value = features.init or features.medi or features.fina or features.isol or false +-- end +-- return normalinitializer(tfmdata,value,features) +-- end +-- +-- registerotffeature { +-- name = "analyze", +-- initializers = { +-- node = analyzeinitializer, +-- }, +-- } + +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 + +--[[ldx-- +

So far we haven't really dealt with features (or whatever we want +to pass along with the font definition. We distinguish the following +situations:

+situations:

+ + +name:xetex like specs +name@virtual font spec +name*context specification + +--ldx]]-- + +-- currently fonts are scaled while constructing the font, so we +-- have to do scaling of commands in the vf at that point using e.g. +-- "local scale = g.parameters.factor or 1" after all, we need to +-- work with copies anyway and scaling needs to be done at some point; +-- however, when virtual tricks are used as feature (makes more +-- sense) we scale the commands in fonts.constructors.scale (and set the +-- factor there) + +local loadfont = definers.loadfont + +function definers.loadfont(specification,size,id) -- overloads the one in font-def + local variants = definers.methods.variants + local virtualfeatures = specification.features.virtual + if virtualfeatures and virtualfeatures.preset then + local variant = variants[virtualfeatures.preset] + if variant then + return variant(specification,size,id) + end + else + local tfmdata = loadfont(specification,size,id) + -- constructors.checkvirtualid(tfmdata,id) + return tfmdata + end +end + +local function predefined(specification) + local variants = definers.methods.variants + local detail = specification.detail + if detail ~= "" and variants[detail] then + specification.features.virtual = { preset = detail } + end + return specification +end + +definers.registersplit("@", predefined,"virtual") + +local normalize_features = otffeatures.normalize -- should be general + +local function definecontext(name,t) -- can be shared + local number = setups[name] and setups[name].number or 0 -- hm, numbers[name] + if number == 0 then + number = #numbers + 1 + numbers[number] = name + end + t.number = number + setups[name] = t + return number, t +end + +local function presetcontext(name,parent,features) -- will go to con and shared + if features == "" and find(parent,"=") then + features = parent + parent = "" + end + if not features or features == "" then + features = { } + elseif type(features) == "string" then + features = normalize_features(settings_to_hash(features)) + 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 + 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 + -- needed for dynamic features + -- maybe number should always be renewed as we can redefine features + 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 + +local function contextnumber(name) -- will be replaced + local t = setups[name] + if not t then + return 0 + elseif t.auto then + local lng = tonumber(tex.language) + local tag = name .. ":" .. lng + local s = setups[tag] + if s then + return s.number or 0 + else + local script, language = languages.association(lng) + if t.script ~= script or t.language ~= language then + local s = fastcopy(t) + local n = #numbers + 1 + setups[tag] = s + numbers[n] = tag + s.number = n + s.script = script + s.language = language + return n + else + setups[tag] = t + return t.number or 0 + end + end + else + return t.number or 0 + end +end + +local function 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, mergedname = { }, nil + if option < 0 then + if current then + for k, v in next, current do + if not extra[k] then + mergedfeatures[k] = v + end + end + end + mergedname = currentnumber .. "-" .. extraname + else + if current then + for k, v in next, current do + mergedfeatures[k] = v + end + end + for k, v in next, extra do + mergedfeatures[k] = v + end + mergedname = currentnumber .. "+" .. extraname + end + local number = #numbers + 1 + mergedfeatures.number = number + numbers[number] = mergedname + merged[number] = option + setups[mergedname] = mergedfeatures + return number -- contextnumber(mergedname) + else + return currentnumber + end +end + +local extrasets = { } + +setmetatableindex(extrasets,function(t,k) + local v = mergehashes(setups,k) + t[k] = v + return v +end) + +local function mergecontextfeatures(currentname,extraname,how,mergedname) -- string string + local extra = setups[extraname] or extrasets[extraname] + if extra then + local current = setups[currentname] + local mergedfeatures = { } + if how == "+" then + if current then + for k, v in next, current do + mergedfeatures[k] = v + end + end + for k, v in next, extra do + mergedfeatures[k] = v + end + 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 + else -- = + for k, v in next, extra do + mergedfeatures[k] = v + 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, mergedname = { }, nil + if option < 0 then + mergedname = fontnumber .. "-" .. extraname + else + mergedname = fontnumber .. "+" .. extraname + end + for k, v in next, extra do + mergedfeatures[k] = v + end + local number = #numbers + 1 + mergedfeatures.number = number + numbers[number] = mergedname + merged[number] = option + setups[mergedname] = mergedfeatures + return number -- contextnumber(mergedname) + else + return 0 + end +end + +local function registercontextfeature(mergedname,extraname,how) + local extra = setups[extraname] + if extra then + local mergedfeatures = { } + for k, v in next, extra do + mergedfeatures[k] = v + end + local number = #numbers + 1 + mergedfeatures.number = number + numbers[number] = mergedname + merged[number] = how == "=" and 1 or 2 -- 1=replace, 2=combine + setups[mergedname] = mergedfeatures + return number -- contextnumber(mergedname) + else + return 0 + end +end + +specifiers.presetcontext = presetcontext +specifiers.contextnumber = contextnumber +specifiers.mergecontext = mergecontext +specifiers.registercontext = registercontext +specifiers.definecontext = definecontext + +-- we extend the hasher: + +constructors.hashmethods.virtual = function(list) + local s = { } + local n = 0 + for k, v in next, list do + n = n + 1 + s[n] = k -- no checking on k + end + if n > 0 then + sort(s) + for i=1,n do + local k = s[i] + s[i] = k .. '=' .. tostring(list[k]) + end + 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 = texattribute[0] +-- local hash = zero .. "+" .. name .. "*" .. what +-- local done = withcache[hash] +-- if not done then +-- done = mergecontext(zero,name,what) +-- withcache[hash] = done +-- end +-- texattribute[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 +-- texattribute[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 splitter = lpeg.splitat("=") +-- +-- local function splitcontext(features) +-- local setup = setups[features] +-- if setup then +-- return setup +-- elseif find(features,",") 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,"=") 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) +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 separator = S(";,") +local equal = P("=") +local spaces = space^0 +local sometext = C((1-equal-space-separator)^1) +local truevalue = P("+") * spaces * sometext * Cc(true) -- "yes" +local falsevalue = P("-") * spaces * sometext * Cc(false) -- "no" +local keyvalue = sometext * spaces * equal * spaces * sometext +local somevalue = sometext * spaces * Cc(true) -- "yes" +local pattern = Cf(Ct("") * (space + separator + Cg(keyvalue + falsevalue + truevalue + 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 space = P(" ") +local spaces = space^0 +local leftparent = (P"(") +local rightparent = (P")") +local value = C((leftparent * (1-rightparent)^0 * rightparent + (1-space))^1) +local dimension = C((space/"" + P(1))^1) +local rest = C(P(1)^0) +local scale_none = Cc(0) +local scale_at = P("at") * Cc(1) * spaces * dimension -- value +local scale_sa = P("sa") * Cc(2) * spaces * dimension -- value +local scale_mo = P("mo") * Cc(3) * spaces * dimension -- value +local scale_scaled = P("scaled") * Cc(4) * spaces * dimension -- value + +local sizepattern = spaces * (scale_at + scale_sa + scale_mo + scale_scaled + scale_none) +local splitpattern = spaces * value * spaces * rest + +function helpers.splitfontpattern(str) + local name, size = lpegmatch(splitpattern,str) + local kind, size = lpegmatch(sizepattern,size) + return name, kind, size +end + +function helpers.fontpatternhassize(str) + local name, size = lpegmatch(splitpattern,str) + local kind, size = lpegmatch(sizepattern,size) + return size or false +end + +local specification -- still needed as local ? + +local getspecification = definers.getspecification + +-- we can make helper macros which saves parsing (but normaly not +-- that many calls, e.g. in mk a couple of 100 and in metafun 3500) + +local setdefaultfontname = context.fntsetdefname +local setsomefontname = context.fntsetsomename +local setemptyfontsize = context.fntsetnopsize +local setsomefontsize = context.fntsetsomesize +local letvaluerelax = context.letvaluerelax + +function commands.definefont_one(str) + statistics.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) + setdefaultfontname() + elseif name == "unknown" then + setdefaultfontname() + else + setsomefontname(name) + end + -- we can also use a count for the size + if size and size ~= "" then + local mode, size = lpegmatch(sizepattern,size) + if size and mode then + texcount.scaledfontmode = mode + setsomefontsize(size) + else + texcount.scaledfontmode = 0 + setemptyfontsize() + end + elseif true then + -- so we don't need to check in tex + texcount.scaledfontmode = 2 + setemptyfontsize() + else + texcount.scaledfontmode = 0 + setemptyfontsize() + end + specification = definers.makespecification(str,lookup,name,sub,method,detail,size) + if trace_defining then + report_defining("stop stage one") + end +end + +local n = 0 + +-- we can also move rscale to here (more consistent) +-- the argument list will become a table + +local function nice_cs(cs) + return (gsub(cs,".->", "")) +end + +function commands.definefont_two(global,cs,str,size,inheritancemode,classfeatures,fontfeatures,classfallbacks,fontfallbacks, + mathsize,textsize,relativeid,classgoodies,goodies,classdesignsize,fontdesignsize) + if trace_defining then + report_defining("start stage two: %s (size %s)",str,size) + end + -- name is now resolved and size is scaled cf sa/mo + local lookup, name, sub, method, detail = getspecification(str or "") + -- new (todo: inheritancemode) + local designsize = fontdesignsize ~= "" and fontdesignsize or classdesignsize or "" + local designname = designsizefilename(name,designsize,size) + if designname and designname ~= "" then + if trace_defining or trace_designsize then + report_defining("remapping name %a, specification %a, size %a, designsize %a",name,designsize,size,designname) + end + -- we don't catch detail here + local o_lookup, o_name, o_sub, o_method, o_detail = getspecification(designname) + if o_lookup and o_lookup ~= "" then lookup = o_lookup end + if o_method and o_method ~= "" then method = o_method end + if o_detail and o_detail ~= "" then detail = o_detail end + name = o_name + sub = o_sub + end + -- so far + -- some settings can have been overloaded + if lookup and lookup ~= "" then + specification.lookup = lookup + end + if relativeid and relativeid ~= "" then -- experimental hook + local id = tonumber(relativeid) or 0 + specification.relativeid = id > 0 and id + end + specification.name = name + specification.size = size + specification.sub = (sub and sub ~= "" and sub) or specification.sub + specification.mathsize = mathsize + specification.textsize = textsize + specification.goodies = goodies + specification.cs = cs + specification.global = global + 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 fontfeatures and fontfeatures ~= "" then + specification.method = "*" + specification.detail = fontfeatures + elseif classfeatures and classfeatures ~= "" then + specification.method = "*" + specification.detail = classfeatures + end + if fontfallbacks and fontfallbacks ~= "" then + specification.fallbacks = fontfallbacks + elseif classfallbacks and classfallbacks ~= "" then + specification.fallbacks = classfallbacks + end + elseif inheritancemode == 4 then + -- classfirst + if classfeatures and classfeatures ~= "" then + specification.method = "*" + specification.detail = classfeatures + elseif fontfeatures and fontfeatures ~= "" then + specification.method = "*" + specification.detail = fontfeatures + 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 + if not tfmdata then + report_defining("unable to define %a as %a",name,nice_cs(cs)) + lastfontid = -1 + letvaluerelax(cs) -- otherwise the current definition takes the previous one + elseif type(tfmdata) == "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 + tex.definefont(global,cs,tfmdata) + -- resolved (when designsize is used): + setsomefontsize((fontdata[tfmdata].parameters.size or 0) .. "sp") + lastfontid = tfmdata + else + -- setting the extra characters will move elsewhere + local characters = tfmdata.characters + local parameters = tfmdata.parameters + -- 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 id = font.define(tfmdata) + csnames[id] = specification.cs + tfmdata.properties.id = id + definers.register(tfmdata,id) -- to be sure, normally already done + tex.definefont(global,cs,id) + constructors.cleanuptable(tfmdata) + constructors.finalize(tfmdata) + if trace_defining then + report_defining("defining %a, id %a, target %a, features %a / %a, fallbacks %a / %a", + name,id,nice_cs(cs),classfeatures,fontfeatures,classfallbacks,fontfallbacks) + end + -- resolved (when designsize is used): + setsomefontsize((tfmdata.parameters.size or 655360) .. "sp") + lastfontid = id + end + if trace_defining then + report_defining("memory usage after: %s",statistics.memused()) + report_defining("stop stage two") + end + -- + texsetcount("global","lastfontid",lastfontid) + if not mathsize then + -- forget about it + elseif mathsize == 0 then + lastmathids[1] = lastfontid + else + lastmathids[mathsize] = lastfontid + end + -- + statistics.stoptiming(fonts) +end + +function definers.define(specification) + -- + local name = specification.name + if not name or name == "" then + return -1 + else + statistics.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 = tex.sp(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 + tex.definefont(specification.global,cs,tfmdata) + csnames[tfmdata] = cs + end + return tfmdata, fontdata[tfmdata] + else + local id = font.define(tfmdata) + tfmdata.properties.id = id + definers.register(tfmdata,id) + if cs then + tex.definefont(specification.global,cs,id) + csnames[id] = cs + end + constructors.cleanuptable(tfmdata) + constructors.finalize(tfmdata) + return id, tfmdata + end + statistics.stoptiming(fonts) + end +end + +-- 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 = specification.size and number.todimen(specification.size) or texdimen.bodyfontsize + local number = tonumber(specification.number) + local id = nil + 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 = definers.define { + name = name, + size = size, + cs = cs, + } + end + if not id then + id = currentfont() + end + return id, csnames[id] +end + +local enable_auto_r_scale = false + +experiments.register("fonts.autorscale", function(v) + enable_auto_r_scale = v +end) + +-- Not ok, we can best use a database for this. The problem is that we +-- have delayed definitions and so we never know what style is taken +-- as start. + +local calculatescale = constructors.calculatescale + +function constructors.calculatescale(tfmdata,scaledpoints,relativeid) + local scaledpoints, delta = calculatescale(tfmdata,scaledpoints) + -- if enable_auto_r_scale and relativeid then -- for the moment this is rather context specific + -- local relativedata = fontdata[relativeid] + -- local rfmdata = relativedata and relativedata.unscaled and relativedata.unscaled + -- local id_x_height = rfmdata and rfmdata.parameters and rfmdata.parameters.x_height + -- local tf_x_height = tfmdata and tfmdata.parameters and tfmdata.parameters.x_height + -- if id_x_height and tf_x_height then + -- local rscale = id_x_height/tf_x_height + -- delta = rscale * delta + -- scaledpoints = rscale * scaledpoints + -- end + -- end + return scaledpoints, delta +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 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 + specification.hash = lower(specification.name .. ' @ ' .. hashfeatures(specification)) + if specification.sub and specification.sub ~= "" then + specification.hash = specification.sub .. ' @ ' .. specification.hash + end + return specification +end + + +-- soon to be obsolete: + +local mappings = fonts.mappings + +local loaded = { -- prevent loading (happens in cont-sys files) + ["original-base.map" ] = true, + ["original-ams-base.map" ] = true, + ["original-ams-euler.map"] = true, + ["original-public-lm.map"] = true, +} + +function mappings.loadfile(name) + name = file.addsuffix(name,"map") + if not loaded[name] then + if trace_mapfiles then + report_mapfiles("loading map file %a",name) + end + pdf.mapfile(name) + loaded[name] = true + end +end + +local loaded = { -- prevent double loading +} + +function mappings.loadline(how,line) + if line then + how = how .. " " .. line + elseif how == "" then + how = "= " .. line + end + if not loaded[how] then + if trace_mapfiles then + report_mapfiles("processing map line %a",line) + end + pdf.mapline(how) + loaded[how] = true + end +end + +function mappings.reset() + pdf.mapfile("") +end + +mappings.reset() -- resets the default file + +-- we need an 'do after the banner hook' + +-- => commands + +local function nametoslot(name) + local t = type(name) + if t == "string" then + return resources[true].unicodes[name] + elseif t == "number" then + return n + end +end + +helpers.nametoslot = nametoslot + +-- 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] = { + format("%03i",id or 0), + format("%09i",parameters.size or 0), + properties.type or "real", + properties.format or "unknown", + properties.name or "", + properties.psname or "", + properties.fullname or "", + } + report_status("%s: % t",properties.name,sortedkeys(data)) + end + formatcolumns(t," ") + report_status() + report_status("defined fonts:") + report_status() + for k=1,tn do + report_status(t[k]) + end + end +end + +luatex.registerstopactions(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," ") + report_status() + report_status("defined featuresets:") + report_status() + for k=1,n do + report_status(t[k]) + end + end +end + +luatex.registerstopactions(loggers.reportusedfeatures) + +statistics.register("fonts load time", function() + return statistics.elapsedseconds(fonts) +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", +} + +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" + context.startfontclass { name } + context.definefontsynonym( { format("%s", Shape) }, { format("spec:%s-%s-regular-%s", fontname, normalweight, normalwidth) } ) + context.definefontsynonym( { format("%sBold", Shape) }, { format("spec:%s-%s-regular-%s", fontname, boldweight, boldwidth ) } ) + context.definefontsynonym( { format("%sBoldItalic", Shape) }, { format("spec:%s-%s-italic-%s", fontname, boldweight, boldwidth ) } ) + context.definefontsynonym( { format("%sItalic", Shape) }, { format("spec:%s-%s-italic-%s", fontname, normalweight, normalwidth) } ) + context.stopfontclass() + local settings = sequenced({ features= t.features },",") + context.dofastdefinetypeface(name, shortcut, shape, size, settings) +end + +function fonts.current() -- todo: also handle name + return fontdata[currentfont()] or fontdata[0] +end + +function fonts.currentid() + return currentfont() or 0 +end + +-- interfaces + +function commands.fontchar(n) + n = nametoslot(n) + if n then + context.char(n) + end +end + +function commands.doifelsecurrentfonthasfeature(name) -- can be made faster with a supportedfeatures hash + local f = fontdata[currentfont()] + f = f and f.shared + f = f and f.rawdata + f = f and f.resources + f = f and f.features + commands.doifelse(f and (f.gpos[name] or f.gsub[name])) +end + +local p, f = 1, formatters["%0.1fpt"] -- normally this value is changed only once + +local stripper = lpeg.patterns.stripzeros + +function commands.nbfs(amount,precision) + if precision ~= p then + p = precision + f = formatters["%0." .. p .. "fpt"] + end + context(lpegmatch(stripper,f(amount/65536))) +end + +function commands.featureattribute(tag) + context(contextnumber(tag)) +end + +function commands.setfontfeature(tag) + texattribute[0] = contextnumber(tag) +end + +function commands.resetfontfeature() + texattribute[0] = 0 +end + +-- function commands.addfs(tag) withset(tag, 1) end +-- function commands.subfs(tag) withset(tag,-1) end +-- function commands.addff(tag) withfnt(tag, 2) end -- on top of font features +-- function commands.subff(tag) withfnt(tag,-2) end -- on top of font features + +function commands.cleanfontname (name) context(names.cleanname(name)) end + +function commands.fontlookupinitialize (name) names.lookup(name) end +function commands.fontlookupnoffound () context(names.noflookups()) end +function commands.fontlookupgetkeyofindex(key,index) context(names.getlookupkey(key,index)) end +function commands.fontlookupgetkey (key) context(names.getlookupkey(key)) end + +-- 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 + +-- 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,tfmdata) -- could be a method of a font instance + if unit == "ex" then + return (tfmdata and tfmdata.parameters.x_height) or 655360 + elseif unit == "em" then + return (tfmdata and tfmdata.parameters.em_width) or 655360 + 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.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 quads = hashes.quads +local xheights = hashes.xheights + +setmetatableindex(number.dimenfactors, function(t,k) + if k == "ex" then + return xheigths[currentfont()] + elseif k == "em" then + return quads[currentfont()] + elseif k == "%" then + return dimen.hsize/100 + else + -- error("wrong dimension: " .. (s or "?")) -- better a message + return false + end +end) + +--[[ldx-- +

Before a font is passed to we scale it. Here we also need +to scale virtual characters.

+--ldx]]-- + +function constructors.checkvirtualids(tfmdata) + -- begin of experiment: we can use { "slot", 0, number } in virtual fonts + local fonts = tfmdata.fonts + local selfid = font.nextid() + if fonts and #fonts > 0 then + for i=1,#fonts do + if fonts[i][2] == 0 then + fonts[i][2] = selfid + end + end + else + -- tfmdata.fonts = { "id", selfid } -- conflicts with other next id's (vf math), too late anyway + end + -- end of experiment +end + +-- function constructors.getvirtualid(tfmdata) +-- -- since we don't know the id yet, we use 0 as signal +-- local tf = tfmdata.fonts +-- if not tf then +-- local properties = tfmdata.properties +-- if properties then +-- properties.virtualized = true +-- else +-- tfmdata.properties = { virtualized = true } +-- end +-- tf = { } +-- tfmdata.fonts = tf +-- end +-- local ntf = #tf + 1 +-- tf[ntf] = { id = 0 } +-- return ntf +-- end +-- +-- function constructors.checkvirtualid(tfmdata, id) -- will go +-- local properties = tfmdata.properties +-- if tfmdata and tfmdata.type == "virtual" or (properties and properties.virtualized) then +-- local vfonts = tfmdata.fonts +-- if not vffonts or #vfonts == 0 then +-- if properties then +-- properties.virtualized = false +-- end +-- tfmdata.fonts = nil +-- else +-- for f=1,#vfonts do +-- local fnt = vfonts[f] +-- if fnt.id and fnt.id == 0 then +-- fnt.id = id +-- end +-- end +-- end +-- end +-- end + +function commands.setfontofid(id) + context.getvalue(csnames[id]) +end + +-- more interfacing: + +commands.definefontfeature = presetcontext + +local cache = { } + +local hows = { + ["+"] = "add", + ["-"] = "subtract", + ["="] = "replace", +} + +function commands.feature(how,parent,name,font) + if not how then + if trace_features and texattribute[0] ~= 0 then + report_cummulative("font %!font:name!, reset",fontdata[font or true]) + end + texattribute[0] = 0 + elseif how == true 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 + texattribute[0] = 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 + texattribute[0] = done + end +end + +function commands.featurelist(...) + context(fonts.specifiers.contexttostring(...)) +end + +function commands.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 + +-- a fontkern plug: + +local copy_node = node.copy +local kern = nodes.pool.register(nodes.pool.kern()) + +node.set_attribute(kern,attributes.private('fontkern'),1) -- we can have several, attributes are shared + +nodes.injections.installnewkern(function(k) + local c = copy_node(kern) + c.kern = k + return c +end) + +directives.register("nodes.injections.fontkern", function(v) kern.subtype = v and 0 or 1 end) + +-- here + +local trace_analyzing = false trackers.register("otf.analyzing", function(v) trace_analyzing = v end) + +local otffeatures = fonts.constructors.newfeatures("otf") +local registerotffeature = otffeatures.register + +local analyzers = fonts.analyzers +local methods = analyzers.methods + +local unsetvalue = attributes.unsetvalue + +local traverse_by_id = node.traverse_id + +local a_color = attributes.private('color') +local a_colormodel = attributes.private('colormodel') +local a_state = attributes.private('state') +local m_color = attributes.list[a_color] or { } + +local glyph_code = nodes.nodecodes.glyph + +local states = analyzers.states + +local names = { + [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", +} + +local function markstates(head) + if head then + local model = head[a_colormodel] or 1 + for glyph in traverse_by_id(glyph_code,head) do + local a = glyph[a_state] + if a then + local name = names[a] + if name then + local color = m_color[name] + if color then + glyph[a_colormodel] = model + 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 in traverse_by_id(glyph_code,head) do + if not font or n.font == font then + n[a_color] = unsetvalue + end + end + return head, true +end -- cgit v1.2.3