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