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