diff options
Diffstat (limited to 'tex/context/base/mkxl/node-fnt.lmt')
-rw-r--r-- | tex/context/base/mkxl/node-fnt.lmt | 580 |
1 files changed, 580 insertions, 0 deletions
diff --git a/tex/context/base/mkxl/node-fnt.lmt b/tex/context/base/mkxl/node-fnt.lmt new file mode 100644 index 000000000..658ea46bb --- /dev/null +++ b/tex/context/base/mkxl/node-fnt.lmt @@ -0,0 +1,580 @@ +if not modules then modules = { } end modules ['node-fnt'] = { + version = 1.001, + comment = "companion to font-ini.mkiv", + author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright = "PRAGMA ADE / ConTeXt Development Team", + license = "see context related readme files", +} + +if not context then os.exit() end -- generic function in node-dum + +local next, type = next, type +local concat, keys = table.concat, table.keys + +local nodes, node, fonts = nodes, node, fonts + +local trace_characters = false trackers.register("nodes.characters", function(v) trace_characters = v end) +local trace_fontrun = false trackers.register("nodes.fontrun", function(v) trace_fontrun = v end) +local trace_variants = false trackers.register("nodes.variants", function(v) trace_variants = v end) + +-- bad namespace for directives + +local force_discrun = true directives.register("nodes.discrun", function(v) force_discrun = v end) +local force_boundaryrun = true directives.register("nodes.boundaryrun", function(v) force_boundaryrun = v end) +local force_basepass = true directives.register("nodes.basepass", function(v) force_basepass = v end) +local keep_redundant = false directives.register("nodes.keepredundant",function(v) keep_redundant = v end) + +local report_fonts = logs.reporter("fonts","processing") + +local fonthashes = fonts.hashes +local fontdata = fonthashes.identifiers +local fontvariants = fonthashes.variants +local fontmodes = fonthashes.modes + +local otf = fonts.handlers.otf + +local starttiming = statistics.starttiming +local stoptiming = statistics.stoptiming + +local nodecodes = nodes.nodecodes +local boundarycodes = nodes.boundarycodes + +local handlers = nodes.handlers + +local nuts = nodes.nuts + +local getid = nuts.getid +local getsubtype = nuts.getsubtype +local getreplace = nuts.getreplace +local getnext = nuts.getnext +local getprev = nuts.getprev +local getboth = nuts.getboth +local getdata = nuts.getdata +local getglyphdata = nuts.getglyphdata + +local setchar = nuts.setchar +local setlink = nuts.setlink +local setnext = nuts.setnext +local setprev = nuts.setprev + +local isglyph = nuts.isglyph -- unchecked +local ischar = nuts.ischar -- checked + +----- traverse_id = nuts.traverse_id +----- traverse_char = nuts.traverse_char +local nextboundary = nuts.traversers.boundary +local nextdisc = nuts.traversers.disc +local nextchar = nuts.traversers.char + +local flush_node = nuts.flush + +local disc_code = nodecodes.disc +local boundary_code = nodecodes.boundary + +local wordboundary_code = boundarycodes.word + +local protect_glyphs = nuts.protect_glyphs +local unprotect_glyphs = nuts.unprotect_glyphs + +local setmetatableindex = table.setmetatableindex + +-- some tests with using an array of dynamics[id] and processes[id] demonstrated +-- that there was nothing to gain (unless we also optimize other parts) +-- +-- maybe getting rid of the intermediate shared can save some time + +local run = 0 + +local setfontdynamics = { } +local fontprocesses = { } + +-- setmetatableindex(setfontdynamics, function(t,font) +-- local tfmdata = fontdata[font] +-- local shared = tfmdata.shared +-- local v = shared and shared.dynamics and otf.setdynamics or false +-- t[font] = v +-- return v +-- end) + +setmetatableindex(setfontdynamics, function(t,font) + local tfmdata = fontdata[font] + local shared = tfmdata.shared + local f = shared and shared.dynamics and otf.setdynamics or false + if f then + local v = { } + t[font] = v + setmetatableindex(v,function(t,k) + local v = f(font,k) + t[k] = v + return v + end) + return v + else + t[font] = false + return false + end +end) + +setmetatableindex(fontprocesses, function(t,font) + local tfmdata = fontdata[font] + local shared = tfmdata.shared -- we need to check shared, only when same features + local processes = shared and shared.processes + if processes and #processes > 0 then + t[font] = processes + return processes + else + t[font] = false + return false + end +end) + +fonts.hashes.setdynamics = setfontdynamics +fonts.hashes.processes = fontprocesses + +-- if we forget about basemode we don't need to test too much here and we can consider running +-- over sub-ranges .. this involves a bit more initializations but who cares .. in that case we +-- also need to use the stop criterium (we already use head too) ... we cannot use traverse +-- then, so i'll test it on some local clone first ... the only pitfall is changed directions +-- inside a run which means that we need to keep track of this which in turn complicates matters +-- in a way i don't like + +-- we need to deal with the basemode fonts here and can only run over ranges as we otherwise get +-- luatex craches due to all kind of asserts in the disc/lig builder + +-- there is no gain in merging used (dynamic 0) and dynamics apart from a bit less code + +local ligaturing = nuts.ligaturing +local kerning = nuts.kerning + +local function start_trace(head) + run = run + 1 + report_fonts() + report_fonts("checking node list, run %s",run) + report_fonts() + local n = head + while n do + local char, id = isglyph(n) + if char then + local font = id + local attr = getglyphdata(n) or 0 + report_fonts("font %03i, dynamic %03i, glyph %C",font,attr,char) + elseif id == disc_code then + report_fonts("[disc] %s",nodes.listtoutf(n,true,false,n)) + elseif id == boundary_code then + report_fonts("[boundary] %i:%i",getsubtype(n),getdata(n)) + else + report_fonts("[%s]",nodecodes[id]) + end + n = getnext(n) + end +end + +local function stop_trace(u,usedfonts,a,attrfonts,b,basefonts,r,redundant) + report_fonts() + report_fonts("statics : %s",u > 0 and concat(keys(usedfonts)," ") or "none") + report_fonts("dynamics: %s",a > 0 and concat(keys(attrfonts)," ") or "none") + report_fonts("built-in: %s",b > 0 and b or "none") + report_fonts("removed : %s",r > 0 and r or "none") + report_fonts() +end + +do + + local usedfonts + local attrfonts + local basefonts -- could be reused + local basefont + local prevfont + local prevattr + local variants + local redundant -- could be reused + local firstnone + local lastfont + local lastproc + local lastnone + + local a, u, b, r + + local function protectnone() + protect_glyphs(firstnone,lastnone) + firstnone = nil + end + + local function setnone(n) + if firstnone then + protectnone() + end + if basefont then + basefont[2] = getprev(n) + basefont = false + end + if not firstnone then + firstnone = n + end + lastnone = n + end + + local function setbase(n) + if firstnone then + protectnone() + end + if force_basepass then + if basefont then + basefont[2] = getprev(n) + end + b = b + 1 + basefont = { n, false } + basefonts[b] = basefont + end + end + + local function setnode(n,font,attr) -- we could use prevfont and prevattr when we set then first + if firstnone then + protectnone() + end + if basefont then + basefont[2] = getprev(n) + basefont = false + end + if attr > 0 then + local used = attrfonts[font] + if not used then + used = { } + attrfonts[font] = used + end + if not used[attr] then + local fd = setfontdynamics[font] + if fd then + used[attr] = fd[attr] + a = a + 1 + end + end + else + local used = usedfonts[font] + if not used then + lastfont = font + lastproc = fontprocesses[font] + if lastproc then + usedfonts[font] = lastproc + u = u + 1 + end + end + end + end + + function handlers.characters(head,groupcode,size,packtype,direction) + -- either next or not, but definitely no already processed list + starttiming(nodes) + + usedfonts = { } + attrfonts = { } + basefonts = { } + basefont = nil + prevfont = nil + prevattr = 0 + variants = nil + redundant = nil + firstnone = nil + lastfont = nil + lastproc = nil + lastnone = nil + +local fontmode = nil + + a, u, b, r = 0, 0, 0, 0 + + if trace_fontrun then + start_trace(head) + end + + -- There is no gain in checking for a single glyph and then having a fast path. On the + -- metafun manual (with some 2500 single char lists) the difference is just noise. + + for n, char, font in nextchar, head do + +-- local attr = getglyphdata(n) or 0 -- zero attribute is reserved for fonts in context +-- if font ~= prevfont or attr ~= prevattr then +-- prevfont = font +-- prevattr = attr +-- variants = fontvariants[font] +-- local fontmode = fontmodes[font] +-- if fontmode == "none" then +-- setnone(n) +-- elseif fontmode == "base" then +-- setbase(n) +-- else +-- setnode(n,font,attr) +-- end +-- elseif firstnone then +-- lastnone = n +-- end + + if font ~= prevfont then + prevfont = font + fontmode = fontmodes[font] + if fontmode == "none" then + prevattr = 0 + variants = false + setnone(n) + elseif fontmode == "base" then + prevattr = 0 + variants = false + setbase(n) + else + local attr = getglyphdata(n) or 0 -- zero attribute is reserved for fonts in context + prevattr = attr + variants = fontvariants[font] + setnode(n,font,attr) + end + elseif fontmode == "node" then + local attr = getglyphdata(n) or 0 -- zero attribute is reserved for fonts in context + if attr ~= prevattr then + prevattr = attr + variants = fontvariants[font] + setnode(n,font,attr) + end + elseif firstnone then + lastnone = n + end + + if variants then + if (char >= 0xFE00 and char <= 0xFE0F) or (char >= 0xE0100 and char <= 0xE01EF) then + -- if variants and char >= 0xFE00 then + -- if char < 0xFE0F or (char >= 0xE0100 and char <= 0xE01EF) then + local hash = variants[char] + if hash then + local p = getprev(n) + if p then + local char = ischar(p) -- checked + local variant = hash[char] + if variant then + if trace_variants then + report_fonts("replacing %C by %C",char,variant) + end + setchar(p,variant) + if redundant then + r = r + 1 + redundant[r] = n + else + r = 1 + redundant = { n } + end + end + end + elseif keep_redundant then + -- go on, can be used for tracing + elseif redundant then + r = r + 1 + redundant[r] = n + else + r = 1 + redundant = { n } + end + end + end + + end + + if firstnone then + protectnone() + end + + if force_boundaryrun then + + -- we can inject wordboundaries and then let the hyphenator do its work + -- but we need to get rid of those nodes in order to build ligatures + -- and kern (a rather context thing) + + for b, subtype in nextboundary, head do + if subtype == wordboundary_code then + if redundant then + r = r + 1 + redundant[r] = b + else + r = 1 + redundant = { b } + end + end + end + + end + + if redundant then + for i=1,r do + local r = redundant[i] + local p, n = getboth(r) + if r == head then + head = n + setprev(n) + else + setlink(p,n) + end + if b > 0 then + for i=1,b do + local bi = basefonts[i] + local b1 = bi[1] + local b2 = bi[2] + if b1 == b2 then + if b1 == r then + bi[1] = false + bi[2] = false + end + elseif b1 == r then + bi[1] = n + elseif b2 == r then + bi[2] = p + end + end + end + flush_node(r) + end + end + + if force_discrun then + -- basefont is not supported in disc only runs ... it would mean a lot of + -- ranges .. we could try to run basemode as a separate processor run but not + -- for now (we can consider it when the new node code is tested + for d in nextdisc, head do + -- doing only replace is good enough because pre and post are normally used + -- for hyphens and these come from fonts that part of the hyphenated word + local r = getreplace(d) + if r then + local prevfont = nil + local prevattr = nil + local none = false + firstnone = nil + basefont = nil + for n, char, font in nextchar, r do + local attr = getglyphdata(n) or 0 -- zero attribute is reserved for fonts in context + if font ~= prevfont or attr ~= prevattr then + prevfont = font + prevattr = attr + local fontmode = fontmodes[font] + if fontmode == "none" then + setnone(n) + elseif fontmode == "base" then + -- so the replace gets an extra treatment ... so be it + setbase(n) + else + setnode(n,font,attr) + end + elseif firstnone then + -- lastnone = n + lastnone = nil + end + -- we assume one font for now (and if there are more and we get into issues then + -- we can always remove the break) + break + end + if firstnone then + protectnone() + end + end + end + + end + + if trace_fontrun then + stop_trace(u,usedfonts,a,attrfonts,b,basefonts,r,redundant) + end + + -- in context we always have at least 2 processors + if u == 0 then + -- skip + elseif u == 1 then + local attr = a > 0 and 0 or false -- 0 is the savest way + for i=1,#lastproc do + head = lastproc[i](head,lastfont,attr,direction) + end + else + -- local attr = a == 0 and false or 0 -- 0 is the savest way + local attr = a > 0 and 0 or false -- 0 is the savest way + for font, processors in next, usedfonts do -- unordered + for i=1,#processors do + head = processors[i](head,font,attr,direction,u) + end + end + end + if a == 0 then + -- skip + elseif a == 1 then + local font, dynamics = next(attrfonts) + for attribute, processors in next, dynamics do -- unordered, attr can switch in between + for i=1,#processors do + head = processors[i](head,font,attribute,direction) + end + end + else + for font, dynamics in next, attrfonts do + for attribute, processors in next, dynamics do -- unordered, attr can switch in between + for i=1,#processors do + head = processors[i](head,font,attribute,direction,a) + end + end + end + end + if b == 0 then + -- skip + elseif b == 1 then + -- only one font + local range = basefonts[1] + local start = range[1] + local stop = range[2] + if (start or stop) and (start ~= stop) then + local front = head == start + if stop then + start = ligaturing(start,stop) + start = kerning(start,stop) + elseif start then -- safeguard + start = ligaturing(start) + start = kerning(start) + end + if front and head ~= start then + head = start + end + end + else + -- multiple fonts + for i=1,b do + local range = basefonts[i] + local start = range[1] + local stop = range[2] + if start then -- and start ~= stop but that seldom happens + local front = head == start + local prev = getprev(start) + local next = getnext(stop) + if stop then + start, stop = ligaturing(start,stop) + start, stop = kerning(start,stop) + else + start = ligaturing(start) + start = kerning(start) + end + -- is done automatically + if prev then + setlink(prev,start) + end + if next then + setlink(stop,next) + end + -- till here + if front and head ~= start then + head = start + end + end + end + end + + stoptiming(nodes) + + if trace_characters then + nodes.report(head) + end + + return head + end + +end + +handlers.protectglyphs = protect_glyphs +handlers.unprotectglyphs = unprotect_glyphs |