diff options
Diffstat (limited to 'Master/texmf-dist/tex/context/base/mkiv/trac-vis.lua')
-rw-r--r-- | Master/texmf-dist/tex/context/base/mkiv/trac-vis.lua | 1107 |
1 files changed, 1107 insertions, 0 deletions
diff --git a/Master/texmf-dist/tex/context/base/mkiv/trac-vis.lua b/Master/texmf-dist/tex/context/base/mkiv/trac-vis.lua new file mode 100644 index 00000000000..061cef8ba5c --- /dev/null +++ b/Master/texmf-dist/tex/context/base/mkiv/trac-vis.lua @@ -0,0 +1,1107 @@ +if not modules then modules = { } end modules ['trac-vis'] = { + version = 1.001, + 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 string, number, table = string, number, table +local node, nodes, attributes, fonts, tex = node, nodes, attributes, fonts, tex +local type = type +local format = string.format +local formatters = string.formatters + +-- 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. + +-- We could use pdf literals and re stream codes but it's not worth the +-- trouble because we would end up in color etc mess. Maybe one day I'll +-- make a nodeinjection variant. + +-- 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: dir and localpar nodes + +local nodecodes = nodes.nodecodes +local disc_code = nodecodes.disc +local kern_code = nodecodes.kern +local glyph_code = nodecodes.glyph +local hlist_code = nodecodes.hlist +local vlist_code = nodecodes.vlist +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 gluespec_code = nodecodes.gluespec + +local kerncodes = nodes.kerncodes +local font_kern_code = kerncodes.fontkern +local user_kern_code = kerncodes.userkern + +local gluecodes = nodes.gluecodes +local cleaders_code = gluecodes.cleaders +local userskip_code = gluecodes.userskip +local space_code = gluecodes.space +local xspace_code = gluecodes.xspace +local leftskip_code = gluecodes.leftskip +local rightskip_code = gluecodes.rightskip + +local whatsitcodes = nodes.whatsitcodes +local mathcodes = nodes.mathcodes + +local nuts = nodes.nuts +local tonut = nuts.tonut +local tonode = nuts.tonode + +local setfield = nuts.setfield +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 getfield = nuts.getfield +local getid = nuts.getid +local getfont = nuts.getfont +local getattr = nuts.getattr +local getsubtype = nuts.getsubtype +local getchar = nuts.getchar +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 hpack_nodes = nuts.hpack +local vpack_nodes = nuts.vpack +local copy_node = nuts.copy +local copy_list = nuts.copy_list +local free_node = nuts.free +local free_node_list = nuts.flush_list +local insert_node_before = nuts.insert_before +local insert_node_after = nuts.insert_after +local traverse_nodes = nuts.traverse +local linked_nodes = nuts.linked + +local effectiveglue = nuts.effective_glue + +local hpack_string = nuts.typesetters.tohpack + +local texgetattribute = tex.getattribute +local texsetattribute = tex.setattribute + +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_penalty = nodepool.penalty +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_fontkern = attributes.private("fontkern") +local a_layer = attributes.private("viewerlayer") + +local hasbit = number.hasbit +local bit = number.bit +local setbit = number.setbit +local clearbit = number.clearbit + +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 report_visualize = logs.reporter("visualize") + +local modes = { + hbox = 1, + vbox = 2, + vtop = 4, + kern = 8, + glue = 16, + penalty = 32, + fontkern = 64, + strut = 128, + whatsit = 256, + glyph = 512, + simple = 1024, + simplehbox = 1024 + 1, + simplevbox = 1024 + 2, + simplevtop = 1024 + 4, + user = 2048, + math = 4096, + italic = 8192, + origin = 16384, +} + +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_italic, l_origin + +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.whatsit + modes.glyph + modes.user + modes.math + +function visualizers.setfont(id) + usedfont = id or current_font() + exheight = exheights[usedfont] + emwidth = emwidths[usedfont] +end + +-- we can preset a bunch of bits + +local function enable() + 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_origin = layers.origin + nodes.tasks.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) -- this will become more efficient when we have the bit lib linked in + if not n or n == "reset" then + return unsetvalue + elseif n == "makeup" then + if not a or a == 0 or a == unsetvalue then + a = preset_makeup + else + a = setbit(a,preset_makeup) + end + elseif n == "boxes" then + if not a or a == 0 or a == unsetvalue then + a = preset_boxes + else + a = setbit(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 = setbit(a,preset_all) + end + else + local m = modes[n] + if not m then + -- go on + elseif a == unsetvalue then + if what == false then + return unsetvalue + else + -- a = setbit(0,m) + a = m + end + elseif what == false then + a = clearbit(a,m) + elseif not a or a == 0 then + a = m + else + a = setbit(a,m) + 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.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 raisepenalties = false + +directives.register("visualizers.raisepenalties",function(v) raisepenalties = v 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_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" +local c_math = "trace:r" +local c_origin = "trace:o" + +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_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 function sometext(str,layer,color,textcolor,lap) -- we can just paste verbatim together .. no typesteting needed + local text = hpack_string(str,usedfont) + local size = getfield(text,"width") + 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 = linked_nodes(rule,kern,text) + setlisttransparency(info,c_zero) + info = new_hlist(info) + local width = getfield(info,"width") + if lap then + info = new_hlist(linked_nodes(new_kern(-width),info)) + end + if layer then + setattr(info,a_layer,layer) + end + return info, width +end + +local f_cache = { } + +local function fontkern(head,current) + local kern = getfield(current,"kern") + getfield(current,"expansion_factor") + local info = f_cache[kern] + if info then + -- print("hit fontkern") + else + 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,c_text_d) + settransparency(rule,c_text_d) + setfield(text,"shift",-5 * exheight) + info = new_hlist(linked_nodes(rule,text)) + setattr(info,a_layer,l_fontkern) + f_cache[kern] = info + end + head = insert_node_before(head,current,copy_list(info)) + return head, current +end + +local w_cache = { } +local tags = { + open = "FIC", + write = "FIW", + close = "FIC", + special = "SPE", + latelua = "LUA", + savepos = "POS", + userdefined = "USR", + -- backend stuff + pdfliteral = "PDF", + pdfrefobj = "PDF", + pdfannot = "PDF", + pdfstartlink = "PDF", + pdfendlink = "PDF", + pdfdest = "PDF", + pdfthread = "PDF", + pdfstartthread = "PDF", + pdfendthread = "PDF", + pdfthreaddata = "PDF", + pdflinkdata = "PDF", + pdfcolorstack = "PDF", + pdfsetmatrix = "PDF", + pdfsave = "PDF", + pdfrestore = "PDF", +} + +local function whatsit(head,current) + local what = getsubtype(current) + local info = w_cache[what] + if info then + -- print("hit whatsit") + else + local tag = whatsitcodes[what] + -- maybe different text colors per tag + info = sometext(formatters["W:%s"](tag and tags[tag] or what),usedfont,nil,c_white) + setattr(info,a_layer,l_whatsit) + w_cache[what] = info + end + head, current = insert_node_after(head,current,copy_list(info)) + return head, current +end + +local u_cache = { } + +local function user(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 = insert_node_after(head,current,copy_list(info)) + return head, current +end + +local m_cache = { } +local tags = { + beginmath = "B", + endmath = "E", +} + +local function math(head,current) + local what = getsubtype(current) + local info = m_cache[what] + if info then + -- print("hit math") + else + local tag = mathcodes[what] + info = sometext(formatters["M:%s"](tag and tags[tag] or what),usedfont,nil,c_math_d) + setattr(info,a_layer,l_math) + m_cache[what] = info + end + head, current = insert_node_after(head,current,copy_list(info)) + return head, current +end + +local b_cache = { } + +local o_cache = table.setmetatableindex(function(t,size) + local rule = new_rule(2*size,size,size) + 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) + +local function ruledbox(head,current,vertical,layer,what,simple,previous,trace_origin,parent) + local wd = getfield(current,"width") + if wd ~= 0 then + local ht = getfield(current,"height") + local dp = getfield(current,"depth") + local shift = getfield(current,"shift") + local next = getnext(current) + local prev = previous + -- local prev = getprev(current) -- prev can be wrong in math mode < 0.78.3 + setboth(current) + local linewidth = emwidth/fraction + local size = 2*linewidth + local baseline, baseskip + if dp ~= 0 and ht ~= 0 then + if wd > 20*linewidth then + baseline = b_cache.baseline + if not baseline then + -- due to an optimized leader color/transparency we need to set the glue node in order + -- to trigger this mechanism + local leader = linked_nodes(new_glue(size),new_rule(3*size,linewidth,0),new_glue(size)) + leader = hpack_nodes(leader) + baseline = new_glue(0) + setleader(baseline,leader) + setsubtype(baseline,cleaders_code) + setfield(baseline,"stretch",65536) + setfield(baseline,"stretch_order",2) + setlisttransparency(baseline,c_text) + b_cache.baseline = baseline + end + baseline = copy_list(baseline) + baseline = hpack_nodes(baseline,wd-size) + -- or new_hlist, set head and also: + -- baseline.width = wd + -- baseline.glue_set = wd/65536 + -- baseline.glue_order = 2 + -- baseline.glue_sign = 1 + baseskip = new_kern(-wd+linewidth) + else + baseline = new_rule(wd-size,linewidth,0) + baseskip = new_kern(-wd+size) + end + end + local this + if not simple then + this = b_cache[what] + if not this then + local text = hpack_string(what,usedfont) + this = linked_nodes(new_kern(-getfield(text,"width")),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 = linked_nodes( + this and copy_list(this) or nil, + new_rule(linewidth,ht,dp), + new_rule(wd-size,-dp+linewidth,dp), + new_rule(linewidth,ht,dp), + new_kern(-wd+linewidth), + new_rule(wd-size,ht,-ht+linewidth) + ) + if baseskip then + info = linked_nodes(info,baseskip,baseline) -- could be in previous linked + end + setlisttransparency(info,c_text) + info = new_hlist(info) + -- + setattr(info,a_layer,layer) + if vertical then + if shift == 0 then + info = linked_nodes(current,info) + elseif trace_origin then + local size = 2*size + local origin = o_cache[size] + origin = copy_list(origin) + if getid(parent) == vlist_code then + setfield(origin,"shift",-shift) + info = linked_nodes(current,new_kern(-size),origin,new_kern(-size),info) + else + -- todo .. i need an example + info = linked_nodes(current,info) + end + setfield(current,"shift",0) + else + info = linked_nodes(current,info) + setfield(current,"shift",0) + end + info = new_vlist(info,wd,ht,dp,shift) + else + if shift == 0 then + info = linked_nodes(current,new_kern(-wd),info) + elseif trace_origin then + local size = 2*size + local origin = o_cache[size] + origin = copy_list(origin) + if getid(parent) == vlist_code then + info = linked_nodes(current,new_kern(-wd-size-shift),origin,new_kern(-size+shift),info) + else + setfield(origin,"shift",-shift) + info = linked_nodes(current,new_kern(-wd-size),origin,new_kern(-size),info) + end + setfield(current,"shift",0) + else + info = linked_nodes(current,new_kern(-wd),info) + setfield(current,"shift",0) + end + info = new_hlist(info,wd,ht,dp,shift) + end + +-- how about dir, so maybe just copy the node +-- +-- local l = getlist(current) +-- setlist(current,nil) +-- local c = copy_node(current) +-- setlist(current,l) +-- setlist(c,info) +-- info = c + + if next then + setlink(info,next) + end + if prev then + if getid(prev) == gluespec_code then + report_visualize("ignoring invalid prev") + -- weird, how can this happen, an inline glue-spec, probably math + else + setlink(prev,info) + end + end + if head == current then + return info, info + else + return head, info + end + else + return head, current + end +end + +local bpfactor = number.dimenfactors.bp + +-- callback.register("process_rule",function(n,h,v) +-- local p = string.formatters["0 0 %0.6F %0.6F re f"](h*bpfactor,v*bpfactor) +-- pdf.print("direct",p) +-- end) + +local function ruledglyph(head,current,previous) + local wd = getfield(current,"width") + -- local wd = chardata[getfont(current)][getchar(current)].width + if wd ~= 0 then + local ht = getfield(current,"height") + local dp = getfield(current,"depth") + local next = getnext(current) + local prev = previous + setboth(current) + local linewidth = emwidth/(2*fraction) + local baseline + -- if dp ~= 0 and ht ~= 0 then + if (dp >= 0 and ht >= 0) or (dp <= 0 and ht <= 0) then + baseline = new_rule(wd-2*linewidth,linewidth,0) + end + local doublelinewidth = 2*linewidth + -- could be a pdf rule (or a user rule now) + local info = linked_nodes( + new_rule(linewidth,ht,dp), + new_rule(wd-doublelinewidth,-dp+linewidth,dp), + new_rule(linewidth,ht,dp), + new_kern(-wd+linewidth), + new_rule(wd-doublelinewidth,ht,-ht+linewidth), + new_kern(-wd+doublelinewidth), + baseline + ) + local char = chardata[getfont(current)][getchar(current)] + 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 = linked_nodes(current,new_kern(-wd),info) + info = hpack_nodes(info) + setfield(info,"width",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 + +local g_cache_v = { } +local g_cache_h = { } + +local tags = { + -- userskip = "US", + lineskip = "LS", + baselineskip = "BS", + parskip = "PS", + abovedisplayskip = "DA", + belowdisplayskip = "DB", + abovedisplayshortskip = "SA", + belowdisplayshortskip = "SB", + leftskip = "LS", + rightskip = "RS", + topskip = "TS", + splittopskip = "ST", + tabskip = "AS", + spaceskip = "SS", + xspaceskip = "XS", + parfillskip = "PF", + thinmuskip = "MS", + medmuskip = "MM", + thickmuskip = "ML", + leaders = "NL", + cleaders = "CL", + xleaders = "XL", + gleaders = "GL", + -- true = "VS", + -- false = "HS", +} + +-- we sometimes pass previous as we can have issues in math (not watertight for all) + +local function ruledglue(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 == space_code or subtype == xspace_code then -- not yet all space + 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 == 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 = copy_list(info) + if vertical then + info = vpack_nodes(info) + end + head, current = insert_node_before(head,current,info) + return head, getnext(current) +end + +local k_cache_v = { } +local k_cache_h = { } + +local function ruledkern(head,current,vertical) + local kern = getfield(current,"kern") + local info = (vertical and k_cache_v or k_cache_h)[kern] + if info then + -- print("kern hit") + else + local amount = formatters["%s:%0.3f"](vertical and "VK" 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 + (vertical and k_cache_v or k_cache_h)[kern] = info + end + info = copy_list(info) + if vertical then + info = vpack_nodes(info) + end + head, current = insert_node_before(head,current,info) + return head, getnext(current) +end + +local i_cache = { } + +local function ruleditalic(head,current) + local kern = getfield(current,"kern") + local info = i_cache[kern] + if info then + -- print("kern hit") + else + 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 = copy_list(info) + head, current = insert_node_before(head,current,info) + return head, getnext(current) +end + +local p_cache_v = { } +local p_cache_h = { } + +local function ruledpenalty(head,current,vertical) + local penalty = getfield(current,"penalty") + 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 = copy_list(info) + if vertical then + info = vpack_nodes(info) + elseif raisepenalties then + setfield(info,"shift",-65536*4) + end + head, current = insert_node_before(head,current,info) + return head, getnext(current) +end + +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 current = head + local previous = nil + local attr = unsetvalue + local prev_trace_fontkern = nil + while current do + local id = getid(current) + local a = forced or getattr(current,a_visual) or unsetvalue + if a ~= attr then + prev_trace_fontkern = trace_fontkern + 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 + else -- dead slow: + trace_hbox = hasbit(a, 1) + trace_vbox = hasbit(a, 2) + trace_vtop = hasbit(a, 4) + trace_kern = hasbit(a, 8) + trace_glue = hasbit(a, 16) + trace_penalty = hasbit(a, 32) + trace_fontkern = hasbit(a, 64) + trace_strut = hasbit(a, 128) + trace_whatsit = hasbit(a, 256) + trace_glyph = hasbit(a, 512) + trace_simple = hasbit(a, 1024) + trace_user = hasbit(a, 2048) + trace_math = hasbit(a, 4096) + trace_italic = hasbit(a, 8192) + trace_origin = hasbit(a,16384) + end + attr = a + 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 + elseif id == disc_code then + 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 + local subtype = getsubtype(current) + -- tricky ... we don't copy the trace attribute in node-inj (yet) + if subtype == font_kern_code or getattr(current,a_fontkern) then + if trace_fontkern or prev_trace_fontkern then + head, current = fontkern(head,current) + end + else -- if subtype == user_kern_code then + if trace_italic then + head, current = ruleditalic(head,current) + elseif trace_kern then + head, current = ruledkern(head,current,vertical) + end + end + 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) + end + elseif id == penalty_code then + if trace_penalty then + head, current = ruledpenalty(head,current,vertical) + end + elseif id == hlist_code then + local content = getlist(current) + if content then + setlist(current,visualize(content,false,nil,current)) + end + if 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 + 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 + end + previous = current + current = getnext(current) + end + return head +end + +do + + local function freed(cache) + local n = 0 + for k, v in next, cache do + free_node_list(v) + n = n + 1 + end + if n == 0 then + return 0, cache + else + return n, { } + end + end + + local function cleanup() + local hf, nw, nb, ng_v, ng_h, np_v, np_h, nk_v, nk_h + nf, f_cache = freed(f_cache) + nw, w_cache = freed(w_cache) + nb, b_cache = freed(b_cache) + no, o_cache = freed(o_cache) + ng_v, g_cache_v = freed(g_cache_v) + ng_h, g_cache_h = freed(g_cache_h) + np_v, p_cache_v = freed(p_cache_v) + np_h, p_cache_h = freed(p_cache_h) + nk_v, k_cache_v = freed(k_cache_v) + nk_h, k_cache_h = freed(k_cache_h) + -- report_visualize("cache cleanup: %s fontkerns, %s skips, %s penalties, %s kerns, %s whatsits, %s boxes, %s origins", + -- nf,ng_v+ng_h,np_v+np_h,nk_v+nk_h,nw,nb,no) + end + + local function handler(head) + if usedfont then + starttiming(visualizers) + -- local l = texgetattribute(a_layer) + -- local v = texgetattribute(a_visual) + -- texsetattribute(a_layer,unsetvalue) + -- texsetattribute(a_visual,unsetvalue) + head = visualize(tonut(head),true) + -- texsetattribute(a_layer,l) + -- texsetattribute(a_visual,v) + -- -- cleanup() + stoptiming(visualizers) + return tonode(head), true + else + return head, false + end + end + + visualizers.handler = handler + + luatex.registerstopactions(cleanup) + +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 + +do + + 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 in traverse_nodes(list) do + local id = getid(n) + 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 format("%s seconds",statistics.elapsedtime(visualizers)) + end +end) + +-- interface + +local implement = interfaces.implement + +implement { name = "setvisual", 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 } + |