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 handlers = nodes.handlers local nuts = nodes.nuts local tonut = nuts.tonut local tonode = nuts.tonode local getattr = nuts.getattr local getid = nuts.getid local getfont = nuts.getfont local getsubtype = nuts.getsubtype local getchar = nuts.getchar local getdisc = nuts.getdisc local getnext = nuts.getnext local getprev = nuts.getprev local getboth = nuts.getboth local getfield = nuts.getfield ----- getdisc = nuts.getdisc local setchar = nuts.setchar local setlink = nuts.setlink local setfield = nuts.setfield local setprev = nuts.setprev local isglyph = nuts.isglyph -- unchecked local ischar = nuts.ischar -- checked local traverse_id = nuts.traverse_id local traverse_char = nuts.traverse_char local remove_node = nuts.remove local protect_glyph = nuts.protect_glyph local free_node = nuts.free local glyph_code = nodecodes.glyph local disc_code = nodecodes.disc local boundary_code = nodecodes.boundary local word_boundary = nodes.boundarycodes.word 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 -- potential speedup: check for subtype < 256 so that we can remove that test -- elsewhere, danger: injected nodes will not be dealt with but that does not -- happen often; we could consider processing sublists but that might need more -- checking later on; the current approach also permits variants 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 local ligaturing = nuts.ligaturing local kerning = nuts.kerning local expanders function fonts.setdiscexpansion(v) if v == nil or v == true then expanders = languages and languages.expanders elseif type(v) == "table" then expanders = v else expanders = false end end function fonts.getdiscexpansion() return expanders and true or false end fonts.setdiscexpansion(true) function handlers.characters(head) -- either next or not, but definitely no already processed list starttiming(nodes) local usedfonts = { } local attrfonts = { } local basefonts = { } local a, u, b = 0, 0, 0 local basefont = nil local prevfont = nil local prevattr = 0 local mode = nil local done = false local variants = nil local redundant = nil if trace_fontrun then run = run + 1 report_fonts() report_fonts("checking node list, run %s",run) report_fonts() local n = tonut(head) while n do local char, id = isglyph(n) if char then local font = getfont(n) local attr = getattr(n,0) 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),getfield(n,"value")) else report_fonts("[%s]",nodecodes[id]) end n = getnext(n) end end local nuthead = tonut(head) for n in traverse_char(nuthead) do local font = getfont(n) local attr = getattr(n,0) or 0 -- zero attribute is reserved for fonts in context if font ~= prevfont or attr ~= prevattr then prevfont = font prevattr = attr mode = fontmodes[font] -- we can also avoid the attr check variants = fontvariants[font] if mode == "none" then -- skip protect_glyph(n) else if basefont then basefont[2] = getprev(n) 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 elseif force_basepass then b = b + 1 basefont = { n, nil } basefonts[b] = basefont end end else local used = usedfonts[font] if not used then local fp = fontprocesses[font] if fp then usedfonts[font] = fp u = u + 1 elseif force_basepass then b = b + 1 basefont = { n, nil } basefonts[b] = basefont end end end end end if variants then local char = getchar(n) if char >= 0xFE00 and (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 not redundant then redundant = { n } else redundant[#redundant+1] = n end end end elseif keep_redundant then -- go on, can be used for tracing elseif not redundant then redundant = { n } else redundant[#redundant+1] = n end end end 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 in traverse_id(boundary_code,nuthead) do if getsubtype(b) == word_boundary then if redundant then redundant[#redundant+1] = b else redundant = { b } end end end end if redundant then for i=1,#redundant do local r = redundant[i] local p, n = getboth(r) if r == nuthead then nuthead = n setprev(n) else setlink(p,n) end if b > 0 then for i=1,b do local bi = basefonts[i] if r == bi[1] then bi[1] = n end if r == bi[2] then bi[2] = n end end end free_node(r) end end local e = 0 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 -- local prevfont = nil -- local prevattr = 0 for d in traverse_id(disc_code,nuthead) do -- we could use first_glyph, only doing replace is good enough local _, _, r = getdisc(d) if r then for n in traverse_char(r) do local font = getfont(n) local attr = getattr(n,0) or 0 -- zero attribute is reserved for fonts in context if font ~= prevfont or attr ~= prevattr then 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 local fp = fontprocesses[font] if fp then usedfonts[font] = fp u = u + 1 end end end prevfont = font prevattr = attr end end break elseif expanders then local subtype = getsubtype(d) if subtype == discretionary_code then -- already done when replace else expanders[subtype](d) e = e + 1 end end end end if trace_fontrun then 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",redundant and #redundant > 0 and #redundant or "none") if expanders then report_fonts("expanded: %s",e > 0 and e or "none") end report_fonts() end -- in context we always have at least 2 processors if u == 0 then -- skip elseif u == 1 then local font, processors = next(usedfonts) for i=1,#processors do local h, d = processors[i](head,font,0) if d then head = h or head done = true end end else for font, processors in next, usedfonts do for i=1,#processors do local h, d = processors[i](head,font,0) if d then head = h or head done = true end end end end if a == 0 then -- skip elseif a == 1 then local font, dynamics = next(attrfonts) for attribute, processors in next, dynamics do -- attr can switch in between for i=1,#processors do local h, d = processors[i](head,font,attribute) if d then head = h or head done = true end end end else for font, dynamics in next, attrfonts do for attribute, processors in next, dynamics do -- attr can switch in between for i=1,#processors do local h, d = processors[i](head,font,attribute) if d then head = h or head done = true end 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 = nuthead == start if stop then start, stop = ligaturing(start,stop) start, stop = kerning(start,stop) elseif start then -- safeguard start = ligaturing(start) start = kerning(start) end if front then head = tonode(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 local front = nuthead == start local prev, next if stop then next = getnext(stop) start, stop = ligaturing(start,stop) start, stop = kerning(start,stop) else prev = getprev(start) start = ligaturing(start) start = kerning(start) end if prev then setlink(prev,start) end if next then setlink(stop,next) end if front and nuthead ~= start then head = tonode(nuthead) end end end end stoptiming(nodes) if trace_characters then nodes.report(head,done) end return head, true end local d_protect_glyphs = nuts.protect_glyphs local d_unprotect_glyphs = nuts.unprotect_glyphs handlers.protectglyphs = function(n) return d_protect_glyphs (tonut(n)) end handlers.unprotectglyphs = function(n) return d_unprotect_glyphs(tonut(n)) end -- function handlers.protectglyphs(h) -- local h = tonut(h) -- for n in traverse_id(disc_code,h) do -- local pre, post, replace = getdisc(n) -- if pre then d_protect_glyphs(pre) end -- if post then d_protect_glyphs(post) end -- if replace then d_protect_glyphs(replace) end -- end -- return d_protect_glyphs(h) -- end