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