node-tra.lua /size: 21 Kb    last modification: 2021-10-28 13:50
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 getglue         = nuts.getglue
50local isglyph         = nuts.isglyph
51local getdirection    = nuts.getdirection
52local getwidth        = nuts.getwidth
53local count_nodes     = nuts.countall
54local used_nodes      = nuts.usedlist
55
56local nextnode        = nuts.traversers.node
57local nextglyph       = nuts.traversers.glyph
58
59local d_tostring      = nuts.tostring
60
61local nutpool         = nuts.pool
62local new_rule        = nutpool.rule
63
64local nodecodes       = nodes.nodecodes
65local whatsitcodes    = nodes.whatsitcodes
66local fillcodes       = nodes.fillcodes
67
68local subtypes        = nodes.subtypes
69
70local glyph_code      = nodecodes.glyph
71local hlist_code      = nodecodes.hlist
72local vlist_code      = nodecodes.vlist
73local disc_code       = nodecodes.disc
74local glue_code       = nodecodes.glue
75local kern_code       = nodecodes.kern
76local rule_code       = nodecodes.rule
77local dir_code        = nodecodes.dir
78local par_code        = nodecodes.par
79local whatsit_code    = nodecodes.whatsit
80local passive_code    = nodecodes.passive
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",status.output_active,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        local w = { }
309        local n = 0
310        local g = formatters["<%i>"]
311        local d = formatters["[%s|%s|%s]"]
312        while h do
313            local c, id = isglyph(h)
314            if c then
315                n = n + 1 ; w[n] = c >= 0 and utfchar(c) or g(c)
316                if joiner then
317                    n = n + 1 ; w[n] = joiner
318                end
319            elseif id == disc_code then
320                local pre, pos, rep = getdisc(h)
321                if not nodisc then
322                    n = n + 1 ; w[n] = d(
323                        pre and listtoutf(pre,joiner,textonly) or "",
324                        pos and listtoutf(pos,joiner,textonly) or "",
325                        rep and listtoutf(rep,joiner,textonly) or ""
326                    )
327                elseif rep then
328                    n = n + 1 ; w[n] = listtoutf(rep,joiner,textonly) or ""
329                end
330                if joiner then
331                    n = n + 1 ; w[n] = joiner
332                end
333            elseif id == passive_code then
334                -- smells like a bug in luatex
335                print("weird: passive node in listtoutf")
336                return ""
337            elseif textonly then
338                if id == glue_code then
339                    if getwidth(h) > 0 then
340                        n = n + 1 ; w[n] = " "
341                    end
342                elseif id == hlist_code or id == vlist_code then
343                    local l = getlist(h)
344                    n = n + 1 ; w[n] = "["
345                    if l then
346                        n = n + 1 ; w[n] = listtoutf(l,joiner,textonly,last,nodisc)
347                    end
348                    n = n + 1 ; w[n] = "]"
349                end
350            else
351                n = n + 1 ; w[n] = "[-]"
352            end
353            if h == last then
354                break
355            else
356                h = getnext(h)
357            end
358        end
359        return concat(w,"",1,(w[n] == joiner) and (n-1) or n)
360    else
361        return ""
362    end
363end
364
365function nodes.listtoutf(h,joiner,textonly,last,nodisc)
366    if h then
367        local joiner = joiner == true and utfchar(0x200C) or joiner -- zwnj
368        return listtoutf(tonut(h),joiner,textonly,last and tonut(last) or nil,nodisc)
369    else
370        return ""
371    end
372end
373
374local what = { [0] = "unknown", "line", "box", "indent", "row", "cell" }
375
376local function showboxes(n,symbol,depth)
377    depth  = depth  or 0
378    symbol = symbol or "."
379    for n, id, subtype in nextnode, tonut(n) do
380        if id == hlist_code or id == vlist_code then
381            report_nodes(rep(symbol,depth) .. what[subtype] or subtype)
382            showboxes(getlist(n),symbol,depth+1)
383        end
384    end
385end
386
387nodes.showboxes = showboxes
388
389local ptfactor = dimenfactors.pt
390local bpfactor = dimenfactors.bp
391local stripper = lpeg.patterns.stripzeros
392
393local f_f_f = formatters["%0.5Fpt plus %0.5F%s minus %0.5F%s"]
394local f_f_m = formatters["%0.5Fpt plus %0.5F%s minus %0.5Fpt"]
395local f_p_f = formatters["%0.5Fpt plus %0.5Fpt minus %0.5F%s"]
396local f_p_m = formatters["%0.5Fpt plus %0.5Fpt minus %0.5Fpt"]
397local f_f_z = formatters["%0.5Fpt plus %0.5F%s"]
398local f_p_z = formatters["%0.5Fpt plus %0.5Fpt"]
399local f_z_f = formatters["%0.5Fpt minus %0.5F%s"]
400local f_z_m = formatters["%0.5Fpt minus %0.5Fpt"]
401local f_z_z = formatters["%0.5Fpt"]
402
403local tonut = nodes.tonut
404
405local function nodetodimen(n)
406    n = tonut(n)
407    local id = getid(n)
408    if id == kern_code then
409        local width = getwidth(n)
410        if width == 0 then
411            return "0pt"
412        else
413            return f_z_z(width)
414        end
415    elseif id ~= glue_code then
416        return "0pt"
417    end
418    local width, stretch, shrink, stretch_order, shrink_order = getglue(n)
419    stretch = stretch / 65536
420    shrink  = shrink  / 65536
421    width   = width   / 65536
422    if stretch_order ~= 0 then
423        if shrink_order ~= 0 then
424            return f_f_f(width,stretch,fillcodes[stretch_order],shrink,fillcodes[shrink_order])
425        elseif shrink ~= 0 then
426            return f_f_m(width,stretch,fillcodes[stretch_order],shrink)
427        else
428            return f_f_z(width,stretch,fillcodes[stretch_order])
429        end
430    elseif shrink_order ~= 0 then
431        if stretch ~= 0 then
432            return f_p_f(width,stretch,shrink,fillcodes[shrink_order])
433        else
434            return f_z_f(width,shrink,fillcodes[shrink_order])
435        end
436    elseif stretch ~= 0 then
437        if shrink ~= 0 then
438            return f_p_m(width,stretch,shrink)
439        else
440            return f_p_z(width,stretch)
441        end
442    elseif shrink ~= 0 then
443        return f_z_m(width,shrink)
444    elseif width == 0 then
445        return "0pt"
446    else
447        return f_z_z(width)
448    end
449end
450
451
452-- number.todimen(123)
453-- number.todimen(123,"cm")
454-- number.todimen(123,false,"%F))
455
456local f_pt = formatters["%p"]
457local f_un = formatters["%F%s"]
458
459dimenfactors[""] = dimenfactors.pt
460
461local function numbertodimen(d,unit,fmt)
462    if not d or d == 0 then
463        if fmt then
464            return formatters[fmt](0,unit or "pt")
465        elseif unit then
466            return 0 .. unit
467        else
468            return "0pt"
469        end
470    elseif fmt then
471        if not unit then
472            unit = "pt"
473        end
474        return formatters[fmt](d*dimenfactors[unit],unit)
475    elseif not unit or unit == "pt" then
476        return f_pt(d)
477    else
478        return f_un(d*dimenfactors[unit],unit)
479    end
480end
481
482number.todimen = numbertodimen
483nodes .todimen = nodetodimen
484
485function number.topoints      (n,fmt) return numbertodimen(n,"pt",fmt) end
486function number.toinches      (n,fmt) return numbertodimen(n,"in",fmt) end
487function number.tocentimeters (n,fmt) return numbertodimen(n,"cm",fmt) end
488function number.tomillimeters (n,fmt) return numbertodimen(n,"mm",fmt) end
489function number.toscaledpoints(n,fmt) return numbertodimen(n,"sp",fmt) end
490function number.toscaledpoints(n)     return            n .. "sp"      end
491function number.tobasepoints  (n,fmt) return numbertodimen(n,"bp",fmt) end
492function number.topicas       (n,fmt) return numbertodimen(n "pc",fmt) end
493function number.todidots      (n,fmt) return numbertodimen(n,"dd",fmt) end
494function number.tociceros     (n,fmt) return numbertodimen(n,"cc",fmt) end
495-------- number.tonewdidots   (n,fmt) return numbertodimen(n,"nd",fmt) end
496-------- number.tonewciceros  (n,fmt) return numbertodimen(n,"nc",fmt) end
497
498function nodes.topoints      (n,fmt) return nodetodimen(n,"pt",fmt) end
499function nodes.toinches      (n,fmt) return nodetodimen(n,"in",fmt) end
500function nodes.tocentimeters (n,fmt) return nodetodimen(n,"cm",fmt) end
501function nodes.tomillimeters (n,fmt) return nodetodimen(n,"mm",fmt) end
502function nodes.toscaledpoints(n,fmt) return nodetodimen(n,"sp",fmt) end
503function nodes.toscaledpoints(n)     return          n .. "sp"      end
504function nodes.tobasepoints  (n,fmt) return nodetodimen(n,"bp",fmt) end
505function nodes.topicas       (n,fmt) return nodetodimen(n "pc",fmt) end
506function nodes.todidots      (n,fmt) return nodetodimen(n,"dd",fmt) end
507function nodes.tociceros     (n,fmt) return nodetodimen(n,"cc",fmt) end
508-------- nodes.tonewdidots   (n,fmt) return nodetodimen(n,"nd",fmt) end
509-------- nodes.tonewciceros  (n,fmt) return nodetodimen(n,"nc",fmt) end
510
511-- stop redefinition
512
513local points = function(n)
514    if not n or n == 0 then
515        return "0pt"
516    elseif type(n) == "number" then
517        return lpegmatch(stripper,format("%.5fpt",n*ptfactor)) -- faster than formatter
518    else
519        return numbertodimen(n,"pt") -- also deals with nodes
520    end
521end
522
523local basepoints = function(n)
524    if not n or n == 0 then
525        return "0bp"
526    elseif type(n) == "number" then
527        return lpegmatch(stripper,format("%.5fbp",n*bpfactor)) -- faster than formatter
528    else
529        return numbertodimen(n,"bp") -- also deals with nodes
530    end
531end
532
533local pts = function(n)
534    if not n or n == 0 then
535        return "0pt"
536    elseif type(n) == "number" then
537        return format("%.5fpt",n*ptfactor) -- faster than formatter
538    else
539        return numbertodimen(n,"pt") -- also deals with nodes
540    end
541end
542
543local nopts = function(n)
544    if not n or n == 0 then
545        return "0"
546    else
547        return format("%.5f",n*ptfactor) -- faster than formatter
548    end
549end
550
551number.points     = points
552number.basepoints = basepoints
553number.pts        = pts
554number.nopts      = nopts
555
556nodes.points     = function(n) return numbertodimen(n,"pt")     end
557nodes.basepoints = function(n) return numbertodimen(n,"bp")     end
558nodes.pts        = function(n) return numbertodimen(n,"pt")     end
559nodes.nopts      = function(n) return format("%.5f",n*ptfactor) end
560
561local colors          = { }
562tracers.colors        = colors
563
564local unsetvalue      = attributes.unsetvalue
565
566local a_color         = attributes.private('color')
567local a_colormodel    = attributes.private('colormodel')
568local m_color         = attributes.list[a_color] or { }
569
570function colors.set(n,c,s)
571    local mc = m_color[c]
572    local nn = tonut(n)
573    if mc then
574        local mm = s or texgetattribute(a_colormodel)
575        setattr(nn,a_colormodel,mm <= 0 and mm or 1)
576        setattr(nn,a_color,mc)
577    else
578        setattr(nn,a_color,unsetvalue)
579    end
580    return n
581end
582
583function colors.setlist(n,c,s)
584    local nn = tonut(n)
585    local mc = m_color[c] or unsetvalue
586    local mm = s or texgetattribute(a_colormodel)
587    if mm <= 0 then
588        mm = 1
589    end
590    while nn do
591        setattr(nn,a_colormodel,mm)
592        setattr(nn,a_color,mc)
593        nn = getnext(nn)
594    end
595    return n
596end
597
598function colors.reset(n)
599    setattr(tonut(n),a_color,unsetvalue)
600    return n
601end
602
603-- maybe
604
605local transparencies   = { }
606tracers.transparencies = transparencies
607
608local a_transparency   = attributes.private('transparency')
609local m_transparency   = attributes.list[a_transparency] or { }
610
611function transparencies.set(n,t)
612    setattr(tonut(n),a_transparency,m_transparency[t] or unsetvalue)
613    return n
614end
615
616function transparencies.setlist(n,c,s)
617    local nn = tonut(n)
618    local mt = m_transparency[c] or unsetvalue
619    while nn do
620        setattr(nn,a_transparency,mt)
621        nn = getnext(nn)
622    end
623    return n
624end
625
626function transparencies.reset(n)
627    setattr(n,a_transparency,unsetvalue)
628    return n
629end
630
631-- for the moment here
632
633local visualizers = nodes.visualizers or { }
634nodes.visualizers = visualizers
635
636function visualizers.handler(head)
637    return head, false
638end
639
640-- we could cache attribute lists and set attr (copy will increment count) .. todo ..
641-- although tracers are used seldom
642
643local function setproperties(n,c,s)
644    local nn = tonut(n)
645    local mm = texgetattribute(a_colormodel)
646    setattr(nn,a_colormodel,mm > 0 and mm or 1)
647    setattr(nn,a_color,m_color[c])
648    setattr(nn,a_transparency,m_transparency[c])
649    return n
650end
651
652tracers.setproperties = setproperties
653
654-- setting attrlist entries instead of attr for successive entries doesn't
655-- speed up much (this function is only used in tracers anyway)
656
657function tracers.setlist(n,c,s)
658    local nn = tonut(n)
659    local mc = m_color[c]
660    local mt = m_transparency[c]
661    local mm = texgetattribute(a_colormodel)
662    if mm <= 0 then
663        mm = 1
664    end
665    while nn do
666        setattr(nn,a_colormodel,mm)
667        setattr(nn,a_color,mc)
668        setattr(nn,a_transparency,mt)
669        nn = getnext(nn)
670    end
671    return n
672end
673
674function tracers.resetproperties(n)
675    local nn = tonut(n)
676    setattr(nn,a_color,unsetvalue)
677    setattr(nn,a_transparency,unsetvalue)
678    return n
679end
680
681-- this one returns a nut
682
683local nodestracerpool = { }
684local nutstracerpool  = { }
685
686tracers.pool = {
687    nodes = nodestracerpool,
688    nuts  = nutstracerpool,
689}
690
691table.setmetatableindex(nodestracerpool,function(t,k,v)
692    local f = nutstracerpool[k]
693    local v = function(...)
694        return tonode(f(...))
695    end
696    t[k] = v
697    return v
698end)
699
700function nutstracerpool.rule(w,h,d,c,s) -- so some day we can consider using literals (speedup)
701    return setproperties(new_rule(w,h,d),c,s)
702end
703
704tracers.rule = nodestracerpool.rule -- for a while
705
706-- local function show(head,n,message)
707--     print("START",message or "")
708--     local i = 0
709--     for current in traverse(head) do
710--         local prev = getprev(current)
711--         local next = getnext(current)
712--         i = i + 1
713--         print(i, prev and nodecodes[getid(prev)],nodecodes[getid(current)],next and nodecodes[getid(next)])
714--         if i == n then
715--             break
716--         end
717--     end
718--     print("STOP", message or "")
719-- end
720