page-mvl.lmt /size: 104 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.mkxl",
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-- In the last quarter of 2024 Mikael and I decided to update columnsets. This
10-- mechanism originates in \MKII\ and has been migrated to \MKIV\ but only
11-- partially, but given its potential it made sense to look into it. As follow
12-- up on extending the par builder we went for more advanced solutions.
13--
14-- Musical timetamp: less is lessie & amarok, boerderij end Nov 2024 etc. and
15-- the new Frost (Life in the Wires, end 2024) when doing the balancing act.
16--
17-- Here we use the mvl approach but some code still resembles the old approach
18-- so some cleanup is needed.
19
20local next, type, tonumber, tostring, rawget = next, type, tonumber, tostring, rawget
21local ceil, odd, round, abs = math.ceil, math.odd, math.round, math.abs
22local format, lower, splitstring = string.format, string.lower, string.split
23local copy, concat = table.copy, table.concat
24local stepper = utilities.parsers.stepper
25local settings_to_array = utilities.parsers.settings_to_array
26
27local trace_state   = false  trackers.register("columnsets.trace",   function(v) trace_state   = v end)
28local trace_details = false  trackers.register("columnsets.details", function(v) trace_details = v end)
29local trace_cells   = false  trackers.register("columnsets.cells",   function(v) trace_cells   = v end)
30local trace_flush   = false  trackers.register("columnsets.flush",   function(v) trace_flush   = v end)
31
32local report            = logs.reporter("column sets")
33
34local setmetatableindex = table.setmetatableindex
35
36local properties        = nodes.properties.data
37
38local nuts              = nodes.nuts
39local tonode            = nuts.tonode
40local tonut             = nuts.tonut
41
42local setlink           = nuts.setlink
43local setbox            = nuts.setbox
44local getid             = nuts.getid
45local getwhd            = nuts.getwhd
46local setwhd            = nuts.setwhd
47local getlist           = nuts.getlist
48local takebox           = nuts.takebox
49local vpack             = nuts.vpack
50local getbox            = nuts.getbox
51
52local texgetcount       = tex.getcount
53local texgetdimen       = tex.getdimen
54local texsetcount       = tex.setcount
55local texsetdimen       = tex.setdimen
56local texiscount        = tex.iscount
57local texisdimen        = tex.isdimen
58
59local nodepool          = nuts.pool
60local new_vlist         = nodepool.vlist
61local new_trace_rule    = nodepool.rule
62local new_empty_rule    = nodepool.emptyrule
63
64local context           = context
65local implement         = interfaces.implement
66
67local expandmacro       = token.expandmacro
68
69local variables         = interfaces.variables
70local v_all             <const> = variables.all
71local v_here            <const> = variables.here
72local v_fixed           <const> = variables.fixed
73local v_top             <const> = variables.top
74local v_bottom          <const> = variables.bottom
75local v_repeat          <const> = variables["repeat"]
76local v_yes             <const> = variables.yes
77local v_page            <const> = variables.page
78local v_sheet           <const> = variables.sheet
79local v_first           <const> = variables.first
80local v_last            <const> = variables.last
81----- v_left            <const> = variables.left
82----- v_right           <const> = variables.right
83----- v_next            <const> = variables.next
84----- v_wide            <const> = variables.wide
85local v_spread          <const> = variables.spread
86
87local integer_value     <const> = tokens.values.integer
88local dimension_value   <const> = tokens.values.dimension
89
90local hlist_code        <const> = nodes.nodecodes.hlist
91local vlist_code        <const> = nodes.nodecodes.vlist
92
93pagebuilders            = pagebuilders or { } -- todo: pages.builders
94local columnsets        = pagebuilders.columnsets or { }
95
96pagebuilders.columnsets = columnsets
97
98local data = { [""] = { } }
99local last = 0
100local nums = { }
101
102columnsets.data = data -- while we test
103
104local nofcolumngaps = 0
105
106local c_realpageno <const> = texiscount("realpageno")
107
108local d_bodyfontsize              <const> = texisdimen("bodyfontsize")
109local d_makeupwidth               <const> = texisdimen("makeupwidth")
110local d_globalbodyfontstrutheight <const> = texisdimen("globalbodyfontstrutheight")
111local d_globalbodyfontstrutdepth  <const> = texisdimen("globalbodyfontstrutdepth")
112local d_backspace                 <const> = texisdimen("backspace")
113
114local c_page_mvl_reserved_state      <const> = texiscount("c_page_mvl_reserved_state")
115local c_page_mvl_first_column        <const> = texiscount("c_page_mvl_first_column")
116local c_page_mvl_last_column         <const> = texiscount("c_page_mvl_last_column")
117local c_page_mvl_current_sheet       <const> = texiscount("c_page_mvl_current_sheet")
118local c_page_mvl_current_spreadsheet <const> = texiscount("c_page_mvl_current_spreadsheet")
119local c_page_mvl_max_used_cells      <const> = texiscount("c_page_mvl_max_used_cells")
120
121local d_page_mvl_reserved_height <const> = texisdimen("d_page_mvl_reserved_height")
122local d_page_mvl_reserved_width  <const> = texisdimen("d_page_mvl_reserved_width")
123local d_page_mvl_column_width    <const> = texisdimen("d_page_mvl_column_width")
124local d_page_mvl_span_width      <const> = texisdimen("d_page_mvl_span_width")
125
126-- page    [R]   [R]   [R]   [R]
127-- sheet    1     2     3     4
128--
129-- page  [- R] [L R] [L R] [L R]
130-- sheet    1   2 3   4 5   6 7
131--
132-- page  [L R] [L R] [L R] [L R]
133-- sheet  1 2   3 4   5 6   7 8
134
135-- All kind of helpers
136
137local function savedstate(dataset)
138    return {
139        count       = dataset.count,
140        sheet       = dataset.sheet,
141        spread      = dataset.spread,
142        firstcolumn = dataset.firstcolumn,
143        lastcolumn  = dataset.lastcolumn,
144        state       = dataset.state,
145    }
146end
147
148local function restorestate(dataset,saved)
149    dataset.count       = saved.count
150    dataset.sheet       = saved.sheet
151    dataset.spread      = saved.spread
152    dataset.firstcolumn = saved.firstcolumn
153    dataset.lastcolumn  = saved.lastcolumn
154    dataset.state       = saved.state
155end
156
157local function setstate(dataset,flushing)
158    local realpage = texgetcount(c_realpageno)
159    if flushing then
160        dataset.count = dataset.count + 1
161        dataset.sheet = dataset.sheet + 1
162        if odd(dataset.count) then
163            local spread = dataset.spread
164            dataset.spread = spread + 1
165        end
166        if odd(realpage) then
167            dataset.state = "right"
168-- dataset.count   = 2
169        else
170            dataset.state = "left"
171-- dataset.count   = 1
172        end
173    else
174        dataset.spread  = 1
175        dataset.sheet   = 1
176        if odd(realpage) then
177            dataset.initial = "right"
178            dataset.count   = 2
179        else
180            dataset.initial = "left"
181            dataset.count   = 1
182        end
183        dataset.state = dataset.initial
184    end
185    if odd(dataset.count) then
186        dataset.firstcolumn = 1
187        dataset.lastcolumn  = dataset.nofleft
188    else
189        dataset.firstcolumn = dataset.nofleft + 1
190        dataset.lastcolumn  = dataset.firstcolumn + dataset.nofright - 1
191        dataset.state       = "right"
192    end
193    dataset.currentcolumn = dataset.firstcolumn
194    dataset.currentrow    = 1
195end
196
197local function block1(list,cells,offset)
198    for c, n in next, list do
199        local column = cells[offset+c]
200        if column then
201            for r=1,n do
202                if not column[r] then
203                    column[r] = true
204                end
205            end
206        end
207    end
208end
209
210local function block2(list,cells,offset,rows)
211    for c, n in next, list do
212        local column = cells[offset+c]
213        if column then
214            if n > 0 then
215                for r=n+1,rows do
216                    if not column[r] then
217                        column[r] = true
218                    end
219                end
220            elseif n < 0 then
221                for r=rows,rows+n+1,-1 do
222                    if not column[r] then
223                        column[r] = true
224                    end
225                end
226            end
227        end
228    end
229end
230
231local function check(dataset,byspread)
232    local cells = dataset.cells
233    local sheet = dataset.sheet
234    local start = dataset.start
235    local lines = dataset.lines
236    local count = dataset.count
237    local rows  = dataset.nofrows
238    --
239    if byspread or not odd(count) then
240        local list = rawget(start,count)
241        if list then
242            block1(list,cells,dataset.nofleft)
243            start[count] = nil
244        end
245    else
246        local list = rawget(start,count)
247        if list then
248            block1(list,cells,0)
249        end
250        local list = rawget(start,count+1)
251        if list then
252            block1(list,cells,dataset.nofleft)
253        end
254    end
255    --
256    if byspread or not odd(count) then
257        local list = rawget(lines,count)
258        if list then
259            block2(list,cells,dataset.nofleft,rows)
260        end
261    else
262        local list = rawget(lines,count)
263        if list then
264            block2(list,cells,0,rows)
265        end
266        local list = rawget(lines,count+1)
267        if list then
268            block2(list,cells,dataset.nofleft,rows)
269        end
270    end
271    --     print(count,sheet)
272    --     inspect(lines)
273    --     inspect(rawget(lines,count))
274    --     inspect(cells)
275end
276
277local function getlocations(dataset,location)
278    local locations = dataset.locations
279    if not locations then
280        local split = settings_to_array(location)
281        locations   = { }
282        for i=1,#split do
283            local t = splitstring(split[i],",")
284            local c = tonumber(t[1])
285            local r = tonumber(t[2])
286            local l = tonumber(t[3])
287            if c and r then
288                -- also check validy here
289                locations[#locations+1] = { c, r, l }
290            end
291        end
292        dataset.locations = locations
293    end
294    if not dataset.putdirect then
295        -- Quick hack: do we have another status variable?
296        local cells    = dataset.cells
297        local noflines = #cells[1]
298        for c=1,dataset.currentcolumn - 1 do
299            local f = cells[c]
300            for l=1,noflines do
301                if not f[l] then
302                    f[l] = true
303                end
304            end
305        end
306        dataset.putdirect = true
307    end
308    return locations
309end
310
311local function updatedistances(dataset)
312    dataset.distances = dataset.distances or setmetatableindex(function(t,k)
313        return dataset.distance -- use rawget to check for setting
314    end)
315end
316
317local function updatewidths(dataset)
318    dataset.widths = dataset.widths or setmetatableindex(function(t,k)
319        return dataset.width -- use rawget to check for setting
320    end)
321end
322
323local function updatespans(dataset)
324    local widths    = dataset.widths
325    local distances = dataset.distances
326    local nofleft   = dataset.nofleft
327    local nofright  = dataset.nofright
328    local spans     = { }
329    local function calculate(first,last)
330        for i=first,last do
331            local s = { }
332            local d = 0
333            for j=i,last do
334                d = d + widths[j]
335                s[#s+1] = round(d)
336                d = d + distances[j]
337            end
338            spans[i] = s
339        end
340    end
341    calculate(1,nofleft)
342    calculate(nofleft+1,nofleft+nofright)
343    dataset.spans = spans
344end
345
346local function updatespreads(dataset)
347    local nofleft   = dataset.nofleft
348    local nofright  = dataset.nofright
349    local spreads   = copy(dataset.spans)
350    dataset.spreads = spreads
351    local gap       = 2 * texgetdimen(d_backspace)
352    for l=1,nofleft do
353        local s = spreads[l]
354        local n = #s
355        local o = s[n] + gap
356        for r=1,nofright do
357            n = n + 1
358            s[n] = s[r] + o
359        end
360    end
361end
362
363local function emptycells(dataset)
364    local cells = { }
365    for c=1,dataset.nofleft + dataset.nofright do
366        cells[c] = { }
367        for r=1,dataset.nofrows do
368            cells[c][r] = false
369        end
370    end
371    return cells
372end
373
374local function futurecells(dataset)
375    local future = dataset.future
376    local spread = dataset.spread
377    local cells  = dataset.future[spread]
378    if not cells then
379        cells = emptycells(dataset)
380        future[spread] = cells
381    end
382    return cells
383end
384
385local function setfirstcells(dataset)
386    local cells = dataset.future[1] or emptycells(dataset)
387    dataset.cells     = cells
388    dataset.future[1] = cells
389end
390
391local function findgap(dataset,everything)
392    local cells         = dataset.cells
393    local mvls          = dataset.mvls
394    local nofcolumns    = dataset.nofcolumns
395    local nofrows       = dataset.nofrows
396    local currentrow    = dataset.currentrow
397    local currentcolumn = dataset.currentcolumn
398    local currentmvl    = dataset.currentmvl or 1
399    local disabled      = dataset.disabled
400    --
401    local foundc     = 0
402    local foundr     = 0
403    local foundn     = 0
404    for c=currentcolumn,everything and dataset.nofcolumns or dataset.lastcolumn do
405        if (mvls[c] == currentmvl or currentmvl == 1) and not disabled[c] then
406            local column = cells[c]
407            foundn = 0
408            for r=currentrow,nofrows do
409                if not column[r] then
410                    if foundc == 0 then
411                        foundc = c
412                        foundr = r
413                        foundn = 0
414                    end
415                    foundn = foundn + 1
416                elseif foundn > 0 then
417                    return foundc, foundr, foundn
418                end
419            end
420            if foundn > 0 then
421                return foundc, foundr, foundn
422            else
423                currentrow = 1
424            end
425        else
426            currentrow = 1
427        end
428    end
429end
430
431-- Housekeeping:
432
433do
434
435    local function checkwidth(dataset,width,where)
436        local widths    = dataset.widths
437        local distances = dataset.distances
438        --
439        if not width then
440            width = 0
441        end
442        --
443-- print(width,dataset.autowidth)
444        if width == 0 or dataset.autowidth then
445            local nofleft  = dataset.nofleft
446            local nofright = dataset.nofright
447            local name     = dataset.name
448            local dl = 0
449            local dr = 0
450-- report("setting %s autowidth in %a",name,where)
451            for i=1,nofleft-1 do
452                dl = dl + distances[i]
453            end
454            for i=1,nofright-1 do
455                dr = dr + distances[nofleft+i] -- rawget
456            end
457            local nl = nofleft
458            local nr = nofright
459            local wl = dataset.maxwidth
460            local wr = wl
461            for i=1,nofleft do
462                local w = rawget(widths,i)
463                if w then
464                    nl = nl - 1
465                    wl = wl - w
466                end
467            end
468            for i=1,nofright do
469                local w = rawget(widths,nofleft+i)
470                if w then
471                    nr = nr - 1
472                    wr = wr - w
473                end
474            end
475-- print(wl,wr,nl,nr)
476            if nl > 0 then
477                dl = (wl - dl) / nl
478            end
479            if nr > 0 then
480                dr = (wr - dr) / nr
481            end
482-- print(dl,dr)
483            if dl > dr then
484                report("using %s page column width %p in columnset %a","right",dr,name)
485                width = dr
486            elseif dl < dr then
487                report("using %s page column width %p in columnset %a","left",dl,name)
488                width = dl
489            else
490                width = dl
491            end
492        else
493        end
494-- inspect(widths)
495        width = round(width)
496        dataset.width = width
497-- print(width,width/65536)
498        return width
499    end
500
501    function columnsets.define(t,resetting)
502        local name            = t.name
503        local nofleft         = t.nofleft or 1
504        local nofright        = t.nofright or 1
505        local nofcolumns      = nofleft + nofright
506        local doublesided     = false
507        local dataset         = data[name] or { }
508        --
509        data[name]            = dataset
510        if not nums[name] then
511            last = last + 1
512            nums[name] = last
513            nums[last] = name
514        end
515        --
516        dataset.doublesided     = doublesided
517        dataset.name            = name
518        dataset.identifier      = nums[name]
519        dataset.nofleft         = nofleft
520        dataset.nofright        = nofright
521        dataset.nofcolumns      = nofcolumns
522        dataset.nofrows         = t.nofrows    or 1
523        dataset.distance        = t.distance   or texgetdimen(d_bodyfontsize)
524        dataset.maxwidth        = t.maxwidth   or texgetdimen(d_makeupwidth)
525        dataset.lineheight      = t.lineheight or texgetdimen(d_globalbodyfontstrutheight)
526        dataset.linedepth       = t.linedepth  or texgetdimen(d_globalbodyfontstrutdepth)
527        dataset.quit            = 0
528        dataset.limit           = t.limit or dataset.limit
529        --
530        dataset.cells           = { }
531        dataset.currentcolumn   = 1
532        dataset.currentrow      = 1
533        dataset.currentmvl      = 2
534        --
535        dataset.future          = { }
536        --
537        dataset.lines           = dataset.lines or setmetatableindex("table")
538        dataset.start           = dataset.start or setmetatableindex("table")
539        dataset.mvls            = dataset.mvls  or setmetatableindex(function(t,k) t[k] = 1 return 1 end)
540        dataset.mvlc            = dataset.mvlc  or { }
541        dataset.disabled        = dataset.disabled or { }
542        --
543        dataset.count           = 1
544        dataset.sheet           = 1
545        dataset.spread          = 1
546        --
547        dataset.sheets          = dataset.sheets          or setmetatableindex("table")
548        dataset.delayed         = dataset.delayed         or setmetatableindex("table")
549        dataset.spreadsheets    = dataset.spreadsheets    or setmetatableindex("table")
550        dataset.nofsheets       = dataset.nofsheets       or 0
551        dataset.nofdelayed      = dataset.nofdelayed      or 0
552        dataset.nofspreadsheets = dataset.nofspreadsheets or 0
553        dataset.lastmvl         = dataset.lastmvl         or 1
554        --
555        updatedistances(dataset)
556        updatewidths(dataset)
557        dataset.autowidth     = not t.width or t.width == 0
558        dataset.width         = checkwidth(dataset,t.width,"define")
559        updatespans(dataset)
560        updatespreads(dataset)
561        --
562        texsetdimen(d_page_mvl_column_width,dataset.width) -- main width
563        --
564        setstate(dataset)
565        --
566        return dataset
567    end
568
569    function columnsets.reset(t)
570        local dataset = columnsets.define(t)
571        if dataset then
572            setfirstcells(dataset)
573            check(dataset)
574            checkwidth(dataset,t.width,"reset")
575        end
576    end
577
578    function columnsets.clean(name)
579        local dataset = data[name]
580        if dataset then
581            dataset.sheets     = setmetatableindex("table")
582            dataset.nofsheets  = 0
583            dataset.delayed    = setmetatableindex("table")
584            dataset.nofdelayed = 0
585        end
586    end
587
588    implement {
589        name      = "definecolumnset",
590        actions   = columnsets.define,
591        arguments = { {
592            { "name",   "string" },
593            { "method", "string" },
594            { "nofleft", "integer" },
595            { "nofright", "integer" },
596            { "nofrows", "integer" },
597        } }
598    }
599
600    implement {
601        name      = "resetcolumnset",
602        actions   = columnsets.reset,
603        arguments = { {
604            { "name", "string" },
605            { "nofleft", "integer" },
606            { "nofright", "integer" },
607            { "nofrows", "integer" },
608            { "lineheight", "dimension" },
609            { "linedepth", "dimension" },
610            { "width", "dimension" },
611            { "distance", "dimension" },
612            { "maxwidth", "dimension" },
613            { "limit", "integer" },
614        } }
615    }
616
617    implement {
618        name      = "cleancolumnset",
619        actions   = columnsets.clean,
620        arguments = "argument",
621    }
622
623    function columnsets.setlines(t)
624        local dataset = data[t.name]
625        if dataset then
626            dataset.lines[t.sheet][t.column] = t.value
627            if t.sheet > dataset.nofsheets then
628                dataset.nofsheets = t.sheet
629            end
630        end
631    end
632
633    function columnsets.setstart(t)
634        local dataset = data[t.name]
635        if dataset then
636            dataset.start[t.sheet][t.column] = t.value
637            if t.sheet > dataset.nofsheets then
638                dataset.nofsheets = t.sheet
639            end
640        end
641    end
642
643    function columnsets.setproperties(t)
644        local dataset = data[t.name]
645        if dataset then
646            local column = t.column
647            dataset.distances[column] = t.distance
648            dataset.widths[column] = t.width
649        end
650    end
651
652    implement {
653        name      = "setcolumnsetlines",
654        actions   = columnsets.setlines,
655        arguments = { {
656            { "name", "string" },
657            { "sheet", "integer" },
658            { "column", "integer" },
659            { "value", "integer" },
660        } }
661    }
662
663    implement {
664        name      = "setcolumnsetstart",
665        actions   = columnsets.setstart,
666        arguments = { {
667            { "name", "string" },
668            { "sheet", "integer" },
669            { "column", "integer" },
670            { "value", "integer" },
671        } }
672    }
673
674    implement {
675        name    = "setcolumnsetproperties",
676        actions = columnsets.setproperties,
677        arguments = { {
678            { "name", "string" },
679            { "column", "integer" },
680            { "distance", "dimension" },
681            { "width", "dimension" },
682        } }
683    }
684
685end
686
687-- Flushing and marks:
688
689do
690
691    local setmacrofrommark = token.setmacrofrommark
692    local getusedmarks     = tex.getusedmarks
693
694    local updatetopmarks   = nuts.updatetopmarks
695    local updatemarks      = nuts.updatemarks
696    local updatefirstmarks = nuts.updatefirstmarks
697
698    function columnsets.prepareflush(name)
699        local dataset     = data[name]
700        local cells       = dataset.cells
701        local firstcolumn = dataset.firstcolumn
702        local lastcolumn  = dataset.lastcolumn
703        local nofrows     = dataset.nofrows
704        local lineheight  = dataset.lineheight
705        local linedepth   = dataset.linedepth
706        local widths      = dataset.widths
707        local height      = (lineheight+linedepth)*nofrows -- - linedepth
708        local count       = dataset.count
709        --
710--         if count == 1 and dataset.initial == "right" then
711--             firstcolumn = dataset.nofleft + 1
712--         else
713--             firstcolumn = 1
714--         end
715        --
716        if trace_flush then
717            report(
718                "flushing: spread %i, count %i, sheet %i, first %i, last %i",
719                dataset.spread,dataset.count,dataset.sheet,firstcolumn,lastcolumn
720            )
721        end
722        --
723        local columns   = { }
724        dataset.columns = columns
725        --
726        local used = 0
727        --
728        updatetopmarks()
729        for c=firstcolumn,lastcolumn do
730            local column = cells[c]
731            for r=1,nofrows do
732                local cell = column[r]
733                if (cell == false) or (cell == true) then
734                    if trace_cells then
735                        column[r] = new_trace_rule(65536*2,lineheight,linedepth)
736                    else
737                        column[r] = new_empty_rule(0,lineheight,linedepth)
738                    end
739                else
740                    -- If needed we can handle per column marks here, either by return
741                    -- value or by additional primitives but then only for first and
742                    -- last columns.
743                    local list = getlist(cell)
744                    while list do
745                        if getid(list) == vlist_code then
746                            updatemarks(list)
747                        end
748                        list = getlist(list)
749                    end
750                    used = r
751                end
752            end
753            for r=1,nofrows-1 do
754                setlink(column[r],column[r+1])
755            end
756            columns[c] = new_vlist(column[1],widths[c],height,0) -- linedepth
757        end
758        updatefirstmarks()
759        --
760        texsetcount("global",c_page_mvl_max_used_cells,used)
761        --
762        if odd(dataset.count) then
763            local spread = dataset.spread
764            dataset.lines [spread] = nil -- ??
765            dataset.start [spread] = nil -- ??
766            -- in mvl mode we can collect the shape already and then wipe the next:
767         -- dataset.future[spread] = nil
768        end
769        --
770        texsetcount(c_page_mvl_first_column,firstcolumn)
771        texsetcount(c_page_mvl_last_column,lastcolumn)
772    end
773
774    function columnsets.flushcolumn(name,column)
775        local dataset = data[name]
776        local columns = dataset.columns
777        local packed  = columns[column]
778        setbox("b_page_mvl_column",packed)
779    end
780
781    function columnsets.finishflush(name)
782        local dataset = data[name]
783        setstate(dataset,true)
784        dataset.cells = dataset.future[dataset.spread] or emptycells(dataset)
785    end
786
787    implement {
788        name      = "preparecolumnsetflush",
789        actions   = columnsets.prepareflush,
790        arguments = "argument",
791    }
792
793    implement {
794        name      = "flushcolumnsetcolumn",
795        actions   = columnsets.flushcolumn,
796        arguments = { "argument" ,"integer" },
797    }
798
799    implement {
800        name      = "finishcolumnsetflush",
801        actions   = columnsets.finishflush,
802        arguments = "argument",
803    }
804
805end
806
807-- Positioning:
808
809do
810
811    function columnsets.block(t)
812        local dataset    = data[t.name]
813        local cells      = dataset.cells
814        local nofcolumns = dataset.nofcolumns
815        local nofrows    = dataset.nofrows
816        --
817        local c = t.c or 0
818        local r = t.r or 0
819        if c == 0 or r == 0 or c > nofcolumns or r > nofrows then
820            return
821        end
822        local nc = t.nc or 0
823        local nr = t.nr or 0
824        if nc == 0 then
825            return
826        end
827        if nr == 0 then
828            return
829        end
830        local rr = r + nr - 1
831        local cc = c + nc - 1
832        if rr > nofrows then
833            rr = nofrows
834        end
835        if cc > nofcolumns then
836            cc = nofcolumns
837        end
838        for i=c,cc do
839            local column = cells[i]
840            for j=r,rr do
841                column[j] = true
842            end
843        end
844    end
845
846    local function here(c,r,nr,nofcolumns,nofrows,cells,width,spans)
847        if c < 1 then
848            c = 1
849        end
850        if r < 1 then
851            r = 1
852        end
853        local rr = r + nr - 1
854        if rr > nofrows then
855         -- report("%i rows needed, %i rows available, no slots free at (%i,%i), discarding",rr,nofrows,c,r)
856            return false
857        end
858        local cc = 0
859        local wd = spans[c]
860        local wc = 0
861        local nc = 0
862        for i=c,nofcolumns do
863            nc = nc + 1
864            wc = wd[nc]
865            if not wc then
866                break
867            elseif wc >= width then
868                cc = i
869                break
870            end
871        end
872        if cc == 0 or cc > nofcolumns then
873         -- report("needed %p, no slot free at (%i,%i), discarding",width,c,r)
874            return false
875        end
876        for i=c,cc do
877            local column = cells[i]
878            if column then
879                for j=r,rr do
880                    if column[j] then
881                      -- report("width %p, needed %p, checking (%i,%i) x (%i,%i), %s",width,wc,c,r,nc,nr,"quit")
882                         return false
883                    end
884                end
885            end
886        end
887     -- report("width %p, needed %p, checking (%i,%i) x (%i,%i), %s",width,wc,c,r,nc,nr,"match")
888        return c, r, nc
889    end
890
891    -- we use c/r as range limiters
892
893    local methods = {
894        [v_here] = here,
895        [v_fixed] = here,
896        tblr = function(c,r,nr,nofcolumns,nofrows,cells,width,spans)
897            for j=r,nofrows-nr+1 do
898                for i=c,nofcolumns do
899                    if not cells[i][j] then
900                        local c, r, cc = here(i,j,nr,nofcolumns,nofrows,cells,width,spans)
901                        if c then
902                            return c, r, cc
903                        end
904                    end
905                end
906            end
907        end,
908        lrtb = function(c,r,nr,nofcolumns,nofrows,cells,width,spans)
909            for i=c,nofcolumns do
910                for j=r,nofrows-nr+1 do
911                    if not cells[i][j] then
912                        local c, r, cc = here(i,j,nr,nofcolumns,nofrows,cells,width,spans)
913                        if c then
914                            return c, r, cc
915                        end
916                    end
917                end
918            end
919        end,
920        tbrl = function(c,r,nr,nofcolumns,nofrows,cells,width,spans)
921            for j=r,nofrows-nr+1 do
922                for i=nofcolumns,c,-1 do
923                    if not cells[i][j] then
924                        local c, r, cc = here(i,j,nr,nofcolumns,nofrows,cells,width,spans)
925                        if c then
926                            return c, r, cc
927                        end
928                    end
929                end
930            end
931        end,
932        rltb = function(c,r,nr,nofcolumns,nofrows,cells,width,spans)
933            for i=nofcolumns,c,-1 do
934                for j=r,nofrows-nr+1 do
935                    if not cells[i][j] then
936                        local c, r, cc = here(i,j,nr,nofcolumns,nofrows,cells,width,spans)
937                        if c then
938                            return c, r, cc
939                        end
940                    end
941                end
942            end
943        end,
944        btlr = function(c,r,nr,nofcolumns,nofrows,cells,width,spans)
945         -- for j=nofrows-nr+1,1,-1 do
946            for j=nofrows-nr+1-r+1,1,-1 do
947                for i=c,nofcolumns do
948                    if not cells[i][j] then
949                        local c, r, cc = here(i,j,nr,nofcolumns,nofrows,cells,width,spans)
950                        if c then
951                            return c, r, cc
952                        end
953                    end
954                end
955            end
956        end,
957        lrbt = function(c,r,nr,nofcolumns,nofrows,cells,width,spans)
958            for i=c,nofcolumns do
959             -- for j=nofrows-nr+1,1,-1 do
960                for j=nofrows-nr+1-r+1,1,-1 do
961                    if not cells[i][j] then
962                        local c, r, cc = here(i,j,nr,nofcolumns,nofrows,cells,width,spans)
963                        if c then
964                            return c, r, cc
965                        end
966                    end
967                end
968            end
969        end,
970        btrl = function(c,r,nr,nofcolumns,nofrows,cells,width,spans)
971         -- for j=nofrows-nr+1,1,-1 do
972            for j=nofrows-nr+1-r+1,1,-1 do
973                for i=nofcolumns,c,-1 do
974                    if not cells[i][j] then
975                        local c, r, cc = here(i,j,nr,nofcolumns,nofrows,cells,width,spans)
976                        if c then
977                            return c, r, cc
978                        end
979                    end
980                end
981            end
982        end,
983        rlbt = function(c,r,nr,nofcolumns,nofrows,cells,width,spans)
984            for i=nofcolumns,c,-1 do
985             -- for j=nofrows-nr+1,1,-1 do
986                for j=nofrows-nr+1-r+1,1,-1 do
987                    if not cells[i][j] then
988                        local c, r, cc = here(i,j,nr,nofcolumns,nofrows,cells,width,spans)
989                        if c then
990                            return c, r, cc
991                        end
992                    end
993                end
994            end
995        end,
996        fxtb = function(c,r,nr,nofcolumns,nofrows,cells,width,spans)
997            for i=c,nofcolumns do
998                for j=r,nofrows-nr+1 do
999                    if not cells[i][j] then
1000                        local c, r, cc = here(i,j,nr,nofcolumns,nofrows,cells,width,spans)
1001                        if c then
1002                            return c, r, cc
1003                        end
1004                    end
1005                    r = 1
1006                end
1007            end
1008        end,
1009        fxbt = function(c,r,nr,nofcolumns,nofrows,cells,width,spans)
1010            for i=c,nofcolumns do
1011                for j=nofrows-nr+1,r,-1 do
1012                    if not cells[i][j] then
1013                        local c, r, cc = here(i,j,nr,nofcolumns,nofrows,cells,width,spans)
1014                        if c then
1015                            return c, r, cc
1016                        end
1017                    end
1018                end
1019                r = 1
1020            end
1021        end,
1022        [v_top] = function(c,r,nr,nofcolumns,nofrows,cells,width,spans)
1023            for i=c,nofcolumns do
1024                for j=1,nofrows-nr+1 do
1025                    if cells[i][j] then
1026                        break
1027                    else
1028                        local c, r, cc = here(i,j,nr,nofcolumns,nofrows,cells,width,spans)
1029                        if c then
1030                            return c, r, cc
1031                        end
1032                    end
1033                end
1034            end
1035        end,
1036        [v_bottom] = function(c,r,nr,nofcolumns,nofrows,cells,width,spans)
1037            for i=c,nofcolumns do
1038                for j=1,nofrows-nr+1 do
1039                    if cells[i][j] then
1040                        break
1041                    else
1042                        local c, r, cc = here(i,j,nr,nofcolumns,nofrows,cells,width,spans)
1043                        if c then
1044                            return c, r, cc
1045                        end
1046                    end
1047                end
1048            end
1049        end,
1050    }
1051
1052    local threshold = 50
1053
1054    function columnsets.check(t)
1055        local dataset    = data[t.name]
1056        local cells      = dataset.cells
1057        local nofcolumns = dataset.nofcolumns
1058        local nofrows    = dataset.nofrows
1059        local widths     = dataset.widths
1060        local lineheight = dataset.lineheight
1061        local linedepth  = dataset.linedepth
1062        local distances  = dataset.distances
1063        local spans      = dataset.spans
1064        --
1065        local method     = lower(t.method or "tblr")
1066        local boxwidth   = t.width  or 0
1067        local boxheight  = t.height or 0
1068        local boxnumber  = t.box
1069        local box        = boxnumber and getbox(boxnumber)
1070        --
1071local ntop    = t.ntop    or 0
1072local nbottom = t.nbottom or 0
1073        --
1074        if boxwidth > 0 and boxheight > 0 then
1075            -- we're ok
1076        elseif box then
1077            local wd, ht, dp = getwhd(box)
1078            boxwidth  = wd
1079            boxheight = ht + dp
1080        else
1081            report("empty box")
1082            return
1083        end
1084        --
1085        boxwidth  = boxwidth  - 100 -- needs testing
1086        boxheight = boxheight - 100 -- needs testing
1087        --
1088        local mm, cc, rr = string.match(method,"^(....):(%d*)%*(%d*)$")
1089        if mm and (cc or rr) then
1090            method = mm
1091            t.c = tonumber(cc) or t.c
1092            t.r = tonumber(rr) or t.r
1093        end
1094        --
1095    -- dataset.compensate = true
1096    -- if t.c and dataset.compensate and dataset.initial == "right" and odd(texgetcount(c_realpageno)) then
1097    --     local tc = t.c + dataset.nofleft
1098    --     report("compensate column %i to %i",t.c,tc)
1099    --     t.c = tc
1100    -- end
1101        --
1102        local c = t.c or 0
1103        local r = t.r or 0
1104        if c == 0 then
1105            c = dataset.currentcolumn
1106        end
1107        if r == 0 then
1108            r = dataset.currentrow
1109        end
1110        if c == 0 or r == 0 or c > nofcolumns or r > nofrows then
1111            texsetcount(c_page_mvl_reserved_state,5)
1112            return
1113        end
1114     -- report("checking width %p, height %p, depth %p, slot (%i,%i)",boxwidth,boxheight,boxdepth,c,r)
1115        local nr = ceil(boxheight/(lineheight+linedepth))
1116        --
1117        local action = methods[method]
1118        local cfound = false
1119        local rfound = false
1120        local lastcolumn = dataset.lastcolumn
1121
1122        if dataset.byspread then
1123            lastcolumn = dataset.nofleft + dataset.nofright
1124        elseif odd(dataset.count) then
1125            c = 1
1126            lastcolumn = dataset.nofleft
1127        else
1128            c = dataset.nofleft + 1
1129            lastcolumn = dataset.nofleft + dataset.nofright
1130        end
1131
1132        if t.c and t.c >= 1 and t.c <= lastcolumn then
1133            c = t.c
1134        end
1135        if t.r and t.r >= 1 and t.r <= nofrows then
1136            r = t.r
1137        end
1138        local w = boxwidth - threshold
1139        if action then
1140         -- report("action %s, c %i, r %i, nr %i, width %p",method,c,r,nr,w)
1141            cfound, rfound, nc = action(c,r,nr,lastcolumn,nofrows,cells,w,spans)
1142        end
1143        if not cfound and method ~= v_here then
1144         -- report("action %s, c %i, r %i, nr %i, width %p","here",c,r,nr,w)
1145            cfound, rfound, nc = here(c,r,nr,lastcolumn,nofrows,cells,w,spans)
1146        end
1147if cfound then
1148    if rfound == 1 then
1149        ntop = 0
1150    end
1151    if rfound + nr - 1 == dataset.nofrows then
1152        nbottom = 0
1153    end
1154    if (rfound - ntop) < 0 then
1155        cfound = false
1156    elseif (rfound + nr + nbottom - 1) > dataset.nofrows then
1157        cfound = false
1158    end
1159end
1160        if cfound then
1161            local ht = nr*(lineheight+linedepth)
1162            local wd = spans[cfound][nc]
1163            dataset.reserved_ht      = ht
1164            dataset.reserved_wd      = wd
1165            dataset.reserved_c       = cfound
1166            dataset.reserved_r       = rfound
1167            dataset.reserved_nc      = nc
1168            dataset.reserved_nr      = nr
1169            dataset.reserved_ntop    = ntop
1170            dataset.reserved_nbottom = nbottom
1171            texsetcount(c_page_mvl_reserved_state,0)
1172            texsetdimen(d_page_mvl_reserved_height,ht)
1173            texsetdimen(d_page_mvl_reserved_width,wd)
1174         -- report("using (%i,%i) x (%i,%i) @ (%p,%p)",cfound,rfound,nc,nr,wd,ht)
1175        else
1176            dataset.reserved_ht      = false
1177            dataset.reserved_wd      = false
1178            dataset.reserved_c       = false
1179            dataset.reserved_r       = false
1180            dataset.reserved_nc      = false
1181            dataset.reserved_nr      = false
1182            dataset.reserved_ntop    = false
1183            dataset.reserved_nbottom = false
1184            texsetcount(c_page_mvl_reserved_state,4)
1185         -- texsetdimen(d_page_mvl_reserved_height,0)
1186         -- texsetdimen(d_page_mvl_reserved_width,0)
1187         -- report("no slot found")
1188        end
1189    end
1190
1191    function columnsets.put(t)
1192        local dataset    = data[t.name]
1193        local cells      = dataset.cells
1194        local widths     = dataset.widths
1195        local lineheight = dataset.lineheight
1196        local linedepth  = dataset.linedepth
1197        local boxnumber  = t.box
1198        local box        = boxnumber and takebox(boxnumber)
1199        --
1200        local c = t.c or dataset.reserved_c
1201        local r = t.r or dataset.reserved_r
1202        if not c or not r then
1203         -- report("no reserved slot (%i,%i)",c,r)
1204            return
1205        end
1206        local lastc   = c + dataset.reserved_nc - 1
1207        local lastr   = r + dataset.reserved_nr - 1
1208        local ntop    = dataset.reserved_ntop    or 0
1209        local nbottom = dataset.reserved_nbottom or 0
1210        --
1211        for i=c,lastc do
1212            local column = cells[i]
1213            for j=r-ntop,lastr+nbottom do
1214                column[j] = true
1215            end
1216        end
1217        cells[c][r] = box
1218        setwhd(box,widths[c],lineheight,linedepth)
1219        dataset.reserved_c       = false
1220        dataset.reserved_r       = false
1221        dataset.reserved_nc      = false
1222        dataset.reserved_nr      = false
1223        dataset.reserved_ntop    = false
1224        dataset.reserved_nbottom = false
1225        --
1226    end
1227
1228    function columnsets.resetdirect(name)
1229        local dataset = data[name]
1230        if dataset then
1231            dataset.locations = nil
1232            dataset.putdirect = nil
1233        end
1234    end
1235
1236    function columnsets.putdirect(t)
1237        local boxnumber = t.box
1238        local box       = boxnumber and takebox(boxnumber)
1239        if not box then
1240            return
1241        end
1242        --
1243        local location = t.location
1244        local lines    = t.lines
1245        local span     = 1
1246        if not location or not lines then
1247            return
1248        end
1249        --
1250        local dataset   = data[t.name]
1251        local locations = getlocations(dataset,location)
1252        if #locations == 0 then
1253            return
1254        end
1255        --
1256        local cells  = dataset.cells
1257        local widths = dataset.widths
1258        local future = dataset.future
1259        --
1260        local c, r, l, s = 0, 0, 0, 0
1261        for i=1,#future do
1262            cells = future[i]
1263            for j=1,#locations do
1264                local v = locations[j]
1265                c, r, l = v[1], v[2], v[3]
1266                if not cells[c][r] then
1267                    s = i
1268                    goto FOUND
1269                end
1270            end
1271        end
1272        if cells[c][r] then
1273            cells = emptycells(dataset)
1274            future[#future+1] = cells
1275            for j=1,#locations do
1276                local v = locations[j]
1277                c, r, l = v[1], v[2], v[3]
1278                if cells[c][r] then
1279                    -- can't happen
1280                else
1281                    s = #future
1282                    goto FOUND
1283                end
1284            end
1285        end
1286        --
1287      ::FOUND::
1288        if s == 0 then
1289            return
1290        end
1291        local wd, ht, dp = getwhd(box)
1292        --
1293        local lastc = c + span  - 1
1294        local lastr = r + lines - 1
1295        --
1296        for i=c,lastc do
1297            local column = cells[i]
1298            for j=r,lastr do
1299                column[j] = true
1300            end
1301        end
1302        cells[c][r] = box
1303        -- really set the ht.dp here?
1304        setwhd(box,widths[c],dataset.lineheight,dataset.linedepth)
1305    end
1306
1307    -- we can enforce grid snapping
1308
1309    function columnsets.add(name,box)
1310        local dataset       = data[name]
1311        local cells         = dataset.cells
1312        local nofcolumns    = dataset.nofcolumns
1313        local nofrows       = dataset.nofrows
1314        local currentrow    = dataset.currentrow
1315        local currentcolumn = dataset.currentcolumn
1316        local currentcells  = dataset.currentcells
1317        local lineheight    = dataset.lineheight
1318        local linedepth     = dataset.linedepth
1319        local widths        = dataset.widths
1320        --
1321        local b = takebox(box)
1322        nofcolumngaps = nofcolumngaps + 1
1323        -- getmetatable(v).columngap = nofcolumngaps
1324        properties[b] = { columngap = nofcolumngaps }
1325     -- report("setting gap %a at (%i,%i)",nofcolumngaps,foundc,foundr)
1326        setwhd(b,widths[currentcolumn],lineheight,linedepth)
1327        local column = cells[currentcolumn]
1328        column[currentrow] = b or true
1329        for i=currentrow+1,currentrow+currentcells-1 do
1330            if not column[i] then
1331                column[i] = true
1332            end
1333        end
1334     -- dataset.currentcolumn = currentcolumn
1335        dataset.currentrow = currentrow + currentcells
1336    end
1337
1338    implement {
1339        name      = "blockcolumnset",
1340        actions   = columnsets.block,
1341        arguments = { {
1342            { "name", "string" },
1343            { "c", "integer" },
1344            { "r", "integer" },
1345            { "nc", "integer" },
1346            { "nr", "integer" },
1347            { "method", "string" },
1348            { "box", "integer" },
1349        } }
1350    }
1351
1352    implement {
1353        name      = "checkcolumnset",
1354        actions   = columnsets.check,
1355        arguments = { {
1356            { "name", "string" },
1357            { "method", "string" },
1358            { "c", "integer" },
1359            { "r", "integer" },
1360            { "ntop", "integer" },
1361            { "nbottom", "integer" },
1362            { "method", "string" },
1363            { "box", "integer" },
1364            { "width", "dimension" },
1365            { "height", "dimension" },
1366            { "option", "string" },
1367        } }
1368    }
1369
1370    implement {
1371        name      = "putincolumnset",
1372        actions   = columnsets.put,
1373        arguments = { {
1374            { "name", "string" },
1375            { "c", "integer" },
1376            { "r", "integer" },
1377            { "method", "string" },
1378            { "box", "integer" },
1379        } }
1380    }
1381
1382    implement {
1383        name      = "columnsetresetdirect",
1384        actions   = columnsets.resetdirect,
1385        arguments = "string"
1386    }
1387
1388    implement {
1389        name      = "putincolumnsetdirect",
1390        actions   = columnsets.putdirect,
1391        arguments = { {
1392            { "name", "string" },
1393            { "location", "string" },
1394            { "lines", "integer" },
1395            { "box", "integer" },
1396        } }
1397    }
1398
1399    implement {
1400        name      = "addtocolumnset",
1401        actions   = columnsets.add,
1402        arguments = { "argument", "integer" },
1403    }
1404
1405end
1406
1407-- The mvl based variant:
1408
1409do
1410
1411    function columnsets.addmvl(name,box,slot,reduce,balance)
1412        local dataset       = data[name]
1413        local cells         = dataset.cells
1414        local currentrow    = dataset.currentrow
1415        local currentcolumn = dataset.currentcolumn
1416        local currentcells  = dataset.currentcells
1417        local lineheight    = dataset.lineheight
1418        local linedepth     = dataset.linedepth
1419        local widths        = dataset.widths
1420        --
1421        local b = takebox(box)
1422-- print(getwhd(b))
1423        nofcolumngaps = nofcolumngaps + 1
1424        -- getmetatable(v).columngap = nofcolumngaps
1425        properties[b] = { columngap = nofcolumngaps }
1426        if reduce then
1427            local w, h, d = getwhd(b)
1428            local usedcells = ceil((h+d)/(lineheight+linedepth))
1429            currentcells = usedcells
1430            dataset.currentcells = usedcells
1431        end
1432        setwhd(b,widths[currentcolumn],lineheight,linedepth)
1433        local column = cells[currentcolumn]
1434        for i=currentrow,currentrow+currentcells-2 do
1435            if not column[i] then
1436                column[i] = true
1437            end
1438        end
1439        local anchor = currentrow + currentcells - 1
1440        if anchor > 0 then
1441            column[anchor] = b or true
1442            dataset.currentrow = anchor + 1
1443            if balance then
1444                dataset.currentrow = dataset.nofrows + 1
1445                local c, r, n = findgap(dataset,true)
1446                if n then
1447                    dataset.currentcolumn, dataset.currentrow, dataset.currentcells = c, r, n
1448                end
1449            else
1450                columnsets.gotoslot(name,slot)
1451            end
1452        end
1453    end
1454
1455    function columnsets.submvl(name,box,slot)
1456        columnsets.addmvl(name,box,slot,true)
1457    end
1458
1459    function columnsets.submvlbalance(name,box,slot)
1460        columnsets.addmvl(name,box,slot,true,true)
1461    end
1462
1463    function columnsets.gotoslot(name,slot)
1464        local dataset = data[name]
1465        if not dataset.present then
1466            dataset.present = 1
1467            setfirstcells(dataset)
1468        end
1469     -- no gap lookup loops
1470     --
1471     -- if slot then
1472     --     local shape = dataset.shape
1473     --     local s = shape[slot+1]
1474     --     if s.column > 0 then
1475     --         while dataset.present < s.spread do
1476     --             dataset.present = dataset.present + 1
1477     --             dataset.cells = dataset.future[dataset.present] or emptycells(dataset)
1478     --             dataset.future[dataset.present] = dataset.cells
1479     --         end
1480     --         dataset.currentcolumn = s.column
1481     --         dataset.currentrow    = s.row
1482     --         dataset.currentcells  = s.lines
1483     --         return
1484     --     end
1485     -- end
1486        local deadcycles = 1000
1487        while deadcycles > 0 do
1488            local c, r, n = findgap(dataset,true)
1489            if n then
1490                dataset.currentcolumn = c
1491                dataset.currentrow    = r
1492                dataset.currentcells  = n
1493            else
1494                dataset.currentcolumn = 1
1495                dataset.currentrow    = 1
1496                dataset.currentcells  = n
1497                n = 0
1498            end
1499            if n > 0 then
1500                break
1501            else
1502                dataset.present = dataset.present + 1
1503                dataset.cells = dataset.future[dataset.present] or emptycells(dataset)
1504                dataset.future[dataset.present] = dataset.cells
1505            end
1506            deadcycles = deadcycles - 1
1507        end
1508        if deadcycles == 0 then
1509            report("fatal error: mvl %i mismatch",dataset.currentmvl or 1)
1510            os.exit()
1511        end
1512    end
1513
1514    function columnsets.setpresent(name,n)
1515        local dataset = data[name]
1516        if dataset then
1517--             if math.odd(n) then
1518                dataset.present = n
1519                dataset.cells   = dataset.future[n] or dataset.cells
1520--             end
1521        end
1522    end
1523
1524    function columnsets.setmvl(name,n)
1525        local dataset = data[name]
1526        if dataset then
1527            setfirstcells(dataset)
1528            dataset.currentcolumn = dataset.firstcolumn
1529            dataset.currentrow    = 1
1530            dataset.present       = 1
1531            dataset.currentmvl    = n
1532        end
1533    end
1534
1535    function columnsets.definesub(t)
1536        local dataset = data[t.name]
1537        if dataset then
1538            local lastmvl    = dataset.lastmvl or 1
1539            local done       = false
1540            local mvls       = dataset.mvls
1541            local nofcolumns = dataset.nofleft + dataset.nofright
1542            local columns    = t.columns
1543            if columns == "*" or columns == v_all then
1544                columns = { }
1545                for i=1,nofcolumns do
1546                    columns[i] = i
1547                end
1548            else
1549                columns = utilities.parsers.settings_to_array(columns or "")
1550            end
1551            for i=1,#columns do
1552                local n = tonumber(columns[i]) or 1
1553                columns[i] = n
1554                -- nof columns etc are not setup yet
1555                if not done then
1556                    lastmvl = lastmvl + 1
1557                    done    = true
1558                end
1559                mvls[n] = lastmvl
1560            end
1561            dataset.mvlc[t.subname] = columns
1562            dataset.lastmvl = lastmvl
1563            return lastmvl
1564        else
1565            return 1
1566        end
1567    end
1568
1569    implement {
1570        name      = "definesubcolumnset",
1571        actions   = function(t)
1572            return integer_value, columnsets.definesub(t)
1573        end,
1574        usage     = "value",
1575        arguments = { {
1576            { "name", "string" },
1577            { "subname", "string" },
1578            { "columns", "string" },
1579        } }
1580    }
1581
1582    implement {
1583        name      = "columnsetlastmvl",
1584        arguments = "argument",
1585        usage     = "value",
1586        public    = true,
1587        actions   = function(name)
1588            local dataset = data[name]
1589            return integer_value, dataset and dataset.lastmvl or 1
1590        end
1591    }
1592
1593    implement {
1594        name      = "addtocolumnsetmvl",
1595        actions   = columnsets.addmvl,
1596        arguments = { "argument", "integer", "integer" },
1597    }
1598
1599    implement {
1600        name      = "subtocolumnsetmvl",
1601        actions   = columnsets.submvl,
1602        arguments = { "argument", "integer", "integer" },
1603    }
1604
1605    implement {
1606        name      = "subtocolumnsetmvlbalance",
1607        actions   = columnsets.submvlbalance,
1608        arguments = { "argument", "integer", "integer" },
1609    }
1610
1611    implement {
1612        name      = "setcolumnsetpresent",
1613        actions   = columnsets.setpresent,
1614        arguments = { "argument", "integer" },
1615    }
1616
1617    implement {
1618        name      = "setcolumnsetmvl",
1619        actions   = columnsets.setmvl,
1620        arguments = { "argument", "integer" },
1621    }
1622
1623    implement {
1624        name      = "gotocolumnsetslot",
1625        actions   = columnsets.gotoslot,
1626        arguments = "argument",
1627    }
1628
1629end
1630
1631do
1632
1633    -- A split approach is more efficient than a context(followup) inside
1634    -- followup itself as we need less (internal) housekeeping.
1635
1636    local followup = nil
1637    local splitter = lpeg.splitter("*",tonumber)
1638
1639    columnsets["noto"] = function(t)
1640        return followup()
1641    end
1642
1643    columnsets["goto"] = function(name,target)
1644        local dataset    = data[name]
1645        local nofcolumns = dataset.nofcolumns
1646        if target == v_yes or target == "" then
1647            local currentcolumn = dataset.currentcolumn
1648            followup = function()
1649                context(dataset.currentcolumn == currentcolumn and 1 or 0)
1650            end
1651            return followup()
1652        end
1653        if target == v_first then
1654            if dataset.currentcolumn > 1  then
1655                target = v_page -- v_sheet
1656            else
1657                return context(0)
1658            end
1659        end
1660        if target == v_page then -- v_sheet
1661            if dataset.currentcolumn == 1 and dataset.currentrow == 1 then
1662                return context(0)
1663            else
1664                local sheet = dataset.sheet
1665                followup = function()
1666                    context(dataset.sheet == sheet and 1 or 0)
1667                end
1668                return followup()
1669            end
1670        end
1671        if target == v_last then
1672            target = dataset.nofcolumns
1673            if dataset.currentcolumn ~= target then
1674                followup = function()
1675                    context(dataset.currentcolumn ~= target and 1 or 0)
1676                end
1677                return followup()
1678            end
1679            return
1680        end
1681        local targetpage = tonumber(target)
1682        if targetpage then
1683            followup = function()
1684                context(dataset.currentcolumn ~= targetpage and 1 or 0)
1685            end
1686            return followup()
1687        end
1688        local targetcolumn, targetrow = lpeg.match(splitter,target)
1689        if targetcolumn and targetrow then
1690            if dataset.currentcolumn ~= targetcolumn and dataset.currentrow ~= targetrow then
1691                followup = function()
1692                    if dataset.currentcolumn ~= targetcolumn then
1693                        context(1)
1694                        return
1695                    end
1696                    dataset.currentrow = targetrow
1697                    context(0)
1698                end
1699                return followup()
1700            end
1701        end
1702    end
1703
1704    implement {
1705        name      = "columnsetgoto",
1706        actions   = columnsets["goto"],
1707        arguments = "2 strings",
1708    }
1709
1710    implement {
1711        name      = "columnsetnoto",
1712        actions   = columnsets["noto"],
1713    }
1714
1715end
1716
1717do
1718
1719    function columnsets.sethsize(name)
1720        local dataset = data[name]
1721        texsetdimen(d_page_mvl_column_width,dataset.width)
1722    end
1723
1724    function columnsets.sethspan(name,span)
1725        -- no checking if there is really space, so we assume it can be
1726        -- placed which makes spans a very explicit feature
1727        local dataset   = data[name]
1728        local column    = dataset.currentcolumn
1729        local available = dataset.lastcolumn - column + 1
1730        if span > available then
1731            span = available
1732        end
1733        local width = dataset.spans[column][span]
1734        texsetdimen(d_page_mvl_span_width,width)
1735    end
1736
1737--     function columnsets.setcolumnhsize(name,column)
1738--         local dataset = data[name]
1739--         texsetdimen(d_page_mvl_column_width,dataset.widths[column])
1740--     end
1741
1742    implement {
1743        name      = "columnsethspan",
1744        arguments = { "argument", "integer", "integer" },
1745        usage     = "value",
1746        actions   = function(name,column,span)
1747            local dataset = data[name]
1748            local total   = 0
1749            if dataset then
1750                local dataset    = data[name]
1751                local nofcolumns = dataset.nofleft + dataset.nofright
1752                local spanned    = dataset.spans[column]
1753                if spanned then
1754                    total = spanned[span] or 0
1755                end
1756            end
1757            return dimension_value, total
1758        end,
1759    }
1760
1761    implement {
1762        name      = "columnsetcolumnwidth",
1763        arguments = { "argument", "argument" },
1764        usage     = "value",
1765        actions   = function(name,subname)
1766            local dataset = data[name]
1767            local width   = 0
1768            if dataset then
1769                local columns = dataset.mvlc[subname]
1770                width = dataset.widths[columns and columns[1] or 1]
1771            end
1772            return dimension_value, width
1773        end,
1774    }
1775
1776    implement {
1777        name      = "setvsizecolumnset",
1778        arguments = "argument",
1779        actions   = function() end -- columnsets.setvsize,
1780    }
1781
1782    implement {
1783        name      = "sethsizecolumnset",
1784        arguments = "argument",
1785        actions   = columnsets.sethsize,
1786    }
1787
1788    implement {
1789        name      = "sethsizecolumnspan",
1790        arguments = { "argument" ,"integer" },
1791        actions   = columnsets.sethspan,
1792    }
1793
1794end
1795
1796-- Areas: these are not bound to a columnset (yet)
1797
1798do
1799
1800    local areas = { }
1801
1802    function columnsets.registerarea(t)
1803        -- maybe metatable with values
1804        areas[#areas+1] = t
1805    end
1806
1807    -- state : repeat | start
1808
1809    --    ctx_page_mvl_set_area = context.protected.page_mvl_set_area
1810    local ctx_page_mvl_set_area = context.page_mvl_set_area
1811
1812    function columnsets.flushareas(name)
1813        local nofareas = #areas
1814        if nofareas == 0 then
1815            return
1816        end
1817        local dataset  = data[name]
1818        local setsheet = dataset.sheet
1819    -- report("flush areas, sheet %i, nofareas %i",setsheet,nofareas)
1820        if odd(setsheet) then
1821         -- report("checking %i areas",#areas)
1822            local kept = { }
1823            for i=1,nofareas do
1824                local area  = areas[i]
1825                local kind  = area.kind -- v_left v_right v_next
1826                local sheet = area.sheet or 0
1827                if sheet == 1 or sheet == setsheet or sheet == (setsheet + 1) then
1828                    local okay       = false
1829                    local nofleft    = dataset.nofleft
1830                    local nofcolumns = area.nc
1831                    local nofrows    = area.nr
1832                    local column     = area.c
1833                    local row        = area.r
1834                    -- maybe also check realpage
1835                    if kind == v_sheet then
1836                        if odd(sheet) then
1837                            -- okay
1838                        elseif column > nofleft then
1839                            -- okay
1840                        else
1841                            column = column + nofleft
1842                        end
1843                    end
1844                    columnsets.block {
1845                        name = name,
1846                        c    = column,
1847                        r    = row,
1848                        nc   = nofcolumns,
1849                        nr   = nofrows,
1850                    }
1851                    local left     = 0
1852                    local start    = nofleft + 1
1853                    local overflow = (column + nofcolumns - 1) - nofleft
1854                    local height   = nofrows * (dataset.lineheight + dataset.linedepth)
1855                    local width    = dataset.spreads[column][nofcolumns]
1856                 -- report("span, width %p, overflow %i",width,overflow)
1857                    if overflow > 0 then
1858                        local used  = nofcolumns - overflow
1859                        local sofar = dataset.spreads[column][used]
1860                        if sofar then
1861                            left = sofar + texgetdimen(d_backspace)
1862                        else
1863    -- report("area overflow: %i",used)
1864                        end
1865                    end
1866                    -- maybe runmacro
1867                    ctx_page_mvl_set_area(name,area.name,column,row,width,height,start,left) -- or via counters / dimens
1868                    if area.state ~= v_repeat then
1869                        area = nil
1870                    end
1871                    if area then
1872                        kept[#kept+1] = area
1873                    end
1874                else
1875                    kept[#kept+1] = area
1876                end
1877            end
1878            areas = kept
1879        end
1880    end
1881
1882    function columnsets.setarea(t)
1883        local dataset = data[t.name]
1884        local cells   = dataset.cells
1885        local box     = takebox(t.box)
1886        local column  = t.c
1887        local row     = t.r
1888        if column and row then
1889            setwhd(box,dataset.widths[column],dataset.lineheight,dataset.linedepth)
1890            cells[column][row] = box
1891        end
1892    end
1893
1894    implement {
1895        name      = "registercolumnsetarea",
1896        actions   = columnsets.registerarea,
1897        arguments = { {
1898            { "name", "string" },
1899            { "kind", "string" },
1900            { "page", "integer" },
1901            { "sheet", "integer" },
1902            { "state", "string" },
1903            { "c", "integer" },
1904            { "r", "integer" },
1905            { "nc", "integer" },
1906            { "nr", "integer" },
1907        } }
1908    }
1909
1910    implement {
1911        name      = "flushcolumnsetareas",
1912        actions   = columnsets.flushareas,
1913        arguments = "argument",
1914    }
1915
1916    implement {
1917        name      = "setcolumnsetarea",
1918        actions   = columnsets.setarea,
1919        arguments = { {
1920            { "name", "string" },
1921            { "c", "integer" },
1922            { "r", "integer" },
1923            { "method", "string" },
1924            { "box", "integer" },
1925        } }
1926    }
1927
1928end
1929
1930-- States and such
1931
1932do
1933
1934    -- nofleft nofright nofcolumns nofrows currentcolumn page
1935
1936    function columnsets.state(name,variable)
1937        local dataset = data[name]
1938        if variable == "column" then
1939            variable = "currentcolumn"
1940        end
1941        return dataset and dataset[variable] or 0
1942    end
1943
1944    implement {
1945        name      = "columnsetstate",
1946        actions   = function(name,variable)
1947            return integer_value, columnsets.state(name,variable)
1948        end,
1949        public    = true,
1950        usage     = "value",
1951        arguments = "2 arguments",
1952    }
1953
1954    -- 1 = left, 2 = right, 3 = both
1955
1956    local function hascontent(cells,nofleft,nofright)
1957        local result = 0
1958        if cells then
1959            local function found(c)
1960                for i=1,#c do
1961                    if c[i] then
1962                        return true
1963                    end
1964                end
1965                return false
1966            end
1967            for i=1,nofleft do
1968                if found(cells[i]) then
1969                    result = result + 1
1970                    break
1971                end
1972            end
1973            for i=nofleft+1,nofleft+nofright do
1974                if found(cells[i]) then
1975                    result = result + 2
1976                    break
1977                end
1978            end
1979        end
1980        return result
1981    end
1982
1983 -- implement {
1984 --     name      = "columnsethascontent",
1985 --     arguments = { "argument", "integer" },
1986 --     usage     = "value",
1987 --     public    = true,
1988 --     actions   = function(name,index)
1989 --         local dataset = data[name]
1990 --         local result  = 0
1991 --         if dataset then
1992 --             result = hascontent(dataset.future[index],dataset.nofleft,dataset.nofright)
1993 --         end
1994 --         return integer_value, result
1995 --     end
1996 -- }
1997
1998    implement {
1999        name      = "columnsethascontent",
2000        arguments = { "argument", "integer" },
2001        usage     = "value",
2002        public    = true,
2003        actions   = function(name,index)
2004            local dataset = data[name]
2005            if not dataset then
2006                return integer_value, 0
2007            elseif index < dataset.lastcontent then
2008                return integer_value, 3
2009            else
2010                return integer_value, dataset.lastresult
2011            end
2012        end
2013    }
2014
2015
2016    implement {
2017        name      = "columnsetlastfuture",
2018        arguments = "argument",
2019        usage     = "value",
2020        public    = true,
2021        actions   = function(name)
2022            local dataset = data[name]
2023            if dataset then
2024                local future   = dataset.future
2025                local nofleft  = dataset.nofleft
2026                local nofright = dataset.nofright
2027                for i=#future,1,-1 do
2028                    local lastcontent = hascontent(future[i],nofleft,nofright)
2029                    if lastcontent > 0 then
2030                        dataset.lastcontent = i
2031                        dataset.lastresult  = lastcontent
2032                        return integer_value, i
2033                    end
2034                end
2035                dataset.lastcontent = 0
2036                dataset.lastresult  = 0
2037            end
2038            return integer_value, 0
2039        end
2040    }
2041
2042end
2043
2044do
2045
2046    local s_page_mvl_temp <const> = "page:mvl:temp"
2047
2048    function columnsets.registerspreadsheets(name,spreadsheets)
2049        local dataset = data[name]
2050        if dataset then
2051            local s = buffers.raw(s_page_mvl_temp)
2052            stepper(spreadsheets,1,function(n)
2053                local d = dataset.spreadsheets[n]
2054                d[#d+1] = s
2055                report("spreadsheets %i, registering entry %i",n,#d)
2056                if n > dataset.nofspreadsheets then
2057                    dataset.nofspreadsheets = n
2058                end
2059            end)
2060            buffers.erase(s_page_mvl_temp)
2061        end
2062    end
2063
2064    function columnsets.presetspreadsheets(name)
2065        local dataset = data[name]
2066        if dataset then
2067            local saved        = savedstate(dataset)
2068            local spreadsheets = dataset.spreadsheets
2069setstate(dataset,false)
2070dataset.byspread = true
2071dataset.spread = 1
2072dataset.count  = 1
2073dataset.sheet  = 1
2074            for n=1,dataset.nofspreadsheets do
2075                dataset.cells = futurecells(dataset)
2076                local list = rawget(spreadsheets,n)
2077                if list then
2078                    if trace_flush then
2079                        report("presetting: spreadsheets %i, %i entries",n,#list)
2080                    end
2081                    texsetcount(c_page_mvl_current_spreadsheet,n)
2082                    for i=1,#list do
2083                        buffers.assign(s_page_mvl_temp,list[i])
2084                        expandmacro("page_mvl_process_spread")
2085                    end
2086                    spreadsheets[n] = nil
2087                else
2088                    if trace_flush then
2089                        report("presetting: spreadsheets %i, no entries",n)
2090                    end
2091                end
2092-- inspect(dataset.future)
2093--                 setstate(dataset,true)
2094
2095    dataset.spread        = dataset.spread + 1
2096    dataset.count         = dataset.spread * 2 - 1
2097    dataset.sheet         = dataset.spread * 2 - 1
2098    dataset.state         = "left"
2099    dataset.firstcolumn   = 1
2100    dataset.lastcolumn    = dataset.firstcolumn + dataset.nofright
2101    dataset.currentcolumn = dataset.firstcolumn
2102    dataset.currentrow    = 1
2103
2104            end
2105dataset.byspread = false
2106            texsetcount(c_page_mvl_current_spreadsheet,0)
2107            restorestate(dataset,saved)
2108            setstate(dataset,false)
2109            setfirstcells(dataset)
2110            check(dataset)
2111        end
2112    end
2113
2114    function columnsets.registersheet(name,sheet,option)
2115        local dataset = data[name]
2116        if dataset then
2117            local s = buffers.raw(s_page_mvl_temp)
2118            if option == v_page then
2119                stepper(sheet,1,function(n)
2120                    local d = dataset.delayed[n]
2121                    d[#d+1] = s
2122                    report("sheet %i, registering entry %i, delayed",n,#d)
2123                    if n > dataset.nofdelayed then
2124                        dataset.nofdelayed = n
2125                    end
2126                end)
2127            else
2128                stepper(sheet,1,function(n)
2129                    local d = dataset.sheets[n]
2130                    d[#d+1] = s
2131                    report("sheet %i, registering entry %i, immediate",n,#d)
2132                    if n > dataset.nofsheets then
2133                        dataset.nofsheets = n
2134                    end
2135                end)
2136            end
2137            buffers.erase(s_page_mvl_temp)
2138        end
2139    end
2140
2141    function columnsets.presetsheets(name)
2142        local dataset = data[name]
2143        if dataset then
2144            local saved  = savedstate(dataset)
2145            local sheets = dataset.sheets
2146            setstate(dataset,false)
2147            for sheet=1,dataset.nofsheets do
2148                dataset.cells = futurecells(dataset)
2149                check(dataset)
2150                local list = rawget(sheets,sheet)
2151                if list then
2152                    if trace_flush then
2153                        report("presetting: spread %i, count %i, sheet %i, %i entries",
2154                            dataset.spread,dataset.count,dataset.sheet,#list)
2155                    end
2156                    if dataset.sheet ~= sheet then
2157                        report("error: sheets are out of sync")
2158                    end
2159                    texsetcount(c_page_mvl_current_sheet,sheet)
2160                    for i=1,#list do
2161                        buffers.assign(s_page_mvl_temp,list[i])
2162                        expandmacro("page_mvl_process_sheet")
2163                    end
2164                    sheets[sheet] = nil
2165                else
2166                    if trace_flush then
2167                        report("presetting: spread %i, count %i, sheet %i, no entries",
2168                            dataset.spread,dataset.count,dataset.sheet)
2169                    end
2170                end
2171                setstate(dataset,true)
2172            end
2173            texsetcount(c_page_mvl_current_sheet,0)
2174            restorestate(dataset,saved)
2175            setstate(dataset,false)
2176            setfirstcells(dataset)
2177            check(dataset)
2178        end
2179    end
2180
2181    function columnsets.delayedsheet(name)
2182        local dataset = data[name]
2183        if dataset then
2184            local delayed = dataset.delayed
2185            local sheet   = dataset.sheet
2186            local list    = rawget(delayed,sheet)
2187            if list then
2188                if trace_flush then
2189                    report("presetting: delayed sheet %i, %i entries",n,#list)
2190                end
2191                for i=1,#list do
2192                    buffers.assign(s_page_mvl_temp,list[i])
2193                    expandmacro("page_mvl_process_delayed")
2194                end
2195                delayed[sheet] = nil
2196            end
2197        end
2198    end
2199
2200    implement {
2201        name      = "registercolumnsetspreadsheets",
2202        arguments = "2 strings",
2203        actions   = columnsets.registerspreadsheets,
2204    }
2205
2206    implement {
2207        name      = "presetcolumnsetspreadsheets",
2208        arguments = "argument",
2209        actions   = columnsets.presetspreadsheets,
2210    }
2211
2212    implement {
2213        name      = "registercolumnsetsheet",
2214        arguments = "3 strings",
2215        actions   = columnsets.registersheet,
2216    }
2217
2218    implement {
2219        name      = "delayedcolumnsetsheet",
2220        arguments = "string",
2221        actions   = columnsets.delayedsheet,
2222    }
2223
2224    implement {
2225        name      = "presetcolumnsetsheets",
2226        arguments = "argument",
2227        actions   = columnsets.presetsheets,
2228    }
2229
2230end
2231
2232do
2233
2234    local setbalanceshape = tex.setbalanceshape
2235
2236    function columnsets.shape(name)
2237        local dataset    = data[name]
2238        local future     = dataset.future
2239        local disabled   = dataset.disabled
2240        local page       = 0
2241        local height     = dataset.lineheight
2242        local depth      = dataset.linedepth
2243        local total      = height + depth
2244        local toggle     = dataset.nofleft + 1
2245        local nofspreads = #future
2246        local nofcolumns = dataset.nofleft + dataset.nofright
2247        local worstcase  = nofcolumns -- still used?
2248        local first      = dataset.initial == "right" and toggle or 1
2249        local shape      = {
2250            identifier = nums[name],
2251            worstcase  = worstcase,
2252         -- nofleft    = dataset.nofleft,
2253         -- nofright   = dataset.nofright,
2254        }
2255
2256        local slack = 0 -- We need to prevent overflow ..
2257
2258        local function add(spread,page,column,n,row,creator)
2259            n = n - slack
2260            if n == 0 then
2261                n = 1
2262            end
2263            if n > 0 then
2264                local index = #shape + 1;
2265                shape[index] = {
2266                    creator    = creator,
2267                    index      = index,
2268                    spread     = spread,
2269                    page       = page,
2270                    column     = column,
2271                    vsize      = n * total,
2272                    topskip    = height,
2273                 -- bottomskip = depth,
2274                    lines      = n,
2275                    row        = row - n + 1,
2276                }
2277            end
2278        end
2279
2280        local mvls       = dataset.mvls
2281        local currentmvl = dataset.currentmvl or 1
2282
2283        for spread=1,nofspreads do
2284            local cells = future[spread]
2285            local worst    = #shape
2286         -- report("spread %i, add to shape",spread)
2287            for c=first,#cells do
2288                if (mvls[c] == currentmvl or currentmvl == 1) and not disabled[c] then
2289                    local column   = cells[c]
2290                    local lines    = 0
2291                    local slot     = 0
2292                    local noflines = #column
2293                    if c == toggle or c == 1 then
2294                        page = page + 1
2295                    end
2296                    for r=1,noflines do
2297                        if not column[r] then
2298                            lines = lines + 1
2299                        elseif lines > 0 then
2300                            add(spread,page,c,lines,r,"specific shape")
2301                            lines = 0
2302                        end
2303                    end
2304                    if lines > 0 then
2305                        add(spread,page,c,lines,noflines,"specific shape")
2306                    end
2307                end
2308            end
2309            first = 1
2310            worst = #shape - worst + 1
2311            if worst > worstcase then
2312                worstcase = worst
2313            end
2314        end
2315        local nofrows = dataset.nofrows
2316        add(nofspreads,0,0,nofrows,nofrows,"generic shape")
2317        shape.worstcase = worstcase
2318        dataset.shape = shape
2319        dataset.lastshape = copy(shape[#shape])
2320        --
2321        -- cleaner as separate loop
2322        --
2323local cnt = #shape
2324local nor = dataset.nofrows
2325
2326-- local function isfirst(t,n)
2327--     for i=1,n-1 do
2328--         if not t[n] then
2329--             return false
2330--         end
2331--     end
2332--     return true
2333-- end
2334for i=1,cnt do
2335    local s = shape[i]
2336    local r = s.row
2337    if r == 1 then -- or isfirst(future[s.spread][s.column],r) then
2338-- print(s.spread,s.column,r,"TTT")
2339        s.options = 1
2340    end
2341--     if (r + s.lines - 1) == nor then
2342--         s.options = (s.options or 0) + 2
2343--     end
2344end
2345-- shape[cnt].options = 3
2346shape[cnt].options = 1
2347        --
2348        setbalanceshape(shape)
2349    end
2350
2351    function columnsets.limit(name)
2352        local dataset = data[name]
2353        if dataset and not dataset.limited then
2354            local future = dataset.future
2355            local limit  = dataset.limit or 1
2356            for i=1,#future do
2357                local cells = future[i]
2358                for c=1,#cells do
2359                    local column = cells[c]
2360                    local lines  = 0
2361                    local first  = 0
2362                    local noflines = #column
2363                    for r=1,noflines do
2364                        if not column[r] then
2365                            if first == 0 then
2366                                -- first free cell
2367                                first = r
2368                                lines = 1
2369                            else
2370                                -- next free cell
2371                                lines = lines + 1
2372                            end
2373                        elseif first > 0 then
2374                            if lines < limit then
2375                                -- free range width in limit
2376                                for i=first,first+lines-1 do
2377                                    column[i] = true
2378                                end
2379                            else
2380                                -- more free than limit
2381                            end
2382                            lines = 0
2383                            first = 0
2384                        else
2385                        end
2386                    end
2387                    if first > 0 and lines < limit then
2388                        for i=first,first+lines-1 do
2389                            column[i] = true
2390                        end
2391                    end
2392                end
2393            end
2394            dataset.limited = true
2395        end
2396    end
2397
2398    function columnsets.reshape(name)
2399        local dataset = data[name]
2400        if dataset then
2401            local shape = dataset.shape
2402            if shape then
2403                setbalanceshape(shape)
2404            end
2405        end
2406    end
2407
2408    implement {
2409        name      = "columnsetshape",
2410        arguments = "argument",
2411        actions   = columnsets.shape,
2412    }
2413
2414    implement {
2415        name      = "columnsetlimit",
2416        arguments = "argument",
2417        actions   = columnsets.limit,
2418    }
2419
2420    implement {
2421        name      = "columnsetreshape",
2422        arguments = "argument",
2423        actions   = columnsets.reshape,
2424    }
2425
2426    function columnsets.setextra(name,index,reset,justadd)
2427        local dataset = data[name]
2428        if dataset then
2429            local shape = dataset.shape
2430            if shape then
2431                local nofshapes = #shape
2432                local s = index < nofshapes and shape[index]
2433                if not s then
2434                    local template   = shape[nofshapes-1]
2435                    local lastshape  = dataset.lastshape
2436                    local threshold  = dataset.nofleft + 1
2437                    local nofcolumns = dataset.nofleft + dataset.nofright
2438                    local lines      = lastshape.lines -- dataset.nofrows
2439                    local vsize      = lastshape.vsize
2440                    local topskip    = lastshape.topskip
2441                    local bottomskip = lastshape.bottomskip
2442                    local spread     = lastshape.spread + 1
2443                    local spread     = template.spread + 1
2444                    local page       = template.page + 1
2445                    while nofshapes <= index do -- otherwise we bleed extra
2446                        for j=1,nofcolumns do
2447                            if j == threshold then
2448                                page = page + 1
2449                            end
2450                            shape[nofshapes] = {
2451                                creator    = "intermediate extra",
2452                                column     = j,
2453                                extra      = 0,
2454                                extralines = 0,
2455                                vsize      = vsize,
2456                                index      = nofshapes,
2457                                lines      = lines,
2458                                page       = page,
2459                                row        = 1,
2460                                spread     = spread,
2461                                topskip    = topskip,
2462                                bottomskip = bottomskip,
2463                            }
2464                            nofshapes = nofshapes + 1
2465                        end
2466                        spread = spread + 1
2467                        page   = page + 1
2468                    end
2469                    if shape[#shape].creator ~= "generic shape" then
2470                        nofshapes = #shape + 1
2471                        local last      = copy(lastshape) -- todo: no copy
2472                        last.spread     = spread
2473                        last.extra      = 0
2474                        last.extralines = 0
2475                        last.index      = nofshapes
2476                        last.creator    = "final extra"
2477                        shape[nofshapes] = last
2478                    end
2479                    s = shape[index] or shape[#shape] -- bad or
2480                end
2481                if justadd then
2482                    return
2483                else
2484                    local extralines = (s.extralines or 0) - (reset and -1 or 1)
2485                    if -extralines >= s.lines + 1 then
2486-- print(reset,index,-extralines,s.lines + 1)
2487                        return integer_value, 0
2488                    else
2489-- print(reset,index,-extralines,s.lines + 1)
2490                        s.extralines = extralines
2491                        s.extra = extralines * (dataset.lineheight + dataset.linedepth)
2492                        return integer_value, 1
2493                    end
2494                end
2495            end
2496        end
2497        if justadd then
2498            return integer_value, 0
2499        end
2500    end
2501
2502    implement {
2503        name      = "columnsetsetshapeextra",
2504        arguments = { "argument", "integer", false },
2505        usage     = "value",
2506        actions   = columnsets.setextra
2507    }
2508
2509    implement {
2510        name      = "columnsetresetshapeextra",
2511        arguments = { "argument", "integer", true },
2512        usage     = "value",
2513        actions   = columnsets.setextra
2514    }
2515
2516    implement {
2517        name      = "balanceshapeextra",
2518        arguments = { "argument", "integer" },
2519        usage     = "value",
2520        public    = true,
2521        protected = true,
2522        actions   = function(name,index)
2523            local dataset = data[name]
2524            if dataset then
2525                local shape = dataset.shape
2526                if shape then
2527                    return dimension_value, (shape[index] or shape[#shape]).extra
2528                end
2529            end
2530            return dimension_value, 0
2531        end
2532    }
2533
2534    implement {
2535        name      = "columnsetshapevsize",
2536        arguments = { "argument", "integer" },
2537        usage     = "value",
2538        public    = true,
2539        actions   = function(name,index)
2540            local dataset = data[name]
2541            local vsize   = 0
2542            if dataset then
2543                local shape = dataset.shape
2544                if shape then
2545                    local s = shape[index] or shape[#shape]
2546                    vsize = s.vsize
2547                end
2548            end
2549            return dimension_value, vsize
2550        end
2551    }
2552
2553    implement {
2554        name      = "columnsetshapeindex",
2555        arguments = { "argument", "integer" },
2556        usage     = "value",
2557        public    = true,
2558        actions   = function(name,index)
2559            local dataset = data[name]
2560            local index   = 0
2561            if dataset then
2562                local shape = dataset.shape
2563                if shape then
2564                    index = (shape[index] or shape[#shape]).page
2565                end
2566            end
2567            return integer_value, index
2568        end
2569    }
2570
2571    implement {
2572        name      = "columnsetshapecount",
2573        arguments = "argument",
2574        usage     = "value",
2575        public    = true,
2576        actions   = function(name,index)
2577            local dataset = data[name]
2578            local count   = 0
2579            if dataset then
2580                local shape = dataset.shape
2581                if shape then
2582                    count = #shape
2583                end
2584            end
2585            return integer_value, count
2586        end
2587    }
2588
2589    implement {
2590        name      = "columnsetshapeworst",
2591        arguments = "argument",
2592        usage     = "value",
2593        public    = true,
2594        actions   = function(name,index)
2595            local dataset = data[name]
2596            local worst   = 0
2597            if dataset then
2598                local shape = dataset.shape
2599                if shape then
2600                    worst = shape.worst
2601                end
2602                if worst == 0 then
2603                    worst = dataset.nofleft + dataset.nofright
2604                end
2605            end
2606            return integer_value, worst
2607        end
2608    }
2609
2610end
2611
2612-- Tracing and status information:
2613
2614do
2615
2616    local trace = false
2617
2618    trackers.register("columnsets.showgrid",function(v)
2619        trace = v
2620    end)
2621
2622    local function showgrid(dataset,where,cells)
2623        local nofcolumns = dataset.nofcolumns
2624        local nofrows    = dataset.nofrows
2625        local row        = { [0] = "0" }
2626        local sheet      = { }
2627        if not cells then
2628            cells = dataset.cells
2629        end
2630        for r=1,nofrows do
2631            row[0] = format("% 3i",r)
2632            for c=1,nofcolumns do
2633                local v = cells[c][r]
2634                -- if type(v) is a rule that we added
2635                row[c] = v == true and "!" or v and "+" or "-"
2636            end
2637            sheet[#sheet+1] = concat(row," ",0,nofcolumns)
2638        end
2639        report("%s :\n%s",where or "show",concat(sheet,"\n"))
2640    end
2641
2642    local function show(name,range)
2643        local dataset = data[name]
2644        if dataset then
2645            local future = dataset.future
2646            if future then
2647                stepper(range or "1:*",#future,function(n)
2648                    showgrid(dataset,"spread " .. n,future[n])
2649                end)
2650            end
2651        end
2652    end
2653
2654    implement {
2655        name      = "columnsetshowtracedgrid",
2656        arguments = "argument",
2657        actions   = function(name)
2658            if type(trace) == "string" then
2659                show(name,trace)
2660            end
2661        end,
2662    }
2663
2664    implement {
2665        name      = "columnsetshowgrid",
2666        arguments = "2 arguments",
2667        public    = true,
2668        protected = true,
2669        actions   = show,
2670    }
2671
2672    function columnsets.showshape(name)
2673        local dataset = data[name]
2674        if dataset then
2675            local shape = dataset.shape
2676            report("")
2677            if shape then
2678                for i=1,#shape do
2679                    local s = shape[i]
2680                    report(
2681                        "%04i: s %03i p %03i c %01i l %02i e %02i v %p c %a",
2682                        i, s.spread, s.page, s.column, s.lines, s.extralines or 0, s.vsize, s.creator
2683                    )
2684                end
2685            else
2686                report("no shape")
2687            end
2688            report("")
2689        end
2690    end
2691
2692    implement {
2693        name      = "columnsetshowshape",
2694        arguments = "argument",
2695        public    = true,
2696        protected = true,
2697        actions   = columnsets.showshape
2698    }
2699
2700    function columnsets.status(name)
2701        local dataset = data[name]
2702        if dataset then
2703            local shape = dataset.shape
2704            local sheet = dataset.sheet
2705            local lines = { "[" }
2706            if shape then
2707                for i=1,#shape do
2708                    local s = shape[i]
2709                    if s and s.page == sheet then
2710                        local l = s.lines
2711                        local e = s.extralines
2712                        local s = tostring(l)
2713                        if e and e ~= 0 then
2714                            if e > 0 then
2715                                s = s .. "+"
2716                            end
2717                            s = s .. tostring(e)
2718                        end
2719                        lines[#lines+1] = s
2720                    end
2721                end
2722                if #lines == 1 then
2723                    lines[#lines+1] = "-"
2724                end
2725            end
2726            lines[#lines+1] = "]"
2727            return
2728             "[ page "       .. texgetcount(c_realpageno) ..
2729             ", spread "     .. dataset.spread ..
2730             ", sheet "      .. sheet ..
2731             ", nofcolumns " .. dataset.nofcolumns ..
2732             ", first "      .. dataset.firstcolumn ..
2733             ", last "       .. dataset.lastcolumn ..
2734             ", nofleft "    .. dataset.nofleft ..
2735             ", nofright "   .. dataset.nofright ..
2736             ", nofrows "    .. dataset.nofrows ..
2737             " ] " .. concat(lines," ")
2738        end
2739    end
2740
2741    implement {
2742        name      = "columnsetstatus",
2743        arguments = "argument",
2744        actions   = { columnsets.status, context },
2745    }
2746
2747end
2748
2749-- Break related callback:
2750
2751do
2752
2753    local div, mod = math.div, math.mod
2754
2755    local actions = { "spread", "page", "column", "slot", "lines", "amount" }
2756    local trace   = false
2757    local eject   <const> = tex.magicconstants.ejectpenalty
2758
2759    trackers.register("columnsets.breaks", function(v)
2760        trace = v
2761    end)
2762
2763    -- In the last shape entry the spread counter holds the number of spreads
2764    -- so far. After that we have nofcolumns per spread so slots are single
2765    -- column.
2766    --
2767    -- We only use a count of one.
2768
2769    ----- nothing_code   <const> = tex.balancecallbackcodes.nothing
2770    local trybreak_code  <const> = tex.balancecallbackcodes.trybreak
2771    local skipzeros_code <const> = tex.balancecallbackcodes.skipzeros
2772
2773    callback.register("balance_boundary", function(what,count,identifier,slot)
2774        local name    = nums[identifier]
2775        local action  = actions[what] or "unknown"
2776        local result  = skipzeros_code
2777        local penalty = 0
2778        local extra   = 0
2779        if name and action then
2780            local dataset = data[name]
2781            if dataset then
2782                local shape = dataset.shape
2783                if shape then
2784                    local spread, page, column
2785                    local quit       = dataset.quit
2786                    local nofleft    = dataset.nofleft
2787                    local nofright   = dataset.nofright
2788                    local nofcolumns = nofleft + nofright
2789                    local current    = slot < #shape and shape[slot]
2790                    local shaped     = current and current.column ~= 0
2791                    if shaped then
2792                        spread = current.spread
2793                        page   = current.page
2794                        column = current.column
2795                    else
2796                        local nofslots = #shape - 1
2797                        local overflow = slot - nofslots
2798                        spread = shape[#shape].spread
2799                        column = overflow
2800                        while column > nofcolumns do
2801                            spread = spread + 1
2802                            column = column - nofcolumns
2803                        end
2804                        page = spread*2
2805                        if column <= nofleft then
2806                            page = page - 1
2807                        end
2808                        if column == 0 then
2809                            count = 0
2810                        end
2811                    end
2812                    if action == "spread" then
2813                        if count > 0 then
2814                            if not shaped then
2815                                quit = #shape + (spread-shape[#shape].spread+1) * nofcolumns - column + 1
2816--                             elseif slot == #shape - 1 then
2817                            elseif slot == #shape then
2818                                quit = #shape
2819                            else
2820                                quit = slot
2821                                for i=slot+1,#shape do
2822                                    local s = shape[i].spread
2823                                    quit = quit + 1
2824                                    if s > spread then
2825                                        break
2826                                    end
2827                                end
2828                            end
2829                            dataset.quit = quit
2830                        end
2831                        if slot < quit then
2832                            result  = trybreak_code
2833                            penalty = eject
2834                        end
2835                    elseif action == "page" then
2836                        if count > 0 then
2837                            if not shaped then
2838                                quit = slot + nofcolumns - column + 1
2839                                if odd(page) then
2840                                    quit = quit - nofright
2841                                end
2842                            elseif slot == #shape then
2843                                quit = #shape
2844                            else
2845                                quit = slot
2846                                for i=slot+1,#shape-1 do
2847                                    local p = shape[i].page
2848                                    quit = quit + 1
2849                                    if p > page then
2850                                        break
2851                                    end
2852                                end
2853                            end
2854                            dataset.quit = quit
2855                        end
2856                        if slot < quit then
2857                            result  = trybreak_code
2858                            penalty = eject
2859                        end
2860                    elseif action == "column" then
2861                        if count > 0 then
2862                            if not shaped then
2863                                quit = slot + 1
2864                            elseif slot == #shape then
2865                                quit = #shape
2866                            else
2867                                quit = slot
2868                                for i=slot+1,#shape-1 do
2869                                    local c = shape[i].column
2870                                    quit = quit + 1
2871                                    if column == nofcolumns and c == 1 then
2872                                        break
2873                                    elseif c > column then
2874                                        break
2875                                    end
2876                                end
2877                            end
2878                            dataset.quit = quit
2879                        end
2880                        if slot < quit then
2881                            result  = trybreak_code
2882                            penalty = eject
2883                        end
2884                    elseif action == "slot" then
2885                        if count > 0 then
2886                            quit = slot + count
2887                            dataset.quit = quit
2888                        end
2889                        if slot < quit then
2890                            result  = trybreak_code
2891                            penalty = eject
2892                        end
2893                    elseif action == "lines" or action == "amount" then
2894                        if count > 0 then
2895                            if action == "lines" then
2896                                extra = count * (dataset.lineheight + dataset.linedepth)
2897                            else
2898                                extra = count
2899                            end
2900                            result = trybreak_code
2901                            if trace then
2902                                report(
2903                                    "name %a, action %s, count %i, slot %i, spread %i, page %i, column %i, extra %p",
2904                                    name, action, count, slot, spread, page, column, shaped, extra
2905                                )
2906                            end
2907                        end
2908                        goto TODO
2909                    end
2910                    if trace then
2911                        report(
2912                            "name %a, action %s, count %i, slot %i, spread %i, page %i, column %i, shaped %l, quit %i",
2913                            name, action, count, slot, spread, page, column, shaped, quit
2914                        )
2915                    end
2916                  ::TODO::
2917                end
2918            end
2919        end
2920        if trace then
2921            if result == skipzeros_code then
2922                report("action %s, ignore rest",action or "quit")
2923            else
2924                report("action %s, eject",action)
2925            end
2926        end
2927        return result, penalty, extra
2928    end)
2929
2930 -- implement {
2931 --     name      = "columnsetidentifier",
2932 --     arguments = "argument",
2933 --     usage     = "value",
2934 --     public    = true,
2935 --     actions   = function(name)
2936 --         return integer_value, nums[name] or 0
2937 --     end
2938 -- }
2939
2940    local function analyse(name,slot)
2941        local dataset = data[name]
2942        local spread  = 0
2943        local page    = 0
2944        local column  = 0
2945        if dataset then
2946            local shape = dataset.shape
2947            if shape then
2948                columnsets.setextra(name,slot,false,true)
2949                local nofleft    = dataset.nofleft
2950                local nofright   = dataset.nofright
2951                local nofcolumns = nofleft + nofright
2952                local current    = slot < #shape and shape[slot]
2953                local shaped     = current and current.column ~= 0
2954                if shaped then
2955                    spread = current.spread
2956                    page   = current.page
2957                    column = current.column
2958                else
2959--                     local nofslots = #shape - 1
2960--                     local overflow = slot - nofslots
2961--                     spread = shape[#shape].spread
2962--                     column = overflow
2963--                     while column > nofcolumns do
2964--                         spread = spread + 1
2965--                         column = column - nofcolumns
2966--                     end
2967--                     page = spread * 2
2968--                     if column <= nofleft then
2969--                         page = page - 1
2970--                     end
2971                end
2972            end
2973        end
2974        return spread, page, column, slot
2975    end
2976
2977    -- maybe split setextra off, cleaner
2978
2979    implement {
2980        name      = "columnsetshapebalanceextend",
2981        arguments = { "argument", "integer" },
2982        actions   = function(name,index)
2983            local dataset = data[name]
2984            if dataset then
2985                columnsets.setextra(name,index,false,true)
2986            end
2987        end
2988    }
2989
2990    implement {
2991        name      = "columnsetshapebalancecheck",
2992        arguments = { "argument", "integer" },
2993        usage     = "value",
2994        actions   = function(name,index)
2995            local dataset = data[name]
2996            if dataset then
2997                local shape = dataset.shape[index]
2998                if shape then
2999                    local spread = shape.spread
3000                    local future = dataset.future[spread]
3001                    if future then
3002                        local column = shape.column
3003                        cells = future[column]
3004                        if cells then
3005                            local first = false
3006                            local last  = false
3007                            local found = false
3008                            -- it's cleaner to not merge these loops
3009                            for r=1,#cells do
3010                                if not cells[r] then
3011                                    if not first then
3012                                        first = r
3013                                    end
3014                                    last = r
3015                                end
3016                            end
3017                            local lastcolumn = column > dataset.nofleft and #future or dataset.nofleft
3018                            local function okay(r)
3019                                for c=column,lastcolumn do
3020                                    if future[c][r] then
3021                                        return false
3022                                    end
3023                                end
3024                                return true
3025                            end
3026                            for r=first,last do
3027                                if not okay(r) then
3028                                    report("prepare found range %i..%i with occupied row %i on spread %i",first,last,r,spread)
3029                                    return integer_value, 0
3030                                end
3031                            end
3032                         -- report("prepare found range %i..%i on spread %i",first,last,spread)
3033                            return integer_value, 1
3034                        end
3035                    else
3036                     -- report("prepare found full generic column slots")
3037                        return integer_value, 1
3038                    end
3039                end
3040            end
3041            return integer_value, 0
3042        end
3043    }
3044
3045
3046    implement {
3047        name      = "columnsetshapesetbalancerange",
3048        arguments = { "argument", "integer" },
3049        usage     = "value",
3050        actions   = function(name,index)
3051            local dataset = data[name]
3052            if dataset then
3053                local shape = dataset.shape
3054                local where = shape[index]
3055                local first = 0
3056                local last  = 0
3057                local page  = 0
3058                if where then
3059                    first = index
3060                    last  = index
3061                    page  = where.page
3062                    for i=index-1,1,-1 do
3063                        if shape[i].page == page then
3064                            first = i
3065                        else
3066                            break
3067                        end
3068                    end
3069                    for i=index+1,#shape-1 do
3070                        if shape[i].page == page then
3071                            last = i
3072                        else
3073                            break
3074                        end
3075                    end
3076                end
3077                dataset.balancefirst = first
3078                dataset.balancelast  = last
3079                dataset.balancepage  = page
3080                dataset.balanceindex = index
3081            end
3082        end
3083    }
3084
3085    implement {
3086        name      = "columnsetbalancepage",
3087        arguments = "argument",
3088        usage     = "value",
3089        actions   = function(name)
3090            local dataset = data[name]
3091            return integer_value, dataset and dataset.balancepage or 0
3092        end
3093    }
3094
3095    implement {
3096        name      = "columnsetbalancefirst",
3097        arguments = "argument",
3098        usage     = "value",
3099        actions   = function(name)
3100            local dataset = data[name]
3101            return integer_value, dataset and dataset.balancefirst or 0
3102        end
3103    }
3104
3105    implement {
3106        name      = "columnsetbalancelast",
3107        arguments = "argument",
3108        usage     = "value",
3109        actions   = function(name)
3110            local dataset = data[name]
3111            return integer_value, dataset and dataset.balancelast or 0
3112        end
3113    }
3114
3115    implement {
3116        name      = "columnsetshapespread",
3117        arguments = { "argument", "integer" },
3118        usage     = "value",
3119        public    = true,
3120        actions   = function(name,index)
3121            local spread, page, column = analyse(name,index)
3122            return integer_value, spread
3123        end
3124    }
3125
3126    implement {
3127        name      = "columnsetshapepage",
3128        arguments = { "argument", "integer" },
3129        usage     = "value",
3130        public    = true,
3131        actions   = function(name,index)
3132            local spread, page, column = analyse(name,index)
3133            return integer_value, page
3134        end
3135    }
3136
3137    implement {
3138        name      = "columnsetshapecolumn",
3139        arguments = { "argument", "integer" },
3140        usage     = "value",
3141        public    = true,
3142        actions   = function(name,index)
3143            local spread, page, column = analyse(name,index)
3144            return integer_value, column
3145        end
3146    }
3147
3148    implement {
3149        name      = "columnsetshapeslots",
3150        arguments = "argument",
3151        usage     = "value",
3152        public    = true,
3153        actions   = function(name,index)
3154            local dataset = data[name]
3155            if dataset then
3156                return integer_value, #dataset.shape
3157            else
3158                return integer_value, 0
3159            end
3160        end
3161    }
3162
3163end
3164
3165do
3166
3167    -- todo: every cell: state 0=unknown 1=some 2=full
3168
3169    function columnsets.blockrows(name,what,n)
3170        local dataset = data[name]
3171        if dataset then
3172            local future     = dataset.future
3173            local nofrows    = dataset.nofrows
3174            local nofleft    = dataset.nofleft
3175            local nofright   = dataset.nofright
3176            local nofcolumns = nofleft + nofright
3177
3178            local function check(f,r,first,last)
3179                local okay = false
3180                for c=first,last do
3181                    if f[c][r] then
3182                        okay = true
3183                    end
3184                end
3185                if okay then
3186                    for c=first,last do
3187                        local fc = f[c]
3188                        if not fc[r] then
3189                            fc[r] = true
3190                        end
3191                    end
3192                end
3193            end
3194
3195            for i=1,n do
3196                local f = future[i]
3197                if what == v_spread then
3198                    for r=1,nofrows do
3199                        check(f,r,1,nofcolumns)
3200                    end
3201                else
3202                    for r=1,nofrows do
3203                        check(f,r,1,nofleft)
3204                        check(f,r,nofleft+1,nofcolumns)
3205                    end
3206                end
3207            end
3208
3209            dataset.currentrow    = 1
3210            dataset.currentcolumn = 1
3211        end
3212    end
3213
3214    implement {
3215        name      = "columnsetsblockrows",
3216        arguments = { "argument", "argument", "integer" },
3217        actions   = columnsets.blockrows,
3218    }
3219
3220    implement {
3221        name      = "columnsetdisablewide",
3222        arguments = "argument",
3223        actions   = function(name)
3224            local dataset = data[name]
3225            if dataset then
3226                local disabled = dataset.disabled
3227                for i=1,dataset.nofcolumns do
3228                    disabled[i] = false
3229                end
3230            end
3231        end
3232    }
3233
3234    implement {
3235        name      = "columnsetenablewide",
3236        arguments = "argument",
3237        actions   = function(name)
3238            local dataset = data[name]
3239            if dataset then
3240                local disabled = dataset.disabled
3241                for i=1,dataset.nofcolumns do
3242                    disabled[i] = true
3243                end
3244                disabled[1] = false
3245                disabled[dataset.nofleft+1] = false
3246            end
3247        end
3248    }
3249
3250end
3251