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