tabl-xtb.lua /size: 41 Kb    last modification: 2021-10-28 13:50
1if not modules then modules = { } end modules ['tabl-xtb'] = {
2    version   = 1.001,
3    comment   = "companion to tabl-xtb.mkvi",
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--[[
10
11This table mechanism is a combination between TeX and Lua. We do process
12cells at the TeX end and inspect them at the Lua end. After some analysis
13we have a second pass using the calculated widths, and if needed cells
14will go through a third pass to get the heights right. This last pass is
15avoided when possible which is why some code below looks a bit more
16complex than needed. The reason for such optimizations is that each cells
17is actually a framed instance and because tables like this can be hundreds
18of pages we want to keep processing time reasonable.
19
20To a large extend the behaviour is comparable with the way bTABLE/eTABLE
21works and there is a module that maps that one onto this one. Eventually
22this mechamism will be improved so that it can replace its older cousin.
23
24]]--
25
26-- todo: use linked list instead of r/c array
27-- todo: we can use the sum of previously forced widths for column spans
28
29local tonumber, next = tonumber, next
30
31local commands            = commands
32local context             = context
33local ctxnode             = context.nodes.flush
34
35local implement           = interfaces.implement
36
37local tex                 = tex
38local texgetcount         = tex.getcount
39local texsetcount         = tex.setcount
40local texgetdimen         = tex.getdimen
41local texsetdimen         = tex.setdimen
42local texget              = tex.get
43
44local format              = string.format
45local concat              = table.concat
46local points              = number.points
47
48local todimen             = string.todimen
49
50local ctx_beginvbox       = context.beginvbox
51local ctx_endvbox         = context.endvbox
52local ctx_blank           = context.blank
53local ctx_nointerlineskip = context.nointerlineskip
54local ctx_dummyxcell      = context.dummyxcell
55
56local variables           = interfaces.variables
57
58local setmetatableindex   = table.setmetatableindex
59local settings_to_hash    = utilities.parsers.settings_to_hash
60
61local nuts                = nodes.nuts -- here nuts gain hardly nothing
62local tonut               = nuts.tonut
63local tonode              = nuts.tonode
64
65local getnext             = nuts.getnext
66local getprev             = nuts.getprev
67local getlist             = nuts.getlist
68local getwidth            = nuts.getwidth
69local getbox              = nuts.getbox
70local getwhd              = nuts.getwhd
71
72local setlink             = nuts.setlink
73local setdirection        = nuts.setdirection
74local setshift            = nuts.setshift
75
76local copynodelist        = nuts.copylist
77local hpacknodelist       = nuts.hpack
78local flushnodelist       = nuts.flushlist
79local takebox             = nuts.takebox
80
81local nodepool            = nuts.pool
82
83local new_glue            = nodepool.glue
84local new_kern            = nodepool.kern
85local new_hlist           = nodepool.hlist
86
87local lefttoright_code    = nodes.dirvalues.lefttoright
88
89local v_stretch           = variables.stretch
90local v_normal            = variables.normal
91local v_width             = variables.width
92local v_height            = variables.height
93local v_repeat            = variables["repeat"]
94local v_max               = variables.max
95local v_fixed             = variables.fixed
96----- v_auto              = variables.auto
97local v_before            = variables.before
98local v_after             = variables.after
99local v_both              = variables.both
100local v_samepage          = variables.samepage
101local v_tight             = variables.tight
102
103local xtables             = { }
104typesetters.xtables       = xtables
105
106local trace_xtable        = false
107local report_xtable       = logs.reporter("xtable")
108
109trackers.register("xtable.construct", function(v) trace_xtable = v end)
110
111local null_mode = 0
112local head_mode = 1
113local foot_mode = 2
114local more_mode = 3
115local body_mode = 4
116
117local namedmodes = { [0] =
118    "null",
119    "head",
120    "foot",
121    "next",
122    "body",
123}
124
125local stack, data = { }, nil
126
127function xtables.create(settings)
128    table.insert(stack,data)
129    local rows           = { }
130    local widths         = { }
131    local heights        = { }
132    local depths         = { }
133 -- local spans          = { }
134    local distances      = { }
135    local autowidths     = { }
136    local modes          = { }
137    local fixedrows      = { }
138    local fixedcolumns   = { }
139 -- local fixedcspans    = { }
140    local frozencolumns  = { }
141    local options        = { }
142    local rowproperties  = { }
143    data = {
144        rows           = rows,
145        widths         = widths,
146        heights        = heights,
147        depths         = depths,
148     -- spans          = spans,
149        distances      = distances,
150        modes          = modes,
151        autowidths     = autowidths,
152        fixedrows      = fixedrows,
153        fixedcolumns   = fixedcolumns,
154     -- fixedcspans    = fixedcspans,
155        frozencolumns  = frozencolumns,
156        options        = options,
157        nofrows        = 0,
158        nofcolumns     = 0,
159        currentrow     = 0,
160        currentcolumn  = 0,
161        settings       = settings or { },
162        rowproperties  = rowproperties,
163    }
164    local function add_zero(t,k)
165        t[k] = 0
166        return 0
167    end
168    local function add_table(t,k)
169        local v = { }
170        t[k] = v
171        return v
172    end
173    local function add_cell(row,c)
174        local cell = {
175            nx     = 0,
176            ny     = 0,
177            list   = false,
178            wd     = 0,
179            ht     = 0,
180            dp     = 0,
181        }
182        row[c] = cell
183        if c > data.nofcolumns then
184            data.nofcolumns = c
185        end
186        return cell
187    end
188    local function add_row(rows,r)
189        local row = { }
190        setmetatableindex(row,add_cell)
191        rows[r] = row
192        if r > data.nofrows then
193            data.nofrows = r
194        end
195        return row
196    end
197    setmetatableindex(rows,add_row)
198    setmetatableindex(widths,add_zero)
199    setmetatableindex(heights,add_zero)
200    setmetatableindex(depths,add_zero)
201    setmetatableindex(distances,add_zero)
202    setmetatableindex(modes,add_zero)
203    setmetatableindex(fixedrows,add_zero)
204    setmetatableindex(fixedcolumns,add_zero)
205    setmetatableindex(options,add_table)
206 -- setmetatableindex(fixedcspans,add_table)
207    --
208    local globaloptions = settings_to_hash(settings.option)
209    --
210    settings.columndistance      = tonumber(settings.columndistance) or 0
211    settings.rowdistance         = tonumber(settings.rowdistance) or 0
212    settings.leftmargindistance  = tonumber(settings.leftmargindistance) or 0
213    settings.rightmargindistance = tonumber(settings.rightmargindistance) or 0
214    settings.options             = globaloptions
215    settings.textwidth           = tonumber(settings.textwidth) or texget("hsize")
216    settings.lineheight          = tonumber(settings.lineheight) or texgetdimen("lineheight")
217    settings.maxwidth            = tonumber(settings.maxwidth) or settings.textwidth/8
218 -- if #stack > 0 then
219 --     settings.textwidth = texget("hsize")
220 -- end
221    data.criterium_v =   2 * data.settings.lineheight
222    data.criterium_h = .75 * data.settings.textwidth
223    --
224    data.tight = globaloptions[v_tight] and true or false
225end
226
227function xtables.initialize_reflow_width(option,width)
228    local r = data.currentrow
229    local c = data.currentcolumn + 1
230    local drc = data.rows[r][c]
231    drc.nx = texgetcount("c_tabl_x_nx")
232    drc.ny = texgetcount("c_tabl_x_ny")
233    local distances = data.distances
234    local distance = texgetdimen("d_tabl_x_distance")
235    if distance > distances[c] then
236        distances[c] = distance
237    end
238    if option and option ~= "" then
239        local options = settings_to_hash(option)
240        data.options[r][c] = options
241        if options[v_fixed] then
242            data.frozencolumns[c] = true
243        end
244    end
245    data.currentcolumn = c
246end
247
248-- todo: we can better set the cell values in one go
249
250function xtables.set_reflow_width()
251    local r = data.currentrow
252    local c = data.currentcolumn
253    local rows = data.rows
254    local row = rows[r]
255    local cold = c
256    while row[c].span do -- can also be previous row ones
257        c = c + 1
258    end
259    -- bah, we can have a span already
260    if c > cold then
261        local ro = row[cold]
262        local rx = ro.nx
263        local ry = ro.ny
264        if rx > 1 or ry > 1 then
265            local rn = row[c]
266            rn.nx = rx
267            rn.ny = ry
268            ro.nx = 1 -- or 0
269            ro.ny = 1 -- or 0
270            -- do we also need to set ro.span and rn.span
271        end
272    end
273    local tb = getbox("b_tabl_x")
274    local drc = row[c]
275    --
276    drc.list = true -- we don't need to keep the content around as we're in trial mode (no: copynodelist(tb))
277    --
278    local width, height, depth = getwhd(tb)
279    --
280    local widths  = data.widths
281    local heights = data.heights
282    local depths  = data.depths
283    local cspan   = drc.nx
284    if cspan < 2 then
285        if width > widths[c] then
286            widths[c] = width
287        end
288    else
289        local options = data.options[r][c]
290        if data.tight then
291            -- no check
292        elseif not options then
293            if width > widths[c] then
294                widths[c] = width
295            end
296        elseif not options[v_tight] then
297            if width > widths[c] then
298                widths[c] = width
299            end
300        end
301    end
302 -- if cspan > 1 then
303 --     local f = data.fixedcspans[c]
304 --     local w = f[cspan] or 0
305 --     if width > w then
306 --         f[cspan] = width -- maybe some day a solution for autospanmax and so
307 --     end
308 -- end
309    if drc.ny < 2 then
310     -- report_xtable("set width, old: ht=%p, dp=%p",heights[r],depths[r])
311     -- report_xtable("set width, new: ht=%p, dp=%p",height,depth)
312        if height > heights[r] then
313            heights[r] = height
314        end
315        if depth > depths[r] then
316            depths[r] = depth
317        end
318    end
319    --
320    drc.wd = width
321    drc.ht = height
322    drc.dp = depth
323    --
324    local dimensionstate = texgetcount("frameddimensionstate")
325    local fixedcolumns = data.fixedcolumns
326    local fixedrows = data.fixedrows
327    if dimensionstate == 1 then
328        if cspan > 1 then
329            -- ignore width
330        elseif width > fixedcolumns[c] then -- how about a span here?
331            fixedcolumns[c] = width
332        end
333    elseif dimensionstate == 2 then
334        fixedrows[r]    = height
335    elseif dimensionstate == 3 then
336        fixedrows[r]    = height -- width
337        fixedcolumns[c] = width -- height
338    elseif width <= data.criterium_h and height >= data.criterium_v then
339        -- somewhat tricky branch
340        if width > fixedcolumns[c] then -- how about a span here?
341            -- maybe an image, so let's fix
342             fixedcolumns[c] = width
343        end
344    end
345--
346-- -- this fails so not good enough predictor
347--
348-- -- \startxtable
349-- -- \startxrow
350-- --     \startxcell knuth        \stopxcell
351-- --     \startxcell \input knuth \stopxcell
352-- -- \stopxrow
353--
354--     else
355--         local o = data.options[r][c]
356--         if o and o[v_auto] then -- new per 5/5/2014 - removed per 15/07/2014
357--             data.autowidths[c] = true
358--         else
359--            -- no dimensions are set in the cell
360--            if width <= data.criterium_h and height >= data.criterium_v then
361--                -- somewhat tricky branch
362--                if width > fixedcolumns[c] then -- how about a span here?
363--                    -- maybe an image, so let's fix
364--                     fixedcolumns[c] = width
365--                end
366--             else
367--                 -- safeguard as it could be text that can be recalculated
368--                 -- and the previous branch could have happened in a previous
369--                 -- row and then forces a wrong one-liner in a multiliner
370--                 if width > fixedcolumns[c] then
371--                     data.autowidths[c] = true -- new per 5/5/2014 - removed per 15/07/2014
372--                 end
373--             end
374--         end
375--     end
376--
377    --
378    drc.dimensionstate = dimensionstate
379    --
380    local nx = drc.nx
381    local ny = drc.ny
382    if nx > 1 or ny > 1 then
383     -- local spans = data.spans -- not used
384        local self = true
385        for y=1,ny do
386            for x=1,nx do
387                if self then
388                    self = false
389                else
390                    local ry = r + y - 1
391                    local cx = c + x - 1
392                 -- if y > 1 then
393                 --     spans[ry] = true -- not used
394                 -- end
395                    rows[ry][cx].span = true
396                end
397            end
398        end
399        c = c + nx - 1
400    end
401    if c > data.nofcolumns then
402        data.nofcolumns = c
403    end
404    data.currentcolumn = c
405end
406
407function xtables.initialize_reflow_height()
408    local r = data.currentrow
409    local c = data.currentcolumn + 1
410    local rows = data.rows
411    local row = rows[r]
412    while row[c].span do -- can also be previous row ones
413        c = c + 1
414    end
415    data.currentcolumn = c
416    local widths = data.widths
417    local w = widths[c]
418    local drc = row[c]
419    for x=1,drc.nx-1 do
420        w = w + widths[c+x]
421    end
422    texsetdimen("d_tabl_x_width",w)
423    local dimensionstate = drc.dimensionstate or 0
424    if dimensionstate == 1 or dimensionstate == 3 then
425        -- width was fixed so height is known
426        texsetcount("c_tabl_x_skip_mode",1)
427    elseif dimensionstate == 2 then
428        -- height is enforced
429        texsetcount("c_tabl_x_skip_mode",1)
430    elseif data.autowidths[c] then
431        -- width has changed so we need to recalculate the height
432        texsetcount("c_tabl_x_skip_mode",0)
433    elseif data.fixedcolumns[c] then
434        texsetcount("c_tabl_x_skip_mode",0) -- new
435    else
436        texsetcount("c_tabl_x_skip_mode",1)
437    end
438end
439
440function xtables.set_reflow_height()
441    local r = data.currentrow
442    local c = data.currentcolumn
443    local rows = data.rows
444    local row = rows[r]
445 -- while row[c].span do -- we could adapt drc.nx instead
446 --     c = c + 1
447 -- end
448    local tb  = getbox("b_tabl_x")
449    local drc = row[c]
450    --
451    local width, height, depth = getwhd(tb)
452    --
453    if drc.ny < 2 then
454        if data.fixedrows[r] == 0 then --  and drc.dimensionstate < 2
455            if drc.ht + drc.dp <= height + depth then -- new per 2017-12-15
456                local heights = data.heights
457                local depths  = data.depths
458             -- report_xtable("set height, old: ht=%p, dp=%p",heights[r],depths[r])
459             -- report_xtable("set height, new: ht=%p, dp=%p",height,depth)
460                if height > heights[r] then
461                    heights[r] = height
462                end
463                if depth > depths[r] then
464                    depths[r] = depth
465                end
466            end
467        end
468    end
469    --
470    drc.wd = width
471    drc.ht = height
472    drc.dp = depth
473    --
474 -- c = c + drc.nx - 1
475 -- data.currentcolumn = c
476end
477
478function xtables.initialize_construct()
479    local r = data.currentrow
480    local c = data.currentcolumn + 1
481    local settings = data.settings
482    local rows = data.rows
483    local row = rows[r]
484    while row[c].span do -- can also be previous row ones
485        c = c + 1
486    end
487    data.currentcolumn = c
488    local widths    = data.widths
489    local heights   = data.heights
490    local depths    = data.depths
491    local distances = data.distances
492    --
493    local drc = row[c]
494    local wd  = drc.wd
495    local ht  = drc.ht
496    local dp  = drc.dp
497    local nx  = drc.nx - 1
498    local ny  = drc.ny - 1
499    --
500    local width  = widths[c]
501    local height = heights[r]
502    local depth  = depths[r] -- problem: can be the depth of a one liner
503    --
504    local total  = height + depth
505    --
506    if nx > 0 then
507        for x=1,nx do
508            width = width + widths[c+x] + distances[c+x-1]
509        end
510        local distance = settings.columndistance
511        if distance ~= 0 then
512            width = width + nx * distance
513        end
514    end
515    --
516    if ny > 0 then
517        for y=1,ny do
518            local nxt = r + y
519            total = total + heights[nxt] + depths[nxt]
520        end
521        local distance = settings.rowdistance
522        if distance ~= 0 then
523            total = total + ny * distance
524        end
525    end
526    --
527    texsetdimen("d_tabl_x_width",width)
528    texsetdimen("d_tabl_x_height",total)
529    texsetdimen("d_tabl_x_depth",0) -- for now
530end
531
532function xtables.set_construct()
533    local r = data.currentrow
534    local c = data.currentcolumn
535    local rows = data.rows
536    local row = rows[r]
537 -- while row[c].span do -- can also be previous row ones
538 --     c = c + 1
539 -- end
540    local drc = row[c]
541    -- this will change as soon as in luatex we can reset a box list without freeing
542    drc.list = takebox("b_tabl_x")
543 -- c = c + drc.nx - 1
544 -- data.currentcolumn = c
545end
546
547local function showwidths(where,widths,autowidths)
548    local result = { }
549    for i=1,#widths do
550        result[#result+1] = format("%12s%s",points(widths[i]),autowidths[i] and "*" or " ")
551    end
552    return report_xtable("%s widths: %s",where,concat(result," "))
553end
554
555function xtables.reflow_width()
556    local nofrows = data.nofrows
557    local nofcolumns = data.nofcolumns
558    local rows = data.rows
559    for r=1,nofrows do
560        local row = rows[r]
561        for c=1,nofcolumns do
562            local drc = row[c]
563            if drc.list then
564             -- flushnodelist(drc.list)
565                drc.list = false
566            end
567        end
568    end
569    -- spread
570    local settings = data.settings
571    local options = settings.options
572    local maxwidth = settings.maxwidth
573    -- calculate width
574    local widths = data.widths
575    local heights = data.heights
576    local depths = data.depths
577    local distances = data.distances
578    local autowidths = data.autowidths
579    local fixedcolumns = data.fixedcolumns
580    local frozencolumns = data.frozencolumns
581    local width = 0
582    local distance = 0
583    local nofwide = 0
584    local widetotal = 0
585    local available = settings.textwidth - settings.leftmargindistance - settings.rightmargindistance
586    if trace_xtable then
587        showwidths("stage 1",widths,autowidths)
588    end
589    local noffrozen = 0
590    -- here we can also check spans
591    if options[v_max] then
592        for c=1,nofcolumns do
593            width = width + widths[c]
594            if width > maxwidth then
595                autowidths[c] = true
596                nofwide = nofwide + 1
597                widetotal = widetotal + widths[c]
598            end
599            if c < nofcolumns then
600                distance = distance + distances[c]
601            end
602            if frozencolumns[c] then
603                noffrozen = noffrozen + 1 -- brr, should be nx or so
604            end
605        end
606    else
607        for c=1,nofcolumns do -- also keep track of forced
608            local fixedwidth = fixedcolumns[c]
609            if fixedwidth > 0 then
610                widths[c] = fixedwidth
611                width = width + fixedwidth
612            else
613                local wc = widths[c]
614                width = width + wc
615             -- if width > maxwidth then
616                if wc > maxwidth then -- per 2015-08-09
617                    autowidths[c] = true
618                    nofwide = nofwide + 1
619                    widetotal = widetotal + wc
620                end
621            end
622            if c < nofcolumns then
623                distance = distance + distances[c]
624            end
625            if frozencolumns[c] then
626                noffrozen = noffrozen + 1 -- brr, should be nx or so
627            end
628        end
629    end
630    if trace_xtable then
631        showwidths("stage 2",widths,autowidths)
632    end
633    local delta = available - width - distance - (nofcolumns-1) * settings.columndistance
634    if delta == 0 then
635        -- nothing to be done
636        if trace_xtable then
637            report_xtable("perfect fit")
638        end
639    elseif delta > 0 then
640        -- we can distribute some
641        if not options[v_stretch] then
642            -- not needed
643            if trace_xtable then
644                report_xtable("too wide but no stretch, delta %p",delta)
645            end
646        elseif options[v_width] then
647            local factor = delta / width
648            if trace_xtable then
649                report_xtable("proportional stretch, delta %p, width %p, factor %a",delta,width,factor)
650            end
651            for c=1,nofcolumns do
652                widths[c] = widths[c] + factor * widths[c]
653            end
654        else
655            -- frozen -> a column with option=fixed will not stretch
656            local extra = delta / (nofcolumns - noffrozen)
657            if trace_xtable then
658                report_xtable("normal stretch, delta %p, extra %p",delta,extra)
659            end
660            for c=1,nofcolumns do
661                if not frozencolumns[c] then
662                    widths[c] = widths[c] + extra
663                end
664            end
665        end
666    elseif nofwide > 0 then
667        while true do
668            done = false
669            local available = (widetotal + delta) / nofwide
670            if trace_xtable then
671                report_xtable("shrink check, total %p, delta %p, columns %s, fixed %p",widetotal,delta,nofwide,available)
672            end
673            for c=1,nofcolumns do
674                if autowidths[c] and available >= widths[c] then
675                    autowidths[c] = nil
676                    nofwide = nofwide - 1
677                    widetotal = widetotal - widths[c]
678                    done = true
679                end
680            end
681            if not done then
682                break
683            end
684        end
685        -- maybe also options[v_width] here but tricky as width does not say
686        -- much about amount
687        if options[v_width] then -- not that much (we could have a clever vpack loop balancing .. no fun)
688            local factor = (widetotal + delta) / width
689            if trace_xtable then
690                report_xtable("proportional shrink used, total %p, delta %p, columns %s, factor %s",widetotal,delta,nofwide,factor)
691            end
692            for c=1,nofcolumns do
693                if autowidths[c] then
694                    widths[c] = factor * widths[c]
695                end
696            end
697        else
698            local available = (widetotal + delta) / nofwide
699            if trace_xtable then
700                report_xtable("normal shrink used, total %p, delta %p, columns %s, fixed %p",widetotal,delta,nofwide,available)
701            end
702            for c=1,nofcolumns do
703                if autowidths[c] then
704                    widths[c] = available
705                end
706            end
707        end
708    end
709    if trace_xtable then
710        showwidths("stage 3",widths,autowidths)
711    end
712    --
713    data.currentrow = 0
714    data.currentcolumn = 0
715end
716
717function xtables.reflow_height()
718    data.currentrow = 0
719    data.currentcolumn = 0
720    local settings = data.settings
721    --
722    -- analyze ny
723    --
724    local nofrows    = data.nofrows
725    local nofcolumns = data.nofcolumns
726    local widths     = data.widths
727    local heights    = data.heights
728    local depths     = data.depths
729    --
730    for r=1,nofrows do
731        for c=1,nofcolumns do
732            local drc = data.rows[r][c]
733            if drc then
734                local ny = drc.ny
735                if ny > 1 then
736                    local height = heights[r]
737                    local depth  = depths[r]
738                    local total  = height + depth
739                    local htdp   = drc.ht + drc.dp
740                    for y=1,ny-1 do
741                        local nxt = r + y
742                        total = total + heights[nxt] + depths[nxt]
743                    end
744                    local delta = htdp - total
745                    if delta > 0 then
746                        delta = delta / ny
747                        for y=0,ny-1 do
748                            local nxt = r + y
749                            heights[nxt] = heights[nxt] + delta
750                        end
751                    end
752                end
753            end
754        end
755    end
756    --
757    if settings.options[v_height] then
758        local totalheight = 0
759        local totaldepth = 0
760        for i=1,nofrows do
761            totalheight = totalheight + heights[i]
762            totalheight = totalheight + depths [i]
763        end
764        local total = totalheight + totaldepth
765        local leftover = settings.textheight - total
766        if leftover > 0 then
767            local leftheight = (totalheight / total) * leftover / #heights
768            local leftdepth  = (totaldepth  / total) * leftover / #depths
769            for i=1,nofrows do
770                heights[i] = heights[i] + leftheight
771                depths [i] = depths [i] + leftdepth
772            end
773        end
774    end
775end
776
777local function showspans(data)
778    local rows = data.rows
779    local modes = data.modes
780    local nofcolumns = data.nofcolumns
781    local nofrows = data.nofrows
782    for r=1,nofrows do
783        local line = { }
784        local row = rows[r]
785        for c=1,nofcolumns do
786            local cell =row[c]
787            if cell.list then
788                line[#line+1] = "list"
789            elseif cell.span then
790                line[#line+1] = "span"
791            else
792                line[#line+1] = "none"
793            end
794        end
795        report_xtable("%3d : %s : % t",r,namedmodes[modes[r]] or "----",line)
796    end
797end
798
799function xtables.construct()
800    local rows = data.rows
801    local heights = data.heights
802    local depths = data.depths
803    local widths = data.widths
804 -- local spans = data.spans
805    local distances = data.distances
806    local modes = data.modes
807    local settings = data.settings
808    local nofcolumns = data.nofcolumns
809    local nofrows = data.nofrows
810    local columndistance = settings.columndistance
811    local rowdistance = settings.rowdistance
812    local leftmargindistance = settings.leftmargindistance
813    local rightmargindistance = settings.rightmargindistance
814    local rowproperties = data.rowproperties
815    -- ranges can be mixes so we collect
816
817    if trace_xtable then
818        showspans(data)
819    end
820
821    local ranges = {
822        [head_mode] = { },
823        [foot_mode] = { },
824        [more_mode] = { },
825        [body_mode] = { },
826    }
827    for r=1,nofrows do
828        local m = modes[r]
829        if m == 0 then
830            m = body_mode
831        end
832        local range = ranges[m]
833        range[#range+1] = r
834    end
835    -- todo: hook in the splitter ... the splitter can ask for a chunk of
836    -- a certain size ... no longer a split memory issue then and header
837    -- footer then has to happen here too .. target height
838    local function packaged_column(r)
839        local row = rows[r]
840        local start = nil
841        local stop = nil
842        if leftmargindistance > 0 then
843            start = new_kern(leftmargindistance)
844            stop = start
845        end
846        local hasspan = false
847        for c=1,nofcolumns do
848            local drc = row[c]
849            if not hasspan then
850                hasspan = drc.span
851            end
852            local list = drc.list
853            if list then
854                local w, h, d = getwhd(list)
855                setshift(list,h+d)
856             -- list = hpacknodelist(list) -- is somehow needed
857             -- setwhd(list,0,0,0)
858                -- faster:
859                local h = new_hlist(list)
860                list = h
861                --
862                if start then
863                    setlink(stop,list)
864                else
865                    start = list
866                end
867                stop = list
868            end
869            local step = widths[c]
870            if c < nofcolumns then
871                step = step + columndistance + distances[c]
872            end
873            local kern = new_kern(step)
874            if stop then
875                setlink(stop,kern)
876            else -- can be first spanning next row (ny=...)
877                start = kern
878            end
879            stop = kern
880        end
881        if start then
882            if rightmargindistance > 0 then
883                local kern = new_kern(rightmargindistance)
884                setlink(stop,kern)
885             -- stop = kern
886            end
887            return start, heights[r] + depths[r], hasspan
888        end
889    end
890    local function collect_range(range)
891        local result, nofr = { }, 0
892        local nofrange = #range
893        for i=1,#range do
894            local r = range[i]
895         -- local row = rows[r]
896            local list, size, hasspan = packaged_column(r)
897            if list then
898                if hasspan and nofr > 0 then
899                    result[nofr][4] = true
900                end
901                nofr = nofr + 1
902                local rp = rowproperties[r]
903                -- we have a direction issue here but hpacknodelist(list,0,"exactly") cannot be used
904                -- due to the fact that we need the width
905                local hbox = hpacknodelist(list)
906                setdirection(hbox,lefttoright_code)
907                result[nofr] = {
908                    hbox,
909                    size,
910                    i < nofrange and rowdistance > 0 and rowdistance or false, -- might move
911                    false,
912                    rp or false,
913                }
914            end
915        end
916        if nofr > 0 then
917            -- the [5] slot gets the after break
918            result[1]   [5] = false
919            result[nofr][5] = false
920            for i=2,nofr-1 do
921                local r = result[i][5]
922                if r == v_both or r == v_before then
923                    result[i-1][5] = true
924                elseif r == v_after then
925                    result[i][5] = true
926                end
927            end
928        end
929        return result
930    end
931    local body = collect_range(ranges[body_mode])
932    data.results = {
933        [head_mode] = collect_range(ranges[head_mode]),
934        [foot_mode] = collect_range(ranges[foot_mode]),
935        [more_mode] = collect_range(ranges[more_mode]),
936        [body_mode] = body,
937    }
938    if #body == 0 then
939        texsetcount("global","c_tabl_x_state",0)
940        texsetdimen("global","d_tabl_x_final_width",0)
941    else
942        texsetcount("global","c_tabl_x_state",1)
943        texsetdimen("global","d_tabl_x_final_width",getwidth(body[1][1]))
944    end
945end
946
947-- todo: join as that is as efficient as fushing multiple
948
949local function inject(row,copy,package)
950    local list = row[1]
951    if copy then
952        row[1] = copynodelist(list)
953    end
954    if package then
955        ctx_beginvbox()
956        ctxnode(tonode(list))
957        ctxnode(tonode(new_kern(row[2])))
958        ctx_endvbox()
959        ctx_nointerlineskip() -- figure out a better way
960        if row[4] then
961            -- nothing as we have a span
962        elseif row[5] then
963            if row[3] then
964                ctx_blank { v_samepage, row[3] .. "sp" }
965            else
966                ctx_blank { v_samepage }
967            end
968        elseif row[3] then
969            ctx_blank { row[3] .. "sp" } -- why blank ?
970        else
971            ctxnode(tonode(new_glue(0)))
972        end
973    else
974        ctxnode(tonode(list))
975        ctxnode(tonode(new_kern(row[2])))
976        if row[3] then
977            ctxnode(tonode(new_glue(row[3])))
978        end
979    end
980end
981
982local function total(row,distance)
983    local n = #row > 0 and rowdistance or 0
984    for i=1,#row do
985        local ri = row[i]
986        n = n + ri[2] + (ri[3] or 0)
987    end
988    return n
989end
990
991-- local function append(list,what)
992--     for i=1,#what do
993--         local l = what[i]
994--         list[#list+1] = l[1]
995--         local k = l[2] + (l[3] or 0)
996--         if k ~= 0 then
997--             list[#list+1] = new_kern(k)
998--         end
999--     end
1000-- end
1001
1002local function spanheight(body,i)
1003    local height, n = 0, 1
1004    while true do
1005        local bi = body[i]
1006        if bi then
1007            height = height + bi[2] + (bi[3] or 0)
1008            if bi[4] then
1009                n = n + 1
1010                i = i + 1
1011            else
1012                break
1013            end
1014        else
1015            break
1016        end
1017    end
1018    return height, n
1019end
1020
1021function xtables.flush(directives) -- todo split by size / no inbetween then ..  glue list kern blank
1022    local height       = directives.height
1023    local method       = directives.method or v_normal
1024    local settings     = data.settings
1025    local results      = data.results
1026    local rowdistance  = settings.rowdistance
1027    local head         = results[head_mode]
1028    local foot         = results[foot_mode]
1029    local more         = results[more_mode]
1030    local body         = results[body_mode]
1031    local repeatheader = settings.header == v_repeat
1032    local repeatfooter = settings.footer == v_repeat
1033    if height and height > 0 then
1034        ctx_beginvbox()
1035        local bodystart = data.bodystart or 1
1036        local bodystop  = data.bodystop or #body
1037        if bodystart > 0 and bodystart <= bodystop then
1038            local bodysize = height
1039            local footsize = total(foot,rowdistance)
1040            local headsize = total(head,rowdistance)
1041            local moresize = total(more,rowdistance)
1042            local firstsize, firstspans = spanheight(body,bodystart)
1043            if bodystart == 1 then -- first chunk gets head
1044                bodysize = bodysize - headsize - footsize
1045                if headsize > 0 and bodysize >= firstsize then
1046                    for i=1,#head do
1047                        inject(head[i],repeatheader)
1048                    end
1049                    if rowdistance > 0 then
1050                        ctxnode(tonode(new_glue(rowdistance)))
1051                    end
1052                    if not repeatheader then
1053                        results[head_mode] = { }
1054                    end
1055                end
1056            elseif moresize > 0 then -- following chunk gets next
1057                bodysize = bodysize - footsize - moresize
1058                if bodysize >= firstsize then
1059                    for i=1,#more do
1060                        inject(more[i],true)
1061                    end
1062                    if rowdistance > 0 then
1063                        ctxnode(tonode(new_glue(rowdistance)))
1064                    end
1065                end
1066            elseif headsize > 0 and repeatheader then -- following chunk gets head
1067                bodysize = bodysize - footsize - headsize
1068                if bodysize >= firstsize then
1069                    for i=1,#head do
1070                        inject(head[i],true)
1071                    end
1072                    if rowdistance > 0 then
1073                        ctxnode(tonode(new_glue(rowdistance)))
1074                    end
1075                end
1076            else -- following chunk gets nothing
1077                bodysize = bodysize - footsize
1078            end
1079            if bodysize >= firstsize then
1080                local i = bodystart
1081                while i <= bodystop do -- room for improvement
1082                    local total, spans = spanheight(body,i)
1083                    local bs = bodysize - total
1084                    if bs > 0 then
1085                        bodysize = bs
1086                        for s=1,spans do
1087                            inject(body[i])
1088                            body[i] = nil
1089                            i = i + 1
1090                        end
1091                        bodystart = i
1092                    else
1093                        break
1094                    end
1095                end
1096                if bodystart > bodystop then
1097                    -- all is flushed and footer fits
1098                    if footsize > 0 then
1099                        if rowdistance > 0 then
1100                            ctxnode(tonode(new_glue(rowdistance)))
1101                        end
1102                        for i=1,#foot do
1103                            inject(foot[i])
1104                        end
1105                        results[foot_mode] = { }
1106                    end
1107                    results[body_mode] = { }
1108                    texsetcount("global","c_tabl_x_state",0)
1109                else
1110                    -- some is left so footer is delayed
1111                    -- todo: try to flush a few more lines
1112                    if repeatfooter and footsize > 0 then
1113                        if rowdistance > 0 then
1114                            ctxnode(tonode(new_glue(rowdistance)))
1115                        end
1116                        for i=1,#foot do
1117                            inject(foot[i],true)
1118                        end
1119                    else
1120                        -- todo: try to fit more of body
1121                    end
1122                    texsetcount("global","c_tabl_x_state",2)
1123                end
1124            else
1125                if firstsize > height then
1126                    -- get rid of the too large cell
1127                    for s=1,firstspans do
1128                        inject(body[bodystart])
1129                        body[bodystart] = nil
1130                        bodystart = bodystart + 1
1131                    end
1132                end
1133                texsetcount("global","c_tabl_x_state",2) -- 1
1134            end
1135        else
1136            texsetcount("global","c_tabl_x_state",0)
1137        end
1138        data.bodystart = bodystart
1139        data.bodystop = bodystop
1140        ctx_endvbox()
1141    else
1142        if method == variables.split then
1143            -- maybe also a non float mode with header/footer repeat although
1144            -- we can also use a float without caption
1145            for i=1,#head do
1146                inject(head[i],false,true)
1147            end
1148            if #head > 0 and rowdistance > 0 then
1149                ctx_blank { rowdistance .. "sp" }
1150            end
1151            for i=1,#body do
1152                inject(body[i],false,true)
1153            end
1154            if #foot > 0 and rowdistance > 0 then
1155                ctx_blank { rowdistance .. "sp" }
1156            end
1157            for i=1,#foot do
1158                inject(foot[i],false,true)
1159            end
1160        else -- normal
1161            ctx_beginvbox()
1162            for i=1,#head do
1163                inject(head[i])
1164            end
1165            if #head > 0 and rowdistance > 0 then
1166                ctxnode(tonode(new_glue(rowdistance)))
1167            end
1168            for i=1,#body do
1169                inject(body[i])
1170            end
1171            if #foot > 0 and rowdistance > 0 then
1172                ctxnode(tonode(new_glue(rowdistance)))
1173            end
1174            for i=1,#foot do
1175                inject(foot[i])
1176            end
1177            ctx_endvbox()
1178        end
1179        results[head_mode] = { }
1180        results[body_mode] = { }
1181        results[foot_mode] = { }
1182        texsetcount("global","c_tabl_x_state",0)
1183    end
1184end
1185
1186function xtables.cleanup()
1187    for mode, result in next, data.results do
1188        for _, r in next, result do
1189            flushnodelist(r[1])
1190        end
1191    end
1192
1193    -- local rows = data.rows
1194    -- for i=1,#rows do
1195    --     local row = rows[i]
1196    --     for i=1,#row do
1197    --         local cell = row[i]
1198    --         local list = cell.list
1199    --         if list then
1200    --             cell.width, cell.height, cell.depth = getwhd(list)
1201    --             cell.list = true
1202    --         end
1203    --     end
1204    -- end
1205    -- data.result = nil
1206
1207    data = table.remove(stack)
1208end
1209
1210function xtables.next_row(specification)
1211    local r = data.currentrow + 1
1212    data.modes[r] = texgetcount("c_tabl_x_mode")
1213    data.currentrow = r
1214    data.currentcolumn = 0
1215    data.rowproperties[r] = specification
1216end
1217
1218function xtables.finish_row()
1219    local c = data.currentcolumn
1220    local r = data.currentrow
1221    local d = data.rows[r][c]
1222    local n = data.nofcolumns - c
1223    if d then
1224        local nx = d.nx
1225        if nx > 0 then
1226            n = n - nx + 1
1227        end
1228    end
1229    if n > 0 then
1230        for i=1,n do
1231            ctx_dummyxcell()
1232        end
1233    end
1234end
1235
1236-- eventually we might only have commands
1237
1238implement {
1239    name      = "x_table_create",
1240    actions   = xtables.create,
1241    arguments = {
1242        {
1243            { "option" },
1244            { "textwidth", "dimen" },
1245            { "textheight", "dimen" },
1246            { "maxwidth", "dimen" },
1247            { "lineheight", "dimen" },
1248            { "columndistance", "dimen" },
1249            { "leftmargindistance", "dimen" },
1250            { "rightmargindistance", "dimen" },
1251            { "rowdistance", "dimen" },
1252            { "header" },
1253            { "footer" },
1254        }
1255    }
1256}
1257
1258implement {
1259    name      = "x_table_flush",
1260    actions   = xtables.flush,
1261    arguments = {
1262        {
1263            { "method" },
1264            { "height", "dimen" }
1265        }
1266    }
1267}
1268
1269implement { name = "x_table_reflow_width",              actions = xtables.reflow_width  }
1270implement { name = "x_table_reflow_height",             actions = xtables.reflow_height }
1271implement { name = "x_table_construct",                 actions = xtables.construct }
1272implement { name = "x_table_cleanup",                   actions = xtables.cleanup }
1273implement { name = "x_table_next_row",                  actions = xtables.next_row }
1274implement { name = "x_table_next_row_option",           actions = xtables.next_row, arguments = "string" }
1275implement { name = "x_table_finish_row",                actions = xtables.finish_row }
1276implement { name = "x_table_init_reflow_width",         actions = xtables.initialize_reflow_width  }
1277implement { name = "x_table_init_reflow_height",        actions = xtables.initialize_reflow_height }
1278implement { name = "x_table_init_reflow_width_option",  actions = xtables.initialize_reflow_width,  arguments = "string" }
1279implement { name = "x_table_init_reflow_height_option", actions = xtables.initialize_reflow_height, arguments = "string" }
1280implement { name = "x_table_init_construct",            actions = xtables.initialize_construct }
1281implement { name = "x_table_set_reflow_width",          actions = xtables.set_reflow_width }
1282implement { name = "x_table_set_reflow_height",         actions = xtables.set_reflow_height }
1283implement { name = "x_table_set_construct",             actions = xtables.set_construct }
1284implement { name = "x_table_r",                         actions = function() context(data.currentrow    or 0) end }
1285implement { name = "x_table_c",                         actions = function() context(data.currentcolumn or 0) end }
1286
1287-- experiment:
1288
1289do
1290
1291    local context         = context
1292    local ctxcore         = context.core
1293
1294    local startxtable     = ctxcore.startxtable
1295    local stopxtable      = ctxcore.stopxtable
1296
1297    local startcollecting = context.startcollecting
1298    local stopcollecting  = context.stopcollecting
1299
1300    function ctxcore.startxtable(...)
1301        startcollecting()
1302        startxtable(...)
1303    end
1304
1305    function ctxcore.stopxtable()
1306        stopxtable()
1307        stopcollecting()
1308    end
1309
1310end
1311