anch-pgr.lmt /size: 41 Kb    last modification: 2025-02-21 11:03
1if not modules then modules = { } end modules ['anch-pgr'] = {
2    version   = 1.001,
3    comment   = "companion to anch-pgr.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-- This is a bit messy module but backgrounds are messy anyway. Especially when we want to
10-- follow shapes. This will always be work in progress as it also depends on new features
11-- in context.
12--
13-- Alas, shapes and inline didn't work as expected end of 2016 so I had to pick up this
14-- thread again. But with regular excursions to listening to Brad Mehldau's Mehliana I
15-- could keep myself motivated. Some old stuff has been removed, some suboptimal code has
16-- been replaced. Background code is still not perfect, but some day ... the details manual
17-- will discuss this issue.
18
19local tonumber = tonumber
20local sort, concat = table.sort, table.concat
21local splitter = lpeg.splitat(":")
22local lpegmatch = lpeg.match
23
24local jobpositions      = job.positions
25local formatters        = string.formatters
26local setmetatableindex = table.setmetatableindex
27local settings_to_array = utilities.parsers.settings_to_array
28
29local enableaction      = nodes.tasks.enableaction
30
31local context           = context
32
33local ctx_doifelse      = commands.doifelse
34
35local implement         = interfaces.implement
36
37local texgetcount       = tex.getcount
38
39local getmacro          = tokens.getters.macro
40local expandasvalue     = tex.expandasvalue
41local scanmpstring      = mp.scan.string
42----- mpgnamespace      = getmacro("??graphicvariable")
43
44local string_value      <const> = tokens.values.string
45
46local report_graphics   = logs.reporter("backgrounds")
47local report_shapes     = logs.reporter("backgrounds","shapes")
48local report_free       = logs.reporter("backgrounds","free")
49
50local trace_shapes      = false  trackers.register("backgrounds.shapes",       function(v) trace_shapes = v end)
51local trace_ranges      = false  trackers.register("backgrounds.shapes.ranges",function(v) trace_ranges = v end)
52local trace_free        = false  trackers.register("backgrounds.shapes.free",  function(v) trace_free   = v end)
53
54local f_b_tag           = formatters["b:%s"]
55local f_e_tag           = formatters["e:%s"]
56local f_p_tag           = formatters["p:%s"]
57
58local f_tag_two         = formatters["%s:%s"]
59
60local f_point           = formatters["%p"]
61local f_pair            = formatters["(%p,%p)"]
62local f_path            = formatters["%--t--cycle"]
63local f_pair_i          = formatters["(%r,%r)"] -- rounded
64
65local c_realpageno      <const> = tex.iscount("realpageno")
66
67graphics                = graphics or { }
68local backgrounds       = { }
69graphics.backgrounds    = backgrounds
70
71-- -- -- these can go -- -- --
72
73backgrounds.point = f_point
74backgrounds.pair  = f_pair
75backgrounds.path  = f_path
76
77-- -- --
78
79local texsetattribute   = tex.setattribute
80
81local a_textbackground  <const> = attributes.private("textbackground")
82
83local nuts              = nodes.nuts
84
85local new_latelua       = nuts.pool.latelua
86local new_rule          = nuts.pool.rule
87local new_kern          = nuts.pool.kern
88local new_hlist         = nuts.pool.hlist
89
90local getbox            = nuts.getbox
91local getid             = nuts.getid
92----- getlist           = nuts.getlist
93local setlink           = nuts.setlink
94local getheight         = nuts.getheight
95local getdepth          = nuts.getdepth
96
97local par_code          <const> = nodes.nodecodes.par
98
99local startofpar        = nuts.startofpar
100local insertbefore      = nuts.insertbefore
101local insertafter       = nuts.insertafter
102
103local processranges     = nuts.processranges
104
105local unsetvalue        <const> = attributes.unsetvalue
106
107local jobpositions      = job.positions
108local getpos            = jobpositions.getpos
109local getfree           = jobpositions.getfree
110
111----- data              = { }
112local realpage          = 1
113----- recycle           = 1000 -- only tables can overflow this
114local enabled           = false
115
116-- Freeing the data is somewhat tricky as we can have backgrounds spanning many
117-- pages but for an arbitrary background shape that is not so common but we use
118-- a different trick anyway in lmtx.
119
120local function check(specification)
121    --
122    local a     = specification.attribute
123    local index = specification.index
124    local depth = specification.depth
125    local d     = specification.data
126    local where = specification.where
127    local ht    = specification.ht
128    local dp    = specification.dp
129    -- this is not yet r2l ready
130    local w = d.shapes[realpage]
131    local x, y = getpos()
132    if trace_ranges then
133        report_shapes("attribute %i, index %i, depth %i, location %s, position (%p,%p)",
134            a,index,depth,where,x,y)
135    end
136    local n = #w
137    if d.index ~= index then
138        n = n + 1
139        d.index = index
140        d.depth = depth
141     -- w[n] = { x, x, y, ht, dp }
142        w[n] = { y, ht, dp, x, x }
143    else
144        local wn = w[n]
145        local wh = wn[2]
146        local wd = wn[3]
147        if depth < d.depth then
148            local wy = wn[1]
149            wn[1] = y
150            d.depth = depth
151            local dy = wy - y
152            wh = wh - dy
153            wd = wd - dy
154        end
155        if where == "r" then
156            if x > wn[5] then
157                wn[5] = x
158            end
159        else
160            if x < wn[4] then
161                wn[4] = x
162            end
163        end
164        if ht > wh then
165            wn[2] = ht
166        end
167        if dp > wd then
168            wn[3] = dp
169        end
170    end
171--   inspect(w)
172end
173
174local index = 0
175
176local registervalue = attributes.registervalue
177local getvalue      = attributes.getvalue
178
179local function flush(head,f,l,a,parent,depth)
180 -- local d = data[a]
181    local d = getvalue(a_textbackground,a)
182    if d then
183        local ix = index
184        local ht = getheight(parent)
185        local dp = getdepth(parent)
186        local ln = new_latelua { action = check, attribute = a, index = ix, depth = depth, data = d, where = "l", ht = ht, dp = dp }
187        local rn = new_latelua { action = check, attribute = a, index = ix, depth = depth, data = d, where = "r", ht = ht, dp = dp }
188        if trace_ranges then
189            ln = new_hlist(setlink(new_rule(65536,65536*4,0),new_kern(-65536),ln))
190            rn = new_hlist(setlink(new_rule(65536,0,65536*4),new_kern(-65536),rn))
191        end
192        if getid(f) == par_code and startofpar(f) then -- we need to clean this mess
193            insertafter(head,f,ln)
194        else
195            head, f = insertbefore(head,f,ln)
196        end
197        insertafter(head,l,rn)
198    end
199    return head, true
200end
201
202-- local function registerbackground(name)
203--     local n = #data + 1
204--     if n > recycle then
205--         -- we could also free all e: that are beyond a page but we don't always
206--         -- know the page so a recycle is nicer and the s lists are kept anyway
207--         -- so the amount of kept data is not that large
208--         n = 1
209--     end
210--     local b = jobpositions.tobesaved["b:"..name]
211--     if b then
212--         local s = setmetatableindex("table")
213--         b.s = s
214--         data[n] = {
215--             bpos   = b,
216--             name   = name,
217--             n      = n,
218--             shapes = s,
219--             count  = 0,
220--             sindex = 0,
221--         }
222--         texsetattribute(a_textbackground,n)
223--         if not enabled then
224--             enableaction("contributers", "nodes.handlers.textbackgrounds")
225--             enabled = true
226--         end
227--     else
228--         texsetattribute(a_textbackground,unsetvalue)
229--     end
230-- end
231
232local function registerbackground(name)
233    local b = jobpositions.tobesaved["b:"..name]
234    if b then
235        local n = registervalue(a_textbackground,t)
236        local s = setmetatableindex("table")
237        b.s = s
238        local t = {
239            bpos   = b,
240            name   = name,
241            n      = n,
242            shapes = s,
243            count  = 0,
244            sindex = 0,
245        }
246        texsetattribute(a_textbackground,n)
247        if not enabled then
248            enableaction("contributers", "nodes.handlers.textbackgrounds")
249            enabled = true
250        end
251    else
252        texsetattribute(a_textbackground,unsetvalue)
253    end
254end
255
256nodes.handlers.textbackgrounds = function(head,where,parent) -- we have hlistdir and local dir
257    -- todo enable action in register
258    index    = index + 1
259    realpage = texgetcount(c_realpageno)
260    return processranges(a_textbackground,flush,head,parent)
261end
262
263interfaces.implement {
264    name      = "registerbackground",
265    actions   = registerbackground,
266    arguments = "string",
267}
268
269-- optimized already but we can assume a cycle i.e. prune the last point and then
270-- even less code .. we could merge some loops but his is more robust
271
272local function topairs(t,n)
273    local r = { }
274    for i=1,n do
275        local ti = t[i]
276        r[i] = f_pair_i(ti[1]/65556,ti[2]/65536)
277    end
278    return concat(r," ")
279end
280
281local eps = 65536 / 4
282local pps =   eps
283local nps = - pps
284
285local function unitvector(x,y)
286    if x < pps and x > nps then
287        x = 0
288    elseif x < 0 then
289        x = -1
290    else
291        x = 1
292    end
293    if y < pps and y > nps then
294        y = 0
295    elseif y < 0 then
296        y = -1
297    else
298        y = 1
299    end
300    return x, y
301end
302
303local function finish(t)
304    local tm = #t
305    if tm < 2 then
306        return
307    end
308    if trace_ranges then
309        report_shapes("initial list: %s",topairs(t,tm))
310    end
311    -- remove similar points
312    local n  = 1
313    local tn = tm
314    local tf = t[1]
315    local tx = tf[1]
316    local ty = tf[2]
317    for i=2,#t do
318        local ti = t[i]
319        local ix = ti[1]
320        local iy = ti[2]
321        local dx = ix - tx
322        local dy = iy - ty
323        if dx > eps or dx < - eps or dy > eps or dy < - eps then
324            n = n + 1
325            t[n] = ti
326            tx = ix
327            ty = iy
328        end
329    end
330    if trace_shapes then
331        report_shapes("removing similar points: %s",topairs(t,n))
332    end
333    if n > 2 then
334        -- remove redundant points
335        repeat
336            tn = n
337            n  = 0
338            local tm  = t[tn]
339            local tmx = tm[1]
340            local tmy = tm[2]
341            local tp  = t[1]
342            local tpx = tp[1]
343            local tpy = tp[2]
344            for i=1,tn do        -- while and only step when done
345                local ti  = tp
346                local tix = tpx
347                local tiy = tpy
348                if i == tn then
349                    tp = t[1]
350                else
351                    tp = t[i+1]
352                end
353                tpx = tp[1]
354                tpy = tp[2]
355
356                local vx1, vx2 = unitvector(tix - tmx,tpx - tix)
357                if vx1 ~= vx2 then
358                    n = n + 1
359                    t[n] = ti
360                else
361                    local vy1, vy2 = unitvector(tiy - tmy,tpy - tiy)
362                    if vy1 ~= vy2 then
363                        n = n + 1
364                        t[n] = ti
365                    end
366                end
367
368                tmx = tix
369                tmy = tiy
370            end
371        until n == tn or n <= 2
372        if trace_shapes then
373            report_shapes("removing redundant points: %s",topairs(t,n))
374        end
375        -- remove spikes
376        if n > 2 then
377            repeat
378                tn = n
379                n  = 0
380                local tm  = t[tn]
381                local tmx = tm[1]
382                local tmy = tm[2]
383                local tp  = t[1]
384                local tpx = tp[1]
385                local tpy = tp[2]
386                for i=1,tn do        -- while and only step when done
387                    local ti  = tp
388                    local tix = tpx
389                    local tiy = tpy
390                    if i == tn then
391                        tp = t[1]
392                    else
393                        tp = t[i+1]
394                    end
395                    tpx = tp[1]
396                    tpy = tp[2]
397
398                    local vx1, vx2 = unitvector(tix - tmx,tpx - tix)
399                    if vx1 ~= - vx2 then
400                        n = n + 1
401                        t[n] = ti
402                    else
403                        local vy1, vy2 = unitvector(tiy - tmy,tpy - tiy)
404                        if vy1 ~= - vy2 then
405                            n = n + 1
406                            t[n] = ti
407                        end
408                    end
409
410                    tmx = tix
411                    tmy = tiy
412                end
413            until n == tn or n <= 2
414            if trace_shapes then
415                report_shapes("removing spikes: %s",topairs(t,n))
416            end
417        end
418    end
419    -- prune trailing points
420    if tm > n then
421        for i=tm,n+1,-1 do
422            t[i] = nil
423        end
424    end
425    if n > 1 then
426        local tf = t[1]
427        local tl = t[n]
428        local dx = tf[1] - tl[1]
429        local dy = tf[2] - tl[2]
430        if dx > eps or dx < - eps or dy > eps or dy < - eps then
431            -- different points
432        else
433            -- saves a point (as we -- cycle anyway)
434            t[n] = nil
435            n = n -1
436        end
437        if trace_shapes then
438            report_shapes("removing cyclic endpoints: %s",topairs(t,n))
439        end
440    end
441    return t
442end
443
444local eps = 65536
445
446-- The next function can introduce redundant points but these are removed later on
447-- in the unspiker. It makes checking easier.
448
449local function shape(kind,b,p,realpage,xmin,xmax,ymin,ymax,fh,ld)
450    local s = b.s
451    if not s then
452        if trace_shapes then
453            report_shapes("calculating %s area, no shape",kind)
454        end
455        return
456    end
457    s = s[realpage]
458    if not s then
459        if trace_shapes then
460            report_shapes("calculating %s area, no shape for page %s",kind,realpage)
461        end
462        return
463    end
464    local ns = #s
465    if ns == 0 then
466        if trace_shapes then
467            report_shapes("calculating %s area, empty shape for page %s",kind,realpage)
468        end
469        return
470    end
471    --
472    if trace_shapes then
473        report_shapes("calculating %s area, using shape for page %s",kind,realpage)
474    end
475    -- it's a bit inefficient to use the par values and later compensate for b and
476    -- e but this keeps the code (loop) cleaner
477    local ph = p and p.h or 0
478    local pd = p and p.d or 0
479    --
480    xmax = xmax + eps
481    xmin = xmin - eps
482    ymax = ymax + eps
483    ymin = ymin - eps
484    local ls = { } -- left shape
485    local rs = { } -- right shape
486    local pl = nil -- previous left x
487    local pr = nil -- previous right x
488    local n  = 0
489    local xl = nil
490    local xr = nil
491    local mh = ph -- min
492    local md = pd -- min
493    for i=1,ns do
494        local si = s[i]
495        local y  = si[1]
496        local ll = si[4] -- can be sparse
497        if ll then
498            xl = ll
499            local rr = si[5] -- can be sparse
500            if rr then
501                xr = rr
502            end
503        end
504        if trace_ranges then
505            report_shapes("original  : [%02i]  xl=%p  xr=%p  y=%p",i,xl,xr,y)
506        end
507        if xl ~= xr then -- could be catched in the finalizer
508            local xm = xl + (xr - xl)/2 -- midpoint should be in region
509            if xm >= xmin and xm <= xmax and y >= ymin and y <= ymax then
510                local ht = si[2] -- can be sparse
511                if ht then
512                    ph = ht
513                    local dp = si[3] -- can be sparse
514                    if dp then
515                        pd = dp
516                    end
517                end
518                local h = y + (ph < mh and mh or ph)
519                local d = y - (pd < md and md or pd)
520                if pl then
521                    n = n + 1
522                    ls[n] = { pl, h }
523                    rs[n] = { pr, h }
524                    if trace_ranges then
525                        report_shapes("paragraph : [%02i]  xl=%p  xr=%p  y=%p",i,pl,pr,h)
526                    end
527                end
528                n = n + 1
529                ls[n] = { xl, h }
530                rs[n] = { xr, h }
531                if trace_ranges then
532                    report_shapes("height    : [%02i]  xl=%p  xr=%p  y=%p",i,xl,xr,h)
533                end
534                n = n + 1
535                ls[n] = { xl, d }
536                rs[n] = { xr, d }
537                if trace_ranges then
538                    report_shapes("depth     : [%02i]  xl=%p  xr=%p  y=%p",i,xl,xr,d)
539                end
540            end
541            pl, pr = xl, xr
542        else
543            if trace_ranges then
544                report_shapes("ignored   : [%02i]  xl=%p  xr=%p  y=%p",i,xl,xr,y)
545            end
546        end
547    end
548    --
549    if true and n > 0 then
550        -- use height of b and depth of e, maybe check for weird border
551        -- cases here
552        if fh then
553            local lsf = ls[1]
554            local rsf = rs[1]
555            if lsf[2] < fh then
556                lsf[2] = fh
557            end
558            if rsf[2] < fh then
559                rsf[2] = fh
560            end
561        end
562        if fd then
563            local lsl = ls[n]
564            local rsl = rs[n]
565            if lsl[2] > fd then
566                lsl[2] = fd
567            end
568            if rsl[2] > fd then
569                rsl[2] = fd
570            end
571        end
572    end
573    --
574    for i=n,1,-1 do
575        n = n + 1 rs[n] = ls[i]
576    end
577    return rs
578end
579
580local function singlepart(b,e,p,realpage,r,left,right)
581    local bx = b.x
582    local by = b.y
583    local ex = e.x
584    local ey = e.y
585    local rx = r.x
586    local ry = r.y
587    local bh = by + b.h
588    local bd = by - b.d
589    local eh = ey + e.h
590    local ed = ey - e.d
591    local rh = ry + r.h
592    local rd = ry - r.d
593    local rw = rx + r.w
594    if left then
595        rx = rx + left
596        rw = rw - right
597    end
598    if ex == rx then
599        -- We probably have a strut at the next line so we force a width
600        -- although of course it is better to move up. But as we have whitespace
601        -- (at least visually) injected then it's best to stress the issue.
602        ex = rw
603    end
604    local area
605    if by == ey then
606        if trace_shapes then
607            report_shapes("calculating single area, partial line")
608        end
609        area = {
610            { bx, bh },
611            { ex, eh },
612            { ex, ed },
613            { bx, bd },
614        }
615    elseif b.k == 2 then
616        area = {
617            { rx, bh },
618            { rw, bh },
619            { rw, ed },
620            { rx, ed },
621        }
622    else
623        area = shape("single",b,p,realpage,rx,rw,rd,rh,bh,ed)
624    end
625    if not area then
626        area = {
627            { bx, bh },
628            { rw, bh },
629            { rw, eh },
630            { ex, eh },
631            { ex, ed },
632            { rx, ed },
633            { rx, bd },
634            { bx, bd },
635        }
636    end
637    return {
638        location = "single",
639        region   = r,
640        area     = finish(area),
641    }
642end
643
644local function firstpart(b,e,p,realpage,r,left,right)
645    local bx = b.x
646    local by = b.y
647    local rx = r.x
648    local ry = r.y
649    local bh = by + b.h
650    local bd = by - b.d
651    local rh = ry + r.h
652    local rd = ry - r.d
653    local rw = rx + r.w
654    if left then
655        rx = rx + left
656        rw = rw - right
657    end
658    local area = shape("first",b,p,realpage,rx,rw,rd,rh,bh,false)
659    if not area then
660        if b.k == 2 then
661            area = {
662                { rx, bh },
663                { rw, bh },
664                { rw, rd },
665                { rx, rd },
666            }
667        else
668            area = {
669                { bx, bh },
670                { rw, bh },
671                { rw, rd }, -- { rw, eh },
672                { rx, rd }, -- { rx, ed },
673                { rx, bd },
674                { bx, bd },
675            }
676        end
677    end
678    return {
679        location = "first",
680        region   = r,
681        area     = finish(area),
682    }
683end
684
685local function middlepart(b,e,p,realpage,r,left,right)
686    local rx = r.x
687    local ry = r.y
688    local rh = ry + r.h
689    local rd = ry - r.d
690    local rw = rx + r.w
691    if left then
692        rx = rx + left
693        rw = rw - right
694    end
695    local area = shape("middle",b,p,realpage,rx,rw,rd,rh,false,false)
696    if not area then
697        area = {
698            { rw, rh },
699            { rw, rd },
700            { rx, rd },
701            { rx, rh },
702        }
703    end
704    return {
705        location = "middle",
706        region   = r,
707        area     = finish(area),
708    }
709end
710
711local function lastpart(b,e,p,realpage,r,left,right)
712    local ex = e.x
713    local ey = e.y
714    local rx = r.x
715    local ry = r.y
716    local eh = ey + e.h
717    local ed = ey - e.d
718    local rh = ry + r.h
719    local rd = ry - r.d
720    local rw = rx + r.w
721    if left then
722        rx = rx + left
723        rw = rw - right
724    end
725    local area  = shape("last",b,p,realpage,rx,rw,rd,rh,false,ed)
726    if not area then
727        if b.k == 2 then
728            area = {
729                { rw, rh },
730                { rw, ed },
731                { rx, ed },
732                { rx, rh },
733            }
734        else
735            area = {
736                { rw, rh }, -- { rw, bh },
737                { rw, eh },
738                { ex, eh },
739                { ex, ed },
740                { rx, ed },
741                { rx, rh }, -- { rx, bd },
742            }
743        end
744    end
745    return {
746        location = "last",
747        region   = r,
748        area     = finish(area),
749    }
750end
751
752local function calculatemultipar(tag)
753    local collected = jobpositions.collected
754    local b = collected[f_b_tag(tag)]
755    local e = collected[f_e_tag(tag)]
756    if not b or not e then
757        report_shapes("invalid tag %a",tag)
758        return { }
759    end
760    local br = b.r
761    local er = e.r
762    if not br or not er then
763        report_shapes("invalid region for %a",tag)
764        return { }
765    end
766    local btag, bindex = lpegmatch(splitter,br)
767    local etag, eindex = lpegmatch(splitter,er)
768    if not bindex or not eindex or btag ~= etag then
769        report_shapes("invalid indices for %a",tag)
770        return { }
771    end
772    local bindex = tonumber(bindex)
773    local eindex = tonumber(eindex)
774    -- Here we compensate for columns (in tables): a table can have a set of column
775    -- entries and these are shared. We compensate left/right based on the columns
776    -- x and w but need to take the region into acount where the specification was
777    -- flushed and not the begin pos's region, because otherwise we get the wrong
778    -- compensation for asymetrical doublesided layouts.
779    local left  = 0
780    local right = 0
781    local bc    = b.c
782    local rc    = bc and collected[bc]
783    if rc then
784        local tb = collected[rc.r]
785        if tb then
786            left  = -(tb.x - rc.x)
787            right =  (tb.w - rc.w - left)
788        end
789    end
790    -- Obeying intermediate changes of left/rightskip makes no sense as it will
791    -- look bad, so we only look at the begin situation.
792    local bn = b.n
793    local p  = bn and collected[f_p_tag(bn)] -- par
794    if p then
795        left  = left  + (p.ls or 0)
796        right = right + (p.rs or 0)
797    end
798    --
799    local bp = b.p -- page
800    if trace_shapes then
801        report_shapes("tag %a, left %p, right %p, par %s, page %s, column %s",
802            tag,left,right,bn or "-",bp or "-",bc or "-")
803    end
804    --
805    if bindex == eindex then
806        return {
807            list = { [bp] = { singlepart(b,e,p,bp,collected[br],left,right) } },
808            bpos = b,
809            epos = e,
810        }
811    else
812        local list = {
813            [bp] = { firstpart(b,e,p,bp,collected[br],left,right) },
814        }
815        for i=bindex+1,eindex-1 do
816            br = f_tag_two(btag,i)
817            local r = collected[br]
818            if r then
819                local rp = r.p -- page
820                local pp = list[rp]
821                local mp = middlepart(b,e,p,rp,r,left,right)
822                if pp then
823                    pp[#pp+1] = mp
824                else
825                    list[rp] = { mp }
826                end
827            else
828                report_graphics("invalid middle for %a",br)
829            end
830        end
831        local ep = e.p -- page
832        local pp = list[ep]
833        local lp = lastpart(b,e,p,ep,collected[er],left,right)
834        if pp then
835            pp[#pp+1] = lp
836        else
837            list[ep] = { lp }
838        end
839        return {
840            list = list,
841            bpos = b,
842            epos = e,
843        }
844    end
845end
846
847local pbg = { } -- will move to pending
848
849local multilocs = {
850    single = 1, -- maybe 0
851    first  = 1,
852    middle = 2,
853    last   = 3,
854}
855
856-- if unknown context_abck : input mp-abck.mpiv ; fi ;
857
858local f_template_a = formatters[ [[
859path multiregs[], multipars[], multibox ;
860string multikind[] ;
861numeric multilocs[], nofmultipars ;
862nofmultipars := %s ;
863multibox := unitsquare xyscaled (%p,%p) ;
864numeric par_strut_height, par_strut_depth, par_line_height ;
865par_strut_height := %p ;
866par_strut_depth := %p ;
867par_line_height := %p ;
868]] ]
869
870local f_template_b = formatters[ [[
871multilocs[%s] := %s ;
872multikind[%s] := "%s" ;
873multipars[%s] := (%--t--cycle) shifted - (%p,%p) ;
874]] ]
875
876-- unspiked(simplified(%--t--cycle)) shifted - (%p,%p) ;
877
878local f_template_c = formatters[ [[
879setbounds currentpicture to multibox ;
880]] ]
881
882local function freemultipar(pagedata,frees) -- ,k
883 -- if k == 3 then
884 --     -- tables have local regions
885 --     return
886 -- end
887    if not frees then
888        return
889    end
890    local nfree = #frees
891    if nfree == 0 then
892        return
893    end
894    for i=1,#pagedata do
895        local data  = pagedata[i]
896        local area  = data.area
897
898        if area then
899
900            local region = data.region
901            local y      = 0 -- region.y
902         -- local x      = region.x
903            local areas  = { }
904            data.areas   = areas
905
906            local f_1 = { }
907            local n_1 = 0
908            local f_2 = { }
909            local n_2 = 0
910            for i=1,#frees do
911                local f = frees[i]
912                local k = f.k
913                if k == 1 then               -- pag
914                    n_1 = n_1 + 1
915                    f_1[n_1] = f
916                elseif k == 2 or k == 3 then -- par
917                    n_2 = n_2 + 1
918                    f_2[n_2] = f
919                end
920            end
921
922            local lineheight = tex.dimen.lineheight
923
924            -- page floats
925
926            local function check_one(free1,free2)
927                local temp = { }
928                local some = false
929                local top  = (free2 and (y + free2.y + free2.h + (free2.to or 0))) or false
930                local bot  = (free1 and (y + free1.y - free1.d - (free1.bo or 0))) or false
931                for i=1,#area do
932                    local a = area[i]
933                    local x = a[1]
934                    local y = a[2]
935                    if free2 and y <= top then
936                        y = top
937                    end
938                    if free1 and y >= bot then
939                        y = bot
940                    end
941                    if not some then
942                        some = y
943                    elseif some == true then
944                        -- done
945                    elseif y ~= some then
946                        some = true
947                    end
948                    temp[i] = { x, y }
949                end
950                if some == true then
951                    areas[#areas+1] = temp
952                end
953            end
954
955            if n_1 > 0 then
956                check_one(false,f_1[1])
957                for i=2,n_1 do
958                    check_one(f_1[i-1],f_1[i])
959                end
960                check_one(f_1[n_1],false)
961            end
962
963            -- par floats
964
965            if #areas == 0 then
966                areas[1] = area
967            end
968
969            -- we can collect the coordinates first
970
971            local function check_two(area,frees)
972                local ul  = area[1]
973                local ur  = area[2]
974                local lr  = area[3]
975                local ll  = area[4]
976                local ulx = ul[1]
977                local uly = ul[2]
978                local urx = ur[1]
979                local ury = ur[2]
980                local lrx = lr[1]
981                local lry = lr[2]
982                local llx = ll[1]
983                local lly = ll[2]
984
985                local temp = { }
986                local n    = 0
987                local done = false
988
989                for i=1,#frees do
990                    local free = frees[i]
991                    local fx   = free.x
992                    local fy   = free.y
993                    local ymax = y + fy + free.h + (free.to or 0)
994                    local ymin = y + fy - free.d - (free.bo or 0)
995                    local xmin =     fx          - (free.lo or 0)
996                    local xmax =     fx + free.w + (free.ro or 0)
997                    if free.k == 3 then
998                        if uly <= ymax and uly >= ymin and lly <= ymin then
999                            if trace_free then
1000                                report_free("case 1, top, right") -- ok
1001                            end
1002                            n = n + 1  temp[n] = { xmin, ury  }
1003                            n = n + 1  temp[n] = { xmin, ymin }
1004                            n = n + 1  temp[n] = { lrx,  ymin }
1005                            n = n + 1  temp[n] = { lrx,  lry  }
1006                            done = true
1007                        elseif uly >= ymax and lly <= ymin then
1008                            if trace_free then
1009                                report_free("case 2, outside, right") -- ok
1010                            end
1011                            if uly - ymax < lineheight then
1012                                n = n + 1  temp[n] = { xmin,  ury  }
1013                            else
1014                                n = n + 1  temp[n] = { urx,  ury  }
1015                                n = n + 1  temp[n] = { urx,  ymax }
1016                            end
1017                            n = n + 1  temp[n] = { xmin, ymax }
1018                            n = n + 1  temp[n] = { xmin, ymin }
1019                            n = n + 1  temp[n] = { lrx,  ymin }
1020                            n = n + 1  temp[n] = { lrx,  lry  }
1021                            done = true
1022                        elseif lly <= ymax and lly >= ymin and uly >= ymax then
1023                            if trace_free then
1024                                report_free("case 3, bottom, right")
1025                            end
1026                            if uly - ymax < lineheight then
1027                                n = n + 1  temp[n] = { xmin,  ury  }
1028                            else
1029                                n = n + 1  temp[n] = { urx,  ury  }
1030                                n = n + 1  temp[n] = { urx,  ymax }
1031                            end
1032                            n = n + 1  temp[n] = { xmin, ymax }
1033                            n = n + 1  temp[n] = { xmin, lry  }
1034                            done = true
1035                        elseif uly <= ymax and lly >= ymin then
1036                            if trace_free then
1037                                report_free("case 4, inside, right")
1038                            end
1039                            n = n + 1  temp[n] = { xmin, uly }
1040                            n = n + 1  temp[n] = { xmin, lly }
1041                            done = true
1042                        else
1043                            -- case 0
1044                            if trace_free then
1045                                report_free("case 0, nothing")
1046                            end
1047                        end
1048                    end
1049                end
1050
1051                if not done then
1052                    if trace_free then
1053                        report_free("no right shape")
1054                    end
1055                    n = n + 1  temp[n] = { urx, ury }
1056                    n = n + 1  temp[n] = { lrx, lry }
1057                    n = n + 1  temp[n] = { llx, lly }
1058                else
1059                    done = false
1060                end
1061
1062                for i=#frees,1,-1 do
1063                    local free = frees[i]
1064                    local fx   = free.x
1065                    local fy   = free.y
1066                    local ymax = y + fy + free.h + (free.to or 0)
1067                    local ymin = y + fy - free.d - (free.bo or 0)
1068                    local xmin =     fx          - (free.lo or 0)
1069                    local xmax =     fx + free.w + (free.ro or 0)
1070                    if free.k == 2 then
1071                        if uly <= ymax and uly >= ymin and lly <= ymin then
1072                            if trace_free then
1073                                report_free("case 1, top, left") -- ok
1074                            end
1075                            n = n + 1  temp[n] = { ulx,  ymin }
1076                            n = n + 1  temp[n] = { xmax, ymin }
1077                            n = n + 1  temp[n] = { xmax, uly  }
1078                            done = true
1079                        elseif uly >= ymax and lly <= ymin then
1080                            if trace_free then
1081                                report_free("case 2, outside, left") -- ok
1082                            end
1083                            n = n + 1  temp[n] = { llx,  lly  }
1084                            n = n + 1  temp[n] = { llx,  ymin }
1085                            n = n + 1  temp[n] = { xmax, ymin }
1086                            n = n + 1  temp[n] = { xmax, ymax }
1087                            if uly - ymax < lineheight then
1088                                n = n + 1  temp[n] = { xmax,  uly }
1089                            else
1090                                n = n + 1  temp[n] = { llx,  ymax }
1091                                n = n + 1  temp[n] = { llx,  uly  }
1092                            end
1093                            done = true
1094                        elseif lly <= ymax and lly >= ymin and uly >= ymax then
1095                            if trace_free then
1096                                report_free("case 3, bottom, left")
1097                            end
1098                            n = n + 1  temp[n] = { xmax, lly }
1099                            n = n + 1  temp[n] = { xmax, ymax }
1100                            if uly - ymax < lineheight then
1101                                n = n + 1  temp[n] = { xmax,  uly }
1102                            else
1103                                n = n + 1  temp[n] = { llx,  ymax }
1104                                n = n + 1  temp[n] = { llx,  uly }
1105                            end
1106                            done = true
1107                        elseif uly <= ymax and lly >= ymin then
1108                            if trace_free then
1109                                report_free("case 4, inside, left")
1110                            end
1111                            n = n + 1  temp[n] = { xmax, lly }
1112                            n = n + 1  temp[n] = { xmax, uly }
1113                            done = true
1114                        else
1115                            -- case 0
1116                        end
1117                    end
1118                end
1119
1120                if not done then
1121                    if trace_free then
1122                        report_free("no left shape")
1123                    end
1124                    n = n + 1  temp[n] = { llx, lly }
1125                end
1126                n = n + 1  temp[n] = { ulx, uly }
1127
1128                return temp
1129            end
1130
1131            if n_2 > 0 then
1132                for i=1,#areas do
1133                    local area = areas[i]
1134                    if #area == 4 then -- and also check type, must be pargaraph
1135                        areas[i] = check_two(area,f_2)
1136                    else
1137                        -- message that not yet supported
1138                    end
1139                end
1140            end
1141
1142            for i=1,#areas do
1143                finish(areas[i]) -- again
1144            end
1145
1146        end
1147
1148    end
1149end
1150
1151local function fetchmultipar(n,anchor,page)
1152    local a = jobpositions.collected[anchor]
1153    if not a then
1154        report_graphics("missing anchor %a",anchor)
1155    else
1156        local data = pbg[n]
1157        if not data then
1158            data = calculatemultipar(n)
1159            pbg[n] = data -- can be replaced by register
1160         -- register(data.list,n,anchor)
1161        end
1162        local list = data and data.list
1163        if list then
1164            local pagedata = list[page]
1165            if pagedata then
1166                local k = data.bpos.k
1167                if k ~= 3 then
1168                    -- to be checked: no need in txt mode
1169                    freemultipar(pagedata,getfree(page))
1170                end
1171                local nofmultipars = #pagedata
1172                if trace_shapes then
1173                    report_graphics("fetching %a at page %s using anchor %a containing %s multipars",
1174                        n,page,anchor,nofmultipars)
1175                end
1176                local x      = a.x
1177                local y      = a.y
1178                local w      = a.w
1179                local h      = a.h
1180                local d      = a.d
1181                local bpos   = data.bpos
1182                local bh     = bpos.h
1183                local bd     = bpos.d
1184                local result = { false } -- slot 1 will be set later
1185                local n      = 0
1186                for i=1,nofmultipars do
1187                    local data     = pagedata[i]
1188                    local location = data.location
1189                    local region   = data.region
1190                    local areas    = data.areas
1191                    if not areas then
1192                        areas = { data.area }
1193                    end
1194                    for i=1,#areas do
1195                        local area = areas[i]
1196                        for i=1,#area do
1197                            local a = area[i]
1198                            area[i] = f_pair(a[1],a[2])
1199                        end
1200                        n = n + 1
1201                        result[n+1] = f_template_b(n,multilocs[location],n,location,n,area,x,y)
1202                    end
1203                end
1204                data[page]  = nil
1205                result[1]   = f_template_a(n,w,h+d,bh,bd,bh+bd) -- was delayed
1206                result[n+2] = f_template_c()
1207                return concat(result,"\n")
1208            end
1209        end
1210    end
1211    return f_template_a(0,0,0,0,0,0);
1212end
1213
1214backgrounds.fetchmultipar = fetchmultipar
1215
1216local function getwhatever(action)
1217     local tags   = scanmpstring()
1218     local anchor = scanmpstring()
1219     local page   = texgetcount(c_realpageno)
1220     if tags == "self" then
1221        tags = expandasvalue(string_value,"mpcategoryparameter",true,"self")
1222     elseif type(tags) == "string" then
1223        tags = settings_to_array(tags)
1224        for i=1,#tags do
1225            tags[i] = expandasvalue(string_value,"mpcategoryparameter",true,tags[i])
1226        end
1227     end
1228     if anchor == "anchor" then
1229        anchor = getmacro("MPanchorid") -- brrr
1230     end
1231     if tags and anchor then
1232        return action(tags,anchor,page)
1233     end
1234end
1235
1236-- backgrounds.getwhatever = getwhatever -- public for tracing
1237
1238metapost.registerscript("getmultipars", function()
1239    return getwhatever(fetchmultipar)
1240end)
1241
1242-- n anchor page
1243
1244implement {
1245    name      = "fetchmultipar",
1246    actions   = { fetchmultipar, context },
1247    arguments = { "string", "string", "integer" }
1248}
1249
1250-- todo: use inject
1251
1252do
1253
1254    local f_template_a = formatters[ [[
1255    path posboxes[], posregions[] ;
1256    numeric pospages[] ;
1257    numeric nofposboxes ;
1258    nofposboxes := %s ;
1259    %t
1260    ]] ]
1261
1262    local f_template_b = formatters[ [[
1263    pospages[%s] := %s ;
1264    posboxes[%s] := (%p,%p)--(%p,%p)--(%p,%p)--(%p,%p)--cycle ;
1265    posregions[%s] := (%p,%p)--(%p,%p)--(%p,%p)--(%p,%p)--cycle ;
1266    ]] ]
1267
1268    local function getposboxes(tags,anchor,page)  -- no caching (yet) / page
1269        local collected = jobpositions.collected
1270        if type(tags) == "string" then
1271            tags = settings_to_array(tags)
1272        end
1273        local list     = { }
1274        local nofboxes = 0
1275        for i=1,#tags do
1276            local tag= tags[i]
1277            local c = collected[tag]
1278            if c then
1279                local r = c.r
1280                if anchor ~= r then
1281                    r = anchor
1282                end
1283                if r then
1284                    r = collected[r]
1285                    if r then
1286                        local rx = r.x
1287                        local ry = r.y
1288                        local rw = r.w
1289                        local rh = r.h
1290                        local rd = r.d
1291                        local cx = c.x - rx
1292                        local cy = c.y - ry
1293                        local cw = cx + c.w
1294                        local ch = cy + c.h
1295                        local cd = cy - c.d
1296                        nofboxes = nofboxes + 1
1297                        list[nofboxes] = f_template_b(
1298                            nofboxes,c.p,
1299                            nofboxes,cx,ch,cw,ch,cw,cd,cx,cd,
1300                            nofboxes,0,rh,rw,rh,rw,rd,0,rd
1301                        )
1302                    end
1303                end
1304            else
1305             -- print("\n missing",tag)
1306            end
1307        end
1308        return f_template_a(nofboxes,list)
1309    end
1310
1311    metapost.registerscript("getposboxes", function()
1312        return getwhatever(getposboxes)
1313    end)
1314
1315    implement {
1316        name      = "fetchposboxes",
1317        arguments = { "string", "string", "integer" },
1318        actions   = { getposboxes, context },
1319    }
1320
1321end
1322
1323do
1324
1325    implement {
1326        name      = "doifelserangeonpage",
1327        arguments = { "string", "string", "integer" },
1328        actions   = function(first,last,page)
1329            local c = jobpositions.collected
1330            local f = c[first]
1331            if f then
1332                f = f.p
1333                if f and f ~= true and page >= f then
1334                    local l = c[last]
1335                    if l then
1336                        l = l.p
1337                        ctx_doifelse(l and l ~= true and page <= l)
1338                        return
1339                    end
1340                end
1341            end
1342            ctx_doifelse(false)
1343        end
1344    }
1345
1346end
1347