node-aux.lmt /size: 12 Kb    last modification: 2025-02-21 11:03
1if not modules then modules = { } end modules ['node-aux'] = {
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-- todo: n1 .. n2 : __concat metatable
10
11local type, tostring = type, tostring
12
13local nodes          = nodes
14local context        = context
15
16local utfvalues      = utf.values
17
18local nodecodes      = nodes.nodecodes
19
20local glyph_code     <const> = nodecodes.glyph
21local hlist_code     <const> = nodecodes.hlist
22local vlist_code     <const> = nodecodes.vlist
23local attribute_code <const> = nodecodes.attribute -- temporary
24local par_code       <const> = nodecodes.par
25
26local nuts           = nodes.nuts
27local tonut          = nuts.tonut
28local tonode         = nuts.tonode
29local vianuts        = nuts.vianuts
30
31local getbox         = nuts.getbox
32local getnext        = nuts.getnext
33local getid          = nuts.getid
34local getlist        = nuts.getlist
35local getattr        = nuts.getattr
36local getboth        = nuts.getboth
37local getprev        = nuts.getprev
38local getwidth       = nuts.getwidth
39local setwidth       = nuts.setwidth
40local getboxglue     = nuts.getboxglue
41local setboxglue     = nuts.setboxglue
42
43local setfield       = nuts.setfield
44local setattr        = nuts.setattr
45local setlink        = nuts.setlink
46local setlist        = nuts.setlist
47local setnext        = nuts.setnext
48local setprev        = nuts.setprev
49local setattrlist    = nuts.setattrlist
50
51local traversers     = nuts.traversers
52local nextnode       = traversers.node
53local nextglyph      = traversers.glyph
54
55local flushnode      = nuts.flush
56local flushlist      = nuts.flushlist
57local hpack_nodes    = nuts.hpack
58local vpack_nodes    = nuts.vpack
59local unsetattribute = nuts.unsetattribute
60----- firstglyphnode = nuts.firstglyphnode
61----- firstglyph     = nuts.firstglyph
62----- firstchar      = nuts.firstchar
63local copy_node      = nuts.copy
64local find_tail      = nuts.tail
65local getbox         = nuts.getbox
66local count          = nuts.count
67local isglyph        = nuts.isglyph
68
69local nodepool       = nuts.pool
70local new_glue       = nodepool.glue
71local new_glyph      = nodepool.glyph
72
73local unsetvalue     <const> = attributes.unsetvalue
74
75local current_font   = font.current
76
77local texsetbox      = tex.setbox
78
79local report_error   = logs.reporter("node-aux:error")
80
81local function takebox(id)
82    local box = getbox(id)
83    if box then
84        local list = getlist(box)
85        setlist(box,nil)
86        local copy = copy_node(box)
87        if list then
88            setlist(copy,list)
89        end
90        texsetbox(id,false)
91        return copy
92    end
93end
94
95function nodes.takebox(id)
96    local b = takebox(id)
97    if b then
98        return tonode(b)
99    end
100end
101
102if not nuts.migratebox then -- todo: add to nodes.direct, or maybe getmigrate, setmigrate
103
104    local setpre  = nuts.setpre
105    local setpost = nuts.setpost
106    local getpre  = nuts.getpre
107    local getpost = nuts.getpost
108
109    function nuts.migratebox(source,target)
110        setpre(target,getpre(source))
111        setpost(target,getpost(source))
112        setpre(source)
113        setpost(source)
114    end
115
116end
117
118local splitbox = tex.splitbox
119nodes.splitbox = splitbox
120
121function nuts.splitbox(id,height)
122    return tonut(splitbox(id,height))
123end
124
125-- function nodes.takelist(n)
126--     -- when we need it
127-- end
128
129function nuts.takelist(n)
130    local l = getlist(n)
131    setlist(n)
132    flushnode(n)
133    return l
134end
135
136nuts.takebox = takebox
137tex.takebox  = nodes.takebox -- sometimes more clear
138
139-- so far
140
141local function repackhlist(list,...)
142    local temp, b = hpack_nodes(list,...)
143    list = getlist(temp)
144    setlist(temp)
145    flushnode(temp)
146    return list, b
147end
148
149nuts.repackhlist = repackhlist
150
151function nodes.repackhlist(list,...)
152    local list, b = repackhlist(tonut(list),...)
153    return tonode(list), b
154end
155
156-- local function setattributes(head,attr,value)
157--     for n, id in nextnode, head do
158--         setattr(n,attr,value)
159--         if id == hlist_node or id == vlist_node then
160--             setattributes(getlist(n),attr,value)
161--         end
162--     end
163-- end
164
165-- local function setunsetattributes(head,attr,value)
166--     for n, id in nextnode, head do
167--         if not getattr(n,attr) then
168--             setattr(n,attr,value)
169--         end
170--         if id == hlist_code or id == vlist_code then
171--             setunsetattributes(getlist(n),attr,value)
172--         end
173--     end
174-- end
175
176-- local function unsetattributes(head,attr)
177--     for n, id in nextnode, head do
178--         setattr(n,attr,unsetvalue)
179--         if id == hlist_code or id == vlist_code then
180--             unsetattributes(getlist(n),attr)
181--         end
182--     end
183-- end
184
185-- function nuts.firstcharacter(n,untagged) -- tagged == subtype > 255
186--     if untagged then
187--         return firstchar(n)
188--     else
189--         return firstglyph(n)
190--     end
191-- end
192--
193-- nodes.firstcharacter = vianuts(firstcharacter)
194--
195-- local function firstcharinbox(n,untagged)
196--     local l = getlist(getbox(n))
197--     if l then
198--         return firstglyphnode(l)
199--     end
200-- end
201--
202-- nuts .firstcharinbox = firstcharinbox
203
204-- this depends on fonts, so we have a funny dependency ... will be
205-- sorted out .. we could make tonodes a plugin into this
206
207local function tonodes(str,fnt,attr) -- (str,template_glyph) -- moved from blob-ini
208    if not str or str == "" then
209        return
210    end
211    local head, tail, space, fnt, template = nil, nil, nil, nil, nil
212    if not fnt then
213        fnt = current_font()
214    elseif type(fnt) ~= "number" and getid(fnt) == glyph_code then -- so it has to be a real node
215        fnt, template = nil, tonut(fnt)
216    end
217    for s in utfvalues(str) do
218        local n
219        if s == 32 then
220            if space then
221                n = copy_node(space)
222            elseif fonts then -- depedency
223                local parameters = fonts.hashes.identifiers[fnt].parameters
224                space = new_glue(parameters.space,parameters.spacestretch,parameters.spaceshrink)
225                n = space
226            end
227        elseif template then
228            n = copy_node(template)
229            setvalue(n,"char",s)
230        else
231            n = new_glyph(fnt,s)
232        end
233        if attr then -- normally false when template
234            setattrlist(n,attr)
235        end
236        if head then
237            setlink(tail,n)
238        else
239            head = n
240        end
241        tail = n
242    end
243    return head, tail
244end
245
246nuts.tonodes = tonodes
247
248nodes.tonodes = function(str,fnt,attr)
249    local head, tail = tonodes(str,fnt,attr)
250    return tonode(head), tonode(tail)
251end
252
253local function link(list,currentfont,currentattr,head,tail) -- an oldie, might be replaced
254    for i=1,#list do
255        local n = list[i]
256        if n then
257            local tn = type(n)
258            if tn == "string" then
259                if #tn > 0 then
260                    if not currentfont then
261                        currentfont = current_font()
262                    end
263                    local h, t = tonodes(n,currentfont,currentattr)
264                    if not h then
265                        -- skip
266                    elseif not head then
267                        head, tail = h, t
268                    else
269                        setnext(tail,h)
270                        setprev(h,t)
271                        tail = t
272                    end
273                end
274            elseif tn == "table" then
275                if #tn > 0 then
276                    if not currentfont then
277                        currentfont = current_font()
278                    end
279                    head, tail = link(n,currentfont,currentattr,head,tail)
280                end
281            elseif not head then
282                head = n
283                tail = find_tail(n)
284            elseif getid(n) == attribute_code then
285                -- weird case
286                report_error("weird node type in list at index %s:",i)
287                for i=1,#list do
288                    local l = list[i]
289                    report_error("%3i: %s %S",i,getid(l) == attribute_code and "!" or ">",l)
290                end
291                os.exit()
292            else
293                setlink(tail,n)
294                if getnext(n) then
295                    tail = find_tail(n)
296                else
297                    tail = n
298                end
299            end
300        else
301            -- permitting nil is convenient
302        end
303    end
304    return head, tail
305end
306
307nuts.link = link
308
309nodes.link = function(list,currentfont,currentattr,head,tail)
310    local head, tail = link(list,currentfont,currentattr,tonut(head),tonut(tail))
311    return tonode(head), tonode(tail)
312end
313
314local function locate(start,wantedid,wantedsubtype)
315    for n, id, subtype in nextnode, start do
316        if id == wantedid then
317            if not wantedsubtype or subtype == wantedsubtype then
318                return n
319            end
320        elseif id == hlist_code or id == vlist_code then
321            local found = locate(getlist(n),wantedid,wantedsubtype)
322            if found then
323                return found
324            end
325        end
326    end
327end
328
329nuts.locate = locate
330
331function nodes.locate(start,wantedid,wantedsubtype)
332    local found = locate(tonut(start),wantedid,wantedsubtype)
333    return found and tonode(found)
334end
335
336local function rehpack(n,width)
337    local head = getlist(n)
338    local size = width or getwidth(n)
339    local temp = hpack_nodes(head,size,"exactly")
340    setwidth(n,size)
341    local set, order, sign = getboxglue(temp)
342    setboxglue(n,set,order,sign)
343    setlist(temp)
344    flushnode(temp)
345    return n
346end
347
348nuts.rehpack = rehpack
349
350function nodes.rehpack(n,...)
351    rehpack(tonut(n),...)
352end
353
354do
355
356    local parcodes      = nodes.parcodes
357    local hmodepar_code <const> = parcodes.hmodepar
358    local vmodepar_code <const> = parcodes.vmodepar
359
360    local getnest       = tex.getnest
361    local getidsubtype  = nuts.getidsubtype
362
363    function nuts.setparproperty(action,...)
364        local tail = tonut(getnest().tail)
365        while tail do
366            local id, subtype = getidsubtype(tail)
367            if id == par_code then
368                if subtype == hmodepar_code or subtype == vmodepar_code then
369                    return action(tail,...)
370                else
371                    -- something is wrong here
372                end
373            end
374            tail = getprev(tail)
375        end
376    end
377
378    local startofpar = nuts.startofpar
379
380    function nodes.startofpar(n)
381        return startofpar(tonut(n))
382    end
383
384end
385
386function nodes.hpack(h,...) return tonode(hpack_nodes(tonut(h),...)) end
387function nodes.vpack(h,...) return tonode(vpack_nodes(tonut(h),...)) end
388
389--
390
391local report_slide = logs.reporter("nodes", "slide")
392
393function nuts.checkslide(head,banner)
394    local c = head
395    local x = nil
396    local b = false
397    while c do
398        local p, n = getboth(c)
399        if p and p ~= x then
400            b = true
401        end
402        x = c
403        c = n
404    end
405    if b then
406        report_slide("")
407        report_slide(banner or "?")
408        report_slide("")
409        c = head
410        x = nil
411        while c do
412            local p, n = getboth(c)
413            report_slide("%s %s",(p and p ~= x and "!") or " ",tostring(tonode(c)))
414            x = c
415            c = getnext(c)
416        end
417        report_slide("")
418        nuts.show(head)
419        report_slide("")
420        nuts.slide(head)
421    end
422end
423
424-- In the end I moved some older experimental mkiv code here because it was bit cleaner
425-- in math extensions. This is not the best place but the old module was already wiped.
426
427do
428
429    local actions = { }
430
431    callback.register("tail_append", function(n,data,detail)
432        local action = actions[data]
433        if action then
434            return action(n,data,detail)
435        else
436            return n
437        end
438    end)
439
440    -- We don't have that many properties (a few in math where we run into a
441    -- processing order issue, otherwise we could use a node list parser).
442
443    local properties   = { }
444
445    local setproperty  = nodes.setproperty
446    local luaboundary  = nodes.pool.luaboundary
447
448    local a_properties <const> = 1
449
450    actions[a_properties] = function(n,data,detail)
451        local property = properties[detail]
452        if property then
453            setproperty(n,property)
454            property[detail] = false
455        end
456        return n
457    end
458
459   function nodes.upcomingproperties(data)
460       for i=1,#properties+1 do
461           if not properties[i] then
462               properties[i] = data
463               return luaboundary(a_properties,i)
464           end
465       end
466   end
467
468end
469