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