diff options
Diffstat (limited to 'tex/context/base/mkiv/trac-vis.lmt')
-rw-r--r-- | tex/context/base/mkiv/trac-vis.lmt | 1700 |
1 files changed, 1700 insertions, 0 deletions
diff --git a/tex/context/base/mkiv/trac-vis.lmt b/tex/context/base/mkiv/trac-vis.lmt new file mode 100644 index 000000000..7ac5964da --- /dev/null +++ b/tex/context/base/mkiv/trac-vis.lmt @@ -0,0 +1,1700 @@ +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 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 copy_list = nuts.copy_list +local copy_node = nuts.copy_node +local flush_node_list = nuts.flush_list +local insert_node_before = nuts.insert_before +local insert_node_after = nuts.insert_after +local apply_to_nodes = nuts.apply +local effectiveglue = nuts.effective_glue + +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.vtop + 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 = insert_node_before(head,current,copy_list(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 = insert_node_before(head,current,copy_list(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 = insert_node_before(head,current,copy_list(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 = insert_node_after(head,current,copy_list(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 = insert_node_after(head,current,copy_list(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 = insert_node_after(head,current,copy_list(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 copy_list(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 = copy_list(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 = copy_list(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 = copy_list(info) + if vertical then + info = vpack_nodes(info) + end + head, current = insert_node_before(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 = copy_list(info) + -- head, current = insert_node_before(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 = copy_list(info) + head, current = insert_node_before(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 = copy_list(info) + if vertical then + info = vpack_nodes(info) + end + head, current = insert_node_before(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 = copy_list(info) + head, current = insert_node_before(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 = copy_list(info) + head, current = insert_node_before(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 = copy_list(info) + head, current = insert_node_before(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 + insert_node_after(head,current,copy_list(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 = copy_list(info) + if vertical then + info = vpack_nodes(info) + elseif raisepenalties then + setshift(info,-65536*4) + end + head, current = insert_node_before(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 vtop_package_state = 3 -- todo: symbolic + + 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) + local isvtop = getstate(current) == vtop_package_state + local tag = nil + local layer = nil + if content then + setlist(current,visualize(content,true,nil,current)) + end + if trace_vtop then + if isvtop then + tag = "_T_" + layer = l_vtop + elseif trace_vbox then + tag = "__V" + layer = l_vbox + end + elseif trace_vbox then + if not isvtop then + tag = "__V" + layer = l_vbox + end + end + if tag then + head, current = ruledbox(head,current,true,layer,tag,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 + flush_node_list(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 |