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