node-fin.lmt /size: 23 Kb    last modification: 2023-12-21 09:44
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     = rulecodes.box
45local imagerule_code   = rulecodes.image
46local emptyrule_code   = rulecodes.empty
47local virtualrule_code = rulecodes.virtual
48
49local container_code   = nodes.listcodes.container
50
51local glyph_code       = nodecodes.glyph
52local disc_code        = nodecodes.disc
53local glue_code        = nodecodes.glue
54local rule_code        = nodecodes.rule
55local hlist_code       = nodecodes.hlist
56local vlist_code       = 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            local list = process(attribute,content,inheritance,default)
211            if content ~= list then
212                setlist(stack,list)
213            end
214        elseif id == rule_code then
215            check = subtype == virtualrule_code or hasdimensions(stack)
216        end
217        -- much faster this way than using a check() and nested() function
218        if check then
219            local c = getattr(stack,attribute)
220            if c then
221                if default and c == inheritance then
222                    if current ~= default then
223                        head    = insertnodebefore(head,stack,copy_node(nsdata[default]))
224                        current = default
225                    end
226                elseif current ~= c then
227                    head    = insertnodebefore(head,stack,copy_node(nsdata[c]))
228                    current = c
229                end
230                if leader then
231                    local savedcurrent = current
232                    local ci = getid(leader)
233                    if ci == hlist_code or ci == vlist_code then
234                        -- else we reset inside a box unneeded, okay, the downside is
235                        -- that we trigger color in each repeated box, so there is room
236                        -- for improvement here
237                        current = 0
238                    end
239                    local list = process(attribute,leader,inheritance,default)
240                    if leader ~= list then
241                        setleader(stack,list)
242                    end
243                    current = savedcurrent
244                    leader = false
245                end
246            elseif default and inheritance then
247                if current ~= default then
248                    head    = insertnodebefore(head,stack,copy_node(nsdata[default]))
249                    current = default
250                end
251            elseif current > 0 then
252                head    = insertnodebefore(head,stack,copy_node(nsnone))
253                current = 0
254            end
255            check = false
256        end
257    end
258    return head
259end
260
261states.process = function(namespace,attribute,head,default)
262    return process(attribute,head,default)
263end
264
265local function simple(attribute,head)
266    local check  = false
267    local leader = nil
268    for stack, id, subtype, content in nextcontent, head do
269        if id == glyph_code or id == disc_code then
270            check = true
271        elseif id == glue_code then
272            check  = true
273            leader = content
274        elseif id == hlist_code or id == vlist_code then
275            if subtype == container_code or hasgeometry(stack) then
276                local outer = getattr(stack,attribute)
277                if outer then
278                    if current ~= outer then
279                        if current > 0 then
280                            head = insertnodebefore(head,stack,copy_node(nsnone))
281                        end
282                        head    = insertnodebefore(head,stack,copy_node(nsdata[c]))
283                        current = outer
284                    end
285                elseif current > 0 then
286                    head    = insertnodebefore(head,stack,copy_node(nsnone))
287                    current = 0
288                end
289            end
290            local list = simple(attribute,content)
291            if content ~= list then
292                setlist(stack,list)
293            end
294        elseif id == rule_code then
295            check = subtype == virtualrule_code or hasdimensions(stack)
296        end
297        if check then
298            local c = getattr(stack,attribute)
299            if c then
300                if current ~= c then
301                    if current > 0 then
302                        head = insertnodebefore(head,stack,copy_node(nsnone))
303                    end
304                    head    = insertnodebefore(head,stack,copy_node(nsdata[c]))
305                    current = c
306                end
307                if leader then
308                    local savedcurrent = current
309                    local ci = getid(leader)
310                    if ci == hlist_code or ci == vlist_code then
311                        current = 0
312                    end
313                    local list = simple(attribute,leader)
314                    if leader ~= list then
315                        setleader(stack,list)
316                    end
317                    current = savedcurrent
318                    leader = false
319                end
320            elseif current > 0 then
321                head    = insertnodebefore(head,stack,copy_node(nsnone))
322                current = 0
323            end
324            check = false
325        end
326    end
327    return head
328end
329
330states.simple = function(namespace,attribute,head,default)
331    return simple(attribute,head,default)
332end
333
334-- We can force a selector, e.g. document wide color spaces, saves a little watch
335-- out, we need to check both the selector state (like colorspace) and the main
336-- state (like color), otherwise we get into troubles when a selector state changes
337-- while the main state stays the same (like two glyphs following each other with
338-- the same color but different color spaces e.g. \showcolor). The triggering
339-- mechanism has been removed because it was never really used, but the original can
340-- be seen in the mkiv (lua) code.
341
342local function selective(attribute,head,inheritance,default) -- two attributes
343    local check  = false
344    local leader = nil
345    for stack, id, subtype, content in nextcontent, head do
346        if id == glyph_code or id == disc_code then
347            check = true
348        elseif id == glue_code then
349            check  = true
350            leader = content
351        elseif id == hlist_code or id == vlist_code then
352            local outer, s
353            if subtype == container_code or hasgeometry(stack) then
354                outer, s = getattrs(stack,attribute,nsselector)
355                if outer then
356                    if default and outer == inheritance then
357                        if current ~= default then
358                            local data = nsdata[default][nsforced or nsselector]
359                            if data then
360                                head = insertnodebefore(head,stack,copy_node(data))
361                            end
362                            current = default
363                        end
364                    elseif current ~= outer or current_selector ~= s then
365                        local data = nsdata[outer][nsforced or s or nsselector]
366                        if data then
367                            head = insertnodebefore(head,stack,copy_node(data))
368                        end
369                        current = outer
370                        current_selector = s
371                    end
372                elseif default and inheritance then
373                    if current ~= default then
374                        local data = nsdata[default][nsforced or s or nsselector]
375                        if data then
376                            head = insertnodebefore(head,stack,copy_node(data))
377                        end
378                        current = default
379                    end
380                elseif current > 0 then
381                    head = insertnodebefore(head,stack,copy_node(nsnone))
382                    current, current_selector = 0, 0
383                end
384            end
385            local list = selective(attribute,content,inheritance,default)
386            if content ~= list then
387                setlist(stack,list)
388            end
389        elseif id == rule_code then
390            if subtype == virtualrule_code then
391                check = true
392            elseif subtype == boxrule_code or subtype == imagerule_code or subtype == emptyrule_code then
393                -- so no redundant color stuff (only here, layers for instance should obey)
394                check = false
395            else
396                check = hasdimensions(stack)
397            end
398        end
399        if check then
400            local c, s = getattrs(stack,attribute,nsselector)
401            if c then
402                if default and c == inheritance then
403                    if current ~= default then
404                        local data = nsdata[default][nsforced or s or nsselector]
405                        if data then
406                            head = insertnodebefore(head,stack,copy_node(data))
407                        end
408                        current = default
409                    end
410                elseif current ~= c or current_selector ~= s then
411                    local data = nsdata[c][nsforced or s or nsselector]
412                    if data then
413                        head = insertnodebefore(head,stack,copy_node(data))
414                    end
415                    current = c
416                    current_selector = s
417                end
418                if leader then
419                    local list = selective(attribute,leader,inheritance,default)
420                    if leader ~= list then
421                        setleader(stack,list)
422                    end
423                    leader = false
424                end
425            elseif default and inheritance then
426                if current ~= default then
427                    local data = nsdata[default][nsforced or s or nsselector]
428                    if data then
429                        head = insertnodebefore(head,stack,copy_node(data))
430                    end
431                    current = default
432                end
433            elseif current > 0 then
434                head = insertnodebefore(head,stack,copy_node(nsnone))
435                current, current_selector = 0, 0
436            end
437            check = false
438        end
439    end
440    return head
441end
442
443states.selective = function(namespace,attribute,head,default)
444    return selective(attribute,head,default)
445end
446
447-- Ideally the next one should be merged with the previous but keeping it separate is
448-- safer. We deal with two situations: efficient boxwise (layoutareas) and mixed layers
449-- (as used in the stepper). In the stepper we cannot use the box branch as it involves
450-- paragraph lines and then gets mixed up. A messy business (esp since we want to be
451-- efficient).
452--
453-- Todo: make a better stacker. Keep track (in attribute) about nesting level. Not
454-- entirely trivial and a generic solution is nicer (compares to the exporter).
455
456local function stacked(attribute,head,default) -- no inheritance, but list-wise
457    local stack   = head
458    local current = default or 0
459    local depth   = 0
460    local check   = false
461    local leader  = false
462    while stack do
463        local id = getid(stack)
464        if id == glyph_code then
465            check = true
466        elseif id == glue_code then
467            leader = getleader(stack)
468            if leader then
469                check = true
470            end
471        elseif id == hlist_code or id == vlist_code then
472            local content = getlist(stack)
473            if content then
474             -- the problem is that broken lines gets the attribute which can be a later one
475                local list
476                if subtype == container_code then
477                    check   = true
478                    current = 0
479                end
480                if nslistwise then
481                    local a = getattr(stack,attribute)
482                    if a and current ~= a and nslistwise[a] then -- viewerlayer / needs checking, see below
483                        local p = current
484                        current = a
485                        head    = insertnodebefore(head,stack,copy_node(nsdata[a]))
486                        list    = stacked(attribute,content,current) -- two return values
487                        head, stack = insertnodeafter(head,stack,copy_node(nsnone))
488                        current = p
489                    else
490                        list = stacked(attribute,content,current)
491                    end
492                else
493                    list = stacked(attribute,content,current)
494                end
495                if content ~= list then
496                    setlist(stack,list) -- only if ok
497                end
498            end
499        elseif id == rule_code then
500            check = subtype == virtualrule_code or hasdimensions(stack)
501        end
502        if check then
503            local a = getattr(stack,attribute)
504            if a then
505                if current ~= a then
506                    head    = insertnodebefore(head,stack,copy_node(nsdata[a]))
507                    depth   = depth + 1
508                    current = a
509                end
510                if leader then
511                    local content = getlist(leader)
512                    if content then
513                        local list = stacked(attribute,content,current)
514                        if leader ~= list then
515                            setleader(stack,list) -- only if ok
516                        end
517                    end
518                    leader = false
519                end
520            elseif default > 0 then
521                --
522            elseif current > 0 then
523                head    = insertnodebefore(head,stack,copy_node(nsnone))
524                depth   = depth - 1
525                current = 0
526            end
527            check = false
528        end
529        stack = getnext(stack)
530    end
531    while depth > 0 do
532        head = insertnodeafter(head,stack,copy_node(nsnone))
533        depth = depth - 1
534    end
535    return head
536end
537
538states.stacked = function(namespace,attribute,head,default)
539    return stacked(attribute,head,default)
540end
541
542local function stacker(attribute,head,default) -- no inheritance, but list-wise
543    local stacked  = false
544    local current  = head
545    local previous = head
546    local attrib   = default or unsetvalue
547    local check    = false
548    local leader   = false
549    for current, id, subtype, content in nextcontent, head do
550        if id == glyph_code then
551            check = true
552        elseif id == glue_code then
553            leader = content
554            check  = true
555        elseif id == hlist_code or id == vlist_code then
556            local list
557            if subtype == container_code then
558                check = true
559            end
560            if nslistwise then
561                local a = getattr(current,attribute)
562                if a and attrib ~= a and nslistwise[a] then -- viewerlayer
563                    head = insertnodebefore(head,current,copy_node(nsdata[a]))
564                    list = stacker(attribute,content,a)
565                    if list ~= content then
566                        setlist(current,list)
567                    end
568                    head, current = insertnodeafter(head,current,copy_node(nsnone))
569                else
570                    list = stacker(attribute,content,attrib)
571                    if list ~= content then
572                        setlist(current,list)
573                    end
574                end
575            else
576                list = stacker(attribute,content,default)
577                if list ~= content then
578                    setlist(current,list)
579                end
580            end
581        elseif id == rule_code then
582            if subtype == virtualrule_code then
583                check = true
584            elseif subtype == boxrule_code or subtype == imagerule_code or subtype == emptyrule_code then
585                -- so no redundant color stuff (only here, layers for instance should obey)
586                check = false
587            else
588                check = hasdimensions(current)
589            end
590        end
591        if check then
592            local a = getattr(current,attribute) or unsetvalue
593            if a ~= attrib then
594                if not stacked then
595                    stacked = true
596                    nsbegin()
597                end
598                local n = nsstep(a)
599                if n then
600                    head = insertnodebefore(head,current,n) -- a
601                end
602                attrib = a
603                if leader then
604                    -- tricky as a leader has to be a list so we cannot inject before
605                    local content = getlist(leader)
606                    if content then
607                        local list = stacker(attribute,leader,attrib)
608                        if leader ~= list then
609                            setleader(current,list)
610                        end
611                    end
612
613                    leader = false
614                end
615            end
616            check = false
617        end
618
619        previous = current
620    end
621    if stacked then
622        local n = nsend()
623        while n do
624            head = insertnodeafter(head,previous,n)
625            n = nsend()
626        end
627    end
628    return head
629end
630
631states.stacker = function(namespace,attribute,head,default)
632    local head = stacker(attribute,head,default)
633    nsreset()
634    return head
635end
636
637-- -- --
638
639statistics.register("attribute processing time", function()
640    return statistics.elapsedseconds(attributes,"front- and backend")
641end)
642