--% Kale Ewasiuk (kalekje@gmail.com) --% 2022-02-27 --% Copyright (C) 2021-2022 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 = {} ltt.col_spec1 = {} -- column spec if one column wide (since makcell nests a tabular, preserve col_spec below) ltt.col_spec = {} -- tab column spec if above 1 ltt.col = '' -- current column spec, single char, only applies to tabular with more than 1 column ltt.col_num = 1 -- current column number 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.set_col_num() -- register current column info (column number and specification) 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.col_num = col else ltt.col_num = 1 end ltt.col = ltt.col_spec[ltt.col_num] end function ltt.set_col_spec(zz) -- contents of string 'zz' -- register the table column specification 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 zz = zz:gsub('%a', ltt.col_replaces) -- sub extra column _col_spec = zz:totable() -- requires pl extras --help_wrt(_col_spec, 'helpme') if #_col_spec > 1 then ltt.col_spec = _col_spec else ltt.col_spec1 = _col_spec end end --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) -- ltt.set_col_num() -- register current column number and column spec 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(ltt.SI_cols):contains(ltt.col) -- special columns for SI and c == '') then -- multicolumn cannot have {} around it 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 = '*' -- * = natural width, = will match p{2cm} for example 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 space local vh, rc = spec:gextract('%a') -- extract characters local v = vh:gfirst({'t', 'm', 'b'}) or ltt.col_ver_repl[ltt.col] 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 function ltt.get_HColSpec(h, mcspec, c) -- take horizontal alignment -- c is num columns, h is horizontal alginment, --Assumes _TabColNum was calculated previosly if c == '+' then -- fill row to end c = tostring(#ltt.col_spec - ltt.col_num + 1) end if h == '' then -- if horizontal not provided, use declared column h = ltt.col_hor_repl[ltt.col] 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.col_num == 1 then -- if first column, auto detect padding mcspec = '@{}'..mcspec end if (ltt.col_num + tonumber(c) - 1) == #ltt.col_spec then -- if end on last column mcspec = mcspec..'@{}' end else -- if mcspec if given, extract the alignment ltt.set_col_spec(mcspec) h = ltt.col_spec1[1] -- get 1 character column spec from mcspec and override h end end return h, mcspec, c end ltt.col_ver_repl = { m = 'm', M = 'm', b = 'b', } ltt.col_hor_repl = { -- horizontal cell alignment that multicolumn should use if () or [hori] not passed to func l = 'l', c = 'c', r = 'r', p = 'l', P = 'c', X = 'l', Y = 'c', Z = 'l', N = 'c', L = 'l', R = 'r', C = 'c', } -- allow user to place their own replacements in for a table, say if they define a column that expands to multiple ltt.col_replaces = { --x = 'lll' } ltt.SI_cols = {'S', 'N', 'Q', 'L', 'R'} --ltt.SI_cols = {'S'} --ltt.SI_cols = {'N'} ----- --- 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.col_spec - tonumber(s) + 1) -- use number of tabular columns above 0, end return s end 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 --help_wrt('TEST COL ') --for _, s in ipairs{ 'll', '*{6}{s}', 'l*{6}{l}', 'lll', 'll[]', 'll[]*{6}{l}', '*{6}{l}', 'y*{6}{sq}x', } do -- ltt. set_col_spec(s) -- help_wrt(ltt.col_spec,s) --end return ltt -- lutabulartools --http://ctan.mirror.rafal.ca/macros/latex/contrib/multirow/multirow.pdf --http://ctan.mirror.colo-serv.net/macros/latex/contrib/makecell/makecell.pdf -- https://tex.stackexchange.com/questions/331716/newline-in-multirow-environment