if not modules then modules = { } end modules ['trac-vis'] = { version = 1.001, optimize = true, comment = "companion to trac-vis.mkiv", author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", copyright = "PRAGMA ADE / ConTeXt Development Team", license = "see context related readme files" } local node, nodes, attributes, tex = node, nodes, attributes, tex local type, tonumber, next, rawget = type, tonumber, next, rawget local gmatch = string.gmatch local formatters = string.formatters local round = math.round -- This module started out in the early days of mkiv and luatex with visualizing -- kerns related to fonts. In the process of cleaning up the visual debugger code it -- made sense to integrate some other code that I had laying around and replace the -- old supp-vis debugging code. As only a subset of the old visual debugger makes -- sense it has become a different implementation. Soms of the m-visual -- functionality will also be ported. The code is rather trivial. The caching is not -- really needed but saves upto 50% of the time needed to add visualization. Of -- course the overall runtime is larger because of color and layer processing in the -- backend (can be times as much) so the runtime is somewhat larger with full -- visualization enabled. In practice this will never happen unless one is demoing. -- todo: global switch (so no attributes) -- todo: maybe also xoffset, yoffset of glyph -- todo: inline concat (more efficient) -- todo: tags can also be numbers (just add to hash) -- todo: make a lmtx variant (a few more efficient fetchers) local nodecodes = nodes.nodecodes local nuts = nodes.nuts local tonut = nuts.tonut local setboth = nuts.setboth local setlink = nuts.setlink local setdisc = nuts.setdisc local setlist = nuts.setlist local setleader = nuts.setleader local setsubtype = nuts.setsubtype local setattr = nuts.setattr local setwidth = nuts.setwidth local setshift = nuts.setshift local getid = nuts.getid local getfont = nuts.getfont local getattr = nuts.getattr local getsubtype = nuts.getsubtype local getbox = nuts.getbox local getlist = nuts.getlist local getleader = nuts.getleader local getnext = nuts.getnext local getprev = nuts.getprev local getboth = nuts.getboth local getdisc = nuts.getdisc local getwhd = nuts.getwhd local getkern = nuts.getkern local getpenalty = nuts.getpenalty local getwidth = nuts.getwidth local getdepth = nuts.getdepth local getshift = nuts.getshift local getexpansion = nuts.getexpansion local getdirection = nuts.getdirection local getstate = nuts.getstate local isglyph = nuts.isglyph local hpack_nodes = nuts.hpack local vpack_nodes = nuts.vpack local copylist = nuts.copylist local copy_node = nuts.copy local insertnodebefore = nuts.insertbefore local insertnodeafter = nuts.insertafter local apply_to_nodes = nuts.apply local find_tail = nuts.tail local effectiveglue = nuts.effectiveglue local flushnodelist = nuts.flushlist local hpack_string = nuts.typesetters.tohpack local texgetattribute = tex.getattribute local texsetattribute = tex.setattribute local setmetatableindex = table.setmetatableindex local unsetvalue = attributes.unsetvalue local current_font = font.current local fonthashes = fonts.hashes local chardata = fonthashes.characters local exheights = fonthashes.exheights local emwidths = fonthashes.emwidths local pt_factor = number.dimenfactors.pt local nodepool = nuts.pool local new_rule = nodepool.rule local new_kern = nodepool.kern local new_glue = nodepool.glue local new_hlist = nodepool.hlist local new_vlist = nodepool.vlist local tracers = nodes.tracers local visualizers = nodes.visualizers local setcolor = tracers.colors.set local setlistcolor = tracers.colors.setlist local settransparency = tracers.transparencies.set local setlisttransparency = tracers.transparencies.setlist local starttiming = statistics.starttiming local stoptiming = statistics.stoptiming local a_visual = attributes.private("visual") local a_layer = attributes.private("viewerlayer") local band = bit32.band local bor = bit32.bor local enableaction = nodes.tasks.enableaction -- local trace_hbox -- local trace_vbox -- local trace_vtop -- local trace_kern -- local trace_glue -- local trace_penalty -- local trace_fontkern -- local trace_strut -- local trace_whatsit -- local trace_user -- local trace_math -- local trace_italic -- local trace_discretionary -- local trace_expansion -- local trace_line -- local trace_space local report_visualize = logs.reporter("visualize") local modes = { hbox = 0x000001, vbox = 0x000002, vtop = 0x000004, kern = 0x000008, glue = 0x000010, penalty = 0x000020, fontkern = 0x000040, strut = 0x000080, whatsit = 0x000100, glyph = 0x000200, simple = 0x000400, simplehbox = 0x000401, simplevbox = 0x000402, simplevtop = 0x000404, user = 0x000800, math = 0x001000, italic = 0x002000, origin = 0x004000, discretionary = 0x008000, expansion = 0x010000, line = 0x020000, space = 0x040000, depth = 0x080000, marginkern = 0x100000, mathlistkern = 0x200000, dir = 0x400000, par = 0x800000, } local usedfont, exheight, emwidth local l_penalty, l_glue, l_kern, l_fontkern, l_hbox, l_vbox, l_vtop, l_strut, l_whatsit, l_glyph, l_user, l_math, l_marginkern, l_mathlistkern, l_italic, l_origin, l_discretionary, l_expansion, l_line, l_space, l_depth, l_dir, l_whatsit local enabled = false local layers = { } local preset_boxes = modes.hbox + modes.vbox + modes.origin local preset_makeup = preset_boxes + modes.kern + modes.glue + modes.penalty local preset_all = preset_makeup + modes.fontkern + modes.marginkern + modes.mathlistkern + modes.whatsit + modes.glyph + modes.user + modes.math + modes.dir + modes.whatsit function visualizers.setfont(id) usedfont = id or current_font() exheight = exheights[usedfont] emwidth = emwidths[usedfont] end -- we can preset a bunch of bits local userrule -- bah, not yet defined: todo, delayed(nuts.rules,"userrule") local outlinerule -- bah, not yet defined: todo, delayed(nuts.rules,"userrule") local function initialize() -- if not usedfont then -- we use a narrow monospaced font -- infofont ? visualizers.setfont(fonts.definers.define { name = "lmmonoltcond10regular", size = tex.sp("4pt") }) end -- for mode, value in next, modes do local tag = formatters["v_%s"](mode) attributes.viewerlayers.define { tag = tag, title = formatters["visualizer %s"](mode), visible = "start", editable = "yes", printable = "yes" } layers[mode] = attributes.viewerlayers.register(tag,true) end l_hbox = layers.hbox l_vbox = layers.vbox l_vtop = layers.vtop l_glue = layers.glue l_kern = layers.kern l_penalty = layers.penalty l_fontkern = layers.fontkern l_strut = layers.strut l_whatsit = layers.whatsit l_glyph = layers.glyph l_user = layers.user l_math = layers.math l_italic = layers.italic l_marginkern = layers.marginkern l_mathlistkern = layers.mathlistkern l_origin = layers.origin l_discretionary = layers.discretionary l_expansion = layers.expansion l_line = layers.line l_space = layers.space l_depth = layers.depth l_dir = layers.dir l_par = layers.par -- if not userrule then userrule = nuts.rules.userrule end -- if not outlinerule then outlinerule = nuts.pool.outlinerule end initialize = false end local function enable() if initialize then initialize() end enableaction("shipouts","nodes.visualizers.handler") report_visualize("enabled") enabled = true tex.setcount("global","c_syst_visualizers_state",1) -- so that we can optimize at the tex end end local function setvisual(n,a,what,list) -- this will become more efficient when we have the bit lib linked in if not n or n == "reset" then return unsetvalue elseif n == true or n == "makeup" then if not a or a == 0 or a == unsetvalue then a = preset_makeup else a = bor(a,preset_makeup) end elseif n == "boxes" then if not a or a == 0 or a == unsetvalue then a = preset_boxes else a = bor(a,preset_boxes) end elseif n == "all" then if what == false then return unsetvalue elseif not a or a == 0 or a == unsetvalue then a = preset_all else a = bor(a,preset_all) end else for s in gmatch(n,"[a-z]+") do local m = modes[s] if not m then -- go on elseif not a or a == 0 or a == unsetvalue then a = m else a = bor(a,m) end end end if not a or a == 0 or a == unsetvalue then return unsetvalue elseif not enabled then -- must happen at runtime (as we don't store layers yet) enable() end return a end function nuts.setvisual(n,mode) setattr(n,a_visual,setvisual(mode,getattr(n,a_visual),true)) end function nuts.setvisuals(n,mode) -- currently the same setattr(n,a_visual,setvisual(mode,getattr(n,a_visual),true,true)) end -- fast setters do local cached = setmetatableindex(function(t,k) if k == true then return texgetattribute(a_visual) elseif not k then t[k] = unsetvalue return unsetvalue else local v = setvisual(k) t[k] = v return v end end) -- local function applyvisuals(n,mode) -- local a = cached[mode] -- apply_to_nodes(n,function(n) setattr(n,a_visual,a) end) -- end local a = unsetvalue local f = function(n) setattr(n,a_visual,a) end local function applyvisuals(n,mode) a = cached[mode] apply_to_nodes(n,f) end nuts.applyvisuals = applyvisuals function nodes.applyvisuals(n,mode) applyvisuals(tonut(n),mode) end function visualizers.attribute(mode) return cached[mode] end visualizers.attributes = cached end function nuts.copyvisual(n,m) setattr(n,a_visual,getattr(m,a_visual)) end function visualizers.setvisual(n) texsetattribute(a_visual,setvisual(n,texgetattribute(a_visual))) end function visualizers.setlayer(n) texsetattribute(a_layer,layers[n] or unsetvalue) end local function set(mode,v) texsetattribute(a_visual,setvisual(mode,texgetattribute(a_visual),v)) end for mode, value in next, modes do trackers.register(formatters["visualizers.%s"](mode), function(v) set(mode,v) end) end local fraction = 10 trackers .register("visualizers.reset", function(v) set("reset", v) end) trackers .register("visualizers.all", function(v) set("all", v) end) trackers .register("visualizers.makeup", function(v) set("makeup",v) end) trackers .register("visualizers.boxes", function(v) set("boxes", v) end) directives.register("visualizers.fraction", function(v) fraction = (v and tonumber(v)) or (v == "more" and 5) or 10 end) local c_positive = "trace:b" local c_negative = "trace:r" local c_zero = "trace:g" local c_text = "trace:s" local c_space = "trace:y" local c_space_x = "trace:m" local c_skip_a = "trace:c" local c_skip_b = "trace:m" local c_glyph = "trace:o" local c_ligature = "trace:s" local c_white = "trace:w" ----- c_math = "trace:s" ----- c_origin = "trace:o" ----- c_discretionary = "trace:d" ----- c_expansion = "trace:o" local c_depth = "trace:o" local c_indent = "trace:s" local c_positive_d = "trace:db" local c_negative_d = "trace:dr" local c_zero_d = "trace:dg" local c_text_d = "trace:ds" local c_space_d = "trace:dy" local c_space_x_d = "trace:dm" local c_skip_a_d = "trace:dc" local c_skip_b_d = "trace:dm" local c_glyph_d = "trace:do" local c_ligature_d = "trace:ds" local c_white_d = "trace:dw" local c_math_d = "trace:dr" local c_origin_d = "trace:do" local c_discretionary_d = "trace:dd" ----- c_expansion_d = "trace:do" ----- c_depth_d = "trace:do" ----- c_indent_d = "trace:ds" local function sometext(str,layer,color,textcolor,lap) -- we can just paste verbatim together .. no typesteting needed local text = hpack_string(str,usedfont) local size = getwidth(text) local rule = new_rule(size,2*exheight,exheight/2) local kern = new_kern(-size) if color then setcolor(rule,color) end if textcolor then setlistcolor(getlist(text),textcolor) end local info = setlink(rule,kern,text) setlisttransparency(info,c_zero) info = hpack_nodes(info) local width = getwidth(info) if lap then info = new_hlist(setlink(new_kern(-width),info)) else info = new_hlist(info) -- a bit overkill: double wrapped end if layer then setattr(info,a_layer,layer) end return info, width end local function someblob(str,layer,color,textcolor,width) local text = hpack_string(str,usedfont) local size = getwidth(text) local rule = new_rule(width,2*exheight,exheight/2) local kern = new_kern(-width + (width-size)/2) if color then setcolor(rule,color) end if textcolor then setlistcolor(getlist(text),textcolor) end local info = setlink(rule,kern,text) setlisttransparency(info,c_zero) info = hpack_nodes(info) local width = getwidth(info) info = new_hlist(info) if layer then setattr(info,a_layer,layer) end return info, width end local caches = setmetatableindex("table") local fontkern, italickern, marginkern, mathlistkern do local f_cache = caches["fontkern"] local i_cache = caches["italickern"] local m_cache = caches["marginkern"] local l_cache = caches["mathlistkern"] local function somekern(head,current,cache,color,layer) local width = getkern(current) local extra = getexpansion(current) local kern = width + extra local info = cache[kern] if not info then local text = hpack_string(formatters[" %0.3f"](kern*pt_factor),usedfont) local rule = new_rule(emwidth/fraction,6*exheight,2*exheight) local list = getlist(text) if kern > 0 then setlistcolor(list,c_positive_d) elseif kern < 0 then setlistcolor(list,c_negative_d) else setlistcolor(list,c_zero_d) end setlisttransparency(list,color) setcolor(rule,color) settransparency(rule,color) setshift(text,-5 * exheight) info = new_hlist(setlink(rule,text)) setattr(info,a_layer,layer) f_cache[kern] = info end head = insertnodebefore(head,current,copylist(info)) return head, current end fontkern = function(head,current) return somekern(head,current,f_cache,c_text_d,l_fontkern) end italickern = function(head,current) return somekern(head,current,i_cache,c_glyph_d,l_italic) end marginkern = function(head,current) return somekern(head,current,m_cache,c_glyph_d,l_marginkern) end mathlistkern = function(head,current) return somekern(head,current,l_cache,c_glyph_d,l_mathlistkern) end end local glyphexpansion do local f_cache = caches["glyphexpansion"] glyphexpansion = function(head,current) local extra = getexpansion(current) if extra and extra ~= 0 then extra = extra / 1000 local info = f_cache[extra] if not info then local text = hpack_string(round(extra),usedfont) local rule = new_rule(emwidth/fraction,exheight,2*exheight) local list = getlist(text) if extra > 0 then setlistcolor(list,c_positive_d) elseif extra < 0 then setlistcolor(list,c_negative_d) end setlisttransparency(list,c_text_d) setcolor(rule,c_text_d) settransparency(rule,c_text_d) setshift(text,1.5 * exheight) info = new_hlist(setlink(rule,text)) setattr(info,a_layer,l_expansion) f_cache[extra] = info end head = insertnodebefore(head,current,copylist(info)) return head, current end return head, current end end local kernexpansion do local f_cache = caches["kernexpansion"] -- in mkiv we actually need to reconstruct but let's not do that now kernexpansion = function(head,current) local extra = getexpansion(current) if extra ~= 0 then extra = extra / 1000 local info = f_cache[extra] if not info then local text = hpack_string(round(extra),usedfont) local rule = new_rule(emwidth/fraction,exheight,4*exheight) local list = getlist(text) if extra > 0 then setlistcolor(list,c_positive_d) elseif extra < 0 then setlistcolor(list,c_negative_d) end setlisttransparency(list,c_text_d) setcolor(rule,c_text_d) settransparency(rule,c_text_d) setshift(text,3.5 * exheight) info = new_hlist(setlink(rule,text)) setattr(info,a_layer,l_expansion) f_cache[extra] = info end head = insertnodebefore(head,current,copylist(info)) return head, current end return head, current end end local whatsit do local whatsitcodes = nodes.whatsitcodes local w_cache = caches["whatsit"] local tags = { open = "OPN", write = "WRI", close = "CLS", special = "SPE", latelua = "LUA", savepos = "POS", userdefined = "USR", literal = "LIT", setmatrix = "MAT", save = "SAV", restore = "RES", } whatsit = function(head,current) local what = getsubtype(current) local info = w_cache[what] if info then -- print("hit whatsit") else info = sometext(formatters["W:%s"](what),usedfont,nil,c_white) setattr(info,a_layer,l_whatsit) w_cache[what] = info end head, current = insertnodeafter(head,current,copylist(info)) return head, current end end local dir, par do local dircodes = nodes.dircodes local dirvalues = nodes.dirvalues local cancel_code = dircodes.cancel local l2r_code = dirvalues.l2r local r2l_code = dirvalues.r2l local d_cache = caches["dir"] local tags = { l2r = "L2R", r2l = "R2L", cancel = "CAN", par = "PAR", } par = function(head,current) local what = "par" -- getsubtype(current) local info = d_cache[what] if info then -- print("hit par") else info = sometext(formatters["L:%s"](what),usedfont,nil,c_white) setattr(info,a_layer,l_dir) d_cache[what] = info end return head, current end dir = function(head,current) local what = getsubtype(current) if what == cancelcode then what = "cancel" elseif getdirection(current) == r2l_code then what = "r2l" else what = "l2r" end local info = d_cache[what] if info then -- print("hit dir") else info = sometext(formatters["D:%s"](what),usedfont,nil,c_white) setattr(info,a_layer,l_dir) d_cache[what] = info end return head, current end end local user do local u_cache = caches["user"] user = function(head,current) local what = getsubtype(current) local info = u_cache[what] if info then -- print("hit user") else info = sometext(formatters["U:%s"](what),usedfont) setattr(info,a_layer,l_user) u_cache[what] = info end head, current = insertnodeafter(head,current,copylist(info)) return head, current end end local math do local mathcodes = nodes.mathcodes local m_cache = { beginmath = caches["bmath"], endmath = caches["emath"], } local tags = { beginmath = "B", endmath = "E", } math = function(head,current) local what = getsubtype(current) local tag = mathcodes[what] local skip = getkern(current) + getwidth(current) -- surround local info = m_cache[tag][skip] if info then -- print("hit math") else local text, width = sometext(formatters["M:%s"](tag and tags[tag] or what),usedfont,nil,c_math_d) local rule = new_rule(skip,-655360/fraction,2*655360/fraction) setcolor(rule,c_math_d) settransparency(rule,c_math_d) setattr(rule,a_layer,l_math) if tag == "beginmath" then info = new_hlist(setlink(new_glue(-skip),rule,new_glue(-width),text)) else info = new_hlist(setlink(new_glue(-skip),rule,new_glue(-skip),text)) end setattr(info,a_layer,l_math) m_cache[tag][skip] = info end head, current = insertnodeafter(head,current,copylist(info)) return head, current end end local ruleddepth do ruleddepth = function(current,wd,ht,dp) local wd, ht, dp = getwhd(current) if dp ~= 0 then local rule = new_rule(wd,0,dp) setcolor(rule,c_depth) settransparency(rule,c_zero) setattr(rule,a_layer,l_depth) setlist(current,setlink(rule,new_kern(-wd),getlist(current))) end end end local ruledbox do local b_cache = caches["box"] local o_cache = caches["origin"] setmetatableindex(o_cache,function(t,size) local rule = new_rule(2*size,size,size) local origin = hpack_nodes(rule) setcolor(rule,c_origin_d) settransparency(rule,c_origin_d) setattr(rule,a_layer,l_origin) t[size] = origin return origin end) ruledbox = function(head,current,vertical,layer,what,simple,previous,trace_origin,parent) local wd, ht, dp = getwhd(current) if wd ~= 0 then local shift = getshift(current) local next = getnext(current) local prev = previous setboth(current) local linewidth = emwidth/fraction local size = 2*linewidth local this if not simple then this = b_cache[what] if not this then local text = hpack_string(what,usedfont) this = setlink(new_kern(-getwidth(text)),text) setlisttransparency(this,c_text) this = new_hlist(this) b_cache[what] = this end end -- we need to trigger the right mode (else sometimes no whatits) local info = setlink( this and copylist(this) or nil, (dp == 0 and outlinerule and outlinerule(wd,ht,dp,linewidth)) or userrule { width = wd, height = ht, depth = dp, line = linewidth, type = "box", dashed = 3*size, } ) -- setlisttransparency(info,c_text) info = new_hlist(info) -- important -- setattr(info,a_layer,layer) if vertical then if shift == 0 then info = setlink(current,dp ~= 0 and new_kern(-dp) or nil,info) elseif trace_origin then local size = 2*size local origin = o_cache[size] origin = copylist(origin) if getid(parent) == vlist_code then setshift(origin,-shift) info = setlink(current,new_kern(-size),origin,new_kern(-size-dp),info) else -- todo .. i need an example info = setlink(current,dp ~= 0 and new_kern(-dp) or nil,info) end setshift(current,0) else info = setlink(current,new_dp ~= 0 and new_kern(-dp) or nil,info) setshift(current,0) end info = new_vlist(info,wd,ht,dp,shift) else if shift == 0 then info = setlink(current,new_kern(-wd),info) elseif trace_origin then local size = 2*size local origin = o_cache[size] origin = copylist(origin) if getid(parent) == vlist_code then info = setlink(current,new_kern(-wd-size-shift),origin,new_kern(-size+shift),info) else setshift(origin,-shift) info = setlink(current,new_kern(-wd-size),origin,new_kern(-size),info) end setshift(current,0) else info = setlink(current,new_kern(-wd),info) setshift(current,0) end info = new_hlist(info,wd,ht,dp,shift) end if next then setlink(info,next) end if prev and prev > 0 then setlink(prev,info) end if head == current then return info, info else return head, info end else return head, current end end end local ruledglyph do -- see boundingbox feature .. maybe a pdf stream is more efficient, after all we -- have a frozen color anyway or i need a more detailed cache .. below is a more -- texie approach ruledglyph = function(head,current,previous) -- wrong for vertical glyphs local wd = getwidth(current) if wd ~= 0 then local wd, ht, dp = getwhd(current) local next = getnext(current) local prev = previous setboth(current) local linewidth = emwidth/(2*fraction) local info -- info = setlink( (dp == 0 and outlinerule and outlinerule(wd,ht,dp,linewidth)) or userrule { width = wd, height = ht, depth = dp, line = linewidth, type = "box", }, new_kern(-wd) ) -- local c, f = isglyph(current) local char = chardata[f][c] if char and type(char.unicode) == "table" then -- hackery test setlistcolor(info,c_ligature) setlisttransparency(info,c_ligature_d) else setlistcolor(info,c_glyph) setlisttransparency(info,c_glyph_d) end info = new_hlist(info) setattr(info,a_layer,l_glyph) local info = setlink(current,new_kern(-wd),info) info = hpack_nodes(info) setwidth(info,wd) if next then setlink(info,next) end if prev then setlink(prev,info) end if head == current then return info, info else return head, info end else return head, current end end function visualizers.setruledglyph(f) ruledglyph = f or ruledglyph end end local ruledglue do local gluecodes = nodes.gluecodes local userskip_code = gluecodes.userskip local spaceskip_code = gluecodes.spaceskip local xspaceskip_code = gluecodes.xspaceskip local zerospaceskip_code = gluecodes.zerospaceskip or gluecodes.userskip -- local keepskip_code = gluecodes.keepskip or gluecodes.userskip local leftskip_code = gluecodes.leftskip local rightskip_code = gluecodes.rightskip local parfillleftskip_code = gluecodes.parfillleftskip or parfillskip_code local parfillrightskip_code = gluecodes.parfillrightskip or parfillskip_code local indentskip_code = gluecodes.indentskip local correctionskip_code = gluecodes.correctionskip local g_cache_v = caches["vglue"] local g_cache_h = caches["hglue"] local tags = { -- [userskip_code] = "US", [gluecodes.lineskip] = "LI", [gluecodes.baselineskip] = "BS", [gluecodes.parskip] = "PS", [gluecodes.abovedisplayskip] = "DA", [gluecodes.belowdisplayskip] = "DB", [gluecodes.abovedisplayshortskip] = "SA", [gluecodes.belowdisplayshortskip] = "SB", [gluecodes.topskip] = "TS", [gluecodes.splittopskip] = "ST", [gluecodes.tabskip] = "AS", [gluecodes.lefthangskip] = "LH", [gluecodes.righthangskip] = "RH", [gluecodes.thinmuskip] = "MS", [gluecodes.medmuskip] = "MM", [gluecodes.thickmuskip] = "ML", [gluecodes.intermathskip] = "IM", [gluecodes.keepskip or 99] = "KS", [gluecodes.mathskip] = "MT", [gluecodes.leaders] = "NL", [gluecodes.cleaders] = "CL", [gluecodes.xleaders] = "XL", [gluecodes.gleaders] = "GL", -- true = "VS", -- false = "HS", [leftskip_code] = "LS", [rightskip_code] = "RS", [spaceskip_code] = "SP", [xspaceskip_code] = "XS", [zerospaceskip_code] = "ZS", [parfillleftskip_code] = "PL", [parfillrightskip_code] = "PR", [indentskip_code] = "IN", [correctionskip_code] = "CS", } -- we sometimes pass previous as we can have issues in math (not watertight for all) ruledglue = function(head,current,vertical,parent) local subtype = getsubtype(current) local width = effectiveglue(current,parent) local amount = formatters["%s:%0.3f"](tags[subtype] or (vertical and "VS") or "HS",width*pt_factor) local info = (vertical and g_cache_v or g_cache_h)[amount] if info then -- print("glue hit") else if subtype == spaceskip_code or subtype == xspaceskip_code or subtype == zerospaceskip_code then info = sometext(amount,l_glue,c_space) elseif subtype == leftskip_code or subtype == rightskip_code then info = sometext(amount,l_glue,c_skip_a) elseif subtype == parfillleftskip_code or subtype == parfillrightskip_code or subtype == indentskip_code or subtype == correctionskip_code then info = sometext(amount,l_glue,c_indent) elseif subtype == userskip_code then if width > 0 then info = sometext(amount,l_glue,c_positive) elseif width < 0 then info = sometext(amount,l_glue,c_negative) else info = sometext(amount,l_glue,c_zero) end else info = sometext(amount,l_glue,c_skip_b) end (vertical and g_cache_v or g_cache_h)[amount] = info end info = copylist(info) if vertical then info = vpack_nodes(info) end head, current = insertnodebefore(head,current,info) return head, getnext(current) end -- ruledspace = function(head,current,parent) -- local subtype = getsubtype(current) -- if subtype == spaceskip_code or subtype == xspaceskip_code or subtype == zerospaceskip_code then -- local width = effectiveglue(current,parent) -- local amount = formatters["%s:%0.3f"](tags[subtype] or "HS",width*pt_factor) -- local info = g_cache_h[amount] -- if info then -- -- print("space hit") -- else -- info = sometext(amount,l_glue,c_space) -- g_cache_h[amount] = info -- end -- info = copylist(info) -- head, current = insertnodebefore(head,current,info) -- return head, getnext(current) -- else -- return head, current -- end -- end local g_cache_s = caches["space"] local g_cache_x = caches["xspace"] ruledspace = function(head,current,parent) local subtype = getsubtype(current) if subtype == spaceskip_code or subtype == xspaceskip_code or subtype == zerospaceskip_code then -- not yet all space local width = effectiveglue(current,parent) local info if subtype == spaceskip_code then info = g_cache_s[width] if not info then info = someblob("SP",l_glue,c_space,nil,width) g_cache_s[width] = info end else info = g_cache_x[width] if not info then info = someblob("XS",l_glue,c_space_x,nil,width) g_cache_x[width] = info end end info = copylist(info) head, current = insertnodebefore(head,current,info) return head, getnext(current) else return head, current end end end local ruledkern do local k_cache_v = caches["vkern"] local k_cache_h = caches["hkern"] ruledkern = function(head,current,vertical,mk) local kern = getkern(current) local cache = vertical and k_cache_v or k_cache_h local info = cache[kern] if not info then local amount = formatters["%s:%0.3f"](vertical and "VK" or (mk and "MK") or "HK",kern*pt_factor) if kern > 0 then info = sometext(amount,l_kern,c_positive) elseif kern < 0 then info = sometext(amount,l_kern,c_negative) else info = sometext(amount,l_kern,c_zero) end cache[kern] = info end info = copylist(info) if vertical then info = vpack_nodes(info) end head, current = insertnodebefore(head,current,info) return head, getnext(current) end end local ruleditalic do local i_cache = caches["italic"] ruleditalic = function(head,current) local kern = getkern(current) local info = i_cache[kern] if not info then local amount = formatters["%s:%0.3f"]("IC",kern*pt_factor) if kern > 0 then info = sometext(amount,l_kern,c_positive) elseif kern < 0 then info = sometext(amount,l_kern,c_negative) else info = sometext(amount,l_kern,c_zero) end i_cache[kern] = info end info = copylist(info) head, current = insertnodebefore(head,current,info) return head, getnext(current) end end local ruledmarginkern do local m_cache = caches["marginkern"] ruledmarginkern = function(head,current) local kern = getkern(current) local info = m_cache[kern] if not info then local amount = formatters["%s:%0.3f"]("MK",kern*pt_factor) if kern > 0 then info = sometext(amount,l_marginkern,c_positive) elseif kern < 0 then info = sometext(amount,l_marginkern,c_negative) else info = sometext(amount,l_marginkern,c_zero) end m_cache[kern] = info end info = copylist(info) head, current = insertnodebefore(head,current,info) return head, getnext(current) end end local ruledmathlistkern do local l_cache = caches["mathlistkern"] ruledmathlistkern = function(head,current) local kern = getkern(current) local info = l_cache[kern] if not info then local amount = formatters["%s:%0.3f"]("LK",kern*pt_factor) if kern > 0 then info = sometext(amount,l_mathlistkern,c_positive) elseif kern < 0 then info = sometext(amount,l_mathlistkern,c_negative) else info = sometext(amount,l_mathlistkern,c_zero) end l_cache[kern] = info end info = copylist(info) head, current = insertnodebefore(head,current,info) return head, getnext(current) end end local ruleddiscretionary do local d_cache = caches["discretionary"] ruleddiscretionary = function(head,current) local d = d_cache[true] if not the_discretionary then local rule = new_rule(4*emwidth/fraction,4*exheight,exheight) local kern = new_kern(-2*emwidth/fraction) setlink(kern,rule) setcolor(rule,c_discretionary_d) settransparency(rule,c_discretionary_d) setattr(rule,a_layer,l_discretionary) d = new_hlist(kern) d_cache[true] = d end insertnodeafter(head,current,copylist(d)) return head, current end end local ruledpenalty do local p_cache_v = caches["vpenalty"] local p_cache_h = caches["hpenalty"] local raisepenalties = false directives.register("visualizers.raisepenalties",function(v) raisepenalties = v end) ruledpenalty = function(head,current,vertical) local penalty = getpenalty(current) local info = (vertical and p_cache_v or p_cache_h)[penalty] if info then -- print("penalty hit") else local amount = formatters["%s:%s"](vertical and "VP" or "HP",penalty) if penalty > 0 then info = sometext(amount,l_penalty,c_positive) elseif penalty < 0 then info = sometext(amount,l_penalty,c_negative) else info = sometext(amount,l_penalty,c_zero) end (vertical and p_cache_v or p_cache_h)[penalty] = info end info = copylist(info) if vertical then info = vpack_nodes(info) elseif raisepenalties then setshift(info,-65536*4) end head, current = insertnodebefore(head,current,info) return head, getnext(current) end end do local disc_code = nodecodes.disc local kern_code = nodecodes.kern local glyph_code = nodecodes.glyph local glue_code = nodecodes.glue local penalty_code = nodecodes.penalty local whatsit_code = nodecodes.whatsit local user_code = nodecodes.user local math_code = nodecodes.math local hlist_code = nodecodes.hlist local vlist_code = nodecodes.vlist local marginkern_code = nodecodes.marginkern local mathlistkern_code = nodecodes.mathlistkern local dir_code = nodecodes.dir local par_code = nodecodes.par local kerncodes = nodes.kerncodes local fontkern_code = kerncodes.fontkern local italickern_code = kerncodes.italiccorrection local leftmarginkern_code = kerncodes.leftmarginkern local rightmarginkern_code = kerncodes.rightmarginkern local mathlistkern_code = kerncodes.mathlistkern ----- userkern_code = kerncodes.userkern local listcodes = nodes.listcodes local linelist_code = listcodes.line local cache local function visualize(head,vertical,forced,parent) local trace_hbox = false local trace_vbox = false local trace_vtop = false local trace_kern = false local trace_glue = false local trace_penalty = false local trace_fontkern = false local trace_strut = false local trace_whatsit = false local trace_glyph = false local trace_simple = false local trace_user = false local trace_math = false local trace_italic = false local trace_origin = false local trace_discretionary = false local trace_expansion = false local trace_line = false local trace_space = false local trace_depth = false local trace_dir = false local trace_par = false local current = head local previous = nil local attr = unsetvalue local prev_trace_fontkern = nil local prev_trace_italic = nil local prev_trace_marginkern = nil -- local prev_trace_mathlist = nil local prev_trace_expansion = nil while current do local id = getid(current) local a = forced or getattr(current,a_visual) or unsetvalue local subtype if a ~= attr then prev_trace_fontkern = trace_fontkern prev_trace_italic = trace_italic prev_trace_marginkern = trace_marginkern -- prev_trace_mathlistkern = trace_mathlistkern prev_trace_expansion = trace_expansion attr = a if a == unsetvalue then trace_hbox = false trace_vbox = false trace_vtop = false trace_kern = false trace_glue = false trace_penalty = false trace_fontkern = false trace_strut = false trace_whatsit = false trace_glyph = false trace_simple = false trace_user = false trace_math = false trace_italic = false trace_origin = false trace_discretionary = false trace_expansion = false trace_line = false trace_space = false trace_depth = false trace_marginkern = false trace_mathlistkern = false trace_dir = false trace_par = false if id == kern_code then goto kern else goto list end else -- dead slow: -- cache[a]() trace_hbox = band(a,0x000001) ~= 0 trace_vbox = band(a,0x000002) ~= 0 trace_vtop = band(a,0x000004) ~= 0 trace_kern = band(a,0x000008) ~= 0 trace_glue = band(a,0x000010) ~= 0 trace_penalty = band(a,0x000020) ~= 0 trace_fontkern = band(a,0x000040) ~= 0 trace_strut = band(a,0x000080) ~= 0 trace_whatsit = band(a,0x000100) ~= 0 trace_glyph = band(a,0x000200) ~= 0 trace_simple = band(a,0x000400) ~= 0 trace_user = band(a,0x000800) ~= 0 trace_math = band(a,0x001000) ~= 0 trace_italic = band(a,0x002000) ~= 0 trace_origin = band(a,0x004000) ~= 0 trace_discretionary = band(a,0x008000) ~= 0 trace_expansion = band(a,0x010000) ~= 0 trace_line = band(a,0x020000) ~= 0 trace_space = band(a,0x040000) ~= 0 trace_depth = band(a,0x080000) ~= 0 trace_marginkern = band(a,0x100000) ~= 0 trace_mathlistkern = band(a,0x200000) ~= 0 trace_dir = band(a,0x400000) ~= 0 trace_whatsit = band(a,0x800000) ~= 0 end elseif a == unsetvalue then goto list end if trace_strut then setattr(current,a_layer,l_strut) elseif id == glyph_code then if trace_glyph then head, current = ruledglyph(head,current,previous) end if trace_expansion then head, current = glyphexpansion(head,current) end elseif id == disc_code then if trace_discretionary then head, current = ruleddiscretionary(head,current) end local pre, post, replace = getdisc(current) if pre then pre = visualize(pre,false,a,parent) end if post then post = visualize(post,false,a,parent) end if replace then replace = visualize(replace,false,a,parent) end setdisc(current,pre,post,replace) elseif id == kern_code then goto kern elseif id == glue_code then local content = getleader(current) if content then setleader(current,visualize(content,false,nil,parent)) elseif trace_glue then head, current = ruledglue(head,current,vertical,parent) elseif trace_space then head, current = ruledspace(head,current,parent) end elseif id == penalty_code then if trace_penalty then head, current = ruledpenalty(head,current,vertical) end elseif id == hlist_code or id == vlist_code then goto list elseif id == whatsit_code then if trace_whatsit then head, current = whatsit(head,current) end elseif id == user_code then if trace_user then head, current = user(head,current) end elseif id == math_code then if trace_math then head, current = math(head,current) end elseif id == marginkern_code then if trace_kern then head, current = ruledkern(head,current,vertical,true) end elseif id == dir_code then if trace_dir then head, current = dir(head,current) end elseif id == par_code then if trace_par then head, current = par(head,current) end end goto next ::kern:: subtype = getsubtype(current) if subtype == fontkern_code then if trace_fontkern or prev_trace_fontkern then head, current = fontkern(head,current) end if trace_expansion or prev_trace_expansion then head, current = kernexpansion(head,current) end elseif subtype == italickern_code then if trace_italic or prev_trace_italic then head, current = italickern(head,current) elseif trace_kern then head, current = ruleditalic(head,current) end elseif subtype == leftmarginkern_code or subtype == rightmarginkern_code then if trace_marginkern or prev_trace_marginkern then head, current = marginkern(head,current) elseif trace_kern then head, current = ruledmarginkern(head,current) end elseif subtype == mathlistkern_code then if trace_mathlist then -- or prev_trace_mathlist then head, current = mathlistkern(head,current) elseif trace_kern then head, current = ruledmathlistkern(head,current) end else if trace_kern then head, current = ruledkern(head,current,vertical) end end goto next; ::list:: if id == hlist_code then local content = getlist(current) if content then setlist(current,visualize(content,false,nil,current)) end if trace_depth then ruleddepth(current) end if trace_line and getsubtype(current) == linelist_code then head, current = ruledbox(head,current,false,l_line,"L__",trace_simple,previous,trace_origin,parent) elseif trace_hbox then head, current = ruledbox(head,current,false,l_hbox,"H__",trace_simple,previous,trace_origin,parent) end elseif id == vlist_code then local content = getlist(current) if content then setlist(current,visualize(content,true,nil,current)) end if trace_vtop then head, current = ruledbox(head,current,true,l_vtop,"_T_",trace_simple,previous,trace_origin,parent) elseif trace_vbox then head, current = ruledbox(head,current,true,l_vbox,"__V",trace_simple,previous,trace_origin,parent) end end ::next:: previous = current current = getnext(current) end return head end local function cleanup() for tag, cache in next, caches do for k, v in next, cache do flushnodelist(v) end end cleanup = function() report_visualize("error, duplicate cleanup") end end luatex.registerstopactions(cleanup) function visualizers.handler(head) if usedfont then starttiming(visualizers) head = visualize(head,true) stoptiming(visualizers) return head, true else return head, false end end function visualizers.box(n) if usedfont then starttiming(visualizers) local box = getbox(n) if box then setlist(box,visualize(getlist(box),getid(box) == vlist_code)) end stoptiming(visualizers) return head, true else return head, false end end end do local hlist_code = nodecodes.hlist local vlist_code = nodecodes.vlist local nextnode = nuts.traversers.node local last = nil local used = nil local mark = { "trace:1", "trace:2", "trace:3", "trace:4", "trace:5", "trace:6", "trace:7", } local function markfonts(list) for n, id in nextnode, list do if id == glyph_code then local font = getfont(n) local okay = used[font] if not okay then last = last + 1 okay = mark[last] used[font] = okay end setcolor(n,okay) elseif id == hlist_code or id == vlist_code then markfonts(getlist(n)) end end end function visualizers.markfonts(list) last, used = 0, { } markfonts(type(n) == "number" and getlist(getbox(n)) or n) end end statistics.register("visualization time",function() if enabled then -- cleanup() -- in case we don't don't do it each time return formatters["%s seconds"](statistics.elapsedtime(visualizers)) end end) -- interface do local implement = interfaces.implement implement { name = "setvisual", arguments = "string", actions = visualizers.setvisual } implement { name = "setvisuals", arguments = "string", actions = visualizers.setvisual } implement { name = "getvisual", arguments = "string", actions = { setvisual, context } } implement { name = "setvisuallayer", arguments = "string", actions = visualizers.setlayer } implement { name = "markvisualfonts", arguments = "integer", actions = visualizers.markfonts } implement { name = "setvisualfont", arguments = "integer", actions = visualizers.setfont } end -- Here for now: do local function make(str,forecolor,rulecolor,layer) if initialize then initialize() end local rule = new_rule(emwidth/fraction,exheight,4*exheight) setcolor(rule,rulecolor) settransparency(rule,rulecolor) local info if str == "" then info = new_hlist(rule) else local text = hpack_string(str,usedfont) local list = getlist(text) setlistcolor(list,textcolor) setlisttransparency(list,textcolor) setshift(text,3.5 * exheight) info = new_hlist(setlink(rule,text)) end setattr(info,a_layer,layer) return info end function visualizers.register(name,textcolor,rulecolor) if rawget(layers,name) then -- message return end local cache = caches[name] local layer = layers[name] if not textcolor then textcolor = c_text_d end if not rulecolor then rulecolor = c_origin_d end return function(str) if not str then str = "" end local info = cache[str] if not info then info = make(str,textcolor,rulecolor,layer) cache[str] = info end return copy_node(info) end end end