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