summaryrefslogtreecommitdiff
path: root/tex/context/base/mkiv/trac-vis.lua
diff options
context:
space:
mode:
Diffstat (limited to 'tex/context/base/mkiv/trac-vis.lua')
-rw-r--r--tex/context/base/mkiv/trac-vis.lua1097
1 files changed, 1097 insertions, 0 deletions
diff --git a/tex/context/base/mkiv/trac-vis.lua b/tex/context/base/mkiv/trac-vis.lua
new file mode 100644
index 000000000..bb94c484b
--- /dev/null
+++ b/tex/context/base/mkiv/trac-vis.lua
@@ -0,0 +1,1097 @@
+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 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_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")
+ 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
+ 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)
+ 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
+
+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
+
+do
+
+ 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 }
+