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