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