math-spa.lmt /size: 16 Kb    last modification: 2025-02-21 11:03
1if not modules then modules = { } end modules ['math-spa'] = {
2    version   = 1.001,
3    comment   = "companion to math-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-- for the moment (when testing) we use a penalty 1
10
11local setmetatableindex = table.setmetatableindex
12
13local nodecodes         = nodes.nodecodes
14local listcodes         = nodes.listcodes
15
16local boundary_code     <const> = nodecodes.boundary
17local hlist_code        <const> = nodecodes.hlist
18local vlist_code        <const> = nodecodes.vlist
19local kern_code         <const> = nodecodes.kern
20local penalty_code      <const> = nodecodes.penalty
21local glue_code         <const> = nodecodes.glue
22
23local line_code         <const> = listcodes.line
24local ghost_code        <const> = listcodes.ghost
25local middle_code       <const> = listcodes.middle
26local wrapped_code      <const> = listcodes.wrapped
27local mathpack_code     <const> = listcodes.mathpack
28local construct_code    <const> = listcodes.construct
29local alignment_code    <const> = listcodes.alignment
30local row_code          <const> = listcodes.row
31local fence_code        <const> = listcodes.fence
32
33local nuts              = nodes.nuts
34local tonut             = nodes.tonut
35local tonode            = nodes.tonode
36
37local getid             = nuts.getid
38local getsubtype        = nuts.getsubtype
39local getnext           = nuts.getnext
40local getprev           = nuts.getprev
41local getattr           = nuts.getattr
42local getprop           = nuts.getprop
43local getwidth          = nuts.getwidth
44local getdata           = nuts.getdata
45local getdepth          = nuts.getdepth
46local getheight         = nuts.getheight
47local getlist           = nuts.getlist
48local setglue           = nuts.setglue
49local setwhd            = nuts.setwhd
50local setoffsets        = nuts.setoffsets
51local getdimensions     = nuts.dimensions
52local getnormalizedline = nuts.getnormalizedline
53local getbox            = nuts.getbox
54local addxoffset        = nuts.addxoffset
55local setattrlist       = nuts.setattrlist
56local rangedimensions   = nuts.rangedimensions
57
58local nextglue          = nuts.traversers.glue
59local nextlist          = nuts.traversers.list
60local nextboundary      = nuts.traversers.boundary
61local nextnode          = nuts.traversers.node
62
63local insertafter       = nuts.insertafter
64local insertbefore      = nuts.insertbefore
65local newkern           = nuts.pool.kern
66local newstrutrule      = nuts.pool.strutrule
67
68local texsetdimen       = tex.setdimen
69local texgetdimen       = tex.getdimen
70local texsetcount       = tex.setcount
71local texisdimen        = tex.isdimen
72local texiscount        = tex.iscount
73
74local stages            = { }
75local initial           = { }
76
77local b_mathalign  <const> = tex.boundaries.system("mathalign")
78local b_mathinject <const> = tex.boundaries.system("mathinject")
79local a_location   <const> = attributes.system("mathnumberlocation")
80
81local c_strc_math_n_of_lines   <const> = texiscount("c_strc_math_n_of_lines")
82local d_strc_math_max_right    <const> = texisdimen("d_strc_math_max_right")
83local d_strc_math_first_right  <const> = texisdimen("d_strc_math_first_right")
84local d_strc_math_last_right   <const> = texisdimen("d_strc_math_last_right")
85local d_strc_math_max_left     <const> = texisdimen("d_strc_math_max_left")
86local d_strc_math_first_left   <const> = texisdimen("d_strc_math_first_left")
87local d_strc_math_last_left    <const> = texisdimen("d_strc_math_last_left")
88local d_strc_math_first_height <const> = texisdimen("d_strc_math_first_height")
89local d_strc_math_last_depth   <const> = texisdimen("d_strc_math_last_depth")
90local d_strc_math_indent       <const> = texisdimen("d_strc_math_indent")
91
92local d_strc_math_left_shift   <const> = texisdimen("d_strc_math_left_shift")
93local d_strc_math_right_shift  <const> = texisdimen("d_strc_math_right_shift")
94
95local report = logs.reporter("mathalign")
96
97local trace  = false  trackers.register("math.align",function(v) trace = v end )
98
99local function moveon(s)
100    for n, id, subtype in nextnode, getnext(s) do
101        s = n
102        if id == kern_code then
103            -- move on (s_2 case)
104        elseif id == glue_code then
105            -- move on
106        elseif id == penalty_code then
107            -- move on (untested)
108        elseif id == hlist_code and subtype == ghost_code then
109            -- move on
110        else
111            break
112        end
113    end
114    return s
115end
116
117-- -- todo: skip over ghost, maybe penalty, maybe glues all in one loop
118--
119-- local n = getnext(s)
120-- if n and getid(n) == kern_code then -- also needed
121--     n = getnext(n)
122-- end
123-- while n and getid(n) == hlist_code and getsubtype(n) == ghost_code do
124--     n = getnext(n)
125-- end
126-- while n and getid(n) == glue_code do
127-- if n and getid(n) == glue_code then
128--     n = getnext(n)
129-- end
130
131local getpenalty = nuts.getpenalty
132
133stages[1] = function(specification,stage)
134    local box      = getbox(specification.box)
135    local head     = getlist(box)
136    local align    = specification.alignstate
137    local distance = specification.distance
138    local found    = { }
139    local max      = 0
140    for s in nextboundary, head do
141        local data = getdata(s)
142        if data == b_mathalign then
143            local s = moveon(s)
144            found[#found+1] = { s, 0, head }
145--         elseif data == b_mathinject then
146--             print("!!!!!!!")
147        end
148    end
149    if #found > 0 then
150        if found[1] then
151            max = distance + getdimensions(head,found[1][1])
152            found[1][2] = max
153        end
154        for i=2,#found do
155            local f = found[i]
156            local n = f[1]
157            local p = n
158            while p do
159                if getid(p) == penalty_code and getpenalty(p) == -10000 then
160                    local w = getdimensions(p,n)
161                    local d = distance + w
162                    f[2] = d
163                    f[3] = p
164                    if d > max then
165                        max = d
166                    end
167                    break
168                end
169                p = getprev(p)
170            end
171        end
172        for i=1,#found do
173            local f = found[i]
174            local w = f[2]
175            local d = i == 1 and (max-w) or -w
176            local k = newkern(d)
177            local r = newstrutrule(0,2*65536,2*65536) -- hm, this adds height and depth !
178            local s = moveon(f[3])
179            if trace then
180                report("row %i, width %p, delta %p",i,w,d)
181            end
182            setattrlist(r,head)
183            setattrlist(k,head)
184            insertbefore(head,s,r)
185            insertafter(head,r,k)
186        end
187    end
188    texsetdimen("global",d_strc_math_indent,max)
189    if align == 2 then
190        for n in nextglue, head do
191            setglue(n,getwidth(n),0,0,0,0)
192        end
193    end
194end
195
196local function handlepacked(parent,plist,position)
197    if plist then
198        local current = plist
199-- print("")
200-- nuts.show(current)
201-- print("")
202        while current do
203            if getid(current) == hlist_code and getsubtype(current) == wrapped_code then
204                local packed = ((getattr(current,a_location) or 0) >> 8) & 0xF
205-- print(packed,a_location,getattr(current,a_location))
206-- packed = 1
207                if packed > 0 then
208                 -- print("")
209                 -- nuts.show(current)
210                 -- print("")
211                    local linewidth, wrapwidth, leading, trailing
212                    local wlist = getlist(current)
213                    if wlist and getid(wlist) == hlist_code and getsubtype(wlist) == fence_code then
214                        wlist = getlist(wlist) -- left=none demands this
215                    end
216                    for wrapped, id, subtype, list in nextlist, wlist do
217                        if id == hlist_code and subtype == construct_code then -- mathpack_code
218                            while getid(list) == vlist_code do
219                                list = getlist(list)
220                            end
221                            for a, id, subtype, alist in nextlist, list do
222                                if subtype == alignment_code then
223                                    for cell, id, subtype, clist in nextlist, alist do
224                                        for n, id, subtype in nextlist, clist do
225                                            local p = getprop(n,"mathalignshift")
226                                            if p == "right" then
227                                                if not leading then
228                                                    linewidth = getwidth(parent)
229                                                    wrapwidth = getwidth(current)
230                                                    leading   = rangedimensions(parent,plist,current)
231                                                end
232                                                local shift = rangedimensions(parent,getnext(wrapped))
233                                                addxoffset(n,shift + linewidth - wrapwidth - leading - position)
234                                            elseif p == "left" then
235                                                if not leading then
236                                                    linewidth = getwidth(parent)
237                                                    wrapwidth = getwidth(current)
238                                                    leading   = rangedimensions(parent,plist,current)
239                                                end
240                                                local shift = nuts.rangedimensions(parent,wlist,wrapped)
241                                                addxoffset(n,- shift - leading - position)
242                                            end
243                                        end
244                                    end
245                                end
246                            end
247                        end
248                     -- wrapped = getnext(wrapped) -- some left-over bad in 5.5 anyway
249                    end
250                end
251            end
252            current = getnext(current)
253        end
254    end
255end
256
257local function reposition(n,offset)
258    -- We need to relocate the local boxes that we use to push something left or
259    -- right ... quite horrible and one needs a bit twisted mindset for this.
260    for n, id, subtype, list in nextlist, getlist(n) do
261        if subtype == middle_code then
262            addxoffset(n,-offset)
263        end
264    end
265    -- this is tricky ... see line numbering so it might become a real shift
266    -- inside the box: setprops(n,"repositioned",true)
267    addxoffset(n,offset)
268end
269
270stages[2] = function(specification,stage)
271    local box         = getbox(specification.box)
272    local head        = getlist(box)
273    local align       = specification.alignstate
274    local cnt         = 0
275    local maxwidth    = false
276    local firstwidth  = 0
277    local lastwidth   = 0
278    local maxright    = false
279    local firstright  = false
280    local lastright   = false
281    local maxleft     = false
282    local firstleft   = false
283    local lastleft    = false
284    local firstheight = 0
285    local lastdepth   = 0
286    local linenumber  = 0
287    local leftmargin  = specification.leftmargin
288    local rightmargin = specification.rightmargin
289    local injections  = { }
290    if trace then
291        report("stage 2")
292    end
293    for n, id, subtype, list in nextlist, head do
294        if subtype == line_code then
295            local t = getnormalizedline(n)
296         -- local m = t.rightskip + t.parfillrightskip
297            local l = t.leftskip  + t.lefthangskip  + t.parinitleftskip  + t.parfillleftskip  + t.indent
298            local r = t.rightskip + t.righthangskip + t.parinitrightskip + t.parfillrightskip
299
300            for s in nextboundary, list do
301                local data = getdata(s)
302                if data == b_mathinject then
303                    injections[n] = true
304                    goto HERE
305                end
306            end
307
308            linenumber = linenumber + 1
309
310            local w = getwidth(n)
311            local m = r
312            if trace then
313                report("line %i, width %p, left %p, right %p, used %p",linenumber,w,l,r,w-l-r)
314            end
315
316            if not maxleft or m > maxleft then
317                maxleft = l
318            end
319            if not maxright or m > maxright then
320                maxright = r
321            end
322            if not firstleft then
323                firstleft = l
324            end
325            if not firstright then
326                firstright = r
327            end
328            lastleft  = l
329            lastright = r
330            if not maxwidth then
331                maxwidth    = m
332                firstheight = getheight(n)
333                firstwidth  = m
334            elseif m < maxwidth then
335                maxwidth = m
336            end
337            cnt       = cnt + 1
338            lastwidth = m
339            lastdepth = getdepth(n)
340          ::HERE::
341        end
342    end
343    if not maxwidth then
344        maxwidth   = getwidth(box) -- safeguard
345        firstleft  = 0
346        lastleft   = 0
347        maxleft    = 0
348        firstright = 0
349        lastright  = 0
350        maxright   = 0
351    end
352    local position = 0
353    if align == 1 then -- flushleft
354        if trace then
355            report("reposition %p, %s",0, "flush left")
356            -- todo
357        end
358     -- position = leftmargin
359    elseif align == 2 then -- middle
360        position = (maxwidth-rightmargin)/2
361        if trace then
362            report("reposition %p, %s",position, "center")
363        end
364    elseif align == 3 then -- flushright
365        position = maxwidth - rightmargin -- a nasty interplay with the tex end (TODO !!!)
366        if trace then
367            report("reposition %p, %s",maxwidth, "flush right")
368        end
369    end
370    if stage == 2 and position ~= 0 then
371        for n, id, subtype, list in nextlist, head do
372            reposition(n,position)
373        end
374        firstleft  = firstleft  + position
375        lastleft   = lastleft   + position
376        maxleft    = maxleft    + position
377        firstright = firstright - position
378        lastright  = lastright  - position
379        maxright   = maxright   - position
380    else
381        position = 0
382    end
383    local leftshift  = math.min(firstleft,lastleft,maxleft) - texgetdimen(d_strc_math_indent)
384    local rightshift = false
385    for n, id, subtype, list in nextlist, head do
386        if subtype == line_code then
387            local t = getnormalizedline(n)
388            local l = t.leftskip  + t.lefthangskip  + t.parinitleftskip  + t.parfillleftskip  + t.indent
389            local r = t.rightskip + t.righthangskip + t.parinitrightskip + t.parfillrightskip
390            if not rightshift then rightshift = r elseif r < rightshift then rightshift = r end
391            if injections[n] then
392                setoffsets(n,-l,nil)
393            end
394            goto DONE
395        end
396        handlepacked(n,list,position)
397      ::DONE::
398    end
399    rightshift = (rightshift or 0) - position
400    --
401    texsetcount("global",c_strc_math_n_of_lines,cnt)
402    texsetdimen("global",d_strc_math_first_height,firstheight)
403    texsetdimen("global",d_strc_math_last_depth,lastdepth)
404    texsetdimen("global",d_strc_math_first_left,firstleft)
405    texsetdimen("global",d_strc_math_first_right,firstright)
406    texsetdimen("global",d_strc_math_last_left,lastleft)
407    texsetdimen("global",d_strc_math_last_right,lastright)
408    texsetdimen("global",d_strc_math_max_left,maxleft)
409    texsetdimen("global",d_strc_math_max_right,maxright)
410    --
411    texsetdimen("global",d_strc_math_left_shift,leftshift or 0)
412    texsetdimen("global",d_strc_math_right_shift,rightshift or 0)
413end
414
415stages[3] = stages[2]
416
417stages[4] = function(specification,stage)
418    local box = getbox(specification.box)
419    nuts.openup(specification,getlist(box))
420    local w, h, d = getdimensions(getlist(box),true) -- vertical
421    setwhd(box,w,h,d)
422end
423
424interfaces.implement {
425    name      = "handlemathhang",
426    arguments = {
427        {
428            { "stage", "integer" },
429         -- { "method" },
430            { "alignstate", "integer" },
431            { "box", "integer" },
432            { "distance", "dimension" },
433            { "inbetween", "dimension" },
434            { "height", "dimension" },
435            { "depth", "dimension" },
436            { "leftmargin", "dimension" },
437            { "rightmargin", "dimension" },
438        }
439    },
440    actions   = function(specification)
441        local stage = specification.stage
442        if stage == 1 then
443            initial = specification
444        else
445            setmetatableindex(specification,initial)
446        end
447        if stage > 0 and stage <= #stages then
448            stages[stage](specification,stage)
449        end
450    end
451}
452