diff options
Diffstat (limited to 'Master/texmf-dist/tex/context/base/mkiv/font-ctx.lua')
-rw-r--r-- | Master/texmf-dist/tex/context/base/mkiv/font-ctx.lua | 585 |
1 files changed, 436 insertions, 149 deletions
diff --git a/Master/texmf-dist/tex/context/base/mkiv/font-ctx.lua b/Master/texmf-dist/tex/context/base/mkiv/font-ctx.lua index 87885f64f70..6847a2b8d30 100644 --- a/Master/texmf-dist/tex/context/base/mkiv/font-ctx.lua +++ b/Master/texmf-dist/tex/context/base/mkiv/font-ctx.lua @@ -12,21 +12,23 @@ if not modules then modules = { } end modules ['font-ctx'] = { -- Todo: make a proper 'next id' mechanism (register etc) or wait till 'true' -- in virtual fonts indices is implemented. -local context, commands = context, commands +local tostring, next, type, rawget, tonumber = tostring, next, type, rawget, tonumber local format, gmatch, match, find, lower, upper, gsub, byte, topattern = string.format, string.gmatch, string.match, string.find, string.lower, string.upper, string.gsub, string.byte, string.topattern local concat, serialize, sort, fastcopy, mergedtable = table.concat, table.serialize, table.sort, table.fastcopy, table.merged local sortedhash, sortedkeys, sequenced = table.sortedhash, table.sortedkeys, table.sequenced -local settings_to_hash, hash_to_string, settings_to_array = utilities.parsers.settings_to_hash, utilities.parsers.hash_to_string, utilities.parsers.settings_to_array +local parsers = utilities.parsers +local settings_to_hash, hash_to_string, settings_to_array = parsers.settings_to_hash, parsers.hash_to_string, parsers.settings_to_array local formatcolumns = utilities.formatters.formatcolumns local mergehashes = utilities.parsers.mergehashes local formatters = string.formatters local basename = file.basename -local tostring, next, type, rawget, tonumber = tostring, next, type, rawget, tonumber local utfchar, utfbyte = utf.char, utf.byte local round = math.round +local context, commands = context, commands + local P, S, C, Cc, Cf, Cg, Ct, lpegmatch = lpeg.P, lpeg.S, lpeg.C, lpeg.Cc, lpeg.Cf, lpeg.Cg, lpeg.Ct, lpeg.match local trace_features = false trackers.register("fonts.features", function(v) trace_features = v end) @@ -66,6 +68,8 @@ local hashes = fonts.hashes local currentfont = font.current local definefont = font.define +local getprivateslot = helpers.getprivateslot + local cleanname = names.cleanname local encodings = fonts.encodings @@ -75,11 +79,12 @@ local aglunicodes = nil -- delayed loading local nuts = nodes.nuts local tonut = nuts.tonut +local nextchar = nuts.traversers.char + local getattr = nuts.getattr local setattr = nuts.setattr local getprop = nuts.getprop local setprop = nuts.setprop -local getfont = nuts.getfont local setsubtype = nuts.setsubtype local texgetattribute = tex.getattribute @@ -158,17 +163,8 @@ helpers.name = getfontname local addformatter = utilities.strings.formatters.add -if LUAVERSION < 5.2 then - - addformatter(formatters,"font:name", [["'"..fontname(%s).."'"]], "local fontname = fonts.helpers.name") - addformatter(formatters,"font:features",[["'"..sequenced(%s," ",true).."'"]],"local sequenced = table.sequenced") - -else - - addformatter(formatters,"font:name", [["'"..fontname(%s).."'"]], { fontname = helpers.name }) - addformatter(formatters,"font:features",[["'"..sequenced(%s," ",true).."'"]],{ sequenced = table.sequenced }) - -end +addformatter(formatters,"font:name", [["'"..fontname(%s).."'"]], { fontname = helpers.name }) +addformatter(formatters,"font:features",[["'"..sequenced(%s," ",true).."'"]],{ sequenced = table.sequenced }) -- ... like font-sfm or so @@ -472,36 +468,40 @@ registerotffeature { -- }, -- } -local beforecopyingcharacters = sequencers.new { - name = "beforecopyingcharacters", - arguments = "target,original", -} +do + + local beforecopyingcharacters = sequencers.new { + name = "beforecopyingcharacters", + arguments = "target,original", + } -appendgroup(beforecopyingcharacters,"before") -- user -appendgroup(beforecopyingcharacters,"system") -- private -appendgroup(beforecopyingcharacters,"after" ) -- user + appendgroup(beforecopyingcharacters,"before") -- user + appendgroup(beforecopyingcharacters,"system") -- private + appendgroup(beforecopyingcharacters,"after" ) -- user -function constructors.beforecopyingcharacters(original,target) - local runner = beforecopyingcharacters.runner - if runner then - runner(original,target) + function constructors.beforecopyingcharacters(original,target) + local runner = beforecopyingcharacters.runner + if runner then + runner(original,target) + end end -end -local aftercopyingcharacters = sequencers.new { - name = "aftercopyingcharacters", - arguments = "target,original", -} + local aftercopyingcharacters = sequencers.new { + name = "aftercopyingcharacters", + arguments = "target,original", + } -appendgroup(aftercopyingcharacters,"before") -- user -appendgroup(aftercopyingcharacters,"system") -- private -appendgroup(aftercopyingcharacters,"after" ) -- user + appendgroup(aftercopyingcharacters,"before") -- user + appendgroup(aftercopyingcharacters,"system") -- private + appendgroup(aftercopyingcharacters,"after" ) -- user -function constructors.aftercopyingcharacters(original,target) - local runner = aftercopyingcharacters.runner - if runner then - runner(original,target) + function constructors.aftercopyingcharacters(original,target) + local runner = aftercopyingcharacters.runner + if runner then + runner(original,target) + end end + end --[[ldx-- @@ -566,6 +566,12 @@ local function definecontext(name,t) -- can be shared return number, t end +-- {a,b,c} as table (so we don' need to parse again when it gets applied) +-- we will update this ... when we have foo={a,b,c} then we can keep the table + +-- \definefontfeature[demo][a={b,c}] +-- \definefontfeature[demo][a={b=12,c={34,35}}] + local function presetcontext(name,parent,features) -- will go to con and shared if features == "" and find(parent,"=",1,true) then features = parent @@ -575,6 +581,16 @@ local function presetcontext(name,parent,features) -- will go to con and shared features = { } elseif type(features) == "string" then features = normalize_features(settings_to_hash(features)) + -- if type(value) == "string" and find(value,"[=:]") then + -- local t = settings_to_hash_colon_too(value) -- clashes with foo=file:bar + for key, value in next, features do + if type(value) == "string" and find(value,"[=]") then + local t = settings_to_hash(value) + if next(t) then + features[key] = sequenced(normalize_features(t,true),",") + end + end + end else features = normalize_features(features) end @@ -584,8 +600,8 @@ local function presetcontext(name,parent,features) -- will go to con and shared local s = setups[p] if s then for k, v in next, s do --- no, as then we cannot overload: e.g. math,mathextra --- reverted, so we only take from parent when not set + -- no, as then we cannot overload: e.g. math,mathextra + -- reverted, so we only take from parent when not set if features[k] == nil then features[k] = v end @@ -777,7 +793,8 @@ end local function registercontext(fontnumber,extraname,option) local extra = setups[extraname] if extra then - local mergedfeatures, mergedname = { }, nil + local mergedfeatures = { } + local mergedname = nil if option < 0 then mergedname = fontnumber .. "-" .. extraname else @@ -997,25 +1014,33 @@ definers.registersplit(":",colonized,"direct") -- define (two steps) ------ space = P(" ") ------ spaces = space^0 -local leftparent = (P"(") -local rightparent = (P")") -local value = C((leftparent * (1-rightparent)^0 * rightparent + (1-space))^1) -local dimension = C((space/"" + P(1))^1) -local rest = C(P(1)^0) -local scale_none = Cc(0) -local scale_at = (P("at") +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" } - -local sizepattern = spaces * (scale_at + scale_sa + scale_mo + scale_ht + scale_cp + scale_scaled + scale_none) -local splitpattern = spaces * value * spaces * rest +local sizepattern, splitpattern, specialscale do + + ----- space = P(" ") + ----- spaces = space^0 + local leftparent = (P"(") + local rightparent = (P")") + local leftbrace = (P"{") + local rightbrace = (P"}") + local withinparents = leftparent * (1-rightparent)^0 * rightparent + local withinbraces = leftbrace * (1-rightbrace )^0 * rightbrace + local value = C((withinparents + withinbraces + (1-space))^1) + local dimension = C((space/"" + P(1))^1) + local rest = C(P(1)^0) + local scale_none = Cc(0) + local scale_at = (P("at") +P("@")) * Cc(1) * spaces * dimension -- dimension + local scale_sa = P("sa") * Cc(2) * spaces * dimension -- number + local scale_mo = P("mo") * Cc(3) * spaces * dimension -- number + local scale_scaled = P("scaled") * Cc(4) * spaces * dimension -- number + local scale_ht = P("ht") * Cc(5) * spaces * dimension -- dimension + local scale_cp = P("cp") * Cc(6) * spaces * dimension -- dimension + + specialscale = { [5] = "ht", [6] = "cp" } + + sizepattern = spaces * (scale_at + scale_sa + scale_mo + scale_ht + scale_cp + scale_scaled + scale_none) + splitpattern = spaces * value * spaces * rest + +end function helpers.splitfontpattern(str) local name, size = lpegmatch(splitpattern,str) @@ -1299,7 +1324,6 @@ do -- else too many locals local busy = false scanners.definefont_two = function() - local global = scanboolean() -- \ifx\fontclass\empty\s!false\else\s!true\fi local cs = scanstring () -- {#csname}% local str = scanstring () -- \somefontfile @@ -1411,6 +1435,7 @@ do -- else too many locals specification.fallbacks = fontfallbacks end end + -- local tfmdata = definers.read(specification,size) -- id not yet known (size in spec?) -- local lastfontid = 0 @@ -1426,15 +1451,15 @@ do -- else too many locals -- characters[0x2007] = { width = characters[0x0030] and characters[0x0030].width or parameters.space } -- figure -- characters[0x2008] = { width = characters[0x002E] and characters[0x002E].width or parameters.space } -- period -- - local fallbacks = specification.fallbacks + local fallbacks = specification.fallbacks or "" local mathsize = (mathsize == 1 or mathsize == 2 or mathsize == 3) and mathsize or nil -- can be unset so we test 1 2 3 - if fallbacks and fallbacks ~= "" and mathsize and not busy then + if fallbacks ~= "" and mathsize and not busy then busy = true -- We need this ugly hack in order to resolve fontnames (at the \TEX end). Originally -- math was done in Lua after loading (plugged into aftercopying). -- -- After tl 2017 I'll also do text fallbacks this way (although backups there are done - -- in a completely different way. + -- in a completely different way.) if trace_defining then report_defining("defining %a, id %a, target %a, features %a / %a, fallbacks %a / %a, step %a", name,id,nice_cs(cs),classfeatures,fontfeatures,classfallbacks,fontfallbacks,1) @@ -1825,7 +1850,7 @@ function mappings.loadfile(name) if trace_mapfiles then report_mapfiles("loading map file %a",name) end - pdf.mapfile(name) + lpdf.setmapfile(name) loaded[name] = true end end @@ -1843,17 +1868,15 @@ function mappings.loadline(how,line) if trace_mapfiles then report_mapfiles("processing map line %a",line) end - pdf.mapline(how) + lpdf.setmapline(how) loaded[how] = true end end function mappings.reset() - pdf.mapfile("") + lpdf.setmapfile("") -- tricky ... backend related end -mappings.reset() -- resets the default file - implement { name = "loadmapfile", actions = mappings.loadfile, @@ -1876,19 +1899,36 @@ implement { -- => commands -local function nametoslot(name) +local pattern = P("P") + * (lpeg.patterns.hexdigit^4 / function(s) return tonumber(s,16) end) + * P(-1) + +local function nametoslot(name) -- also supports PXXXXX (4+ positions) local t = type(name) if t == "string" then - local slot = unicodes[true][name] + local unic = unicodes[true] + local slot = unic[name] + if slot then + return slot + end + -- + local slot = unic[gsub(name,"_"," ")] or unic[gsub(name,"_","-")] or + unic[gsub(name,"-"," ")] or unic[gsub(name,"-","_")] or + unic[gsub(name," ","_")] or unic[gsub(name," ","-")] if slot then return slot end + -- if not aglunicodes then aglunicodes = encodings.agl.unicodes end local char = characters[true] local slot = aglunicodes[name] - if char[slot] then + if slot and char[slot] then + return slot + end + local slot = lpegmatch(pattern,name) + if slot and char[slot] then return slot end -- not in font @@ -1901,14 +1941,13 @@ local function nametoslot(name) end end - local found = { } local function descriptiontoslot(name) local t = type(name) if t == "string" then -- slow - local list = sortedkeys(chardata) + local list = sortedkeys(chardata) -- can be a cache with weak tables local slot = found[name] local char = characters[true] if slot then @@ -1971,13 +2010,17 @@ local function descriptiontoslot(name) end end -local function indextoslot(index) - local r = resources[true] +local function indextoslot(font,index) + if not index then + index = font + font = true + end + local r = resources[font] if r then local indices = r.indices if not indices then indices = { } - local c = characters[true] + local c = characters[font] for unicode, data in next, c do local di = data.index if di then @@ -2397,9 +2440,12 @@ do return f and (f.gpos[n] or f.gsub[n]) end + local ctx_doifelse = commands.doifelse + local ctx_doif = commands.doif + implement { name = "doifelsecurrentfonthasfeature", - actions = { constructors.currentfonthasfeature, commands.doifelse }, + actions = { constructors.currentfonthasfeature, ctx_doifelse }, arguments = "string" } @@ -2443,10 +2489,23 @@ do implement { name = "definefontfeature", arguments = "3 strings", - actions = presetcontext + actions = presetcontext, } implement { + name = "doifelsefontfeature", + arguments = "string", + actions = function(name) ctx_doifelse(contextnumber(name) > 1) end, + } + + implement { + name = "doifunknownfontfeature", + arguments = "string", + actions = function(name) ctx_doif(contextnumber(name) == 0) end, + } + + + implement { name = "adaptfontfeature", arguments = "2 strings", actions = adaptcontext @@ -2602,8 +2661,6 @@ do local unsetvalue = attributes.unsetvalue - local traverse_char = nuts.traverse_char - local a_color = attributes.private('color') local a_colormodel = attributes.private('colormodel') local a_state = attributes.private('state') @@ -2627,11 +2684,14 @@ do [states.pstf] = "font:5", } + -- todo: traversers + -- todo: check attr_list so that we can use the same .. helper: setcolorattr + local function markstates(head) if head then head = tonut(head) local model = getattr(head,a_colormodel) or 1 - for glyph in traverse_char(head) do + for glyph in nextchar, head do local a = getprop(glyph,a_state) if a then local name = colornames[a] @@ -2682,8 +2742,8 @@ do function methods.nocolor(head,font,attr) - for n in traverse_char(head) do - if not font or getfont(n) == font then + for n, c, f in nextchar, head do + if not font or f == font then setattr(n,a_color,unsetvalue) end end @@ -2708,9 +2768,13 @@ implement { arguments = "string", } -local list = storage.shared.bodyfontsizes or { } +local sharedstorage = storage.shared -storage.shared.bodyfontsizes = list +local list = sharedstorage.bodyfontsizes or { } +local unknown = sharedstorage.unknownbodyfontsizes or { } + +sharedstorage.bodyfontsizes = list +sharedstorage.unknownbodyfontsizes = unknown implement { name = "registerbodyfontsize", @@ -2720,6 +2784,17 @@ implement { end } +interfaces.implement { + name = "registerunknownbodysize", + arguments = "string", + actions = function(size) + if not unknown[size] then + interfaces.showmessage("fonts",14,size) + end + unknown[size] = true + end, +} + implement { name = "getbodyfontsizes", arguments = "string", @@ -2873,6 +2948,12 @@ end -- for the font manual +statistics.register("body font sizes", function() + if next(unknown) then + return formatters["defined: % t, undefined: % t"](sortedkeys(list),sortedkeys(unknown)) + end +end) + statistics.register("used fonts",function() if trace_usage then local filename = file.nameonly(environment.jobname) .. "-fonts-usage.lua" @@ -3018,79 +3099,285 @@ end -- for the moment here (and not in font-con.lua): -local identical = table.identical -local copy = table.copy -local fontdata = fonts.hashes.identifiers -local addcharacters = font.addcharacters - --- This helper is mostly meant to add last-resort (virtual) characters --- or runtime generated fonts (so we forget about features and such). It --- will probably take a while before it get used. - -local trace_adding = false -local report_adding = logs.reporter("fonts","add characters") - -trackers.register("fonts.addcharacters",function(v) trace_adding = v end) - -if addcharacters then - - function fonts.constructors.addcharacters(id,list) - local newchar = list.characters - if newchar then - local data = fontdata[id] - local newfont = list.fonts - local oldchar = data.characters - local oldfont = data.fonts - addcharacters(id, { - characters = newchar, - fonts = newfont, - nomath = not data.properties.hasmath, - }) - -- this is just for tracing, as the assignment only uses the fonts list - -- and doesn't store it otherwise - if newfont then - if oldfont then - local oldn = #oldfont - local newn = #newfont - for n=1,newn do - local ok = false - local nf = newfont[n] - for o=1,oldn do - if identical(nf,oldfont[o]) then - ok = true - break +do + + local identical = table.identical + local copy = table.copy + local fontdata = fonts.hashes.identifiers + local addcharacters = font.addcharacters + + -- This helper is mostly meant to add last-resort (virtual) characters + -- or runtime generated fonts (so we forget about features and such). It + -- will probably take a while before it get used. + + local trace_adding = false + local report_adding = logs.reporter("fonts","add characters") + + trackers.register("fonts.addcharacters",function(v) trace_adding = v end) + + if addcharacters then + + function fonts.constructors.addcharacters(id,list) + local newchar = list.characters + if newchar then + local data = fontdata[id] + local newfont = list.fonts + local oldchar = data.characters + local oldfont = data.fonts + addcharacters(id, { + characters = newchar, + fonts = newfont, + nomath = not data.properties.hasmath, + }) + -- this is just for tracing, as the assignment only uses the fonts list + -- and doesn't store it otherwise + if newfont then + if oldfont then + local oldn = #oldfont + local newn = #newfont + for n=1,newn do + local ok = false + local nf = newfont[n] + for o=1,oldn do + if identical(nf,oldfont[o]) then + ok = true + break + end + end + if not ok then + oldn = oldn + 1 + oldfont[oldn] = newfont[i] end end - if not ok then - oldn = oldn + 1 - oldfont[oldn] = newfont[i] - end + else + data.fonts = newfont end - else - data.fonts = newfont end - end - -- this is because we need to know what goes on and also might - -- want to access character data - for u, c in next, newchar do - if trace_adding then - report_adding("adding character %U to font %!font:name!",u,id) + -- this is because we need to know what goes on and also might + -- want to access character data + for u, c in next, newchar do + if trace_adding then + report_adding("adding character %U to font %!font:name!",u,id) + end + oldchar[u] = c end - oldchar[u] = c end end + + else + function fonts.constructors.addcharacters(id,list) + report_adding("adding characters to %!font:name! is not yet supported",id) + end end -else - function fonts.constructors.addcharacters(id,list) - report_adding("adding characters to %!font:name! is not yet supported",id) + implement { + name = "addfontpath", + arguments = "string", + actions = function(list) + names.addruntimepath(settings_to_array(list)) + end + } + +end + +-- moved here + +do + + local family_font = node.family_font + local new_glyph = nodes.pool.glyph + local fontproperties = fonts.hashes.properties + + local function getprivateslot(id,name) + if not name then + name = id + id = currentfont() + end + local properties = fontproperties[id] + local privates = properties and properties.privates + return privates and privates[name] + end + + local function getprivatenode(tfmdata,name) + if type(tfmdata) == "number" then + tfmdata = fontdata[tfmdata] + end + local properties = tfmdata.properties + local font = properties.id + local slot = getprivateslot(font,name) + if slot then + -- todo: set current attribibutes + local char = tfmdata.characters[slot] + local tonode = char.tonode + if tonode then + return tonode(font,char) + else + return new_glyph(font,slot) + end + end + end + + local function getprivatecharornode(tfmdata,name) + if type(tfmdata) == "number" then + tfmdata = fontdata[tfmdata] + end + local properties = tfmdata.properties + local font = properties.id + local slot = getprivateslot(font,name) + if slot then + -- todo: set current attributes + local char = tfmdata.characters[slot] + local tonode = char.tonode + if tonode then + return "node", tonode(tfmdata,char) + else + return "char", slot + end + end end + + helpers.getprivateslot = getprivateslot + helpers.getprivatenode = getprivatenode + helpers.getprivatecharornode = getprivatecharornode + + implement { + name = "getprivatechar", + arguments = "string", + actions = function(name) + local p = getprivateslot(name) + if p then + context(utfchar(p)) + end + end + } + + implement { + name = "getprivatemathchar", + arguments = "string", + actions = function(name) + local p = getprivateslot(family_font(0),name) + if p then + context(utfchar(p)) + end + end + } + + implement { + name = "getprivateslot", + arguments = "string", + actions = function(name) + local p = getprivateslot(name) + if p then + context(p) + end + end + } + end -implement { - name = "addfontpath", - arguments = "string", - actions = function(list) - names.addruntimepath(settings_to_array(list)) +-- handy, for now here: + +function fonts.helpers.collectanchors(tfmdata) + + local resources = tfmdata.resources -- todo: use shared + + if not resources or resources.anchors then + return resources.anchors end -} + + local anchors = { } + + local function set(unicode,target,class,anchor) + local a = anchors[unicode] + if not a then + anchors[unicode] = { [target] = { anchor } } + return + end + local t = a[target] + if not t then + a[target] = { anchor } + return + end + local x = anchor[1] + local y = anchor[2] + for k, v in next, t do + if v[1] == x and v[2] == y then + return + end + end + t[#t+1] = anchor + end + + local function getanchors(steps,target) + for i=1,#steps do + local step = steps[i] + local coverage = step.coverage + for unicode, data in next, coverage do + local class = data[1] + local anchor = data[2] + if anchor[1] ~= 0 or anchor[2] ~= 0 then + set(unicode,target,class,anchor) + end + end + end + end + + local function getcursives(steps) + for i=1,#steps do + local step = steps[i] + local coverage = step.coverage + for unicode, data in next, coverage do + local class = data[1] + local en = data[2] + local ex = data[3] + if en then + set(unicode,"entry",class,en) + end + if ex then + set(unicode,"exit", class,ex) + end + end + end + end + + local function collect(list) + if list then + for i=1,#list do + local entry = list[i] + local steps = entry.steps + local kind = entry.type + if kind == "gpos_mark2mark" then + getanchors(steps,"mark") + elseif kind == "gpos_mark2base" then + getanchors(steps,"base") + elseif kind == "gpos_mark2ligature" then + getanchors(steps,"ligature") + elseif kind == "gpos_cursive" then + getcursives(steps) + end + end + end + end + + collect(resources.sequences) + collect(resources.sublookups) + + local function sorter(a,b) + if a[1] == b[1] then + return a[2] < b[2] + else + return a[1] < b[1] + end + end + + for unicode, old in next, anchors do + for target, list in next, old do + sort(list,sorter) + end + end + + resources.anchors = anchors + + return anchors + +end |