page-cst.lua /size: 35 Kb    last modification: 2025-02-21 11:03
1if not modules then modules = { } end modules ["page-cst"] = {
2    version   = 1.001,
3    comment   = "companion to page-cst.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
9local next, type, tonumber, rawget = next, type, tonumber, rawget
10local ceil, odd, round, abs = math.ceil, math.odd, math.round, math.abs
11local lower = string.lower
12local copy = table.copy
13
14local trace_state   = false  trackers.register("columnsets.trace",   function(v) trace_state  = v end)
15local trace_details = false  trackers.register("columnsets.details", function(v) trace_details = v end)
16local trace_cells   = false  trackers.register("columnsets.cells",   function(v) trace_cells  = v end)
17
18local report            = logs.reporter("column sets")
19
20local setmetatableindex = table.setmetatableindex
21
22local properties        = nodes.properties.data
23
24local nuts              = nodes.nuts
25local tonode            = nuts.tonode
26local tonut             = nuts.tonut
27
28local setlink           = nuts.setlink
29local setbox            = nuts.setbox
30local getwhd            = nuts.getwhd
31local setwhd            = nuts.setwhd
32
33local getlist           = nuts.getlist
34local takebox           = nuts.takebox
35local splitbox          = nuts.splitbox
36local vpack             = nuts.vpack
37
38local getbox            = nuts.getbox
39
40local texgetcount       = tex.getcount
41local texgetdimen       = tex.getdimen
42local texsetcount       = tex.setcount
43local texsetdimen       = tex.setdimen
44
45local theprop           = nuts.theprop
46
47local nodepool          = nuts.pool
48local new_vlist         = nodepool.vlist
49local new_trace_rule    = nodepool.rule
50local new_empty_rule    = nodepool.emptyrule
51
52local context           = context
53local implement         = interfaces.implement
54
55local variables         = interfaces.variables
56local v_here            = variables.here
57local v_fixed           = variables.fixed
58local v_top             = variables.top
59local v_bottom          = variables.bottom
60local v_repeat          = variables["repeat"]
61local v_yes             = variables.yes
62local v_page            = variables.page
63local v_first           = variables.first
64local v_last            = variables.last
65----- v_wide            = variables.wide
66
67pagebuilders            = pagebuilders or { } -- todo: pages.builders
68local columnsets        = pagebuilders.columnsets or { }
69
70pagebuilders.columnsets = columnsets
71
72local data = { [""] = { } }
73
74-- todo: use state
75
76local function setstate(t,start)
77    if start or not t.firstcolumn then
78        t.firstcolumn = odd(texgetcount("realpageno")) and 1 or 2
79    end
80    if t.firstcolumn > 1 then
81        t.firstcolumn = 1
82        t.lastcolumn  = t.nofleft
83        t.state       = "left"
84    else
85        t.firstcolumn = t.nofleft + 1
86        t.lastcolumn  = t.firstcolumn + t.nofright - 1
87        t.state       = "right"
88    end
89    t.currentcolumn = t.firstcolumn
90    t.currentrow    = 1
91end
92
93function columnsets.define(t)
94    local name            = t.name
95    local nofleft         = t.nofleft or 1
96    local nofright        = t.nofright or 1
97    local nofcolumns      = nofleft + nofright
98    local dataset         = data[name] or { }
99    data[name]            = dataset
100    dataset.nofleft       = nofleft
101    dataset.nofright      = nofright
102    dataset.nofcolumns    = nofcolumns
103    dataset.nofrows       = t.nofrows    or 1
104    dataset.distance      = t.distance   or texgetdimen("bodyfontsize")
105    dataset.maxwidth      = t.maxwidth   or texgetdimen("makeupwidth")
106    dataset.lineheight    = t.lineheight or texgetdimen("globalbodyfontstrutheight")
107    dataset.linedepth     = t.linedepth  or texgetdimen("globalbodyfontstrutdepth")
108    --
109    dataset.cells         = { }
110    dataset.currentcolumn = 1
111    dataset.currentrow    = 1
112    --
113    dataset.lines         = dataset.lines or setmetatableindex("table")
114    dataset.start         = dataset.start or setmetatableindex("table")
115    --
116    dataset.page          = 1
117    --
118    local distances = dataset.distances or setmetatableindex(function(t,k)
119        return dataset.distance
120    end)
121    dataset.distances = distances
122    --
123    local widths = dataset.widths or setmetatableindex(function(t,k)
124        return dataset.width
125    end)
126    dataset.widths = widths
127    --
128    local width = t.width
129    if not width or width == 0 then
130        local dl = 0
131        local dr = 0
132        for i=1,nofleft-1 do
133            dl = dl + distances[i]
134        end
135        for i=1,nofright-1 do
136            dr = dr + distances[nofleft+i]
137        end
138        local nl = nofleft
139        local nr = nofright
140        local wl = dataset.maxwidth
141        local wr = wl
142        for i=1,nofleft do
143            local w = rawget(widths,i)
144            if w then
145                nl = nl - 1
146                wl = wl - w
147            end
148        end
149        for i=1,nofright do
150            local w = rawget(widths,nofleft+i)
151            if w then
152                nr = nr - 1
153                wr = wr - w
154            end
155        end
156        dl = (wl - dl) / nl
157        dr = (wr - dr) / nr
158        if dl > dr then
159            report("using %s page column width %p in columnset %a","right",dr,name)
160            width = dr
161        elseif dl < dr then
162            report("using %s page column width %p in columnset %a","left",dl,name)
163            width = dl
164        else
165            width = dl
166        end
167    end
168 -- report("width %p, nleft %i, nright %i",width,nofleft,nofright)
169    width         = round(width)
170    dataset.width = width
171    local spans   = { }
172    dataset.spans = spans
173    for i=1,nofleft do
174        local s = { }
175        local d = 0
176        for j=1,nofleft-i+1 do
177            d = d + width
178            s[j] = round(d)
179            d = d + distances[j]
180        end
181        spans[i]  = s
182    end
183    for i=1,nofright do
184        local s = { }
185        local d = 0
186        for j=1,nofright-i+1 do
187            d = d + width
188            s[j] = round(d)
189            d = d + distances[j]
190        end
191        spans[nofleft+i]  = s
192    end
193    --
194    local spreads   = copy(spans)
195    dataset.spreads = spreads
196    local gap       = 2 * texgetdimen("backspace")
197    for l=1,nofleft do
198        local s = spreads[l]
199        local n = #s
200        local o = s[n] + gap
201        for r=1,nofright do
202            n = n + 1
203            s[n] = s[r] + o
204        end
205    end
206    --
207    texsetdimen("d_page_grd_column_width",dataset.width)
208    --
209    setstate(dataset,true)
210    --
211    return dataset
212end
213
214local function check(dataset)
215    local cells  = dataset.cells
216    local page   = dataset.page
217    local offset = odd(page) and dataset.nofleft or 0
218    local start  = dataset.start
219    local list   = rawget(start,page)
220    if list then
221        for c, n in next, list do
222            local column = cells[offset + c]
223            if column then
224                for r=1,n do
225                    column[r] = true
226                end
227            end
228        end
229        start[page] = nil
230    end
231    local lines = dataset.lines
232    local list  = rawget(lines,page)
233    local rows  = dataset.nofrows
234    if list then
235        for c, n in next, list do
236            local column = cells[offset + c]
237            if column then
238                if n > 0 then
239                    for r=n+1,rows do
240                        column[r] = true
241                    end
242                elseif n < 0 then
243                    for r=rows,rows+n+1,-1 do
244                        column[r] = true
245                    end
246                end
247            end
248        end
249        lines[page] = nil
250    end
251end
252
253local function erase(dataset,all)
254    local cells   = dataset.cells
255    local nofrows = dataset.nofrows
256    local first   = 1
257    local last    = dataset.nofcolumns
258    --
259    if not all then
260        first = dataset.firstcolumn or first
261        last  = dataset.lastcolumn  or last
262    end
263    for c=first,last do
264        local column = { }
265        for r=1,nofrows do
266            if column[r] then
267                report("slot (%i,%i) is not empty",c,r)
268            end
269            column[r] = false -- not used
270        end
271        cells[c] = column
272    end
273end
274
275function columnsets.reset(t)
276    local dataset = columnsets.define(t)
277    erase(dataset,true)
278    check(dataset)
279end
280
281function columnsets.prepareflush(name)
282    local dataset     = data[name]
283    local cells       = dataset.cells
284    local firstcolumn = dataset.firstcolumn
285    local lastcolumn  = dataset.lastcolumn
286    local nofrows     = dataset.nofrows
287    local lineheight  = dataset.lineheight
288    local linedepth   = dataset.linedepth
289    local widths      = dataset.widths
290    local height      = (lineheight+linedepth)*nofrows -- - linedepth
291    --
292    local columns     = { }
293    dataset.columns   = columns
294    --
295    for c=firstcolumn,lastcolumn do
296        local column = cells[c]
297        for r=1,nofrows do
298            local cell = column[r]
299            if (cell == false) or (cell == true) then
300                if trace_cells then
301                    column[r] = new_trace_rule(65536*2,lineheight,linedepth)
302                else
303                    column[r] = new_empty_rule(0,lineheight,linedepth)
304                end
305            end
306        end
307        for r=1,nofrows-1 do
308            setlink(column[r],column[r+1])
309        end
310        columns[c] = new_vlist(column[1],widths[c],height,0) -- linedepth
311    end
312    --
313    texsetcount("c_page_grd_first_column",firstcolumn)
314    texsetcount("c_page_grd_last_column",lastcolumn)
315end
316
317function columnsets.flushcolumn(name,column)
318    local dataset = data[name]
319    local columns = dataset.columns
320    local packed  = columns[column]
321    setbox("b_page_grd_column",packed)
322    columns[column] = nil
323end
324
325function columnsets.finishflush(name)
326    local dataset     = data[name]
327    local cells       = dataset.cells
328    local firstcolumn = dataset.firstcolumn
329    local lastcolumn  = dataset.lastcolumn
330    local nofrows     = dataset.nofrows
331    for c=firstcolumn,lastcolumn do
332        local column = { }
333        for r=1,nofrows do
334            column[r] = false -- not used
335        end
336        cells[c] = column
337    end
338    dataset.page = dataset.page + 1
339    check(dataset)
340    setstate(dataset)
341end
342
343function columnsets.block(t)
344    local dataset    = data[t.name]
345    local cells      = dataset.cells
346    local nofcolumns = dataset.nofcolumns
347    local nofrows    = dataset.nofrows
348    --
349    local c = t.c or 0
350    local r = t.r or 0
351    if c == 0 or r == 0 or c > nofcolumns or r > nofrows then
352        return
353    end
354    local nc = t.nc or 0
355    local nr = t.nr or 0
356    if nc == 0 then
357        return
358    end
359    if nr == 0 then
360        return
361    end
362    local rr = r + nr - 1
363    local cc = c + nc - 1
364    if rr > nofrows then
365        rr = nofrows
366    end
367    if cc > nofcolumns then
368        cc = nofcolumns
369    end
370    for i=c,cc do
371        local column = cells[i]
372        for j=r,rr do
373            column[j] = true
374        end
375    end
376end
377
378local function here(c,r,nr,nofcolumns,nofrows,cells,width,spans)
379    local rr = r + nr - 1
380    if rr > nofrows then
381        report("%i rows needed, %i rows available, no slots free at (%i,%i), discarding",rr,nofrows,c,r)
382        return false
383    end
384    local cc = 0
385    local wd = spans[c]
386    local wc = 0
387    local nc = 0
388    for i=c,nofcolumns do
389        nc = nc + 1
390        wc = wd[nc]
391        if not wc then
392            break
393        elseif wc >= width then
394            cc = i
395            break
396        end
397    end
398    if cc == 0 or cc > nofcolumns then
399        report("needed %p, no slot free at (%i,%i), discarding",width,c,r)
400        return false
401    end
402    for i=c,cc do
403        local column = cells[i]
404        for j=r,rr do
405            if column[j] then
406                report("width %p, needed %p, checking (%i,%i) x (%i,%i), %s",width,wc,c,r,nc,nr,"quit")
407                return false
408            end
409        end
410    end
411 -- report("width %p, needed %p, checking (%i,%i) x (%i,%i), %s",width,wc,c,r,nc,nr,"match")
412    return c, r, nc
413end
414
415-- we use c/r as range limiters
416
417local methods = {
418    [v_here] = here,
419    [v_fixed] = here,
420    tblr = function(c,r,nr,nofcolumns,nofrows,cells,width,spans)
421        for j=r,nofrows-nr+1 do
422            for i=c,nofcolumns do
423                if not cells[i][j] then
424                    local c, r, cc = here(i,j,nr,nofcolumns,nofrows,cells,width,spans)
425                    if c then
426                        return c, r, cc
427                    end
428                end
429            end
430        end
431    end,
432    lrtb = function(c,r,nr,nofcolumns,nofrows,cells,width,spans)
433        for i=c,nofcolumns do
434            for j=r,nofrows-nr+1 do
435                if not cells[i][j] then
436                    local c, r, cc = here(i,j,nr,nofcolumns,nofrows,cells,width,spans)
437                    if c then
438                        return c, r, cc
439                    end
440                end
441            end
442        end
443    end,
444    tbrl = function(c,r,nr,nofcolumns,nofrows,cells,width,spans)
445        for j=r,nofrows-nr+1 do
446            for i=nofcolumns,c,-1 do
447                if not cells[i][j] then
448                    local c, r, cc = here(i,j,nr,nofcolumns,nofrows,cells,width,spans)
449                    if c then
450                        return c, r, cc
451                    end
452                end
453            end
454        end
455    end,
456    rltb = function(c,r,nr,nofcolumns,nofrows,cells,width,spans)
457        for i=nofcolumns,c,-1 do
458            for j=r,nofrows-nr+1 do
459                if not cells[i][j] then
460                    local c, r, cc = here(i,j,nr,nofcolumns,nofrows,cells,width,spans)
461                    if c then
462                        return c, r, cc
463                    end
464                end
465            end
466        end
467    end,
468    btlr = function(c,r,nr,nofcolumns,nofrows,cells,width,spans)
469     -- for j=nofrows-nr+1,1,-1 do
470        for j=nofrows-nr+1-r+1,1,-1 do
471            for i=c,nofcolumns do
472                if not cells[i][j] then
473                    local c, r, cc = here(i,j,nr,nofcolumns,nofrows,cells,width,spans)
474                    if c then
475                        return c, r, cc
476                    end
477                end
478            end
479        end
480    end,
481    lrbt = function(c,r,nr,nofcolumns,nofrows,cells,width,spans)
482        for i=c,nofcolumns do
483         -- for j=nofrows-nr+1,1,-1 do
484            for j=nofrows-nr+1-r+1,1,-1 do
485                if not cells[i][j] then
486                    local c, r, cc = here(i,j,nr,nofcolumns,nofrows,cells,width,spans)
487                    if c then
488                        return c, r, cc
489                    end
490                end
491            end
492        end
493    end,
494    btrl = function(c,r,nr,nofcolumns,nofrows,cells,width,spans)
495     -- for j=nofrows-nr+1,1,-1 do
496        for j=nofrows-nr+1-r+1,1,-1 do
497            for i=nofcolumns,c,-1 do
498                if not cells[i][j] then
499                    local c, r, cc = here(i,j,nr,nofcolumns,nofrows,cells,width,spans)
500                    if c then
501                        return c, r, cc
502                    end
503                end
504            end
505        end
506    end,
507    rlbt = function(c,r,nr,nofcolumns,nofrows,cells,width,spans)
508        for i=nofcolumns,c,-1 do
509         -- for j=nofrows-nr+1,1,-1 do
510            for j=nofrows-nr+1-r+1,1,-1 do
511                if not cells[i][j] then
512                    local c, r, cc = here(i,j,nr,nofcolumns,nofrows,cells,width,spans)
513                    if c then
514                        return c, r, cc
515                    end
516                end
517            end
518        end
519    end,
520    fxtb = function(c,r,nr,nofcolumns,nofrows,cells,width,spans)
521        for i=c,nofcolumns do
522            for j=r,nofrows-nr+1 do
523                if not cells[i][j] then
524                    local c, r, cc = here(i,j,nr,nofcolumns,nofrows,cells,width,spans)
525                    if c then
526                        return c, r, cc
527                    end
528                end
529                r = 1
530            end
531        end
532    end,
533    fxbt = function(c,r,nr,nofcolumns,nofrows,cells,width,spans)
534        for i=c,nofcolumns do
535            for j=nofrows-nr+1,r,-1 do
536                if not cells[i][j] then
537                    local c, r, cc = here(i,j,nr,nofcolumns,nofrows,cells,width,spans)
538                    if c then
539                        return c, r, cc
540                    end
541                end
542            end
543            r = 1
544        end
545    end,
546    [v_top] = function(c,r,nr,nofcolumns,nofrows,cells,width,spans)
547        for i=c,nofcolumns do
548            for j=1,nofrows-nr+1 do
549                if cells[i][j] then
550                    break
551                else
552                    local c, r, cc = here(i,j,nr,nofcolumns,nofrows,cells,width,spans)
553                    if c then
554                        return c, r, cc
555                    end
556                end
557            end
558        end
559    end,
560    [v_bottom] = function(c,r,nr,nofcolumns,nofrows,cells,width,spans)
561        for i=c,nofcolumns do
562            for j=1,nofrows-nr+1 do
563                if cells[i][j] then
564                    break
565                else
566                    local c, r, cc = here(i,j,nr,nofcolumns,nofrows,cells,width,spans)
567                    if c then
568                        return c, r, cc
569                    end
570                end
571            end
572        end
573    end,
574}
575
576local threshold = 50
577
578function columnsets.check(t)
579    local dataset    = data[t.name]
580    local cells      = dataset.cells
581    local nofcolumns = dataset.nofcolumns
582    local nofrows    = dataset.nofrows
583    local widths     = dataset.widths
584    local lineheight = dataset.lineheight
585    local linedepth  = dataset.linedepth
586    local distances  = dataset.distances
587    local spans      = dataset.spans
588    --
589    local method     = lower(t.method or "tblr")
590    local boxwidth   = t.width  or 0
591    local boxheight  = t.height or 0
592    local boxnumber  = t.box
593    local box        = boxnumber and getbox(boxnumber)
594    --
595    if boxwidth > 0 and boxheight > 0 then
596        -- we're ok
597    elseif box then
598        local wd, ht, dp = getwhd(box)
599        boxwidth  = wd
600        boxheight = ht + dp
601    else
602        report("empty box")
603        return
604    end
605    --
606    local c = t.c or 0
607    local r = t.r or 0
608    if c == 0 then
609        c = dataset.currentcolumn
610    end
611    if r == 0 then
612        r = dataset.currentrow
613    end
614    if c == 0 or r == 0 or c > nofcolumns or r > nofrows then
615        texsetcount("c_page_grd_reserved_state",5)
616        return
617    end
618 -- report("checking width %p, height %p, depth %p, slot (%i,%i)",boxwidth,boxheight,boxdepth,c,r)
619    local nr = ceil(boxheight/(lineheight+linedepth))
620    --
621    local action = methods[method]
622    local cfound = false
623    local rfound = false
624    local lastcolumn = dataset.lastcolumn
625 -- if t.option == v_wide then
626 --     lastcolumn = nofcolumns
627 --     spans = dataset.spreads
628 -- end
629    if action then
630        cfound, rfound, nc = action(c,r,nr,lastcolumn,nofrows,cells,boxwidth-threshold,spans)
631    end
632    if not cfound and method ~= v_here then
633--     if not cfound and method == v_here then
634        cfound, rfound, nc = here(c,r,nr,lastcolumn,nofrows,cells,boxwidth-threshold,spans)
635    end
636    if cfound then
637        local ht = nr*(lineheight+linedepth)
638        local wd = spans[cfound][nc]
639        dataset.reserved_ht = ht
640        dataset.reserved_wd = wd
641        dataset.reserved_c  = cfound
642        dataset.reserved_r  = rfound
643        dataset.reserved_nc = nc
644        dataset.reserved_nr = nr
645        texsetcount("c_page_grd_reserved_state",0)
646        texsetdimen("d_page_grd_reserved_height",ht)
647        texsetdimen("d_page_grd_reserved_width",wd)
648     -- report("using (%i,%i) x (%i,%i) @ (%p,%p)",cfound,rfound,nc,nr,wd,ht)
649    else
650        dataset.reserved_ht = false
651        dataset.reserved_wd = false
652        dataset.reserved_c  = false
653        dataset.reserved_r  = false
654        dataset.reserved_nc = false
655        dataset.reserved_nr = false
656        texsetcount("c_page_grd_reserved_state",4)
657     -- texsetdimen("d_page_grd_reserved_height",0)
658     -- texsetdimen("d_page_grd_reserved_width",0)
659     -- report("no slot found")
660    end
661end
662
663function columnsets.put(t)
664    local dataset    = data[t.name]
665    local cells      = dataset.cells
666    local widths     = dataset.widths
667    local lineheight = dataset.lineheight
668    local linedepth  = dataset.linedepth
669    local boxnumber  = t.box
670    local box        = boxnumber and takebox(boxnumber)
671    --
672    local c = t.c or dataset.reserved_c
673    local r = t.r or dataset.reserved_r
674    if not c or not r then
675     -- report("no reserved slot (%i,%i)",c,r)
676        return
677    end
678    local lastc = c + dataset.reserved_nc - 1
679    local lastr = r + dataset.reserved_nr - 1
680    --
681    for i=c,lastc do
682        local column = cells[i]
683        for j=r,lastr do
684            column[j] = true
685        end
686    end
687    cells[c][r] = box
688    setwhd(box,widths[c],lineheight,linedepth)
689    dataset.reserved_c  = false
690    dataset.reserved_r  = false
691    dataset.reserved_nc = false
692    dataset.reserved_nr = false
693    --
694end
695
696local function findgap(dataset)
697    local cells         = dataset.cells
698    local nofcolumns    = dataset.nofcolumns
699    local nofrows       = dataset.nofrows
700    local currentrow    = dataset.currentrow
701    local currentcolumn = dataset.currentcolumn
702    --
703    local foundc = 0
704    local foundr = 0
705    local foundn = 0
706    for c=currentcolumn,dataset.lastcolumn do
707        local column = cells[c]
708        foundn = 0
709        for r=currentrow,nofrows do
710            if not column[r] then
711                if foundc == 0 then
712                    foundc = c
713                    foundr = r
714                end
715                foundn = foundn + 1
716            elseif foundn > 0 then
717                return foundc, foundr, foundn
718            end
719        end
720        if foundn > 0 then
721            return foundc, foundr, foundn
722        end
723        currentrow = 1
724    end
725end
726
727-- we can enforce grid snapping
728
729local nofcolumngaps = 0
730
731function columnsets.add(name,box)
732    local dataset       = data[name]
733    local cells         = dataset.cells
734    local nofcolumns    = dataset.nofcolumns
735    local nofrows       = dataset.nofrows
736    local currentrow    = dataset.currentrow
737    local currentcolumn = dataset.currentcolumn
738    local lineheight    = dataset.lineheight
739    local linedepth     = dataset.linedepth
740    local widths        = dataset.widths
741    --
742    local b = getbox(box)
743    local l = b and getlist(b)
744    if l then
745        local hd = lineheight + linedepth
746        while l do
747            local foundc, foundr, foundn = findgap(dataset)
748            if foundc then
749                local available = foundn * hd
750                --
751                if available < hd then
752                    local column = cells[foundc]
753                    for i=foundr,foundr+foundn-1 do
754                        column[i] = true
755                    end
756                    -- now l needs top skip
757                    -- and the rest not
758                else
759                    local first = splitbox(box,available,"additional") -- upto
760                    if first then
761
762                        local used = nuts.gettotal(first)
763                        local foundm = ceil(used/hd)
764                        if abs(foundm-foundn) == 1 then
765                            first = vpack(getlist(first),available,"exactly")
766                            used = nuts.gettotal(first)
767                            foundm = ceil(used/hd)
768                        else
769                            foundn = foundm
770                        end
771
772                        local v = first
773                        nofcolumngaps = nofcolumngaps + 1
774                        -- getmetatable(v).columngap = nofcolumngaps
775                        properties[v] = { columngap = nofcolumngaps }
776                     -- report("setting gap %a at (%i,%i)",nofcolumngaps,foundc,foundr)
777                        setwhd(v,widths[currentcolumn],lineheight,linedepth)
778                        local column = cells[foundc]
779                        --
780                        column[foundr] = v
781                        used = used - hd
782                        if used > 0 then
783                            for r=foundr+1,foundr+foundn-1 do
784                                used = used - hd
785                                foundr = foundr + 1
786                                column[r] = true
787                                if used <= 0 then
788                                    break
789                                end
790                            end
791                        end
792                        currentcolumn = foundc
793                        currentrow    = foundr
794                        dataset.currentcolumn = currentcolumn
795                        dataset.currentrow    = currentrow
796                        b = getbox(box)
797                        l = b and getlist(b)
798                    else
799                        local column = cells[foundc]
800                        for i=foundr,foundr+foundn-1 do
801                            column[i] = true
802                        end
803                        b = getbox(box)
804                        l = b and getlist(b)
805                    end
806                end
807            else
808                b = getbox(box)
809                l = b and getlist(b)
810                break
811            end
812        end
813    end
814end
815
816do
817
818    -- A split approach is more efficient than a context(followup) inside
819    -- followup itself as we need less (internal) housekeeping.
820
821    local followup = nil
822    local splitter = lpeg.splitter("*",tonumber)
823
824    columnsets["noto"] = function(t)
825        return followup()
826    end
827
828    columnsets["goto"] = function(name,target)
829        local dataset    = data[name]
830        local nofcolumns = dataset.nofcolumns
831        if target == v_yes or target == "" then
832            local currentcolumn = dataset.currentcolumn
833            followup = function()
834                context(dataset.currentcolumn == currentcolumn and 1 or 0)
835            end
836            return followup()
837        end
838        if target == v_first then
839            if dataset.currentcolumn > 1  then
840                target = v_page
841            else
842                return context(0)
843            end
844        end
845        if target == v_page then
846            if dataset.currentcolumn == 1 and dataset.currentrow == 1 then
847                return context(0)
848            else
849                local currentpage = dataset.page
850                followup = function()
851                    context(dataset.page == currentpage and 1 or 0)
852                end
853                return followup()
854            end
855        end
856        if target == v_last then
857            target = dataset.nofcolumns
858            if dataset.currentcolumn ~= target then
859                followup = function()
860                    context(dataset.currentcolumn ~= target and 1 or 0)
861                end
862                return followup()
863            end
864            return
865        end
866        local targetpage = tonumber(target)
867        if targetpage then
868            followup = function()
869                context(dataset.currentcolumn ~= targetpage and 1 or 0)
870            end
871            return followup()
872        end
873        local targetcolumn, targetrow = lpeg.match(splitter,target)
874        if targetcolumn and targetrow then
875            if dataset.currentcolumn ~= targetcolumn and dataset.currentrow ~= targetrow then
876                followup = function()
877                    if dataset.currentcolumn ~= targetcolumn then
878                        context(1)
879                        return
880                    end
881                    if dataset.currentcolumn == targetcolumn then
882                        context(dataset.currentrow ~= targetrow and 1 or 0)
883                    else
884                        context(0)
885                    end
886                end
887                return followup()
888            end
889        end
890    end
891
892end
893
894function columnsets.currentcolumn(name)
895    local dataset = data[name]
896    context(dataset.currentcolumn)
897end
898
899function columnsets.flushrest(name,box)
900    local dataset = data[name]
901    local rest    = dataset.rest
902    if rest then
903        dataset.rest = nil
904        setbox("global",box,new_vlist(rest))
905    end
906end
907
908function columnsets.setvsize(name)
909    local dataset = data[name]
910    local c, r, n = findgap(dataset)
911    if n then
912        dataset.currentcolumn = c
913        dataset.currentrow    = r
914    else
915        dataset.currentcolumn = 1
916        dataset.currentrow    = 1
917        n = 0
918    end
919    local gap = n*(dataset.lineheight+dataset.linedepth)
920    texsetdimen("d_page_grd_gap_height",gap)
921    -- can be integrated
922 -- report("state %a, n %a, column %a, row %a",dataset.state,n,dataset.currentcolumn,dataset.currentrow)
923end
924
925function columnsets.sethsize(name)
926    local dataset = data[name]
927    texsetdimen("d_page_grd_column_width",dataset.widths[dataset.currentcolumn])
928end
929
930function columnsets.sethspan(name,span)
931    -- no checking if there is really space, so we assume it can be
932    -- placed which makes spans a very explicit feature
933    local dataset   = data[name]
934    local column    = dataset.currentcolumn
935    local available = dataset.lastcolumn - column + 1
936    if span > available then
937        span = available
938    end
939    local width = dataset.spans[column][span]
940    texsetdimen("d_page_grd_span_width",width)
941end
942
943function columnsets.setlines(t)
944    local dataset = data[t.name]
945    dataset.lines[t.page][t.column] = t.value
946end
947
948function columnsets.setstart(t)
949    local dataset = data[t.name]
950    dataset.start[t.page][t.column] = t.value
951end
952
953function columnsets.setproperties(t)
954    local dataset = data[t.name]
955    local column  = t.column
956    dataset.distances[column] = t.distance
957    dataset.widths[column] = t.width
958end
959
960local areas = { }
961
962function columnsets.registerarea(t)
963    -- maybe metatable with values
964    areas[#areas+1] = t
965end
966
967-- state : repeat | start
968
969-- local ctx_page_grd_set_area = context.protected.page_grd_set_area
970local ctx_page_grd_set_area = context.page_grd_set_area
971
972function columnsets.flushareas(name)
973    local nofareas = #areas
974    if nofareas == 0 then
975        return
976    end
977    local dataset = data[name]
978    local page    = dataset.page
979    if odd(page) then
980     -- report("checking %i areas",#areas)
981        local kept = { }
982        for i=1,nofareas do
983            local area = areas[i]
984         -- local page = area.page -- maybe use page counter in columnset
985         -- local type = area.type
986            local okay = false
987            --
988            local nofcolumns = area.nc
989            local nofrows    = area.nr
990            local column     = area.c
991            local row        = area.r
992            columnsets.block {
993                name = name,
994                c    = column,
995                r    = row,
996                nc   = nofcolumns,
997                nr   = nofrows,
998            }
999            local left     = 0
1000            local start    = dataset.nofleft + 1
1001            local overflow = (column + nofcolumns - 1) - dataset.nofleft
1002            local height   = nofrows * (dataset.lineheight + dataset.linedepth)
1003            local width    = dataset.spreads[column][nofcolumns]
1004         -- report("span, width %p, overflow %i",width,overflow)
1005            if overflow > 0 then
1006                local used = nofcolumns - overflow
1007                left  = dataset.spreads[column][used] + texgetdimen("backspace")
1008            end
1009            ctx_page_grd_set_area(name,area.name,column,row,width,height,start,left) -- or via counters / dimens
1010            if area.state ~= v_repeat then
1011                area = nil
1012            end
1013            if area then
1014                kept[#kept+1] = area
1015            end
1016        end
1017        areas = kept
1018    end
1019end
1020
1021function columnsets.setarea(t)
1022    local dataset = data[t.name]
1023    local cells   = dataset.cells
1024    local box     = takebox(t.box)
1025    local column  = t.c
1026    local row     = t.r
1027    if column and row then
1028        setwhd(box,dataset.widths[column],dataset.lineheight,dataset.linedepth)
1029        cells[column][row] = box
1030    end
1031end
1032
1033-- The interface.
1034
1035implement {
1036    name      = "definecolumnset",
1037    actions   = columnsets.define,
1038    arguments = { {
1039        { "name", "string" },
1040    } }
1041}
1042
1043implement {
1044    name      = "resetcolumnset",
1045    actions   = columnsets.reset,
1046    arguments = { {
1047        { "name", "string" },
1048        { "nofleft", "integer" },
1049        { "nofright", "integer" },
1050        { "nofrows", "integer" },
1051        { "lineheight", "dimension" },
1052        { "linedepth", "dimension" },
1053        { "width", "dimension" },
1054        { "distance", "dimension" },
1055        { "maxwidth", "dimension" },
1056    } }
1057}
1058
1059implement {
1060    name      = "preparecolumnsetflush",
1061    actions   = columnsets.prepareflush,
1062    arguments = "string",
1063}
1064
1065implement {
1066    name      = "finishcolumnsetflush",
1067    actions   = columnsets.finishflush,
1068    arguments = "string",
1069}
1070
1071implement {
1072    name      = "flushcolumnsetcolumn",
1073    actions   = columnsets.flushcolumn,
1074    arguments = { "string" ,"integer" },
1075}
1076
1077implement {
1078    name      = "setvsizecolumnset",
1079    actions   = columnsets.setvsize,
1080    arguments = "string",
1081}
1082
1083implement {
1084    name      = "sethsizecolumnset",
1085    actions   = columnsets.sethsize,
1086    arguments = "string",
1087}
1088
1089implement {
1090    name      = "sethsizecolumnspan",
1091    actions   = columnsets.sethspan,
1092    arguments = { "string" ,"integer" },
1093}
1094
1095implement {
1096    name      = "flushcolumnsetrest",
1097    actions   = columnsets.flushrest,
1098    arguments = { "string", "integer" },
1099}
1100
1101implement {
1102    name      = "blockcolumnset",
1103    actions   = columnsets.block,
1104    arguments = { {
1105        { "name", "string" },
1106        { "c", "integer" },
1107        { "r", "integer" },
1108        { "nc", "integer" },
1109        { "nr", "integer" },
1110        { "method", "string" },
1111        { "box", "integer" },
1112    } }
1113}
1114
1115implement {
1116    name      = "checkcolumnset",
1117    actions   = columnsets.check,
1118    arguments = { {
1119        { "name", "string" },
1120        { "method", "string" },
1121        { "c", "integer" },
1122        { "r", "integer" },
1123        { "method", "string" },
1124        { "box", "integer" },
1125        { "width", "dimension" },
1126        { "height", "dimension" },
1127        { "option", "string" },
1128    } }
1129}
1130
1131implement {
1132    name      = "putincolumnset",
1133    actions   = columnsets.put,
1134    arguments = { {
1135        { "name", "string" },
1136        { "c", "integer" },
1137        { "r", "integer" },
1138        { "method", "string" },
1139        { "box", "integer" },
1140    } }
1141}
1142
1143implement {
1144    name    = "addtocolumnset",
1145    actions = columnsets.add,
1146    arguments = { "string", "integer" },
1147}
1148
1149implement {
1150    name    = "setcolumnsetlines",
1151    actions = columnsets.setlines,
1152    arguments = { {
1153        { "name", "string" },
1154        { "page", "integer" },
1155        { "column", "integer" },
1156        { "value", "integer" },
1157    } }
1158}
1159
1160implement {
1161    name    = "setcolumnsetstart",
1162    actions = columnsets.setstart,
1163    arguments = { {
1164        { "name", "string" },
1165        { "page", "integer" },
1166        { "column", "integer" },
1167        { "value", "integer" },
1168    } }
1169}
1170
1171implement {
1172    name    = "setcolumnsetproperties",
1173    actions = columnsets.setproperties,
1174    arguments = { {
1175        { "name", "string" },
1176        { "column", "integer" },
1177        { "distance", "dimension" },
1178        { "width", "dimension" },
1179    } }
1180}
1181
1182implement {
1183    name      = "registercolumnsetarea",
1184    actions   = columnsets.registerarea,
1185    arguments = { {
1186        { "name", "string" },
1187        { "type", "string" },
1188        { "page", "integer" },
1189        { "state", "string" },
1190        { "c", "integer" },
1191        { "r", "integer" },
1192        { "nc", "integer" },
1193        { "nr", "integer" },
1194    } }
1195}
1196
1197implement {
1198    name      = "flushcolumnsetareas",
1199    actions   = columnsets.flushareas,
1200    arguments = "string",
1201}
1202
1203implement {
1204    name      = "setcolumnsetarea",
1205    actions   = columnsets.setarea,
1206    arguments = { {
1207        { "name", "string" },
1208        { "c", "integer" },
1209        { "r", "integer" },
1210        { "method", "string" },
1211        { "box", "integer" },
1212    } }
1213}
1214
1215implement {
1216    name      = "columnsetgoto",
1217    actions   = columnsets["goto"],
1218    arguments = "2 strings",
1219}
1220
1221implement {
1222    name      = "columnsetnoto",
1223    actions   = columnsets["noto"],
1224}
1225
1226implement {
1227    name      = "columnsetcurrentcolumn",
1228    actions   = columnsets.currentcolumn,
1229    arguments = "string",
1230}
1231