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