node-tra.lmt /size: 24 Kb    last modification: 2025-02-21 11:03
1if not modules then modules = { } end modules ['node-tra'] = {
2    version   = 1.001,
3    comment   = "companion to node-ini.mkiv",
4    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5    copyright = "PRAGMA ADE / ConTeXt Development Team",
6    license   = "see context related readme files"
7}
8
9-- Some of the code here might become a runtime module instead. This old module will
10-- be cleaned up anyway!
11
12local next = next
13local utfchar = utf.char
14local format, match, gmatch, concat, rep = string.format, string.match, string.gmatch, table.concat, string.rep
15local lpegmatch = lpeg.match
16local clock = os.gettimeofday or os.clock -- should go in environment
17
18local report_nodes = logs.reporter("nodes","tracing")
19
20local nodes, node, context = nodes, node, context
21
22local texgetattribute = tex.getattribute
23
24local tracers         = nodes.tracers or { }
25nodes.tracers         = tracers
26
27local tasks           = nodes.tasks or { }
28nodes.tasks           = tasks
29
30local handlers        = nodes.handlers or {}
31nodes.handlers        = handlers
32
33local injections      = nodes.injections or { }
34nodes.injections      = injections
35
36local nuts            = nodes.nuts
37local tonut           = nuts.tonut
38local tonode          = nuts.tonode
39
40local getnext         = nuts.getnext
41local getprev         = nuts.getprev
42local getid           = nuts.getid
43local getsubtype      = nuts.getsubtype
44local getlist         = nuts.getlist
45local getdisc         = nuts.getdisc
46local setattr         = nuts.setattr
47local setattrs        = nuts.setattrs
48local getglue         = nuts.getglue
49local isglyph         = nuts.isglyph
50local getdirection    = nuts.getdirection
51local getwidth        = nuts.getwidth
52
53local count_nodes     = nuts.countall
54
55local nextnode        = nuts.traversers.node
56local nextglyph       = nuts.traversers.glyph
57
58local shownodes       = nuts.show
59
60local d_tostring      = nuts.tostring
61
62local nutpool         = nuts.pool
63local new_rule        = nutpool.rule
64
65local nodecodes       = nodes.nodecodes
66local whatsitcodes    = nodes.whatsitcodes
67local fillcodes       = tex.fillcodes
68
69local subtypes        = nodes.subtypes
70
71local glyph_code      <const> = nodecodes.glyph
72local hlist_code      <const> = nodecodes.hlist
73local vlist_code      <const> = nodecodes.vlist
74local disc_code       <const> = nodecodes.disc
75local glue_code       <const> = nodecodes.glue
76local kern_code       <const> = nodecodes.kern
77local rule_code       <const> = nodecodes.rule
78local dir_code        <const> = nodecodes.dir
79local par_code        <const> = nodecodes.par
80local whatsit_code    <const> = nodecodes.whatsit
81
82local dimenfactors    = number.dimenfactors
83local formatters      = string.formatters
84
85local startofpar      = nuts.startofpar
86
87-- this will be reorganized:
88
89function nodes.showlist(head, message)
90    if message then
91        report_nodes(message)
92    end
93    for n in nextnode, tonut(head) do
94        report_nodes(d_tostring(n))
95    end
96end
97
98function nodes.handlers.checkglyphs(head,message)
99    local h = tonut(head) -- tonut needed?
100    local t = { }
101    local n = 0
102    local f = formatters["%U:%s"]
103    for g, char, font in nextglyph, h do
104        n = n + 1
105        t[n] = f(char,getsubtype(g))
106    end
107    if n == 0 then
108        -- nothing to report
109    elseif message and message ~= "" then
110        report_nodes("%s, %s glyphs: % t",message,n,t)
111    else
112        report_nodes("%s glyphs: % t",n,t)
113    end
114    return false
115end
116
117local fontcharacters -- = fonts.hashes.descriptions
118
119local function tosequence(start,stop,compact)
120    if start then
121        if not fontcharacters then
122            fontcharacters = fonts.hashes.descriptions
123            if not fontcharacters then
124                return "[no char data]"
125            end
126        end
127        local f_sequence = formatters["U+%04X:%s"]
128        local f_subrange = formatters["[[ %s ][ %s ][ %s ]]"]
129        start = tonut(start)
130        stop = stop and tonut(stop)
131        local t = { }
132        local n = 0
133        while start do
134            local c, id = isglyph(start)
135            if c then
136                local u = fontcharacters[id][c] -- id == font id
137                u = u and u.unicode or c
138                if type(u) == "table" then
139                    local tt = { }
140                    for i=1,#u do
141                        local c = u[i]
142                        tt[i] = compact and utfchar(c) or f_sequence(c,utfchar(c))
143                    end
144                    n = n + 1 ; t[n] = "(" .. concat(tt," ") .. ")"
145                else
146                    n = n + 1 ; t[n] = compact and utfchar(c) or f_sequence(c,utfchar(c))
147                end
148            elseif id == disc_code then
149                local pre, post, replace = getdisc(start)
150                t[#t+1] = f_subrange(pre and tosequence(pre),post and tosequence(post),replace and tosequence(replace))
151            elseif id == rule_code then
152                n = n + 1 ; t[n] = compact and "|" or nodecodes[id] or "?"
153            elseif id == dir_code then
154                local d, p = getdirection(start)
155                n = n + 1 ; t[n] = "[<" .. (p and "-" or "+") .. d .. ">]" -- todo l2r etc
156            elseif id == par_code and startofpar(current) then
157                n = n + 1 ; t[n] = "[<" .. getdirection(start) .. ">]" -- todo l2r etc
158            elseif compact then
159                n = n + 1 ; t[n] = "[]"
160            else
161                n = n + 1 ; t[n] = nodecodes[id]
162            end
163            if start == stop then
164                break
165            else
166                start = getnext(start)
167            end
168        end
169        if compact then
170            return concat(t)
171        else
172            return concat(t," ")
173        end
174    else
175        return "[empty]"
176    end
177end
178
179nodes.tosequence = tosequence
180nuts .tosequence = tosequence
181
182function nodes.report(t)
183    report_nodes("output %a, %s nodes",tex.getoutputactive(),count_nodes(t))
184end
185
186function nodes.packlist(head)
187    local t = { }
188    for n in nextnode, tonut(head) do
189        t[#t+1] = d_tostring(n)
190    end
191    return t
192end
193
194function nodes.idstostring(head,tail)
195    head = tonut(head)
196    tail = tail and tonut(tail)
197    local t       = { }
198    local last_id = nil
199    local last_n  = 0
200    local f_two   = formatters["[%s*%s]"]
201    local f_one   = formatters["[%s]"]
202    for n, id, subtype in nextnode, head do
203        if id == whatsit_code then
204            id = whatsitcodes[subtype]
205        else
206            id = nodecodes[id]
207        end
208        if not last_id then
209            last_id = id
210            last_n  = 1
211        elseif last_id == id then
212            last_n = last_n + 1
213        else
214            if last_n > 1 then
215                t[#t+1] = f_two(last_n,last_id)
216            else
217                t[#t+1] = f_one(last_id)
218            end
219            last_id = id
220            last_n  = 1
221        end
222        if n == tail then
223            break
224        end
225    end
226    if not last_id then
227        t[#t+1] = "no nodes"
228    else
229        if last_n > 1 then
230            t[#t+1] = f_two(last_n,last_id)
231        else
232            t[#t+1] = f_one(last_id)
233        end
234    end
235    return concat(t," ")
236end
237
238function nodes.idsandsubtypes(head)
239    local h = tonut(head)
240    local t = { }
241    local f = formatters["%s:%s"]
242    for n, id, subtype in nextnode, h do
243        local c = nodecodes[id]
244        if subtype then
245            t[#t+1] = f(c,subtypes[id][subtype])
246        else
247            t[#t+1] = c
248        end
249    end
250    return concat(t, " ")
251end
252
253-- function nodes.xidstostring(head,tail) -- only for special tracing of backlinks
254--     head = tonut(head)
255--     tail = tonut(tail)
256--     local n = head
257--     while n.next do
258--         n = n.next
259--     end
260--     local t, last_id, last_n = { }, nil, 0
261--     while n do
262--         local id = n.id
263--         if not last_id then
264--             last_id, last_n = id, 1
265--         elseif last_id == id then
266--             last_n = last_n + 1
267--         else
268--             if last_n > 1 then
269--                 t[#t+1] = formatters["[%s*%s]"](last_n,nodecodes[last_id] or "?")
270--             else
271--                 t[#t+1] = formatters["[%s]"](nodecodes[last_id] or "?")
272--             end
273--             last_id, last_n = id, 1
274--         end
275--         if n == head then
276--             break
277--         end
278--         n = getprev(n)
279--     end
280--     if not last_id then
281--         t[#t+1] = "no nodes"
282--     elseif last_n > 1 then
283--         t[#t+1] = formatters["[%s*%s]"](last_n,nodecodes[last_id] or "?")
284--     else
285--         t[#t+1] = formatters["[%s]"](nodecodes[last_id] or "?")
286--     end
287--     return table.concat(table.reversed(t)," ")
288-- end
289
290local function showsimplelist(h,depth,n)
291    h = h and tonut(h)
292    while h do
293        report_nodes("% w%s",n,d_tostring(h))
294        if not depth or n < depth then
295            local id = getid(h)
296            if id == hlist_code or id == vlist_code then
297                showsimplelist(getlist(h),depth,n+1)
298            end
299        end
300        h = getnext(h)
301    end
302end
303
304nodes.showsimplelist = function(h,depth) showsimplelist(h,depth,0) end
305
306local function listtoutf(h,joiner,textonly,last,nodisc)
307    if h then
308        --
309        local chardata = fonts.hashes.characters -- not yet defined
310        --
311        local w = { }
312        local n = 0
313        local g = formatters["<%i>"]
314        local d = formatters["[%s|%s|%s]"]
315        while h do
316            local c, id = isglyph(h)
317            if c then
318                if c >= 0 then
319                    local d = chardata[id]
320                    if d then
321                        d = d[c]
322                        if d then
323                            local u = d.unicode
324                            if type(u) == "table" then
325                                for i=1,#u do
326                                    n = n + 1 ; w[n] = utfchar(u[i])
327                                end
328                                goto DONE
329                            elseif u then
330                                n = n + 1 ; w[n] = utfchar(u)
331                                goto DONE
332                            end
333                        end
334                    end
335                end
336                n = n + 1 ; w[n] = g(c)
337              ::DONE::
338                if joiner then
339                    n = n + 1 ; w[n] = joiner
340                end
341            elseif id == disc_code then
342                local pre, pos, rep = getdisc(h)
343                if not nodisc then
344                    n = n + 1 ; w[n] = d(
345                        pre and listtoutf(pre,joiner,textonly) or "",
346                        pos and listtoutf(pos,joiner,textonly) or "",
347                        rep and listtoutf(rep,joiner,textonly) or ""
348                    )
349                elseif rep then
350                    n = n + 1 ; w[n] = listtoutf(rep,joiner,textonly) or ""
351                end
352                if joiner then
353                    n = n + 1 ; w[n] = joiner
354                end
355            elseif textonly then
356                if id == glue_code then
357                    if getwidth(h) > 0 then
358                        n = n + 1 ; w[n] = " "
359                    end
360                elseif id == hlist_code or id == vlist_code then
361                    local l = getlist(h)
362                    n = n + 1 ; w[n] = "["
363                    if l then
364                        n = n + 1 ; w[n] = listtoutf(l,joiner,textonly,last,nodisc)
365                    end
366                    n = n + 1 ; w[n] = "]"
367                end
368            else
369                n = n + 1 ; w[n] = "[-]"
370            end
371            if h == last then
372                break
373            else
374                h = getnext(h)
375            end
376        end
377        return concat(w,"",1,(w[n] == joiner) and (n-1) or n)
378    else
379        return ""
380    end
381end
382
383function nodes.listtoutf(h,joiner,textonly,last,nodisc)
384    if h then
385        local joiner = joiner == true and utfchar(0x200C) or joiner -- zwnj
386        return listtoutf(tonut(h),joiner,textonly,last and tonut(last) or nil,nodisc)
387    else
388        return ""
389    end
390end
391
392local what = { [0] = "unknown", "line", "box", "indent", "row", "cell" }
393
394local function showboxes(n,symbol,depth)
395    depth  = depth  or 0
396    symbol = symbol or "."
397    for n, id, subtype in nextnode, tonut(n) do
398        if id == hlist_code or id == vlist_code then
399            report_nodes(rep(symbol,depth) .. what[subtype] or subtype)
400            showboxes(getlist(n),symbol,depth+1)
401        end
402    end
403end
404
405nodes.showboxes = showboxes
406
407local stripper  = lpeg.patterns.stripzeros
408
409local ptfactor <const> = dimenfactors.pt
410local bpfactor <const> = dimenfactors.bp
411
412local f_f_f = formatters["%0.5Fpt plus %0.5F%s minus %0.5F%s"]
413local f_f_m = formatters["%0.5Fpt plus %0.5F%s minus %0.5Fpt"]
414local f_p_f = formatters["%0.5Fpt plus %0.5Fpt minus %0.5F%s"]
415local f_p_m = formatters["%0.5Fpt plus %0.5Fpt minus %0.5Fpt"]
416local f_f_z = formatters["%0.5Fpt plus %0.5F%s"]
417local f_p_z = formatters["%0.5Fpt plus %0.5Fpt"]
418local f_z_f = formatters["%0.5Fpt minus %0.5F%s"]
419local f_z_m = formatters["%0.5Fpt minus %0.5Fpt"]
420local f_z_z = formatters["%0.5Fpt"]
421
422local tonut = nodes.tonut
423
424local function nodetodimen(n)
425    n = tonut(n)
426    local id = getid(n)
427    if id == kern_code then
428        local width = getwidth(n)
429        if width == 0 then
430            return "0pt"
431        else
432            return f_z_z(width)
433        end
434    elseif id ~= glue_code then
435        return "0pt"
436    end
437    local width, stretch, shrink, stretch_order, shrink_order = getglue(n)
438    stretch = stretch / 65536
439    shrink  = shrink  / 65536
440    width   = width   / 65536
441    if stretch_order ~= 0 then
442        if shrink_order ~= 0 then
443            return f_f_f(width,stretch,fillcodes[stretch_order],shrink,fillcodes[shrink_order])
444        elseif shrink ~= 0 then
445            return f_f_m(width,stretch,fillcodes[stretch_order],shrink)
446        else
447            return f_f_z(width,stretch,fillcodes[stretch_order])
448        end
449    elseif shrink_order ~= 0 then
450        if stretch ~= 0 then
451            return f_p_f(width,stretch,shrink,fillcodes[shrink_order])
452        else
453            return f_z_f(width,shrink,fillcodes[shrink_order])
454        end
455    elseif stretch ~= 0 then
456        if shrink ~= 0 then
457            return f_p_m(width,stretch,shrink)
458        else
459            return f_p_z(width,stretch)
460        end
461    elseif shrink ~= 0 then
462        return f_z_m(width,shrink)
463    elseif width == 0 then
464        return "0pt"
465    else
466        return f_z_z(width)
467    end
468end
469
470
471-- number.todimen(123)
472-- number.todimen(123,"cm")
473-- number.todimen(123,false,"%F))
474
475local f_pt = formatters["%p"]
476local f_un = formatters["%F%s"]
477
478dimenfactors[""] = dimenfactors.pt
479
480local function numbertodimen(d,unit,fmt)
481    if not d or d == 0 then
482        if fmt then
483            return formatters[fmt](0,unit or "pt")
484        elseif unit then
485            return 0 .. unit
486        else
487            return "0pt"
488        end
489    elseif fmt then
490        if not unit then
491            unit = "pt"
492        end
493        return formatters[fmt](d*dimenfactors[unit],unit)
494    elseif not unit or unit == "pt" then
495        return f_pt(d)
496    else
497        -- beware: unscaled em and ex
498        return f_un(d*dimenfactors[unit],unit)
499    end
500end
501
502number.todimen = numbertodimen
503nodes .todimen = nodetodimen
504
505-- todo: dk
506
507function number.topoints      (n,fmt) return numbertodimen(n,"pt",fmt) end
508function number.toinches      (n,fmt) return numbertodimen(n,"in",fmt) end
509function number.tocentimeters (n,fmt) return numbertodimen(n,"cm",fmt) end
510function number.tomillimeters (n,fmt) return numbertodimen(n,"mm",fmt) end
511function number.toscaledpoints(n,fmt) return numbertodimen(n,"sp",fmt) end
512function number.toscaledpoints(n)     return            n .. "sp"      end
513function number.tobasepoints  (n,fmt) return numbertodimen(n,"bp",fmt) end
514function number.topicas       (n,fmt) return numbertodimen(n "pc",fmt) end
515function number.todidots      (n,fmt) return numbertodimen(n,"dd",fmt) end
516function number.tociceros     (n,fmt) return numbertodimen(n,"cc",fmt) end
517-------- number.tonewdidots   (n,fmt) return numbertodimen(n,"nd",fmt) end
518-------- number.tonewciceros  (n,fmt) return numbertodimen(n,"nc",fmt) end
519
520function nodes.topoints      (n,fmt) return nodetodimen(n,"pt",fmt) end
521function nodes.toinches      (n,fmt) return nodetodimen(n,"in",fmt) end
522function nodes.tocentimeters (n,fmt) return nodetodimen(n,"cm",fmt) end
523function nodes.tomillimeters (n,fmt) return nodetodimen(n,"mm",fmt) end
524function nodes.toscaledpoints(n,fmt) return nodetodimen(n,"sp",fmt) end
525function nodes.toscaledpoints(n)     return          n .. "sp"      end
526function nodes.tobasepoints  (n,fmt) return nodetodimen(n,"bp",fmt) end
527function nodes.topicas       (n,fmt) return nodetodimen(n "pc",fmt) end
528function nodes.todidots      (n,fmt) return nodetodimen(n,"dd",fmt) end
529function nodes.tociceros     (n,fmt) return nodetodimen(n,"cc",fmt) end
530-------- nodes.tonewdidots   (n,fmt) return nodetodimen(n,"nd",fmt) end
531-------- nodes.tonewciceros  (n,fmt) return nodetodimen(n,"nc",fmt) end
532
533-- stop redefinition
534
535local points = function(n)
536    if not n or n == 0 then
537        return "0pt"
538    elseif type(n) == "number" then
539        return lpegmatch(stripper,format("%.5fpt",n*ptfactor)) -- faster than formatter
540    else
541        return numbertodimen(n,"pt") -- also deals with nodes
542    end
543end
544
545local basepoints = function(n)
546    if not n or n == 0 then
547        return "0bp"
548    elseif type(n) == "number" then
549        return lpegmatch(stripper,format("%.5fbp",n*bpfactor)) -- faster than formatter
550    else
551        return numbertodimen(n,"bp") -- also deals with nodes
552    end
553end
554
555local pts = function(n)
556    if not n or n == 0 then
557        return "0pt"
558    elseif type(n) == "number" then
559        return format("%.5fpt",n*ptfactor) -- faster than formatter
560    else
561        return numbertodimen(n,"pt") -- also deals with nodes
562    end
563end
564
565local nopts = function(n)
566    if not n or n == 0 then
567        return "0"
568    else
569        return format("%.5f",n*ptfactor) -- faster than formatter
570    end
571end
572
573number.points      = points
574number.basepoints  = basepoints
575number.pts         = pts
576number.nopts       = nopts
577
578nodes.points       = function(n) return numbertodimen(n,"pt")     end
579nodes.basepoints   = function(n) return numbertodimen(n,"bp")     end
580nodes.pts          = function(n) return numbertodimen(n,"pt")     end
581nodes.nopts        = function(n) return format("%.5f",n*ptfactor) end
582
583local colors       = { }
584tracers.colors     = colors
585
586local unsetvalue   <const> = attributes.unsetvalue
587
588local a_color      <const> = attributes.private('color')
589local a_colormodel <const> = attributes.private('colormodel')
590
591local m_color      = attributes.list[a_color] or { }
592
593function colors.set(n,c,s,t) -- also unsets !
594    local mc = m_color[c]
595    local nn = tonut(n)
596    if mc then
597        local mm = s or texgetattribute(a_colormodel)
598        if mm <= 0 then
599            mm = 1
600        end
601        -- only here t is dealt with
602        if t then
603            setattrs(nn,a_colormodel,mm,a_color,mc,a_transparency,m_transparency[t] or unsetvalue)
604        else
605            setattrs(nn,a_colormodel,mm,a_color,mc)
606        end
607    else
608        setattr(nn,a_color,unsetvalue)
609    end
610    return n
611end
612
613function colors.setlist(n,c,s,t)
614    local nn = tonut(n)
615    local mc = type(c) == "number" and c or m_color[c] or unsetvalue
616    local mm = s or texgetattribute(a_colormodel)
617    if mm <= 0 then
618        mm = 1
619    end
620    if t then
621        if type(t) == "string" then
622            t = m_transparency[t] or unsetvalue
623        end
624        while nn do
625            setattrs(nn,a_colormodel,mm,a_color,mc,a_transparency,t)
626            nn = getnext(nn)
627        end
628    else
629        while nn do
630            setattrs(nn,a_colormodel,mm,a_color,mc)
631            nn = getnext(nn)
632        end
633    end
634    return n
635end
636
637function colors.reset(n)
638    setattr(tonut(n),a_color,unsetvalue)
639    return n
640end
641
642-- maybe
643
644local transparencies   = { }
645tracers.transparencies = transparencies
646
647local a_transparency   <const> = attributes.private('transparency')
648local m_transparency   = attributes.list[a_transparency] or { }
649
650function transparencies.set(n,t)
651    setattr(tonut(n),a_transparency,m_transparency[t] or unsetvalue)
652    return n
653end
654
655function transparencies.setlist(n,c,s)
656    local nn = tonut(n)
657    local mt = m_transparency[c] or unsetvalue
658    while nn do
659        setattr(nn,a_transparency,mt)
660        nn = getnext(nn)
661    end
662    return n
663end
664
665function transparencies.reset(n)
666    setattr(n,a_transparency,unsetvalue)
667    return n
668end
669
670-- for the moment here
671
672local visualizers = nodes.visualizers or { }
673nodes.visualizers = visualizers
674
675function visualizers.handler(head)
676    return head, false
677end
678
679-- we could cache attribute lists and set attr (copy will increment count) .. todo ..
680-- although tracers are used seldom
681
682local function setproperties(n,c,s)
683    local nn = tonut(n)
684    local mm = texgetattribute(a_colormodel)
685    setattr(nn,a_colormodel,mm > 0 and mm or 1)
686    setattr(nn,a_color,m_color[c])
687    setattr(nn,a_transparency,m_transparency[c])
688    return n
689end
690
691tracers.setproperties = setproperties
692
693-- setting attrlist entries instead of attr for successive entries doesn't
694-- speed up much (this function is only used in tracers anyway)
695
696function tracers.setlist(n,c,s)
697    local nn = tonut(n)
698    local mc = m_color[c]
699    local mt = m_transparency[c]
700    local mm = texgetattribute(a_colormodel)
701    if mm <= 0 then
702        mm = 1
703    end
704    while nn do
705        setattr(nn,a_colormodel,mm)
706        setattr(nn,a_color,mc)
707        setattr(nn,a_transparency,mt)
708        nn = getnext(nn)
709    end
710    return n
711end
712
713function tracers.resetproperties(n)
714    local nn = tonut(n)
715    setattr(nn,a_color,unsetvalue)
716    setattr(nn,a_transparency,unsetvalue)
717    return n
718end
719
720-- this one returns a nut
721
722local nodestracerpool = { }
723local nutstracerpool  = { }
724
725tracers.pool = {
726    nodes = nodestracerpool,
727    nuts  = nutstracerpool,
728}
729
730table.setmetatableindex(nodestracerpool,function(t,k,v)
731    local f = nutstracerpool[k]
732    local v = function(...)
733        return tonode(f(...))
734    end
735    t[k] = v
736    return v
737end)
738
739function nutstracerpool.rule(w,h,d,c,s)
740    return setproperties(new_rule(w,h,d),c,s)
741end
742
743tracers.rule = nodestracerpool.rule -- for a while
744
745-- local function show(head,n,message)
746--     print("START",message or "")
747--     local i = 0
748--     for current in traverse(head) do
749--         local prev = getprev(current)
750--         local next = getnext(current)
751--         i = i + 1
752--         print(i, prev and nodecodes[getid(prev)],nodecodes[getid(current)],next and nodecodes[getid(next)])
753--         if i == n then
754--             break
755--         end
756--     end
757--     print("STOP", message or "")
758-- end
759
760function nodes.handlers.show(n)
761    if n then
762        shownodes(tonut(n))
763    else
764        -- maybe a message
765    end
766    return n
767end
768
769trackers.register("fonts.result.show", function(v)
770    nodes.tasks.setaction("processors","nodes.handlers.show",v)
771end)
772
773-- This is a weird spot but it's just a compatibility hack:
774
775local getattributes   = nuts.getattributes
776local patchattributes = nuts.patchattributes
777
778local texgetattribute = tex.getattribute
779
780function recolor(head,colormodel,color,transparency)
781    -- todo loop over content
782    for n, id in nextnode, head do
783        if id == glyph_code or id == rule_code then
784            local m, c, t = getattributes(n,a_colormodel,a_color,a_transparency)
785            if not c or c == 1 then
786                if not t then
787                    patchattributes(n,a_colormodel,colormodel,a_color,color,a_transparency,transparency)
788                else
789                    patchattributes(n,a_colormodel,colormodel,a_color,color)
790                end
791            else
792                if not t then
793                    patchattributes(n,a_transparency,transparency)
794                end
795            end
796            -- maybe skip the visuals
797        elseif id == hlist_node or id == vlist_node then
798            recolor(getlist(n),colormodel,color,transparency)
799        end
800    end
801end
802
803local function recolorbox(head)
804    recolor(getlist(head),texgetattribute(a_colormodel),texgetattribute(a_color),texgetattribute(a_transparency))
805end
806
807nodes.handlers.recolor = recolorbox
808
809local getbox = nodes.nuts.getbox
810
811interfaces.implement {
812    name      = "recolorbox",
813    public    = true,
814    protected = true,
815    arguments = { "integer" },
816    actions   = function(n)
817        recolor(getbox(n))
818    end
819}
820