trac-vis.lua /size: 55 Kb    last modification: 2021-10-28 13:50
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 = string.gmatch
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 nodecodes           = nodes.nodecodes
34
35local nuts                = nodes.nuts
36local tonut               = nuts.tonut
37
38local setboth             = nuts.setboth
39local setlink             = nuts.setlink
40local setdisc             = nuts.setdisc
41local setlist             = nuts.setlist
42local setleader           = nuts.setleader
43local setsubtype          = nuts.setsubtype
44local setattr             = nuts.setattr
45local setwidth            = nuts.setwidth
46local setshift            = nuts.setshift
47
48local getid               = nuts.getid
49local getfont             = nuts.getfont
50local getattr             = nuts.getattr
51local getsubtype          = nuts.getsubtype
52local getbox              = nuts.getbox
53local getlist             = nuts.getlist
54local getleader           = nuts.getleader
55local getnext             = nuts.getnext
56local getprev             = nuts.getprev
57local getboth             = nuts.getboth
58local getdisc             = nuts.getdisc
59local getwhd              = nuts.getwhd
60local getkern             = nuts.getkern
61local getpenalty          = nuts.getpenalty
62local getwidth            = nuts.getwidth
63local getdepth            = nuts.getdepth
64local getshift            = nuts.getshift
65local getexpansion        = nuts.getexpansion
66local getdirection        = nuts.getdirection
67local getstate            = nuts.getstate
68
69local isglyph             = nuts.isglyph
70
71local hpack_nodes         = nuts.hpack
72local vpack_nodes         = nuts.vpack
73local copylist            = nuts.copylist
74local copy_node           = nuts.copy
75local insertnodebefore    = nuts.insertbefore
76local insertnodeafter     = nuts.insertafter
77local apply_to_nodes      = nuts.apply
78local find_tail           = nuts.tail
79local effectiveglue       = nuts.effectiveglue
80local flushnodelist       = nuts.flushlist
81
82local hpack_string        = nuts.typesetters.tohpack
83
84local texgetattribute     = tex.getattribute
85local texsetattribute     = tex.setattribute
86
87local setmetatableindex   = table.setmetatableindex
88
89local unsetvalue          = attributes.unsetvalue
90
91local current_font        = font.current
92
93local fonthashes          = fonts.hashes
94local chardata            = fonthashes.characters
95local exheights           = fonthashes.exheights
96local emwidths            = fonthashes.emwidths
97local pt_factor           = number.dimenfactors.pt
98
99local nodepool            = nuts.pool
100local new_rule            = nodepool.rule
101local new_kern            = nodepool.kern
102local new_glue            = nodepool.glue
103local new_hlist           = nodepool.hlist
104local new_vlist           = nodepool.vlist
105
106local tracers             = nodes.tracers
107local visualizers         = nodes.visualizers
108
109local setcolor            = tracers.colors.set
110local setlistcolor        = tracers.colors.setlist
111local settransparency     = tracers.transparencies.set
112local setlisttransparency = tracers.transparencies.setlist
113
114local starttiming         = statistics.starttiming
115local stoptiming          = statistics.stoptiming
116
117local a_visual            = attributes.private("visual")
118local a_layer             = attributes.private("viewerlayer")
119
120local band  = bit32.band
121local bor   = bit32.bor
122
123local enableaction        = nodes.tasks.enableaction
124
125-- local trace_hbox
126-- local trace_vbox
127-- local trace_vtop
128-- local trace_kern
129-- local trace_glue
130-- local trace_penalty
131-- local trace_fontkern
132-- local trace_strut
133-- local trace_whatsit
134-- local trace_user
135-- local trace_math
136-- local trace_italic
137-- local trace_discretionary
138-- local trace_expansion
139-- local trace_line
140-- local trace_space
141
142local report_visualize = logs.reporter("visualize")
143
144local modes = {
145    hbox          = 0x000001,
146    vbox          = 0x000002,
147    vtop          = 0x000004,
148    kern          = 0x000008,
149    glue          = 0x000010,
150    penalty       = 0x000020,
151    fontkern      = 0x000040,
152    strut         = 0x000080,
153    whatsit       = 0x000100,
154    glyph         = 0x000200,
155    simple        = 0x000400,
156    simplehbox    = 0x000401,
157    simplevbox    = 0x000402,
158    simplevtop    = 0x000404,
159    user          = 0x000800,
160    math          = 0x001000,
161    italic        = 0x002000,
162    origin        = 0x004000,
163    discretionary = 0x008000,
164    expansion     = 0x010000,
165    line          = 0x020000,
166    space         = 0x040000,
167    depth         = 0x080000,
168    marginkern    = 0x100000,
169    mathlistkern  = 0x200000,
170    dir           = 0x400000,
171    par           = 0x800000,
172}
173
174local usedfont, exheight, emwidth
175local l_penalty, l_glue, l_kern, l_fontkern, l_hbox, l_vbox, l_vtop, l_strut, l_whatsit, l_glyph, l_user, l_math, l_marginkern, l_mathlistkern, l_italic, l_origin, l_discretionary, l_expansion, l_line, l_space, l_depth,
176    l_dir, l_whatsit
177
178local enabled = false
179local layers  = { }
180
181local preset_boxes  = modes.hbox + modes.vbox + modes.origin
182local preset_makeup = preset_boxes
183                    + modes.kern + modes.glue + modes.penalty
184local preset_all    = preset_makeup
185                    + modes.fontkern + modes.marginkern + modes.mathlistkern
186                    + modes.whatsit + modes.glyph + modes.user + modes.math
187                    + modes.dir + modes.whatsit
188
189function visualizers.setfont(id)
190    usedfont = id or current_font()
191    exheight = exheights[usedfont]
192    emwidth  = emwidths[usedfont]
193end
194
195-- we can preset a bunch of bits
196
197local userrule    -- bah, not yet defined: todo, delayed(nuts.rules,"userrule")
198local outlinerule -- bah, not yet defined: todo, delayed(nuts.rules,"userrule")
199
200local function initialize()
201    --
202    if not usedfont then
203        -- we use a narrow monospaced font -- infofont ?
204        visualizers.setfont(fonts.definers.define { name = "lmmonoltcond10regular", size = tex.sp("4pt") })
205    end
206    --
207    for mode, value in next, modes do
208        local tag = formatters["v_%s"](mode)
209        attributes.viewerlayers.define {
210            tag       = tag,
211            title     = formatters["visualizer %s"](mode),
212            visible   = "start",
213            editable  = "yes",
214            printable = "yes"
215        }
216        layers[mode] = attributes.viewerlayers.register(tag,true)
217    end
218    l_hbox          = layers.hbox
219    l_vbox          = layers.vbox
220    l_vtop          = layers.vtop
221    l_glue          = layers.glue
222    l_kern          = layers.kern
223    l_penalty       = layers.penalty
224    l_fontkern      = layers.fontkern
225    l_strut         = layers.strut
226    l_whatsit       = layers.whatsit
227    l_glyph         = layers.glyph
228    l_user          = layers.user
229    l_math          = layers.math
230    l_italic        = layers.italic
231    l_marginkern    = layers.marginkern
232    l_mathlistkern  = layers.mathlistkern
233    l_origin        = layers.origin
234    l_discretionary = layers.discretionary
235    l_expansion     = layers.expansion
236    l_line          = layers.line
237    l_space         = layers.space
238    l_depth         = layers.depth
239    l_dir           = layers.dir
240    l_par           = layers.par
241    --
242    if not userrule then
243       userrule = nuts.rules.userrule
244    end
245    --
246    if not outlinerule then
247       outlinerule = nuts.pool.outlinerule
248    end
249    initialize = false
250end
251
252local function enable()
253    if initialize then
254        initialize()
255    end
256    enableaction("shipouts","nodes.visualizers.handler")
257    report_visualize("enabled")
258    enabled = true
259    tex.setcount("global","c_syst_visualizers_state",1) -- so that we can optimize at the tex end
260end
261
262local function setvisual(n,a,what,list) -- this will become more efficient when we have the bit lib linked in
263    if not n or n == "reset" then
264        return unsetvalue
265    elseif n == true or n == "makeup" then
266        if not a or a == 0 or a == unsetvalue then
267            a = preset_makeup
268        else
269            a = bor(a,preset_makeup)
270        end
271    elseif n == "boxes" then
272        if not a or a == 0 or a == unsetvalue then
273            a = preset_boxes
274        else
275            a = bor(a,preset_boxes)
276        end
277    elseif n == "all" then
278        if what == false then
279            return unsetvalue
280        elseif not a or a == 0 or a == unsetvalue then
281            a = preset_all
282        else
283            a = bor(a,preset_all)
284        end
285    else
286        for s in gmatch(n,"[a-z]+") do
287            local m = modes[s]
288            if not m then
289                -- go on
290            elseif not a or a == 0 or a == unsetvalue then
291                a = m
292            else
293                a = bor(a,m)
294            end
295        end
296    end
297    if not a or a == 0 or a == unsetvalue then
298        return unsetvalue
299    elseif not enabled then -- must happen at runtime (as we don't store layers yet)
300        enable()
301    end
302    return a
303end
304
305function nuts.setvisual(n,mode)
306    setattr(n,a_visual,setvisual(mode,getattr(n,a_visual),true))
307end
308
309function nuts.setvisuals(n,mode) -- currently the same
310    setattr(n,a_visual,setvisual(mode,getattr(n,a_visual),true,true))
311end
312
313-- fast setters
314
315do
316
317    local cached = setmetatableindex(function(t,k)
318        if k == true then
319            return texgetattribute(a_visual)
320        elseif not k then
321            t[k] = unsetvalue
322            return unsetvalue
323        else
324            local v = setvisual(k)
325            t[k] = v
326            return v
327        end
328    end)
329
330 -- local function applyvisuals(n,mode)
331 --     local a = cached[mode]
332 --     apply_to_nodes(n,function(n) setattr(n,a_visual,a) end)
333 -- end
334
335    local a = unsetvalue
336
337    local f = function(n) setattr(n,a_visual,a) end
338
339    local function applyvisuals(n,mode)
340        a = cached[mode]
341        apply_to_nodes(n,f)
342    end
343
344    nuts.applyvisuals = applyvisuals
345
346    function nodes.applyvisuals(n,mode)
347        applyvisuals(tonut(n),mode)
348    end
349
350    function visualizers.attribute(mode)
351        return cached[mode]
352    end
353
354    visualizers.attributes = cached
355
356end
357
358function nuts.copyvisual(n,m)
359    setattr(n,a_visual,getattr(m,a_visual))
360end
361
362function visualizers.setvisual(n)
363    texsetattribute(a_visual,setvisual(n,texgetattribute(a_visual)))
364end
365
366function visualizers.setlayer(n)
367    texsetattribute(a_layer,layers[n] or unsetvalue)
368end
369
370local function set(mode,v)
371    texsetattribute(a_visual,setvisual(mode,texgetattribute(a_visual),v))
372end
373
374for mode, value in next, modes do
375    trackers.register(formatters["visualizers.%s"](mode), function(v) set(mode,v) end)
376end
377
378local fraction = 10
379
380trackers  .register("visualizers.reset",    function(v) set("reset", v) end)
381trackers  .register("visualizers.all",      function(v) set("all",   v) end)
382trackers  .register("visualizers.makeup",   function(v) set("makeup",v) end)
383trackers  .register("visualizers.boxes",    function(v) set("boxes", v) end)
384directives.register("visualizers.fraction", function(v) fraction = (v and tonumber(v)) or (v == "more" and 5) or 10 end)
385
386local c_positive        = "trace:b"
387local c_negative        = "trace:r"
388local c_zero            = "trace:g"
389local c_text            = "trace:s"
390local c_space           = "trace:y"
391local c_space_x         = "trace:m"
392local c_skip_a          = "trace:c"
393local c_skip_b          = "trace:m"
394local c_glyph           = "trace:o"
395local c_ligature        = "trace:s"
396local c_white           = "trace:w"
397----- c_math            = "trace:s"
398----- c_origin          = "trace:o"
399----- c_discretionary   = "trace:d"
400----- c_expansion       = "trace:o"
401local c_depth           = "trace:o"
402local c_indent          = "trace:s"
403
404local c_positive_d      = "trace:db"
405local c_negative_d      = "trace:dr"
406local c_zero_d          = "trace:dg"
407local c_text_d          = "trace:ds"
408local c_space_d         = "trace:dy"
409local c_space_x_d       = "trace:dm"
410local c_skip_a_d        = "trace:dc"
411local c_skip_b_d        = "trace:dm"
412local c_glyph_d         = "trace:do"
413local c_ligature_d      = "trace:ds"
414local c_white_d         = "trace:dw"
415local c_math_d          = "trace:dr"
416local c_origin_d        = "trace:do"
417local c_discretionary_d = "trace:dd"
418----- c_expansion_d     = "trace:do"
419----- c_depth_d         = "trace:do"
420----- c_indent_d        = "trace:ds"
421
422local function sometext(str,layer,color,textcolor,lap) -- we can just paste verbatim together .. no typesteting needed
423    local text = hpack_string(str,usedfont)
424    local size = getwidth(text)
425    local rule = new_rule(size,2*exheight,exheight/2)
426    local kern = new_kern(-size)
427    if color then
428        setcolor(rule,color)
429    end
430    if textcolor then
431        setlistcolor(getlist(text),textcolor)
432    end
433    local info = setlink(rule,kern,text)
434    setlisttransparency(info,c_zero)
435    info = hpack_nodes(info)
436    local width = getwidth(info)
437    if lap then
438        info = new_hlist(setlink(new_kern(-width),info))
439    else
440        info = new_hlist(info) -- a bit overkill: double wrapped
441    end
442    if layer then
443        setattr(info,a_layer,layer)
444    end
445    return info, width
446end
447
448local function someblob(str,layer,color,textcolor,width)
449    local text = hpack_string(str,usedfont)
450    local size = getwidth(text)
451    local rule = new_rule(width,2*exheight,exheight/2)
452    local kern = new_kern(-width + (width-size)/2)
453    if color then
454        setcolor(rule,color)
455    end
456    if textcolor then
457        setlistcolor(getlist(text),textcolor)
458    end
459    local info = setlink(rule,kern,text)
460    setlisttransparency(info,c_zero)
461    info = hpack_nodes(info)
462    local width = getwidth(info)
463    info = new_hlist(info)
464    if layer then
465        setattr(info,a_layer,layer)
466    end
467    return info, width
468end
469
470local caches = setmetatableindex("table")
471
472local fontkern, italickern, marginkern, mathlistkern do
473
474    local f_cache = caches["fontkern"]
475    local i_cache = caches["italickern"]
476    local m_cache = caches["marginkern"]
477    local l_cache = caches["mathlistkern"]
478
479    local function somekern(head,current,cache,color,layer)
480        local width = getkern(current)
481        local extra = getexpansion(current)
482        local kern  = width + extra
483        local info  = cache[kern]
484        if not info then
485            local text = hpack_string(formatters[" %0.3f"](kern*pt_factor),usedfont)
486            local rule = new_rule(emwidth/fraction,6*exheight,2*exheight)
487            local list = getlist(text)
488            if kern > 0 then
489                setlistcolor(list,c_positive_d)
490            elseif kern < 0 then
491                setlistcolor(list,c_negative_d)
492            else
493                setlistcolor(list,c_zero_d)
494            end
495            setlisttransparency(list,color)
496            setcolor(rule,color)
497            settransparency(rule,color)
498            setshift(text,-5 * exheight)
499            info = new_hlist(setlink(rule,text))
500            setattr(info,a_layer,layer)
501            f_cache[kern] = info
502        end
503        head = insertnodebefore(head,current,copylist(info))
504        return head, current
505    end
506
507    fontkern = function(head,current)
508        return somekern(head,current,f_cache,c_text_d,l_fontkern)
509    end
510
511    italickern = function(head,current)
512        return somekern(head,current,i_cache,c_glyph_d,l_italic)
513    end
514
515    marginkern = function(head,current)
516        return somekern(head,current,m_cache,c_glyph_d,l_marginkern)
517    end
518
519    mathlistkern = function(head,current)
520        return somekern(head,current,l_cache,c_glyph_d,l_mathlistkern)
521    end
522
523end
524
525local glyphexpansion do
526
527    local f_cache = caches["glyphexpansion"]
528
529    glyphexpansion = function(head,current)
530        local extra = getexpansion(current)
531        if extra and extra ~= 0 then
532            extra = extra / 1000
533            local info = f_cache[extra]
534            if not info then
535                local text = hpack_string(round(extra),usedfont)
536                local rule = new_rule(emwidth/fraction,exheight,2*exheight)
537                local list = getlist(text)
538                if extra > 0 then
539                    setlistcolor(list,c_positive_d)
540                elseif extra < 0 then
541                    setlistcolor(list,c_negative_d)
542                end
543                setlisttransparency(list,c_text_d)
544                setcolor(rule,c_text_d)
545                settransparency(rule,c_text_d)
546                setshift(text,1.5 * exheight)
547                info = new_hlist(setlink(rule,text))
548                setattr(info,a_layer,l_expansion)
549                f_cache[extra] = info
550            end
551            head = insertnodebefore(head,current,copylist(info))
552            return head, current
553        end
554        return head, current
555    end
556
557end
558
559local kernexpansion do
560
561    local f_cache = caches["kernexpansion"]
562
563    -- in mkiv we actually need to reconstruct but let's not do that now
564
565    kernexpansion = function(head,current)
566        local extra = getexpansion(current)
567        if extra ~= 0 then
568            extra = extra / 1000
569            local info = f_cache[extra]
570            if not info then
571                local text = hpack_string(round(extra),usedfont)
572                local rule = new_rule(emwidth/fraction,exheight,4*exheight)
573                local list = getlist(text)
574                if extra > 0 then
575                    setlistcolor(list,c_positive_d)
576                elseif extra < 0 then
577                    setlistcolor(list,c_negative_d)
578                end
579                setlisttransparency(list,c_text_d)
580                setcolor(rule,c_text_d)
581                settransparency(rule,c_text_d)
582                setshift(text,3.5 * exheight)
583                info = new_hlist(setlink(rule,text))
584                setattr(info,a_layer,l_expansion)
585                f_cache[extra] = info
586            end
587            head = insertnodebefore(head,current,copylist(info))
588            return head, current
589        end
590        return head, current
591    end
592
593end
594
595local whatsit do
596
597    local whatsitcodes = nodes.whatsitcodes
598    local w_cache      = caches["whatsit"]
599
600    local tags         = {
601        open        = "OPN",
602        write       = "WRI",
603        close       = "CLS",
604        special     = "SPE",
605        latelua     = "LUA",
606        savepos     = "POS",
607        userdefined = "USR",
608        literal     = "LIT",
609        setmatrix   = "MAT",
610        save        = "SAV",
611        restore     = "RES",
612    }
613
614    whatsit = function(head,current)
615        local what = getsubtype(current)
616        local info = w_cache[what]
617        if info then
618            -- print("hit whatsit")
619        else
620            info = sometext(formatters["W:%s"](what),usedfont,nil,c_white)
621            setattr(info,a_layer,l_whatsit)
622            w_cache[what] = info
623        end
624        head, current = insertnodeafter(head,current,copylist(info))
625        return head, current
626    end
627
628end
629
630local dir, par do
631
632    local dircodes    = nodes.dircodes
633    local dirvalues   = nodes.dirvalues
634
635    local cancel_code = dircodes.cancel
636    local l2r_code    = dirvalues.l2r
637    local r2l_code    = dirvalues.r2l
638
639    local d_cache     = caches["dir"]
640
641    local tags = {
642        l2r    = "L2R",
643        r2l    = "R2L",
644        cancel = "CAN",
645        par    = "PAR",
646    }
647
648    par = function(head,current)
649        local what = "par" -- getsubtype(current)
650        local info = d_cache[what]
651        if info then
652            -- print("hit par")
653        else
654            info = sometext(formatters["L:%s"](what),usedfont,nil,c_white)
655            setattr(info,a_layer,l_dir)
656            d_cache[what] = info
657        end
658        return head, current
659    end
660
661    dir = function(head,current)
662        local what = getsubtype(current)
663        if what == cancelcode then
664            what = "cancel"
665        elseif getdirection(current) == r2l_code then
666            what = "r2l"
667        else
668            what = "l2r"
669        end
670        local info = d_cache[what]
671        if info then
672            -- print("hit dir")
673        else
674            info = sometext(formatters["D:%s"](what),usedfont,nil,c_white)
675            setattr(info,a_layer,l_dir)
676            d_cache[what] = info
677        end
678        return head, current
679    end
680
681end
682
683local user do
684
685    local u_cache = caches["user"]
686
687    user = function(head,current)
688        local what = getsubtype(current)
689        local info = u_cache[what]
690        if info then
691            -- print("hit user")
692        else
693            info = sometext(formatters["U:%s"](what),usedfont)
694            setattr(info,a_layer,l_user)
695            u_cache[what] = info
696        end
697        head, current = insertnodeafter(head,current,copylist(info))
698        return head, current
699    end
700
701end
702
703local math do
704
705    local mathcodes = nodes.mathcodes
706    local m_cache   = {
707        beginmath = caches["bmath"],
708        endmath   = caches["emath"],
709    }
710    local tags      = {
711        beginmath = "B",
712        endmath   = "E",
713    }
714
715    math = function(head,current)
716        local what = getsubtype(current)
717        local tag  = mathcodes[what]
718        local skip = getkern(current) + getwidth(current) -- surround
719        local info = m_cache[tag][skip]
720        if info then
721            -- print("hit math")
722        else
723            local text, width = sometext(formatters["M:%s"](tag and tags[tag] or what),usedfont,nil,c_math_d)
724            local rule = new_rule(skip,-655360/fraction,2*655360/fraction)
725            setcolor(rule,c_math_d)
726            settransparency(rule,c_math_d)
727            setattr(rule,a_layer,l_math)
728            if tag == "beginmath" then
729                info = new_hlist(setlink(new_glue(-skip),rule,new_glue(-width),text))
730            else
731                info = new_hlist(setlink(new_glue(-skip),rule,new_glue(-skip),text))
732            end
733            setattr(info,a_layer,l_math)
734            m_cache[tag][skip] = info
735        end
736        head, current = insertnodeafter(head,current,copylist(info))
737        return head, current
738    end
739
740end
741
742local ruleddepth do
743
744    ruleddepth = function(current,wd,ht,dp)
745        local wd, ht, dp = getwhd(current)
746        if dp ~= 0 then
747            local rule = new_rule(wd,0,dp)
748            setcolor(rule,c_depth)
749            settransparency(rule,c_zero)
750            setattr(rule,a_layer,l_depth)
751            setlist(current,setlink(rule,new_kern(-wd),getlist(current)))
752        end
753    end
754
755end
756
757local ruledbox do
758
759    local b_cache = caches["box"]
760    local o_cache = caches["origin"]
761
762    setmetatableindex(o_cache,function(t,size)
763        local rule   = new_rule(2*size,size,size)
764        local origin = hpack_nodes(rule)
765        setcolor(rule,c_origin_d)
766        settransparency(rule,c_origin_d)
767        setattr(rule,a_layer,l_origin)
768        t[size] = origin
769        return origin
770    end)
771
772    ruledbox = function(head,current,vertical,layer,what,simple,previous,trace_origin,parent)
773        local wd, ht, dp = getwhd(current)
774        if wd ~= 0 then
775            local shift = getshift(current)
776            local next = getnext(current)
777            local prev = previous
778            setboth(current)
779            local linewidth = emwidth/fraction
780            local size      = 2*linewidth
781            local this
782            if not simple then
783                this = b_cache[what]
784                if not this then
785                    local text = hpack_string(what,usedfont)
786                    this = setlink(new_kern(-getwidth(text)),text)
787                    setlisttransparency(this,c_text)
788                    this = new_hlist(this)
789                    b_cache[what] = this
790                end
791            end
792            -- we need to trigger the right mode (else sometimes no whatits)
793            local info = setlink(
794                this and copylist(this) or nil,
795                (dp == 0 and outlinerule and outlinerule(wd,ht,dp,linewidth)) or userrule {
796                    width  = wd,
797                    height = ht,
798                    depth  = dp,
799                    line   = linewidth,
800                    type   = "box",
801                    dashed = 3*size,
802                }
803            )
804            --
805            setlisttransparency(info,c_text)
806            info = new_hlist(info) -- important
807            --
808            setattr(info,a_layer,layer)
809            if vertical then
810                if shift == 0 then
811                    info = setlink(current,dp ~= 0 and new_kern(-dp) or nil,info)
812                elseif trace_origin then
813                    local size   = 2*size
814                    local origin = o_cache[size]
815                    origin = copylist(origin)
816                    if getid(parent) == vlist_code then
817                        setshift(origin,-shift)
818                        info = setlink(current,new_kern(-size),origin,new_kern(-size-dp),info)
819                    else
820                        -- todo .. i need an example
821                        info = setlink(current,dp ~= 0 and new_kern(-dp) or nil,info)
822                    end
823                    setshift(current,0)
824                else
825                    info = setlink(current,new_dp ~= 0 and new_kern(-dp) or nil,info)
826                    setshift(current,0)
827                end
828                info = new_vlist(info,wd,ht,dp,shift)
829            else
830                if shift == 0 then
831                    info = setlink(current,new_kern(-wd),info)
832                elseif trace_origin then
833                    local size   = 2*size
834                    local origin = o_cache[size]
835                    origin = copylist(origin)
836                    if getid(parent) == vlist_code then
837                        info = setlink(current,new_kern(-wd-size-shift),origin,new_kern(-size+shift),info)
838                    else
839                        setshift(origin,-shift)
840                        info = setlink(current,new_kern(-wd-size),origin,new_kern(-size),info)
841                    end
842                    setshift(current,0)
843                else
844                    info = setlink(current,new_kern(-wd),info)
845                    setshift(current,0)
846                end
847                info = new_hlist(info,wd,ht,dp,shift)
848            end
849            if next then
850                setlink(info,next)
851            end
852            if prev and prev > 0 then
853                setlink(prev,info)
854            end
855            if head == current then
856                return info, info
857            else
858                return head, info
859            end
860        else
861            return head, current
862        end
863    end
864
865end
866
867local ruledglyph do
868
869    -- see boundingbox feature .. maybe a pdf stream is more efficient, after all we
870    -- have a frozen color anyway or i need a more detailed cache .. below is a more
871    -- texie approach
872
873    ruledglyph = function(head,current,previous) -- wrong for vertical glyphs
874        local wd = getwidth(current)
875        if wd ~= 0 then
876            local wd, ht, dp = getwhd(current)
877            local next = getnext(current)
878            local prev = previous
879            setboth(current)
880            local linewidth = emwidth/(2*fraction)
881            local info
882            --
883            info = setlink(
884                (dp == 0 and outlinerule and outlinerule(wd,ht,dp,linewidth)) or userrule {
885                    width  = wd,
886                    height = ht,
887                    depth  = dp,
888                    line   = linewidth,
889                    type   = "box",
890                },
891                new_kern(-wd)
892            )
893            --
894            local c, f = isglyph(current)
895            local char = chardata[f][c]
896            if char and type(char.unicode) == "table" then -- hackery test
897                setlistcolor(info,c_ligature)
898                setlisttransparency(info,c_ligature_d)
899            else
900                setlistcolor(info,c_glyph)
901                setlisttransparency(info,c_glyph_d)
902            end
903            info = new_hlist(info)
904            setattr(info,a_layer,l_glyph)
905            local info = setlink(current,new_kern(-wd),info)
906            info = hpack_nodes(info)
907            setwidth(info,wd)
908            if next then
909                setlink(info,next)
910            end
911            if prev then
912                setlink(prev,info)
913            end
914            if head == current then
915                return info, info
916            else
917                return head, info
918            end
919        else
920            return head, current
921        end
922    end
923
924    function visualizers.setruledglyph(f)
925        ruledglyph = f or ruledglyph
926    end
927
928end
929
930local ruledglue do
931
932    local gluecodes             = nodes.gluecodes
933
934    local userskip_code         = gluecodes.userskip
935    local spaceskip_code        = gluecodes.spaceskip
936    local xspaceskip_code       = gluecodes.xspaceskip
937    local zerospaceskip_code    = gluecodes.zerospaceskip or gluecodes.userskip
938 -- local keepskip_code         = gluecodes.keepskip or gluecodes.userskip
939    local leftskip_code         = gluecodes.leftskip
940    local rightskip_code        = gluecodes.rightskip
941    local parfillleftskip_code  = gluecodes.parfillleftskip or parfillskip_code
942    local parfillrightskip_code = gluecodes.parfillrightskip or parfillskip_code
943    local indentskip_code       = gluecodes.indentskip
944    local correctionskip_code   = gluecodes.correctionskip
945
946    local g_cache_v = caches["vglue"]
947    local g_cache_h = caches["hglue"]
948
949    local tags = {
950     -- [userskip_code]                   = "US",
951        [gluecodes.lineskip]              = "LI",
952        [gluecodes.baselineskip]          = "BS",
953        [gluecodes.parskip]               = "PS",
954        [gluecodes.abovedisplayskip]      = "DA",
955        [gluecodes.belowdisplayskip]      = "DB",
956        [gluecodes.abovedisplayshortskip] = "SA",
957        [gluecodes.belowdisplayshortskip] = "SB",
958        [gluecodes.topskip]               = "TS",
959        [gluecodes.splittopskip]          = "ST",
960        [gluecodes.tabskip]               = "AS",
961        [gluecodes.lefthangskip]          = "LH",
962        [gluecodes.righthangskip]         = "RH",
963        [gluecodes.thinmuskip]            = "MS",
964        [gluecodes.medmuskip]             = "MM",
965        [gluecodes.thickmuskip]           = "ML",
966        [gluecodes.intermathskip]         = "IM",
967        [gluecodes.keepskip or 99]        = "KS",
968        [gluecodes.mathskip]              = "MT",
969        [gluecodes.leaders]               = "NL",
970        [gluecodes.cleaders]              = "CL",
971        [gluecodes.xleaders]              = "XL",
972        [gluecodes.gleaders]              = "GL",
973     -- true                              = "VS",
974     -- false                             = "HS",
975        [leftskip_code]                   = "LS",
976        [rightskip_code]                  = "RS",
977        [spaceskip_code]                  = "SP",
978        [xspaceskip_code]                 = "XS",
979        [zerospaceskip_code]              = "ZS",
980        [parfillleftskip_code]            = "PL",
981        [parfillrightskip_code]           = "PR",
982        [indentskip_code]                 = "IN",
983        [correctionskip_code]             = "CS",
984    }
985
986    -- we sometimes pass previous as we can have issues in math (not watertight for all)
987
988    ruledglue = function(head,current,vertical,parent)
989        local subtype = getsubtype(current)
990        local width   = effectiveglue(current,parent)
991        local amount  = formatters["%s:%0.3f"](tags[subtype] or (vertical and "VS") or "HS",width*pt_factor)
992        local info    = (vertical and g_cache_v or g_cache_h)[amount]
993        if info then
994            -- print("glue hit")
995        else
996            if subtype == spaceskip_code or subtype == xspaceskip_code or subtype == zerospaceskip_code then
997                info = sometext(amount,l_glue,c_space)
998            elseif subtype == leftskip_code or subtype == rightskip_code then
999                info = sometext(amount,l_glue,c_skip_a)
1000            elseif subtype == parfillleftskip_code or subtype == parfillrightskip_code or subtype == indentskip_code or subtype == correctionskip_code then
1001                info = sometext(amount,l_glue,c_indent)
1002            elseif subtype == userskip_code then
1003                if width > 0 then
1004                    info = sometext(amount,l_glue,c_positive)
1005                elseif width < 0 then
1006                    info = sometext(amount,l_glue,c_negative)
1007                else
1008                    info = sometext(amount,l_glue,c_zero)
1009                end
1010            else
1011                info = sometext(amount,l_glue,c_skip_b)
1012            end
1013            (vertical and g_cache_v or g_cache_h)[amount] = info
1014        end
1015        info = copylist(info)
1016        if vertical then
1017            info = vpack_nodes(info)
1018        end
1019        head, current = insertnodebefore(head,current,info)
1020        return head, getnext(current)
1021    end
1022
1023 -- ruledspace = function(head,current,parent)
1024 --     local subtype = getsubtype(current)
1025 --     if subtype == spaceskip_code or subtype == xspaceskip_code or subtype == zerospaceskip_code then
1026 --         local width  = effectiveglue(current,parent)
1027 --         local amount = formatters["%s:%0.3f"](tags[subtype] or "HS",width*pt_factor)
1028 --         local info   = g_cache_h[amount]
1029 --         if info then
1030 --             -- print("space hit")
1031 --         else
1032 --             info = sometext(amount,l_glue,c_space)
1033 --             g_cache_h[amount] = info
1034 --         end
1035 --         info = copylist(info)
1036 --         head, current = insertnodebefore(head,current,info)
1037 --         return head, getnext(current)
1038 --     else
1039 --         return head, current
1040 --     end
1041 -- end
1042
1043    local g_cache_s = caches["space"]
1044    local g_cache_x = caches["xspace"]
1045
1046    ruledspace = function(head,current,parent)
1047        local subtype = getsubtype(current)
1048        if subtype == spaceskip_code or subtype == xspaceskip_code or subtype == zerospaceskip_code then -- not yet all space
1049            local width = effectiveglue(current,parent)
1050            local info
1051            if subtype == spaceskip_code then
1052                info = g_cache_s[width]
1053                if not info then
1054                    info = someblob("SP",l_glue,c_space,nil,width)
1055                    g_cache_s[width] = info
1056                end
1057            else
1058                info = g_cache_x[width]
1059                if not info then
1060                    info = someblob("XS",l_glue,c_space_x,nil,width)
1061                    g_cache_x[width] = info
1062                end
1063            end
1064            info = copylist(info)
1065            head, current = insertnodebefore(head,current,info)
1066            return head, getnext(current)
1067        else
1068            return head, current
1069        end
1070    end
1071
1072end
1073
1074local ruledkern do
1075
1076    local k_cache_v = caches["vkern"]
1077    local k_cache_h = caches["hkern"]
1078
1079    ruledkern = function(head,current,vertical,mk)
1080        local kern  = getkern(current)
1081        local cache = vertical and k_cache_v or k_cache_h
1082        local info  = cache[kern]
1083        if not info then
1084            local amount = formatters["%s:%0.3f"](vertical and "VK" or (mk and "MK") or "HK",kern*pt_factor)
1085            if kern > 0 then
1086                info = sometext(amount,l_kern,c_positive)
1087            elseif kern < 0 then
1088                info = sometext(amount,l_kern,c_negative)
1089            else
1090                info = sometext(amount,l_kern,c_zero)
1091            end
1092            cache[kern] = info
1093        end
1094        info = copylist(info)
1095        if vertical then
1096            info = vpack_nodes(info)
1097        end
1098        head, current = insertnodebefore(head,current,info)
1099        return head, getnext(current)
1100    end
1101
1102end
1103
1104local ruleditalic do
1105
1106    local i_cache = caches["italic"]
1107
1108    ruleditalic = function(head,current)
1109        local kern = getkern(current)
1110        local info = i_cache[kern]
1111        if not info then
1112            local amount = formatters["%s:%0.3f"]("IC",kern*pt_factor)
1113            if kern > 0 then
1114                info = sometext(amount,l_kern,c_positive)
1115            elseif kern < 0 then
1116                info = sometext(amount,l_kern,c_negative)
1117            else
1118                info = sometext(amount,l_kern,c_zero)
1119            end
1120            i_cache[kern] = info
1121        end
1122        info = copylist(info)
1123        head, current = insertnodebefore(head,current,info)
1124        return head, getnext(current)
1125    end
1126
1127end
1128
1129local ruledmarginkern do
1130
1131    local m_cache = caches["marginkern"]
1132
1133    ruledmarginkern = function(head,current)
1134        local kern = getkern(current)
1135        local info = m_cache[kern]
1136        if not info then
1137            local amount = formatters["%s:%0.3f"]("MK",kern*pt_factor)
1138            if kern > 0 then
1139                info = sometext(amount,l_marginkern,c_positive)
1140            elseif kern < 0 then
1141                info = sometext(amount,l_marginkern,c_negative)
1142            else
1143                info = sometext(amount,l_marginkern,c_zero)
1144            end
1145            m_cache[kern] = info
1146        end
1147        info = copylist(info)
1148        head, current = insertnodebefore(head,current,info)
1149        return head, getnext(current)
1150    end
1151
1152end
1153
1154local ruledmathlistkern do
1155
1156    local l_cache = caches["mathlistkern"]
1157
1158    ruledmathlistkern = function(head,current)
1159        local kern = getkern(current)
1160        local info = l_cache[kern]
1161        if not info then
1162            local amount = formatters["%s:%0.3f"]("LK",kern*pt_factor)
1163            if kern > 0 then
1164                info = sometext(amount,l_mathlistkern,c_positive)
1165            elseif kern < 0 then
1166                info = sometext(amount,l_mathlistkern,c_negative)
1167            else
1168                info = sometext(amount,l_mathlistkern,c_zero)
1169            end
1170            l_cache[kern] = info
1171        end
1172        info = copylist(info)
1173        head, current = insertnodebefore(head,current,info)
1174        return head, getnext(current)
1175    end
1176
1177end
1178
1179local ruleddiscretionary do
1180
1181    local d_cache = caches["discretionary"]
1182
1183    ruleddiscretionary = function(head,current)
1184        local d = d_cache[true]
1185        if not the_discretionary then
1186            local rule = new_rule(4*emwidth/fraction,4*exheight,exheight)
1187            local kern = new_kern(-2*emwidth/fraction)
1188            setlink(kern,rule)
1189            setcolor(rule,c_discretionary_d)
1190            settransparency(rule,c_discretionary_d)
1191            setattr(rule,a_layer,l_discretionary)
1192            d = new_hlist(kern)
1193            d_cache[true] = d
1194        end
1195        insertnodeafter(head,current,copylist(d))
1196        return head, current
1197    end
1198
1199end
1200
1201local ruledpenalty do
1202
1203    local p_cache_v = caches["vpenalty"]
1204    local p_cache_h = caches["hpenalty"]
1205
1206    local raisepenalties = false
1207
1208    directives.register("visualizers.raisepenalties",function(v) raisepenalties = v end)
1209
1210    ruledpenalty = function(head,current,vertical)
1211        local penalty = getpenalty(current)
1212        local info = (vertical and p_cache_v or p_cache_h)[penalty]
1213        if info then
1214            -- print("penalty hit")
1215        else
1216            local amount = formatters["%s:%s"](vertical and "VP" or "HP",penalty)
1217            if penalty > 0 then
1218                info = sometext(amount,l_penalty,c_positive)
1219            elseif penalty < 0 then
1220                info = sometext(amount,l_penalty,c_negative)
1221            else
1222                info = sometext(amount,l_penalty,c_zero)
1223            end
1224            (vertical and p_cache_v or p_cache_h)[penalty] = info
1225        end
1226        info = copylist(info)
1227        if vertical then
1228            info = vpack_nodes(info)
1229        elseif raisepenalties then
1230            setshift(info,-65536*4)
1231        end
1232        head, current = insertnodebefore(head,current,info)
1233        return head, getnext(current)
1234    end
1235
1236end
1237
1238do
1239
1240    local disc_code            = nodecodes.disc
1241    local kern_code            = nodecodes.kern
1242    local glyph_code           = nodecodes.glyph
1243    local glue_code            = nodecodes.glue
1244    local penalty_code         = nodecodes.penalty
1245    local whatsit_code         = nodecodes.whatsit
1246    local user_code            = nodecodes.user
1247    local math_code            = nodecodes.math
1248    local hlist_code           = nodecodes.hlist
1249    local vlist_code           = nodecodes.vlist
1250    local marginkern_code      = nodecodes.marginkern
1251    local mathlistkern_code    = nodecodes.mathlistkern
1252    local dir_code             = nodecodes.dir
1253    local par_code             = nodecodes.par
1254
1255    local kerncodes            = nodes.kerncodes
1256    local fontkern_code        = kerncodes.fontkern
1257    local italickern_code      = kerncodes.italiccorrection
1258    local leftmarginkern_code  = kerncodes.leftmarginkern
1259    local rightmarginkern_code = kerncodes.rightmarginkern
1260    local mathlistkern_code    = kerncodes.mathlistkern
1261    ----- userkern_code        = kerncodes.userkern
1262
1263    local listcodes            = nodes.listcodes
1264    local linelist_code        = listcodes.line
1265
1266    local cache
1267
1268    local function visualize(head,vertical,forced,parent)
1269        local trace_hbox            = false
1270        local trace_vbox            = false
1271        local trace_vtop            = false
1272        local trace_kern            = false
1273        local trace_glue            = false
1274        local trace_penalty         = false
1275        local trace_fontkern        = false
1276        local trace_strut           = false
1277        local trace_whatsit         = false
1278        local trace_glyph           = false
1279        local trace_simple          = false
1280        local trace_user            = false
1281        local trace_math            = false
1282        local trace_italic          = false
1283        local trace_origin          = false
1284        local trace_discretionary   = false
1285        local trace_expansion       = false
1286        local trace_line            = false
1287        local trace_space           = false
1288        local trace_depth           = false
1289        local trace_dir             = false
1290        local trace_par             = false
1291        local current               = head
1292        local previous              = nil
1293        local attr                  = unsetvalue
1294        local prev_trace_fontkern   = nil
1295        local prev_trace_italic     = nil
1296        local prev_trace_marginkern = nil
1297--         local prev_trace_mathlist   = nil
1298        local prev_trace_expansion  = nil
1299
1300        while current do
1301            local id = getid(current)
1302            local a = forced or getattr(current,a_visual) or unsetvalue
1303            local subtype
1304            if a ~= attr then
1305                prev_trace_fontkern     = trace_fontkern
1306                prev_trace_italic       = trace_italic
1307                prev_trace_marginkern   = trace_marginkern
1308--                 prev_trace_mathlistkern = trace_mathlistkern
1309                prev_trace_expansion    = trace_expansion
1310                attr = a
1311                if a == unsetvalue then
1312                    trace_hbox          = false
1313                    trace_vbox          = false
1314                    trace_vtop          = false
1315                    trace_kern          = false
1316                    trace_glue          = false
1317                    trace_penalty       = false
1318                    trace_fontkern      = false
1319                    trace_strut         = false
1320                    trace_whatsit       = false
1321                    trace_glyph         = false
1322                    trace_simple        = false
1323                    trace_user          = false
1324                    trace_math          = false
1325                    trace_italic        = false
1326                    trace_origin        = false
1327                    trace_discretionary = false
1328                    trace_expansion     = false
1329                    trace_line          = false
1330                    trace_space         = false
1331                    trace_depth         = false
1332                    trace_marginkern    = false
1333                    trace_mathlistkern  = false
1334                    trace_dir           = false
1335                    trace_par           = false
1336                    if id == kern_code then
1337                        goto kern
1338                    else
1339                        goto list
1340                    end
1341                else -- dead slow:
1342                 -- cache[a]()
1343                    trace_hbox          = band(a,0x000001) ~= 0
1344                    trace_vbox          = band(a,0x000002) ~= 0
1345                    trace_vtop          = band(a,0x000004) ~= 0
1346                    trace_kern          = band(a,0x000008) ~= 0
1347                    trace_glue          = band(a,0x000010) ~= 0
1348                    trace_penalty       = band(a,0x000020) ~= 0
1349                    trace_fontkern      = band(a,0x000040) ~= 0
1350                    trace_strut         = band(a,0x000080) ~= 0
1351                    trace_whatsit       = band(a,0x000100) ~= 0
1352                    trace_glyph         = band(a,0x000200) ~= 0
1353                    trace_simple        = band(a,0x000400) ~= 0
1354                    trace_user          = band(a,0x000800) ~= 0
1355                    trace_math          = band(a,0x001000) ~= 0
1356                    trace_italic        = band(a,0x002000) ~= 0
1357                    trace_origin        = band(a,0x004000) ~= 0
1358                    trace_discretionary = band(a,0x008000) ~= 0
1359                    trace_expansion     = band(a,0x010000) ~= 0
1360                    trace_line          = band(a,0x020000) ~= 0
1361                    trace_space         = band(a,0x040000) ~= 0
1362                    trace_depth         = band(a,0x080000) ~= 0
1363                    trace_marginkern    = band(a,0x100000) ~= 0
1364                    trace_mathlistkern  = band(a,0x200000) ~= 0
1365                    trace_dir           = band(a,0x400000) ~= 0
1366                    trace_whatsit       = band(a,0x800000) ~= 0
1367                end
1368            elseif a == unsetvalue then
1369                goto list
1370            end
1371            if trace_strut then
1372                setattr(current,a_layer,l_strut)
1373            elseif id == glyph_code then
1374                if trace_glyph then
1375                    head, current = ruledglyph(head,current,previous)
1376                end
1377                if trace_expansion then
1378                    head, current = glyphexpansion(head,current)
1379                end
1380            elseif id == disc_code then
1381                if trace_discretionary then
1382                    head, current = ruleddiscretionary(head,current)
1383                end
1384                local pre, post, replace = getdisc(current)
1385                if pre then
1386                    pre = visualize(pre,false,a,parent)
1387                end
1388                if post then
1389                    post = visualize(post,false,a,parent)
1390                end
1391                if replace then
1392                    replace = visualize(replace,false,a,parent)
1393                end
1394                setdisc(current,pre,post,replace)
1395            elseif id == kern_code then
1396                goto kern
1397            elseif id == glue_code then
1398                local content = getleader(current)
1399                if content then
1400                    setleader(current,visualize(content,false,nil,parent))
1401                elseif trace_glue then
1402                    head, current = ruledglue(head,current,vertical,parent)
1403                elseif trace_space then
1404                    head, current = ruledspace(head,current,parent)
1405                end
1406            elseif id == penalty_code then
1407                if trace_penalty then
1408                    head, current = ruledpenalty(head,current,vertical)
1409                end
1410            elseif id == hlist_code or id == vlist_code then
1411                goto list
1412            elseif id == whatsit_code then
1413                if trace_whatsit then
1414                    head, current = whatsit(head,current)
1415                end
1416            elseif id == user_code then
1417                if trace_user then
1418                    head, current = user(head,current)
1419                end
1420            elseif id == math_code then
1421                if trace_math then
1422                    head, current = math(head,current)
1423                end
1424            elseif id == marginkern_code then
1425                if trace_kern then
1426                    head, current = ruledkern(head,current,vertical,true)
1427                end
1428            elseif id == dir_code then
1429                if trace_dir then
1430                    head, current = dir(head,current)
1431                end
1432            elseif id == par_code then
1433                if trace_par then
1434                    head, current = par(head,current)
1435                end
1436            end
1437            goto next
1438            ::kern::
1439            subtype = getsubtype(current)
1440            if subtype == fontkern_code then
1441                if trace_fontkern or prev_trace_fontkern then
1442                    head, current = fontkern(head,current)
1443                end
1444                if trace_expansion or prev_trace_expansion then
1445                    head, current = kernexpansion(head,current)
1446                end
1447            elseif subtype == italickern_code then
1448                if trace_italic or prev_trace_italic then
1449                    head, current = italickern(head,current)
1450                elseif trace_kern then
1451                    head, current = ruleditalic(head,current)
1452                end
1453            elseif subtype == leftmarginkern_code or subtype == rightmarginkern_code then
1454                if trace_marginkern or prev_trace_marginkern then
1455                    head, current = marginkern(head,current)
1456                elseif trace_kern then
1457                    head, current = ruledmarginkern(head,current)
1458                end
1459            elseif subtype == mathlistkern_code then
1460                if trace_mathlist then -- or prev_trace_mathlist then
1461                    head, current = mathlistkern(head,current)
1462                elseif trace_kern then
1463                    head, current = ruledmathlistkern(head,current)
1464                end
1465            else
1466                if trace_kern then
1467                    head, current = ruledkern(head,current,vertical)
1468                end
1469            end
1470            goto next;
1471            ::list::
1472            if id == hlist_code then
1473                local content = getlist(current)
1474                if content then
1475                    setlist(current,visualize(content,false,nil,current))
1476                end
1477                if trace_depth then
1478                    ruleddepth(current)
1479                end
1480                if trace_line and getsubtype(current) == linelist_code then
1481                    head, current = ruledbox(head,current,false,l_line,"L__",trace_simple,previous,trace_origin,parent)
1482                elseif trace_hbox then
1483                    head, current = ruledbox(head,current,false,l_hbox,"H__",trace_simple,previous,trace_origin,parent)
1484                end
1485            elseif id == vlist_code then
1486                local content = getlist(current)
1487                if content then
1488                    setlist(current,visualize(content,true,nil,current))
1489                end
1490                if trace_vtop then
1491                    head, current = ruledbox(head,current,true,l_vtop,"_T_",trace_simple,previous,trace_origin,parent)
1492                elseif trace_vbox then
1493                    head, current = ruledbox(head,current,true,l_vbox,"__V",trace_simple,previous,trace_origin,parent)
1494                end
1495            end
1496            ::next::
1497            previous = current
1498            current  = getnext(current)
1499        end
1500        return head
1501    end
1502
1503    local function cleanup()
1504        for tag, cache in next, caches do
1505            for k, v in next, cache do
1506                flushnodelist(v)
1507            end
1508        end
1509        cleanup = function()
1510            report_visualize("error, duplicate cleanup")
1511        end
1512    end
1513
1514    luatex.registerstopactions(cleanup)
1515
1516    function visualizers.handler(head)
1517        if usedfont then
1518            starttiming(visualizers)
1519            head = visualize(head,true)
1520            stoptiming(visualizers)
1521            return head, true
1522        else
1523            return head, false
1524        end
1525    end
1526
1527    function visualizers.box(n)
1528        if usedfont then
1529            starttiming(visualizers)
1530            local box = getbox(n)
1531            if box then
1532                setlist(box,visualize(getlist(box),getid(box) == vlist_code))
1533            end
1534            stoptiming(visualizers)
1535            return head, true
1536        else
1537            return head, false
1538        end
1539    end
1540
1541end
1542
1543do
1544
1545    local hlist_code = nodecodes.hlist
1546    local vlist_code = nodecodes.vlist
1547    local nextnode   = nuts.traversers.node
1548
1549    local last       = nil
1550    local used       = nil
1551
1552    local mark       = {
1553        "trace:1", "trace:2", "trace:3",
1554        "trace:4", "trace:5", "trace:6",
1555        "trace:7",
1556    }
1557
1558    local function markfonts(list)
1559        for n, id in nextnode, list do
1560            if id == glyph_code then
1561                local font = getfont(n)
1562                local okay = used[font]
1563                if not okay then
1564                    last = last + 1
1565                    okay = mark[last]
1566                    used[font] = okay
1567                end
1568                setcolor(n,okay)
1569            elseif id == hlist_code or id == vlist_code then
1570                markfonts(getlist(n))
1571            end
1572        end
1573    end
1574
1575    function visualizers.markfonts(list)
1576        last, used = 0, { }
1577        markfonts(type(n) == "number" and getlist(getbox(n)) or n)
1578    end
1579
1580end
1581
1582statistics.register("visualization time",function()
1583    if enabled then
1584     -- cleanup() -- in case we don't don't do it each time
1585        return formatters["%s seconds"](statistics.elapsedtime(visualizers))
1586    end
1587end)
1588
1589-- interface
1590
1591do
1592
1593    local implement = interfaces.implement
1594
1595    implement {
1596        name      = "setvisual",
1597        arguments = "string",
1598        actions   = visualizers.setvisual
1599    }
1600
1601    implement {
1602        name      = "setvisuals",
1603        arguments = "string",
1604        actions   = visualizers.setvisual
1605    }
1606
1607    implement {
1608        name      = "getvisual",
1609        arguments = "string",
1610        actions   = { setvisual, context }
1611    }
1612
1613        implement {
1614        name      = "setvisuallayer",
1615        arguments = "string",
1616        actions   = visualizers.setlayer
1617    }
1618
1619    implement {
1620        name      = "markvisualfonts",
1621        arguments = "integer",
1622        actions   = visualizers.markfonts
1623    }
1624
1625    implement {
1626        name      = "setvisualfont",
1627        arguments = "integer",
1628        actions   = visualizers.setfont
1629    }
1630
1631end
1632
1633-- Here for now:
1634
1635do
1636
1637    local function make(str,forecolor,rulecolor,layer)
1638        if initialize then
1639            initialize()
1640        end
1641        local rule = new_rule(emwidth/fraction,exheight,4*exheight)
1642        setcolor(rule,rulecolor)
1643        settransparency(rule,rulecolor)
1644        local info
1645        if str == "" then
1646            info = new_hlist(rule)
1647        else
1648            local text = hpack_string(str,usedfont)
1649            local list = getlist(text)
1650            setlistcolor(list,textcolor)
1651            setlisttransparency(list,textcolor)
1652            setshift(text,3.5 * exheight)
1653            info = new_hlist(setlink(rule,text))
1654        end
1655        setattr(info,a_layer,layer)
1656        return info
1657    end
1658
1659    function visualizers.register(name,textcolor,rulecolor)
1660        if rawget(layers,name) then
1661            -- message
1662            return
1663        end
1664        local cache = caches[name]
1665        local layer = layers[name]
1666        if not textcolor then
1667            textcolor = c_text_d
1668        end
1669        if not rulecolor then
1670            rulecolor = c_origin_d
1671        end
1672        return function(str)
1673            if not str then
1674                str = ""
1675            end
1676            local info = cache[str]
1677            if not info then
1678                info = make(str,textcolor,rulecolor,layer)
1679                cache[str] = info
1680            end
1681            return copy_node(info)
1682        end
1683    end
1684
1685end
1686