summaryrefslogtreecommitdiff
path: root/Master/texmf-dist/tex/lualatex/luaprogtable/luaprogtable.lua
diff options
context:
space:
mode:
Diffstat (limited to 'Master/texmf-dist/tex/lualatex/luaprogtable/luaprogtable.lua')
-rw-r--r--Master/texmf-dist/tex/lualatex/luaprogtable/luaprogtable.lua559
1 files changed, 559 insertions, 0 deletions
diff --git a/Master/texmf-dist/tex/lualatex/luaprogtable/luaprogtable.lua b/Master/texmf-dist/tex/lualatex/luaprogtable/luaprogtable.lua
new file mode 100644
index 00000000000..1e3c2a76570
--- /dev/null
+++ b/Master/texmf-dist/tex/lualatex/luaprogtable/luaprogtable.lua
@@ -0,0 +1,559 @@
+require "luaprogtable-parse.lua"
+require "luaprogtable-stringbuffer.lua"
+
+TeXTable = {}
+TeXTable.__index = TeXTable
+
+function TeXTable:new()
+ local ret = {
+ rows = {}
+ }
+ setmetatable(ret, TeXTable)
+ return ret
+end
+
+TeXTableCell = {}
+TeXTableCell.__index = TeXTableCell
+
+local _std_shape = {1, 1}
+
+function TeXTableCell:new(default_val)
+ local ret = {
+ data=default_val,
+ shape=_std_shape,
+ parent=nil
+ }
+ setmetatable(ret, TeXTableCell)
+ return ret
+end
+
+
+TeXTableRow = {}
+TeXTableRow.__index = TeXTableRow
+
+function TeXTableRow:new(tex_table)
+
+ local ncols = tex_table.ncols
+ local default_val = rawget(tex_table, "default value")
+
+ local cells = {}
+
+ for i=1,ncols do
+ table.insert(cells, TeXTableCell:new(default_val))
+ end
+
+ local ret = {
+ ["before line"] = "",
+ ["after line"] = rawget(tex_table, "default after line"),
+ ["after spacing"] = rawget(tex_table, "default after spacing"),
+ ["cells"] = cells
+ }
+
+ setmetatable(ret, TeXTableRow)
+ return ret
+end
+
+
+function TeXTable:resize(nrows, ncols)
+ local now_nrows = #self.rows
+ local now_ncols = self.ncols
+
+ if nrows < now_nrows or ncols < now_ncols then
+ raise_error("'TeXTable:resize' doesn not support table truncation")
+ end
+
+ local nrows_delta = nrows - now_nrows
+ local ncols_delta = ncols - now_ncols
+
+ -- change column for existing rows
+ self.ncols = ncols
+ for i=1,now_nrows do
+ local row = self.rows[i]
+ for j=1,ncols_delta do
+ table.insert(row.cells, TeXTableCell:new(rawget(self, "default value")))
+ end
+ end
+
+ -- add new rows
+ for i=1,nrows_delta do
+ table.insert(self.rows, TeXTableRow:new(self))
+ end
+end
+
+function TeXTable:to_string(no_linebreak)
+ local ret = {}
+ local no_linebreak = no_linebreak or false
+ local lb = "\n"
+ if no_linebreak then
+ lb = " "
+ end
+
+ table.insert(ret, string.format([[\begin{%s}{%s}]], self.backend, rawget(self, "table preamble")))
+
+ local nrows = #self.rows
+ for i=1,nrows do
+
+ local row = self.rows[i]
+
+ local cell_data = {}
+ local j = 1
+
+ while j <= self.ncols do
+ local cell = row.cells[j]
+
+ local next_j = nil
+
+ if cell.shape ~= nil then
+ table.insert(cell_data, cell.data)
+ next_j = j + cell.shape[2]
+ else
+ local parent_row = cell.parent[1]
+ local parent_col = cell.parent[2]
+ local parent_cell = self:get_cell(parent_row, parent_col)
+ table.insert(cell_data, "")
+ next_j = j + parent_cell.shape[2]
+ end
+
+ j = next_j
+ end
+
+ local row_str = ""
+
+ if string.len(rawget(row, "before line")) > 0 then
+ row_str = rawget(row, "before line") .. lb .. row_str
+ end
+
+ row_str = row_str .. table.concat(cell_data, " & ") .. [[ \\]]
+
+ if string.len(rawget(row, "after spacing")) > 0 then
+ row_str = row_str .. "[" .. rawget(row, "after spacing") .. "]"
+ end
+
+ row_str = row_str .. rawget(row, "after line")
+
+ table.insert(ret, row_str)
+ end
+
+ table.insert(ret, string.format([[\end{%s}]], self.backend))
+
+ return table.concat(ret, lb)
+
+end
+
+
+function TeXTable:get_cell(row_id, cell_id)
+ local row = self.rows[row_id]
+ if row == nil then
+ raise_error(string.format("row id '%d' is invalid for table '%s'", row_id, rawget(self, "table name")))
+ end
+ local cell = row.cells[cell_id]
+ if cell == nil then
+ raise_error(string.format("cell location '(%d,%d)' is invalid for table '%s'", row_id, cell_id, rawget(self, "table name")))
+ end
+ return cell
+end
+
+function TeXTable:set_cell(row_id, cell_id, data, shape)
+ local cell = self:get_cell(row_id, cell_id)
+
+ -- check if we can modify this cell
+ if cell.parent ~= nil then
+ local parent_1 = cell.parent[1]
+ local parent_2 = cell.parent[2]
+ raise_error(string.format("cell location '(%d,%d)' of table '%s' is not acceesible because it is overlapped by cell '(%d,%d)'", row_id, cell_id, rawget(self, "table name"), parent_1, parent_2))
+ end
+
+
+ -- check cell boundary
+ local max_row = row_id + shape[1] - 1
+ local max_col = cell_id + shape[2] - 1
+ if max_row > #self.rows or max_col > self.ncols then
+ raise_error(string.format("cell '(%d,%d)' is too large for table '%s'", row_id, col_id, rawget(self, "table name")))
+ end
+
+
+ local old_shape = cell.shape
+
+ local std_shape = {1, 1}
+ -- clear control info for children
+ for i=1, old_shape[1] do
+ local row_id = row_id + i - 1
+ for j = 1, old_shape[2] do
+ local col_id = cell_id + j - 1
+ local other_cell = self:get_cell(row_id, col_id)
+ other_cell.parent = nil
+ other_cell.shape = std_shape
+ end
+ end
+
+ local parent_t = {row_id, cell_id}
+ -- adjust control info based on new shape
+ for i=1, shape[1] do
+ local row_id = row_id + i - 1
+ for j = 1, shape[2] do
+ local col_id = cell_id + j - 1
+ local other_cell = self:get_cell(row_id, col_id)
+ other_cell.parent = parent_t
+ other_cell.shape = nil
+ end
+ end
+
+ -- adjust control info for this cell
+ cell.parent = nil
+ cell.shape = shape
+ cell.data = data
+end
+
+function TeXTable:fill(data)
+ local nrows = #self.rows
+ for i=1,nrows do
+ for j=1,self.ncols do
+ local cell = self:get_cell(i, j)
+ cell.shape = _std_shape
+ cell.parent = nil
+ cell.data = data
+ end
+ end
+end
+
+function TeXTable:fix_row_index(index_pair)
+ local ret = {index_pair[1], index_pair[2]}
+
+ if ret[1] < 0 then
+ ret[1] = #self.rows + ret[1] + 1
+ end
+ if ret[2] < 0 then
+ ret[2] = #self.rows + ret[2] + 1
+ end
+
+ if ret[2] < ret[1] then
+ raise_error("invalid index expression: second element must not be smaller than the first")
+ end
+
+ if ret[1] <= 0 then
+ raise_error("invalid index expression: index must be greater than zero after conversion")
+ end
+
+ return ret
+end
+
+function TeXTable:fix_col_index(index_pair)
+ local ret = {index_pair[1], index_pair[2]}
+
+ if ret[1] < 0 then
+ ret[1] = self.ncols + ret[1] + 1
+ end
+ if ret[2] < 0 then
+ ret[2] = self.ncols + ret[2] + 1
+ end
+
+ if ret[2] < ret[1] then
+ raise_error("invalid index expression: second element must not be smaller than the first")
+ end
+
+ if ret[1] <= 0 then
+ raise_error("invalid index expression: index must be greater than zero after conversion")
+ end
+
+ return ret
+end
+
+-- fix the index of a single cell
+function TeXTable:fix_cell_index(index_expr)
+ local the_table = self
+ local index_parse = _lpt_parse_index_expr(index_expr)
+ if #index_parse ~= 2 then
+ raise_error(string.format("invalid index expression for cell: '%s'", index_expr))
+ end
+
+ local row_expr = the_table:fix_row_index(index_parse[1])
+ local col_expr = the_table:fix_col_index(index_parse[2])
+
+ if row_expr[1] ~= row_expr[2] then
+ raise_error(string.format("invalid row index expression for cell: '%s'", index_expr))
+ end
+ if col_expr[1] ~= col_expr[2] then
+ raise_error(string.format("invalid column index expression for cell: '%s'", index_expr))
+ end
+
+ return {row_expr[1], col_expr[1]}
+end
+
+-- make sure table views do not cut through multicol/multirow
+function TeXTable:check_view_validity(start_row, start_col, row_span, col_span)
+ local max_row = start_row + row_span - 1
+ local max_col = start_col + col_span - 1
+
+ for i = 1, row_span do
+ local row_id = start_row + i - 1
+ for j=1,col_span do
+ local col_id = start_col + j - 1
+ local cell = self:get_cell(row_id, col_id)
+ if cell.shape ~= nil then
+ local boundary_1 = row_id + cell.shape[1] - 1
+ local boundary_2 = col_id + cell.shape[2] - 1
+ if boundary_1 > max_row or boundary_2 > max_col then
+ raise_error(string.format("invalid table view: cell '(%d, %d)' has shape '(%d, %d)', which does not fit into viewed region", row_id, col_id, cell.shape[1], cell.shape[2]))
+ end
+ end
+ end
+ end
+end
+
+
+lpt_info = {}
+
+function _lpt_get_table(table_name)
+ local table_name = _lpt_strip(table_name)
+ local the_table = lpt_info[table_name]
+ if the_table == nil then
+ raise_error(string.format("table '%s' does not exist", table_name))
+ end
+ return the_table
+end
+
+
+_verb_lines = {}
+_verb_end_env = ""
+
+function _lpt_store_verbatim_lines(str)
+ if string.find(str , _verb_end_env) then
+ luatexbase.remove_from_callback("process_input_buffer" , "lpt_store_verbatim_lines")
+ return _verb_end_env
+ else
+ if str ~= nil then
+ table.insert(_verb_lines, str)
+ end
+ return ""
+ end
+end
+
+function _lpt_register_verbatim(end_env)
+ for k in next, _verb_lines do rawset(_verb_lines, k, nil) end
+ _verb_end_env = end_env
+ luatexbase.add_to_callback("process_input_buffer", _lpt_store_verbatim_lines, "lpt_store_verbatim_lines")
+end
+
+local _lpt_ignored_fields = {nrows=true}
+local _lpt_numeric_fields = {ncols=true}
+
+
+
+function lpt_new_table(param)
+ local new_table = TeXTable:new()
+
+ for key, val in pairs(param) do
+ if _lpt_numeric_fields[key] then
+ rawset(new_table, key, _lpt_tonumber(val))
+ elseif _lpt_ignored_fields[key] then
+ -- do nothing
+ else
+ rawset(new_table, key, val)
+ end
+ end
+
+ local table_name = new_table["table name"]
+
+ local table_fetch = lpt_info[table_name]
+ if table_fetch ~= nil then
+ raise_error(string.format("table '%s' already exists", table_name))
+ end
+
+ local nrows = _lpt_tonumber(param["nrows"])
+
+ -- integrity check
+ if nrows < 0 then
+ raise_error(string.format("'nrows' (%d) of table '%s' is invalid", nrows, table_name))
+ end
+ if new_table.ncols <= 0 then
+ raise_error(string.format("'ncols' (%d) of table '%s' is invalid", new_table.ncols, table_name))
+ end
+
+ -- create table
+ new_table:resize(nrows, new_table.ncols)
+
+ lpt_info[table_name] = new_table
+end
+
+function lpt_table_fill(table_name, index_expr, env_content)
+ local the_table = _lpt_get_table(table_name)
+ local env_content = env_content or table.concat(_verb_lines, " ")
+
+ if index_expr:len() == 0 then
+ the_table:fill(env_content)
+ else
+ local index_parse = _lpt_parse_index_expr(index_expr)
+ if #index_parse ~= 2 then
+ raise_error(string.format("invalid index expr '%s' for table '%s'", index_expr, table_name))
+ end
+
+ local row_expr = the_table:fix_row_index(index_parse[1])
+ local col_expr = the_table:fix_col_index(index_parse[2])
+
+ local row_span = row_expr[2] - row_expr[1] + 1
+ local col_span = col_expr[2] - col_expr[1] + 1
+
+ the_table:check_view_validity(row_expr[1], col_expr[1], row_span, col_span)
+
+ for i=1,row_span do
+ local row_id = i + row_expr[1] - 1
+ for j=1,col_span do
+ local col_id = j + col_expr[1] - 1
+ the_table:set_cell(row_id, col_id, env_content, _std_shape)
+ end
+ end
+ end
+end
+
+
+function lpt_table_view(table_name, index_expr)
+ local env_content = table.concat(_verb_lines, "\n")
+ local the_table = _lpt_get_table(table_name)
+
+ local index_parse = _lpt_parse_index_expr(index_expr)
+ if #index_parse ~= 2 then
+ raise_error(string.format("invalid index expr '%s' for table '%s'", index_expr, table_name))
+ end
+
+ local row_expr = the_table:fix_row_index(index_parse[1])
+ local col_expr = the_table:fix_col_index(index_parse[2])
+
+ local row_span = row_expr[2] - row_expr[1] + 1
+ local col_span = col_expr[2] - col_expr[1] + 1
+
+ the_table:check_view_validity(row_expr[1], col_expr[1], row_span, col_span)
+
+ local parsed_table = _lpt_parse_table_view(env_content)
+
+ if #parsed_table ~= row_span then
+ raise_error(string.format("lpttableview: expecting '%d' rows, but get '%d' instead for table '%s'", row_span, #parsed_table, table_name))
+ end
+
+ for i=1,row_span do
+ local row_id = i + row_expr[1] - 1
+ local cols = parsed_table[i]
+
+ -- sum the number of total columns in this row
+ local n_table_cols = 0
+ local max_row_size = 0
+ for _, val in ipairs(cols) do
+ n_table_cols = n_table_cols + val["shape"][2]
+ max_row_size = math.max(max_row_size, val["shape"][1])
+ end
+
+ local max_row = row_id + max_row_size - 1
+ if max_row > row_expr[2] then
+ raise_error("lpttableview: the height of table is greater than table view")
+ end
+
+
+ -- count the numbers of columns needs to be filled in this row
+ local pending_table_cols = 0
+ for j=1,col_span do
+ local col_id = j + col_expr[1] - 1
+ local cell = the_table:get_cell(row_id, col_id)
+ if cell.shape ~= nil then
+ pending_table_cols = pending_table_cols + 1
+ else
+ local parent_row = cell.parent[1]
+ -- if the parent is in the same row,
+ -- then this space needs to be filled in the same row
+ if parent_row == row_id then
+ pending_table_cols = pending_table_cols + 1
+ end
+ end
+ end
+
+ if n_table_cols ~= pending_table_cols then
+ raise_error(string.format("lpttableview: expecting '%d' cols for row '%d', but get '%d' instead for table '%s'", pending_table_cols, i, n_table_cols, table_name))
+ end
+
+
+ --[[
+ if n_table_cols ~= col_span then
+ raise_error(string.format("lpttableview: expecting '%d' cols for row '%d', but get '%d' instead for table '%s'", col_span, i, n_table_cols, table_name))
+ end
+ --]]
+
+ local k = 1
+ for j=1,col_span do
+ local col_id = j + col_expr[1] - 1
+ local cell = the_table:get_cell(row_id, col_id)
+ if cell.shape ~= nil then
+ local col_data = cols[k]["data"]
+ local col_shape = cols[k]["shape"]
+ the_table:set_cell(row_id, col_id, col_data, col_shape)
+ k = k + 1
+ end
+ end
+
+ end
+
+
+end
+
+local _s_buf = TeXStringBuffer:new()
+
+function lpt_use_table(table_name)
+ local the_table = _lpt_get_table(table_name)
+ local input_method = rawget(the_table, "input method")
+
+ if input_method == "stringbuffer" then
+ local table_str = the_table:to_string(true)
+ _s_buf:clear()
+ _s_buf:append(table_str)
+ _s_buf:register_callback()
+ tex.print([[\input{stringbuffer}]])
+ elseif input_method == "file" then
+ local table_str = the_table:to_string()
+ local filename = tex.jobname .. "-" .. table_name .. ".table"
+ local outfile = io.open(filename, "w")
+ outfile:write(table_str)
+ outfile:close()
+ tex.print(string.format([[\input{%s}]], filename))
+ else
+ raise_error(string.format("invalid input method '%s' for table '%s'", input_method, table_name))
+ end
+
+end
+
+function lpt_set_row_prop(table_name, index_expr, param)
+
+ local the_table = _lpt_get_table(table_name)
+ local index_parse = _lpt_parse_index_expr(index_expr)
+ for key, val in ipairs(index_parse) do
+ local fixed_val = the_table:fix_row_index(val)
+
+ local l = fixed_val[1]
+ local r = fixed_val[2]
+
+ for i=l,r do
+ local row = the_table.rows[i]
+ if row == nil then
+ raise_error(string.format("lpt_set_row_prop: invalid index expression '%s'", index_expr))
+ end
+ for kkey, vval in pairs(param) do
+ if rawget(row, kkey) == nil then
+ raise_error(string.format("row '%d' of table '%s' does not have attribute '%s'", i, table_name, kkey))
+ end
+ rawset(row, kkey, vval)
+ end
+ end
+ end
+end
+
+
+function lpt_add_row(table_name, param)
+ local the_table = _lpt_get_table(table_name)
+ local nrows = #the_table.rows
+ local ncols = the_table.ncols
+ the_table:resize(nrows+1, ncols)
+ lpt_set_row_prop(table_name, "-1", param)
+end
+
+function lpt_get_table_shape(table_name)
+ local the_table = _lpt_get_table(table_name)
+ local nrows = #the_table.rows
+ local ncols = the_table.ncols
+ tex.print(string.format("{%d}{%d}", nrows, ncols))
+end \ No newline at end of file