Initial commit: Klammertext source distribution
Curated source subset assembled by klammertext-dev's doc/make_dist.sh: the Klammermachine (mac), the Standard Klammer Set (sks), the commands (com), editor plugins and install guides (doc), a test subset (tst), and lib/bin placeholders. Builds with 'make -C com'. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
23
sks/table/border.py
Normal file
23
sks/table/border.py
Normal file
@@ -0,0 +1,23 @@
|
||||
import re
|
||||
|
||||
class Border:
|
||||
def __init__(self, top, right, bottom, left, left_all, right_all):
|
||||
self.top = top
|
||||
self.right = right
|
||||
self.bottom = bottom
|
||||
self.left = left
|
||||
self.left_all = left_all
|
||||
self.right_all = right_all
|
||||
|
||||
def __str__(self):
|
||||
def sym(value, letter):
|
||||
return letter if value else ""
|
||||
T, R, B, L, l, r = self.top, self.right, self.bottom, self.left, self.left_all, self.right_all
|
||||
abbrev = f'{sym(T, "t")}{sym(R, "r")}{sym(B, "b")}{sym(L, "l")}{sym(l, "L")}{sym(R, "R")}'
|
||||
abbrev = re.sub(" ", "", abbrev)
|
||||
if abbrev:
|
||||
abbrev = f"|{abbrev}|"
|
||||
return f'<border{abbrev}>'
|
||||
|
||||
def has(self):
|
||||
return [self.top, self.right, self.bottom, self.left]
|
||||
1
sks/table/css/list.txt
Normal file
1
sks/table/css/list.txt
Normal file
@@ -0,0 +1 @@
|
||||
table.css
|
||||
82
sks/table/css/table.css
Normal file
82
sks/table/css/table.css
Normal file
@@ -0,0 +1,82 @@
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
tr {
|
||||
padding: 1rem 0 0 0;
|
||||
}
|
||||
|
||||
tr:first-child {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
|
||||
td {
|
||||
/* padding-left: 1rem; */
|
||||
padding: .1rem .5rem .2rem .5rem;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.vcenter {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/*
|
||||
td:first-child {
|
||||
padding-left: 0;
|
||||
}
|
||||
*/
|
||||
.line_top {
|
||||
border-top: 1px black solid;
|
||||
}
|
||||
|
||||
.line_bottom {
|
||||
border-bottom: 1px black solid;
|
||||
}
|
||||
|
||||
.pad_top {
|
||||
padding-top: .3rem;
|
||||
}
|
||||
|
||||
.pad_bottom {
|
||||
padding-bottom: .3rem;
|
||||
}
|
||||
|
||||
:root {
|
||||
--cell-border: 1px black solid;
|
||||
}
|
||||
|
||||
.Bt {
|
||||
border-top: var(--cell-border);
|
||||
}
|
||||
|
||||
.Br {
|
||||
border-right: var(--cell-border);
|
||||
}
|
||||
|
||||
.Bb {
|
||||
border-bottom: var(--cell-border);
|
||||
}
|
||||
|
||||
.Bl {
|
||||
border-left: var(--cell-border);
|
||||
}
|
||||
|
||||
|
||||
.Hl {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.Hc {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.Hr {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.cell_arrow {
|
||||
padding: 1rem;
|
||||
font-size: 1.5rem;
|
||||
color: rgb(25%,25%,25%);
|
||||
}
|
||||
147
sks/table/sequences.py
Normal file
147
sks/table/sequences.py
Normal file
@@ -0,0 +1,147 @@
|
||||
import sys
|
||||
import re
|
||||
|
||||
syntax_description = """
|
||||
|
||||
A "sequence" is an integer (the "index") followed by an optional description of one
|
||||
or more sequence subsets. A subset is defined by a series of subset
|
||||
descriptions, separated by a comma. A subset description is either an integer,
|
||||
two integers separated by a hypen to indicate a range, or an integer followed
|
||||
only by a hyphen, which will include all the following elements of the sequence to
|
||||
the end. No spaces are allowed in a sequence. All indices are zero-origin.
|
||||
|
||||
Sequence examples for an index of "3":
|
||||
3
|
||||
3(1)
|
||||
3(0-4)
|
||||
3(1-4,6-9)
|
||||
3(5-)
|
||||
|
||||
Note that some sequence subsets must include two numbers, for example, border
|
||||
lines in a table.
|
||||
|
||||
""".strip()
|
||||
|
||||
|
||||
class Sequence:
|
||||
def __init__(self, count, maxval, spec):
|
||||
def parse_range(match):
|
||||
start, hyphen, end = match.groups()
|
||||
if hyphen is None and end is None:
|
||||
end = start
|
||||
elif end is None:
|
||||
end = maxval
|
||||
return [int(start), int(end)]
|
||||
self.maxval = maxval
|
||||
self.all = True
|
||||
subset_pat = "[-\\d,]+"
|
||||
sequence_rgx = re.compile(fr"(\d+)(\({subset_pat}\))*")
|
||||
match = sequence_rgx.match(spec)
|
||||
self.spec = spec
|
||||
if match and match.group(0) == spec:
|
||||
range_rgx = re.compile(r"(\d+)(-)?(\d+)?")
|
||||
self.index = int(match.group(1))
|
||||
if (match.group(2)):
|
||||
self.subsets = match.group(2).strip("()").split(",")
|
||||
matches = [range_rgx.match(e) for e in self.subsets]
|
||||
self.ranges = [parse_range(e) if e else None for e in matches]
|
||||
self.all = False
|
||||
else:
|
||||
self.ranges = [[0, self.maxval]]
|
||||
else:
|
||||
print(f'The sequence specification "{spec}" is incorrect.\n\n{syntax_description}\n')
|
||||
sys.exit(1)
|
||||
|
||||
def __str__(self):
|
||||
#subsets = "all" if self.all else ",".join([f"{e[0]}-{e[1]}" for e in self.ranges])
|
||||
subsets = "all" if False else ",".join([f"{e[0]}-{e[1]}" for e in self.ranges])
|
||||
return f"{self.index}[{subsets}]"
|
||||
|
||||
def __repr__(self):
|
||||
return self.__str__()
|
||||
|
||||
def has(self, i):
|
||||
for start, end in self.ranges:
|
||||
if i >= start and i <= end:
|
||||
return True
|
||||
return False
|
||||
|
||||
def items(self, invert=False):
|
||||
result = []
|
||||
for start,end in self.ranges:
|
||||
for e in range(start, end+1):
|
||||
result.append((e, self.index) if invert else (self.index, e))
|
||||
return result
|
||||
|
||||
class Sequences:
|
||||
def __init__(self, count, maxval, sequence_specs):
|
||||
self.count = count
|
||||
self.maxval = maxval
|
||||
self.sequences = {}
|
||||
match_all = re.compile(r"\*(.*)").match(sequence_specs)
|
||||
match_some = re.compile(r"\[(\d+)-(\d+)\](.*)").match(sequence_specs)
|
||||
spec_list = []
|
||||
if match_all:
|
||||
subseq = match_all.group(1)
|
||||
for i in range(count):
|
||||
spec_list.append(f"{i}{subseq}")
|
||||
elif match_some:
|
||||
start = int(match_some.group(1))
|
||||
end = int(match_some.group(2))
|
||||
subseq = match_some.group(3)
|
||||
for i in range(start, end+1):
|
||||
spec_list.append(f"{i}{subseq}")
|
||||
else:
|
||||
spec_list = sequence_specs.split()
|
||||
for specs in spec_list:
|
||||
for spec in self.parse_spec(specs):
|
||||
self.sequences[spec.index] = spec
|
||||
|
||||
def __getitem__(self, index):
|
||||
return self.sequences.get(index)
|
||||
|
||||
def has(self, index, subseq_index):
|
||||
return self[index].has(subseq_index) if self[index] else None
|
||||
|
||||
def parse_spec(self, spec):
|
||||
named_spec = { "last" : [str(self.count-1)],
|
||||
"outer" : ["0", str(self.count-1)],
|
||||
"inner" : [str(e) for e in range(1, self.count - 1)],
|
||||
"all" : [str(e) for e in range(0, self.count)]
|
||||
}.get(spec)
|
||||
if named_spec is None:
|
||||
return [Sequence(self.count, self.maxval, spec)]
|
||||
else:
|
||||
return [Sequence(self.count, self.maxval, e) for e in named_spec]
|
||||
|
||||
def items(self, invert=False):
|
||||
result = []
|
||||
for seq in self.sequences:
|
||||
result += self.sequences[seq].items(invert)
|
||||
return set(result)
|
||||
|
||||
if __name__ == "__main__":
|
||||
import pprint
|
||||
for s in [
|
||||
Sequence(10, "0"),
|
||||
Sequence(10, "1"),
|
||||
Sequence(10, "2(2)"),
|
||||
Sequence(10, "3(2-)"),
|
||||
Sequence(10, "4(2-4)"),
|
||||
Sequence(10, "5(2-4)"),
|
||||
Sequence(10, "6(2-4,6)"),
|
||||
Sequence(10, "7(2,6-8)"),
|
||||
Sequence(10, "8(2,7-8,9-11,13-14)")]:
|
||||
print(s.spec, "->", s)
|
||||
|
||||
print("Sequences")
|
||||
S = Sequences(10, "0")
|
||||
for name in "top bottom head outer inner all 1(2-3)".split():
|
||||
print(name, "->", S.parse_spec(name))
|
||||
|
||||
print("Instantiate:")
|
||||
s = Sequences(10, "3(1-2,4-5) 4(8-9)")
|
||||
|
||||
#print(s.items(True))
|
||||
print(s)
|
||||
print(s.has(3,1))
|
||||
111
sks/table/span.py
Normal file
111
sks/table/span.py
Normal file
@@ -0,0 +1,111 @@
|
||||
import sys
|
||||
import re
|
||||
|
||||
syntax_description = """
|
||||
|
||||
A "span" is an integer (the "index") followed by an optional description of one
|
||||
or more sequence subsets. A subset is defined by a series of subset
|
||||
descriptions, separated by a comma. A subset description is either an integer,
|
||||
two integers separated by a hypen to indicate a range, or an integer followed
|
||||
only by a hyphen, which will include all the following elements of the span to
|
||||
the end. No spaces are allowed in a span. All indices are zero-origin.
|
||||
|
||||
Span examples for an index of "3":
|
||||
3
|
||||
3(1)
|
||||
3(0-4)
|
||||
3(1-4,6-9)
|
||||
3(5-)
|
||||
|
||||
Note that some sequence subsets must include two numbers, for example, border
|
||||
lines in a table.
|
||||
|
||||
""".strip()
|
||||
|
||||
|
||||
class Span:
|
||||
def __init__(self, count, spec):
|
||||
def parse_range(match):
|
||||
start, hyphen, end = match.groups()
|
||||
if hyphen is None and end is None:
|
||||
end = start
|
||||
elif end is None:
|
||||
end = count - 1
|
||||
return [int(start), int(end)]
|
||||
self.all = True
|
||||
subset_pat = "[-\\d,]+"
|
||||
span_rgx = re.compile(f"(\d+)(\({subset_pat}\))*")
|
||||
match = span_rgx.match(spec)
|
||||
self.spec = spec
|
||||
if match and match.group(0) == spec:
|
||||
range_rgx = re.compile("(\d+)(-)?(\d+)?")
|
||||
self.index = int(match.group(1))
|
||||
if (match.group(2)):
|
||||
self.subsets = match.group(2).strip("()").split(",")
|
||||
matches = [range_rgx.match(e) for e in self.subsets]
|
||||
self.ranges = [parse_range(e) if e else None for e in matches]
|
||||
self.all = False
|
||||
else:
|
||||
self.ranges = [[0, count-1]]
|
||||
else:
|
||||
print(f'The span specification "{spec}" is incorrect.\n\n{syntax_description}\n')
|
||||
sys.exit(1)
|
||||
|
||||
def __str__(self):
|
||||
subsets = "all" if self.all else ",".join([f"{e[0]}-{e[1]}" for e in self.ranges])
|
||||
return f"{self.index}[{subsets}]"
|
||||
|
||||
def __repr__(self):
|
||||
return self.__str__()
|
||||
|
||||
|
||||
|
||||
class Spanset:
|
||||
def __init__(self, count, span_specs):
|
||||
self.count = count
|
||||
self.spans = {}
|
||||
for specs in span_specs.split():
|
||||
for spec in self.parse_spec(specs):
|
||||
print("Spanset spec:", spec)
|
||||
self.spans[spec.index] = spec
|
||||
|
||||
|
||||
def parse_spec(self, spec):
|
||||
named_spec = {"top" : ["0"],
|
||||
"bottom" : [str(self.count-1)],
|
||||
"head" : ["1"],
|
||||
"outer" : ["0", str(self.count-1)],
|
||||
"inner" : [str(e) for e in range(1,self.count-1)],
|
||||
"all" : [str(e) for e in range(0,self.count+1)]
|
||||
}.get(spec)
|
||||
if named_spec is None:
|
||||
return [Span(self.count, spec)]
|
||||
else:
|
||||
return [Span(self.count, e) for e in named_spec]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import pprint
|
||||
for s in [
|
||||
Span(10, "0"),
|
||||
Span(10, "1"),
|
||||
Span(10, "2(2)"),
|
||||
Span(10, "3(2-)"),
|
||||
Span(10, "4(2-4)"),
|
||||
Span(10, "5(2-4)"),
|
||||
Span(10, "6(2-4,6)"),
|
||||
Span(10, "7(2,6-8)"),
|
||||
Span(10, "8(2,7-8,9-11,13-14)")]:
|
||||
print(s.spec, "->", s)
|
||||
|
||||
print("Spanset")
|
||||
S = Spanset(10, "1")
|
||||
for name in "top bottom head outer inner all 1(2-3)".split():
|
||||
print(name, "->", S.parse_spec(name))
|
||||
|
||||
print("Instantiate:")
|
||||
s = Spanset(10, "3(1-2,4-5)")
|
||||
print(s.tex_hline(3))
|
||||
|
||||
|
||||
|
||||
30
sks/table/sty/table.sty
Normal file
30
sks/table/sty/table.sty
Normal file
@@ -0,0 +1,30 @@
|
||||
\usepackage{booktabs}
|
||||
\usepackage{multirow}
|
||||
\usepackage{array}
|
||||
|
||||
%\usepackage{longtable}
|
||||
|
||||
\usepackage{color, colortbl}
|
||||
\definecolor{Table-red}{rgb}{1,.88,.88}
|
||||
\definecolor{Table-green}{rgb}{.92,1,.92}
|
||||
\definecolor{Table-blue}{rgb}{.9,.9,1}
|
||||
\definecolor{Table-yellow}{rgb}{1,1,.9}
|
||||
|
||||
%\newcommand{\topstrut}[1]{\rule[0pt]{0pt}{#1}}
|
||||
\newcommand{\botstrut}[1]{\rule[-#1]{0pt}{#1}}
|
||||
|
||||
\newcommand{\colorrow}{\rowcolor}
|
||||
|
||||
\setlength{\arrayrulewidth}{.5pt}
|
||||
|
||||
\newcommand{\strutline}{\topstrut{12pt}\botstrut{6pt}}
|
||||
|
||||
\newcommand{\noleftvline}[2]{\multicolumn{1}{#1|}{#2}}
|
||||
\newcommand{\norightvline}[2]{\multicolumn{1}{|#1}{#2}}
|
||||
\newcommand{\novline}[2]{\multicolumn{1}{#1}{#2}}
|
||||
|
||||
\newlength{\tablewidth}
|
||||
\setlength{\tabcolsep}{4pt}
|
||||
|
||||
\newsavebox{\tablebox}
|
||||
\newlength{\tableboxwidth}
|
||||
69
sks/table/table.k
Normal file
69
sks/table/table.k
Normal file
@@ -0,0 +1,69 @@
|
||||
#[ f: 0->1; n: 1.. (integer)
|
||||
l, c, r
|
||||
[<numeric-size>] <width> <justification>
|
||||
width is widest line in a cell f [default]
|
||||
width is fraction of table <f>t
|
||||
width is specific length <n>pt|px|in|cm [...not portable?]
|
||||
width width is remaining (evenly divided) *
|
||||
]#
|
||||
|
||||
@@@argtype table_hpos |
|
||||
horizontal formatting in a table cell. One of 'l', 'c' or 'r' for each
|
||||
cell in a row. If there are fewer positions than cells in a row, the
|
||||
last value is repeated. Extra positions generate a warning. Default is 'l'
|
||||
# :pattern ((f^|(0?\.'uint't^|\\*))[lcr]?^|\s)+
|
||||
# :pattern ((f^|'float't^|\\*)[lcr]?^|\s)+
|
||||
:pattern ([.\w]+^|\*^|\s+)+
|
||||
#:pattern (l^|c^|r^|\s)+
|
||||
:python_cast (lambda s : s.split())
|
||||
@@@
|
||||
|
||||
@@@argtype table_hline |
|
||||
a table's horizontal line description; one or more of 'top',
|
||||
'head', 'inner', 'bottom', or a row number for a line at the bottom
|
||||
of that row
|
||||
#:pattern (top^|head^|inner^|bottom^|\d+^|\d+:\(\d+\-\d+\)^|\s+)*
|
||||
#:python_cast (lambda s : s.split())
|
||||
@@@
|
||||
|
||||
@@@argtype table_vline |
|
||||
a table's vertical line description; one or more of 'outer', 'inner,
|
||||
or a column number for a line at the right of that column
|
||||
# :pattern (outer^|inner^|\d+^|\d+^|\d+:\(\d+\-\d+\)^|\s+)*
|
||||
# :python_cast (lambda s : s.split())
|
||||
@@@
|
||||
|
||||
@@@argtype table_span |
|
||||
a list of spans in a table in the form (X,Y):N (no spaces), where
|
||||
(X,Y) is the position in the table (zero origin in the top left
|
||||
corner) and N is the number of columns or rows in the span
|
||||
#:pattern (\(\d+(?:-\d+)?,\d+(?:-\d+)?\):\d+\s*)*
|
||||
#:python_cast (lambda s : s.split())
|
||||
@@@
|
||||
|
||||
@@rowcolor.tex s : \colorrow{*s*} @@
|
||||
|
||||
@@table rows.rest
|
||||
:id
|
||||
@caption_arguments@
|
||||
:center.bool true
|
||||
:indent.length 1em
|
||||
:header.bool true
|
||||
:allow_break.bool false
|
||||
:hline.table_hline
|
||||
:vline.table_vline
|
||||
:grid.bool false
|
||||
:cell_hpos.table_hpos c
|
||||
:header_font.font i
|
||||
:font.font_list r
|
||||
:colspan.table_span
|
||||
:rowspan.table_span
|
||||
:leading.float 1.3
|
||||
:colsep 4pt
|
||||
:
|
||||
@eval table.Table(K) eval@
|
||||
@@
|
||||
|
||||
@@tbl spec.figure_id :
|
||||
@reference *spec* | Table @
|
||||
@@
|
||||
308
sks/table/table.py
Normal file
308
sks/table/table.py
Normal file
@@ -0,0 +1,308 @@
|
||||
import functools
|
||||
import collections
|
||||
import re
|
||||
import sys
|
||||
import traceback
|
||||
import pprint
|
||||
|
||||
import kutil
|
||||
import klammer_base
|
||||
import html_util
|
||||
from html_util import E
|
||||
import latex_util
|
||||
from sequences import Sequences
|
||||
import table_cell
|
||||
import font
|
||||
|
||||
def extend(lst, count, fill=None):
|
||||
if isinstance(lst, str):
|
||||
lst = lst.strip().split("\\s+")
|
||||
if fill is None:
|
||||
fill = lst[-1] if lst else ""
|
||||
return lst + ([fill] * (count - len(lst)))
|
||||
|
||||
def parse_hpos(target, pos):
|
||||
if '"' in pos:
|
||||
pos_list = []
|
||||
parts = pos.split('"')
|
||||
i = 0
|
||||
while i < len(parts):
|
||||
pos_list += parts[i].split()
|
||||
i += 1
|
||||
if i == len(parts):
|
||||
break
|
||||
#pos_list.append(f"{{{parts[i]}}}")
|
||||
pos_list.append(kutil.parse_length(target, f'"{parts[i]}"')[0])
|
||||
i += 1
|
||||
else:
|
||||
# pos_list = pos.split()
|
||||
pos_list = pos
|
||||
#print("pos_list:", pos_list)
|
||||
return pos_list
|
||||
|
||||
class Table(klammer_base.Klammer_base):
|
||||
id = 0
|
||||
def __init__(self, K):
|
||||
super().__init__(K)
|
||||
# pprint.pprint(self.__dict__)
|
||||
if self.grid:
|
||||
self.vline = "all"
|
||||
self.hline = "all"
|
||||
self.number = self.number == "true"
|
||||
self.rows = kutil.rest_args(self.rows, 2)
|
||||
self.row_count = len(self.rows)
|
||||
if self.header:
|
||||
self.hline += f" 1 {self.row_count}"
|
||||
self.row_size = max([len(e) for e in self.rows])
|
||||
self.s_vline = Sequences(self.row_size + 1, self.row_count - 1, self.vline)
|
||||
self.s_hline = Sequences(self.row_count + 1, self.row_size - 1, self.hline)
|
||||
self.s_rowspan = Sequences(self.row_size, self.row_count, self.rowspan)
|
||||
self.s_colspan = Sequences(self.row_count, self.row_size, self.colspan)
|
||||
self.cell_hpos = extend(parse_hpos(self.K_target, self.cell_hpos.split()), self.row_size)
|
||||
#self.cell_hpos = self.cell_hpos.split(";")
|
||||
self.font = extend(self.font, self.row_size)
|
||||
self.make_cells(self.rows)
|
||||
|
||||
def span_count(self, span_seq, row_i, col_i):
|
||||
result = 0
|
||||
row_seq = span_seq[row_i]
|
||||
if row_seq:
|
||||
for range in row_seq.ranges:
|
||||
if range[0] == col_i:
|
||||
result = range[1] - range[0] + 1
|
||||
return result
|
||||
|
||||
def remove_redundant_borders(self):
|
||||
remove_right = []
|
||||
for row_i in range(self.row_count):
|
||||
for cell_i in range(self.row_size):
|
||||
a = self.cells[row_i][cell_i]
|
||||
b = self.cells[row_i][cell_i+1]
|
||||
if a.border.right and b.border.left:
|
||||
a.border.right = False
|
||||
a.border.right_all = False
|
||||
|
||||
def column_width_text(self):
|
||||
self.column_widths = []
|
||||
# print("column_width_text:", len(self.cells), len(self.cells[0]))
|
||||
for col_i in range(len(self.cells[0])):
|
||||
longest = ""
|
||||
for row_i in range(len(self.cells)):
|
||||
cell = self.cells[row_i][col_i]
|
||||
if cell is not None:
|
||||
cell_text = cell.text
|
||||
#lines = [e.strip() for e in cell_text.split("\\newline")]
|
||||
lines = [e.strip() for e in cell_text.split("\newline")]
|
||||
lines = sorted(lines, key=len)
|
||||
longest_in_line = lines[-1]
|
||||
if len(longest_in_line) > len(longest):
|
||||
longest = longest_in_line
|
||||
self.column_widths.append(longest)
|
||||
# kutil.msg("column_widths:")
|
||||
# print(self.column_widths)
|
||||
|
||||
def make_cells(self, rows):
|
||||
result = []
|
||||
cells = []
|
||||
for row_i, row in enumerate(rows):
|
||||
row_cells = []
|
||||
for cell_i, cell in enumerate(row):
|
||||
rspan = self.span_count(self.s_rowspan, row_i, cell_i)
|
||||
cspan = self.span_count(self.s_colspan, row_i, cell_i)
|
||||
font = self.font[cell_i]
|
||||
if row_i == 0 and self.header:
|
||||
font = self.header_font
|
||||
row_cells.append(
|
||||
table_cell.Cell(
|
||||
cell,
|
||||
font, self.cell_hpos[cell_i],
|
||||
self.s_hline.has(row_i, cell_i),
|
||||
self.s_vline.has(cell_i+1, row_i),
|
||||
self.s_hline.has(row_i+1, cell_i),
|
||||
self.s_vline.has(cell_i, row_i),
|
||||
self.s_vline.sequences.get(cell_i),
|
||||
self.s_vline.sequences.get(cell_i+1),
|
||||
rspan, cspan))
|
||||
cells.append(row_cells)
|
||||
widths = [len(e) for e in cells]
|
||||
max_width = max(widths)
|
||||
self.cells = [extend(row, max_width, None) for row in cells] \
|
||||
if max_width != min(widths) else cells
|
||||
self.column_width_text()
|
||||
|
||||
# HTML
|
||||
|
||||
def html(self):
|
||||
result = ""
|
||||
for row in self.cells:
|
||||
row_html = ""
|
||||
for cell in row:
|
||||
row_html += cell.html().strip() + "\n"
|
||||
result += E("tr").body(row_html).str()
|
||||
result = E("table").body(result)
|
||||
if self.number or self.caption:
|
||||
result = html_util.add_caption(
|
||||
result, "Table", self.number, self.caption, self.caption_font)
|
||||
else:
|
||||
result = result.str()
|
||||
return result
|
||||
|
||||
# LaTeX
|
||||
|
||||
def tex_hpos(self):
|
||||
def par_format(s, justification):
|
||||
command = {"l" : "raggedright",
|
||||
"c" : "centering",
|
||||
"r" : "raggedleft"}[justification]
|
||||
return f">{{\\{command}}}p{{{s}}}"
|
||||
|
||||
widths = []
|
||||
hpos_pat = re.compile("(f|(?:0?(\\.\\d+)(t))|\\*)?([lcr]?)")
|
||||
|
||||
def parse(s, width_text):
|
||||
match = hpos_pat.match(s)
|
||||
# print("MATCH:", match, match.groups())
|
||||
width, frac, table, just = match.groups()
|
||||
width = width or "f"
|
||||
just = just or "l"
|
||||
if width and width[0] == "{":
|
||||
width = f"\\widthof{{{s}}}"
|
||||
elif table == "t":
|
||||
width = f"{frac}\\tablewidth"
|
||||
elif width == "f":
|
||||
width = f"\\widthof{{{width_text}}}"
|
||||
|
||||
if width != "*":
|
||||
widths.append(width)
|
||||
|
||||
result = par_format(width, just) if width != "*" else s
|
||||
# print("PARSE:", result)
|
||||
return result
|
||||
|
||||
# print("self.column_widths:", len(self.column_widths), self.column_widths)
|
||||
# return extend([parse(e) for e in self.cell_hpos], self.row_size)
|
||||
hpos_list = []
|
||||
for i, hpos in enumerate(extend(self.cell_hpos, self.row_size)):
|
||||
# print(f" Loop {i}:", hpos)
|
||||
if i >= len(self.column_widths):
|
||||
print(f"Warning: Ignoring table column width: {hpos}")
|
||||
else:
|
||||
hpos_list.append(parse(hpos, self.column_widths[i]))
|
||||
|
||||
# print("hpos_list:", hpos_list)
|
||||
fill_count = sum([1 if "*" in e else 0 for e in hpos_list])
|
||||
# print("fill_count:", fill_count)
|
||||
|
||||
if fill_count > 0:
|
||||
margins = f"(\\tabcolsep * {2 * len(hpos_list)})"
|
||||
# print("MARGINS:", margins)
|
||||
#expr = "\\linewidth - " + " - ".join(widths) + str("
|
||||
if fill_count == len(hpos_list):
|
||||
expr = f"{1/fill_count}\\tablewidth"
|
||||
else:
|
||||
#expr = f"(\\textwidth - {margins} - {' - '.join(widths)}) / {fill_count}"
|
||||
expr = f"(\\tablewidth - {' - '.join(widths)}) / {fill_count}"
|
||||
# print(expr)
|
||||
result = []
|
||||
for h in hpos_list:
|
||||
if h[0] == "*":
|
||||
just = h[1] if len(h) > 1 else "l"
|
||||
result.append(par_format(expr, just))
|
||||
else:
|
||||
result.append(h)
|
||||
else:
|
||||
result = hpos_list
|
||||
# print("tex_hpos:", result)
|
||||
|
||||
return result
|
||||
|
||||
def tex_column_spec(self):
|
||||
parts = [""] * (self.row_size * 2 + 1)
|
||||
for i in self.s_vline.sequences:
|
||||
parts[i * 2] = "|"
|
||||
for i, hpos in enumerate(self.tex_hpos()):
|
||||
parts[i * 2 + 1] = hpos
|
||||
# print("tex_column_spec:", "".join(parts))
|
||||
return "".join(parts)
|
||||
|
||||
def tex_hline(self, index):
|
||||
hline = ""
|
||||
bottom = index == self.row_count
|
||||
if bottom:
|
||||
index -= 1
|
||||
count = 0
|
||||
for i, cell in enumerate(self.cells[index]):
|
||||
has_border = cell.border.bottom if bottom else cell.border.top
|
||||
if has_border:
|
||||
hline += f"\\cline{{{i+1}-{i+1}}} "
|
||||
count += 1
|
||||
#if count == self.row_size:
|
||||
# hline = "\\hline"
|
||||
return hline.strip() + "\n"
|
||||
|
||||
def tex_rows(self):
|
||||
result = ""
|
||||
for row_i, row in enumerate(self.cells):
|
||||
result += self.tex_hline(row_i)
|
||||
tab = ""
|
||||
for cell_i, cell in enumerate(row):
|
||||
result += tab + cell.tex()
|
||||
tab = " & "
|
||||
#result += " \\\\\n"
|
||||
result += " \\tabularnewline\n"
|
||||
result += self.tex_hline(self.row_count)
|
||||
return result
|
||||
|
||||
def get_width(self):
|
||||
box = "\\savebox{\\tablebox}{%\n"
|
||||
box += "\\begin{tabular}{"
|
||||
box += self.tex_column_spec()
|
||||
box += "}\n"
|
||||
box += self.tex_rows()
|
||||
box += "\\end{tabular}}\n"
|
||||
result = box + "\\setlength{\\tableboxwidth}{\\wd\\tablebox}\n"
|
||||
# result += "\\the\\tableboxwidth\n\n"
|
||||
# print(result)
|
||||
return result
|
||||
|
||||
def make_caption(self):
|
||||
column_count = str(len(self.rows[0]))
|
||||
caption = latex_util.make_caption_text(
|
||||
self.number, "Table", self.caption, self.caption_font, self.caption_font_size)
|
||||
result = "\\multicolumn{" + column_count + "}{c}{\\parbox{\\tableboxwidth}{"
|
||||
result += "\\vspace*{8pt}\\centering\\small\\em "
|
||||
result += caption
|
||||
result += "}} \\\\ \\endlastfoot\n"
|
||||
return result
|
||||
|
||||
def tex(self):
|
||||
result = self.get_width()
|
||||
result += "\\vspace*{-.75\\baselineskip}"
|
||||
# result = ""
|
||||
result += "\\renewcommand*{\\arraystretch}{1.3}\n"
|
||||
if self.allow_break:
|
||||
result += "\\vspace*{12pt}\n"
|
||||
result += "\\begin{longtable}{"
|
||||
result += self.tex_column_spec()
|
||||
result += "}\n"
|
||||
if self.allow_break:
|
||||
result += self.make_caption()
|
||||
result += self.tex_rows()
|
||||
result += "\\end{longtable}\n"
|
||||
|
||||
if not self.allow_break:
|
||||
if self.number or self.caption:
|
||||
result = latex_util.add_caption(result, "Table", self.number, self.caption, "\\tablewidth")
|
||||
else:
|
||||
result = latex_util.caption_wrapper(result, "center")
|
||||
|
||||
name = f"Reference-Table-{Table.id}"
|
||||
result = f"\\hypertarget{{{name}}}{{}}\\label{{Label-{name}}}\n{result}"
|
||||
result = f"\\setlength{{\\tablewidth}}{{\\textwidth - {2 * self.row_count}\\tabcolsep}}\n" + result
|
||||
# result += "\\vspace*{-8pt}"
|
||||
result = re.sub(r"\newline", r"\\\\", result)
|
||||
return result
|
||||
|
||||
def txt(self):
|
||||
return "TXT"
|
||||
|
||||
60
sks/table/table_cell.py
Normal file
60
sks/table/table_cell.py
Normal file
@@ -0,0 +1,60 @@
|
||||
import border
|
||||
import font
|
||||
import html_util
|
||||
from html_util import E
|
||||
|
||||
class Cell:
|
||||
def __init__(self, text, font, hpos,
|
||||
top, right, bottom, left,
|
||||
left_all, right_all,
|
||||
rowspan, colspan):
|
||||
self.text = text
|
||||
self.font = font
|
||||
self.hpos = hpos
|
||||
self.border = border.Border(top, right, bottom, left, left_all, right_all)
|
||||
self.rowspan = rowspan
|
||||
self.colspan = colspan
|
||||
#print("Cell:", text, hpos)
|
||||
|
||||
def __str__(self):
|
||||
b = str(self.border)
|
||||
b = f"[{b}]" if b else ""
|
||||
rs = f"[r{self.rowspan}]" if self.rowspan else ""
|
||||
cs = f"[r{self.colspan}]" if self.colspan else ""
|
||||
#return f'<cell"{self.text}"{self.font}{self.hpos}{b}>'
|
||||
return f'<cell"{self.text}"{b}{rs}{cs}>'
|
||||
|
||||
def __repr__(self):
|
||||
return self.__str__()
|
||||
|
||||
def html(self):
|
||||
result = self.text
|
||||
result = E("td").body(font.html_fontify(result, self.font, 1.0))
|
||||
for pred, cls_name in zip(self.border.has(), "Bt Br Bb Bl".split()):
|
||||
if pred:
|
||||
result.cls(cls_name)
|
||||
result.cls(f"H{self.hpos}")
|
||||
return result.str()
|
||||
|
||||
def tex(self, debug=False): # , left_line, right_line):
|
||||
result = self.text
|
||||
if self.font != "r":
|
||||
result = font.tex_fontify(result, self.font, 1.0)
|
||||
remove_left = self.border.left_all and not self.border.left
|
||||
remove_right = self.border.right_all and not self.border.right
|
||||
if debug:
|
||||
left_marker = "xL" if remove_left else "L" if self.border.left_all else ""
|
||||
right_marker = "Rx" if remove_right else "R" if self.border.right_all else ""
|
||||
result = f"{left_marker} {result} {right_marker}"
|
||||
if remove_left or remove_right:
|
||||
pos = self.hpos
|
||||
if self.border.left_all and self.border.left:
|
||||
pos = "|" + pos
|
||||
if self.border.right_all and self.border.right:
|
||||
pos = pos + "|"
|
||||
result = f"\\multicolumn{{1}}{{{pos}}}{{{result}}}"
|
||||
return result
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(Cell("dog", "r", "c", True, False, True, False))
|
||||
Reference in New Issue
Block a user