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