summaryrefslogtreecommitdiff
path: root/macros/luatex/generic/lutabulartools/lutabulartools.lua
diff options
context:
space:
mode:
Diffstat (limited to 'macros/luatex/generic/lutabulartools/lutabulartools.lua')
-rw-r--r--macros/luatex/generic/lutabulartools/lutabulartools.lua287
1 files changed, 287 insertions, 0 deletions
diff --git a/macros/luatex/generic/lutabulartools/lutabulartools.lua b/macros/luatex/generic/lutabulartools/lutabulartools.lua
new file mode 100644
index 0000000000..40f916e3bc
--- /dev/null
+++ b/macros/luatex/generic/lutabulartools/lutabulartools.lua
@@ -0,0 +1,287 @@
+--% Kale Ewasiuk (kalekje@gmail.com)
+--% 2021-10-13
+--%
+--% Copyright (C) 2021 Kale Ewasiuk
+--%
+--% Permission is hereby granted, free of charge, to any person obtaining a copy
+--% of this software and associated documentation files (the "Software"), to deal
+--% in the Software without restriction, including without limitation the rights
+--% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+--% copies of the Software, and to permit persons to whom the Software is
+--% furnished to do so, subject to the following conditions:
+--%
+--% The above copyright notice and this permission notice shall be included in
+--% all copies or substantial portions of the Software.
+--%
+--% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
+--% ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+--% TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+--% PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+--% SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
+--% ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+--% ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+--% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
+--% OR OTHER DEALINGS IN THE SOFTWARE.
+
+local ltt = {}
+
+--todo clean up the code, make some stuff local
+--todo maybe add the penlight lua table to latex tabular?
+
+--integrate current cell
+--todo check if S[] column cells work!!!
+--todo might be smarter to just append this to tabular commands and read argument
+
+-- Initialize counter variable
+ltt.NumTabCols = 0
+ltt.NumTabColsAbv = 0 -- num of tab columns if above 1
+ltt.TabColSpec = ''
+ltt.TabColSpecAbv = '' -- tab column spec if above 1
+ltt.CurTabColAbv = '' -- current tab column spec if above 1
+ltt.TabColNum = 0
+ltt.NumBkts = 0
+ltt.NumTabColsMX = 0
+
+
+--todo re=enable
+local glue_t, unset_t, tabskip_st = node.id'glue', node.id'unset'
+local tabskip_st = table.swapped(node.subtypes'glue').tabskip
+assert(tabskip_st)
+
+
+function ltt.get_TabColNum()
+ local nest
+ for i = tex.nest.ptr, 1, -1 do
+ local tail = tex.nest[i].tail
+ if tail.id == glue_t and tail.subtype == tabskip_st then
+ nest = tex.nest[i]
+ break
+ end
+ end
+ if nest then
+ local col = 1
+ for _, sub in node.traverse_id(unset_t, nest.head) do
+ col = col + sub + 1
+ end
+ ltt.TabColNum = col
+ else
+ ltt.TabColNum = 1
+ end
+ ltt.CurTabColAbv = ltt.TabColSpecAbv:sub(ltt.TabColNum,ltt.TabColNum)
+ ltt.NumTabColsMX = math.max(ltt.NumTabColsMX, ltt.TabColNum) -- a different way to calculate total cols
+ return ltt.TabColNum
+end
+
+
+function ltt.get_TabRowNum()
+ return tex.count['c@RowNumCnt'] -- access latex counter
+end
+
+-- The next function computes the number of columns from
+-- contents of string 'zz'
+function ltt.calc_NumTabCols( zz )
+ --help_wrt(zz, 'col spec OG')
+ -- -- NOT NEEDED: zz = zz:gsub ( "^.-(%b{}).-$", "%1" ) -- retain the first "{....}" substring
+ zz = zz:gsub ( "%*%s-{(%d-)}%s-(%b{})" , -- expand expressions such as "*{5}{l}" to "lllll"
+ function(y, z ) z = z:sub (2 , -2) return string.rep (z, y) end ) --
+ zz = zz:gsub ( "%b{}" , "" ) -- omit all stuff in curly braces and square
+ zz = zz:gsub ( "%b[]" , "" )
+ zz = zz:gsub ( "[@!|><%s%*\']" , "" ) -- some more characters to ignore
+ -- todo: any columns defined that break into more than one column should be expanded here
+ --help_wrt(zz, 'col spec CLEAN')
+ ltt.TabColSpec = zz -- stripped column spec with no {} or <
+ ltt.NumTabCols = string.len(ltt.TabColSpec)
+ if ltt.NumTabCols > 1 then
+ ltt.TabColSpecAbv = ltt.TabColSpec
+ ltt.NumTabColsAbv = ltt.NumTabCols
+ end
+end
+
+--todo fix square brackets after letters, expand multiple
+--calc_NumTabCols('ss')
+--calc_NumTabCols('*{6}{s}')
+--calc_NumTabCols('l*{6}{l}')
+--calc_NumTabCols('lll')
+--calc_NumTabCols('ll[]')
+--calc_NumTabCols('ll[]*{6}{l}')
+--calc_NumTabCols('*{6}{l}')
+--calc_NumTabCols('y*{6}{sq}x')
+--print(_TabColSpec.. '<---Column spec')
+
+--http://ctan.mirror.rafal.ca/macros/latex/contrib/multirow/multirow.pdf
+--http://ctan.mirror.colo-serv.net/macros/latex/contrib/makecell/makecell.pdf
+-- todo CONSIDER THIS
+-- https://tex.stackexchange.com/questions/331716/newline-in-multirow-environment
+
+
+
+--todo
+-- if p{} column, and multirow is 1, use {=} instead of {*}
+-- but note, makecell will not work. So you may want to skip it.multicolumn
+-- this case should be considered in this code.
+-- for example: \multirow{2}{=}
+
+function ltt.MagicCell(s0,spec,mcspec,pre,content)
+ --
+ local STR = ''
+ reset_bkt_cnt()
+
+ local v, h, r, c, mrowsym, skipmakecell = ltt.parse_MagicCell_spec(spec) -- get v/h align, number rows/columns
+
+ local mcspec = mcspec or ''
+
+ h, mcspec, c = ltt.get_HColSpec(h, mcspec, c) -- infer horizontal alignment, num columns
+
+ --help_wrt(_CurTabColAbv,'current column')
+ if s0 == _xTrue or (pl.List({'S', 'Q', 'L', 'R'}):contains(ltt.CurTabColAbv) -- special columns for SI
+ and c == '') then -- multicolumn cannot have {} around it todo test with siunitx, num rows, num columns
+ STR = STR .. '{' -- multirow and makcell must have {} around it S column is used
+ add_bkt_cnt()
+ end
+
+ if c ~= '' then
+ STR = STR .. "\\multicolumn{"..c.."}{"..mcspec.."}{"
+ add_bkt_cnt()
+ end
+
+ if r ~= '' then
+ STR = STR .."\\multirow["..v.."]{"..r.."}{"..mrowsym.."}{" -- optional arg here
+ add_bkt_cnt()
+ end
+
+ if not skipmakecell then
+ if pre ~= '' then
+ STR = STR.."\\renewcommand{\\cellset}{"..pre.."}"
+ end
+
+ STR = STR.."\\makecell[{"..v.."}{"..h.."}]{"
+ add_bkt_cnt()
+ else
+ content = content:gsub('\\\\', '\\newline')
+ end
+
+ STR = STR..content..close_bkt_cnt()
+ --Troubleshooting
+ --help_wrt(STR..' <<< magic cell string')
+ tex.sprint(STR)--tex print the STR
+end
+
+
+function ltt.parse_MagicCell_spec(spec)
+ local mrowsym = '*'
+ local skipmakecell = false
+ if string.find(spec, '=') then
+ spec = spec:gsub('=', '')
+ mrowsym = '='
+ skipmakecell = true
+ end
+
+ spec = spec:lower():gsub('%s','') -- take lower case and remove
+ local vh, rc = spec:gextract('%a') -- extract characters
+ local v = vh:gfirst({'t', 'm', 'b'}) or 't'
+ local h = vh:gfirst({'l', 'c', 'r'}) or ''
+ v = v:gsub('m', 'c')
+
+ local rc_ = (rc):split(',')
+ local c = rc_[1] or '' --num columns, width
+ local r = rc_[2] or '' --num rows, height
+ if c == '0' or c == '1' then c = '' end
+ if r == '0' or r == '1' then r = '' end
+
+ return v, h, r, c, mrowsym, skipmakecell
+end
+
+
+ltt.TabColMapping = { -- horizontal cell alignment that multicolumn should use if () or [hori] not passed to func
+ l = 'l',
+ c = 'c',
+ r = 'r',
+ p = 'p',
+ P = 'c',
+ X = 'l',
+ Y = 'c',
+ Z = 'l',
+ N = 'c',
+ L = 'l',
+ R = 'r',
+ C = 'c',
+}
+
+function ltt.get_HColSpec(h, mcspec, c) -- take horizontal alignment
+ -- c is num columns, h is horizontal alginment,
+ --Assumes _TabColNum was calculated previosly
+ ltt.get_TabColNum()
+ if c == '+' then -- fill row to end
+ c = tostring(ltt.NumTabColsAbv - ltt.TabColNum + 1)
+ end
+ if h == '' then -- if horizontal not provided, use declared column
+ h = ltt.TabColMapping[ltt.CurTabColAbv] or 'l'
+ end
+ if c ~= '' then -- only make new mcspec if column nums > 0
+ if mcspec == '' then -- and if no mcspec was passed
+ mcspec = h
+ if ltt.TabColNum == 1 then -- if first column, auto detect padding
+ mcspec = '@{}'..mcspec
+ end
+ if (ltt.TabColNum + tonumber(c) - 1) == ltt.NumTabColsAbv then -- if end on last column
+ mcspec = mcspec..'@{}'
+ end
+ else -- if mcspec if given, extract the alignment
+ ltt.calc_NumTabCols(mcspec)
+ h = ltt.TabColSpec -- get 1 character column spec from mcspec and override h
+ end
+ end
+ return h, mcspec, c
+end
+
+
+-- midrule stuff
+
+function ltt.get_midrule_col(s)
+ if string.find(s, '+') then
+ s = s:gsub('+', '')
+ if (s == '') or (s == '0') then
+ s = 1
+ end
+ s = tostring(ltt.NumTabColsAbv - tonumber(s) + 1) -- use number of tabular columns above 0,
+ end
+ return s
+end
+
+--todo if comma present, create multiple according to spec. Also allow LR in []
+
+--- midrule stuff ---
+
+function ltt.make1cmidrule(s, r, c, cmd) -- s=square r=round c=curly
+ cmd = '\\'..cmd
+ if s ~= '' then
+ cmd = cmd..'['..s..']'
+ end
+ if r ~= '' then
+ cmd = cmd..'('..r..')'
+ end
+ t = string.split(c, '-')
+ if t[2] == '' then
+ t[2] = '+'
+ end
+ if t[2] == nil then
+ t[2] = t[1]
+ end
+ c = ltt.get_midrule_col(t[1])..'-'..ltt.get_midrule_col(t[2])
+ cmd = cmd..'{'..c..'}'
+ --help_wrt(cmd)
+ tex.print(cmd)
+end
+
+function ltt.makecmidrules(s, r, c, cmd)
+ for k, c1 in pairs(string.split(c, ',')) do
+ r1, c2 = c1:gextract('%a')
+ if r1 == '' then -- if nothing passed in with the column
+ r1 = r -- set to the global value passed in round brackets
+ end
+ ltt.make1cmidrule(s, r1:strip(), c2:strip(), cmd)
+ end
+end
+
+
+return ltt -- lutabulartools \ No newline at end of file