x-cals.lua /size: 7950 b    last modification: 2020-07-01 14:35
1if not modules then modules = { } end modules ['x-cals'] = {
2    version   = 1.001,
3    comment   = "companion to x-cals.mkiv",
4    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5    copyright = "PRAGMA ADE / ConTeXt Development Team",
6    license   = "see context related readme files"
7}
8
9local next, type = next, type
10local format, lower = string.format, string.lower
11local xmlcprint, xmlcollected, xmlelements = xml.cprint, xml.collected, xml.elements
12local n_todimen, s_todimen = number.todimen, string.todimen
13
14-- there is room for speedups as well as cleanup (using context functions)
15
16local cals      = { }
17moduledata.cals = cals
18lxml.mathml     = cals -- for the moment
19
20cals.ignore_widths  = false
21cals.shrink_widths  = false
22cals.stretch_widths = false
23
24-- the following flags only apply to columns that have a specified width
25--
26-- proportional : shrink or stretch proportionally to the width
27-- equal        : shrink or stretch equaly distributed
28-- n < 1        : shrink or stretch proportionally to the width but multiplied by n
29--
30-- more clever things, e.g. the same but applied to unspecified widths
31-- has to happen at the core-ntb level (todo)
32
33local halignments = {
34    left    = "flushleft",
35    right   = "flushright",
36    center  = "middle",
37    centre  = "middle",
38    justify = "normal",
39}
40
41local valignments = {
42    top    = "high",
43    bottom = "low",
44    middle = "lohi",
45}
46
47local function adapt(widths,b,w,delta,sum,n,what)
48    if b == "equal" then
49        delta = delta/n
50        for k, v in next, w do
51            widths[k] = n_todimen(v - delta)
52        end
53    elseif b == "proportional" then
54        delta = delta/sum
55        for k, v in next, w do
56            widths[k] = n_todimen(v - v*delta)
57        end
58    elseif type(b) == "number" and b < 1 then
59        delta = b*delta/sum
60        for k, v in next, w do
61            widths[k] = n_todimen(v - v*delta)
62        end
63    end
64end
65
66local function getspecs(root, pattern, names, widths)
67    -- here, but actually we need this in core-ntb.tex
68    -- but ideally we need an mkiv enhanced core-ntb.tex
69    local ignore_widths  = cals.ignore_widths
70--     local shrink_widths  = at.option == "shrink"  or cals.shrink_widths
71--     local stretch_widths = at.option == "stretch" or cals.stretch_widths
72    local shrink_widths  = cals.shrink_widths
73    local stretch_widths = cals.stretch_widths
74    for e in xmlcollected(root,pattern) do
75        local at = e.at
76        local column = at.colnum
77        if column then
78            if not ignore_widths then
79                local width = at.colwidth
80                if width then
81                    widths[tonumber(column)] = lower(width)
82                end
83            end
84            local name = at.colname
85            if name then
86                names[name] = tonumber(column)
87            end
88        end
89    end
90    if ignore_width then
91        -- forget about it
92    elseif shrink_widths or stretch_widths then
93        local sum, n, w = 0, 0, { }
94        for _, v in next, widths do
95            n = n + 1
96            v = (type(v) == "string" and s_todimen(v)) or v
97            if v then
98                w[n] = v
99                sum = sum + v
100            end
101        end
102        local hsize = tex.hsize
103        if type(hsize) == "string" then
104            hsize = s_todimen(hsize)
105        end
106        local delta = sum - hsize
107        if shrink_widths and delta > 0 then
108            adapt(widths,shrink_widths,w,delta,sum,n,"shrink")
109        elseif stretch_widths and delta < 0 then
110            adapt(widths,stretch_widths,w,delta,sum,n,"stretch")
111        end
112    end
113end
114
115local function getspans(root, pattern, names, spans)
116    for e in xmlcollected(root,pattern) do
117        local at = e.at
118        local name, namest, nameend = at.colname, names[at.namest or "?"], names[at.nameend or "?"]
119        if name and namest and nameend then
120            spans[name] = tonumber(nameend) - tonumber(namest) + 1
121        end
122    end
123end
124
125local bTR, eTR, bTD, eTD = context.bTR, context.eTR, context.bTD, context.eTD
126
127function cals.table(root,namespace)
128
129    local prefix = (namespace or "cals") .. ":"
130
131    local prefix = namespace and namespace ~= "" and (namespace .. ":") or ""
132    local p = "/" .. prefix
133
134    local tgroupspec = p .. "tgroup"
135    local colspec    = p .. "colspec"
136    local spanspec   = p .. "spanspec"
137    local hcolspec   = p .. "thead" .. p .. "colspec"
138    local bcolspec   = p .. "tbody" .. p .. "colspec"
139    local fcolspec   = p .. "tfoot" .. p .. "colspec"
140    local entryspec  = p .. "entry" .. "|" .. prefix .. "entrytbl" -- shouldn't that be p ?
141    local hrowspec   = p .. "thead" .. p .. "row"
142    local browspec   = p .. "tbody" .. p .. "row"
143    local frowspec   = p .. "tfoot" .. p .. "row"
144
145    local function tablepart(root, xcolspec, xrowspec, before, after) -- move this one outside
146        before()
147        local at = root.at
148        local pphalign, ppvalign = at.align, at.valign
149        local names, widths, spans = { }, { }, { }
150        getspecs(root, colspec , names, widths)
151        getspecs(root, xcolspec, names, widths)
152        getspans(root, spanspec, names, spans)
153        for r, d, k in xmlelements(root,xrowspec) do
154            bTR()
155            local dk = d[k]
156            local at = dk.at
157            local phalign, pvalign = at.align or pphalign, at.valign or ppvalign -- todo: __p__ test
158            local col = 1
159            for rr, dd, kk in xmlelements(dk,entryspec) do
160                local dk = dd[kk]
161                if dk.tg == "entrytbl" then
162                 -- bTD(function() cals.table(dk) end)
163                    bTD()
164                    context("{")
165                    cals.table(dk)
166                    context("}")
167                    eTD()
168                    col = col + 1
169                else
170                    local at = dk.at
171                    local b, e, s, m = names[at.namest or "?"], names[at.nameend or "?"], spans[at.spanname or "?"], at.morerows
172                    local halign, valign = at.align or phalign, at.valign or pvalign
173                    if b and e then
174                        s = e - b + 1
175                    end
176                    if halign then
177                        halign = halignments[halign]
178                    end
179                    if valign then
180                        valign = valignments[valign]
181                    end
182                    local width = widths[col]
183                    if s or m or halign or valign or width then -- currently only english interface !
184                        bTD {
185                            nx    = s or 1,
186                            ny    = (m or 0) + 1,
187                            align = format("{%s,%s}",halign or "flushleft",valign or "high"),
188                            width = width or "fit",
189                        }
190                    else
191                        bTD {
192                            align = "{flushleft,high}",
193                            width = "fit", -- else problems with vertical material
194                        }
195                    end
196                    xmlcprint(dk)
197                    eTD()
198                    col = col + (s or 1)
199                end
200            end
201            eTR()
202        end
203        after()
204    end
205
206    for tgroup in lxml.collected(root,tgroupspec) do
207        context.directsetup("cals:table:before")
208        lxml.directives.before(root,"cdx") -- "cals:table"
209        context.bgroup()
210        lxml.directives.setup(root,"cdx") -- "cals:table"
211        context.bTABLE()
212        tablepart(tgroup, hcolspec, hrowspec, context.bTABLEhead, context.eTABLEhead)
213        tablepart(tgroup, bcolspec, browspec, context.bTABLEbody, context.eTABLEbody)
214        tablepart(tgroup, fcolspec, frowspec, context.bTABLEfoot, context.eTABLEfoot)
215        context.eTABLE()
216        context.egroup()
217        lxml.directives.after(root,"cdx") -- "cals:table"
218        context.directsetup("cals:table:after")
219    end
220
221end
222