if not modules then modules = { } end modules ['trac-vis'] = {
    version   = 1.001,
    optimize  = true,
    comment   = "companion to trac-vis.mkiv",
    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
    copyright = "PRAGMA ADE / ConTeXt Development Team",
    license   = "see context related readme files"
}

-- We no longer have 'user' so we can use that flag.

local node, nodes, attributes, tex = node, nodes, attributes, tex
local type, tonumber, next, rawget = type, tonumber, next, rawget
local gmatch = string.gmatch
local formatters = string.formatters
local round = math.round

-- This module started out in the early days of mkiv and luatex with visualizing
-- kerns related to fonts. In the process of cleaning up the visual debugger code it
-- made sense to integrate some other code that I had laying around and replace the
-- old supp-vis debugging code. As only a subset of the old visual debugger makes
-- sense it has become a different implementation. Soms of the m-visual
-- functionality will also be ported. The code is rather trivial. The caching is not
-- really needed but saves upto 50% of the time needed to add visualization. Of
-- course the overall runtime is larger because of color and layer processing in the
-- backend (can be times as much) so the runtime is somewhat larger with full
-- visualization enabled. In practice this will never happen unless one is demoing.

-- todo: global switch (so no attributes)
-- todo: maybe also xoffset, yoffset of glyph
-- todo: inline concat (more efficient)
-- todo: tags can also be numbers (just add to hash)
-- todo: make a lmtx variant (a few more efficient fetchers)

local nuts                = nodes.nuts
local tonut               = nuts.tonut

local setboth             = nuts.setboth
local setlink             = nuts.setlink
local setlist             = nuts.setlist
local setsubtype          = nuts.setsubtype
local setattr             = nuts.setattr
local setattrs            = nuts.setattrs
local setwidth            = nuts.setwidth
local setshift            = nuts.setshift
local setoffsets          = nuts.setoffsets

local getid               = nuts.getid
local getfont             = nuts.getfont
local getattr             = nuts.getattr
local getidsubtype        = nuts.getidsubtype
local getbox              = nuts.getbox
local getlist             = nuts.getlist
local getprev             = nuts.getprev
local getnext             = nuts.getnext
local getboth             = nuts.getboth
local getwhd              = nuts.getwhd
local getkern             = nuts.getkern
local getpenalty          = nuts.getpenalty
local getwidth            = nuts.getwidth
local getdepth            = nuts.getdepth
local getexpansion        = nuts.getexpansion
local getoffsets          = nuts.getoffsets
local getindex            = nuts.getindex
local getprop             = nuts.getprop
local getdiscpart         = nuts.getdiscpart

local hpack_nodes         = nuts.hpack
local vpack_nodes         = nuts.vpack
local copylist            = nuts.copylist
local copy_node           = nuts.copy
local insertnodebefore    = nuts.insertbefore
local insertnodeafter     = nuts.insertafter

local hpack_string        = nuts.typesetters.tohpack

local texgetattribute     = tex.getattribute
local texsetattribute     = tex.setattribute

local setmetatableindex   = table.setmetatableindex

local current_font        = font.current

local fonthashes          = fonts.hashes
local chardata            = fonthashes.characters
local exheights           = fonthashes.exheights
local emwidths            = fonthashes.emwidths

local unsetvalue  <const> = attributes.unsetvalue
local ptfactor    <const> = number.dimenfactors.pt

local nodepool            = nuts.pool
local new_rule            = nodepool.rule
local new_virtual_rule    = nodepool.virtualrule
local new_kern            = nodepool.kern
local new_glue            = nodepool.glue
local new_hlist           = nodepool.hlist
local new_vlist           = nodepool.vlist

local tracers             = nodes.tracers
local visualizers         = nodes.visualizers

local setcolor            = tracers.colors.set
local setlistcolor        = tracers.colors.setlist
----- settransparency     = tracers.transparencies.set
local setlisttransparency = tracers.transparencies.setlist

local starttiming         = statistics.starttiming
local stoptiming          = statistics.stoptiming

local a_visual            <const> = attributes.private("visual")
local a_filter            <const> = attributes.private("filter")
local a_layer             <const> = attributes.private("viewerlayer")
local a_tagged            <const> = attributes.private('tagged')

local enableaction        = nodes.tasks.enableaction

local report_visualize = logs.reporter("visualize")

local f_amount = formatters["%s:%0.3f"]

local modes = {
    hbox          = 0x0000001,
    vbox          = 0x0000002,
    vtop          = 0x0000004,
    kern          = 0x0000008,
    glue          = 0x0000010,
    penalty       = 0x0000020,
    fontkern      = 0x0000040,
    strut         = 0x0000080,
    whatsit       = 0x0000100,
    glyph         = 0x0000200,
    simple        = 0x0000400,
    simplehbox    = 0x0000401,
    simplevbox    = 0x0000402,
    simplevtop    = 0x0000404,
    user          = 0x0000800,
    math          = 0x0001000,
    italic        = 0x0002000,
    origin        = 0x0004000,
    discretionary = 0x0008000,
    expansion     = 0x0010000,
    line          = 0x0020000,
    space         = 0x0040000,
    depth         = 0x0080000,
    marginkern    = 0x0100000,
    mathkern      = 0x0200000, --
    dir           = 0x0400000,
    par           = 0x0800000,
    mathglue      = 0x1000000, --
    mark          = 0x2000000,
    insert        = 0x4000000,
    boundary      = 0x8000000,

    vkern         = 0x0000008,
    hkern         = 0x0000008,
    vglue         = 0x0000010,
    hglue         = 0x0000010,
    sglue         = 0x0000010,
    pglue         = 0x0000010,
    vpenalty      = 0x0000020,
    hpenalty      = 0x0000020,

    toddler       = 0x0000200,

    short         = 0x0020000,
}

local filters = {
    vglue        = 0x00001,
    hglue        = 0x00002,
 -- mglue        = 0x00004,
    vkern        = 0x00010,
    hkern        = 0x00020,
 -- mkern        = 0x00040,
    vpenalty     = 0x00100,
    hpenalty     = 0x00200,
 -- mpenalty     = 0x00400,
    toddler      = 0x01000,
    continuation = 0x02000,
    short        = 0x04000,
    sglue        = 0x08000,
    pglue        = 0x10000,
}

visualizers.modes   = modes
visualizers.filters = filters

local usedfont, exheight, emwidth
local
 -- l_penalty, l_glue, l_kern,
    l_h_penalty, l_h_kern, l_h_glue,
    l_v_penalty, l_v_kern, l_v_glue,
    l_hbox, l_vbox, l_vtop, l_origin, l_line,
    l_depth, l_strut,
    l_glyph, l_discretionary, l_expansion, l_fontkern, l_italic, l_marginkern, l_space,
    l_math, l_mathkern, l_mathshape,
    l_dir, l_whatsit,
    l_mark, l_insert, l_boundary,
 -- l_user,
    l_mathshapekern, l_par

local enabled = false
local layers  = { }

function visualizers.setfont(id)
    usedfont = id or current_font()
    exheight = exheights[usedfont]
    emwidth  = emwidths[usedfont]
end

-- we can preset a bunch of bits

local userrule    -- bah, not yet defined: todo, delayed(nuts.rules,"userrule")
local outlinerule -- bah, not yet defined: todo, delayed(nuts.rules,"userrule")
local emptyrule   -- bah, not yet defined: todo, delayed(nuts.rules,"userrule")

local function getusedfont()
    if not usedfont then
        -- we use a narrow monospaced font -- infofont ?
        visualizers.setfont(fonts.definers.define { name = "lmmonoltcond10regular", size = tex.sp("4pt") })
    end
    return usedfont
end

local use_layers = true  directives.register("visualizers.layers", function(v) use_layers = v end)

visualizers.getusedfont = getusedfont

function visualizers.definelayer(name)
    local tag = formatters["v_%s"](name)
    attributes.viewerlayers.define {
        tag       = tag,
        title     = formatters["visualizer %s"](name),
        visible   = "start",
        editable  = "yes",
        printable = "yes",
    }
    layers[name] = attributes.viewerlayers.register(tag,true)
    return tag
end

local function initialize()
    --
    if not usedfont then
        getusedfont()
    end
    --
    for mode in next, modes do
        visualizers.definelayer(mode)
    end
    l_hbox          = layers.hbox
    l_vbox          = layers.vbox
    l_vtop          = layers.vtop
 -- l_glue          = layers.glue
    l_v_glue        = layers.vglue
    l_h_glue        = layers.hglue
 -- l_kern          = layers.kern
    l_v_kern        = layers.vkern
    l_h_kern        = layers.hkern
 -- l_penalty       = layers.penalty
    l_v_penalty     = layers.vpenalty
    l_h_penalty     = layers.hpenalty
    l_fontkern      = layers.fontkern
    l_strut         = layers.strut
    l_whatsit       = layers.whatsit
    l_glyph         = layers.glyph
 -- l_user          = layers.user
    l_math          = layers.math
    l_italic        = layers.italic
    l_marginkern    = layers.marginkern
    l_mathkern      = layers.mathkern
    l_mathshapekern = layers.mathshapekern
    l_origin        = layers.origin
    l_discretionary = layers.discretionary
    l_expansion     = layers.expansion
    l_line          = layers.line
    l_space         = layers.space
    l_depth         = layers.depth
    l_dir           = layers.dir
    l_par           = layers.par
    l_mark          = layers.mark
    l_insert        = layers.insert
    l_boundary      = layers.boundary
    --
    if not userrule then
       userrule = nuts.rules.userrule
    end
    --
    if not outlinerule then
       outlinerule = nuts.pool.outlinerule
    end
    --
    if not emptyrule then
       emptyrule = nuts.pool.emptyrule
    end
    initialize = false
end

local function enable()
    if initialize then
        initialize()
    end
    enableaction("shipouts","nodes.visualizers.handler")
    report_visualize("enabled")
    enabled = true
    tex.setcount("global","c_syst_visualizers_state",1) -- so that we can optimize at the tex end
end

function visualizers.enable()
    if not enabled then
        enable()
    end
end

local setvisual  do

    local preset_boxes  = modes.hbox + modes.vbox + modes.vtop + modes.origin
    local preset_makeup = preset_boxes
                        + modes.kern + modes.glue + modes.penalty + modes.boundary
    local preset_all    = preset_makeup
                        + modes.fontkern + modes.marginkern + modes.mathkern
                        + modes.whatsit + modes.glyph + modes.user + modes.math
                        + modes.dir + modes.mathglue + modes.mark + modes.insert

    setvisual = function(n,a,what,list)
        if not n or n == "reset" then
            return unsetvalue
        elseif n == true or n == "makeup" then
            if not a or a == 0 or a == unsetvalue then
                a = preset_makeup
            else
                a = a | preset_makeup
            end
        elseif n == "boxes" then
            if not a or a == 0 or a == unsetvalue then
                a = preset_boxes
            else
                a = a | preset_boxes
            end
        elseif n == "all" then
            if what == false then
                return unsetvalue
            elseif not a or a == 0 or a == unsetvalue then
                a = preset_all
            else
                a = a | preset_all
            end
        elseif type(n) == "string" then
            for s in gmatch(n,"[a-z]+") do
                local m = modes[s]
                if not m then
                    -- go on
                elseif not a or a == 0 or a == unsetvalue then
                    a = m
                else
                    a = a | m
                end
            end
        elseif type(n) == "number" then
            if not a or a == 0 or a == unsetvalue then
                a = n
            else
                a = a | n
            end
        end
        if not a or a == 0 or a == unsetvalue then
            return unsetvalue
        elseif not enabled then -- must happen at runtime (as we don't store layers yet)
            enable()
        end
        return a
    end

end

local function setfilter(n,a,what,list)
    if not n or n == "reset" then
        return unsetvalue
    elseif type(n) == "string" then
        for s in gmatch(n,"[a-z]+") do
            local m = filters[s]
            if not m then
                -- go on
            elseif not a or a == 0 or a == unsetvalue then
                a = m
            else
                a = a | m
            end
        end
    end
    if not a or a == 0 or a == unsetvalue then
        return unsetvalue
    else
        return a
    end
end

do

    local getattrs       = nuts.getattrs
    local apply_to_nodes = nuts.apply

    function nuts.setvisual(n,mode)
        if mode then
            local nv, nf = getattrs(n,a_visual,a_filter)
            setattrs(n,
                a_visual,setvisual(mode,nv,true),
                a_filter,setfilter(mode,nf,true)
            )
        else
            local a = texgetattribute(a_visual)
            if a ~= unsetvalue then
                setattrs(n,
                    a_visual,a,
                    a_filter,texgetattribute(a_filter)
                )
            end
        end
    end

    function nuts.setvisuals(n,mode) -- currently the same
        local nv, nf = getattrs(n,a_visual,a_filter)
        setattrs(n,
            a_visual,setvisual(mode,nv,true,true),
            a_filter,setfilter(mode,nf,true,true)
        )
    end

    local cached = setmetatableindex(function(t,k)
        if k == true then
            return texgetattribute(a_visual)
        elseif not k then
            t[k] = unsetvalue
            return unsetvalue
        else
            local v = setvisual(k)
            t[k] = v
            return v
        end
    end)

 -- local function applyvisuals(n,mode)
 --     local a = cached[mode]
 --     apply_to_nodes(n,function(n) setattr(n,a_visual,a) end)
 -- end

    local a = unsetvalue

    local f = function(n) setattr(n,a_visual,a) end

    local function applyvisuals(n,mode)
        a = cached[mode]
        apply_to_nodes(n,f)
    end

    nuts.applyvisuals = applyvisuals

    function nodes.applyvisuals(n,mode)
        applyvisuals(tonut(n),mode)
    end

    function visualizers.attribute(mode)
        return cached[mode]
    end

    visualizers.attributes = cached

end

function nuts.copyvisual(n,m)
    setattr(n,a_visual,getattr(m,a_visual))
end

function visualizers.setvisual(n)
    texsetattribute(a_visual,setvisual(n,texgetattribute(a_visual)))
    texsetattribute(a_filter,setfilter(n,texgetattribute(a_filter)))
end

function visualizers.setlayer(n)
    if use_layers then
        texsetattribute(a_layer,layers[n] or unsetvalue)
    end
end

local fraction = 10  do

    local function set(mode,v)
        texsetattribute(a_visual,setvisual(mode,texgetattribute(a_visual),v))
        texsetattribute(a_filter,setfilter(mode,texgetattribute(a_filter),v))
    end

    for mode, value in next, modes do
        trackers.register(formatters["visualizers.%s"](mode), function(v) set(mode,v) end)
    end

    trackers  .register("visualizers.reset",    function(v) set("reset", v) end)
    trackers  .register("visualizers.all",      function(v) set("all",   v) end)
    trackers  .register("visualizers.makeup",   function(v) set("makeup",v) end)
    trackers  .register("visualizers.boxes",    function(v) set("boxes", v) end)
    directives.register("visualizers.fraction", function(v) fraction = (v and tonumber(v)) or (v == "more" and 5) or 10 end)

end

-- we can just paste verbatim together .. no typesetting needed

-- experiment ... we can move the font definition away too

    local expandmacro = token.expand_macro
    local takebox     = tex.takebox

    local function hpack_string(str)
        expandmacro("syst_v_p",true,str)
        return tonut(takebox("visualizerbox"))
    end

-- ... if okay it will replace the current hpack_string in node_typ

local crappytaggingmode = false

updaters.register("structures.tagging",function()
    crappytaggingmode = true
end)

local function sometext(str,layer,color,textcolor,lap,variant)
    local text = hpack_string(str,usedfont)
    local size = getwidth(text)
    local rule = new_virtual_rule(size,2*exheight,exheight/2)
    if color then
        setcolor(rule,color,false,"trace:g")
    end
    if textcolor then
        setlistcolor(getlist(text),textcolor,false,"trace:g")
    end
    local info = setlink(rule,text)
    info = new_hlist(info)
    local x, y
    if lap then
        x = -size
    end
    if variant then
        y = variant * exheight
    end
    if x or y then
        setoffsets(info,x,y)
    end
    if use_layers and layer then
        setattr(info,a_layer,layer)
    end
    return info, size
end

local function someblob(str,layer,color,textcolor,width)
    local text = hpack_string(str,usedfont)
    local size = getwidth(text)
    local rule = new_rule(width,2*exheight,exheight/2)
    local kern = new_kern(-width + (width-size)/2)
    if color then
        setcolor(rule,color,false,"trace:g")
    end
    if textcolor then
        setlistcolor(getlist(text),textcolor) -- no transparency
    end
    local info = setlink(rule,kern,text)
    info = hpack_nodes(info)
    local width = getwidth(info)
    info = new_hlist(info)
    if use_layers and layer then
        setattr(info,a_layer,layer)
    end
    return info, width
end

local caches = setmetatableindex("table")

function visualizers.registercache(name)
    return caches[name]
end

local fontkern, italiccorrection, marginkern, mathkern, mathshapekern do

    local f_cache = caches["fontkern"]
    local i_cache = caches["italiccorrection"]
    local s_cache = caches["shapekern"]
    local m_cache = caches["marginkern"]
    local l_cache = caches["mathkern"]

    local function somekern(head,current,cache,color,layer)
        local width = getkern(current)
        local extra = getexpansion(current)
        local kern  = width + extra
        local info  = cache[kern]
        if not info then
            local text = hpack_string(formatters[" %0.3f"](kern*ptfactor),usedfont)
            local rule = new_rule(emwidth/fraction,6*exheight,2*exheight)
            local list = getlist(text)
            local colr
            if kern > 0 then
                colr = "trace:db"
            elseif kern < 0 then
                colr = "trace:dr"
            else
                colr = "trace:dg"
            end
            setlistcolor(list,colr,false,color)
            setcolor(rule,color,false,color)
            setshift(text,-5 * exheight)
            info = new_hlist(setlink(rule,text))
            if use_layers and layer then
                setattr(info,a_layer,layer)
            end
            if crappytaggingmode then
                setattr(info,a_tagged,0)
            end
            cache[kern] = info
        end
        head = insertnodebefore(head,current,copylist(info))
        return head, current
    end

    fontkern = function(head,current)
        return somekern(head,current,f_cache,"trace:ds",l_fontkern)
    end

    italiccorrection = function(head,current)
        return somekern(head,current,i_cache,"trace:do",l_italic)
    end

    mathshapekern = function(head,current)
        return somekern(head,current,s_cache,"trace:dg",l_mathshapekern)
    end

    marginkern = function(head,current)
        return somekern(head,current,m_cache,"trace:dr",l_marginkern)
    end

    mathkern = function(head,current)
        return somekern(head,current,l_cache,"trace:db",l_mathkern)
    end

end

local ruledglyphexpansion do

    local f_cache = caches["glyphexpansion"]

    ruledglyphexpansion = function(head,current,glyph)
        local extra = getexpansion(glyph)
        if extra and extra ~= 0 then
            extra = extra / 1000
            local info = f_cache[extra]
            if not info then
                local text = hpack_string(tostring(round(extra)),usedfont)
                local rule = new_rule(emwidth/fraction,exheight,2*exheight)
                local list = getlist(text)
                if extra > 0 then
                    setlistcolor(list,"trace:db",false,"trace:ds")
                elseif extra < 0 then
                    setlistcolor(list,"trace:dr",false,"trace:ds")
                else
                    setlisttransparency(list,"trace:ds")
                end
                setcolor(rule,"trace:ds",false,"trace:ds")
                setshift(text,1.75 * exheight)
                info = new_hlist(setlink(rule,text))
                if use_layers then
                    setattr(info,a_layer,l_expansion)
                end
                if crappytaggingmode then
                    setattr(info,a_tagged,0)
                end
                f_cache[extra] = info
            end
            head = insertnodebefore(head,current,copylist(info))
        end
        return head, current
    end

end

local kernexpansion do

    local f_cache = caches["kernexpansion"]

    -- in mkiv we actually need to reconstruct but let's not do that now

    kernexpansion = function(head,current)
        local extra = getexpansion(current)
        if extra ~= 0 then
            extra = extra / 1000
            local info = f_cache[extra]
            if not info then
                local text = hpack_string(tostring(round(extra)),usedfont)
                local rule = new_rule(emwidth/fraction,exheight,4*exheight)
                local list = getlist(text)
                if extra > 0 then
                    setlistcolor(list,"trace:db",false,"trace:ds")
                elseif extra < 0 then
                    setlistcolor(list,"trace:dr",false,"trace:ds")
                else
                    setlisttransparency(list,"trace:ds")
                end
                setcolor(rule,"trace:ds",false,"trace:ds")
                setshift(text,3.5 * exheight)
                info = new_hlist(setlink(rule,text))
                if use_layers then
                    setattr(info,a_layer,l_expansion)
                end
                if crappytaggingmode then
                    setattr(info,a_tagged,0)
                end
                f_cache[extra] = info
            end
            head = insertnodebefore(head,current,copylist(info))
        end
        return head, current
    end

end

local ruledmark do

    local set_code <const> = nodes.markcodes.set

    local sm_cache = caches["setmark"]
    local rm_cache = caches["resetmark"]

    ruledmark = function(head,current,subtype)
        local index = getindex(current)
        local info
        if subtype == set_code then
            info = sm_cache[index]
            if not info then
                info = sometext(formatters["SM:%i"](index),usedfont,nil,"trace:w") -- whatsit
                if use_layers then
                    setattr(info,a_layer,l_mark)
                end
                if crappytaggingmode then
                    setattr(info,a_tagged,0)
                end
                sm_cache[index] = info
            end
        else
            info = rm_cache[index]
            if not info then
                info = sometext(formatters["RM:%i"](index),usedfont,nil,"trace:w") -- whatsit
                if use_layers then
                    setattr(info,a_layer,l_mark)
                end
                if crappytaggingmode then
                    setattr(info,a_tagged,0)
                end
                rm_cache[index] = info
            end
        end
        head, current = insertnodeafter(head,current,copylist(info))
        return head, current
    end

end

local ruledinsert do

    local si_cache = caches["insert"]

    ruledinsert = function(head,current)
        local index = getindex(current)
        local info  = si_cache[index]
        if not info then
            info = sometext(formatters["SI:%i"](index),usedfont,nil,"trace:w") -- whatsit
            if use_layers then
                setattr(info,a_layer,l_insert)
            end
            if crappytaggingmode then
                setattr(info,a_tagged,0)
            end
            si_cache[index] = info
        end
        head, current = insertnodeafter(head,current,copylist(info))
        return head, current
    end

end

local ruledwhatsit do

    local whatsitcodes = nodes.whatsitcodes
    local w_cache      = caches["whatsit"]

    local tags         = {
        [whatsitcodes.open]           =  "OPN",
        [whatsitcodes.write]          =  "WRI",
        [whatsitcodes.close]          =  "CLS",
     -- [whatsitcodes.special]        =  "SPE",
        [whatsitcodes.latelua]        =  "LUA",
        [whatsitcodes.savepos]        =  "POS",
        [whatsitcodes.userdefined]    =  "USR",
        [whatsitcodes.literal]        =  "LIT",
        [whatsitcodes.setmatrix]      =  "MAT",
        [whatsitcodes.save]           =  "SAV",
        [whatsitcodes.restore]        =  "RES",
        [whatsitcodes.startscaling]   = "+SCA",
        [whatsitcodes.stopscaling]    = "-SCA",
        [whatsitcodes.startrotation]  = "+ROT",
        [whatsitcodes.stoprotation]   = "-ROT",
        [whatsitcodes.startmirroring] = "+MIR",
        [whatsitcodes.stopmirroring]  = "-MIR",
        [whatsitcodes.startclipping]  = "+CLP",
        [whatsitcodes.stopclipping]   = "-CLP",
        [whatsitcodes.startmatrix]    = "+MAT",
        [whatsitcodes.stopmatrix]     = "-MAT",
        [whatsitcodes.setstate]       =  "SET", -- can't happen because these are added after visualizing
    }

    ruledwhatsit = function(head,current,subtype)
        local info = w_cache[subtype]
        if not info then
            info = sometext(formatters["W:%s"](tags[subtype] or subtype),usedfont,nil,"trace:w")
            if use_layers then
                setattr(info,a_layer,l_whatsit)
            end
            if crappytaggingmode then
                setattr(info,a_tagged,0)
            end
            w_cache[subtype] = info
        end
        head, current = insertnodeafter(head,current,copylist(info))
        return head, current
    end

end

local ruledboundary do

    local boundarycodes = nodes.boundarycodes
    local b_cache       = caches["boundary"]

    local tags          = {
        [boundarycodes.cancel]     = "CAN",
        [boundarycodes.user]       = "USR",
        [boundarycodes.protrusion] = "PRO",
        [boundarycodes.word]       = "WRD",
    }

    ruledboundary = function(head,current,subtype)
        local info = b_cache[subtype]
        if not info then
            info = sometext(formatters["B:%s"](tags[subtype] or subtype),usedfont,nil,"trace:w")
            if use_layers then
                setattr(info,a_layer,l_boundary)
            end
            if crappytaggingmode then
                setattr(info,a_tagged,0)
            end
            b_cache[subtype] = info
        end
        head, current = insertnodeafter(head,current,copylist(info))
        return head, current
    end

end

local ruledpar do

    local parcodes        = nodes.parcodes
    local directioncodes  = tex.directioncodes -- nodes.dirvalues

    local l2r_code        <const> = directioncodes.l2r
    local r2l_code        <const> = directioncodes.r2l

    local hmodepar_code   <const> = parcodes.hmodepar
    local vmodepar_code   <const> = parcodes.vmodepar
    local localbox_code   <const> = parcodes.localbox
    local parameter_code  <const> = parcodes.parameter
    local localbreak_code <const> = parcodes.localbreak

    local p_cache         = caches["par"]

    local getdirection        = nuts.getdirection
    local getlocalparboxstate = nuts.getlocalparboxstate

    ruledpar = function(head,current,subtype)
        local what
        if subtype == hmodepar_code or subtype == vmodepar_code then
            what = getdirection(current) == r2l_code and "RLP" or "LRP"
        elseif subtype == localbox_code then
            local l, m, r = getlocalparboxstate(current)
            if l or m or r then
                what = (l and "L" or "-") .. (m and "M" or "-") .. (r and "R" or "-")
            else
                what = "BOX"
            end
        elseif subtype == parameter_code then
            what = "LOC"
        elseif subtype == localbreak_code then
            what = "BRK"
        else
            what = "???"
        end
        local info = p_cache[what]
        if not info then
            info = sometext(what,usedfont,nil,"trace:w")
            if use_layers then
                setattr(info,a_layer,l_dir)
            end
            if crappytaggingmode then
                setattr(info,a_tagged,0)
            end
            p_cache[what] = info
        end
        head, current = insertnodeafter(head,current,copylist(info))
        return head, current
    end

end

local ruleddir do

    local dircodes       = nodes.dircodes
    local directioncodes = tex.directioncodes

    local cancel_code    <const> = dircodes.cancel

    local l2r_code       <const> = directioncodes.l2r
    local r2l_code       <const> = directioncodes.r2l

    local d_cache        = caches["dir"]

    local getdirection   = nuts.getdirection

    ruleddir = function(head,current,subtype)
        local what
        if subtype == cancel_code then
            what = "CAN"
        elseif getdirection(current) == r2l_code then
            what = "R2L"
        else
            what = "L2R"
        end
        local info = d_cache[what]
        if not info then
            info = sometext(what,usedfont,nil,"trace:w")
            if use_layers then
                setattr(info,a_layer,l_dir)
            end
            if crappytaggingmode then
                setattr(info,a_tagged,0)
            end
            d_cache[what] = info
        end
        head, current = insertnodeafter(head,current,copylist(info))
        return head, current
    end

end

-- local ruleduser do
--
--     local u_cache = caches["user"]
--
--     ruleduser = function(head,current,subtype)
--         local info = u_cache[subtype]
--         if not info then
--             info = sometext(formatters["U:%s"](subtype),usedfont)
--             if use_layers then
--                 setattr(info,a_layer,l_user)
--             end
--             if crappytaggingmode then
--                 setattr(info,a_tagged,0)
--             end
--             u_cache[subtype] = info
--         end
--         head, current = insertnodeafter(head,current,copylist(info))
--         return head, current
--     end
--
-- end

local ruleddepth do

    ruleddepth = function(current,wd,ht,dp)
        local wd, ht, dp = getwhd(current)
        if dp ~= 0 then
            local rule = new_rule(wd,0,dp)
            setcolor(rule,"trace:o",false,"trace:g")
            if use_layers then
                setattr(rule,a_layer,l_depth)
            end
            if crappytaggingmode then
                setattr(rule,a_tagged,0)
            end
            setlist(current,setlink(rule,new_kern(-wd),getlist(current)))
        end
    end

end

local ruledbox do

    local b_cache = caches["box"]
    local o_cache = caches["origin"]
    local s_cache = caches["short"]

    local getshift       = nuts.getshift
    local getorientation = nuts.getorientation
    local setorientation = nuts.setorientation
    local getheight      = nuts.getheight
    local issnapped      = nuts.issnapped

    setmetatableindex(o_cache,function(t,size)
        local rule   = new_rule(2*size,size,size)
        local origin = hpack_nodes(rule)
        setcolor(rule,"trace:do",false,"trace:do")
        if use_layers then
            setattr(rule,a_layer,l_origin)
            setattr(origin,a_layer,l_origin)
        end
        if crappytaggingmode then
            setattr(origin,a_tagged,0)
        end
        t[size] = origin
        return origin
    end)

    ruledbox = function(head,current,vertical,layer,what,simple,previous,trace_origin,parent,short)
        local wd, ht, dp = getwhd(current)
     -- local wd, ht, dp, shift, list = nuts.getlistdimensions(current) -- MAYBE
        local force_origin = wd == 0 or (dp + ht) == 0
        local shift = getshift(current)
        local orientation, xoffset, yoffset, w, h, d = getorientation(current) -- TODO
        if orientation and orientation ~= 0 and (h ~= 0 or d ~= 0) then
         -- wd = w
            ht = h
            dp = d
        end
        local next = getnext(current)
        local prev = previous
        setboth(current)
        local linewidth = emwidth/fraction
        local size      = 2*linewidth
        local this
        if issnapped(current) then
            what = "S" .. what
        end
        if not simple then
            this = b_cache[what]
            if not this then
                local text = hpack_string(what,usedfont)
                this = setlink(new_kern(-getwidth(text)),text)
                setlisttransparency(this,"trace:s")
                this = new_hlist(this)
                if crappytaggingmode then
                    setattr(this,a_tagged,0)
                end
                b_cache[what] = this
            end
        end
        local rest, less, more, oeps
        if force_origin then
            rest = emptyrule(wd,ht,dp) -- we accept some overhead
        elseif what == "_D_" then
            -- also the other line
            local list = getlist(current)
            local up   = list and getheight(list) or 0
            rest = userrule {
                width  = wd,
                height = ht,
                depth  = dp,
                line   = linewidth,
                type   = "box",
                dashed = 3*size,
                double = ht - up,
            }
        elseif dp == 0 then
            rest = userrule {
                width    = wd,
                height   = ht,
                line     = linewidth,
                type     = "box",
                baseline = false,
            }
        else
            rest = userrule {
                width  = wd,
                height = ht,
                depth  = dp,
                line   = linewidth,
                type   = "box",
                dashed = 3*size,
            }
        end
-- if crappytaggingmode then
--     setattr(rest,a_tagged,0)
-- end
        --
        if short then
            local s = nuts.getshort(current)
            if s and s ~= 0 and wd ~= 0 then
                less = new_kern(-s)
                more = userrule {
                    width  = s,
                    height = ht,
                    depth  = dp,
                    line   = 5*linewidth,
                    type   = "box",
            --                 dashed = 3*size,
                }
                local what = formatters[" %0.1f"](100 * (s < 0 and -s or s) / wd)
                oeps = s_cache[what]
                if not oeps then
--                         local text = hpack_string(what,usedfont)
                    oeps = hpack_string(what,usedfont)
--                         oeps = setlink(new_kern(-getwidth(text)),text)
                    setlisttransparency(oeps,"trace:s")
--                         oeps = new_hlist(oeps)
                    if crappytaggingmode then
                        setattr(oeps,a_tagged,0)
                    end
                    s_cache[what] = oeps
                end
                oeps = copylist(oeps)
            end
        end
        --
        local info = setlink(this and copylist(this) or nil,rest,less,more,oeps)
        --
        setlisttransparency(info,"trace:s")
        info = new_hlist(info) -- important
        if use_layers then
            setattr(info,a_layer,layer)
        end
        if vertical then
            if not force_origin and shift == 0 then
                info = setlink(current,dp ~= 0 and new_kern(-dp) or nil,info)
            elseif trace_origin or force_origin then
                local size   = 2*size
                local origin = o_cache[size]
                origin = copylist(origin)
                if getid(parent) == vlist_code then
                    setshift(origin,-shift)
                    info = setlink(current,new_kern(-size),origin,new_kern(-size-dp),info)
                else
                    -- todo .. i need an example
                    info = setlink(current,dp ~= 0 and new_kern(-dp) or nil,info)
                end
                setshift(current,0)
            else
                info = setlink(current,new_dp ~= 0 and new_kern(-dp) or nil,info)
                setshift(current,0)
            end
            info = new_vlist(info,wd,ht,dp,shift)
        else
            if not force_origin and shift == 0 then
                info = setlink(current,new_kern(-wd),info)
            elseif trace_origin or force_origin then
                local size   = 2*size
                local origin = o_cache[size]
                origin = copylist(origin)
                if getid(parent) == vlist_code then
                    info = setlink(current,new_kern(-wd-size-shift),origin,new_kern(-size+shift),info)
                else
                    setshift(origin,-shift)
                    info = setlink(current,new_kern(-wd-size),origin,new_kern(-size),info)
                end
                setshift(current,0)
            else
                info = setlink(current,new_kern(-wd),info)
                setshift(current,0)
            end
            info = new_hlist(info,wd,ht,dp,shift)
        end
     -- if crappytaggingmode then
     --     setattr(info,a_tagged,0)
     -- end
     -- if orientation then
     --     setorientation(current,0,0)
     --     setorientation(info,orientation,xoffset,yoffset)
     -- end
        if next then
            setlink(info,next)
        end
        if prev and prev > 0 then
            setlink(prev,info)
        end
        if head == current then
            return info, info
        else
            return head, info
        end
    end

end

local ruledglyph do

    -- see boundingbox feature .. maybe a pdf stream is more efficient, after all we
    -- have a frozen color anyway or i need a more detailed cache .. below is a more
    -- texie approach

 -- local ligature_code = 0x8000 + nodes.glyphcodes.ligature

    local getglyphdimensions  = nuts.getglyphdimensions
    local isglyph             = nuts.isglyph
    local getoptions          = nuts.getoptions

    local istoddler_code      <const> = tex.glyphoptioncodes.istoddler
    local iscontinuation_code <const> = tex.glyphoptioncodes.iscontinuation

    ruledglyph = function(head,current,previous,glyph,toddler,continuation) -- wrong for vertical glyphs
        if not glyph then
            glyph = current
        end
        if toddler then
            local options = getoptions(glyph)
            if (options & istoddler_code) ~= istoddler_code then
                return head, current
            end
        end
        if continuation then
            local options = getoptions(glyph)
            continuation = (options & iscontinuation_code) == iscontinuation_code
        end
        local wd, ht, dp = getglyphdimensions(glyph)
        if wd ~= 0 then
            local next = getnext(current)
            local prev = previous
            setboth(current)
            local linewidth = emwidth/(2*fraction)
         -- local x_offset, y_offset, l_margin, r_margin, raise = getoffsets(current)
            local c, f = isglyph(glyph)
            local char = chardata[f][c]
            local info = (dp == 0 and outlinerule and outlinerule(wd,ht,dp,linewidth)) or userrule {
                width  = wd,
                height = ht,
                depth  = dp,
                line   = linewidth,
                type   = "box",
            }
            if toddler then
                setlistcolor(info,"trace:g",false,"trace:dg")
            elseif continuation then
                setlistcolor(info,"trace:b",false,"trace:db")
            elseif char and type(char.unicode) == "table" then -- hackery test
                -- ligature
                setlistcolor(info,"trace:s",false,"trace:ds")
            else
                -- regular
                setlistcolor(info,"trace:o",false,"trace:do")
            end
            info = new_hlist(info)
            if use_layers then
                setattr(info,a_layer,l_glyph)
            end
            local info = setlink(current,new_kern(-wd),info)
            info = hpack_nodes(info)
            if crappytaggingmode then
                setattr(info,a_tagged,0)
            end
            setwidth(info,wd)
            if next then
                setlink(info,next)
            end
            if prev then
                setlink(prev,info)
            end
            if head == current then
                return info, info
            else
                return head, info
            end
        else
            return head, current
        end
    end

    function visualizers.setruledglyph(f)
        ruledglyph = f or ruledglyph
    end

end

local ruledglue, ruledmathglue, ruledspace do

    local effectiveglue         = nuts.effectiveglue
    local iszeroglue            = nuts.iszeroglue
    local getoptions            = nuts.getoptions

    local gluecodes             = nodes.gluecodes

    local userskip_code         <const> = gluecodes.userskip
    local spaceskip_code        <const> = gluecodes.spaceskip
    local xspaceskip_code       <const> = gluecodes.xspaceskip
    local zerospaceskip_code    <const> = gluecodes.zerospaceskip or gluecodes.userskip
 -- local keepskip_code         <const> = gluecodes.keepskip or gluecodes.userskip
    local leftskip_code         <const> = gluecodes.leftskip
    local rightskip_code        <const> = gluecodes.rightskip
    local lefthangskip_code     <const> = gluecodes.lefthangskip
    local righthangskip_code    <const> = gluecodes.righthangskip
    local parfillleftskip_code  <const> = gluecodes.parfillleftskip or parfillskip_code
    local parfillrightskip_code <const> = gluecodes.parfillrightskip or parfillskip_code
    local parinitleftskip_code  <const> = gluecodes.parinitleftskip
    local parinitrightskip_code <const> = gluecodes.parinitrightskip
    local indentskip_code       <const> = gluecodes.indentskip
    local intermathskip_code    <const> = gluecodes.intermathskip
    local correctionskip_code   <const> = gluecodes.correctionskip
    local tabskip_code          <const> = gluecodes.tabskip

    local noautobreak_code      <const> = tex.glueoptioncodes.noautobreak

    local g_cache_v  = caches["vglue"]
    local g_cache_h  = caches["hglue"]

    local g_cache_gn = caches["gluename"]
    local g_cache_gz = caches["gluezero"]
    local g_cache_gf = caches["gluefixed"]

    local tags = { -- todo: keep in order, indexed
     -- [userskip_code]                   = "US",
        [gluecodes.lineskip]              = "LI",
        [gluecodes.baselineskip]          = "BS",
        [gluecodes.parskip]               = "PS",
        [gluecodes.abovedisplayskip]      = "DA",
        [gluecodes.belowdisplayskip]      = "DB",
        [gluecodes.abovedisplayshortskip] = "SA",
        [gluecodes.belowdisplayshortskip] = "SB",
        [gluecodes.topskip]               = "TO",
        [gluecodes.bottomskip]            = "BO",
        [gluecodes.splittopskip]          = "ST",
        [tabskip_code]                    = "TB",
        [gluecodes.thinmuskip]            = "MS",
        [gluecodes.medmuskip]             = "MM",
        [gluecodes.thickmuskip]           = "ML",
        [intermathskip_code]              = "IM",
        [gluecodes.keepskip or 99]        = "KS",
        [gluecodes.mathskip]              = "MT",
        [gluecodes.leaders]               = "NL",
        [gluecodes.cleaders]              = "CL",
        [gluecodes.xleaders]              = "XL",
        [gluecodes.gleaders]              = "GL",
     -- true                              = "VS",
     -- false                             = "HS",
        [leftskip_code]                   = "LS",
        [rightskip_code]                  = "RS",
        [lefthangskip_code]               = "LH",
        [righthangskip_code]              = "RH",
        [spaceskip_code]                  = "SP",
        [xspaceskip_code]                 = "XS",
        [zerospaceskip_code]              = "ZS",
        [parfillleftskip_code]            = "PL",
        [parfillrightskip_code]           = "PR",
        [parinitleftskip_code]            = "IL",
        [parinitrightskip_code]           = "IR",
        [indentskip_code]                 = "IN",
        [correctionskip_code]             = "CS",
    }

    local stags = {
        [lefthangskip_code]     =  0.5,
        [righthangskip_code]    =  0.5,
        [leftskip_code]         = -2,
        [rightskip_code]        = -2,
        [parinitleftskip_code]  = -1.3775,
        [parinitrightskip_code] = -1.3775,
        [parfillleftskip_code]  = -0.75,
        [parfillrightskip_code] = -0.75,
    }

    local g_caches = { } for k, v in next, tags do g_caches[k] = caches[v] end

    ruledglue = function(head,current,subtype,vertical,parent)
        local width   = effectiveglue(current,parent)
        local nobreak = (getoptions(current) & noautobreak_code) ~= 0
        local stag    = stags[subtype]
        local cache   = (nobreak and g_caches["NB"])
                     or g_caches[subtype]
                     or (vertical and g_cache_v)
                     or g_cache_h
        local amount  = f_amount(nobreak and "NB" or tags[subtype] or (vertical and "VS") or "HS",width*ptfactor)
        local penalty = getpenalty(current)
        if penalty then
            amount = amount .. " " .. penalty
        end
        local info    = cache[amount]
        if subtype == intermathskip_code then
            head = ruledmathglue(head,current)
        end
        if info then
            -- print("glue hit")
        else
            local l_glue = vertical and l_v_glue or l_h_glue
            if subtype == spaceskip_code or subtype == xspaceskip_code or subtype == zerospaceskip_code then
                info = sometext(amount,l_glue,"trace:y")
            elseif subtype == userskip_code then
                if width > 0 then
                    info = sometext(amount,l_glue,"trace:b")
                elseif width < 0 then
                    info = sometext(amount,l_glue,"trace:r")
                else
                    info = sometext(amount,l_glue,"trace:g")
                end
            elseif subtype == tabskip_code then
                info = sometext(amount,l_glue,"trace:s")
            elseif subtype == indentskip_code or subtype == correctionskip_code then
                info = sometext(amount,l_glue,"trace:s")
            elseif subtype == leftskip_code then
                info = sometext(amount,l_glue,"trace:y",false,true,stag)
            elseif subtype == rightskip_code then
                info = sometext(amount,l_glue,"trace:y",false,false,stag)
            elseif subtype == lefthangskip_code then
                info = sometext(amount,l_glue,"trace:y",false,true,stag)
            elseif subtype == righthangskip_code then
                info = sometext(amount,l_glue,"trace:y",false,false,stag)
            elseif subtype == parfillleftskip_code then
                info = sometext(amount,l_glue,"trace:s",false,true,stag)
            elseif subtype == parfillrightskip_code then
                info = sometext(amount,l_glue,"trace:s",false,false,stag)
            elseif subtype == parinitleftskip_code then
                info = sometext(amount,l_glue,"trace:s",false,true,stag)
            elseif subtype == parinitrightskip_code then
                info = sometext(amount,l_glue,"trace:s",false,false,stag)
            else
                info = sometext(amount,l_glue,"trace:m")
            end
            if crappytaggingmode then
                setattr(info,a_tagged,0)
            end
            cache[amount] = info
        end
        info = copylist(info)
        if vertical then
            info = vpack_nodes(info)
        end
        head, current = insertnodebefore(head,current,info)
        return head, getnext(current)
    end

    local g_cache_s = caches["space"]
    local g_cache_x = caches["xspace"]
    local g_cache_f = caches["fspace"]

    local getoptions = nuts.getoptions

    local factor_code <const> = tex.glueoptioncodes.hasfactor

    ruledspace = function(head,current,subtype,parent)
        local width = effectiveglue(current,parent)
        local info
        if subtype ~= spaceskip_code then
            info = g_cache_x[width]
            if not info then
                info = someblob("XS",l_glue,"trace:m",nil,width)
                if crappytaggingmode then
                    setattr(info,a_tagged,0)
                end
                g_cache_x[width] = info
            end
        elseif (getoptions(current) & factor_code) == factor_code then
            info = g_cache_f[width]
            if not info then
                info = someblob("SF",l_glue,"trace:c",nil,width)
                if crappytaggingmode then
                    setattr(info,a_tagged,0)
                end
                g_cache_f[width] = info
            end
        else
            info = g_cache_s[width]
            if not info then
                info = someblob("SP",l_glue,"trace:y",nil,width)
                if crappytaggingmode then
                    setattr(info,a_tagged,0)
                end
                g_cache_s[width] = info
            end
        end
        info = copylist(info)
        head, current = insertnodebefore(head,current,info)
        return head, getnext(current)
    end

    -- we sometimes pass previous as we can have issues in math (not watertight for all)

    local mathvalues = tex.mathparametercodes -- nodes.mathvalues

    mathvalues[-1] = "left"  -- (script) slack detail value
    mathvalues[-2] = "right" -- (script) slack detail value

    local gsub, sub = string.gsub, string.sub

    local morehack = setmetatableindex(function(t,k)
        local v = mathematics.classnames[k] -- yet unknown
        v = v and sub(v,1,3) or formatters["x%02x"](k)
        t[k] = v
        return v
    end)

    local temphack = setmetatableindex(function(t,k)
        local v = mathvalues[k]
        if v then
            v = gsub(v,"spacing","") -- old, quads and so
        else
            v = k - 256
            v = morehack[v//64] .. morehack[v%64]
        end
        t[k] = v
        return v
    end)

    local g_cache_qd = caches["mathquad"]

    ruledmathglue = function(head,current,parent)
        local name  = getfont(current)
        local zero  = iszeroglue(current)
        local fixed = getprop(current,"fixedmathalign")
        local color = false
        local info  = zero and g_cache_gz[name]
        local quad  = name == 0
        local width = quad and effectiveglue(current,parent)
        if not info then
            if quad then
                info  = g_cache_qd[width]
                color = "trace:z"
            elseif fixed then
                info  = g_cache_gf[name]
                color = "trace:dr"
            else
                info  = g_cache_gn[name]
                color = "trace:z"
            end
        end
        if not info then
            local amount = quad and f_amount("QUAD",width*ptfactor) or temphack[name]
         -- local amount = quad and f_amount("QD",width*ptfactor) or temphack[name]
            local text   = hpack_string(amount,usedfont)
            local rule   = new_rule(emwidth/fraction,2*exheight,(zero and 4.25 or 2.75)*exheight)
            local list   = getlist(text)
            setlistcolor(list,color,false,color)
            setcolor(rule,color,false,color)
            setshift(text,(zero and 3.5 or 2)*exheight)
            info = new_hlist(setlink(rule,text))
            if use_layers then
                setattr(info,a_layer,l_glue)
            end
            if crappytaggingmode then
                setattr(info,a_tagged,0)
            end
            if quad then
                g_cache_qd[width] = info
            elseif fixed then
                g_cache_gf[name] = info
            else
                g_cache_gn[name] = info
            end
        end
        return insertnodebefore(head,current,copylist(info))
    end

end

local ruledkern do

    local v_cache = caches["vkern"]
    local h_cache = caches["hkern"]

    ruledkern = function(head,current,vertical)
        local kern  = getkern(current)
        local cache = vertical and v_cache or h_cache
        local info  = cache[kern]
        if not info then
            local amount = f_amount(vertical and "VK" or "HK",kern*ptfactor)
            local l_kern = vertical and l_v_kern or l_h_kern
            if kern > 0 then
                info = sometext(amount,l_kern,"trace:b")
            elseif kern < 0 then
                info = sometext(amount,l_kern,"trace:r")
            else
                info = sometext(amount,l_kern,"trace:g")
            end
            if crappytaggingmode then
                setattr(info,a_tagged,0)
            end
            cache[kern] = info
        end
        info = copylist(info)
        if vertical then
            info = vpack_nodes(info)
        end
        head, current = insertnodebefore(head,current,info)
        return head, getnext(current)
    end

end

local ruledstrut do

    local strut_size      <const> = 65536 * 8 / 10
    local strut_code      <const> = nodes.rulecodes.strut
    local math_code       <const> = nodes.nodecodes.math
    local traverseid      = nuts.traverseid
    local rangedimensions = nuts.rangedimensions
    local a_mathaxis      <const> = attributes.private("mathaxis")

    ruledstrut = function(head,current,subtype,parent)
        if getwidth(current) == 0 then
            if subtype == strut_code  then
                local w = strut_size
                local a = getattr(current,a_mathaxis)
                if use_layers then
                    setattr(current,a_layer,l_strut)
                end
                if a then
                    local p = getprev(current)
                    local b, e, rule
                    for n in traverseid(math_code,current) do
                        e = n
                        break
                    end
                    for n in traverseid(math_code,current,true) do
                        b = n
                        break
                    end
                    if not b then
                        b = head
                    end
                    if not e then
                        e = nuts.tail(b)
                    end
                    w = rangedimensions(parent,b,e)
                    setwidth(current,w)
                    setcolor(current,"trace:ds",false,"trace:ds")
                    if crappytaggingmode then
                        setattr(current,a_tagged,0)
                    end
                    head, current, rule = nuts.remove(head,current)
                    local kern = new_kern(-w)
                    if a == 2 then
                        head = insertnodebefore(head,e,kern)
                        head = insertnodebefore(head,e,rule)
                    else
                        insertnodeafter(head,b,kern)
                        insertnodeafter(head,b,rule)
                    end
                    current = p
                else
                    setwidth(current,w)
                    head, current = insertnodeafter(head,current,new_kern(-w))
                end
            end
        end
        return head, current
    end

end

local ruleditalic do

    local i_cache = caches["italic"]

    ruleditalic = function(head,current)
        local kern = getkern(current)
        local info = i_cache[kern]
        if not info then
            local amount = f_amount("IC",kern*ptfactor)
            if kern > 0 then
                info = sometext(amount,l_kern,"trace:b")
            elseif kern < 0 then
                info = sometext(amount,l_kern,"trace:r")
            else
                info = sometext(amount,l_kern,"trace:g")
            end
            if crappytaggingmode then
                setattr(info,a_tagged,0)
            end
            i_cache[kern] = info
        end
        info = copylist(info)
        head, current = insertnodebefore(head,current,info)
        return head, getnext(current)
    end

end

local ruledmarginkern do

    local leftmarginkern_code  <const> = nodes.kerncodes.leftmarginkern
    local rightmarginkern_code <const> = nodes.kerncodes.rightmarginkern

    local l_cache = caches["leftmarginkern"]
    local r_cache = caches["rightmarginkern"]

    ruledmarginkern = function(head,current,subtype)
        local kern  = getkern(current)
        local left  = subtype == leftmarginkern_code
        local cache = left and l_cache or r_cache
        local info  = cache[kern]
        if not info then
            local amount = f_amount(left and "ML" or "MR",kern*ptfactor)
            if kern > 0 then
                info = sometext(amount,l_marginkern,"trace:b")
            elseif kern < 0 then
                info = sometext(amount,l_marginkern,"trace:r")
            else
                info = sometext(amount,l_marginkern,"trace:g")
            end
            if crappytaggingmode then
                setattr(info,a_tagged,0)
            end
            cache[kern] = info
        end
        info = copylist(info)
        head, current = insertnodebefore(head,current,info)
        return head, getnext(current)
    end

end

local ruledmathkern do

    local h_cache = caches["horizontalmathkern"]
    local v_cache = caches["verticalmathkern"]

    ruledmathkern = function(head,current,vertical)
        local kern  = getkern(current)
        local cache = vertical and v_cache or h_cache
        local info  = cache[kern]
        if not info then
            local amount = f_amount(vertical and "MV" or "MH",kern*ptfactor)
            if kern > 0 then
                info = sometext(amount,l_mathkern,"trace:b")
            elseif kern < 0 then
                info = sometext(amount,l_mathkern,"trace:r")
            else
                info = sometext(amount,l_mathkern,"trace:g")
            end
            if crappytaggingmode then
                setattr(info,a_tagged,0)
            end
            cache[kern] = info
        end
        info = copylist(info)
        head, current = insertnodebefore(head,current,info)
        return head, getnext(current)
    end

end

local ruleddiscretionary, ruledglyphdiscretionary do

    local d_cache = caches["discretionary"]
    local g_cache = caches["glyphdiscretionary"]

    local glyphdisccodes = tex.glyphdisccodes

    ruleddiscretionary = function(head,current) -- untested as we wipe
        local info = d_cache[true]
        if not info then
            local rule = new_rule(4*emwidth/fraction,4*exheight,exheight)
            local kern = new_kern(-2*emwidth/fraction)
            setlink(kern,rule)
            setcolor(rule,"trace:dd",false,"trace:dd")
            info = new_hlist(kern)
            if use_layers then
                setattr(info,a_layer,l_discretionary)
            end
            if crappytaggingmode then
                setattr(info,a_tagged,0)
            end
            d_cache[true] = info
        end
        insertnodeafter(head,current,copylist(info))
     -- maybe: return head, getnext(current)
        return head, current
    end

    local sub = string.sub

    ruledglyphdiscretionary = function(head,current,glyph)
        local part, after, code = getdiscpart(glyph)
-- print("part",part,"code",code)
        if after ~= 0 then
            local info = g_cache[after]
            if not info then
                local code = glyphdisccodes[after] or "?"
                local kern = new_kern(-2*emwidth/fraction)
                local rule = new_rule(4*emwidth/fraction,4*exheight,exheight)
                local text, width = sometext(sub(code,1,1),l_discretionary,"trace:dd","trace:w",false,-2)
                local back = new_kern(-width/2-2*emwidth/fraction)
                setlink(kern,rule,back,text)
                setcolor(rule,"trace:dd",false,"trace:dd")
                info = new_hlist(kern)
                if use_layers then
                    setattr(info,a_layer,l_discretionary)
                end
                if crappytaggingmode then
                    setattr(info,a_tagged,0)
                end
                g_cache[after] = info
            end
            head = insertnodebefore(head,current,copylist(info))
        end
        return head, current
    end

end

local ruledpenalty do

    local cachehash = {
        ["MP=%s"] = caches["MP=%s"],
        ["MP>%s"] = caches["MP>%s"],
        ["MP<%s"] = caches["MP<%s"],
        ["MP:%s"] = caches["MP:%s"],
        ["VP:%s"] = caches["VP:%s"],
        ["HP:%s"] = caches["HP:%s"],
        ["VP!%s"] = caches["VP:%s"],
        ["HP!%s"] = caches["HP:%s"],
     -- ["DP:%s"] = caches["DP:%s"], which ones: hyphen exhyphen word ...
    }

    local raisepenalties = false

    ----- getpenalty        = nuts.getpenalty
    local pre_penalty_code  <const> = nodes.penaltycodes.mathprepenalty
    local post_penalty_code <const> = nodes.penaltycodes.mathpostpenalty

    directives.register("visualizers.raisepenalties",function(v) raisepenalties = v end)

    local getoptions   = nuts.getoptions

    local mathforward   <const> = tex.penaltyoptioncodes.mathforward
    local mathbackward  <const> = tex.penaltyoptioncodes.mathbackward
    local doubled       <const> = tex.penaltyoptioncodes.doubleused

    local vpenalty_code <const> = filters.vpenalty

    ruledpenalty = function(head,current,vertical,subtype,f)
        local double  = (getoptions(current) & doubled) ~= 0
        local penalty = getpenalty(current,double)
        local ismath  = subtype == pre_penalty_code or subtype == post_penalty_code or subtype == true
        local amount
        if ismath then
            if f and (f & vpenalty_code) == vpenalty_code then
                return head, getnext(current)
            else
                local options  = getoptions(current)
                local forward  = (options & mathforward) ~= 0
                local backward = (options & mathbackward) ~= 0
                if forward then
                    if backward then
                        amount = "MP=%s"
                    else
                        amount = "MP>%s"
                    end
                elseif backward then
                    amount = "MP<%s"
                else
                    amount = "MP:%s"
                end
            end
        elseif vertical then
            amount = double and "VP!%s" or "VP:%s"
        else
            amount = double and "HP!%s" or "HP:%s"
        end
        local cache = cachehash[amount]
        local info  = cache[penalty]
        if info then
            -- print("penalty hit")
        else
            amount = formatters[amount](penalty)
            local l_penalty = vertical and l_v_penalty or l_h_penalty
            if ismath then
                info = sometext(amount,l_penalty,"trace:s")
            elseif penalty > 0 then
                info = sometext(amount,l_penalty,"trace:b")
            elseif penalty < 0 then
                info = sometext(amount,l_penalty,"trace:r")
            else
                info = sometext(amount,l_penalty,"trace:g")
            end
            if crappytaggingmode then
                setattr(info,a_tagged,0)
            end
            cache[penalty] = info
        end
        info = copylist(info)
        if vertical then
            info = vpack_nodes(info)
        elseif ismath then
            setshift(info, 65536*4)
        elseif raisepenalties then
            setshift(info,-65536*4)
        end
        head, current = insertnodebefore(head,current,info)
        return head, getnext(current)
    end

end

local ruledmath do

    local mathcodes = nodes.mathcodes
    local tags      = {
        [true] = {
            math            = { "SM:?", caches["SM:?"] },
            beginmath       = { "SM:B", caches["SM:B"] },
            endmath         = { "SM:E", caches["SM:E"] },
            beginbrokenmath = { "SM:>", caches["SB:>"] },
            endbrokenmath   = { "SM:<", caches["SB:<"] },
        },
        [false] = {
            math            = { "M:?", caches["M:?"] },
            beginmath       = { "M:B", caches["M:B"] },
            endmath         = { "M:E", caches["M:E"] },
            beginbrokenmath = { "M:>", caches["M:>"] },
            endbrokenmath   = { "M:<", caches["M:<"] },
        },
    }

    local getoptions = nuts.getoptions
    local shortmath  <const> = tex.mathoptioncodes.short

    ruledmath = function(head,current,subtype)
        local mtag  = mathcodes[subtype]
        local skip  = getkern(current) + getwidth(current) -- surround
        local short = (getoptions(current) & shortmath) ~= 0
        local htag  = tags[short][mtag or "math"] or tags[false].math
        local ttag  = htag[1]
        local cache = htag[2]
        local info  = cache[skip]
        if info then
            -- print("hit math")
        else
            local text, width = sometext(ttag,usedfont,nil,"trace:dr")
            local rule = new_rule(skip,-655360/fraction,2*655360/fraction)
            local dist = (mtag == "beginmath" or mtag == "beginbrokenmath") and width or skip
            setcolor(rule,"trace:dr",false,"trace:dr")
            if use_layers then
                setattr(rule,a_layer,l_math)
            end
            info = new_hlist(setlink(new_glue(-skip),rule,new_glue(-dist),text))
            if use_layers then
                setattr(info,a_layer,l_math)
            end
            if crappytaggingmode then
                setattr(info,a_tagged,0)
            end
            if crappytaggingmode then
                setattr(info,a_tagged,0)
            end
            cache[skip] = info
        end
        local saved = current
        head, current = insertnodeafter(head,current,copylist(info))
        if getpenalty(saved) ~= 0 then
            head, current = ruledpenalty(head,saved,false,true,getattr(saved,a_filter))
        end
        return head, current
    end

end

do

    local nodecodes               = nodes.nodecodes
    local disc_code               <const> = nodecodes.disc
    local kern_code               <const> = nodecodes.kern
    local glyph_code              <const> = nodecodes.glyph
    local glue_code               <const> = nodecodes.glue
    local penalty_code            <const> = nodecodes.penalty
    local whatsit_code            <const> = nodecodes.whatsit
 -- local user_code               <const> = nodecodes.user
    local math_code               <const> = nodecodes.math
    local hlist_code              <const> = nodecodes.hlist
    local vlist_code              <const> = nodecodes.vlist
    local dir_code                <const> = nodecodes.dir
    local par_code                <const> = nodecodes.par
    local mark_code               <const> = nodecodes.mark
    local insert_code             <const> = nodecodes.insert
    local rule_code               <const> = nodecodes.rule
    local boundary_code           <const> = nodecodes.boundary

    local kerncodes               = nodes.kerncodes
    local fontkern_code           <const> = kerncodes.fontkern
    local italiccorrection_code   <const> = kerncodes.italiccorrection
    local spacefontkern_code      <const> = kerncodes.spacefontkern
    local leftkern_code           <const> = kerncodes.leftcorrectionkern
    local rightkern_code          <const> = kerncodes.rightcorrectionkern
    local leftmarginkern_code     <const> = kerncodes.leftmarginkern
    local rightmarginkern_code    <const> = kerncodes.rightmarginkern
    local mathshapekern_code      <const> = kerncodes.mathshapekern
    local horizontalmathkern_code <const> = kerncodes.horizontalmathkern
    local verticalmathkern_code   <const> = kerncodes.verticalmathkern
    local leftmathslackkern_code  <const> = kerncodes.leftmathslackkern
    local rightmathslackkern_code <const> = kerncodes.rightmathslackkern
    ----- userkern_code           <const> = kerncodes.userkern

 -- local skipcodes               = nodes.skipcodes
--     local spaceskip_code          <const> = nodes.skipcodes.spaceskip
--     local xspaceskip_code         <const> = nodes.skipcodes.xspaceskip
--     local zerospaceskip_code      <const> = nodes.skipcodes.zerospaceskip
--     local intermathskip_code      <const> = nodes.skipcodes.intermathskip

 -- local listcodes               = nodes.listcodes
    local linelist_code           <const> = nodes.listcodes.line
    local rowlist_code            <const> = nodes.listcodes.alignment

 -- local vtop_package_state      = 3 -- todo: symbolic
 -- local dbox_package_state      = 4 -- todo: symbolic

    local getleader               = nuts.getleader
    local getdisc                 = nuts.getdisc
    local getstate                = nuts.getstate

    local setleader               = nuts.setleader
    local setdisc                 = nuts.setdisc

 -- local cache

    local function visualize(head,vertical,forced,parent)
        local trace_hbox            = false
        local trace_vbox            = false
        local trace_vtop            = false
        local trace_kern            = false
        local trace_glue            = false
        local trace_penalty         = false
        local trace_fontkern        = false
        local trace_strut           = false
        local trace_whatsit         = false
        local trace_glyph           = false
        local trace_simple          = false
     -- local trace_user            = false
        local trace_math            = false
        local trace_mathkern        = false
        local trace_marginkern      = false
        local trace_italic          = false
        local trace_origin          = false
        local trace_discretionary   = false
        local trace_expansion       = false
        local trace_line            = false
        local trace_space           = false
        local trace_depth           = false
        local trace_dir             = false
        local trace_par             = false
        local trace_mathglue        = false
        local trace_mark            = false
        local trace_insert          = false
        local trace_boundary        = false
        local current               = head
        local previous              = nil
        local attr                  = unsetvalue
        local prev_trace_fontkern   = nil
        local prev_trace_italic     = nil
        local prev_trace_expansion  = nil

        -- too many locals so:

        local vglue_code         <const> = filters.vglue
        local hglue_code         <const> = filters.hglue
        local vkern_code         <const> = filters.vkern
        local hkern_code         <const> = filters.hkern
        local vpenalty_code      <const> = filters.vpenalty
        local hpenalty_code      <const> = filters.hpenalty
        local toddler_code       <const> = filters.toddler
        local continuation_code  <const> = filters.continuation
        local short_code         <const> = filters.short
        local sglue_code         <const> = filters.sglue
        local pglue_code         <const> = filters.pglue

        local spaceskip_code     <const> = nodes.skipcodes.spaceskip
        local xspaceskip_code    <const> = nodes.skipcodes.xspaceskip
        local zerospaceskip_code <const> = nodes.skipcodes.zerospaceskip
        local intermathskip_code <const> = nodes.skipcodes.intermathskip

        while current do
            local id, subtype = getidsubtype(current)
            local a = forced or getattr(current,a_visual) or unsetvalue
            local content
            if a ~= attr then
                prev_trace_fontkern  = trace_fontkern
                prev_trace_italic    = trace_italic
                prev_trace_expansion = trace_expansion
                attr = a
                if a == unsetvalue then
                    trace_hbox          = false
                    trace_vbox          = false
                    trace_vtop          = false
                    trace_kern          = false
                    trace_glue          = false
                    trace_penalty       = false
                    trace_fontkern      = false
                    trace_strut         = false
                    trace_whatsit       = false
                    trace_glyph         = false
                    trace_simple        = false
                 -- trace_user          = false
                    trace_math          = false
                    trace_italic        = false
                    trace_origin        = false
                    trace_discretionary = false
                    trace_expansion     = false
                    trace_line          = false
                    trace_space         = false
                    trace_depth         = false
                    trace_marginkern    = false
                    trace_mathkern      = false
                    trace_dir           = false
                    trace_par           = false
                    trace_mathglue      = false
                    trace_mark          = false
                    trace_insert        = false
                    trace_boundary      = false
                    if id == kern_code then
                        goto kern
                    else
                        goto list
                    end
                else -- we need them to be booleans
                 -- cache[a]()
                    trace_hbox          = a & 0x0000001 ~= 0
                    trace_vbox          = a & 0x0000002 ~= 0
                    trace_vtop          = a & 0x0000004 ~= 0
                    trace_kern          = a & 0x0000008 ~= 0
                    trace_glue          = a & 0x0000010 ~= 0
                    trace_penalty       = a & 0x0000020 ~= 0
                    trace_fontkern      = a & 0x0000040 ~= 0
                    trace_strut         = a & 0x0000080 ~= 0
                    trace_whatsit       = a & 0x0000100 ~= 0
                    trace_glyph         = a & 0x0000200 ~= 0
                    trace_simple        = a & 0x0000400 ~= 0
                 -- trace_user          = a & 0x0000800 ~= 0
                    trace_math          = a & 0x0001000 ~= 0
                    trace_italic        = a & 0x0002000 ~= 0
                    trace_origin        = a & 0x0004000 ~= 0
                    trace_discretionary = a & 0x0008000 ~= 0
                    trace_expansion     = a & 0x0010000 ~= 0
                    trace_line          = a & 0x0020000 ~= 0
                    trace_space         = a & 0x0040000 ~= 0
                    trace_depth         = a & 0x0080000 ~= 0
                    trace_marginkern    = a & 0x0100000 ~= 0
                    trace_mathkern      = a & 0x0200000 ~= 0
                    trace_dir           = a & 0x0400000 ~= 0
                    trace_par           = a & 0x0800000 ~= 0
                    trace_mathglue      = a & 0x1000000 ~= 0
                    trace_mark          = a & 0x2000000 ~= 0
                    trace_insert        = a & 0x4000000 ~= 0
                    trace_boundary      = a & 0x8000000 ~= 0
                end
            elseif a == unsetvalue then
                goto list
            end
         -- if use_layers and trace_strut then
         --     setattr(current,a_layer,l_strut)
         -- else
            if id == glyph_code then
                local glyph = current
                if trace_glyph then
                    local f = getattr(current,a_filter)
                    local t = f and (f & toddler_code) == toddler_code
                    local c = f and (f & continuation_code) == continuation_code
                    head, current = ruledglyph(head,current,previous,glyph,t,c)
                end
                if trace_expansion then
                    head, current = ruledglyphexpansion(head,current,glyph)
                end
                if trace_discretionary then
                    head, current = ruledglyphdiscretionary(head,current,glyph)
                end
            elseif id == disc_code then
                -- these are gone already when we display them.
                if trace_discretionary then
                    head, current = ruleddiscretionary(head,current)
--                 elseif trace_penalty then
--                     head, current = ruledpenalty(head,current,true,true)
                end
                local pre, post, replace = getdisc(current)
                if pre then
                    pre = visualize(pre,false,a,parent)
                end
                if post then
                    post = visualize(post,false,a,parent)
                end
                if replace then
                    replace = visualize(replace,false,a,parent)
                end
                setdisc(current,pre,post,replace)
            elseif id == kern_code then
                goto kern
            elseif id == glue_code then
                goto glue
            elseif id == penalty_code then
                goto penalty
            elseif id == hlist_code or id == vlist_code then
                goto list
            elseif id == rule_code then
                if trace_strut then
                    head, current = ruledstrut(head,current,subtype,parent)
                end
            elseif id == whatsit_code then
                if trace_whatsit then
                    head, current = ruledwhatsit(head,current,subtype)
                end
         -- elseif id == user_code then
         --     if trace_user then
         --         head, current = ruleduser(head,current)
         --     end
            elseif id == math_code then
                local saved = current
                if trace_math then
                    head, current = ruledmath(head,current,subtype)
                elseif trace_penalty then
                    head, current = ruledpenalty(head,current,false,true,getattr(saved,a_filter))
                end
            elseif id == dir_code then
                if trace_dir then
                    head, current = ruleddir(head,current,subtype)
                end
            elseif id == par_code then
                if trace_par then
                    head, current = ruledpar(head,current,subtype)
                end
            elseif id == mark_code then
                if trace_mark then
                    head, current = ruledmark(head,current,subtype)
                end
            elseif id == insert_code then
                if trace_insert then
                    head, current = ruledinsert(head,current)
                end
            elseif id == boundary_code then
                if trace_boundary then
                    head, current = ruledboundary(head,current,subtype)
                end
            end
            goto next
          ::penalty::
            if trace_penalty then
                local f = getattr(current,a_filter)
                if f then
                    if vertical then
                        if (f & hpenalty_code) == hpenalty_code then
                            goto next
                        end
                    else
                        if (f & vpenalty_code) == vpenalty_code then
                            goto next
                        end
                    end
                end
                -- todo: discretionary
                head, current = ruledpenalty(head,current,vertical,subtype,f)
            end
            goto next
          ::glue::
            content = getleader(current)
            if content then
                setleader(current,visualize(content,false,nil,parent))
            elseif trace_glue then
                local f = getattr(current,a_filter)
                if f then
--                     if vertical then
--                         if (f & hglue_code) == hglue_code then
--                             goto next
--                         end
--                     else
--                         if (f & vglue_code) == vglue_code then
--                             goto next
--                         end
--                     end
                    if (f & hglue_code) == hglue_code then
                        if vertical then
                            goto next
                        end
                    elseif (f & vglue_code) == vglue_code then
                        if not vertical then
                            goto next
                        end
                    elseif (f & sglue_code) == sglue_code then
                        if subtype ~= spaceskip_code and subtype ~= xspaceskip_code then
                            goto next
                        end
                    elseif (f & pglue_code) == pglue_code then
                        if not getpenalty(current) then
                            goto next
                        end
                    end
                end
                head, current = ruledglue(head,current,subtype,vertical,parent)
            else
                if subtype == spaceskip_code or subtype == xspaceskip_code or subtype == zerospaceskip_code then -- not yet all space
                    if trace_space then
                        head, current = ruledspace(head,current,subtype,parent)
                    end
                elseif subtype == intermathskip_code then
                    if trace_math or trace_mathglue then
                        head = ruledmathglue(head,current,parent)
                    end
                end
            end
            goto next
          ::kern::
            if subtype == fontkern_code or subtype == spacefontkern_code then
                if trace_fontkern or prev_trace_fontkern then
                    head, current = fontkern(head,current)
                end
                if trace_expansion or prev_trace_expansion then
                    head, current = kernexpansion(head,current)
                end
            elseif subtype == italiccorrection_code or subtype == leftkern_code or subtype == rightkern_code then
                if trace_italic or prev_trace_italic then
                    head, current = italiccorrection(head,current)
                elseif trace_kern then
                    head, current = ruleditalic(head,current)
                end
            elseif subtype == leftmarginkern_code or subtype == rightmarginkern_code then
                if trace_marginkern then
                    head, current = marginkern(head,current)
                elseif trace_kern then
                    head, current = ruledmarginkern(head,current,subtype)
                end
            elseif subtype == verticalmathkern_code then
                if trace_mathkern or trace_kern then
                    head, current = ruledmathkern(head,current,true)
                end
            elseif subtype == horizontalmathkern_code or subtype == leftmathslackkern_code or subtype == rightmathslackkern_code then
                if trace_mathkern then
                    head, current = mathkern(head,current)
                elseif trace_kern then
                    head, current = ruledmathkern(head,current,false)
                end
            elseif subtype == mathshapekern_code then
                if trace_mathkern or trace_italic then
                    head, current = mathshapekern(head,current)
                elseif trace_kern then
                    head, current = ruledmathkern(head,current,false)
                end
            else
                if trace_kern then
                    local f = getattr(current,a_filter)
                    if f then
                        if vertical then
                            if (f & hkern_code) == hkern_code then
                                goto next
                            end
                        else
                            if (f & vkern_code) == vkern_code then
                                goto next
                            end
                        end
                    end
                    head, current = ruledkern(head,current,vertical)
                end
            end
            goto next
          ::list::
            if id == hlist_code then
                local content = getlist(current)
                if content then
                    setlist(current,visualize(content,false,nil,current))
                end
                if trace_depth then
                    ruleddepth(current)
                end
                if trace_line and (subtype == linelist_code or subtype == rowlist_code) then
                    local f = getattr(current,a_filter)
                    local s = f and (f & short_code) == short_code
                    head, current = ruledbox(head,current,false,l_line,"L__",trace_simple,previous,trace_origin,parent,s)
                elseif trace_hbox then
                    head, current = ruledbox(head,current,false,l_hbox,"H__",trace_simple,previous,trace_origin,parent)
                end
            elseif id == vlist_code then
                local content = getlist(current)
                local state   = getstate(current)
                local isvtop  = state == 3 -- vtop_package_state
                local isdbox  = state == 4 -- dbox_package_state
                local tag     = nil
                local layer   = nil
                if content then
                    setlist(current,visualize(content,true,nil,current))
                end
                if trace_vtop then
                    if isdbox then
                        tag   = "_D_"
                        layer = l_vtop
                    elseif isvtop then
                        tag   = "_T_"
                        layer = l_vtop
                    elseif trace_vbox then
                        tag   = "__V"
                        layer = l_vbox
                    end
                elseif trace_vbox then
                    if isdbox then
                        tag   = "_D_"
                        layer = l_vtop
                    elseif not isvtop then
                        tag   = "__V"
                        layer = l_vbox
                    end
                end
                if tag then
                    head, current = ruledbox(head,current,true,layer,tag,trace_simple,previous,trace_origin,parent)
                end
            end
          ::next::
            previous = current
            current  = getnext(current)
        end
        return head
    end

    local function cleanup()
        local flushnodelist = nuts.flushlist
        for tag, cache in next, caches do
            for k, v in next, cache do
                flushnodelist(v)
            end
        end
        cleanup = function()
            report_visualize("error, duplicate cleanup")
        end
    end

    luatex.registerstopactions(cleanup)

    function visualizers.handler(head)
        if usedfont then
            starttiming(visualizers)
            head = visualize(head,true)
            stoptiming(visualizers)
            return head, true
        else
            return head, false
        end
    end

    function visualizers.box(n)
        if usedfont then
            starttiming(visualizers)
            local box = getbox(n)
            if box then
                setlist(box,visualize(getlist(box),getid(box) == vlist_code))
            end
            stoptiming(visualizers)
            return head, true
        else
            return head, false
        end
    end

end

do

    local nodecodes  = nodes.nodecodes
    local hlist_code <const> = nodecodes.hlist
    local vlist_code <const> = nodecodes.vlist
    local nextnode   = nuts.traversers.node

    local last       = nil
    local used       = nil

    local mark       = {
        "trace:1", "trace:2", "trace:3",
        "trace:4", "trace:5", "trace:6",
        "trace:7",
    }

    local function markfonts(list)
        for n, id in nextnode, list do
            if id == glyph_code then
                local font = getfont(n)
                local okay = used[font]
                if not okay then
                    last = last + 1
                    okay = mark[last]
                    used[font] = okay
                end
                setcolor(n,okay)
            elseif id == hlist_code or id == vlist_code then
                markfonts(getlist(n))
            end
        end
    end

    function visualizers.markfonts(list)
        last, used = 0, { }
        markfonts(type(n) == "number" and getlist(getbox(n)) or n)
    end

end

statistics.register("visualization time",function()
    if enabled then
     -- cleanup() -- in case we don't don't do it each time
        return formatters["%s seconds"](statistics.elapsedtime(visualizers))
    end
end)

-- interface

do

    local implement = interfaces.implement

    implement {
        name      = "setvisual",
        arguments = "string",
        actions   = visualizers.setvisual
    }

    implement {
        name      = "setvisuals",
        arguments = "string",
        actions   = visualizers.setvisual
    }

    implement {
        name      = "getvisual",
        arguments = "string",
        actions   = { setvisual, context }
    }

    implement {
        name      = "setvisuallayer",
        arguments = "string",
        actions   = visualizers.setlayer
    }

    implement {
        name      = "markvisualfonts",
        arguments = "integer",
        actions   = visualizers.markfonts
    }

    implement {
        name      = "setvisualfont",
        arguments = "integer",
        actions   = visualizers.setfont
    }

end

-- Here for now:

do

    local function make(str,textcolor,rulecolor,layer,delta,extra)
     -- if initialize then
     --     initialize()
     -- end
        if not usedfont then
            getusedfont()
        end
        local rule = new_rule(emwidth/fraction,exheight,(4+delta)*exheight)
        setcolor(rule,rulecolor,false,rulecolor)
        local info
        if str == "" then
            info = new_hlist(rule)
        else
            local text = hpack_string(str,usedfont)
            local list = getlist(text)
            setlistcolor(list,textcolor,false,textcolor)
            setshift(text,(3.5+delta)*exheight)
            if extra then
                setwidth(text)
                extra = hpack_string(extra,usedfont)
                local list = getlist(text)
                setlistcolor(extra,textcolor,false,textcolor)
                setshift(extra,(3.5)*exheight)
            end
            info = new_hlist(setlink(rule,text,extra))
        end
        if use_layers then
            setattr(info,a_layer,layer)
        end
        if crappytaggingmode then
            setattr(info,a_tagged,0,0)
        end
        return info
    end

    function visualizers.register(name,textcolor,rulecolor,shift,nocache)
        if rawget(layers,name) then
            -- message
            return
        end

        visualizers.definelayer(name,true)

        local cache = caches[name]
        local layer = layers[name]

        if not textcolor then
            textcolor = "trace:ds"
        end
        if not rulecolor then
            rulecolor = "trace:do"
        end
        local delta = shift or 0
        return function(str,extra)
            if not str then
                str = ""
            end
            local info
            if nocache then
                info = make(str,textcolor,rulecolor,layer,delta,extra)
            else
                info = cache[str]
                if not info then
                    info = make(str,textcolor,rulecolor,layer,delta,extra)
                    cache[str] = info
                end
            end
            return copy_node(info)
        end
    end

end
