diff options
author | Hans Hagen <pragma@wxs.nl> | 2017-05-14 19:58:50 +0200 |
---|---|---|
committer | Context Git Mirror Bot <phg42.2a@gmail.com> | 2017-05-14 19:58:50 +0200 |
commit | fd0c4577a4b6e85ca2db664906e1a03807ce133f (patch) | |
tree | fa23fcc04248d03ff82e34634b8ef1bb9cf28acb /tex/context/base/mkiv/font-ctx.lua | |
parent | db581096187dc2d3cbdbe4cdc39d247c168b1607 (diff) | |
download | context-fd0c4577a4b6e85ca2db664906e1a03807ce133f.tar.gz |
2017-05-14 19:15:00
Diffstat (limited to 'tex/context/base/mkiv/font-ctx.lua')
-rw-r--r-- | tex/context/base/mkiv/font-ctx.lua | 1108 |
1 files changed, 843 insertions, 265 deletions
diff --git a/tex/context/base/mkiv/font-ctx.lua b/tex/context/base/mkiv/font-ctx.lua index be233e570..578babc75 100644 --- a/tex/context/base/mkiv/font-ctx.lua +++ b/tex/context/base/mkiv/font-ctx.lua @@ -14,7 +14,7 @@ if not modules then modules = { } end modules ['font-ctx'] = { local context, commands = context, commands -local format, gmatch, match, find, lower, gsub, byte = string.format, string.gmatch, string.match, string.find, string.lower, string.gsub, string.byte +local format, gmatch, match, find, lower, upper, gsub, byte, topattern = string.format, string.gmatch, string.match, string.find, string.lower, string.upper, string.gsub, string.byte, string.topattern local concat, serialize, sort, fastcopy, mergedtable = table.concat, table.serialize, table.sort, table.fastcopy, table.merged local sortedhash, sortedkeys, sequenced = table.sortedhash, table.sortedkeys, table.sequenced local settings_to_hash, hash_to_string = utilities.parsers.settings_to_hash, utilities.parsers.hash_to_string @@ -42,11 +42,14 @@ 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 report_newline = logs.newline local setmetatableindex = table.setmetatableindex local implement = interfaces.implement +local chardata = characters.data + local fonts = fonts local handlers = fonts.handlers local otf = handlers.otf -- brrr @@ -63,6 +66,8 @@ local hashes = fonts.hashes local currentfont = font.current local definefont = font.define +local cleanname = names.cleanname + local encodings = fonts.encodings ----- aglunicodes = encodings.agl.unicodes local aglunicodes = nil -- delayed loading @@ -102,15 +107,13 @@ local parameters = hashes.parameters local designsizefilename = fontgoodies.designsizes.filename -local context_char = context.char -local context_getvalue = context.getvalue +local ctx_char = context.char +local ctx_getvalue = context.getvalue 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 @@ -153,17 +156,19 @@ local function getfontname(tfmdata) return basename(type(tfmdata) == "number" and properties[tfmdata].name or tfmdata.properties.name) end -fonts.helpers.name = getfontname +helpers.name = getfontname + +local addformatter = utilities.strings.formatters.add if _LUAVERSION < 5.2 then - utilities.strings.formatters.add(formatters,"font:name", [["'"..fontname(%s).."'"]], "local fontname = fonts.helpers.name") - utilities.strings.formatters.add(formatters,"font:features",[["'"..sequenced(%s," ",true).."'"]],"local sequenced = table.sequenced") + addformatter(formatters,"font:name", [["'"..fontname(%s).."'"]], "local fontname = fonts.helpers.name") + addformatter(formatters,"font:features",[["'"..sequenced(%s," ",true).."'"]],"local sequenced = table.sequenced") else - utilities.strings.formatters.add(formatters,"font:name", [["'"..fontname(%s).."'"]], { fontname = fonts.helpers.name }) - utilities.strings.formatters.add(formatters,"font:features",[["'"..sequenced(%s," ",true).."'"]],{ sequenced = table.sequenced }) + addformatter(formatters,"font:name", [["'"..fontname(%s).."'"]], { fontname = helpers.name }) + addformatter(formatters,"font:features",[["'"..sequenced(%s," ",true).."'"]],{ sequenced = table.sequenced }) end @@ -176,58 +181,109 @@ constructors.nofsharedhashes = 0 constructors.nofsharedvectors = 0 constructors.noffontsloaded = 0 -local shares = { } -local hashes = { } - -function constructors.trytosharefont(target,tfmdata) - constructors.noffontsloaded = constructors.noffontsloaded + 1 - if constructors.sharefonts then - local fonthash = target.specification.hash - if fonthash then - local properties = target.properties - local fullname = target.fullname - local sharedname = hashes[fonthash] - if sharedname then - -- this is ok for context as we know that only features can mess with font definitions - -- so a similar hash means that the fonts are similar too - if trace_defining then - report_defining("font %a uses backend resources of font %a (%s)",target.fullname,sharedname,"common hash") - end - target.fullname = sharedname - properties.sharedwith = sharedname - constructors.nofsharedfonts = constructors.nofsharedfonts + 1 - constructors.nofsharedhashes = constructors.nofsharedhashes + 1 - else - -- the one takes more time (in the worst case of many cjk fonts) but it also saves - -- embedding time - local characters = target.characters - local n = 1 - local t = { target.psname } - local u = sortedkeys(characters) - for i=1,#u do - local k = u[i] - n = n + 1 ; t[n] = k - n = n + 1 ; t[n] = characters[k].index or k - end - local checksum = md5.HEX(concat(t," ")) - local sharedname = shares[checksum] +do + + local shares = { } + local hashes = { } + + local nofinstances = 0 + local instances = table.setmetatableindex(function(t,k) + nofinstances = nofinstances + 1 + t[k] = nofinstances + return nofinstances + end) + + function constructors.trytosharefont(target,tfmdata) + constructors.noffontsloaded = constructors.noffontsloaded + 1 + if constructors.sharefonts then + local fonthash = target.specification.hash + if fonthash then + local properties = target.properties local fullname = target.fullname + local fontname = target.fontname + local psname = target.psname + -- for the moment here: + local instance = properties.instance + if instance then + local format = tfmdata.properties.format + if format == "opentype" then + target.streamprovider = 1 + elseif format == "truetype" then + target.streamprovider = 2 + else + target.streamprovider = 0 + end + if target.streamprovider > 0 then + if fullname then + fullname = fullname .. ":" .. instances[instance] + target.fullname = fullname + end + if fontname then + fontname = fontname .. ":" .. instances[instance] + target.fontname = fontname + end + if psname then + -- this one is used for the funny prefix in font names in pdf + -- so it has ot be kind of unique in order to avoid subset prefix + -- clashes being reported + psname = psname .. ":" .. instances[instance] + target.psname = psname + end + end + end + -- + local sharedname = hashes[fonthash] if sharedname then + -- this is ok for context as we know that only features can mess with font definitions + -- so a similar hash means that the fonts are similar too if trace_defining then - report_defining("font %a uses backend resources of font %a (%s)",fullname,sharedname,"common vector") + report_defining("font %a uses backend resources of font %a (%s)",target.fullname,sharedname,"common hash") end - fullname = sharedname - properties.sharedwith= sharedname + target.fullname = sharedname + properties.sharedwith = sharedname constructors.nofsharedfonts = constructors.nofsharedfonts + 1 - constructors.nofsharedvectors = constructors.nofsharedvectors + 1 + constructors.nofsharedhashes = constructors.nofsharedhashes + 1 else - shares[checksum] = fullname + -- the one takes more time (in the worst case of many cjk fonts) but it also saves + -- embedding time .. haha, this is interesting: when i got a clash on subset tag + -- collision i saw in the source that these tags are also using a hash like below + -- so maybe we should have an option to pass it from lua + local characters = target.characters + local n = 1 + local t = { target.psname } + -- for the moment here: + if instance then + n = n + 1 + t[n] = instance + end + -- + local u = sortedkeys(characters) + for i=1,#u do + local k = u[i] + n = n + 1 ; t[n] = k + n = n + 1 ; t[n] = characters[k].index or k + end + local checksum = md5.HEX(concat(t," ")) + local sharedname = shares[checksum] + local fullname = target.fullname + if sharedname then + if trace_defining then + report_defining("font %a uses backend resources of font %a (%s)",fullname,sharedname,"common vector") + end + fullname = sharedname + properties.sharedwith= sharedname + constructors.nofsharedfonts = constructors.nofsharedfonts + 1 + constructors.nofsharedvectors = constructors.nofsharedvectors + 1 + else + shares[checksum] = fullname + end + target.fullname = fullname + hashes[fonthash] = fullname end - target.fullname = fullname - hashes[fonthash] = fullname end end end + end directives.register("fonts.checksharing",function(v) @@ -294,21 +350,13 @@ 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 if resources.features then for g, list in next, resources.features do for f, scripts in next, list do - if privatefeatures[f] then - -- skip - elseif scripts.dflt then + if scripts.dflt then script = "dflt" break elseif scripts.latn then @@ -537,10 +585,11 @@ local function presetcontext(name,parent,features) -- will go to con and shared 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 + for k, v in next, s do +-- no, as then we cannot overload: e.g. math,mathextra +-- if features[k] == nil then features[k] = v - end +-- end end else -- just ignore an undefined one .. i.e. we can refer to not yet defined @@ -570,7 +619,7 @@ local function presetcontext(name,parent,features) -- will go to con and shared -- optimization) local t = { } -- can we avoid t ? for k,v in next, features do --- if v then t[k] = v end + -- if v then t[k] = v end t[k] = v end -- needed for dynamic features @@ -585,42 +634,57 @@ local function presetcontext(name,parent,features) -- will go to con and shared 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 +local function adaptcontext(pattern,features) + local pattern = topattern(pattern,false,true) + for name in next, setups do + if find(name,pattern) then + presetcontext(name,name,features) end - else - return t.number or 0 end end +-- local function contextnumber(name) -- will be replaced +-- local t = setups[name] +-- if not t then +-- return 0 +-- elseif t.auto then -- check where used, autolanguage / autoscript? +-- local lng = tonumber(tex.language) +-- local tag = name .. ":" .. lng +-- local s = setups[tag] +-- if s then +-- return s.number or 0 +-- else +-- local script, language = languages.association(lng) +-- if t.script ~= script or t.language ~= language then +-- local s = fastcopy(t) +-- local n = #numbers + 1 +-- setups[tag] = s +-- numbers[n] = tag +-- s.number = n +-- s.script = script +-- s.language = language +-- return n +-- else +-- setups[tag] = t +-- return t.number or 0 +-- end +-- end +-- else +-- return t.number or 0 +-- end +-- end + +local function contextnumber(name) -- will be replaced + local t = setups[name] + return t and t.number or 0 +end + local function mergecontext(currentnumber,extraname,option) -- number string number (used in scrp-ini local extra = setups[extraname] if extra then local current = setups[numbers[currentnumber]] - local mergedfeatures, mergedname = { }, nil + local mergedfeatures = { } + local mergedname = nil if option < 0 then if current then for k, v in next, current do @@ -941,13 +1005,13 @@ 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 -- dimension -local scale_sa = P("sa") * Cc(2) * spaces * dimension -- number -local scale_mo = P("mo") * Cc(3) * spaces * dimension -- number -local scale_scaled = P("scaled") * Cc(4) * spaces * dimension -- number -local scale_ht = P("ht") * Cc(5) * spaces * dimension -- dimension -local scale_cp = P("cp") * Cc(6) * spaces * dimension -- dimension +local scale_none = Cc(0) +local scale_at = (P("at") +P("@")) * Cc(1) * spaces * dimension -- dimension +local scale_sa = P("sa") * Cc(2) * spaces * dimension -- number +local scale_mo = P("mo") * Cc(3) * spaces * dimension -- number +local scale_scaled = P("scaled") * Cc(4) * spaces * dimension -- number +local scale_ht = P("ht") * Cc(5) * spaces * dimension -- dimension +local scale_cp = P("cp") * Cc(6) * spaces * dimension -- dimension local specialscale = { [5] = "ht", [6] = "cp" } @@ -973,6 +1037,8 @@ local getspecification = definers.getspecification -- we can make helper macros which saves parsing (but normaly not -- that many calls, e.g. in mk a couple of 100 and in metafun 3500) +local specifiers = { } + do -- else too many locals ----- ctx_setdefaultfontname = context.fntsetdefname @@ -987,6 +1053,7 @@ do -- else too many locals local scanners = tokens.scanners local scanstring = scanners.string local scaninteger = scanners.integer + local scannumber = scanners.number local scanboolean = scanners.boolean local setmacro = tokens.setters.macro @@ -1051,6 +1118,187 @@ do -- else too many locals -- function commands.definefont_two(global,cs,str,size,inheritancemode,classfeatures,fontfeatures,classfallbacks,fontfallbacks, -- mathsize,textsize,relativeid,classgoodies,goodies,classdesignsize,fontdesignsize,scaledfontmode) +-- scanners.definefont_two = function() + +-- local global = scanboolean() -- \ifx\fontclass\empty\s!false\else\s!true\fi +-- local cs = scanstring () -- {#csname}% +-- local str = scanstring () -- \somefontfile +-- local size = scaninteger() -- \d_font_scaled_font_size +-- local inheritancemode = scaninteger() -- \c_font_feature_inheritance_mode +-- local classfeatures = scanstring () -- \m_font_class_features +-- local fontfeatures = scanstring () -- \m_font_features +-- local classfallbacks = scanstring () -- \m_font_class_fallbacks +-- local fontfallbacks = scanstring () -- \m_font_fallbacks +-- local mathsize = scaninteger() -- \fontface +-- local textsize = scaninteger() -- \d_font_scaled_text_face +-- local relativeid = scaninteger() -- \relativefontid +-- local classgoodies = scanstring () -- \m_font_class_goodies +-- local goodies = scanstring () -- \m_font_goodies +-- local classdesignsize = scanstring () -- \m_font_class_designsize +-- local fontdesignsize = scanstring () -- \m_font_designsize +-- local scaledfontmode = scaninteger() -- \scaledfontmode + +-- if trace_defining then +-- report_defining("start stage two: %s, size %s, features %a & %a",str,size,classfeatures,fontfeatures) +-- end +-- -- name is now resolved and size is scaled cf sa/mo +-- local lookup, name, sub, method, detail = getspecification(str or "") +-- -- new (todo: inheritancemode) +-- local designsize = fontdesignsize ~= "" and fontdesignsize or classdesignsize or "" +-- local designname = designsizefilename(name,designsize,size) +-- if designname and designname ~= "" then +-- if trace_defining or trace_designsize then +-- report_defining("remapping name %a, specification %a, size %a, designsize %a",name,designsize,size,designname) +-- end +-- -- we don't catch detail here +-- local o_lookup, o_name, o_sub, o_method, o_detail = getspecification(designname) +-- if o_lookup and o_lookup ~= "" then lookup = o_lookup end +-- if o_method and o_method ~= "" then method = o_method end +-- if o_detail and o_detail ~= "" then detail = o_detail end +-- name = o_name +-- sub = o_sub +-- end +-- -- so far +-- -- some settings can have been overloaded +-- if lookup and lookup ~= "" then +-- specification.lookup = lookup +-- end +-- if relativeid and relativeid ~= "" then -- experimental hook +-- local id = tonumber(relativeid) or 0 +-- specification.relativeid = id > 0 and id +-- end +-- -- +-- specification.name = name +-- specification.size = size +-- specification.sub = (sub and sub ~= "" and sub) or specification.sub +-- specification.mathsize = mathsize +-- specification.textsize = textsize +-- specification.goodies = goodies +-- specification.cs = cs +-- specification.global = global +-- specification.scalemode = scaledfontmode -- context specific +-- if detail and detail ~= "" then +-- specification.method = method or "*" +-- specification.detail = detail +-- elseif specification.detail and specification.detail ~= "" then +-- -- already set +-- elseif inheritancemode == 0 then +-- -- nothing +-- elseif inheritancemode == 1 then +-- -- fontonly +-- if fontfeatures and fontfeatures ~= "" then +-- specification.method = "*" +-- specification.detail = fontfeatures +-- end +-- if fontfallbacks and fontfallbacks ~= "" then +-- specification.fallbacks = fontfallbacks +-- end +-- elseif inheritancemode == 2 then +-- -- classonly +-- if classfeatures and classfeatures ~= "" then +-- specification.method = "*" +-- specification.detail = classfeatures +-- end +-- if classfallbacks and classfallbacks ~= "" then +-- specification.fallbacks = classfallbacks +-- end +-- elseif inheritancemode == 3 then +-- -- fontfirst +-- if 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 +-- texsetcount("scaledfontsize",0) +-- -- ctx_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 +-- texdefinefont(global,cs,tfmdata) +-- -- resolved (when designsize is used): +-- local size = fontdata[tfmdata].parameters.size or 0 +-- -- ctx_setsomefontsize(size .. "sp") +-- setmacro("somefontsize",size.."sp") +-- texsetcount("scaledfontsize",size) +-- 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 +-- -- +-- constructors.checkvirtualids(tfmdata) -- experiment, will become obsolete when slots can selfreference +-- local id = definefont(tfmdata) +-- csnames[id] = specification.cs +-- tfmdata.properties.id = id +-- definers.register(tfmdata,id) -- to be sure, normally already done +-- texdefinefont(global,cs,id) +-- constructors.cleanuptable(tfmdata) +-- constructors.finalize(tfmdata) +-- if trace_defining then +-- report_defining("defining %a, id %a, target %a, features %a / %a, fallbacks %a / %a", +-- name,id,nice_cs(cs),classfeatures,fontfeatures,classfallbacks,fontfallbacks) +-- end +-- -- resolved (when designsize is used): +-- local size = tfmdata.parameters.size or 655360 +-- setmacro("somefontsize",size.."sp") +-- -- ctx_setsomefontsize(size .. "sp") +-- texsetcount("scaledfontsize",size) +-- 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) +-- specifiers[lastfontid] = { str, size } +-- if not mathsize then +-- -- forget about it +-- elseif mathsize == 0 then +-- lastmathids[1] = lastfontid +-- else +-- lastmathids[mathsize] = lastfontid +-- end +-- -- +-- stoptiming(fonts) +-- end + scanners.definefont_two = function() local global = scanboolean() -- \ifx\fontclass\empty\s!false\else\s!true\fi @@ -1072,7 +1320,7 @@ do -- else too many locals local scaledfontmode = scaninteger() -- \scaledfontmode if trace_defining then - report_defining("start stage two: %s (size %s)",str,size) + report_defining("start stage two: %s, size %s, features %a & %a",str,size,classfeatures,fontfeatures) end -- name is now resolved and size is scaled cf sa/mo local lookup, name, sub, method, detail = getspecification(str or "") @@ -1167,25 +1415,8 @@ do -- else too many locals 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 - texsetcount("scaledfontsize",0) - -- ctx_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 - texdefinefont(global,cs,tfmdata) - -- resolved (when designsize is used): - local size = fontdata[tfmdata].parameters.size or 0 - -- ctx_setsomefontsize(size .. "sp") - setmacro("somefontsize",size.."sp") - texsetcount("scaledfontsize",size) - lastfontid = tfmdata - else + local tfmtype = type(tfmdata) + if tfmtype == "table" then -- setting the extra characters will move elsewhere local characters = tfmdata.characters local parameters = tfmdata.parameters @@ -1196,23 +1427,83 @@ do -- else too many locals -- characters[0x2008] = { width = characters[0x002E] and characters[0x002E].width or parameters.space } -- period -- constructors.checkvirtualids(tfmdata) -- experiment, will become obsolete when slots can selfreference - local id = definefont(tfmdata) - csnames[id] = specification.cs - tfmdata.properties.id = id - definers.register(tfmdata,id) -- to be sure, normally already done - texdefinefont(global,cs,id) - constructors.cleanuptable(tfmdata) - constructors.finalize(tfmdata) + local fallbacks = specification.fallbacks + if fallbacks and fallbacks ~= "" and tfmdata.properties.hasmath then + -- We need this ugly hack in order to resolve fontnames (at the \TEX end). Originally + -- math was done in Lua after loading (plugged into aftercopying). + -- + -- After tl 2017 I'll also do text falbacks this way (although backups there are done + -- in a completely different way. + mathematics.resolvefallbacks(tfmdata,specification,fallbacks) + context(function() + mathematics.finishfallbacks(tfmdata,specification,fallbacks) + local id = definefont(tfmdata) + csnames[id] = specification.cs + tfmdata.properties.id = id + definers.register(tfmdata,id) -- to be sure, normally already done + texdefinefont(global,cs,id) + constructors.cleanuptable(tfmdata) + constructors.finalize(tfmdata) + if trace_defining then + report_defining("defining %a, id %a, target %a, features %a / %a, fallbacks %a / %a", + name,id,nice_cs(cs),classfeatures,fontfeatures,classfallbacks,fontfallbacks) + end + -- resolved (when designsize is used): + local size = tfmdata.parameters.size or 655360 + setmacro("somefontsize",size.."sp") + -- ctx_setsomefontsize(size .. "sp") + texsetcount("scaledfontsize",size) + lastfontid = id + -- + texsetcount("global","lastfontid",lastfontid) + specifiers[lastfontid] = { str, size } + if not mathsize then + -- forget about it + elseif mathsize == 0 then + lastmathids[1] = lastfontid + else + lastmathids[mathsize] = lastfontid + end + stoptiming(fonts) + end) + return + else + local id = definefont(tfmdata) + csnames[id] = specification.cs + tfmdata.properties.id = id + definers.register(tfmdata,id) -- to be sure, normally already done + texdefinefont(global,cs,id) + constructors.cleanuptable(tfmdata) + constructors.finalize(tfmdata) + if trace_defining then + report_defining("defining %a, id %a, target %a, features %a / %a, fallbacks %a / %a", + name,id,nice_cs(cs),classfeatures,fontfeatures,classfallbacks,fontfallbacks) + end + -- resolved (when designsize is used): + local size = tfmdata.parameters.size or 655360 + setmacro("somefontsize",size.."sp") + -- ctx_setsomefontsize(size .. "sp") + texsetcount("scaledfontsize",size) + lastfontid = id + end + elseif tfmtype == "number" then if trace_defining then - report_defining("defining %a, id %a, target %a, features %a / %a, fallbacks %a / %a", - name,id,nice_cs(cs),classfeatures,fontfeatures,classfallbacks,fontfallbacks) + report_defining("reusing %s, id %a, target %a, features %a / %a, fallbacks %a / %a, goodies %a / %a, designsize %a / %a", + name,tfmdata,nice_cs(cs),classfeatures,fontfeatures,classfallbacks,fontfallbacks,classgoodies,goodies,classdesignsize,fontdesignsize) end + csnames[tfmdata] = specification.cs + texdefinefont(global,cs,tfmdata) -- resolved (when designsize is used): - local size = tfmdata.parameters.size or 655360 - setmacro("somefontsize",size.."sp") + local size = fontdata[tfmdata].parameters.size or 0 -- ctx_setsomefontsize(size .. "sp") + setmacro("somefontsize",size.."sp") texsetcount("scaledfontsize",size) - lastfontid = id + lastfontid = tfmdata + else + report_defining("unable to define %a as %a",name,nice_cs(cs)) + lastfontid = -1 + texsetcount("scaledfontsize",0) + -- ctx_letvaluerelax(cs) -- otherwise the current definition takes the previous one end if trace_defining then report_defining("memory usage after: %s",statistics.memused()) @@ -1220,6 +1511,7 @@ do -- else too many locals end -- texsetcount("global","lastfontid",lastfontid) + specifiers[lastfontid] = { str, size } if not mathsize then -- forget about it elseif mathsize == 0 then @@ -1231,6 +1523,26 @@ do -- else too many locals stoptiming(fonts) end + function scanners.specifiedfontspec() + local f = specifiers[scaninteger()] + if f then + context(f[1]) + end + end + function scanners.specifiedfontsize() + local f = specifiers[scaninteger()] + if f then + context(f[2]) + end + end + function scanners.specifiedfont() + local f = specifiers[scaninteger()] + local s = scannumber() + if f and s then + context("%s at %0.2p",f[1],s * f[2]) -- we round to 2 decimals (as at the tex end) + end + end + -- function definers.define(specification) @@ -1383,7 +1695,7 @@ function constructors.calculatescale(tfmdata,scaledpoints,relativeid,specificati local scaledpoints, delta = calculatescale(tfmdata,scaledpoints) -- if enable_auto_r_scale and relativeid then -- for the moment this is rather context specific (we need to hash rscale then) -- local relativedata = fontdata[relativeid] - -- local rfmdata = relativedata and relativedata.unscaled and relativedata.unscaled + -- local rfmdata = relativedata and relativedata.unscaled and relativedata.unscaled -- just use metadata instead -- local id_x_height = rfmdata and rfmdata.parameters and rfmdata.parameters.x_height -- local tf_x_height = tfmdata and tfmdata.parameters and tfmdata.parameters.x_height -- if id_x_height and tf_x_height then @@ -1397,14 +1709,19 @@ end local designsizes = constructors.designsizes +-- called quite often when in mp labels +-- otf.normalizedaxis + function constructors.hashinstance(specification,force) - local hash, size, fallbacks = specification.hash, specification.size, specification.fallbacks + local hash = specification.hash + local size = specification.size + local fallbacks = specification.fallbacks if force or not hash then hash = constructors.hashfeatures(specification) specification.hash = hash end if size < 1000 and designsizes[hash] then - size = math.round(constructors.scaled(size,designsizes[hash])) + size = round(constructors.scaled(size,designsizes[hash])) specification.size = size end if fallbacks then @@ -1437,7 +1754,7 @@ function definers.resolve(specification) -- overload function in font-con.lua else specification.forced = specification.forced end - -- goodies are a context specific thing and not always defined + -- goodies are a context specific thing and are not always defined -- as feature, so we need to make sure we add them here before -- hashing because otherwise we get funny goodies applied local goodies = specification.goodies @@ -1456,14 +1773,18 @@ function definers.resolve(specification) -- overload function in font-con.lua 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 + local hash = hashfeatures(specification) + local name = specification.name + local sub = specification.sub + if sub and sub ~= "" then + specification.hash = lower(name .. " @ " .. sub .. ' @ ' .. hash) + else + specification.hash = lower(name .. " @ " .. ' @ ' .. hash) end + -- return specification end - -- soon to be obsolete: local mappings = fonts.mappings @@ -1534,7 +1855,6 @@ implement { local function nametoslot(name) local t = type(name) - local s = nil if t == "string" then local slot = unicodes[true][name] if slot then @@ -1543,12 +1863,82 @@ local function nametoslot(name) if not aglunicodes then aglunicodes = encodings.agl.unicodes end - slot = aglunicodes[name] - if characters[true][slot] then + local char = characters[true] + local slot = aglunicodes[name] + if char[slot] then + return slot + end + -- not in font + elseif t == "number" then + if characters[true][name] then return slot else -- not in font end + end +end + + +local found = { } + +local function descriptiontoslot(name) + local t = type(name) + if t == "string" then + -- slow + local list = sortedkeys(chardata) + local slot = found[name] + local char = characters[true] + if slot then + return char[slot] and slot or nil + end + local NAME = upper(name) + for i=1,#list do + slot = list[i] + local c = chardata[slot] + local d = c.description + if d == NAME then + found[name] = slot + return char[slot] and slot or nil + end + end + for i=1,#list do + slot = list[i] + local c = chardata[slot] + local s = c.synonyms + if s then + for i=1,#s do + local si = s[i] + if si == name then + found[name] = si + return char[slot] and slot or nil + end + end + end + end + for i=1,#list do + slot = list[i] + local c = chardata[slot] + local d = c.description + if d and find(d,NAME) then + found[name] = slot + return char[slot] and slot or nil + end + end + for i=1,#list do + slot = list[i] + local c = chardata[slot] + local s = c.synonyms + if s then + for i=1,#s do + local si = s[i] + if find(s[i],name) then + found[name] = si + return char[slot] and slot or nil + end + end + end + end + -- not in font elseif t == "number" then if characters[true][name] then return slot @@ -1625,6 +2015,16 @@ do -- else too many locals local n = nametoslot(name) return n and utfchar(n) or name end, + -- unicode description (synonym) + u = function(name) + local n = descriptiontoslot(name,false) + return n and utfchar(n) or name + end, + -- all + a = function(name) + local n = nametoslot(name) or descriptiontoslot(name) + return n and utfchar(n) or name + end, -- char c = function(name) return name @@ -1657,21 +2057,22 @@ do -- else too many locals end end - helpers.nametoslot = nametoslot - helpers.indextoslot = indextoslot - helpers.tochar = tochar + helpers.nametoslot = nametoslot + helpers.descriptiontoslot = descriptiontoslot + helpers.indextoslot = indextoslot + helpers.tochar = tochar -- interfaces: implement { name = "fontchar", - actions = { nametoslot, context_char }, + actions = { nametoslot, ctx_char }, arguments = "string", } implement { name = "fontcharbyindex", - actions = { indextoslot, context_char }, + actions = { indextoslot, ctx_char }, arguments = "integer", } @@ -1702,15 +2103,16 @@ function loggers.reportdefinedfonts() properties.fullname or "", properties.sharedwith or "", } - report_status("%s: % t",properties.name,sortedkeys(data)) end formatcolumns(t," ") - report_status() + logs.pushtarget("logfile") + report_newline() report_status("defined fonts:") - report_status() + report_newline() for k=1,tn do report_status(t[k]) end + logs.poptarget() end end @@ -1729,12 +2131,14 @@ function loggers.reportusedfeatures() setup.number = n -- restore it (normally not needed as we're done anyway) end formatcolumns(t," ") - report_status() + logs.pushtarget("logfile") + report_newline() report_status("defined featuresets:") - report_status() + report_newline() for k=1,n do report_status(t[k]) end + logs.poptarget() end end @@ -2023,15 +2427,7 @@ end do - -- local scanners = tokens.scanners - -- local scanstring = scanners.string - -- local scaninteger = scanners.integer - -- local scandimen = scanners.dimen - -- local scanboolean = scanners.boolean - - -- local scanners = interfaces.scanners - - local setmacro = tokens.setters.macro + local setmacro = tokens.setters.macro function constructors.currentfonthasfeature(n) local f = fontdata[currentfont()] @@ -2048,25 +2444,9 @@ do arguments = "string" } - -- 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 - local f_strip = formatters["%0.2fpt"] -- normally this value is changed only once local stripper = lpeg.patterns.stripzeros - -- scanners.nbfs = function() - -- context(lpegmatch(stripper,f_strip(scandimen()/65536))) - -- end - implement { name = "nbfs", arguments = "dimen", @@ -2075,18 +2455,6 @@ do end } - -- commands.featureattribute = function(tag) context(contextnumber(tag)) end - -- commands.setfontfeature = function(tag) texsetattribute(0,contextnumber(tag)) end - -- commands.resetfontfeature = function() texsetattribute(0,0) end - -- commands.setfontofid = function(id) context_getvalue(csnames[id]) end - -- commands.definefontfeature = presetcontext - - -- scanners.featureattribute = function() context(contextnumber(scanstring())) end - -- scanners.setfontfeature = function() texsetattribute(0,contextnumber(scanstring())) end - -- scanners.resetfontfeature = function() texsetattribute(0,0) end - -- scanners.setfontofid = function() context_getvalue(csnames[scaninteger()]) end - -- scanners.definefontfeature = function() presetcontext(scanstring(),scanstring(),scanstring()) end - implement { name = "featureattribute", arguments = "string", @@ -2109,7 +2477,7 @@ do name = "setfontofid", arguments = "integer", actions = function(id) - context_getvalue(csnames[id]) + ctx_getvalue(csnames[id]) end } @@ -2119,6 +2487,12 @@ do actions = presetcontext } + implement { + name = "adaptfontfeature", + arguments = { "string", "string" }, + actions = adaptcontext + } + local cache = { } local hows = { @@ -2189,6 +2563,9 @@ do end end + constructors.setfeature = setfeature + constructors.resetfeature = resetfeature + implement { name = "resetfeature", actions = resetfeature } implement { name = "addfeature", actions = setfeature, arguments = { "'+'", "string", "string" } } implement { name = "subtractfeature", actions = setfeature, arguments = { "'-'", "string", "string" } } @@ -2202,8 +2579,8 @@ do } implement { - name = "registerlanguagefeatures", - actions = registerlanguagefeatures, + name = "registerlanguagefeatures", + actions = registerlanguagefeatures, } end @@ -2216,7 +2593,7 @@ do local copy_node = nuts.copy local kern = nuts.pool.register(nuts.pool.kern()) - setattr(kern,attributes.private('fontkern'),1) + setattr(kern,attributes.private('fontkern'),1) -- no gain in setprop as it's shared nodes.injections.installnewkern(function(k) local c = copy_node(kern) @@ -2275,100 +2652,106 @@ end -- make a closure (200 limit): -local trace_analyzing = false trackers.register("otf.analyzing", function(v) trace_analyzing = v end) +do -local analyzers = fonts.analyzers -local methods = analyzers.methods + local trace_analyzing = false trackers.register("otf.analyzing", function(v) trace_analyzing = v end) -local unsetvalue = attributes.unsetvalue + local analyzers = fonts.analyzers + local methods = analyzers.methods -local traverse_id = nuts.traverse_id + local unsetvalue = attributes.unsetvalue -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 traverse_id = nuts.traverse_id -local glyph_code = nodes.nodecodes.glyph + 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 states = analyzers.states + local glyph_code = nodes.nodecodes.glyph -local colornames = { - [states.init] = "font:1", - [states.medi] = "font:2", - [states.fina] = "font:3", - [states.isol] = "font:4", - [states.mark] = "font:5", - [states.rest] = "font:6", - [states.rphf] = "font:1", - [states.half] = "font:2", - [states.pref] = "font:3", - [states.blwf] = "font:4", - [states.pstf] = "font:5", -} + local states = analyzers.states + + local colornames = { + [states.init] = "font:1", + [states.medi] = "font:2", + [states.fina] = "font:3", + [states.isol] = "font:4", + [states.mark] = "font:5", + [states.rest] = "font:6", + [states.rphf] = "font:1", + [states.half] = "font:2", + [states.pref] = "font:3", + [states.blwf] = "font:4", + [states.pstf] = "font:5", + } -local function markstates(head) - if head then - head = tonut(head) - local model = getattr(head,a_colormodel) or 1 - for glyph in traverse_id(glyph_code,head) do - local a = getprop(glyph,a_state) - if a then - local name = colornames[a] - if name then - local color = m_color[name] - if color then - setattr(glyph,a_colormodel,model) - setattr(glyph,a_color,color) + local function markstates(head) + if head then + head = tonut(head) + local model = getattr(head,a_colormodel) or 1 + for glyph in traverse_id(glyph_code,head) do + local a = getprop(glyph,a_state) + if a then + local name = colornames[a] + if name then + local color = m_color[name] + if color then + setattr(glyph,a_colormodel,model) + setattr(glyph,a_color,color) + end end end end end end -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) + 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 - 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) + 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 - return head, done - else - return head, false end -end -registerotffeature { -- adapts - name = "analyze", - processors = { - node = analyzeprocessor, + registerotffeature { -- adapts + name = "analyze", + processors = { + node = analyzeprocessor, + } } -} -function methods.nocolor(head,font,attr) - for n in traverse_id(glyph_code,head) do - if not font or getfont(n) == font then - setattr(n,a_color,unsetvalue) + + function methods.nocolor(head,font,attr) + for n in traverse_id(glyph_code,head) do + if not font or getfont(n) == font then + setattr(n,a_color,unsetvalue) + end end + return head, true end - return head, true + end + local function purefontname(name) if type(name) == "number" then name = getfontname(name) @@ -2385,6 +2768,7 @@ implement { } local list = storage.shared.bodyfontsizes or { } + storage.shared.bodyfontsizes = list implement { @@ -2421,7 +2805,7 @@ implement { implement { name = "cleanfontname", - actions = { names.cleanname, context }, + actions = { cleanname, context }, arguments = "string" } @@ -2496,3 +2880,197 @@ implement { actions = { names.exists, commands.doifelse }, arguments = "string" } + +-- we use 0xFE000+ and 0xFF000+ in math and for runtime (text) extensions we +-- use 0xFD000+ + +constructors.privateslots = constructors.privateslots or { } + +storage.register("fonts/constructors/privateslots", constructors.privateslots, "fonts.constructors.privateslots") + +do + + local privateslots = constructors.privateslots + local lastprivateslot = 0xFD000 + + constructors.privateslots = setmetatableindex(privateslots,function(t,k) + local v = lastprivateslot + lastprivateslot = lastprivateslot + 1 + t[k] = v + return v + end) + + implement { + name = "getprivateglyphslot", + actions = function(name) context(privateslots[name]) end, + arguments = "string", + } + +end + +-- an extra helper + +function helpers.getcoloredglyphs(tfmdata) + if type(tfmdata) == "number" then + tfmdata = fontdata[tfmdata] + end + if not tfmdata then + tfmdata = fontdata[true] + end + local characters = tfmdata.characters + local descriptions = tfmdata.descriptions + local collected = { } + for unicode, character in next, characters do + local description = descriptions[unicode] + if description and (description.colors or character.svg) then + collected[#collected+1] = unicode + end + end + table.sort(collected) + return collected +end + +-- for the font manual + +statistics.register("used fonts",function() + if trace_usage then + local filename = file.nameonly(environment.jobname) .. "-fonts-usage.lua" + if next(fontdata) then + local files = { } + local list = { } + for id, tfmdata in sortedhash(fontdata) do + local filename = tfmdata.properties.filename + if filename then + local filedata = files[filename] + if filedata then + filedata.instances = filedata.instances + 1 + else + local rawdata = tfmdata.shared and tfmdata.shared.rawdata + local metadata = rawdata and rawdata.metadata + files[filename] = { + instances = 1, + filename = filename, + version = metadata and metadata.version, + size = rawdata and rawdata.size, + } + end + else + -- what to do + end + end + for k, v in sortedhash(files) do + list[#list+1] = v + end + table.save(filename,list) + else + os.remove(filename) + end + end +end) + +-- new + +do + + local settings_to_array = utilities.parsers.settings_to_array + -- local namedcolorattributes = attributes.colors.namedcolorattributes + -- local colorvalues = attributes.colors.values + + -- implement { + -- name = "definefontcolorpalette", + -- arguments = { "string", "string" }, + -- actions = function(name,set) + -- set = settings_to_array(set) + -- for i=1,#set do + -- local name = set[i] + -- local space, color = namedcolorattributes(name) + -- local values = colorvalues[color] + -- if values then + -- set[i] = { r = values[3], g = values[4], b = values[5] } + -- else + -- set[i] = { r = 0, g = 0, b = 0 } + -- end + -- end + -- otf.registerpalette(name,set) + -- end + -- } + + implement { + name = "definefontcolorpalette", + arguments = { "string", "string" }, + actions = function(name,set) + otf.registerpalette(name,settings_to_array(set)) + end + } + +end + +do + + local pattern = C((1-S("* "))^1) -- strips all after * or ' at' + + implement { + name = "truefontname", + arguments = "string", + actions = function(s) + -- context(match(s,"[^* ]+") or s) + context(lpegmatch(pattern,s) or s) + end + } + +end + +do + + local function getinstancespec(id) + local data = fontdata[id or true] + local shared = data.shared + local resources = shared and shared.rawdata.resources + if resources then + local instancespec = data.properties.instance + if instancespec then + local variabledata = resources.variabledata + if variabledata then + local instances = variabledata.instances + if instances then + for i=1,#instances do + local instance = instances[i] + if cleanname(instance.subfamily)== instancespec then + local values = table.copy(instance.values) + local axis = variabledata.axis + for i=1,#values do + for j=1,#axis do + if values[i].axis == axis[j].tag then + values[i].name = axis[j].name + break + end + end + end + return values + end + end + end + end + end + end + end + + helpers.getinstancespec = getinstancespec + + implement { + name = "currentfontinstancespec", + actions = function() + local t = getinstancespec() -- current font + if t then + for i=1,#t do + if i > 1 then + context.space() + end + local ti = t[i] + context("%s=%s",ti.name,ti.value) + end + end + end + } + +end |