trac-vis.lmt /size: 79 Kb    last modification: 2024-01-16 09:03
1if not modules then modules = { } end modules ['trac-vis'] = {
2    version   = 1.001,
3    optimize  = true,
4    comment   = "companion to trac-vis.mkiv",
5    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
6    copyright = "PRAGMA ADE / ConTeXt Development Team",
7    license   = "see context related readme files"
8}
9
10local node, nodes, attributes, tex = node, nodes, attributes, tex
11local type, tonumber, next, rawget = type, tonumber, next, rawget
12local gmatch, gsub = string.gmatch, string.gsub
13local formatters = string.formatters
14local round = math.round
15
16-- This module started out in the early days of mkiv and luatex with visualizing
17-- kerns related to fonts. In the process of cleaning up the visual debugger code it
18-- made sense to integrate some other code that I had laying around and replace the
19-- old supp-vis debugging code. As only a subset of the old visual debugger makes
20-- sense it has become a different implementation. Soms of the m-visual
21-- functionality will also be ported. The code is rather trivial. The caching is not
22-- really needed but saves upto 50% of the time needed to add visualization. Of
23-- course the overall runtime is larger because of color and layer processing in the
24-- backend (can be times as much) so the runtime is somewhat larger with full
25-- visualization enabled. In practice this will never happen unless one is demoing.
26
27-- todo: global switch (so no attributes)
28-- todo: maybe also xoffset, yoffset of glyph
29-- todo: inline concat (more efficient)
30-- todo: tags can also be numbers (just add to hash)
31-- todo: make a lmtx variant (a few more efficient fetchers)
32
33local nuts                = nodes.nuts
34local tonut               = nuts.tonut
35
36local setboth             = nuts.setboth
37local setlink             = nuts.setlink
38local setlist             = nuts.setlist
39local setsubtype          = nuts.setsubtype
40local setattr             = nuts.setattr
41local setwidth            = nuts.setwidth
42local setshift            = nuts.setshift
43local setoffsets          = nuts.setoffsets
44
45local getid               = nuts.getid
46local getfont             = nuts.getfont
47local getattr             = nuts.getattr
48local getsubtype          = nuts.getsubtype
49local getbox              = nuts.getbox
50local getlist             = nuts.getlist
51local getprev             = nuts.getprev
52local getnext             = nuts.getnext
53local getboth             = nuts.getboth
54local getwhd              = nuts.getwhd
55local getkern             = nuts.getkern
56local getpenalty          = nuts.getpenalty
57local getwidth            = nuts.getwidth
58local getdepth            = nuts.getdepth
59local getexpansion        = nuts.getexpansion
60local getstate            = nuts.getstate
61local getoffsets          = nuts.getoffsets
62local getindex            = nuts.getindex
63local getprop             = nuts.getprop
64
65local isglyph             = nuts.isglyph
66
67local hpack_nodes         = nuts.hpack
68local vpack_nodes         = nuts.vpack
69local copylist            = nuts.copylist
70local copy_node           = nuts.copy
71local insertnodebefore    = nuts.insertbefore
72local insertnodeafter     = nuts.insertafter
73local flushnodelist       = nuts.flushlist
74
75local hpack_string        = nuts.typesetters.tohpack
76
77local texgetattribute     = tex.getattribute
78local texsetattribute     = tex.setattribute
79
80local setmetatableindex   = table.setmetatableindex
81
82local unsetvalue          = attributes.unsetvalue
83
84local current_font        = font.current
85
86local fonthashes          = fonts.hashes
87local chardata            = fonthashes.characters
88local exheights           = fonthashes.exheights
89local emwidths            = fonthashes.emwidths
90local pt_factor           = number.dimenfactors.pt
91
92local nodepool            = nuts.pool
93local new_rule            = nodepool.rule
94local new_virtual_rule    = nodepool.virtualrule
95local new_kern            = nodepool.kern
96local new_glue            = nodepool.glue
97local new_hlist           = nodepool.hlist
98local new_vlist           = nodepool.vlist
99
100local tracers             = nodes.tracers
101local visualizers         = nodes.visualizers
102
103local setcolor            = tracers.colors.set
104local setlistcolor        = tracers.colors.setlist
105local settransparency     = tracers.transparencies.set
106local setlisttransparency = tracers.transparencies.setlist
107
108local starttiming         = statistics.starttiming
109local stoptiming          = statistics.stoptiming
110
111local a_visual            = attributes.private("visual")
112local a_filter            = attributes.private("filter")
113local a_layer             = attributes.private("viewerlayer")
114
115local enableaction        = nodes.tasks.enableaction
116
117local report_visualize = logs.reporter("visualize")
118
119local modes = {
120    hbox          = 0x0000001,
121    vbox          = 0x0000002,
122    vtop          = 0x0000004,
123    kern          = 0x0000008,
124    glue          = 0x0000010,
125    penalty       = 0x0000020,
126    fontkern      = 0x0000040,
127    strut         = 0x0000080,
128    whatsit       = 0x0000100,
129    glyph         = 0x0000200,
130    simple        = 0x0000400,
131    simplehbox    = 0x0000401,
132    simplevbox    = 0x0000402,
133    simplevtop    = 0x0000404,
134    user          = 0x0000800,
135    math          = 0x0001000,
136    italic        = 0x0002000,
137    origin        = 0x0004000,
138    discretionary = 0x0008000,
139    expansion     = 0x0010000,
140    line          = 0x0020000,
141    space         = 0x0040000,
142    depth         = 0x0080000,
143    marginkern    = 0x0100000,
144    mathkern      = 0x0200000, --
145    dir           = 0x0400000,
146    par           = 0x0800000,
147    mathglue      = 0x1000000, --
148    mark          = 0x2000000,
149    insert        = 0x4000000,
150    boundary      = 0x8000000,
151
152    vkern         = 0x0000008,
153    hkern         = 0x0000008,
154    vglue         = 0x0000010,
155    hglue         = 0x0000010,
156    vpenalty      = 0x0000020,
157    hpenalty      = 0x0000020,
158}
159
160local filters = {
161    vglue    = 0x001,
162    hglue    = 0x002,
163 -- mglue    = 0x004,
164    vkern    = 0x010,
165    hkern    = 0x020,
166 -- mkern    = 0x040,
167    vpenalty = 0x100,
168    hpenalty = 0x200,
169 -- mpenalty = 0x400,
170}
171
172visualizers.modes   = modes
173visualizers.filters = filters
174
175local usedfont, exheight, emwidth
176local 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_mathkern, l_mathshape, l_italic, l_origin, l_discretionary, l_expansion, l_line, l_space, l_depth,
177    l_dir, l_whatsit, l_mark, l_insert, l_boundary
178
179local enabled = false
180local layers  = { }
181
182local preset_boxes  = modes.hbox + modes.vbox + modes.vtop + modes.origin
183local preset_makeup = preset_boxes
184                    + modes.kern + modes.glue + modes.penalty + modes.boundary
185local preset_all    = preset_makeup
186                    + modes.fontkern + modes.marginkern + modes.mathkern
187                    + modes.whatsit + modes.glyph + modes.user + modes.math
188                    + modes.dir + modes.mathglue + modes.mark + modes.insert
189
190function visualizers.setfont(id)
191    usedfont = id or current_font()
192    exheight = exheights[usedfont]
193    emwidth  = emwidths[usedfont]
194end
195
196-- we can preset a bunch of bits
197
198local userrule    -- bah, not yet defined: todo, delayed(nuts.rules,"userrule")
199local outlinerule -- bah, not yet defined: todo, delayed(nuts.rules,"userrule")
200local emptyrule   -- bah, not yet defined: todo, delayed(nuts.rules,"userrule")
201
202local function getusedfont()
203    if not usedfont then
204        -- we use a narrow monospaced font -- infofont ?
205        visualizers.setfont(fonts.definers.define { name = "lmmonoltcond10regular", size = tex.sp("4pt") })
206    end
207    return usedfont
208end
209
210visualizers.getusedfont = getusedfont
211
212local function initialize()
213    --
214    if not usedfont then
215        getusedfont()
216    end
217    --
218    for mode, value in next, modes do
219        local tag = formatters["v_%s"](mode)
220        attributes.viewerlayers.define {
221            tag       = tag,
222            title     = formatters["visualizer %s"](mode),
223            visible   = "start",
224            editable  = "yes",
225            printable = "yes"
226        }
227        layers[mode] = attributes.viewerlayers.register(tag,true)
228    end
229    l_hbox          = layers.hbox
230    l_vbox          = layers.vbox
231    l_vtop          = layers.vtop
232    l_glue          = layers.glue
233    l_kern          = layers.kern
234    l_penalty       = layers.penalty
235    l_fontkern      = layers.fontkern
236    l_strut         = layers.strut
237    l_whatsit       = layers.whatsit
238    l_glyph         = layers.glyph
239    l_user          = layers.user
240    l_math          = layers.math
241    l_italic        = layers.italic
242    l_marginkern    = layers.marginkern
243    l_mathkern      = layers.mathkern
244    l_mathshapekern = layers.mathshapekern
245    l_origin        = layers.origin
246    l_discretionary = layers.discretionary
247    l_expansion     = layers.expansion
248    l_line          = layers.line
249    l_space         = layers.space
250    l_depth         = layers.depth
251    l_dir           = layers.dir
252    l_par           = layers.par
253    l_mark          = layers.mark
254    l_insert        = layers.insert
255    l_boundary      = layers.boundary
256    --
257    if not userrule then
258       userrule = nuts.rules.userrule
259    end
260    --
261    if not outlinerule then
262       outlinerule = nuts.pool.outlinerule
263    end
264    --
265    if not emptyrule then
266       emptyrule = nuts.pool.emptyrule
267    end
268    initialize = false
269end
270
271local function enable()
272    if initialize then
273        initialize()
274    end
275    enableaction("shipouts","nodes.visualizers.handler")
276    report_visualize("enabled")
277    enabled = true
278    tex.setcount("global","c_syst_visualizers_state",1) -- so that we can optimize at the tex end
279end
280
281function visualizers.enable()
282    if not enabled then
283        enable()
284    end
285end
286
287local function setvisual(n,a,what,list)
288    if not n or n == "reset" then
289        return unsetvalue
290    elseif n == true or n == "makeup" then
291        if not a or a == 0 or a == unsetvalue then
292            a = preset_makeup
293        else
294            a = a | preset_makeup
295        end
296    elseif n == "boxes" then
297        if not a or a == 0 or a == unsetvalue then
298            a = preset_boxes
299        else
300            a = a | preset_boxes
301        end
302    elseif n == "all" then
303        if what == false then
304            return unsetvalue
305        elseif not a or a == 0 or a == unsetvalue then
306            a = preset_all
307        else
308            a = a | preset_all
309        end
310    elseif type(n) == "string" then
311        for s in gmatch(n,"[a-z]+") do
312            local m = modes[s]
313            if not m then
314                -- go on
315            elseif not a or a == 0 or a == unsetvalue then
316                a = m
317            else
318                a = a | m
319            end
320        end
321    elseif type(n) == "number" then
322        if not a or a == 0 or a == unsetvalue then
323            a = n
324        else
325            a = a | n
326        end
327    end
328    if not a or a == 0 or a == unsetvalue then
329        return unsetvalue
330    elseif not enabled then -- must happen at runtime (as we don't store layers yet)
331        enable()
332    end
333    return a
334end
335
336local function setfilter(n,a,what,list)
337    if not n or n == "reset" then
338        return unsetvalue
339    elseif type(n) == "string" then
340        for s in gmatch(n,"[a-z]+") do
341            local m = filters[s]
342            if not m then
343                -- go on
344            elseif not a or a == 0 or a == unsetvalue then
345                a = m
346            else
347                a = a | m
348            end
349        end
350    end
351    if not a or a == 0 or a == unsetvalue then
352        return unsetvalue
353    else
354        return a
355    end
356end
357
358function nuts.setvisual(n,mode)
359    if mode then
360        setattr(n,a_visual,setvisual(mode,getattr(n,a_visual),true))
361        setattr(n,a_filter,setfilter(mode,getattr(n,a_filter),true))
362    else
363        local a = texgetattribute(a_visual)
364        if a ~= unsetvalue then
365            setattr(n,a_visual,a)
366            setattr(n,a_filter,texgetattribute(a_visual))
367        end
368    end
369end
370
371function nuts.setvisuals(n,mode) -- currently the same
372    setattr(n,a_visual,setvisual(mode,getattr(n,a_visual),true,true))
373    setattr(n,a_filter,setfilter(mode,getattr(n,a_filter),true,true))
374end
375
376-- fast setters
377
378do
379
380    local apply_to_nodes = nuts.apply
381
382    local cached = setmetatableindex(function(t,k)
383        if k == true then
384            return texgetattribute(a_visual)
385        elseif not k then
386            t[k] = unsetvalue
387            return unsetvalue
388        else
389            local v = setvisual(k)
390            t[k] = v
391            return v
392        end
393    end)
394
395 -- local function applyvisuals(n,mode)
396 --     local a = cached[mode]
397 --     apply_to_nodes(n,function(n) setattr(n,a_visual,a) end)
398 -- end
399
400    local a = unsetvalue
401
402    local f = function(n) setattr(n,a_visual,a) end
403
404    local function applyvisuals(n,mode)
405        a = cached[mode]
406        apply_to_nodes(n,f)
407    end
408
409    nuts.applyvisuals = applyvisuals
410
411    function nodes.applyvisuals(n,mode)
412        applyvisuals(tonut(n),mode)
413    end
414
415    function visualizers.attribute(mode)
416        return cached[mode]
417    end
418
419    visualizers.attributes = cached
420
421end
422
423function nuts.copyvisual(n,m)
424    setattr(n,a_visual,getattr(m,a_visual))
425end
426
427function visualizers.setvisual(n)
428    texsetattribute(a_visual,setvisual(n,texgetattribute(a_visual)))
429    texsetattribute(a_filter,setfilter(n,texgetattribute(a_filter)))
430end
431
432function visualizers.setlayer(n)
433    texsetattribute(a_layer,layers[n] or unsetvalue)
434end
435
436local fraction = 10  do
437
438    local function set(mode,v)
439        texsetattribute(a_visual,setvisual(mode,texgetattribute(a_visual),v))
440        texsetattribute(a_filter,setfilter(mode,texgetattribute(a_filter),v))
441    end
442
443    for mode, value in next, modes do
444        trackers.register(formatters["visualizers.%s"](mode), function(v) set(mode,v) end)
445    end
446
447    trackers  .register("visualizers.reset",    function(v) set("reset", v) end)
448    trackers  .register("visualizers.all",      function(v) set("all",   v) end)
449    trackers  .register("visualizers.makeup",   function(v) set("makeup",v) end)
450    trackers  .register("visualizers.boxes",    function(v) set("boxes", v) end)
451    directives.register("visualizers.fraction", function(v) fraction = (v and tonumber(v)) or (v == "more" and 5) or 10 end)
452
453end
454
455-- we can just paste verbatim together .. no typesetting needed
456
457-- experiment ... we can move the font definition away too
458
459    local expandmacro = token.expand_macro
460    local takebox     = tex.takebox
461
462    local function hpack_string(str)
463        expandmacro("syst_v_p",true,str)
464        return tonut(takebox("scratchbox"))
465    end
466
467-- ... if okay it will replace the current hpack_string in node_typ
468
469-- local function sometext(str,layer,color,textcolor,lap,variant)
470--     local text = hpack_string(str,usedfont)
471--     local size = getwidth(text)
472--     local rule = new_rule(size,2*exheight,exheight/2)
473--     local kern = new_kern(-size)
474--     if color then
475--         setcolor(rule,color)
476--     end
477--     if textcolor then
478--         setlistcolor(getlist(text),textcolor)
479--     end
480--     local info = setlink(rule,kern,text)
481--     setlisttransparency(info,"trace:g")
482--     info = hpack_nodes(info)
483--     local width = getwidth(info)
484--     if variant then
485--         setoffsets(info,0,variant*exheight)
486--     end
487--     if lap then
488--         info = new_hlist(setlink(new_kern(-width),info)) -- use xoffset and set info wd to 0
489--     else
490--         info = new_hlist(info) -- a bit overkill: double wrapped
491--     end
492--     if layer then
493--         setattr(info,a_layer,layer)
494--     end
495--     return info, width
496-- end
497
498-- local function sometext(str,layer,color,textcolor,lap,variant)
499--     local text = hpack_string(str,usedfont)
500--     local size = getwidth(text)
501--     local rule = new_virtual_rule(size,2*exheight,exheight/2)
502--     if color then
503--         setcolor(rule,color)
504--     end
505--     if textcolor then
506--         setlistcolor(getlist(text),textcolor)
507--     end
508--     local info = setlink(rule,text)
509--     setlisttransparency(info,"trace:g")
510--     info = hpack_nodes(info)
511--     if variant then
512--         setoffsets(info,0,variant*exheight)
513--     end
514--     info = new_hlist(info) -- a bit overkill: double wrapped
515--     if lap then
516--         setoffsets(info,-size)
517--     end
518--     if layer then
519--         setattr(info,a_layer,layer)
520--     end
521--     return info, size
522-- end
523
524local function sometext(str,layer,color,textcolor,lap,variant)
525    local text = hpack_string(str,usedfont)
526    local size = getwidth(text)
527    local rule = new_virtual_rule(size,2*exheight,exheight/2)
528    if color then
529        setcolor(rule,color)
530    end
531    if textcolor then
532        setlistcolor(getlist(text),textcolor)
533    end
534    local info = setlink(rule,text)
535    setlisttransparency(info,"trace:g")
536    info = new_hlist(info)
537    local x, y
538    if lap then
539        x = -size
540    end
541    if variant then
542        y = variant * exheight
543    end
544    if x or y then
545        setoffsets(info,x,y)
546    end
547    if layer then
548        setattr(info,a_layer,layer)
549    end
550    return info, size
551end
552
553local function someblob(str,layer,color,textcolor,width)
554    local text = hpack_string(str,usedfont)
555    local size = getwidth(text)
556    local rule = new_rule(width,2*exheight,exheight/2)
557    local kern = new_kern(-width + (width-size)/2)
558    if color then
559        setcolor(rule,color)
560    end
561    if textcolor then
562        setlistcolor(getlist(text),textcolor)
563    end
564    local info = setlink(rule,kern,text)
565    setlisttransparency(info,"trace:g")
566    info = hpack_nodes(info)
567    local width = getwidth(info)
568    info = new_hlist(info)
569    if layer then
570        setattr(info,a_layer,layer)
571    end
572    return info, width
573end
574
575local caches = setmetatableindex("table")
576
577local fontkern, italickern, marginkern, mathkern do
578
579    local f_cache = caches["fontkern"]
580    local i_cache = caches["italickern"]
581    local s_cache = caches["shapekern"]
582    local m_cache = caches["marginkern"]
583    local l_cache = caches["mathkern"]
584
585    local function somekern(head,current,cache,color,layer)
586        local width = getkern(current)
587        local extra = getexpansion(current)
588        local kern  = width + extra
589        local info  = cache[kern]
590        if not info then
591            local text = hpack_string(formatters[" %0.3f"](kern*pt_factor),usedfont)
592            local rule = new_rule(emwidth/fraction,6*exheight,2*exheight)
593            local list = getlist(text)
594            if kern > 0 then
595                setlistcolor(list,"trace:db")
596            elseif kern < 0 then
597                setlistcolor(list,"trace:dr")
598            else
599                setlistcolor(list,"trace:dg")
600            end
601            setlisttransparency(list,color)
602            setcolor(rule,color)
603            settransparency(rule,color)
604            setshift(text,-5 * exheight)
605            info = new_hlist(setlink(rule,text))
606            setattr(info,a_layer,layer)
607            cache[kern] = info
608        end
609        head = insertnodebefore(head,current,copylist(info))
610        return head, current
611    end
612
613    fontkern = function(head,current)
614        return somekern(head,current,f_cache,"trace:ds",l_fontkern)
615    end
616
617    italickern = function(head,current)
618        return somekern(head,current,i_cache,"trace:do",l_italic)
619    end
620
621    mathshapekern = function(head,current)
622        return somekern(head,current,s_cache,"trace:dg",l_mathshapekern)
623    end
624
625    marginkern = function(head,current)
626        return somekern(head,current,m_cache,"trace:dr",l_marginkern)
627    end
628
629    mathkern = function(head,current)
630        return somekern(head,current,l_cache,"trace:db",l_mathkern)
631    end
632
633end
634
635local ruledglyphexpansion do
636
637    local f_cache = caches["glyphexpansion"]
638
639    ruledglyphexpansion = function(head,current)
640        local extra = getexpansion(current)
641        if extra and extra ~= 0 then
642            extra = extra / 1000
643            local info = f_cache[extra]
644            if not info then
645                local text = hpack_string(tostring(round(extra)),usedfont)
646                local rule = new_rule(emwidth/fraction,exheight,2*exheight)
647                local list = getlist(text)
648                if extra > 0 then
649                    setlistcolor(list,"trace:db")
650                elseif extra < 0 then
651                    setlistcolor(list,"trace:dr")
652                end
653                setlisttransparency(list,"trace:ds")
654                setcolor(rule,"trace:ds")
655                settransparency(rule,"trace:ds")
656                setshift(text,1.75 * exheight)
657                info = new_hlist(setlink(rule,text))
658                setattr(info,a_layer,l_expansion)
659                f_cache[extra] = info
660            end
661            head = insertnodebefore(head,current,copylist(info))
662        end
663        return head, current
664    end
665
666end
667
668local kernexpansion do
669
670    local f_cache = caches["kernexpansion"]
671
672    -- in mkiv we actually need to reconstruct but let's not do that now
673
674    kernexpansion = function(head,current)
675        local extra = getexpansion(current)
676        if extra ~= 0 then
677            extra = extra / 1000
678            local info = f_cache[extra]
679            if not info then
680                local text = hpack_string(tostring(round(extra)),usedfont)
681                local rule = new_rule(emwidth/fraction,exheight,4*exheight)
682                local list = getlist(text)
683                if extra > 0 then
684                    setlistcolor(list,"trace:db")
685                elseif extra < 0 then
686                    setlistcolor(list,"trace:dr")
687                end
688                setlisttransparency(list,"trace:ds")
689                setcolor(rule,"trace:ds")
690                settransparency(rule,"trace:ds")
691                setshift(text,3.5 * exheight)
692                info = new_hlist(setlink(rule,text))
693                setattr(info,a_layer,l_expansion)
694                f_cache[extra] = info
695            end
696            head = insertnodebefore(head,current,copylist(info))
697        end
698        return head, current
699    end
700
701end
702
703local ruledmark do
704
705    local set_code = nodes.markcodes.set
706
707    local sm_cache = setmetatableindex(caches["setmark"], function(t,index)
708        local info = sometext(formatters["SM:%i"](index),usedfont,nil,"trace:w") -- whatsit
709        setattr(info,a_layer,l_mark)
710        t[index] = info
711        return info
712    end)
713
714    local rm_cache = setmetatableindex(caches["resetmark"], function(t,index)
715        local info = sometext(formatters["RM:%i"](index),usedfont,nil,"trace:w") -- whatsit
716        setattr(info,a_layer,l_mark)
717        t[index] = info
718        return info
719    end)
720
721    ruledmark = function(head,current)
722        local index = getindex(current)
723        local info  = getsubtype(current) == set_code and sm_cache[index] or rm_cache[index]
724        head, current = insertnodeafter(head,current,copylist(info))
725        return head, current
726    end
727
728end
729
730local ruledinsert do
731
732    local si_cache = setmetatableindex(caches["insert"], function(f,index)
733        local info = sometext(formatters["SI:%i"](index),usedfont,nil,"trace:w") -- whatsit
734        setattr(info,a_layer,l_insert)
735        si_cache[index] = info
736    end)
737
738    ruledinsert = function(head,current)
739        local info = si_cache[getindex(current)]
740        head, current = insertnodeafter(head,current,copylist(info))
741        return head, current
742    end
743
744end
745
746local ruledwhatsit do
747
748    local whatsitcodes = nodes.whatsitcodes
749    local w_cache      = caches["whatsit"]
750
751    local tags         = {
752        [whatsitcodes.open]           = "OPN",
753        [whatsitcodes.write]          = "WRI",
754        [whatsitcodes.close]          = "CLS",
755     -- [whatsitcodes.special]        = "SPE",
756        [whatsitcodes.latelua]        = "LUA",
757        [whatsitcodes.savepos]        = "POS",
758        [whatsitcodes.userdefined]    = "USR",
759        [whatsitcodes.literal]        = "LIT",
760        [whatsitcodes.setmatrix]      = "MAT",
761        [whatsitcodes.save]           = "SAV",
762        [whatsitcodes.restore]        = "RES",
763        [whatsitcodes.startscaling]   = "+SCA",
764        [whatsitcodes.stopscaling]    = "-SCA",
765        [whatsitcodes.startrotation]  = "+ROT",
766        [whatsitcodes.stoprotation]   = "-ROT",
767        [whatsitcodes.startmirroring] = "+MIR",
768        [whatsitcodes.stopmirroring]  = "-MIR",
769        [whatsitcodes.startclipping]  = "+CLP",
770        [whatsitcodes.stopclipping]   = "-CLP",
771        [whatsitcodes.startmatrix]    = "+MAT",
772        [whatsitcodes.stopmatrix]     = "-MAT",
773        [whatsitcodes.setstate]       = "SET",   -- can't happen because these are added after visualizing
774    }
775
776    ruledwhatsit = function(head,current)
777        local what = getsubtype(current)
778        local info = w_cache[what]
779        if info then
780            -- print("hit whatsit")
781        else
782            info = sometext(formatters["W:%s"](tags[what] or what),usedfont,nil,"trace:w")
783            setattr(info,a_layer,l_whatsit)
784            w_cache[what] = info
785        end
786        head, current = insertnodeafter(head,current,copylist(info))
787        return head, current
788    end
789
790end
791
792local ruledboundary do
793
794    local boundarycodes = nodes.boundarycodes
795    local b_cache       = caches["boundary"]
796
797    local tags          = {
798        [boundarycodes.cancel]     = "CAN",
799        [boundarycodes.user]       = "USR",
800        [boundarycodes.protrusion] = "PRO",
801        [boundarycodes.word]       = "WRD",
802    }
803
804    ruledboundary = function(head,current)
805        local what = getsubtype(current)
806        local info = b_cache[what]
807        if info then
808            -- print("hit whatsit")
809        else
810            info = sometext(formatters["B:%s"](tags[what] or what),usedfont,nil,"trace:w")
811            setattr(info,a_layer,l_boundary)
812            b_cache[what] = info
813        end
814        head, current = insertnodeafter(head,current,copylist(info))
815        return head, current
816    end
817
818end
819
820local ruleddir, ruledpar do
821
822    local dircodes       = nodes.dircodes
823    local directioncodes = tex.directioncodes -- nodes.dirvalues
824
825    local cancel_code    = dircodes.cancel
826    local l2r_code       = directioncodes.l2r
827    local r2l_code       = directioncodes.r2l
828
829    local d_cache        = caches["dir"]
830
831    local getdirection   = nuts.getdirection
832
833    local tags = {
834        l2r    = "L2R",
835        r2l    = "R2L",
836        cancel = "CAN",
837        par    = "PAR",
838    }
839
840    ruledpar = function(head,current)
841        local what = "par" -- getsubtype(current)
842        local info = d_cache[what]
843        if info then
844            -- print("hit par")
845        else
846            info = sometext(formatters["L:%s"](what),usedfont,nil,"trace:w")
847            setattr(info,a_layer,l_dir)
848            d_cache[what] = info
849        end
850        head, current = insertnodeafter(head,current,copylist(info))
851        return head, current
852    end
853
854    ruleddir = function(head,current)
855        local what = getsubtype(current)
856        if what == cancel_code then
857            what = "cancel"
858        elseif getdirection(current) == r2l_code then
859            what = "r2l"
860        else
861            what = "l2r"
862        end
863        local info = d_cache[what]
864        if info then
865            -- print("hit dir")
866        else
867            info = sometext(formatters["D:%s"](what),usedfont,nil,"trace:w")
868            setattr(info,a_layer,l_dir)
869            d_cache[what] = info
870        end
871        head, current = insertnodeafter(head,current,copylist(info))
872        return head, current
873    end
874
875end
876
877local ruleduser do
878
879    local u_cache = caches["user"]
880
881    ruleduser = function(head,current)
882        local what = getsubtype(current)
883        local info = u_cache[what]
884        if info then
885            -- print("hit user")
886        else
887            info = sometext(formatters["U:%s"](what),usedfont)
888            setattr(info,a_layer,l_user)
889            u_cache[what] = info
890        end
891        head, current = insertnodeafter(head,current,copylist(info))
892        return head, current
893    end
894
895end
896
897local ruleddepth do
898
899    ruleddepth = function(current,wd,ht,dp)
900        local wd, ht, dp = getwhd(current)
901        if dp ~= 0 then
902            local rule = new_rule(wd,0,dp)
903            setcolor(rule,"trace:o")
904            settransparency(rule,"trace:g")
905            setattr(rule,a_layer,l_depth)
906            setlist(current,setlink(rule,new_kern(-wd),getlist(current)))
907        end
908    end
909
910end
911
912local ruledbox do
913
914    local b_cache = caches["box"]
915    local o_cache = caches["origin"]
916
917    local getshift       = nuts.getshift
918    local getorientation = nuts.getorientation
919    local setorientation = nuts.setorientation
920    local getheight      = nuts.getheight
921
922    setmetatableindex(o_cache,function(t,size)
923        local rule   = new_rule(2*size,size,size)
924        local origin = hpack_nodes(rule)
925        setcolor(rule,"trace:do")
926        settransparency(rule,"trace:do")
927        setattr(rule,a_layer,l_origin)
928        t[size] = origin
929        return origin
930    end)
931
932    ruledbox = function(head,current,vertical,layer,what,simple,previous,trace_origin,parent)
933        local wd, ht, dp = getwhd(current)
934--       local wd, ht, dh, shift = nuts.getlistdimensions(current) -- MAYBE
935        local force_origin = wd == 0 or (dp + ht) == 0
936        local shift = getshift(current)
937-- print(getorientation(current,true))
938        local orientation, xoffset, yoffset, w, h, d = getorientation(current) -- TODO
939if orientation and orientation ~= 0 and (h ~= 0 or d ~= 0) then
940--     wd = w
941    ht = h
942    dp = d
943end
944        local next = getnext(current)
945        local prev = previous
946        setboth(current)
947        local linewidth = emwidth/fraction
948        local size      = 2*linewidth
949        local this
950        if not simple then
951            this = b_cache[what]
952            if not this then
953                local text = hpack_string(what,usedfont)
954                this = setlink(new_kern(-getwidth(text)),text)
955                setlisttransparency(this,"trace:s")
956                this = new_hlist(this)
957                b_cache[what] = this
958            end
959        end
960        local rest, more
961        if force_origin then
962            rest = emptyrule(wd,ht,dp) -- we accept some overhead
963        elseif what == "_D_" then
964            -- also the other line
965            local list = getlist(current)
966            local up   = list and getheight(list) or 0
967            rest = userrule {
968                width  = wd,
969                height = ht,
970                depth  = dp,
971                line   = linewidth,
972                type   = "box",
973                dashed = 3*size,
974                double = ht - up,
975            }
976        elseif dp == 0 then
977            rest = userrule {
978                width    = wd,
979                height   = ht,
980                line     = linewidth,
981                type     = "box",
982                baseline = false,
983            }
984        else
985            rest = userrule {
986                width  = wd,
987                height = ht,
988                depth  = dp,
989                line   = linewidth,
990                type   = "box",
991                dashed = 3*size,
992            }
993        end
994        --
995        local info = setlink(this and copylist(this) or nil,rest,more)
996        --
997        setlisttransparency(info,"trace:s")
998        info = new_hlist(info) -- important
999        --
1000        setattr(info,a_layer,layer)
1001        if vertical then
1002            if not force_origin and shift == 0 then
1003                info = setlink(current,dp ~= 0 and new_kern(-dp) or nil,info)
1004            elseif trace_origin or force_origin then
1005                local size   = 2*size
1006                local origin = o_cache[size]
1007                origin = copylist(origin)
1008                if getid(parent) == vlist_code then
1009                    setshift(origin,-shift)
1010                    info = setlink(current,new_kern(-size),origin,new_kern(-size-dp),info)
1011                else
1012                    -- todo .. i need an example
1013                    info = setlink(current,dp ~= 0 and new_kern(-dp) or nil,info)
1014                end
1015                setshift(current,0)
1016            else
1017                info = setlink(current,new_dp ~= 0 and new_kern(-dp) or nil,info)
1018                setshift(current,0)
1019            end
1020            info = new_vlist(info,wd,ht,dp,shift)
1021        else
1022            if not force_origin and shift == 0 then
1023                info = setlink(current,new_kern(-wd),info)
1024            elseif trace_origin or force_origin then
1025                local size   = 2*size
1026                local origin = o_cache[size]
1027                origin = copylist(origin)
1028                if getid(parent) == vlist_code then
1029                    info = setlink(current,new_kern(-wd-size-shift),origin,new_kern(-size+shift),info)
1030                else
1031                    setshift(origin,-shift)
1032                    info = setlink(current,new_kern(-wd-size),origin,new_kern(-size),info)
1033                end
1034                setshift(current,0)
1035            else
1036                info = setlink(current,new_kern(-wd),info)
1037                setshift(current,0)
1038            end
1039            info = new_hlist(info,wd,ht,dp,shift)
1040        end
1041-- if orientation then
1042--     setorientation(current,0,0)
1043--     setorientation(info,orientation,xoffset,yoffset)
1044-- end
1045        if next then
1046            setlink(info,next)
1047        end
1048        if prev and prev > 0 then
1049            setlink(prev,info)
1050        end
1051        if head == current then
1052            return info, info
1053        else
1054            return head, info
1055        end
1056    end
1057
1058end
1059
1060local ruledglyph do
1061
1062    -- see boundingbox feature .. maybe a pdf stream is more efficient, after all we
1063    -- have a frozen color anyway or i need a more detailed cache .. below is a more
1064    -- texie approach
1065
1066 -- local ligature_code = 0x8000 + nodes.glyphcodes.ligature
1067
1068    local getglyphdimensions  = nuts.getglyphdimensions
1069
1070    ruledglyph = function(head,current,previous) -- wrong for vertical glyphs
1071        local wd, ht, dp = getglyphdimensions(current)
1072        if wd ~= 0 then
1073            local next = getnext(current)
1074            local prev = previous
1075            setboth(current)
1076            local linewidth = emwidth/(2*fraction)
1077         -- local x_offset, y_offset, l_margin, r_margin, raise = getoffsets(current)
1078            local c, f = isglyph(current)
1079            local char = chardata[f][c]
1080            local info = (dp == 0 and outlinerule and outlinerule(wd,ht,dp,linewidth)) or userrule {
1081                width  = wd,
1082                height = ht,
1083                depth  = dp,
1084                line   = linewidth,
1085                type   = "box",
1086            }
1087            if char and type(char.unicode) == "table" then -- hackery test
1088                setlistcolor(info,"trace:s")
1089                setlisttransparency(info,"trace:ds")
1090            else
1091                setlistcolor(info,"trace:o")
1092                setlisttransparency(info,"trace:do")
1093            end
1094            info = new_hlist(info)
1095            setattr(info,a_layer,l_glyph)
1096            local info = setlink(current,new_kern(-wd),info)
1097            info = hpack_nodes(info)
1098            setwidth(info,wd)
1099            if next then
1100                setlink(info,next)
1101            end
1102            if prev then
1103                setlink(prev,info)
1104            end
1105            if head == current then
1106                return info, info
1107            else
1108                return head, info
1109            end
1110        else
1111            return head, current
1112        end
1113    end
1114
1115    function visualizers.setruledglyph(f)
1116        ruledglyph = f or ruledglyph
1117    end
1118
1119end
1120
1121local ruledglue, ruledmathglue do
1122
1123    local effectiveglue         = nuts.effectiveglue
1124    local iszeroglue            = nuts.iszeroglue
1125
1126    local gluecodes             = nodes.gluecodes
1127
1128    local userskip_code         = gluecodes.userskip
1129    local spaceskip_code        = gluecodes.spaceskip
1130    local xspaceskip_code       = gluecodes.xspaceskip
1131    local zerospaceskip_code    = gluecodes.zerospaceskip or gluecodes.userskip
1132 -- local keepskip_code         = gluecodes.keepskip or gluecodes.userskip
1133    local leftskip_code         = gluecodes.leftskip
1134    local rightskip_code        = gluecodes.rightskip
1135    local lefthangskip_code     = gluecodes.lefthangskip
1136    local righthangskip_code    = gluecodes.righthangskip
1137    local parfillleftskip_code  = gluecodes.parfillleftskip or parfillskip_code
1138    local parfillrightskip_code = gluecodes.parfillrightskip or parfillskip_code
1139    local parinitleftskip_code  = gluecodes.parinitleftskip
1140    local parinitrightskip_code = gluecodes.parinitrightskip
1141    local indentskip_code       = gluecodes.indentskip
1142    local intermathskip_code    = gluecodes.intermathskip
1143    local correctionskip_code   = gluecodes.correctionskip
1144    local tabskip_code          = gluecodes.tabskip
1145
1146    local g_cache_v  = caches["vglue"]
1147    local g_cache_h  = caches["hglue"]
1148
1149    local g_cache_gn = caches["gluename"]
1150    local g_cache_gz = caches["gluezero"]
1151    local g_cache_gf = caches["gluefixed"]
1152
1153    local tags = {
1154     -- [userskip_code]                   = "US",
1155        [gluecodes.lineskip]              = "LI",
1156        [gluecodes.baselineskip]          = "BS",
1157        [gluecodes.parskip]               = "PS",
1158        [gluecodes.abovedisplayskip]      = "DA",
1159        [gluecodes.belowdisplayskip]      = "DB",
1160        [gluecodes.abovedisplayshortskip] = "SA",
1161        [gluecodes.belowdisplayshortskip] = "SB",
1162        [gluecodes.topskip]               = "TS",
1163        [gluecodes.splittopskip]          = "ST",
1164        [tabskip_code]                    = "TB",
1165        [gluecodes.thinmuskip]            = "MS",
1166        [gluecodes.medmuskip]             = "MM",
1167        [gluecodes.thickmuskip]           = "ML",
1168        [intermathskip_code]              = "IM",
1169        [gluecodes.keepskip or 99]        = "KS",
1170        [gluecodes.mathskip]              = "MT",
1171        [gluecodes.leaders]               = "NL",
1172        [gluecodes.cleaders]              = "CL",
1173        [gluecodes.xleaders]              = "XL",
1174        [gluecodes.gleaders]              = "GL",
1175     -- true                              = "VS",
1176     -- false                             = "HS",
1177        [leftskip_code]                   = "LS",
1178        [rightskip_code]                  = "RS",
1179        [lefthangskip_code]               = "LH",
1180        [righthangskip_code]              = "RH",
1181        [spaceskip_code]                  = "SP",
1182        [xspaceskip_code]                 = "XS",
1183        [zerospaceskip_code]              = "ZS",
1184        [parfillleftskip_code]            = "PL",
1185        [parfillrightskip_code]           = "PR",
1186        [parinitleftskip_code]            = "IL",
1187        [parinitrightskip_code]           = "IR",
1188        [indentskip_code]                 = "IN",
1189        [correctionskip_code]             = "CS",
1190    }
1191
1192    local stags = {
1193        [lefthangskip_code]     =  0.5,
1194        [righthangskip_code]    =  0.5,
1195        [leftskip_code]         = -2,
1196        [rightskip_code]        = -2,
1197        [parinitleftskip_code]  = -1.3775,
1198        [parinitrightskip_code] = -1.3775,
1199        [parfillleftskip_code]  = -0.75,
1200        [parfillrightskip_code] = -0.75,
1201    }
1202
1203    local f_amount = formatters["%s:%0.3f"]
1204
1205--     ruledglue = function(head,current,vertical,parent)
1206--         local subtype = getsubtype(current)
1207--         local width   = effectiveglue(current,parent)
1208--         local stag    = stags[subtype]
1209--         local amount  = f_amount(tags[subtype] or (vertical and "VS") or "HS",width*pt_factor) -- can be sped up
1210--         local info    = (vertical and g_cache_v or g_cache_h)[amount]
1211--         if subtype == intermathskip_code then
1212--             head = ruledmathglue(head, current)
1213--         end
1214--         if info then
1215--             -- print("glue hit")
1216--         else
1217--             if subtype == spaceskip_code or subtype == xspaceskip_code or subtype == zerospaceskip_code then
1218--                 info = sometext(amount,l_glue,"trace:y")
1219--             elseif subtype == userskip_code then
1220--                 if width > 0 then
1221--                     info = sometext(amount,l_glue,"trace:b")
1222--                 elseif width < 0 then
1223--                     info = sometext(amount,l_glue,"trace:r")
1224--                 else
1225--                     info = sometext(amount,l_glue,"trace:g")
1226--                 end
1227--             elseif subtype == tabskip_code then
1228--                 info = sometext(amount,l_glue,"trace:s")
1229--             elseif subtype == indentskip_code or subtype == correctionskip_code then
1230--                 info = sometext(amount,l_glue,"trace:s")
1231--             elseif subtype == leftskip_code then
1232--                 info = sometext(amount,l_glue,"trace:y",false,true,stag)
1233--             elseif subtype == rightskip_code then
1234--                 info = sometext(amount,l_glue,"trace:y",false,false,stag)
1235--             elseif subtype == lefthangskip_code then
1236--                 info = sometext(amount,l_glue,"trace:y",false,true,stag)
1237--             elseif subtype == righthangskip_code then
1238--                 info = sometext(amount,l_glue,"trace:y",false,false,stag)
1239--             elseif subtype == parfillleftskip_code then
1240--                 info = sometext(amount,l_glue,"trace:s",false,true,stag)
1241--             elseif subtype == parfillrightskip_code then
1242--                 info = sometext(amount,l_glue,"trace:s",false,false,stag)
1243--             elseif subtype == parinitleftskip_code then
1244--                 info = sometext(amount,l_glue,"trace:s",false,true,stag)
1245--             elseif subtype == parinitrightskip_code then
1246--                 info = sometext(amount,l_glue,"trace:s",false,false,stag)
1247--             else
1248--                 info = sometext(amount,l_glue,"trace:m")
1249--             end
1250--             (vertical and g_cache_v or g_cache_h)[amount] = info
1251--         end
1252--         info = copylist(info)
1253--         if vertical then
1254--             info = vpack_nodes(info)
1255--         end
1256--         head, current = insertnodebefore(head,current,info)
1257--         return head, getnext(current)
1258--     end
1259
1260    local g_caches = { } for k, v in next, tags do g_caches[k] = caches[v] end
1261
1262    ruledglue = function(head,current,vertical,parent)
1263        local subtype = getsubtype(current)
1264        local width   = effectiveglue(current,parent)
1265        local stag    = stags[subtype]
1266        local cache   = g_caches[subtype] or (vertical and g_cache_v) or g_cache_h
1267        local info    = cache[amount]
1268        if subtype == intermathskip_code then
1269            head = ruledmathglue(head, current)
1270        end
1271        if info then
1272            -- print("glue hit")
1273        else
1274            local amount = f_amount(tags[subtype] or (vertical and "VS") or "HS",width*pt_factor)
1275            if subtype == spaceskip_code or subtype == xspaceskip_code or subtype == zerospaceskip_code then
1276                info = sometext(amount,l_glue,"trace:y")
1277            elseif subtype == userskip_code then
1278                if width > 0 then
1279                    info = sometext(amount,l_glue,"trace:b")
1280                elseif width < 0 then
1281                    info = sometext(amount,l_glue,"trace:r")
1282                else
1283                    info = sometext(amount,l_glue,"trace:g")
1284                end
1285            elseif subtype == tabskip_code then
1286                info = sometext(amount,l_glue,"trace:s")
1287            elseif subtype == indentskip_code or subtype == correctionskip_code then
1288                info = sometext(amount,l_glue,"trace:s")
1289            elseif subtype == leftskip_code then
1290                info = sometext(amount,l_glue,"trace:y",false,true,stag)
1291            elseif subtype == rightskip_code then
1292                info = sometext(amount,l_glue,"trace:y",false,false,stag)
1293            elseif subtype == lefthangskip_code then
1294                info = sometext(amount,l_glue,"trace:y",false,true,stag)
1295            elseif subtype == righthangskip_code then
1296                info = sometext(amount,l_glue,"trace:y",false,false,stag)
1297            elseif subtype == parfillleftskip_code then
1298                info = sometext(amount,l_glue,"trace:s",false,true,stag)
1299            elseif subtype == parfillrightskip_code then
1300                info = sometext(amount,l_glue,"trace:s",false,false,stag)
1301            elseif subtype == parinitleftskip_code then
1302                info = sometext(amount,l_glue,"trace:s",false,true,stag)
1303            elseif subtype == parinitrightskip_code then
1304                info = sometext(amount,l_glue,"trace:s",false,false,stag)
1305            else
1306                info = sometext(amount,l_glue,"trace:m")
1307            end
1308            cache[amount] = info
1309        end
1310        info = copylist(info)
1311        if vertical then
1312            info = vpack_nodes(info)
1313        end
1314        head, current = insertnodebefore(head,current,info)
1315        return head, getnext(current)
1316    end
1317
1318    local g_cache_s = caches["space"]
1319    local g_cache_x = caches["xspace"]
1320    local g_cache_f = caches["fspace"]
1321
1322    local getoptions = nuts.getoptions
1323
1324    ruledspace = function(head,current,parent)
1325        local subtype = getsubtype(current)
1326        local width = effectiveglue(current,parent)
1327        local info
1328        if subtype ~= spaceskip_code then
1329            info = g_cache_x[width]
1330            if not info then
1331                info = someblob("XS",l_glue,"trace:m",nil,width)
1332                g_cache_x[width] = info
1333            end
1334        elseif (getoptions(current) & 2) == 2 then
1335            info = g_cache_f[width]
1336            if not info then
1337                info = someblob("SF",l_glue,"trace:c",nil,width)
1338                g_cache_f[width] = info
1339            end
1340        else
1341            info = g_cache_s[width]
1342            if not info then
1343                info = someblob("SP",l_glue,"trace:y",nil,width)
1344                g_cache_s[width] = info
1345            end
1346        end
1347        info = copylist(info)
1348        head, current = insertnodebefore(head,current,info)
1349        return head, getnext(current)
1350    end
1351
1352    -- we sometimes pass previous as we can have issues in math (not watertight for all)
1353
1354    local mathvalues = tex.mathparametercodes -- nodes.mathvalues
1355
1356    mathvalues[-1] = "left"
1357    mathvalues[-2] = "right"
1358
1359    local morehack = setmetatableindex(function(t,k)
1360        local v = mathematics.classnames[k] -- yet unknown
1361        v = v and string.sub(v,1,3) or string.formatters["x%02x"](k)
1362        t[k] = v
1363        return v
1364    end)
1365
1366    local temphack = setmetatableindex(function(t,k)
1367        local v = mathvalues[k]
1368        if v then
1369            v = gsub(v,"spacing","") -- old, quads and so
1370        else
1371            v = k - 256
1372            v = morehack[v//64] .. morehack[v%64]
1373        end
1374        t[k] = v
1375        return v
1376    end)
1377
1378    local g_cache_qd = caches["mathquad"]
1379
1380    ruledmathglue = function(head,current,parent)
1381        local name  = getfont(current)
1382        local zero  = iszeroglue(current)
1383        local fixed = getprop(current,"fixedmathalign")
1384        local color = false
1385        local info  = zero and g_cache_gz[name]
1386        local quad  = name == 0
1387        local width = quad and effectiveglue(current,parent)
1388        if not info then
1389            if quad then
1390                info  = g_cache_qd[width]
1391                color = "trace:z"
1392            elseif fixed then
1393                info  = g_cache_gf[name]
1394                color = "trace:dr"
1395            else
1396                info  = g_cache_gn[name]
1397                color = "trace:z"
1398            end
1399        end
1400        if not info then
1401            local amount = quad and f_amount("QUAD",width*pt_factor) or temphack[name]
1402         -- local amount = quad and f_amount("QD",width*pt_factor) or temphack[name]
1403            local text   = hpack_string(amount,usedfont)
1404            local rule   = new_rule(emwidth/fraction,2*exheight,(zero and 4.25 or 2.75)*exheight)
1405            local list   = getlist(text)
1406            setlistcolor(list,color)
1407            setcolor(rule,color)
1408            setlisttransparency(list,color)
1409            settransparency(rule,color)
1410            setshift(text,(zero and 3.5 or 2)*exheight)
1411            info = new_hlist(setlink(rule,text))
1412            setattr(info,a_layer,l_glue)
1413            if quad then
1414                g_cache_qd[width] = info
1415            elseif fixed then
1416                g_cache_gf[name] = info
1417            else
1418                g_cache_gn[name] = info
1419            end
1420        end
1421        return insertnodebefore(head,current,copylist(info))
1422    end
1423
1424end
1425
1426local ruledkern do
1427
1428    local v_cache = caches["vkern"]
1429    local h_cache = caches["hkern"]
1430
1431    ruledkern = function(head,current,vertical)
1432        local kern  = getkern(current)
1433        local cache = vertical and v_cache or h_cache
1434        local info  = cache[kern]
1435        if not info then
1436            local amount = formatters["%s:%0.3f"](vertical and "VK" or "HK",kern*pt_factor)
1437            if kern > 0 then
1438                info = sometext(amount,l_kern,"trace:b")
1439            elseif kern < 0 then
1440                info = sometext(amount,l_kern,"trace:r")
1441            else
1442                info = sometext(amount,l_kern,"trace:g")
1443            end
1444            cache[kern] = info
1445        end
1446        info = copylist(info)
1447        if vertical then
1448            info = vpack_nodes(info)
1449        end
1450        head, current = insertnodebefore(head,current,info)
1451        return head, getnext(current)
1452    end
1453
1454end
1455
1456local ruledstrut do
1457
1458    local strut_size      = 65536 * 8 / 10
1459    local strut_code      = nodes.rulecodes.strut
1460    local math_code       = nodes.nodecodes.math
1461    local traverseid      = nuts.traverseid
1462    local rangedimensions = nuts.rangedimensions
1463    local a_mathaxis      = attributes.private("mathaxis")
1464
1465    ruledstrut = function(head,current,parent)
1466        if getwidth(current) == 0 then
1467            if getsubtype(current) == strut_code  then
1468                local w = strut_size
1469                local a = getattr(current,a_mathaxis)
1470                setattr(current,a_layer,l_strut)
1471                if a then
1472                    local p = getprev(current)
1473                    local b, e
1474                    for n in traverseid(math_code,current) do
1475                        e = n
1476                        break
1477                    end
1478                    for n in traverseid(math_code,current,true) do
1479                        b = n
1480                        break
1481                    end
1482                    if not b then
1483                        b = head
1484                    end
1485                    if not e then
1486                        e = nuts.tail(b)
1487                    end
1488                    w = rangedimensions(parent,b,e)
1489                    setwidth(current,w)
1490                    setcolor(current,"trace:ds")
1491                    settransparency(current,"trace:ds")
1492                    head, current, rule = nuts.remove(head,current)
1493                    local kern = new_kern(-w)
1494                    if a == 2 then
1495                        head = insertnodebefore(head,e,kern)
1496                        head = insertnodebefore(head,e,rule)
1497                    else
1498                        insertnodeafter(head,b,kern)
1499                        insertnodeafter(head,b,rule)
1500                    end
1501                    current = p
1502                else
1503                    setwidth(current,w)
1504                    head, current = insertnodeafter(head,current,new_kern(-w))
1505                end
1506            end
1507        end
1508        return head, current
1509    end
1510
1511end
1512
1513local ruleditalic do
1514
1515    local i_cache = caches["italic"]
1516
1517    ruleditalic = function(head,current)
1518        local kern = getkern(current)
1519        local info = i_cache[kern]
1520        if not info then
1521            local amount = formatters["%s:%0.3f"]("IC",kern*pt_factor)
1522            if kern > 0 then
1523                info = sometext(amount,l_kern,"trace:b")
1524            elseif kern < 0 then
1525                info = sometext(amount,l_kern,"trace:r")
1526            else
1527                info = sometext(amount,l_kern,"trace:g")
1528            end
1529            i_cache[kern] = info
1530        end
1531        info = copylist(info)
1532        head, current = insertnodebefore(head,current,info)
1533        return head, getnext(current)
1534    end
1535
1536end
1537
1538local ruledmarginkern do
1539
1540    local l_cache = caches["leftmarginkern"]
1541    local r_cache = caches["rightmarginkern"]
1542
1543    ruledmarginkern = function(head,current,subtype)
1544        local kern  = getkern(current)
1545        local left  = subtype == leftmarginkern_code
1546        local cache = left and l_cache or r_cache
1547        local info  = cache[kern]
1548        if not info then
1549            local amount = formatters["%s:%0.3f"](left and "ML" or "MR",kern*pt_factor)
1550            if kern > 0 then
1551                info = sometext(amount,l_marginkern,"trace:b")
1552            elseif kern < 0 then
1553                info = sometext(amount,l_marginkern,"trace:r")
1554            else
1555                info = sometext(amount,l_marginkern,"trace:g")
1556            end
1557            cache[kern] = info
1558        end
1559        info = copylist(info)
1560        head, current = insertnodebefore(head,current,info)
1561        return head, getnext(current)
1562    end
1563
1564end
1565
1566local ruledmathkern do
1567
1568    local h_cache = caches["horizontalmathkern"]
1569    local v_cache = caches["verticalmathkern"]
1570
1571    ruledmathkern = function(head,current,vertical)
1572        local kern  = getkern(current)
1573        local cache = vertical and v_cache or h_cache
1574        local info  = cache[kern]
1575        if not info then
1576            local amount = formatters["%s:%0.3f"](vertical and "MV" or "MH",kern*pt_factor)
1577            if kern > 0 then
1578                info = sometext(amount,l_mathkern,"trace:b")
1579            elseif kern < 0 then
1580                info = sometext(amount,l_mathkern,"trace:r")
1581            else
1582                info = sometext(amount,l_mathkern,"trace:g")
1583            end
1584            cache[kern] = info
1585        end
1586        info = copylist(info)
1587        head, current = insertnodebefore(head,current,info)
1588        return head, getnext(current)
1589    end
1590
1591end
1592
1593local ruleddiscretionary do
1594
1595    local d_cache = caches["discretionary"]
1596
1597    ruleddiscretionary = function(head,current)
1598        local d = d_cache[true]
1599        if not the_discretionary then
1600            local rule = new_rule(4*emwidth/fraction,4*exheight,exheight)
1601            local kern = new_kern(-2*emwidth/fraction)
1602            setlink(kern,rule)
1603            setcolor(rule,"trace:dd")
1604            settransparency(rule,"trace:dd")
1605            setattr(rule,a_layer,l_discretionary)
1606            d = new_hlist(kern)
1607            d_cache[true] = d
1608        end
1609        insertnodeafter(head,current,copylist(d))
1610        return head, current
1611    end
1612
1613end
1614
1615local ruledpenalty do
1616
1617    local cachehash = {
1618        ["MP=%s"] = caches["MP=%s"],
1619        ["MP>%s"] = caches["MP>%s"],
1620        ["MP<%s"] = caches["MP<%s"],
1621        ["MP:%s"] = caches["MP:%s"],
1622        ["VP:%s"] = caches["VP:%s"],
1623        ["HP:%s"] = caches["HP:%s"],
1624    }
1625
1626    local raisepenalties = false
1627
1628    ----- getpenalty        = nuts.getpenalty
1629    local pre_penalty_code  = nodes.penaltycodes.mathprepenalty
1630    local post_penalty_code = nodes.penaltycodes.mathpostpenalty
1631
1632    directives.register("visualizers.raisepenalties",function(v) raisepenalties = v end)
1633
1634    local getoptions   = nuts.getoptions
1635
1636    local mathforward  = tex.penaltyoptioncodes.mathforward
1637    local mathbackward = tex.penaltyoptioncodes.mathbackward
1638
1639    ruledpenalty = function(head,current,vertical,subtype)
1640        local penalty = getpenalty(current)
1641        local ismath  = subtype == pre_penalty_code or subtype == post_penalty_code or subtype == true
1642        local amount
1643        if ismath then
1644            local options  = getoptions(current)
1645            local forward  = (options & mathforward) ~= 0
1646            local backward = (options & mathbackward) ~= 0
1647            if forward then
1648                if backward then
1649                    amount = "MP=%s"
1650                else
1651                    amount = "MP>%s"
1652                end
1653            elseif backward then
1654                amount = "MP<%s"
1655            else
1656                amount = "MP:%s"
1657            end
1658        elseif vertical then
1659            amount = "VP:%s"
1660        else
1661            amount = "HP:%s"
1662        end
1663        local cache = cachehash[amount]
1664        local info  = cache[penalty]
1665        if info then
1666            -- print("penalty hit")
1667        else
1668            amount = formatters[amount](penalty)
1669            if ismath then
1670                info = sometext(amount,l_penalty,"trace:s")
1671            elseif penalty > 0 then
1672                info = sometext(amount,l_penalty,"trace:b")
1673            elseif penalty < 0 then
1674                info = sometext(amount,l_penalty,"trace:r")
1675            else
1676                info = sometext(amount,l_penalty,"trace:g")
1677            end
1678            cache[penalty] = info
1679        end
1680        info = copylist(info)
1681        if vertical then
1682            info = vpack_nodes(info)
1683        elseif ismath then
1684            setshift(info, 65536*4)
1685        elseif raisepenalties then
1686            setshift(info,-65536*4)
1687        end
1688        head, current = insertnodebefore(head,current,info)
1689        return head, getnext(current)
1690    end
1691
1692end
1693
1694local ruledmath do
1695
1696    local mathcodes = nodes.mathcodes
1697    local tags      = {
1698        [true] = {
1699            math      = { "SM:?", caches["SM:?"] },
1700            beginmath = { "SM:B", caches["SM:B"] },
1701            endmath   = { "SM:E", caches["SM:E"] },
1702        },
1703        [false] = {
1704            math      = { "M:?", caches["M:?"] },
1705            beginmath = { "M:B", caches["M:B"] },
1706            endmath   = { "M:E", caches["M:E"] },
1707        },
1708    }
1709
1710    local getoptions = nuts.getoptions
1711    local shortmath  = tex.mathoptioncodes.short
1712
1713    ruledmath = function(head,current)
1714        local what  = getsubtype(current)
1715        local mtag  = mathcodes[what]
1716        local skip  = getkern(current) + getwidth(current) -- surround
1717        local short = (getoptions(current) & shortmath) ~= 0
1718        local htag  = tags[short][mtag or "math"] or tags[false].math
1719        local ttag  = htag[1]
1720        local cache = htag[2]
1721        local info  = cache[skip]
1722        if info then
1723            -- print("hit math")
1724        else
1725            local text, width = sometext(ttag,usedfont,nil,"trace:dr")
1726            local rule = new_rule(skip,-655360/fraction,2*655360/fraction)
1727            local dist = mtag == "beginmath" and width or skip
1728            setcolor(rule,"trace:dr")
1729            settransparency(rule,"trace:dr")
1730            setattr(rule,a_layer,l_math)
1731            info = new_hlist(setlink(new_glue(-skip),rule,new_glue(-dist),text))
1732            setattr(info,a_layer,l_math)
1733            cache[skip] = info
1734        end
1735        local saved = current
1736        head, current = insertnodeafter(head,current,copylist(info))
1737        if getpenalty(saved) ~= 0 then
1738            head, current = ruledpenalty(head,saved,false,true)
1739        end
1740        return head, current
1741    end
1742
1743end
1744
1745do
1746
1747    local nodecodes               = nodes.nodecodes
1748    local disc_code               = nodecodes.disc
1749    local kern_code               = nodecodes.kern
1750    local glyph_code              = nodecodes.glyph
1751    local glue_code               = nodecodes.glue
1752    local penalty_code            = nodecodes.penalty
1753    local whatsit_code            = nodecodes.whatsit
1754    local user_code               = nodecodes.user
1755    local math_code               = nodecodes.math
1756    local hlist_code              = nodecodes.hlist
1757    local vlist_code              = nodecodes.vlist
1758    local dir_code                = nodecodes.dir
1759    local par_code                = nodecodes.par
1760    local mark_code               = nodecodes.mark
1761    local insert_code             = nodecodes.insert
1762    local rule_code               = nodecodes.rule
1763    local boundary_code           = nodecodes.boundary
1764
1765    local kerncodes               = nodes.kerncodes
1766    local fontkern_code           = kerncodes.fontkern
1767    local italickern_code         = kerncodes.italiccorrection
1768    local spacefontkern_code      = kerncodes.spacefontkern
1769    local leftkern_code           = kerncodes.leftcorrectionkern
1770    local rightkern_code          = kerncodes.rightcorrectionkern
1771    local leftmarginkern_code     = kerncodes.leftmarginkern
1772    local rightmarginkern_code    = kerncodes.rightmarginkern
1773    local mathshapekern_code      = kerncodes.mathshapekern
1774    local horizontalmathkern_code = kerncodes.horizontalmathkern
1775    local verticalmathkern_code   = kerncodes.verticalmathkern
1776    ----- userkern_code           = kerncodes.userkern
1777
1778    local skipcodes               = nodes.skipcodes
1779    local spaceskip_code          = skipcodes.spaceskip
1780    local xspaceskip_code         = skipcodes.xspaceskip
1781    local zerospaceskip_code      = skipcodes.zerospaceskip
1782    local intermathskip_code      = skipcodes.intermathskip
1783
1784    local listcodes               = nodes.listcodes
1785    local linelist_code           = listcodes.line
1786    local rowlist_code            = listcodes.alignment
1787
1788 -- local vtop_package_state      = 3 -- todo: symbolic
1789 -- local dbox_package_state      = 4 -- todo: symbolic
1790
1791    local getleader               = nuts.getleader
1792    local getdisc                 = nuts.getdisc
1793
1794    local setleader               = nuts.setleader
1795    local setdisc                 = nuts.setdisc
1796
1797 -- local cache
1798
1799    local function visualize(head,vertical,forced,parent)
1800        local trace_hbox            = false
1801        local trace_vbox            = false
1802        local trace_vtop            = false
1803        local trace_kern            = false
1804        local trace_glue            = false
1805        local trace_penalty         = false
1806        local trace_fontkern        = false
1807        local trace_strut           = false
1808        local trace_whatsit         = false
1809        local trace_glyph           = false
1810        local trace_simple          = false
1811        local trace_user            = false
1812        local trace_math            = false
1813        local trace_mathkern        = false
1814        local trace_marginkern      = false
1815        local trace_italic          = false
1816        local trace_origin          = false
1817        local trace_discretionary   = false
1818        local trace_expansion       = false
1819        local trace_line            = false
1820        local trace_space           = false
1821        local trace_depth           = false
1822        local trace_dir             = false
1823        local trace_par             = false
1824        local trace_mathglue        = false
1825        local trace_mark            = false
1826        local trace_insert          = false
1827        local trace_boundary        = false
1828        local current               = head
1829        local previous              = nil
1830        local attr                  = unsetvalue
1831        local prev_trace_fontkern   = nil
1832        local prev_trace_italic     = nil
1833        local prev_trace_expansion  = nil
1834
1835        -- too many locals so:
1836
1837        local vglue_code    = filters.vglue
1838        local hglue_code    = filters.hglue
1839        local vkern_code    = filters.vkern
1840        local hkern_code    = filters.hkern
1841        local vpenalty_code = filters.vpenalty
1842        local hpenalty_code = filters.hpenalty
1843
1844        while current do
1845            local id = getid(current)
1846            local a = forced or getattr(current,a_visual) or unsetvalue
1847            local subtype, content
1848            if a ~= attr then
1849                prev_trace_fontkern  = trace_fontkern
1850                prev_trace_italic    = trace_italic
1851                prev_trace_expansion = trace_expansion
1852                attr = a
1853                if a == unsetvalue then
1854                    trace_hbox          = false
1855                    trace_vbox          = false
1856                    trace_vtop          = false
1857                    trace_kern          = false
1858                    trace_glue          = false
1859                    trace_penalty       = false
1860                    trace_fontkern      = false
1861                    trace_strut         = false
1862                    trace_whatsit       = false
1863                    trace_glyph         = false
1864                    trace_simple        = false
1865                    trace_user          = false
1866                    trace_math          = false
1867                    trace_italic        = false
1868                    trace_origin        = false
1869                    trace_discretionary = false
1870                    trace_expansion     = false
1871                    trace_line          = false
1872                    trace_space         = false
1873                    trace_depth         = false
1874                    trace_marginkern    = false
1875                    trace_mathkern      = false
1876                    trace_dir           = false
1877                    trace_par           = false
1878                    trace_mathglue      = false
1879                    trace_mark          = false
1880                    trace_insert        = false
1881                    trace_boundary      = false
1882                    if id == kern_code then
1883                        goto kern
1884                    else
1885                        goto list
1886                    end
1887                else -- we need them to be booleans
1888                 -- cache[a]()
1889                    trace_hbox          = a & 0x0000001 ~= 0
1890                    trace_vbox          = a & 0x0000002 ~= 0
1891                    trace_vtop          = a & 0x0000004 ~= 0
1892                    trace_kern          = a & 0x0000008 ~= 0
1893                    trace_glue          = a & 0x0000010 ~= 0
1894                    trace_penalty       = a & 0x0000020 ~= 0
1895                    trace_fontkern      = a & 0x0000040 ~= 0
1896                    trace_strut         = a & 0x0000080 ~= 0
1897                    trace_whatsit       = a & 0x0000100 ~= 0
1898                    trace_glyph         = a & 0x0000200 ~= 0
1899                    trace_simple        = a & 0x0000400 ~= 0
1900                    trace_user          = a & 0x0000800 ~= 0
1901                    trace_math          = a & 0x0001000 ~= 0
1902                    trace_italic        = a & 0x0002000 ~= 0
1903                    trace_origin        = a & 0x0004000 ~= 0
1904                    trace_discretionary = a & 0x0008000 ~= 0
1905                    trace_expansion     = a & 0x0010000 ~= 0
1906                    trace_line          = a & 0x0020000 ~= 0
1907                    trace_space         = a & 0x0040000 ~= 0
1908                    trace_depth         = a & 0x0080000 ~= 0
1909                    trace_marginkern    = a & 0x0100000 ~= 0
1910                    trace_mathkern      = a & 0x0200000 ~= 0
1911                    trace_dir           = a & 0x0400000 ~= 0
1912                    trace_par           = a & 0x0800000 ~= 0
1913                    trace_mathglue      = a & 0x1000000 ~= 0
1914                    trace_mark          = a & 0x2000000 ~= 0
1915                    trace_insert        = a & 0x4000000 ~= 0
1916                    trace_boundary      = a & 0x8000000 ~= 0
1917                end
1918            elseif a == unsetvalue then
1919                goto list
1920            end
1921         -- if trace_strut then
1922         --     setattr(current,a_layer,l_strut)
1923         -- else
1924            if id == glyph_code then
1925                if trace_glyph then
1926                    head, current = ruledglyph(head,current,previous)
1927                end
1928                if trace_expansion then
1929                    head, current = ruledglyphexpansion(head,current)
1930                end
1931            elseif id == disc_code then
1932                if trace_discretionary then
1933                    head, current = ruleddiscretionary(head,current)
1934                end
1935                local pre, post, replace = getdisc(current)
1936                if pre then
1937                    pre = visualize(pre,false,a,parent)
1938                end
1939                if post then
1940                    post = visualize(post,false,a,parent)
1941                end
1942                if replace then
1943                    replace = visualize(replace,false,a,parent)
1944                end
1945                setdisc(current,pre,post,replace)
1946            elseif id == kern_code then
1947                goto kern
1948            elseif id == glue_code then
1949                goto glue
1950            elseif id == penalty_code then
1951                goto penalty
1952            elseif id == hlist_code or id == vlist_code then
1953                goto list
1954            elseif id == rule_code then
1955                if trace_strut then
1956                    head, current = ruledstrut(head,current,parent)
1957                end
1958            elseif id == whatsit_code then
1959                if trace_whatsit then
1960                    head, current = ruledwhatsit(head,current)
1961                end
1962            elseif id == user_code then
1963                if trace_user then
1964                    head, current = ruleduser(head,current)
1965                end
1966            elseif id == math_code then
1967                local saved = current
1968                if trace_math then
1969                    head, current = ruledmath(head,current)
1970                elseif trace_penalty then
1971                    head, current = ruledpenalty(head,current,false,true)
1972                end
1973            elseif id == dir_code then
1974                if trace_dir then
1975                    head, current = ruleddir(head,current)
1976                end
1977            elseif id == par_code then
1978                if trace_par then
1979                    head, current = ruledpar(head,current)
1980                end
1981            elseif id == mark_code then
1982                if trace_mark then
1983                    head, current = ruledmark(head,current)
1984                end
1985            elseif id == insert_code then
1986                if trace_insert then
1987                    head, current = ruledinsert(head,current)
1988                end
1989            elseif id == boundary_code then
1990                if trace_boundary then
1991                    head, current = ruledboundary(head,current)
1992                end
1993            end
1994            goto next
1995          ::penalty::
1996            if trace_penalty then
1997                local f = getattr(current,a_filter)
1998                if f then
1999                    if vertical then
2000                        if (f & hpenalty_code) == hpenalty_code then
2001                            goto next
2002                        end
2003                    else
2004                        if (f & vpenalty_code) == vpenalty_code then
2005                            goto next
2006                        end
2007                    end
2008                end
2009                subtype = getsubtype(current)
2010                head, current = ruledpenalty(head,current,vertical,subtype)
2011            end
2012          ::glue::
2013            content = getleader(current)
2014            if content then
2015                setleader(current,visualize(content,false,nil,parent))
2016            elseif trace_glue then
2017                local f = getattr(current,a_filter)
2018                if f then
2019                    if vertical then
2020                        if (f & hglue_code) == hglue_code then
2021                            goto next
2022                        end
2023                    else
2024                        if (f & vglue_code) == vglue_code then
2025                            goto next
2026                        end
2027                    end
2028                end
2029                head, current = ruledglue(head,current,vertical,parent)
2030            else
2031                subtype = getsubtype(current)
2032                if subtype == spaceskip_code or subtype == xspaceskip_code or subtype == zerospaceskip_code then -- not yet all space
2033                    if trace_space then
2034                        head, current = ruledspace(head,current,parent)
2035                    end
2036                elseif subtype == intermathskip_code then
2037                    if trace_math or trace_mathglue then
2038                        head = ruledmathglue(head,current,parent)
2039                    end
2040                end
2041            end
2042            goto next
2043          ::kern::
2044            subtype = getsubtype(current)
2045            if subtype == fontkern_code or subtype == spacefontkern_code then
2046                if trace_fontkern or prev_trace_fontkern then
2047                    head, current = fontkern(head,current)
2048                end
2049                if trace_expansion or prev_trace_expansion then
2050                    head, current = kernexpansion(head,current)
2051                end
2052            elseif subtype == italickern_code or subtype == leftkern_code or subtype == rightkern_code then
2053                if trace_italic or prev_trace_italic then
2054                    head, current = italickern(head,current)
2055                elseif trace_kern then
2056                    head, current = ruleditalic(head,current)
2057                end
2058            elseif subtype == leftmarginkern_code or subtype == rightmarginkern_code then
2059                if trace_marginkern then
2060                    head, current = marginkern(head,current)
2061                elseif trace_kern then
2062                    head, current = ruledmarginkern(head,current,subtype)
2063                end
2064            elseif subtype == verticalmathkern_code then
2065                if trace_mathkern or trace_kern then
2066                    head, current = ruledmathkern(head,current,true)
2067                end
2068            elseif subtype == horizontalmathkern_code then
2069                if trace_mathkern then
2070                    head, current = mathkern(head,current)
2071                elseif trace_kern then
2072                    head, current = ruledmathkern(head,current,false)
2073                end
2074            elseif subtype == mathshapekern_code then
2075                if trace_mathkern or trace_italic then
2076                    head, current = mathshapekern(head,current)
2077                elseif trace_kern then
2078                    head, current = ruledmathkern(head,current,false)
2079                end
2080            else
2081                if trace_kern then
2082                    local f = getattr(current,a_filter)
2083                    if f then
2084                        if vertical then
2085                            if (f & hkern_code) == hkern_code then
2086                                goto next
2087                            end
2088                        else
2089                            if (f & vkern_code) == vkern_code then
2090                                goto next
2091                            end
2092                        end
2093                    end
2094                    head, current = ruledkern(head,current,vertical)
2095                end
2096            end
2097            goto next
2098          ::list::
2099            if id == hlist_code then
2100                local content = getlist(current)
2101                if content then
2102                    setlist(current,visualize(content,false,nil,current))
2103                end
2104                if trace_depth then
2105                    ruleddepth(current)
2106                end
2107                if trace_line and (getsubtype(current) == linelist_code or getsubtype(current) == rowlist_code) then
2108                    head, current = ruledbox(head,current,false,l_line,"L__",trace_simple,previous,trace_origin,parent)
2109                elseif trace_hbox then
2110                    head, current = ruledbox(head,current,false,l_hbox,"H__",trace_simple,previous,trace_origin,parent)
2111                end
2112            elseif id == vlist_code then
2113                local content = getlist(current)
2114                local state   = getstate(current)
2115                local isvtop  = state == 3 -- vtop_package_state
2116                local isdbox  = state == 4 -- dbox_package_state
2117                local tag     = nil
2118                local layer   = nil
2119                if content then
2120                    setlist(current,visualize(content,true,nil,current))
2121                end
2122                if trace_vtop then
2123                    if isdbox then
2124                        tag   = "_D_"
2125                        layer = l_vtop
2126                    elseif isvtop then
2127                        tag   = "_T_"
2128                        layer = l_vtop
2129                    elseif trace_vbox then
2130                        tag   = "__V"
2131                        layer = l_vbox
2132                    end
2133                elseif trace_vbox then
2134                    if isdbox then
2135                        tag   = "_D_"
2136                        layer = l_vtop
2137                    elseif not isvtop then
2138                        tag   = "__V"
2139                        layer = l_vbox
2140                    end
2141                end
2142                if tag then
2143                    head, current = ruledbox(head,current,true,layer,tag,trace_simple,previous,trace_origin,parent)
2144                end
2145            end
2146          ::next::
2147            previous = current
2148            current  = getnext(current)
2149        end
2150        return head
2151    end
2152
2153    local function cleanup()
2154        for tag, cache in next, caches do
2155            for k, v in next, cache do
2156                flushnodelist(v)
2157            end
2158        end
2159        cleanup = function()
2160            report_visualize("error, duplicate cleanup")
2161        end
2162    end
2163
2164    luatex.registerstopactions(cleanup)
2165
2166    function visualizers.handler(head)
2167        if usedfont then
2168            starttiming(visualizers)
2169            head = visualize(head,true)
2170            stoptiming(visualizers)
2171            return head, true
2172        else
2173            return head, false
2174        end
2175    end
2176
2177    function visualizers.box(n)
2178        if usedfont then
2179            starttiming(visualizers)
2180            local box = getbox(n)
2181            if box then
2182                setlist(box,visualize(getlist(box),getid(box) == vlist_code))
2183            end
2184            stoptiming(visualizers)
2185            return head, true
2186        else
2187            return head, false
2188        end
2189    end
2190
2191end
2192
2193do
2194
2195    local nodecodes  = nodes.nodecodes
2196    local hlist_code = nodecodes.hlist
2197    local vlist_code = nodecodes.vlist
2198    local nextnode   = nuts.traversers.node
2199
2200    local last       = nil
2201    local used       = nil
2202
2203    local mark       = {
2204        "trace:1", "trace:2", "trace:3",
2205        "trace:4", "trace:5", "trace:6",
2206        "trace:7",
2207    }
2208
2209    local function markfonts(list)
2210        for n, id in nextnode, list do
2211            if id == glyph_code then
2212                local font = getfont(n)
2213                local okay = used[font]
2214                if not okay then
2215                    last = last + 1
2216                    okay = mark[last]
2217                    used[font] = okay
2218                end
2219                setcolor(n,okay)
2220            elseif id == hlist_code or id == vlist_code then
2221                markfonts(getlist(n))
2222            end
2223        end
2224    end
2225
2226    function visualizers.markfonts(list)
2227        last, used = 0, { }
2228        markfonts(type(n) == "number" and getlist(getbox(n)) or n)
2229    end
2230
2231end
2232
2233statistics.register("visualization time",function()
2234    if enabled then
2235     -- cleanup() -- in case we don't don't do it each time
2236        return formatters["%s seconds"](statistics.elapsedtime(visualizers))
2237    end
2238end)
2239
2240-- interface
2241
2242do
2243
2244    local implement = interfaces.implement
2245
2246    implement {
2247        name      = "setvisual",
2248        arguments = "string",
2249        actions   = visualizers.setvisual
2250    }
2251
2252    implement {
2253        name      = "setvisuals",
2254        arguments = "string",
2255        actions   = visualizers.setvisual
2256    }
2257
2258    implement {
2259        name      = "getvisual",
2260        arguments = "string",
2261        actions   = { setvisual, context }
2262    }
2263
2264    implement {
2265        name      = "setvisuallayer",
2266        arguments = "string",
2267        actions   = visualizers.setlayer
2268    }
2269
2270    implement {
2271        name      = "markvisualfonts",
2272        arguments = "integer",
2273        actions   = visualizers.markfonts
2274    }
2275
2276    implement {
2277        name      = "setvisualfont",
2278        arguments = "integer",
2279        actions   = visualizers.setfont
2280    }
2281
2282end
2283
2284-- Here for now:
2285
2286do
2287
2288    local function make(str,forecolor,rulecolor,layer)
2289        if initialize then
2290            initialize()
2291        end
2292        local rule = new_rule(emwidth/fraction,exheight,4*exheight)
2293        setcolor(rule,rulecolor)
2294        settransparency(rule,rulecolor)
2295        local info
2296        if str == "" then
2297            info = new_hlist(rule)
2298        else
2299            local text = hpack_string(str,usedfont)
2300            local list = getlist(text)
2301            setlistcolor(list,textcolor)
2302            setlisttransparency(list,textcolor)
2303            setshift(text,3.5 * exheight)
2304            info = new_hlist(setlink(rule,text))
2305        end
2306        setattr(info,a_layer,layer)
2307        return info
2308    end
2309
2310    function visualizers.register(name,textcolor,rulecolor)
2311        if rawget(layers,name) then
2312            -- message
2313            return
2314        end
2315        local cache = caches[name]
2316        local layer = layers[name]
2317        if not textcolor then
2318            textcolor = "trace:ds"
2319        end
2320        if not rulecolor then
2321            rulecolor = "trace:do"
2322        end
2323        return function(str)
2324            if not str then
2325                str = ""
2326            end
2327            local info = cache[str]
2328            if not info then
2329                info = make(str,textcolor,rulecolor,layer)
2330                cache[str] = info
2331            end
2332            return copy_node(info)
2333        end
2334    end
2335
2336end
2337