diff options
Diffstat (limited to 'Master/texmf-dist/tex/context/base/font-ctx.lua')
-rw-r--r-- | Master/texmf-dist/tex/context/base/font-ctx.lua | 954 |
1 files changed, 600 insertions, 354 deletions
diff --git a/Master/texmf-dist/tex/context/base/font-ctx.lua b/Master/texmf-dist/tex/context/base/font-ctx.lua index 2583c652062..2bfcf38598a 100644 --- a/Master/texmf-dist/tex/context/base/font-ctx.lua +++ b/Master/texmf-dist/tex/context/base/font-ctx.lua @@ -14,7 +14,6 @@ if not modules then modules = { } end modules ['font-ctx'] = { 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 @@ -35,6 +34,7 @@ local trace_designsize = false trackers.register("fonts.designsize", functio local trace_usage = false trackers.register("fonts.usage", function(v) trace_usage = v end) local trace_mapfiles = false trackers.register("fonts.mapfiles", function(v) trace_mapfiles = v end) local trace_automode = false trackers.register("fonts.automode", function(v) trace_automode = v end) +local trace_merge = false trackers.register("fonts.merge", function(v) trace_merge = v end) local report_features = logs.reporter("fonts","features") local report_cummulative = logs.reporter("fonts","cummulative") @@ -56,8 +56,26 @@ 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 nuts = nodes.nuts +local tonut = nuts.tonut + +local getfield = nuts.getfield +local setfield = nuts.setfield +local getattr = nuts.getattr +local setattr = nuts.setattr +local getprop = nuts.getprop +local setprop = nuts.setprop +local getfont = nuts.getfont + +local texgetattribute = tex.getattribute +local texsetattribute = tex.setattribute +local texgetdimen = tex.getdimen +local texsetcount = tex.setcount +local texget = tex.get + +local texdefinefont = tex.definefont +local texsp = tex.sp local fontdata = hashes.identifiers local characters = hashes.chardata @@ -67,6 +85,8 @@ local resources = hashes.resources local csnames = hashes.csnames local marks = hashes.markdata local lastmathids = hashes.lastmathids +local exheights = hashes.exheights +local emwidths = hashes.emwidths local designsizefilename = fontgoodies.designsizes.filename @@ -114,13 +134,93 @@ 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).."'"]]) +function fonts.helpers.name(tfmdata) + return file.basename(type(tfmdata) == "number" and properties[tfmdata].name or tfmdata.properties.name) +end + +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") + +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 }) + +end -- ... like font-sfm or so constructors.resolvevirtualtoo = true -- context specific (due to resolver) +constructors.sharefonts = true -- experimental +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] + local fullname = target.fullname + if sharedname then + if trace_defining then + report_defining("font %a uses backend resources of font %a (%s)",fullname,sharedname,"common vector") + end + fullname = sharedname + properties.sharedwith= sharedname + constructors.nofsharedfonts = constructors.nofsharedfonts + 1 + constructors.nofsharedvectors = constructors.nofsharedvectors + 1 + else + shares[checksum] = fullname + end + target.fullname = fullname + hashes[fonthash] = fullname + end + end + end +end + + +directives.register("fonts.checksharing",function(v) + if not v then + report_defining("font sharing in backend is disabled") + end + constructors.sharefonts = v +end) + local limited = false directives.register("system.inputmode", function(v) @@ -157,10 +257,24 @@ commands.resetnullfont = definers.resetnullfont -- 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, +local needsnodemode = { -- we will have node mode by default anyway + -- gsub_single = true, + gsub_multiple = true, + -- gsub_alternate = true, + -- gsub_ligature = true, + gsub_context = true, + gsub_contextchain = true, + gsub_reversecontextchain = true, + -- chainsub = true, + -- reversesub = true, + gpos_mark2base = true, + gpos_mark2ligature = true, + gpos_mark2mark = true, + gpos_cursive = true, + -- gpos_single = true, + -- gpos_pair = true, + gpos_context = true, + gpos_contextchain = true, } otftables.scripts.auto = "automatic fallback to latn when no dflt present" @@ -198,6 +312,8 @@ local function checkedscript(tfmdata,resources,features) return script end +-- basemode combined with dynamics is somewhat tricky + local function checkedmode(tfmdata,resources,features) local sequences = resources.sequences if sequences and #sequences > 0 then @@ -240,6 +356,9 @@ local function checkedmode(tfmdata,resources,features) end end end + if trace_automode then + report_defining("forcing mode base, font %!font:name!",tfmdata) + end features.mode = "base" -- new, or is this wrong? return "base" end @@ -387,7 +506,7 @@ local function definecontext(name,t) -- can be shared end local function presetcontext(name,parent,features) -- will go to con and shared - if features == "" and find(parent,"=") then + if features == "" and find(parent,"=",1,true) then features = parent parent = "" end @@ -540,6 +659,9 @@ local function mergecontextfeatures(currentname,extraname,how,mergedname) -- str for k, v in next, extra do mergedfeatures[k] = v end + if trace_merge then + report_features("merge %a, method %a, current %|T, extra %|T, result %|T",mergedname,"add",current or { },extra,mergedfeatures) + end elseif how == "-" then if current then for k, v in next, current do @@ -552,10 +674,16 @@ local function mergecontextfeatures(currentname,extraname,how,mergedname) -- str mergedfeatures[k] = false end end + if trace_merge then + report_features("merge %a, method %a, current %|T, extra %|T, result %|T",mergedname,"subtract",current or { },extra,mergedfeatures) + end else -- = for k, v in next, extra do mergedfeatures[k] = v end + if trace_merge then + report_features("merge %a, method %a, result %|T",mergedname,"replace",mergedfeatures) + end end local number = #numbers + 1 mergedfeatures.number = number @@ -617,19 +745,39 @@ 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 + 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 + -- if v == true then + -- s[n] = k .. '=true' + -- elseif v == false then + -- s[n] = k .. '=false' + -- else + -- s[n] = k .. "=" .. v + -- end + s[n] = k .. "=" .. tostring(v) end if n > 0 then sort(s) - for i=1,n do - local k = s[i] - s[i] = k .. '=' .. tostring(list[k]) - end return concat(s,"+") end end @@ -639,14 +787,14 @@ end -- local withcache = { } -- concat might be less efficient than nested tables -- -- local function withset(name,what) --- local zero = texattribute[0] +-- local zero = texgetattribute(0) -- local hash = zero .. "+" .. name .. "*" .. what -- local done = withcache[hash] -- if not done then -- done = mergecontext(zero,name,what) -- withcache[hash] = done -- end --- texattribute[0] = done +-- texsetattribute(0,done) -- end -- -- local function withfnt(name,what,font) @@ -657,7 +805,7 @@ end -- done = registercontext(font,name,what) -- withcache[hash] = done -- end --- texattribute[0] = done +-- texsetattribute(0,done) -- end function specifiers.showcontext(name) @@ -675,7 +823,7 @@ local function splitcontext(features) -- presetcontext creates dummy here local sf = setups[features] if not sf then local n -- number - if find(features,",") then + if find(features,",",a,true) then -- let's assume a combination which is not yet defined but just specified (as in math) n, sf = presetcontext(features,features,"") else @@ -692,13 +840,13 @@ end -- local setup = setups[features] -- if setup then -- return setup --- elseif find(features,",") then +-- elseif find(features,",",1,true) then -- -- This is not that efficient but handy anyway for quick and dirty tests -- -- beware, due to the way of caching setups you can get the wrong results -- -- when components change. A safeguard is to nil the cache. -- local merge = nil -- for feature in gmatch(features,"[^, ]+") do --- if find(feature,"=") then +-- if find(feature,"=",1,true) then -- local k, v = lpegmatch(splitter,feature) -- if k and v then -- if not merge then @@ -775,12 +923,16 @@ local value = C((leftparent * (1-rightparent)^0 * rightparent + (1-space) 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 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 sizepattern = spaces * (scale_at + scale_sa + scale_mo + scale_scaled + scale_none) +local specialscale = { [5] = "ht", [6] = "cp" } + +local sizepattern = spaces * (scale_at + scale_sa + scale_mo + scale_ht + scale_cp + scale_scaled + scale_none) local splitpattern = spaces * value * spaces * rest function helpers.splitfontpattern(str) @@ -802,311 +954,327 @@ 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 +do -- else too many locals --- we can also move rscale to here (more consistent) --- the argument list will become a table + local ctx_setdefaultfontname = context.fntsetdefname + local ctx_setsomefontname = context.fntsetsomename + local ctx_setemptyfontsize = context.fntsetnopsize + local ctx_setsomefontsize = context.fntsetsomesize + local ctx_letvaluerelax = context.letvaluerelax -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 + 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 - if classfallbacks and classfallbacks ~= "" then - specification.fallbacks = classfallbacks - elseif fontfallbacks and fontfallbacks ~= "" then - specification.fallbacks = fontfallbacks + local fullname, size = lpegmatch(splitpattern,str) + local lookup, name, sub, method, detail = getspecification(fullname) + if not name then + report_defining("strange definition %a",str) + ctx_setdefaultfontname() + elseif name == "unknown" then + ctx_setdefaultfontname() + else + ctx_setsomefontname(name) 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) + -- we can also use a count for the size + if size and size ~= "" then + local mode, size = lpegmatch(sizepattern,size) + if size and mode then + texsetcount("scaledfontmode",mode) + ctx_setsomefontsize(size) + else + texsetcount("scaledfontmode",0) + ctx_setemptyfontsize() + end + elseif true then + -- so we don't need to check in tex + texsetcount("scaledfontmode",2) + ctx_setemptyfontsize() + else + texsetcount("scaledfontmode",0) + ctx_setemptyfontsize() end - 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) + specification = definers.makespecification(str,lookup,name,sub,method,detail,size) 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("stop stage one") 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 + + 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 - -- - 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 + function commands.definefont_two(global,cs,str,size,inheritancemode,classfeatures,fontfeatures,classfallbacks,fontfallbacks, + mathsize,textsize,relativeid,classgoodies,goodies,classdesignsize,fontdesignsize,scaledfontmode) + 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.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 + 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 tfmdata = definers.read(specification,specification.size) + local lastfontid = 0 if not tfmdata then - return -1, nil + report_defining("unable to define %a as %a",name,nice_cs(cs)) + lastfontid = -1 + ctx_letvaluerelax(cs) -- otherwise the current definition takes the previous one elseif type(tfmdata) == "number" then - if cs then - tex.definefont(specification.global,cs,tfmdata) - csnames[tfmdata] = cs + 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 - return tfmdata, fontdata[tfmdata] + 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") + 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 = font.define(tfmdata) + csnames[id] = specification.cs tfmdata.properties.id = id - definers.register(tfmdata,id) - if cs then - tex.definefont(specification.global,cs,id) - csnames[id] = cs - end + definers.register(tfmdata,id) -- to be sure, normally already done + texdefinefont(global,cs,id) constructors.cleanuptable(tfmdata) constructors.finalize(tfmdata) - return id, 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 + 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) + 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 = texsp(specification.size) or 655260 + end + -- + specification.specification = "" -- not used + specification.resolved = "" + specification.forced = "" + specification.features = { } -- via detail, maybe some day + -- + -- we don't care about mathsize textsize goodies fallbacks + -- + local cs = specification.cs + if cs == "" then + cs = nil + specification.cs = nil + specification.global = false + elseif specification.global == nil then + specification.global = false + end + -- + local tfmdata = definers.read(specification,specification.size) + if not tfmdata then + return -1, nil + elseif type(tfmdata) == "number" then + if cs then + texdefinefont(specification.global,cs,tfmdata) + csnames[tfmdata] = cs + end + return tfmdata, fontdata[tfmdata] + else + constructors.checkvirtualids(tfmdata) -- experiment, will become obsolete when slots can selfreference + local id = font.define(tfmdata) + tfmdata.properties.id = id + definers.register(tfmdata,id) + if cs then + texdefinefont(specification.global,cs,id) + csnames[id] = cs + end + constructors.cleanuptable(tfmdata) + constructors.finalize(tfmdata) + return id, tfmdata + end + statistics.stoptiming(fonts) + end + 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 +do + + 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 texgetdimen("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 - id = definers.define { - name = name, - size = size, - cs = cs, - } - end - if not id then - id = currentfont() + if not id then + id = currentfont() + end + return id, csnames[id] end - return id, csnames[id] + end local enable_auto_r_scale = false @@ -1121,9 +1289,25 @@ end) 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 +function constructors.calculatescale(tfmdata,scaledpoints,relativeid,specification) + if specification then + local scalemode = specification.scalemode + local special = scalemode and specialscale[scalemode] + if special then + -- we also have available specification.textsize + local parameters = tfmdata.parameters + local designsize = parameters.designsize + if special == "ht" then + local height = parameters.ascender * designsize / parameters.units + scaledpoints = (scaledpoints/height) * designsize + elseif special == "cp" then + local height = (tfmdata.descriptions[utf.byte("X")].height or parameters.ascender) * designsize / parameters.units + scaledpoints = (scaledpoints/height) * designsize + end + end + end + 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 id_x_height = rfmdata and rfmdata.parameters and rfmdata.parameters.x_height @@ -1137,6 +1321,31 @@ function constructors.calculatescale(tfmdata,scaledpoints,relativeid) return scaledpoints, delta end +local designsizes = constructors.designsizes + +function constructors.hashinstance(specification,force) + local hash, size, fallbacks = specification.hash, specification.size, 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])) + specification.size = size + end + if fallbacks then + return hash .. ' @ ' .. tostring(size) .. ' @ ' .. fallbacks + else + local scalemode = specification.scalemode + local special = scalemode and specialscale[scalemode] + if special then + return hash .. ' @ ' .. tostring(size) .. ' @ ' .. special + else + return hash .. ' @ ' .. tostring(size) + end + end +end + -- We overload the (generic) resolver: local resolvers = definers.resolvers @@ -1252,13 +1461,14 @@ function loggers.reportdefinedfonts() 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 "", + 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 "", + properties.sharedwith or "", } report_status("%s: % t",properties.name,sortedkeys(data)) end @@ -1299,7 +1509,14 @@ end luatex.registerstopactions(loggers.reportusedfeatures) statistics.register("fonts load time", function() - return statistics.elapsedseconds(fonts) + local elapsed = statistics.elapsedseconds(fonts) + local nofshared = constructors.nofsharedfonts or 0 + if nofshared > 0 then + return format("%sfor %s fonts, %s shared in backend, %s common vectors, %s common hashes", + elapsed,constructors.noffontsloaded,nofshared,constructors.nofsharedvectors,constructors.nofsharedhashes) + else + return elapsed + end end) -- experimental mechanism for Mojca: @@ -1323,6 +1540,11 @@ local Shapes = { mono = "Mono", } +local ctx_startfontclass = context.startfontclass +local ctx_stopfontclass = context.stopfontclass +local ctx_definefontsynonym = context.definefontsynonym +local ctx_dofastdefinetypeface = context.dofastdefinetypeface + function fonts.definetypeface(name,t) if type(name) == "table" then -- {name=abc,k=v,...} @@ -1350,14 +1572,14 @@ function fonts.definetypeface(name,t) 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() + ctx_startfontclass { name } + ctx_definefontsynonym( { format("%s", Shape) }, { format("spec:%s-%s-regular-%s", fontname, normalweight, normalwidth) } ) + ctx_definefontsynonym( { format("%sBold", Shape) }, { format("spec:%s-%s-regular-%s", fontname, boldweight, boldwidth ) } ) + ctx_definefontsynonym( { format("%sBoldItalic", Shape) }, { format("spec:%s-%s-italic-%s", fontname, boldweight, boldwidth ) } ) + ctx_definefontsynonym( { format("%sItalic", Shape) }, { format("spec:%s-%s-italic-%s", fontname, normalweight, normalwidth) } ) + ctx_stopfontclass() local settings = sequenced({ features= t.features },",") - context.dofastdefinetypeface(name, shortcut, shape, size, settings) + ctx_dofastdefinetypeface(name, shortcut, shape, size, settings) end function fonts.current() -- todo: also handle name @@ -1370,10 +1592,15 @@ end -- interfaces +local context_char = context.char +local context_getvalue = context.getvalue + +local commands_doifelse = commands.doifelse + function commands.fontchar(n) n = nametoslot(n) if n then - context.char(n) + context_char(n) end end @@ -1383,7 +1610,7 @@ function commands.doifelsecurrentfonthasfeature(name) -- can be made faster with 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])) + 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 @@ -1403,11 +1630,11 @@ function commands.featureattribute(tag) end function commands.setfontfeature(tag) - texattribute[0] = contextnumber(tag) + texsetattribute(0,contextnumber(tag)) end function commands.resetfontfeature() - texattribute[0] = 0 + texsetattribute(0,0) end -- function commands.addfs(tag) withset(tag, 1) end @@ -1463,11 +1690,11 @@ end local dimenfactors = number.dimenfactors -function helpers.dimenfactor(unit,tfmdata) -- could be a method of a font instance +function helpers.dimenfactor(unit,id) if unit == "ex" then - return (tfmdata and tfmdata.parameters.x_height) or 655360 + return id and exheights[id] or 282460 -- lm 10pt elseif unit == "em" then - return (tfmdata and tfmdata.parameters.em_width) or 655360 + return id and emwidths [id] or 655360 -- lm 10pt else local du = dimenfactors[unit] return du and 1/du or tonumber(unit) or 1 @@ -1555,22 +1782,28 @@ end -- redefinition -local quads = hashes.quads -local xheights = hashes.xheights +-- local hashes = fonts.hashes +-- local emwidths = hashes.emwidths +-- local exheights = hashes.exheights -setmetatableindex(number.dimenfactors, function(t,k) +setmetatableindex(dimenfactors, function(t,k) if k == "ex" then - return xheigths[currentfont()] + return 1/exheights[currentfont()] elseif k == "em" then - return quads[currentfont()] - elseif k == "%" then - return dimen.hsize/100 + return 1/emwidths[currentfont()] + elseif k == "pct" or k == "%" then + return 1/(texget("hsize")/100) else -- error("wrong dimension: " .. (s or "?")) -- better a message return false end end) +dimenfactors.ex = nil +dimenfactors.em = nil +dimenfactors["%"] = nil +dimenfactors.pct = nil + --[[ldx-- <p>Before a font is passed to <l n='tex'/> we scale it. Here we also need to scale virtual characters.</p> @@ -1582,8 +1815,11 @@ function constructors.checkvirtualids(tfmdata) 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 + local fi = fonts[i] + if fi[2] == 0 then + fi[2] = selfid + elseif fi.id == 0 then + fi.id = selfid end end else @@ -1631,7 +1867,7 @@ end -- end function commands.setfontofid(id) - context.getvalue(csnames[id]) + context_getvalue(csnames[id]) end -- more interfacing: @@ -1646,19 +1882,19 @@ local hows = { ["="] = "replace", } -function commands.feature(how,parent,name,font) - if not how then - if trace_features and texattribute[0] ~= 0 then +function commands.feature(how,parent,name,font) -- 0/1 test temporary for testing + if not how or how == 0 then + if trace_features and texgetattribute(0) ~= 0 then report_cummulative("font %!font:name!, reset",fontdata[font or true]) end - texattribute[0] = 0 - elseif how == true then + texsetattribute(0,0) + elseif how == true or how == 1 then local hash = "feature > " .. parent local done = cache[hash] if trace_features and done then report_cummulative("font %!font:name!, revive %a : %!font:features!",fontdata[font or true],parent,setups[numbers[done]]) end - texattribute[0] = done or 0 + texsetattribute(0,done or 0) else local full = parent .. how .. name local hash = "feature > " .. full @@ -1676,7 +1912,7 @@ function commands.feature(how,parent,name,font) report_cummulative("font %!font:name!, %s %a : %!font:features!",fontdata[font or true],hows[how],full,setups[numbers[done]]) end end - texattribute[0] = done + texsetattribute(0,done) end end @@ -1707,24 +1943,24 @@ end -- a fontkern plug: -local copy_node = node.copy -local kern = nodes.pool.register(nodes.pool.kern()) +local copy_node = nuts.copy +local kern = nuts.pool.register(nuts.pool.kern()) -node.set_attribute(kern,attributes.private('fontkern'),1) -- we can have several, attributes are shared +setattr(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 + setfield(c,"kern",k) return c end) -directives.register("nodes.injections.fontkern", function(v) kern.subtype = v and 0 or 1 end) +directives.register("nodes.injections.fontkern", function(v) setfield(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 otffeatures = constructors.newfeatures("otf") local registerotffeature = otffeatures.register local analyzers = fonts.analyzers @@ -1732,7 +1968,7 @@ local methods = analyzers.methods local unsetvalue = attributes.unsetvalue -local traverse_by_id = node.traverse_id +local traverse_by_id = nuts.traverse_id local a_color = attributes.private('color') local a_colormodel = attributes.private('colormodel') @@ -1759,16 +1995,17 @@ local names = { local function markstates(head) if head then - local model = head[a_colormodel] or 1 + head = tonut(head) + local model = getattr(head,a_colormodel) or 1 for glyph in traverse_by_id(glyph_code,head) do - local a = glyph[a_state] + local a = getprop(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 + setattr(glyph,a_colormodel,model) + setattr(glyph,a_color,color) end end end @@ -1803,7 +2040,7 @@ local function analyzeprocessor(head,font,attr) end registerotffeature { -- adapts - name = "analyze", + name = "analyze", processors = { node = analyzeprocessor, } @@ -1811,9 +2048,18 @@ registerotffeature { -- adapts 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 + if not font or getfont(n) == font then + setattr(n,a_color,unsetvalue) end end return head, true end + +function commands.purefontname(name) + if type(name) == "number" then + name = fonts.helpers.name(name) + end + if type(name) == "string" then + context(file.basename(name)) + end +end |