node-aux.lua /size: 13 Kb    last modification: 2021-10-28 13:50
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         = nodecodes.glyph
21local hlist_code         = nodecodes.hlist
22local vlist_code         = nodecodes.vlist
23local attributelist_code = nodecodes.attributelist -- temporary
24local par_code           = 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 getsubtype         = nuts.getsubtype
35local getlist            = nuts.getlist
36local getattr            = nuts.getattr
37local getboth            = nuts.getboth
38local getprev            = nuts.getprev
39local getwidth           = nuts.getwidth
40local setwidth           = nuts.setwidth
41local getboxglue         = nuts.getboxglue
42local setboxglue         = nuts.setboxglue
43
44local setfield           = nuts.setfield
45local setattr            = nuts.setattr
46local setlink            = nuts.setlink
47local setlist            = nuts.setlist
48local setnext            = nuts.setnext
49local setprev            = nuts.setprev
50local setattrlist        = nuts.setattrlist
51
52local traversers         = nuts.traversers
53local nextnode           = traversers.node
54local nextglyph          = traversers.glyph
55
56local flushnode          = nuts.flush
57local flushlist          = nuts.flushlist
58local hpack_nodes        = nuts.hpack
59local unsetattribute     = nuts.unsetattribute
60local firstglyph         = nuts.firstglyph
61local copy_node          = nuts.copy
62local find_tail          = nuts.tail
63local getbox             = nuts.getbox
64local count              = nuts.count
65local isglyph            = nuts.isglyph
66
67local nodepool           = nuts.pool
68local new_glue           = nodepool.glue
69local new_glyph          = nodepool.glyph
70
71local unsetvalue         = attributes.unsetvalue
72
73local current_font       = font.current
74
75local texsetbox          = tex.setbox
76
77local report_error       = logs.reporter("node-aux:error")
78
79-- At some point we figured that copying before using was the safest bet
80-- when dealing with boxes at the tex end. This is because tex also needs
81-- to manage the grouping (i.e. savestack). However, there is an easy
82-- solution that keeps the tex end happy as tex.setbox deals with this. The
83-- overhead of one temporary list node is neglectable.
84--
85-- function tex.takebox(id)
86--     local box = tex.getbox(id)
87--     if box then
88--         local copy = nodes.copy(box)
89--         local list = box.list
90--         copy.list = list
91--         box.list = nil
92--         tex.setbox(id,nil)
93--         return copy
94--     end
95-- end
96
97local function takebox(id)
98    local box = getbox(id)
99    if box then
100        local list = getlist(box)
101        setlist(box,nil)
102        local copy = copy_node(box)
103        if list then
104            setlist(copy,list)
105        end
106        texsetbox(id,false)
107        return copy
108    end
109end
110
111function nodes.takebox(id)
112    local b = takebox(id)
113    if b then
114        return tonode(b)
115    end
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
156if not nuts.setattributes then
157
158    local function setattributes(head,attr,value)
159        for n, id in nextnode, head do
160            setattr(n,attr,value)
161            if id == hlist_node or id == vlist_node then
162                setattributes(getlist(n),attr,value)
163            end
164        end
165    end
166
167    nuts .setattributes = setattributes
168    nodes.setattributes = vianuts(setattributes)
169
170end
171
172if not nuts.setunsetattributes then
173
174    local function setunsetattributes(head,attr,value)
175        for n, id in nextnode, head do
176            if not getattr(n,attr) then
177                setattr(n,attr,value)
178            end
179            if id == hlist_code or id == vlist_code then
180                setunsetattributes(getlist(n),attr,value)
181            end
182        end
183    end
184
185    nuts .setunsetattributes = setunsetattributes
186    nodes.setunsetattributes = vianuts(setunsetattributes)
187
188end
189
190if not nuts.unsetattributes then
191
192    local function unsetattributes(head,attr)
193        for n, id in nextnode, head do
194            setattr(n,attr,unsetvalue)
195            if id == hlist_code or id == vlist_code then
196                unsetattributes(getlist(n),attr)
197            end
198        end
199    end
200
201    nuts .unsetattributes = unsetattributes
202    nodes.unsetattributes = vianuts(unsetattributes)
203
204end
205
206function nuts.firstcharacter(n,untagged) -- tagged == subtype > 255
207    if untagged then
208        return firstglyph(n)
209    else
210        for g in nextglyph ,n do
211            return g
212        end
213    end
214end
215
216local function firstcharinbox(n)
217    local l = getlist(getbox(n))
218    if l then
219        for g, c in nextglyph, l do
220            return c
221        end
222    end
223    return 0
224end
225
226nuts .firstcharinbox = firstcharinbox
227nodes.firstcharinbox = firstcharinbox -- hm, ok ?
228nodes.firstcharacter = vianuts(firstcharacter)
229
230interfaces.implement {
231    name      = "buildtextaccent",
232    arguments = "integer",
233    actions   = function(n) -- Is this crap really used? Or was it an experiment?
234        local char = firstcharinbox(n)
235        if char > 0 then
236         -- context.accent(false,char)
237            context([[\accent%s\relax]],char)
238        end
239    end
240}
241
242-- this depends on fonts, so we have a funny dependency ... will be
243-- sorted out .. we could make tonodes a plugin into this
244
245local function tonodes(str,fnt,attr) -- (str,template_glyph) -- moved from blob-ini
246    if not str or str == "" then
247        return
248    end
249    local head, tail, space, fnt, template = nil, nil, nil, nil, nil
250    if not fnt then
251        fnt = current_font()
252    elseif type(fnt) ~= "number" and getid(fnt) == glyph_code then -- so it has to be a real node
253        fnt, template = nil, tonut(fnt)
254    end
255    for s in utfvalues(str) do
256        local n
257        if s == 32 then
258            if space then
259                n = copy_node(space)
260            elseif fonts then -- depedency
261                local parameters = fonts.hashes.identifiers[fnt].parameters
262                space = new_glue(parameters.space,parameters.space_stretch,parameters.space_shrink)
263                n = space
264            end
265        elseif template then
266            n = copy_node(template)
267            setvalue(n,"char",s)
268        else
269            n = new_glyph(fnt,s)
270        end
271        if attr then -- normally false when template
272            setattrlist(n,attr)
273        end
274        if head then
275            setlink(tail,n)
276        else
277            head = n
278        end
279        tail = n
280    end
281    return head, tail
282end
283
284nuts.tonodes = tonodes
285
286nodes.tonodes = function(str,fnt,attr)
287    local head, tail = tonodes(str,fnt,attr)
288    return tonode(head), tonode(tail)
289end
290
291local function link(list,currentfont,currentattr,head,tail) -- an oldie, might be replaced
292    for i=1,#list do
293        local n = list[i]
294        if n then
295            local tn = type(n)
296            if tn == "string" then
297                if #tn > 0 then
298                    if not currentfont then
299                        currentfont = current_font()
300                    end
301                    local h, t = tonodes(n,currentfont,currentattr)
302                    if not h then
303                        -- skip
304                    elseif not head then
305                        head, tail = h, t
306                    else
307                        setnext(tail,h)
308                        setprev(h,t)
309                        tail = t
310                    end
311                end
312            elseif tn == "table" then
313                if #tn > 0 then
314                    if not currentfont then
315                        currentfont = current_font()
316                    end
317                    head, tail = link(n,currentfont,currentattr,head,tail)
318                end
319            elseif not head then
320                head = n
321                tail = find_tail(n)
322            elseif getid(n) == attributelist_code then
323                -- weird case
324                report_error("weird node type in list at index %s:",i)
325                for i=1,#list do
326                    local l = list[i]
327                    report_error("%3i: %s %S",i,getid(l) == attributelist_code and "!" or ">",l)
328                end
329                os.exit()
330            else
331                setlink(tail,n)
332                if getnext(n) then
333                    tail = find_tail(n)
334                else
335                    tail = n
336                end
337            end
338        else
339            -- permitting nil is convenient
340        end
341    end
342    return head, tail
343end
344
345nuts.link = link
346
347nodes.link = function(list,currentfont,currentattr,head,tail)
348    local head, tail = link(list,currentfont,currentattr,tonut(head),tonut(tail))
349    return tonode(head), tonode(tail)
350end
351
352local function locate(start,wantedid,wantedsubtype)
353    for n, id, subtype in nextnode, start do
354        if id == wantedid then
355            if not wantedsubtype or subtype == wantedsubtype then
356                return n
357            end
358        elseif id == hlist_code or id == vlist_code then
359            local found = locate(getlist(n),wantedid,wantedsubtype)
360            if found then
361                return found
362            end
363        end
364    end
365end
366
367nuts.locate = locate
368
369function nodes.locate(start,wantedid,wantedsubtype)
370    local found = locate(tonut(start),wantedid,wantedsubtype)
371    return found and tonode(found)
372end
373
374local function rehpack(n,width)
375    local head = getlist(n)
376    local size = width or getwidth(n)
377    local temp = hpack_nodes(head,size,"exactly")
378    setwidth(n,size)
379    local set, order, sign = getboxglue(temp)
380    setboxglue(n,set,order,sign)
381    setlist(temp)
382    flushnode(temp)
383    return n
384end
385
386nuts.rehpack = rehpack
387
388function nodes.rehpack(n,...)
389    rehpack(tonut(n),...)
390end
391
392do
393
394    local parcodes      = nodes.parcodes
395    local hmodepar_code = parcodes.vmode_par
396    local vmodepar_code = parcodes.hmode_par
397
398    local getnest       = tex.getnest
399    local getsubtype    = nuts.getsubtype
400
401    function nuts.setparproperty(action,...)
402        local tail = tonut(getnest().tail)
403        while tail do
404            if getid(tail) == par_code then
405                local s = getsubtype(tail)
406                if s == hmodepar_code or s == vmodepar_code then
407                    return action(tail,...)
408                else
409                    -- something is wrong here
410                end
411            end
412            tail = getprev(tail)
413        end
414    end
415
416    local getsubtype = nodes.getsubtype
417
418    function nodes.startofpar(n)
419        local s = getsubtype(n)
420        return s == hmodepar_code or s == vmodepar_code
421    end
422
423end
424
425if not nuts.getnormalizedline then
426
427    local nextnode           = traversers.glue
428    local findfail           = nuts.tail
429
430    local getid              = nuts.getid
431    local getsubtype         = nuts.getsubtype
432    local getlist            = nuts.getlist
433    local getwidth           = nuts.getwidth
434
435    local nodecodes          = nodes.nodecodes
436    local skipcodes          = nodes.skipcodes
437
438    local hlist_code         = nodecodes.hlist
439    local line_code          = nodecodes.line
440
441    local leftskip_code      = skipcodes.leftskip
442    local rightskip_code     = skipcodes.rightskip
443    local lefthangskip_code  = skipcodes.lefthangskip
444    local righthangskip_code = skipcodes.righthangskip
445    local indentskip_code    = skipcodes.indentskip
446    local parfillskip_code   = skipcodes.parfillskip
447
448    nuts.findnode = node.direct.find_node or function(h,t,s)
449        if h then
450            if s then
451                for node, subtype in traversers[t] do
452                    if s == subtype then
453                        return current
454                    end
455                end
456            else
457                for node, subtype in traversers[t] do
458                    return current, subtype
459                end
460            end
461        end
462    end
463
464
465    function nuts.getnormalizedline(h)
466        if getid(h) == hlist_code and getsubtype(h) == line_code then
467            local ls, rs = 0, 0
468            local lh, rh = 0, 0
469            local lp, rp = 0, 0
470            local is     = 0
471            local h = getlist(h)
472            local t = findtail(h)
473            for n, subtype in nextglue, h do
474                if     subtype == leftskip_code      then ls = getwidth(n)
475                elseif subtype == rightskip_code     then rs = getwidth(n)
476                elseif subtype == lefthangskip_code  then lh = getwidth(n)
477                elseif subtype == righthangskip_code then rh = getwidth(n)
478                elseif subtype == indentskip_code    then is = getwidth(n)
479                elseif subtype == parfillskip_code   then rp = getwidth(n)
480                end
481            end
482            return {
483                leftskip         = ls,
484                rightskip        = rs,
485                lefthangskip     = lh,
486                righthangskip    = rh,
487                indent           = is,
488                parfillrightskip = rp,
489                parfillleftskip  = lp,
490                head             = h,
491                tail             = t,
492            }
493        end
494    end
495
496end
497