node-ali.lmt /size: 41 Kb    last modification: 2024-01-16 09:02
1if not modules then modules = { } end modules ['node-ali'] = {
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 aligncharacter_code = 1
10local mathalign_code      = 2
11local mathmatrix_code     = 3
12
13local setmetatableindex  = table.setmetatableindex
14
15local nuts               = nodes.nuts
16local tonut              = nuts.tonut
17local tonode             = nuts.tonode
18local getwidth           = nuts.getwidth
19local setwidth           = nuts.setwidth
20local getid              = nuts.getid
21local getattr            = nuts.getattr
22local setnext            = nuts.setnext
23local getnext            = nuts.getnext
24local getprev            = nuts.getprev
25local getboth            = nuts.getboth
26local getglue            = nuts.getglue
27local setglue            = nuts.setglue
28local getwhd             = nuts.getwhd
29local setwhd             = nuts.setwhd
30local setpenalty         = nuts.setpenalty
31local getlist            = nuts.getlist
32local setlist            = nuts.setlist
33local getleader          = nuts.getleader
34local setattrlist        = nuts.setattrlist
35local setprop            = nuts.setprop
36local getprop            = nuts.getprop
37local getfont            = nuts.getfont
38local getchar            = nuts.getchar
39local addmargins         = nuts.addmargins
40local findtail           = nuts.tail
41local hasglyph           = nuts.hasglyph
42local getwordrange       = nuts.getwordrange
43local dimensions         = nuts.rangedimensions
44local flushnode          = nuts.flush
45local hpack              = nuts.hpack
46local repack             = nuts.repack
47local insertbefore       = nuts.insertbefore
48local insertafter        = nuts.insertafter
49local effectiveglue      = nuts.effectiveglue
50
51local newkern            = nuts.pool.kern
52local newrule            = nuts.pool.rule
53local newglue            = nuts.pool.glue
54
55local traversers         = nuts.traversers
56local nextrecord         = traversers.alignrecord
57local nextunset          = traversers.unset
58local nextglyph          = traversers.glyph
59local nextglue           = traversers.glue
60local nextpenalty        = traversers.penalty
61local nextboundary       = traversers.boundary
62local nextnode           = traversers.node
63local nextlist           = traversers.list
64local nextrule           = traversers.rule
65
66local nodecodes          = nodes.nodecodes
67local glyph_code         = nodecodes.glyph
68local glue_code          = nodecodes.glue
69local kern_code          = nodecodes.kern
70local disc_code          = nodecodes.disc
71local unset_code         = nodecodes.unset
72local alignrecord_code   = nodecodes.alignrecord
73local rule_code          = nodecodes.rule
74local temp_code          = nodecodes.temp
75
76local spaceskip_code     = nodes.gluecodes.spaceskip
77local xspaceskip_code    = nodes.gluecodes.xspaceskip
78local intermathskip_code = nodes.gluecodes.intermathskip
79local fontkern_code      = nodes.kerncodes.fontkern
80local row_code           = nodes.listcodes.alignment -- should be row
81local cell_code          = nodes.listcodes.cell
82local line_code          = nodes.listcodes.line
83local linepenalty_code   = nodes.penaltycodes.linepanalty
84
85-- local preamble_pass <const> = tex.alignmentcontextcodes.preamble
86-- local preroll_pass  <const> = tex.alignmentcontextcodes.preroll
87-- local wrapup_pass   <const> = tex.alignmentcontextcodes.wrapup
88
89-- todo statistics and tracing
90
91do
92
93
94    local a_alignchar = attributes.private("aligncharacter")
95
96    local method      = 2
97    local unislots    = fonts.hashes.unislots -- todo
98    local chardata    = fonts.hashes.characters
99
100    function nodes.handlers.aligncharacter(head,where,callback,attr,preamble) -- 0x1
101        if callback ~= aligncharacter_code then
102            -- nothing to do here
103        elseif where == "preroll" then
104            local attr = getattr(attr,a_alignchar) -- 1 : value doesn't matter (for now)
105            if attr then
106                local widths = { }
107                local data   = { }
108                local rows   = 0
109                local cols   = 0
110                for col in nextrecord, preamble do
111                    cols = cols + 1
112                    local w, s = getwidth(col,true)
113                    widths[cols] = { col, w, s }
114                end
115                --
116                for row in nextunset, head do
117                    rows = rows + 1
118                    local c = 0
119                    local d = { }
120                    data[rows] = d
121                    for col in nextunset, getlist(row) do
122                        c = c + 1
123                        if widths[c][2] then
124                            local list = getlist(col)
125                         -- if method == 1 then
126                         --     local left   = nil
127                         --     local right  = nil
128                         --     local middle = nil
129                         --     for g, char in nextglyph, list do
130                         --         if not left then
131                         --             left = g
132                         --         end
133                         --         if char == getattr(g,a_alignchar) then
134                         --             middle = g
135                         --         end
136                         --         right = g
137                         --     end
138                         --     d[c] = middle and { col, left, middle, right, 0, 0, getwidth(middle) } or false
139                         -- elseif method == 2 then
140                                local middle = nil
141                                -- we can either cache unislots or we can cache for this font
142                                for g, char, font in nextglyph, list do
143                                    local unicode = getattr(g,a_alignchar)
144                                    if unicode then
145                                        if char == unicode then
146                                            middle = g
147                                        elseif unislots[font][char] == unicode then
148                                            middle = g
149                                        end
150                                    end
151                                end
152                                if middle then
153                                    local left, right = getwordrange(middle) -- not real gain but handy anyway (less code too)
154                                 -- local left  = middle
155                                 -- local right = middle
156                                 -- for g, id, subtype in nextnode, middle do
157                                 --     if id == glyph_code or id == disc_code then
158                                 --         right = g
159                                 --     elseif id == kern_code and subtype == fontkern_code then
160                                 --         right = g
161                                 --     else
162                                 --         break
163                                 --     end
164                                 -- end
165                                 -- for g, id, subtype in prevnode, middle do
166                                 --     if id == glyph_code or id == disc_code then
167                                 --         left = g
168                                 --     elseif id == kern_code and subtype == fontkern_code then
169                                 --         left = g
170                                 --     else
171                                 --         break
172                                 --     end
173                                 -- end
174                                    d[c] = { col, left, middle, right, 0, 0, getwidth(middle) }
175                                else
176                                    d[c] = false
177                                end
178                         -- else
179                         --     local middle = nil
180                         --     for g, char in nextglyph, list do
181                         --         if char == getattr(g,a_alignchar) then
182                         --             middle = g
183                         --         end
184                         --     end
185                         --     if middle then
186                         --         local left  = list
187                         --         local right = findtail(list)
188                         --         if getid(left) == glue_code then
189                         --             left = getnext(left)
190                         --         end
191                         --         if getid(right) == glue_code then
192                         --             right = getprev(right)
193                         --         end
194                         --         d[c] = { col, left, middle, right, 0, 0, getwidth(middle) }
195                         --     else
196                         --         d[c] = false
197                         --     end
198                         -- end
199                        else
200                            d[c] = false
201                        end
202                    end
203                end
204                --
205                for col=1,cols do
206                    local maxl = 0
207                    local maxr = 0
208                    local minm = 0
209                    local maxm = 0
210                    local colw = widths[col]
211                    for row=1,rows do
212                        local d = data[row][col]
213                        if d then
214                            local p = d[1]
215                            local l = d[2]
216                            local m = d[3]
217                            local r = d[4]
218                            if m then
219                                local lw = l == m and 0 or dimensions(p,l,m)
220                                local rw = m == r and 0 or dimensions(p,getnext(m),getnext(r))
221                                d[5] = lw
222                                d[6] = rw
223                                if lw > maxl then
224                                    maxl = lw
225                                end
226                                if rw > maxr then
227                                    maxr = rw
228                                end
229                                local mw = d[7]
230                                if maxm == 0 then
231                                    minm = mw
232                                    maxm = mw
233                                else
234                                    if mw > maxm then
235                                        maxm = mw
236                                    end
237                                    if mw < minm then
238                                        minm = mw
239                                    end
240                                end
241                            end
242                        end
243                    end
244                    --
245                    local fixedwidth = colw[3] ~= 0
246                    --
247                    local old = colw[2]
248                    local new = old
249                    for row=1,rows do
250                        local d = data[row][col]
251                        if d then
252                            local p = d[1]
253                            local l = d[2]
254                            local m = d[3]
255                            local r = d[4]
256                            if l and m and r then
257                                local lw = d[5]
258                                local rw = d[6]
259                                local mw = d[7]
260                                dl = maxl - lw
261                                dr = maxr - rw
262                                if dl ~= 0 or dr ~= 0 or mw ~= maxm then
263                                    local lst = getlist(p)
264                                    local wid = getwidth(p)
265                                    if dl ~= 0 then
266                                        local k = newkern(dl)
267                                        lst = insertbefore(lst,l,k)
268                                        setattrlist(k,m)
269                                        setlist(p,lst)
270                                        wid = wid + dl
271                                    end
272                                    if dr ~= 0 then
273                                        local k = newkern(dr)
274                                        insertafter(lst,r,k)
275                                        setattrlist(k,m)
276                                        wid = wid + dr
277                                    end
278                                    if mw ~= maxm then
279                                        local dw = (maxm - mw)
280                                        local dx = dw / 2
281                                        addmargins(m,-dx,-dx)
282                                        wid = wid + dw
283                                    end
284                                    setwidth(p,wid)
285                                    if wid > new then
286                                        new = wid
287                                    end
288                                    setlist(p,lst)
289                                    -- somewhat fuzzy:
290                                    if fixedwidth then
291                                        local l = hpack(h,getwidth(p),"exactly")
292                                        setglue(p,getglue(l))
293                                        setlist(l)
294                                        flushnode(l)
295                                    else
296                                        setglue(p)
297                                    end
298                                    --
299                                end
300                            end
301                        end
302                    end
303                    if new > old then
304                        if fixedwidth then
305                            -- issue overflow warning
306                        else
307                            setwidth(colw[1],new)
308                        end
309                    end
310                end
311            end
312        end
313    end
314
315    local enabled = false
316
317    interfaces.implement {
318        name      = "enablealignmentcharacter",
319     -- onlyonce  = true,
320        public    = true,
321        protected = true,
322        actions   = function()
323            if not enabled then
324                nodes.tasks.enableaction("alignments", "nodes.handlers.aligncharacter") -- 0x1
325                enabled = true
326            end
327        end,
328    }
329
330end
331
332-- This will go to math-ali.lmt
333
334do
335
336    local getdata           = nuts.getdata
337    local removenode        = nuts.remove
338    local getwhd            = nuts.getwhd
339    local getheight         = nuts.getheight
340    local getdepth          = nuts.getdepth
341    local setheight         = nuts.setheight
342    local setdepth          = nuts.setdepth
343    local getglue           = nuts.getglue
344    local setoffsets        = nuts.setoffsets
345    local setsubtype        = nuts.setsubtype
346
347    local baselineskip_code = nodes.gluecodes.baselineskip
348    local lineskip_code     = nodes.gluecodes.lineskip
349
350    local alignrecord_code  = nodecodes.alignrecord
351    local hlist_code        = nodecodes.hlist
352    local unset_code        = nodecodes.unset
353 -- local rule_code         = nodecodes.rule
354
355    local nextnode          = nuts.traversers.node
356
357    local texgetdimen       = tex.getdimen
358    local texsetdimen       = tex.setdimen
359    local texgetglue        = tex.getglue
360    local texget            = tex.get
361
362    local leftmarker        = tex.boundaries.system("c_math_align_l_marker")
363    local rightmarker       = tex.boundaries.system("c_math_align_r_marker")
364
365    local a_location        = attributes.system("mathnumberlocation")
366    local a_threshold       = attributes.system("mathnumberthreshold")
367
368    local v_first          = interfaces.variables.first
369    local v_last           = interfaces.variables.last
370    local v_both           = interfaces.variables.both
371
372    -- Here:
373
374    local function openup(specification,head)
375        local inbetween   = specification.inbetween or 0
376        local height      = specification.height or 0
377        local depth       = specification.depth or 0
378        local splitmethod = specification.splitmethod or ""
379        local lines       = { }
380        for n, id, subtype, list in nextlist, head do
381            lines[#lines+1] = { n, subtype, getwhd(n) }
382        end
383        local noflines = #lines
384        if noflines > 0 then
385            local currentline = 1
386            for n, subtype in nextglue, head do
387-- print(currentline,height/65536,depth/65536)
388                -- one day we can decide what to do with intertext stuff based on the
389                -- subtype but not now ... on our agenda (intertext etc)
390                if subtype == baselineskip_code or subtype == lineskip_code then
391                    local nextline = currentline + 1
392                    local amount, stretch, shrink = getglue(n)
393-- todo: safeguard for no nextline
394                    local prevdp = lines[currentline][5]
395                    local nextht = lines[nextline][4]
396-- todo: add catch for no line here ^^^
397                    local delta  = 0
398                    if prevdp < depth then
399                        setdepth(lines[currentline][1],depth)
400                        delta = delta + (depth - prevdp)
401-- lines[currentline][5] = depth
402                    end
403                    if nextht < height then
404                        setheight(lines[nextline][1],height)
405-- lines[nextline][4] = height
406                        delta = delta + (height - nextht)
407                    end
408--                     if subtype == lineskip_code then
409--                         setglue(n,inbetween,stretch,shrink)
410                        setsubtype(n,baselineskip_code)
411--                     else
412                        setglue(n,amount+inbetween-delta,stretch,shrink)
413--                     end
414                    currentline = nextline -- was curline
415-- if currentline > noflines then
416--     break
417-- end
418                end
419            end
420            if splitmethod ~= "" then
421                local currentline = 0
422                for n, subtype in nextpenalty, head do
423                    if subtype == linepenalty_code then
424                        if l == 1 then
425                            if splitmethod == v_both or splitmethod == v_first then
426-- print("FIRST")
427                                setpenalty(n, 10000)
428                            end
429                        elseif l == noflines then
430                            if splitmethod == v_both or splitmethod == v_last then
431-- print("LAST")
432                                setpenalty(n, 10000)
433                            end
434                        end
435                        curline = currentline
436                    end
437                end
438            end
439            local firstht = lines[1][4]
440            local lastdp  = lines[noflines][5]
441            if firstht < height then
442                setheight(lines[1],height)
443            end
444            if lastdp < depth then
445                setdepth(lines[noflines],depth)
446            end
447        end
448    end
449
450    nuts.openup = openup
451
452    -- When present, the number is after the right marker. We need to move the
453    -- number when we want it at the left.
454    --
455    -- Todo: set a flag in mathalignment and support atttibutes on it so that
456    -- we can check if this is needed.
457
458    -- [dummy] [left -2] [second -1] [number 0]
459
460    -- In the end it makes more sense to just calculate the alignment in lua
461    -- but it is kind of fun to see how we can control alignments. This code
462    -- is (in the end) too complex.
463
464    local totals     = { }
465    local widths     = { }
466    local records    = { }
467    local deltas     = { }
468    local cellwidths = { }
469
470    local a_flushleft  <const> = 1
471    local a_centered   <const> = 2
472    local a_flushright <const> = 3
473
474    local a_numberright <const> = 1
475    local a_numberleft  <const> = 2
476
477    local function first_pass(head,callback,attr,preamble,signal)
478            --
479        local width    = 0
480        local hsize    = texget("hsize")
481        local count    = 0
482        local overflow = false
483        totals   = { } -- maybe use one table
484        widths   = { }
485        records  = { }
486        deltas   = { }
487        for n in nextrecord, preamble do
488            local wd = getwidth(n)
489            count    = count + 1
490            width    = width + wd
491            totals [count] = width
492            widths [count] = wd
493            records[count] = n
494            deltas [count] = 0
495        end
496        --
497        local lindex   = 0
498        local rindex   = 0
499        local lwidth   = 0
500        local rwidth   = 0
501        local centered = false
502        --
503        for row in nextunset, head do
504            local count      = 0
505            local anchor     = nil
506            local rochan     = nil
507         -- local cellwidths = { }
508            for cell in nextunset, getlist(row) do
509                local list = getlist(cell)
510                count = count + 1
511                cellwidths[count] = getwidth(cell)
512                for bound in nextboundary, list do
513                    local marker = getdata(bound)
514                    if marker == leftmarker then
515                        lindex = count
516                        anchor = bound
517                        rochan = list
518                    elseif marker == rightmarker then
519                        local n = getnext(bound)
520                        if n and getid(n) == hlist_code then
521                            local wd, ht, dp = getwhd(n)
522                            local lc = getattr(n,a_location)
523                            if lc then
524                                -- todo: just store align in the outer attribute so once ...
525                             -- local align     = lc // 0x10
526                             -- local location  = lc % 0x10
527                                local packed    = (lc >> 8) & 0xF
528                                local align     = (lc >> 4) & 0xF
529                                local location  = (lc     ) & 0xF
530                                local threshold = getattr(n,a_threshold)
531                                if location == a_numberright then
532                                    -- (more advanced than left)
533                                    local m = 1
534                                    local s = align == a_centered and 2 or 1
535                                    if align == a_flushleft then
536                                        rwidth = wd
537                                    elseif align == a_flushright then
538                                        rwidth = wd
539                                    elseif wd > lwidth then
540                                        lwidth = wd
541                                        rwidth = wd
542                                        centered = true
543                                    end
544
545if packed > 0 then
546    setprop(n,"mathalignshift","right")
547    lwidth = 0
548    rwidth = 0
549end
550
551                                    if totals[count-2] + cellwidths[count-1] + s*wd - s*threshold > hsize then
552                                        local total = ht + dp
553                                        setdepth(row,getdepth(row) + total)
554                                        setoffsets(n,0,-total)
555                                        local pr = records[count-1]
556                                        local cw = getwidth(pr)
557                                        if cw - wd > deltas[count-1] then
558                                            deltas[count-1] = cw - wd
559                                        end
560                                        overflow = true
561                                    end
562                                elseif location == a_numberleft then
563                                    if align == a_flushleft then
564                                        lwidth = wd
565                                    elseif align == a_flushright then
566                                        lwidth = wd
567                                    end
568                                    if signal == 0x80 then
569                                        lwidth = wd
570                                    end
571
572if packed > 0 then
573    setprop(n,"mathalignshift","left")
574    lwidth = 0
575    rwidth = 0
576end
577
578                                end
579                                if location == a_numberleft and anchor then
580                                    local l, l, n = removenode(list,n)
581                                    if l ~= list then
582                                        setlist(cell,l)
583                                     -- setwidth(cell,0)
584                                    end
585                                    if signal == 0x80 then
586                                        setwidth(cell,lwidth)
587                                    end
588                                    insertafter(rochan,anchor,n)
589                                end
590                            end
591                        end
592                        rindex = count
593                    end
594                end
595            end
596        end
597        --
598        if overflow then
599            if deltas[rindex-1] ~= 0 then
600                setwidth(records[rindex-1],deltas[rindex-1])
601            end
602        end
603        --
604        for count=1,#records do
605            if count == lindex then
606                if centered and overflow then
607                    lwidth = lwidth - texgetdimen("d_math_eqalign_number_distance")
608                end
609                setwidth(records[count],lwidth)
610            elseif count == rindex then
611                setwidth(records[count],rwidth)
612            end
613        end
614    end
615
616    local function second_pass(head,callback,attr,preamble,signal)
617        local done  = setmetatableindex("table")
618        local glues = { }
619        local okay  = false
620        for row, id, subtype in nextlist, head do
621            if id == hlist_code and subtype == row_code then
622                for cell, id, subtype in nextlist, getlist(row) do
623                    if id == hlist_code and subtype == cell_code then
624                        for n, s in nextglue, getlist(cell) do
625                            if s == intermathskip_code then
626                                local e = effectiveglue(n,cell)
627                                local g = getglue(n)
628                                local f = getfont(n)
629                                local a = done[f]
630                                local d = a[g]
631                                glues[n] = g
632                                if not d then
633                                    a[g] = e
634                                elseif d > e then
635                                    a[g] = e
636                                    okay = true
637                                end
638                            end
639                        end
640                    end
641                end
642            end
643        end
644        if okay then
645            for k, v in next, glues do
646                local g = done[getfont(k)][v]
647                if g then
648                    setglue(k,g)
649                    setprop(k,"fixedmathalign",true)
650                end
651            end
652            for row, id, subtype in nextlist, head do
653                if id == hlist_code and subtype == row_code then
654                    for cell, id, subtype, list in nextlist, getlist(row) do
655                        if list and id == hlist_code and subtype == cell_code then
656                            local wd = getwidth(cell)
657                            repack(cell,wd,"exactly")
658                        end
659                    end
660                end
661            end
662        end
663    end
664
665    local function third_pass(head,callback,attr,preamble,signal)
666        local inbetween, stretch, shrink = texgetglue("s_strc_math_alignment_inbetween")
667        openup({ inbetween = inbetween }, head)
668    end
669
670    -- This will become a bit more pluggable so that we have less checking.
671
672    -- maybe zero pass: preamble pass
673
674    function nodes.handlers.mathalignrelocate(head,where,callback,attr,preamble) -- 0x2
675        if callback ~= mathalign_code then
676            -- nothing to do here
677        elseif where == "preroll" then
678            local signal = getattr(attr,a_location)
679            if signal == 0x20 or signal == 0x40 or signal == 0x80 then
680                first_pass(head,callback,attr,preamble,signal)
681            end
682        elseif where == "wrapup" then
683            local signal = getattr(attr,a_location)
684            if signal == 0x40 or signal == 0x80 then
685                second_pass(head,callback,attr,preamble,signal)
686            end
687            if signal then -- also 0x00
688                third_pass(head,callback,attr,preamble,signal)
689            end
690        end
691    end
692
693do
694
695    local a_mathalignmentvrule = attributes.private("mathalignmentvrule")
696    local a_mathalignmenthrule = attributes.private("mathalignmenthrule")
697    local a_mathalignmenttop   = attributes.private("mathalignmenttop")
698    local a_mathalignmentmid   = attributes.private("mathalignmentmid")
699    local a_mathalignmentbot   = attributes.private("mathalignmentbot")
700
701    local function hrule_pass(head,attr,preamble)
702        local i = 0
703        for row, id, subtype, list in nextlist, head do
704            if id == hlist_code and subtype == row_code then
705                local height = 0
706                local depth  = 0
707                local done   = false
708                for cell, id, subtype, list in nextlist, list do
709                    if list then
710                        -- look for leaders
711                        for n, id, subtype in nextglue, list do
712                            -- here we can have the attribute on the glue (leader)
713                            -- but more consistent is to have it on the rule
714                            local rule = getleader(n)
715                            if rule then
716                                local signal = getattr(rule,a_mathalignmenthrule)
717                                if signal then
718                                    local w, h, d = getwhd(n)
719                                    if h and d then
720                                        if h > height then
721                                            height = h
722                                        end
723                                        if d > depth then
724                                            depth = d
725                                        end
726                                        done = true
727                                    end
728                                end
729                            end
730                        end
731                    end
732                end
733                if done then
734                    setheight(row,height)
735                    setdepth(row,depth)
736                end
737            end
738        end
739    end
740
741    -- todo  : cache
742    -- maybe : change prev pointer of head (is now temp)
743
744    local function vrule_pass(head,attr,preamble)
745        for row, id, subtype, list in nextlist, head do
746            if id == hlist_code and subtype == row_code then
747                local prv, nxt = getboth(row)
748                for cell, id, subtype, list in nextlist, list do
749                    if list then
750                        -- look for vertical rules
751                        local d = false
752                        local h = false
753                        for n, id, subtype in nextrule, list do
754                            local signal = getattr(n,a_mathalignmentvrule)
755                            -- always 1
756                            if signal then
757                                if not d then
758                                    if prv then
759                                        if getattr(prv,a_mathalignmentvrule) == 4 then
760                                            local p = prv
761                                            while p do
762                                                local a = getattr(p,a_mathalignmentvrule)
763                                                if not a then
764                                                    break
765                                                elseif a == 2 then
766                                                    if not getprev(p) or getid(getprev(p)) == temp_code then
767                                                        local h = getattr(p,a_mathalignmenttop) or 0
768                                                        nuts.setheight(p,h)
769                                                    end
770                                                    break
771                                                else
772                                                    p = getprev(p)
773                                                end
774                                            end
775                                        else
776                                            prv = nil
777                                        end
778    --                                     if getid(prv) ~= rule_code or not getattr(prv,a_mathalignmentvrule) then
779    --                                         prv = nil
780    --                                     end
781                                    end
782                                    if nxt then
783                                        if getattr(nxt,a_mathalignmentvrule) == 2 then
784                                            local p = nxt
785                                            while p do
786                                                local a = getattr(p,a_mathalignmentvrule)
787                                                if not a then
788                                                    break
789                                                elseif a == 4 then
790                                                    if not getnext(p) then
791                                                        local d = getattr(p,a_mathalignmentbot) or 0
792                                                        nuts.setdepth(p,d)
793                                                    end
794                                                    break
795                                                else
796                                                    p = getnext(p)
797                                                end
798                                            end
799                                        else
800                                            nxt = nil
801                                        end
802    --                                     if getid(nxt) ~= rule_code or not getattr(nxt,a_mathalignmentvrule) then
803    --                                         nxt = nil
804    --                                     end
805                                    end
806                                    d = prv and -getdepth (prv) or 0
807                                    h = nxt and -getheight(nxt) or 0
808                                    d = d - (getattr(prv,a_mathalignmentmid) or 0)
809                                end
810                                setoffsets(n,nil,nil,d,h)
811                            end
812                        end
813                    end
814                end
815            end
816        end
817    end
818
819    function nodes.handlers.mathmatrixrules(head,where,callback,attr,preamble) -- 0x3
820        if callback ~= mathmatrix_code then
821            -- nothing to do here
822        elseif where == "wrapup" then
823            hrule_pass(head,attr,preamble)
824            vrule_pass(head,attr,preamble)
825        end
826    end
827
828    local enabled = false
829
830    interfaces.implement {
831        name      = "enablematrixrules",
832     -- onlyonce  = true,
833        public    = true,
834        protected = true,
835        actions   = function()
836            if not enabled then
837                nodes.tasks.enableaction("alignments","nodes.handlers.mathmatrixrules") -- 0x3
838                enabled = true
839            end
840        end,
841    }
842
843end
844
845    --
846
847    local a_ornament     = attributes.system("mathmatrixornament")
848
849    local leftornament   = tex.boundaries.system("c_math_matrix_ornament_l")
850    local rightornament  = tex.boundaries.system("c_math_matrix_ornament_r")
851    local topornament    = tex.boundaries.system("c_math_matrix_ornament_t")
852    local bottomornament = tex.boundaries.system("c_math_matrix_ornament_b")
853
854    local verticalline   = tex.boundaries.system("c_math_matrix_vl_boundary")
855
856    local left     = 0
857    local right    = 0
858    local nofcells = 0
859    local found    = false
860
861    local lefts    = false
862    local rights   = false
863    local tops     = false
864    local bottoms  = false
865
866    local function first_pass(head,callback,attr,preamble,signal)
867        nofcells = 0
868        left     = 0
869        right    = 0
870        found    = false
871        lefts   = { }
872        rights  = { }
873        tops    = { }
874        bottoms = { }
875        for n in nextrecord, preamble do
876            nofcells = nofcells + 1
877        end
878        local cells = { }
879        for row in nextunset, head do
880            local c = 0
881            for cell in nextunset, getlist(row) do
882                local list = getlist(cell)
883                c = c + 1
884                for bound in nextboundary, list do
885                    local ornament = getdata(bound)
886                    if ornament == verticalline then
887                        -- this hack is is beyond ugly as we omit and span
888                        c = c + 1
889                        break
890                    elseif ornament == leftornament then
891                        if c == 1 then
892                            local w = getwidth(cell)
893                            if w > left then
894                                left = w
895                            end
896                            setwidth(cell,0)
897                            cells[c+1] = true
898                            cells[c] = true
899                            found = true
900                            lefts[#lefts+1] = cell
901                        end
902                    elseif ornament == rightornament then
903                        if c == nofcells then
904                            local w = getwidth(cell)
905                            if w > right then
906                                right = w
907                            end
908                            setwidth(cell,0)
909                            cells[c-1] = true
910                            cells[c] = true
911                            found = true
912                            rights[#rights+1] = cell
913                        end
914                    elseif ornament == topornament then
915                        setheight(row,0)
916                        setdepth(row,0)
917                        found = true
918                        tops[#tops+1] = cell
919                    elseif ornament == bottomornament then
920                        setheight(row,0)
921                        setdepth(row,0)
922                        found = true
923                        bottoms[#bottoms+1] = cell
924                    end
925                end
926            end
927        end
928        if next(cells) then
929            local c = 0
930            for n in nextrecord, preamble do
931                c = c + 1
932                if cells[c] then
933                    setwidth(n)
934                end
935            end
936        end
937    end
938
939    local function second_pass(box,callback,attr,preamble,signal)
940        if found then
941            local head = getlist(nuts.getbox(box))
942            local leftmargin   = texgetdimen("d_math_matrix_margin_l")
943            local rightmargin  = texgetdimen("d_math_matrix_margin_r")
944            local topmargin    = texgetdimen("d_math_matrix_margin_t")
945            local bottommargin = texgetdimen("d_math_matrix_margin_b")
946            for i=1,#lefts do
947                setoffsets(lefts[i],-(left+leftmargin),0)
948            end
949            for i=1,#rights do
950                setoffsets(rights[i], (rightmargin),0)
951            end
952            for i=1,#tops do
953                setoffsets(tops[i],0,topmargin)
954            end
955            for i=1,#bottoms do
956                setoffsets(bottoms[i],0,-bottommargin)
957            end
958            lefts   = false
959            rights  = false
960            tops    = false
961            bottoms = false
962            texsetdimen("global","d_math_matrix_max_left", left)
963            texsetdimen("global","d_math_matrix_max_right",right)
964        end
965    end
966
967    function nodes.handlers.mathmatrixornaments(head,where,callback,attr,preamble) -- 0x3
968        if callback ~= mathmatrix_code then
969            -- nothing to do here
970        elseif where == "preroll" then
971            first_pass(head,callback,attr,preamble,signal)
972     -- elseif where == "wrapup" then
973     --     second_pass(head,callback,attr,preamble,signal)
974        end
975    end
976
977    interfaces.implement {
978        name      = "shiftmatrixornaments",
979        actions   = second_pass,
980        arguments = "integer",
981    }
982
983    local enabled = false
984
985    interfaces.implement {
986        name      = "enablemathalignrelocate",
987     -- onlyonce  = true,
988        public    = true,
989        protected = true,
990        actions   = function()
991            if not enabled then
992                nodes.tasks.enableaction("alignments","nodes.handlers.mathalignrelocate") -- 0x2
993                enabled = true
994            end
995        end,
996    }
997
998    local enabled = false
999
1000    interfaces.implement {
1001        name      = "enablematrixornaments",
1002     -- onlyonce  = true,
1003        public    = true,
1004        protected = true,
1005        actions   = function()
1006            if not enabled then
1007                nodes.tasks.enableaction("alignments","nodes.handlers.mathmatrixornaments") -- 0x3
1008                enabled = true
1009            end
1010        end,
1011    }
1012
1013end
1014
1015-- This only kicks in when we have a callback set.
1016
1017local report = logs.reporter("alignment","preamble")
1018local trace    trackers.register("alignments.showstates",function(v) trace = v end)
1019
1020function nodes.handlers.showpreamble(head,where,callback,attr,preamble)
1021    if trace then
1022        local c = 0
1023        for n, id in nextnode, preamble do
1024            if id == unset_code or id == alignrecord_code then
1025                c = c + 1
1026                report("stage %a, cell %i, width %p",where,c,getwidth(n))
1027            elseif id == glue_code then
1028                report("stage %a, tabskip %s",where,node.direct.gluetostring(n))
1029            else
1030                report("stage %a, node %a",where,nodecodes[id])
1031            end
1032        end
1033    end
1034end
1035