anch-loc.lmt /size: 14 Kb    last modification: 2025-02-21 11:03
1if not modules then modules = { } end modules ['anch-loc'] = {
2    version   = 1.001,
3    comment   = "companion to anch-loc.lmtx",
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 next, type = next, type
10local setmetatableindex, sortedhash, insert, remove = table.setmetatableindex, table.sortedhash, table.insert, table.remove
11
12local context          = context
13
14local nuts             = nodes.nuts
15local nodepool         = nodes.pool
16local new_usernode     = nodepool.usernode
17local new_kern         = nuts.pool.kern
18local getbox           = nuts.getbox
19local getwidth         = nuts.getwidth
20local setwidth         = nuts.setwidth
21local getprop          = nuts.getprop
22local insertbefore     = nuts.insertbefore
23local insertafter      = nuts.insertafter
24local setattributelist = nuts.setattributelist
25
26local texgetbox        = tex.getbox
27
28local implement        = interfaces.implement
29
30local analyze          = drivers.converters.analyze
31
32local dimension_value  <const> = tokens.values.dimension
33
34local whatever         <const> = nodepool.userids["localanchor"]
35
36local v_left           <const> = interfaces.variables.left
37local v_middle         <const> = interfaces.variables.middle
38
39local positionsstack   = setmetatableindex("table")
40
41local function allocate2(t,k)
42    local v = { min = false, max = false }
43    t[k] = v
44    return v
45end
46
47local function allocate1(t,k)
48    local v = setmetatableindex({ cnt = { }, min = false, max = false }, allocate2)
49    t[k] = v
50    return v
51end
52
53local positions = setmetatableindex(allocate1)
54
55-- The basics:
56
57local function pushpositions()
58    insert(positionsstack,positions)
59    positions = setmetatableindex(allocate1)
60end
61
62local function poppositions()
63    positions = remove(positionsstack) or { }
64end
65
66local function initializepositions(driver,specification)
67 -- positions.width  = specification.boundingbox[3]
68 -- positions.height = specification.boundingbox[4]
69end
70
71-- local function finalizepositions(...)
72-- end
73
74local function collectpositions(current,pos_h,pos_v,cur_b)
75    -- beware, we actually can have a copy due to setting trialrun so we cannot
76    -- fetch the nodetable directly but go via the metatable ... fast enough
77    local data = getprop(current,"data")
78    local hash = positions[data.name]
79    local x    = data.x
80    local y    = data.y
81    if not hash.min then
82        hash.min = x
83        hash.max = x
84    elseif x > hash.max then
85        hash.max = x
86    end
87    hash = hash[x]
88    if not hash.min then
89        hash.min = y
90        hash.max = y
91    elseif y > hash.max then
92        hash.max = y
93    end
94    hash[y] = { pos_h, pos_v, data, current, 0, false, cur_b }
95end
96
97local function valid(name,x,y)
98    if positions then
99        local xlist = positions[name]
100        if xlist then
101            xlist = xlist[x]
102            return xlist and  xlist[y]
103        end
104    end
105end
106
107local function anchorx(name,x,y)
108    local v = valid(name,x,y)
109    return v and v[1] or 0
110end
111
112local function anchory(name,x,y)
113    local v = valid(name,x,y)
114    return v and v[2] or 0
115end
116
117local function anchorht(name,x,y)
118    local v = valid(name,x,y)
119    if v then
120        return v[7][2]
121    else
122        return 0
123    end
124end
125
126local function anchordp(name,x,y)
127    local v = valid(name,x,y)
128    if v then
129        return v[7][3]
130    else
131        return 0
132    end
133end
134
135local function anchorlr(name,x,y)
136    local v = valid(name,x,y)
137    if v then
138        return v[1] + v[7][1], v[2] - v[7][3]
139    else
140        return 0, 0
141    end
142end
143
144local function anchorur(name,x,y)
145    local v = valid(name,x,y)
146    if v then
147        return v[1] + v[7][1], v[2] + v[7][2]
148    else
149        return 0, 0
150    end
151end
152
153local function anchorul(name,x,y)
154    local v = valid(name,x,y)
155    if v then
156        return v[1], v[2] + v[7][2]
157    else
158        return 0, 0
159    end
160end
161
162local function anchorll(name,x,y)
163    local v = valid(name,x,y)
164    if v then
165        return v[1], v[2] - v[7][3]
166    else
167        return 0, 0
168    end
169end
170
171local function anchorxy(name,x,y)
172    local v = valid(name,x,y)
173    if v then
174        return v[1], v[2]
175    else
176        return 0, 0
177    end
178end
179
180local driver = {
181    actions = {
182        initialize = initializepositions,
183     -- finalize   = finalizepositions,
184    },
185    flushers = {
186        userdefined = {
187            [whatever] = collectpositions,
188        }
189    }
190}
191
192function drivers.converters.resyncbox(n)
193    local b = getbox(n)
194    analyze(driver,b)
195    for name, position in next, positions do
196        local xlast = { }
197        local aligned = false
198        for c=position.min,position.max do
199            local column = position[c]
200            if column then
201                local min = column.min
202                if min then
203                    local max = column.max
204                    local xlimit = 0
205                    for r=min,max do
206                        local cell = column[r]
207                        if cell and cell[3].kind == "sync" then
208                            local x = cell[1]
209                            local l = xlast[r]
210                            if l and l ~= 0 then
211                                x = x + l
212                                cell[1] = x
213                            end
214                            if x > xlimit then
215                                xlimit = x
216                            end
217                            if not aligned then
218                                aligned = cell[3].align
219                            end
220                        end
221                    end
222                    for r=min,max do
223                        local cell = column[r]
224                        if cell and cell[3].kind == "sync" then
225                            local progress = xlimit - cell[1]
226                            if aligned or progress ~= 0 then
227                                local kern = new_kern(progress)
228                                local current = cell[4]
229                                setattributelist(kern,current)
230                                insertafter(current,current,kern) -- why does before not work
231                                cell[5] = progress
232                                cell[6] = kern
233                                xlast[r] = (xlast[r] or 0) + progress
234                            end
235                        end
236                    end
237                end
238            end
239        end
240
241        if aligned then
242            local min = position.min
243            local max = position.max
244            local previous = { }
245            for c=min,max do
246                local column = position[c]
247                if column then
248                    local min = column.min
249                    if min then
250                        local max = column.max
251                        for r=min,max do
252                            local cell = column[r]
253                            if cell then
254                                local prev = previous[r]
255                                if prev then
256                                    local align = prev[3].align
257                                    if align then
258                                        local p = prev[6]
259                                        local n = cell[6]
260                                        local d = cell[5]
261                                        if align == "r" or align == v_right then
262                                            setwidth(p,getwidth(p)+d)
263                                            setwidth(n,getwidth(n)-d)
264                                        elseif align == "c" or align == "m" or align == v_middle then
265                                            setwidth(p,getwidth(p)+d/2)
266                                            setwidth(n,getwidth(n)-d/2)
267                                        end
268                                    end
269                                end
270                                previous[r] = cell
271                            end
272                        end
273                    end
274                end
275            end
276        end
277
278    end
279    return b
280end
281
282-- The ConTeXt interface (at that end we call them localanchors):
283
284implement {
285    name      = "pushlocalanchors",
286    public    = true,
287    protected = true,
288    untraced  = true,
289    actions   = pushpositions,
290}
291
292implement {
293    name      = "poplocalanchors",
294    public    = true,
295    protected = true,
296    untraced  = true,
297    actions   = poppositions,
298}
299
300implement {
301    name      = "analyzelocalanchors",
302    arguments = { "integerargument" },
303    public    = true,
304    protected = true,
305    untraced  = true,
306    actions   = function(n)
307        analyze(driver,texgetbox(n))
308    end
309}
310
311implement {
312    name      = "synchronizelocalanchors",
313    arguments = { "integerargument" },
314    public    = true,
315    protected = true,
316    untraced  = true,
317    actions   = drivers.converters.resyncbox,
318}
319
320implement {
321    name      = "setlocalsyncanchor",
322    arguments = { "argument", "integerargument", "integerargument" },
323    public    = true,
324    protected = true,
325    usage     = "value",
326    actions   = function(name,x,y)
327        -- value node ... only hlist or vlist or whatsit but we need trialmode so:
328        context(new_usernode(whatever,{ name = name, kind = "sync", x = x, y = y }))
329    end
330}
331
332implement {
333    name      = "setlocalalignanchor",
334    arguments = { "argument", "integerargument", "integerargument", "argument" },
335    public    = true,
336    protected = true,
337    usage     = "value",
338    actions   = function(name,x,y,align)
339        -- value node ... only hlist or vlist or whatsit but we need trialmode so:
340        context(new_usernode(whatever,{ name = name, kind = "sync", x = x, y = y, align = align }))
341    end
342}
343
344implement {
345    name      = "setlocalmarkanchor",
346    arguments = { "argument", "integerargument", "integerargument" },
347    public    = true,
348    protected = true,
349    usage     = "value",
350    actions   = function(name,x,y)
351        context(new_usernode(whatever,{ name = name, kind = "mark", x = x, y = y }))
352    end
353}
354
355implement {
356    name      = "localanchorx",
357    arguments = { "argument", "integerargument", "integerargument" },
358    public    = true,
359    usage     = "value",
360    actions   = function(name,x,y)
361        return dimension_value, anchorx(name,x,y)
362    end
363}
364
365implement {
366    name      = "localanchory",
367    arguments = { "argument", "integerargument", "integerargument" },
368    public    = true,
369    usage     = "value",
370    actions   = function(name,x,y)
371        return dimension_value, anchory(name,x,y)
372    end
373}
374
375interfaces.implement {
376    name      = "sync",
377    arguments = { "argument", "integerargument" },
378    protected = true,
379    public    = true,
380    actions   = function(name,x)
381        local t = positions[name].cnt
382        local y = (t[x] or 0) + 1
383        t[x] = y
384        context(new_usernode(whatever,{ name = name, kind = "sync", x = x, y = y }))
385    end,
386}
387
388interfaces.implement {
389    name      = "async",
390    arguments = { "argument", "integerargument", "argument" },
391    protected = true,
392    untraced  = true,
393    public    = true,
394    actions   = function(name,x,align)
395        local t = positions[name].cnt
396        local y = (t[x] or 0) + 1
397        t[x] = y
398        context(new_usernode(whatever,{ name = name, kind = "sync", x = x, y = y, align = align }))
399    end,
400}
401
402-- The MetaFun interface:
403
404do
405
406    local injectors     = mp.inject
407    local scanners      = mp.scan
408
409    local injectnumeric = injectors.numeric
410    local injectpair    = injectors.pair
411    local injectpath    = injectors.path
412
413    local scaninteger   = scanners.integer
414    local scanstring    = scanners.string
415
416    local bpfactor      = number.dimenfactors.bp
417
418    local registerscript = metapost.registerscript
419    local registerdirect = metapost.registerdirect
420
421    registerscript("anchorxy", function()
422        local x, y = anchorxy(scanstring(),scaninteger(),scaninteger())
423        return injectpair(x*bpfactor,y*bpfactor)
424    end)
425
426    registerdirect("anchorx", function() return anchorx(scanstring(),scaninteger(),scaninteger()) * bpfactor end)
427    registerdirect("anchory", function() return anchory(scanstring(),scaninteger(),scaninteger()) * bpfactor end)
428
429    registerdirect("anchorht", function() return anchorht(scanstring(),scaninteger(),scaninteger()) * bpfactor end)
430    registerdirect("anchordp", function() return anchordp(scanstring(),scaninteger(),scaninteger()) * bpfactor end)
431
432    local function corner(f)
433        local x, y = f(scanstring(),scaninteger(),scaninteger())
434        return injectpair(x*bpfactor,y*bpfactor)
435    end
436
437    registerdirect("anchorlr", function() return corner(anchorlr) end)
438    registerdirect("anchorur", function() return corner(anchorur) end)
439    registerdirect("anchorul", function() return corner(anchorul) end)
440    registerdirect("anchorll", function() return corner(anchorll) end)
441
442    registerscript("anchorbox", function()
443        local l = valid(scanstring(),scaninteger(),scaninteger())
444        local r = valid(scanstring(),scaninteger(),scaninteger())
445        local llx, lly, urx, ury, llb, urb
446        if l and r then
447            llx = l[1]
448            lly = l[2]
449            urx = r[1]
450            ury = r[2]
451            llb = l[7]
452            urb = r[7]
453            if llx > urx then
454                llx, urx = urx, llx
455            end
456            if lly > ury then
457                lly, ury = ury, lly
458                lly = lly - urb[3]
459                ury = ury + llb[2]
460            else
461                lly = lly - llb[3]
462                ury = ury + urb[2]
463            end
464            llx = llx * bpfactor
465            lly = lly * bpfactor
466            urx = urx * bpfactor
467            ury = ury * bpfactor
468        else
469            llx = 0
470            lly = 0
471            urx = 0
472            ury = 0
473        end
474        local p = {
475            cycle  = true,
476            curled = true,
477            { llx, lly },
478            { urx, lly },
479            { urx, ury },
480            { llx, ury }
481        }
482        injectpath(p)
483    end)
484
485 -- boundingbox (
486 --     anchorul(lname, lx, ly) --
487 --     anchorlr(rname, rx, ry)
488 -- )
489
490    local min = math.min
491    local max = math.max
492
493    registerscript("anchorspan", function()
494        local l = valid(scanstring(),scaninteger(),scaninteger())
495        local r = valid(scanstring(),scaninteger(),scaninteger())
496        local llx, lly, urx, ury, lb, ub
497        if l and r then
498            lb  = l[7]
499            rb  = r[7]
500            llx = min((l[1]        ),(r[1]        )) * bpfactor
501            lly = min((l[2] - lb[3]),(r[2] - rb[3])) * bpfactor
502            urx = max((l[1] + lb[1]),(r[1] + rb[1])) * bpfactor
503            ury = max((l[2] + lb[2]),(r[2] + rb[2])) * bpfactor
504        else
505            llx = 0
506            lly = 0
507            urx = 0
508            ury = 0
509        end
510        local p = {
511            cycle  = true,
512--             curled = true,
513            { llx, lly },
514            { urx, lly },
515            { urx, ury },
516            { llx, ury }
517        }
518        injectpath(p)
519    end)
520
521end
522