node-scn.lmt /size: 15 Kb    last modification: 2025-02-21 11:03
1if not modules then modules = { } end modules ['node-scn'] = {
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
9local floor = math.floor
10
11local attributes         = attributes
12local nodes              = nodes
13
14local nuts               = nodes.nuts
15
16local getnext            = nuts.getnext
17local getprev            = nuts.getprev
18local getid              = nuts.getid
19local getattr            = nuts.getattr
20local getattrs           = nuts.getattrs
21local getsubtype         = nuts.getsubtype
22local getlist            = nuts.getlist
23local setlist            = nuts.setlist
24
25local nodecodes          = nodes.nodecodes
26local gluecodes          = nodes.gluecodes
27local kerncodes          = nodes.kerncodes
28
29local glyph_code         <const> = nodecodes.glyph
30local disc_code          <const> = nodecodes.disc
31local rule_code          <const> = nodecodes.rule
32local boundary_code      <const> = nodecodes.boundary
33local dir_code           <const> = nodecodes.dir
34local math_code          <const> = nodecodes.math
35local glue_code          <const> = nodecodes.glue
36local penalty_code       <const> = nodecodes.penalty
37local kern_code          <const> = nodecodes.kern
38local hlist_code         <const> = nodecodes.hlist
39local vlist_code         <const> = nodecodes.vlist
40
41local userskip_code      <const> = gluecodes.userskip
42local spaceskip_code     <const> = gluecodes.spaceskip
43local xspaceskip_code    <const> = gluecodes.xspaceskip
44local intermathkip_code  <const> = gluecodes.intermathskip
45local leaders_code       <const> = gluecodes.leaders
46local rightfillskip_code <const> = gluecodes.rightparfillskip
47local righthangskip_code <const> = gluecodes.righthangskip
48
49local fontkern_code           <const> = kerncodes.fontkern
50local mathshapekern_code      <const> = kerncodes.mathshapekern
51local rightmathslackkern_code <const> = kerncodes.rightmathslackkern
52
53local a_runningtext      <const> = attributes.private('runningtext')
54
55local v_yes              <const> = interfaces.variables.yes
56local v_all              <const> = interfaces.variables.all
57
58local function striprange(first,last) -- todo: dir
59    if first and last then -- just to be sure
60        if first == last then
61            return first, last
62        end
63        while first and first ~= last do
64            local id = getid(first)
65            if id == glyph_code or id == disc_code or id == dir_code or id == boundary_code then -- or id == rule_code
66                break
67            else
68                first = getnext(first)
69            end
70        end
71        if not first then
72            return nil, nil
73        elseif first == last then
74            return first, last
75        end
76        while last and last ~= first do
77            local id = getid(last)
78            if id == glyph_code or id == disc_code or id == dir_code or id == boundary_code  then -- or id == rule_code
79                break
80            else
81                local prev = getprev(last) -- luatex < 0.70 has italic correction kern not prev'd
82                if prev then
83                    last = prev
84                else
85                    break
86                end
87            end
88        end
89        if not last then
90            return nil, nil
91        end
92    end
93    return first, last
94end
95
96nuts.striprange = striprange
97
98-- todo: order and maybe other dimensions
99
100-- we can use this one elsewhere too
101--
102-- todo: functions: word, sentence
103--
104-- glyph rule unset whatsit glue margin_kern kern math disc
105
106-- we assume {glyphruns} and no funny extra kerning, ok, maybe we need
107-- a dummy character as start and end; anyway we only collect glyphs
108--
109-- this one needs to take layers into account (i.e. we need a list of
110-- critical attributes)
111
112-- omkeren class en level -> scheelt functie call in analyze
113
114-- todo: switching inside math
115
116-- handlers (some are very specialized and demanding)
117
118local maxlevel = 1
119
120local ignore = table.setmetatableindex("number")
121
122local function processtricky(attribute,data,flush,head,parent)
123    local n = head
124    if n then
125        local f, l, a, d
126        while n do
127            local id = getid(n)
128            local aa, ra = getattrs(n,attribute,a_runningtext)
129            if id == glyph_code
130             -- or id == disc_code
131             -- or id == rule_code
132             -- or id == boundary_code
133                or id == hlist_code
134                or id == vlist_code
135            then
136                if aa and ignore[aa] > 0 then
137                    aa = nil
138                end
139                if not aa then
140                    if f then
141                        head = flush(head,f,l,data[a],1,parent,false)
142                    end
143                    f, l, a = nil, nil, nil
144                elseif aa == a then
145                    if not f then -- ?
146                        f = n
147                    end
148                    l = n
149                else
150                    if f then
151                        head = flush(head,f,l,data[a],1,parent,false)
152                    end
153                    f, l, a = n, n, aa
154                end
155                if id == hlist_code or id == vlist_code then
156                    local list = getlist(n)
157                    if list then
158                        if a then
159                            ignore[a] = ignore[a] + 1
160                        end
161                        setlist(n,processtricky(attribute,data,flush,list,n))
162                        if a then
163                            ignore[a] = ignore[a] - 1
164                        end
165                    end
166                end
167            end
168            n = getnext(n)
169        end
170        if f then
171            head = flush(head,f,l,data[a],1,parent,false)
172        end
173    end
174    return head
175end
176
177local function processwords(nesting,attribute,data,flush,head,parent,skip) -- we have hlistdir and local dir
178    local n = head
179    if n then
180        local f, l, a, d, class
181        local continue, leaders, strip, level = false, false, true, -1
182        while n do
183            local id = getid(n)
184            local aa, ra = getattrs(n,attribute,a_runningtext)
185            if ra and ra < 0 then
186                processtricky(attribute,data,flush,head,parent) -- we have hlistdir and local dir
187            elseif id == glyph_code
188                or id == disc_code                                 -- can indeed be a start
189                or id == rule_code                                 -- can be a strut
190                or id == boundary_code                             -- can be between quote and text
191                or (ra and id == hlist_code and ra > 0) -- well ...
192            then
193                -- new approach
194                local dd, ll
195                if aa then
196                    dd = data[aa]
197                    ll = dd.level or 1
198                    if nesting == 1 and ll > maxlevel then
199                        maxlevel = ll
200                    end
201                    if ll == nesting then
202                        -- we're okay
203                    else
204                        while true do
205                            local nestingvalue = dd.nestingvalue
206                            if nestingvalue then
207                                dd = dd.nestingdata
208                                ll = dd.level
209                                if ll == nesting then
210                                    aa = nestingvalue
211                                    break
212                                end
213                            else
214                                -- no matching level found
215                                aa = nil
216                                dd = nil
217                                break
218                            end
219                        end
220                    end
221                end
222                -- old approach
223                if aa and aa ~= skip then
224                    if aa == a then
225                        if not f then -- ?
226                            f = n
227                        end
228                        l = n
229                    else
230                        if f then
231                            if class == aa then -- and newlevel > level then
232                                head = flush(head,f,l,d,level,parent,false)
233                            else
234                                head = flush(head,f,l,d,level,parent,strip)
235                            end
236                        end
237                        f, l, a = n, n, aa
238                        d     = dd
239                        class = aa
240                        if d then
241                            continue = d.continue
242                            level    = d.level or 1
243                            leaders  = continue == v_all
244                            continue = leaders or continue == v_yes
245                        else
246                            continue = true
247                            level    = 1
248                            leaders  = false
249                        end
250                    end
251                else
252                    if f then
253                        head = flush(head,f,l,d,level,parent,strip)
254                    end
255                    f, l, a = nil, nil, nil
256                end
257                if id == hlist_code then
258                    local list = getlist(n)
259                    if list then
260                        setlist(n,processwords(nesting,attribute,data,flush,list,n,aa))
261                    end
262                end
263                goto next
264         -- elseif id == disc_code or id == boundary_code then
265         --     if f then
266         --         l = n
267         --     end
268         --     goto next
269            elseif id == kern_code then
270                local subtype = getsubtype(n)
271                if subtype == fontkern_code then
272--                 if subtype == fontkern_code or subtype == mathshapekern_code or subtype == rightmathslackkern_code then
273                    if f then
274                        l = n
275                    end
276                    goto next
277                else
278                    goto rest
279                end
280            elseif id == math_code then
281                -- otherwise not consistent: a $b$ c vs a $b+c$ d etc
282                -- we need a special (optional) go over math variant
283                if f then
284                    head = flush(head,f,l,d,level,parent,strip)
285                    f, l, a = nil, nil, nil
286                end
287                goto next
288            elseif id == hlist_code or id == vlist_code then
289                if f then
290                    head = flush(head,f,l,d,level,parent,strip)
291                    f, l, a = nil, nil, nil
292                end
293                local list = getlist(n)
294                if list then
295                    setlist(n,processwords(nesting,attribute,data,flush,list,n,skip))
296                end
297                goto next
298            elseif id == dir_code then -- only changes in dir, we assume proper boundaries
299                if f then
300                    l = n
301                end
302                goto next
303            end
304          ::rest::
305            if f then
306                if continue then
307                    if id == penalty_code then
308                        l = n
309                        goto next
310                 -- elseif id == kern_code then
311                 --     l = n
312                 -- goto next
313                    elseif id == glue_code then
314                        -- catch \underbar{a} \underbar{a} (subtype test is needed)
315                        local subtype = getsubtype(n)
316                        if getattr(n,attribute) and (subtype == userskip_code or subtype == spaceskip_code or subtype == xspaceskip_code or subtype == intermathkip_code or (leaders and subtype >= leaders_code)) then
317                            l = n
318                        else
319                            head = flush(head,f,l,d,level,parent,strip)
320                            f, l, a = nil, nil, nil
321                        end
322                    end
323                else
324                    head = flush(head,f,l,d,level,parent,strip)
325                    f, l, a = nil, nil, nil
326                end
327            end
328          ::next::
329            n = getnext(n)
330        end
331        if f then
332            head = flush(head,f,l,d,level,parent,strip)
333        end
334    end
335    return head
336end
337
338nuts.processwords = function(attribute,data,flush,head,parent) -- we have hlistdir and local dir
339 -- print("processing words at level "..1)io.flush()
340    maxlevel = 1
341    head = processwords(1,attribute,data,flush,head,parent)
342    for i=2,maxlevel do
343     -- print("processing words at level "..i)io.flush()
344        head = processwords(i,attribute,data,flush,head,parent)
345    end
346    return head
347end
348
349-- works on lines !
350-- todo: stack because skip can change when nested
351
352local function processranges(attribute,flush,head,parent,depth,skip)
353    local n = head
354    if n then
355        local f, l, a
356        while n do
357            local id = getid(n)
358            if id == glyph_code or id == rule_code then
359                local aa = getattr(n,attribute)
360             -- if aa and (not skip or aa ~= skip) then
361                if aa then
362                    if aa == a then
363                        if not f then
364                            f = n
365                        end
366                        l = n
367                    else
368                        if f then
369                            head = flush(head,f,l,a,parent,depth)
370                        end
371                        f, l, a = n, n, aa
372                    end
373                else
374                    if f then
375                        head = flush(head,f,l,a,parent,depth)
376                    end
377                    f, l, a = nil, nil, nil
378                end
379            elseif id == disc_code or id == boundary_code then
380                if f then
381                    l = n
382                else
383                    -- weird
384                end
385            elseif id == kern_code then
386                if getsubtype(n) == fontkern_code then
387                    if f then
388                        l = n
389                    end
390                end
391         -- elseif id == penalty_code then
392            elseif id == glue_code then
393                -- todo: leaders
394--                 if getsubtype(n) == rightfillskip_code or getsubtype(n) == righthangskip_code then
395--                     break
396--                 end
397            elseif id == hlist_code or id == vlist_code then
398                local aa = getattr(n,attribute)
399             -- if aa and (not skip or aa ~= skip) then
400                if aa then
401                    if aa == a then
402                        if not f then
403                            f = n
404                        end
405                        l = n
406                    else
407                        if f then
408                            head = flush(head,f,l,a,parent,depth), true
409                        end
410                        f, l, a = n, n, aa
411                    end
412                else
413                    if f then
414                        head = flush(head,f,l,a,parent,depth), true
415                    end
416                    f, l, a = nil, nil, nil
417                end
418                local list = getlist(n)
419                if list then
420                    setlist(n,processranges(attribute,flush,list,n,depth+1,aa))
421                end
422            end
423            n = getnext(n)
424        end
425        if f then
426            head = flush(head,f,l,a,parent,depth)
427        end
428    end
429    return head
430end
431
432nuts.processranges = function(attribute,flush,head,parent) -- we have hlistdir and local dir
433    return processranges(attribute,flush,head,parent,0)
434end
435