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