node-fin.lmt /size: 23 Kb    last modification: 2025-02-21 11:03
1if not modules then modules = { } end modules ['node-fin'] = {
2    version   = 1.001,
3    optimize  = true,
4    comment   = "companion to node-fin.mkiv",
5    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
6    copyright = "PRAGMA ADE / ConTeXt Development Team",
7    license   = "see context related readme files",
8}
9
10local next, type, format = next, type, string.format
11local setmetatableindex = table.setmetatableindex
12
13local attributes, nodes, node = attributes, nodes, node
14
15local nuts             = nodes.nuts
16local tonut            = nodes.tonut
17
18local getnext          = nuts.getnext
19local getid            = nuts.getid
20local getlist          = nuts.getlist
21local getleader        = nuts.getleader
22local getattr          = nuts.getattr
23local getattrs         = nuts.getattrs
24local getwidth         = nuts.getwidth
25local getwhd           = nuts.getwhd
26local hasgeometry      = nuts.hasgeometry
27local hasdimensions    = nuts.hasdimensions
28local getbox           = nuts.getbox
29
30local setlist          = nuts.setlist
31local setleader        = nuts.setleader
32
33local copy_node        = nuts.copy
34local insertnodebefore = nuts.insertbefore
35local insertnodeafter  = nuts.insertafter
36local appendaftertail  = nuts.appendaftertail
37
38local nextnode         = nuts.traversers.node
39local nextcontent      = nuts.traversers.content
40
41local nodecodes        = nodes.nodecodes
42local rulecodes        = nodes.rulecodes
43
44local boxrule_code     <const> = rulecodes.box
45local imagerule_code   <const> = rulecodes.image
46local emptyrule_code   <const> = rulecodes.empty
47local virtualrule_code <const> = rulecodes.virtual
48
49local container_code   <const> = nodes.listcodes.container
50
51local glyph_code       <const> = nodecodes.glyph
52local disc_code        <const> = nodecodes.disc
53local glue_code        <const> = nodecodes.glue
54local rule_code        <const> = nodecodes.rule
55local hlist_code       <const> = nodecodes.hlist
56local vlist_code       <const> = nodecodes.vlist
57
58local texlists         = tex.lists
59local texgetnest       = tex.getnest
60
61local states           = attributes.states
62local numbers          = attributes.numbers
63
64local implement        = interfaces.implement
65
66local starttiming      = statistics.starttiming
67local stoptiming       = statistics.stoptiming
68local loadstripped     = utilities.lua.loadstripped
69local unsetvalue       = attributes.unsetvalue
70
71-- these two will be like trackers
72
73function states.enabletriggering () triggering = true  end
74function states.disabletriggering() triggering = false end
75
76implement { name = "enablestatetriggering",  actions = states.enabletriggering  }
77implement { name = "disablestatetriggering", actions = states.disabletriggering }
78
79nodes.plugindata = nil
80
81-- inheritance: -0x7FFFFFFF -- we can best use nil and skip !
82
83local template <const> = [[
84local plugin = nodes.plugindata
85local starttiming = statistics.starttiming
86local stoptiming = statistics.stoptiming
87local namespace = plugin.namespace
88local attribute = namespace.attribute or attributes.numbers[plugin.name]
89local processor = plugin.processor
90local initializer = plugin.initializer
91local resolver = plugin.resolver
92local finalizer = plugin.finalizer
93local flusher = plugin.flusher
94if not processor then
95    return function(head)
96        return head
97    end
98elseif initializer or finalizer or resolver then
99    return function(head)
100        starttiming(attributes)
101        local used, inheritance
102        if resolver then
103            inheritance = resolver()
104        end
105        if initializer then
106            initializer(namespace,attribute,head)
107        end
108        head = processor(namespace,attribute,head,inheritance)
109        if finalizer then
110            head, used = finalizer(namespace,attribute,head)
111            if used and flusher then
112                head = flusher(namespace,attribute,head,used)
113            end
114        end
115        stoptiming(attributes)
116        return head
117    end
118else
119    return function(head)
120        starttiming(attributes)
121        head = processor(namespace,attribute,head)
122        stoptiming(attributes)
123        return head
124    end
125end
126nodes.plugindata = nil
127]]
128
129function nodes.installattributehandler(plugin)
130    nodes.plugindata = plugin
131    return loadstripped(template)()
132end
133
134-- the injectors
135
136local nsdata, nsnone, nslistwise, nsforced, nsselector
137local current, current_selector = 0, 0 -- nb, stack has a local current !
138local nsbegin, nsend, nsreset
139
140function states.initialize(namespace,attribute,head)
141    nsdata           = namespace.data
142    nsnone           = namespace.none
143    nsforced         = namespace.forced
144    nsselector       = namespace.selector
145    nslistwise       = namespace.listwise
146    current          = 0
147    current_selector = 0
148    nsstep           = namespace.resolve_step
149    if nsstep then
150        nsreset      = namespace.resolve_reset
151        nsbegin      = namespace.resolve_begin
152        nsend        = namespace.resolve_end
153        nspush       = namespace.push
154        nspop        = namespace.pop
155    end
156end
157
158function states.finalize(namespace,attribute,head) -- is this one ok?
159    if current > 0 and nsnone then
160        local id = getid(head)
161        if id == hlist_code or id == vlist_code then
162            local content = getlist(head)
163            if content then
164                appendaftertail(content,copy_node(nsnone))
165            end
166        else
167            appendaftertail(head,copy_node(nsnone))
168        end
169        return head, true
170    end
171    return head, false
172end
173
174-- we need to deal with literals too (reset as well as oval)
175
176local function process(attribute,head,inheritance,default) -- one attribute
177    local check  = false
178    local leader = nil
179    for stack, id, subtype, content in nextcontent, head do
180        if id == glyph_code or id == disc_code then
181            check = true
182        elseif id == glue_code then
183            check  = true
184            leader = content
185        elseif id == hlist_code or id == vlist_code then
186            -- tricky checking
187            local outer
188            if subtype == container_code or hasgeometry(stack) then
189                outer = getattr(stack,attribute)
190                if outer then
191                    if default and outer == inheritance then
192                        if current ~= default then
193                            head    = insertnodebefore(head,stack,copy_node(nsdata[default]))
194                            current = default
195                        end
196                    elseif current ~= outer then
197                        head    = insertnodebefore(head,stack,copy_node(nsdata[outer]))
198                        current = outer
199                    end
200                elseif default and inheritance then
201                    if current ~= default then
202                        head    = insertnodebefore(head,stack,copy_node(nsdata[default]))
203                        current = default
204                    end
205                elseif current > 0 then
206                    head    = insertnodebefore(head,stack,copy_node(nsnone))
207                    current = 0
208                end
209            end
210            -- what if no content
211            local list = process(attribute,content,inheritance,default)
212            if content ~= list then
213                setlist(stack,list)
214            end
215        elseif id == rule_code then
216            check = subtype == virtualrule_code or hasdimensions(stack)
217        end
218        -- much faster this way than using a check() and nested() function
219        if check then
220            local c = getattr(stack,attribute)
221            if c then
222                if default and c == inheritance then
223                    if current ~= default then
224                        head    = insertnodebefore(head,stack,copy_node(nsdata[default]))
225                        current = default
226                    end
227                elseif current ~= c then
228                    head    = insertnodebefore(head,stack,copy_node(nsdata[c]))
229                    current = c
230                end
231                if leader then
232                    local savedcurrent = current
233                    local ci = getid(leader)
234                    if ci == hlist_code or ci == vlist_code then
235                        -- else we reset inside a box unneeded, okay, the downside is
236                        -- that we trigger color in each repeated box, so there is room
237                        -- for improvement here
238                        current = 0
239                    end
240                    local list = process(attribute,leader,inheritance,default)
241                    if leader ~= list then
242                        setleader(stack,list)
243                    end
244                    current = savedcurrent
245                    leader = false
246                end
247            elseif default and inheritance then
248                if current ~= default then
249                    head    = insertnodebefore(head,stack,copy_node(nsdata[default]))
250                    current = default
251                end
252            elseif current > 0 then
253                head    = insertnodebefore(head,stack,copy_node(nsnone))
254                current = 0
255            end
256            check = false
257        end
258    end
259    return head
260end
261
262states.process = function(namespace,attribute,head,default)
263    return process(attribute,head,default)
264end
265
266local function simple(attribute,head)
267    local check  = false
268    local leader = nil
269    for stack, id, subtype, content in nextcontent, head do
270        if id == glyph_code or id == disc_code then
271            check = true
272        elseif id == glue_code then
273            check  = true
274            leader = content
275        elseif id == hlist_code or id == vlist_code then
276            if subtype == container_code or hasgeometry(stack) then
277                local outer = getattr(stack,attribute)
278                if outer then
279                    if current ~= outer then
280                        if current > 0 then
281                            head = insertnodebefore(head,stack,copy_node(nsnone))
282                        end
283                        head    = insertnodebefore(head,stack,copy_node(nsdata[c]))
284                        current = outer
285                    end
286                elseif current > 0 then
287                    head    = insertnodebefore(head,stack,copy_node(nsnone))
288                    current = 0
289                end
290            end
291            local list = simple(attribute,content)
292            if content ~= list then
293                setlist(stack,list)
294            end
295        elseif id == rule_code then
296            check = subtype == virtualrule_code or hasdimensions(stack)
297        end
298        if check then
299            local c = getattr(stack,attribute)
300            if c then
301                if current ~= c then
302                    if current > 0 then
303                        head = insertnodebefore(head,stack,copy_node(nsnone))
304                    end
305                    head    = insertnodebefore(head,stack,copy_node(nsdata[c]))
306                    current = c
307                end
308                if leader then
309                    local savedcurrent = current
310                    local ci = getid(leader)
311                    if ci == hlist_code or ci == vlist_code then
312                        current = 0
313                    end
314                    local list = simple(attribute,leader)
315                    if leader ~= list then
316                        setleader(stack,list)
317                    end
318                    current = savedcurrent
319                    leader = false
320                end
321            elseif current > 0 then
322                head    = insertnodebefore(head,stack,copy_node(nsnone))
323                current = 0
324            end
325            check = false
326        end
327    end
328    return head
329end
330
331states.simple = function(namespace,attribute,head,default)
332    return simple(attribute,head,default)
333end
334
335-- We can force a selector, e.g. document wide color spaces, saves a little watch
336-- out, we need to check both the selector state (like colorspace) and the main
337-- state (like color), otherwise we get into troubles when a selector state changes
338-- while the main state stays the same (like two glyphs following each other with
339-- the same color but different color spaces e.g. \showcolor). The triggering
340-- mechanism has been removed because it was never really used, but the original can
341-- be seen in the mkiv (lua) code.
342
343local function selective(attribute,head,inheritance,default) -- two attributes
344    local check  = false
345    local leader = nil
346--   nuts.checkslide(head,"SELECTIVE")
347    for stack, id, subtype, content in nextcontent, head do
348        if id == glyph_code or id == disc_code then
349            check = true
350        elseif id == glue_code then
351            check  = true
352            leader = content
353        elseif id == hlist_code or id == vlist_code then
354            local outer, s
355            if subtype == container_code or hasgeometry(stack) then
356                outer, s = getattrs(stack,attribute,nsselector)
357                if outer then
358                    if default and outer == inheritance then
359                        if current ~= default then
360                            local data = nsdata[default][nsforced or nsselector]
361                            if data then
362                                head = insertnodebefore(head,stack,copy_node(data))
363                            end
364                            current = default
365                        end
366                    elseif current ~= outer or current_selector ~= s then
367                        local data = nsdata[outer][nsforced or s or nsselector]
368                        if data then
369                            head = insertnodebefore(head,stack,copy_node(data))
370                        end
371                        current = outer
372                        current_selector = s
373                    end
374                elseif default and inheritance then
375                    if current ~= default then
376                        local data = nsdata[default][nsforced or s or nsselector]
377                        if data then
378                            head = insertnodebefore(head,stack,copy_node(data))
379                        end
380                        current = default
381                    end
382                elseif current > 0 then
383                    head = insertnodebefore(head,stack,copy_node(nsnone))
384                    current = 0
385                    current_selector = 0
386                end
387            end
388            if content then
389                local list = selective(attribute,content,inheritance,default)
390                if content ~= list then
391                    setlist(stack,list)
392                end
393            end
394        elseif id == rule_code then
395            if subtype == virtualrule_code then
396                check = true
397            elseif subtype == boxrule_code or subtype == imagerule_code or subtype == emptyrule_code then
398                -- so no redundant color stuff (only here, layers for instance should obey)
399                check = false
400            else
401                check = hasdimensions(stack)
402            end
403        end
404        if check then
405            local c, s = getattrs(stack,attribute,nsselector)
406            if c then
407                if default and c == inheritance then
408                    if current ~= default then
409                        local data = nsdata[default][nsforced or s or nsselector]
410                        if data then
411                            head = insertnodebefore(head,stack,copy_node(data))
412                        end
413                        current = default
414                    end
415                elseif current ~= c or current_selector ~= s then
416                    local data = nsdata[c][nsforced or s or nsselector]
417                    if data then
418                        head = insertnodebefore(head,stack,copy_node(data))
419                    end
420                    current = c
421                    current_selector = s
422                end
423                if leader then
424                    local list = selective(attribute,leader,inheritance,default)
425                    if leader ~= list then
426                        setleader(stack,list)
427                    end
428                    leader = false
429                end
430            elseif default and inheritance then
431                if current ~= default then
432                    local data = nsdata[default][nsforced or s or nsselector]
433                    if data then
434                        head = insertnodebefore(head,stack,copy_node(data))
435                    end
436                    current = default
437                end
438            elseif current > 0 then
439                head = insertnodebefore(head,stack,copy_node(nsnone))
440                current = 0
441                current_selector = 0
442            end
443            check = false
444        end
445    end
446    return head
447end
448
449states.selective = function(namespace,attribute,head,default)
450    return selective(attribute,head,default)
451end
452
453-- Ideally the next one should be merged with the previous but keeping it separate is
454-- safer. We deal with two situations: efficient boxwise (layoutareas) and mixed layers
455-- (as used in the stepper). In the stepper we cannot use the box branch as it involves
456-- paragraph lines and then gets mixed up. A messy business (esp since we want to be
457-- efficient).
458--
459-- Todo: make a better stacker. Keep track (in attribute) about nesting level. Not
460-- entirely trivial and a generic solution is nicer (compares to the exporter).
461
462local function stacked(attribute,head,default) -- no inheritance, but list-wise
463    local stack   = head
464    local current = default or 0
465    local depth   = 0
466    local check   = false
467    local leader  = false
468    while stack do
469        local id = getid(stack)
470        if id == glyph_code then
471            check = true
472        elseif id == glue_code then
473            leader = getleader(stack)
474            if leader then
475                check = true
476            end
477        elseif id == hlist_code or id == vlist_code then
478            local content = getlist(stack)
479            if content then
480             -- the problem is that broken lines gets the attribute which can be a later one
481                local list
482                if subtype == container_code then
483                    check   = true
484                    current = 0
485                end
486                if nslistwise then
487                    local a = getattr(stack,attribute)
488                    if a and current ~= a and nslistwise[a] then -- viewerlayer / needs checking, see below
489                        local p = current
490                        current = a
491                        head    = insertnodebefore(head,stack,copy_node(nsdata[a]))
492                        list    = stacked(attribute,content,current) -- two return values
493                        head, stack = insertnodeafter(head,stack,copy_node(nsnone))
494                        current = p
495                    else
496                        list = stacked(attribute,content,current)
497                    end
498                else
499                    list = stacked(attribute,content,current)
500                end
501                if content ~= list then
502                    setlist(stack,list) -- only if ok
503                end
504            end
505        elseif id == rule_code then
506            check = subtype == virtualrule_code or hasdimensions(stack)
507        end
508        if check then
509            local a = getattr(stack,attribute)
510            if a then
511                if current ~= a then
512                    head    = insertnodebefore(head,stack,copy_node(nsdata[a]))
513                    depth   = depth + 1
514                    current = a
515                end
516                if leader then
517                    local content = getlist(leader)
518                    if content then
519                        local list = stacked(attribute,content,current)
520                        if leader ~= list then
521                            setleader(stack,list) -- only if ok
522                        end
523                    end
524                    leader = false
525                end
526            elseif default > 0 then
527                --
528            elseif current > 0 then
529                head    = insertnodebefore(head,stack,copy_node(nsnone))
530                depth   = depth - 1
531                current = 0
532            end
533            check = false
534        end
535        stack = getnext(stack)
536    end
537    while depth > 0 do
538        head = insertnodeafter(head,stack,copy_node(nsnone))
539        depth = depth - 1
540    end
541    return head
542end
543
544states.stacked = function(namespace,attribute,head,default)
545    return stacked(attribute,head,default)
546end
547
548local function stacker(attribute,head,default) -- no inheritance, but list-wise
549    local stacked  = false
550    local current  = head
551    local previous = head
552    local attrib   = default or unsetvalue
553    local check    = false
554    local leader   = false
555    for n, id, subtype, content in nextcontent, head do
556        local current = n
557        if id == glyph_code then
558            check = true
559        elseif id == glue_code then
560            leader = content
561            check  = true
562        elseif id == hlist_code or id == vlist_code then
563            local list
564            if subtype == container_code then
565                check = true
566            end
567            if nslistwise then
568                local a = getattr(current,attribute)
569                if a and attrib ~= a and nslistwise[a] then -- viewerlayer
570                    head = insertnodebefore(head,current,copy_node(nsdata[a]))
571                    list = stacker(attribute,content,a)
572                    if list ~= content then
573                        setlist(current,list)
574                    end
575                    head, current = insertnodeafter(head,current,copy_node(nsnone))
576                else
577                    list = stacker(attribute,content,attrib)
578                    if list ~= content then
579                        setlist(current,list)
580                    end
581                end
582            else
583-- if content then -- test this !
584                list = stacker(attribute,content,default)
585                if list ~= content then
586                    setlist(current,list)
587                end
588-- end
589            end
590        elseif id == rule_code then
591            check = hasdimensions(current)
592            if subtype == virtualrule_code then
593                check = true
594         -- elseif subtype == boxrule_code or subtype == imagerule_code or subtype == emptyrule_code then
595            elseif subtype == emptyrule_code then
596                check = false
597            else
598                check = hasdimensions(current)
599            end
600        end
601        if check then
602            local a = getattr(current,attribute) or unsetvalue
603            if a ~= attrib then
604                if not stacked then
605                    stacked = true
606                    nsbegin()
607                end
608                local n = nsstep(a)
609                if n then
610                    head = insertnodebefore(head,current,n) -- a
611                end
612                attrib = a
613                if leader then
614                    -- tricky as a leader has to be a list so we cannot inject before
615                    local content = getlist(leader)
616                    if content then
617                        local list = stacker(attribute,leader,attrib)
618                        if leader ~= list then
619                            setleader(current,list)
620                        end
621                    end
622
623                    leader = false
624                end
625            end
626            check = false
627        end
628        previous = current
629    end
630    if stacked then
631        local n = nsend()
632        while n do
633            head = insertnodeafter(head,previous,n)
634            n = nsend()
635        end
636    end
637    return head
638end
639
640states.stacker = function(namespace,attribute,head,default)
641    local head = stacker(attribute,head,default)
642    nsreset()
643    return head
644end
645
646-- -- --
647
648statistics.register("attribute processing time", function()
649    return statistics.elapsedseconds(attributes,"front- and backend")
650end)
651