katom types from ref/ in groups, @table :leading in lines of text and :rulesep, @code under txt, one table style attribute, literal-klammer options (from dev 2e453744f5e9)

kdesc --katoms is Klammertext rendered from ref/, the types in titled groups,
with --katoms full for the regex column.  @table :leading L is L lines of the
surrounding text in html as in pdf (default 1), and :rulesep spaces the
horizontal rules.  The html table has one style attribute, so :column_width
fractions take effect again.  @code and @source_listing render in plain
text; captions are numbered per kind there, and a table needs no blank line
around it.  @c takes :nd, and an option-shaped word before a bar inside
literal code is code.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
ackandClaude Fable 5.1 committed 2026-09-21 22:37:13 +02:00
1 parent 63956c00e5
commit 217c0f3fc5
23 files changed
+625 -290

No files matched your search

+4 -3
View File
@@ -13,15 +13,16 @@ katom when no bar is written, "^@code :filename f :pattern p" DISPLAYED THOSE
WORDS as the listing rather than acting on them.
@@
@@code.html,tex :: @eval code_block.Code(K) @ @@
@@code.html,tex,txt :: @eval code_block.Code(K) @ @@
@@c.k code_text.literal :
@@c.k :nd.bool | code_text.literal :
A word or phrase displayed verbatim in a line. Like ^@code, its content is
LITERAL -- nothing in it is interpreted as Klammertext -- so it must be closed
with the named delimiter "c^@". ^@c is the in-line form and ^@code the block
form of the same thing; before 2026-08-16 this parameter was an ordinary
string, so a "^#" or a bare "^@" inside it was read as Klammertext and usually
failed the file, which is not what "verbatim" can mean.
If :nd (no delimiters) is true, then the txt target delimiters are excluded.
@@
@@c.html,tex,txt :: @eval code_block.Code_fragment(K) eval@
@@ -44,4 +45,4 @@ quietly different listing.
Named "^@source_file" until 2026-08-16. # retired-ok
@@
@@source_listing.html,tex :: @eval :cwd *K_input_dir* code_block.Source(K) @ @@
@@source_listing.html,tex,txt :: @eval :cwd *K_input_dir* code_block.Source(K) @ @@
+73 -7
View File
@@ -5,11 +5,13 @@
import re
import klammer_base
import kutil
import html_util
import txt_util
import phases
from html_util import E
import latex_util as L
def escape_newlines(s):
# A newline becomes the marker the html paragraph pass turns back into a
# line break, so a verbatim source file keeps its lines. Used by Source.
@@ -119,7 +121,13 @@ def tex_line(line):
a monospace font, which LaTeX will not collapse -- so the code's
indentation and internal alignment survive.
"""
return quote_specials(escape_latex(line)).replace(" ", "~")
# An apostrophe is hidden as a marker: after a quoted caret it would
# otherwise read back as "^" + "^'", the opening of a literal span, and
# "@c \^' c@" -- the regex of that very katom -- failed the document
# with a span that does not end. html_line hides it with the other
# typographic characters; the marker decodes to the plain character.
quoted = quote_specials(escape_latex(line)).replace("'", klammer_base.escape_ktesc("'"))
return quoted.replace(" ", "~")
def widest_line(lines):
r"""The line to measure the block's width with.
@@ -325,12 +333,64 @@ class Code(klammer_base.Klammer_base):
# As in tex: the caption goes through the shared helper, so it sits
# in the same position container as the listing and moves with it.
if self.number or self.caption:
# A left-placed (or flowing) listing's caption is flush with it,
# as in tex, where such a listing is unboxed and its caption a
# paragraph on the same indent; a centred or right-placed one is
# boxed there and its caption centred over the box, which is
# the helper's default here too.
align = "left" if self.hpos in ("left", "none") else None
return html_util.add_caption(
listing, "Listing", self.number, self.caption,
self.caption_style, self.hpos, self.caption_side, True,
self.caption_size, offset=self.offset)
self.caption_size, offset=self.offset, caption_align=align)
return html_util.hpos_container(listing, self.hpos, self.offset).str()
def txt_indent(self):
# The block's indent in columns, by @table's rule: a left- or
# right-placed block is inset by its :offset (0 = flush), ":hpos
# none" flows with no indent, and "center" -- not attempted in plain
# text -- keeps the block convention, Target_txt_indent.
if self.hpos in ("left", "right"):
return int(kutil.parse_width("txt", self.offset)[0])
if self.hpos == "none":
return 0
return int(self.Target_txt_indent)
def txt(self):
# The listing is LAID OUT here, line by line, and says so with the
# verbatim marker (phases.txt_lay_out) so the justification pass
# leaves it alone -- a blank line in the code included, which holds
# a marker and therefore does not end the paragraph.
#
# Each line is prepared exactly as txt()'s inline sibling prepares a
# fragment: the Klammertext specials QUOTED, because this result is
# re-read as Klammertext (raw, a "#" removed the rest of its line and
# a "code@" in a quoted source file closed the document's own span:
# "A named end katom does not match"), and the typographic characters
# hidden, so "--x" stays two hyphens. Spaces become the nbsp marker
# AFTER that, not "~": hide_typographic has already hidden the
# code's own tildes, and a "~" written here would be turned into a
# space by the txt target's transform only as long as it survived.
lines = self.text.strip("\n").split("\n")
indent = txt_util.NBSP * self.txt_indent()
def prepared(line):
line = klammer_base.hide_typographic(quote_specials(line.rstrip()))
return line.replace(" ", txt_util.NBSP)
width = max(len(line.rstrip()) for line in lines)
rule = indent + klammer_base.hide_typographic("-" * width)
body = [indent + prepared(line) for line in lines]
# A caption above the listing is ruled off by the caption pass, so
# the listing draws its own top rule only when nothing does.
captioned = self.number or self.caption
caption_above = captioned and self.caption_side in ("top", "left")
if not caption_above:
body.insert(0, rule)
body.append(rule)
result = phases.txt_lay_out("\n".join(body))
return txt_util.add_caption(
result, "Listing", self.number, self.caption, self.caption_side,
self.txt_indent())
class Code_fragment(klammer_base.Klammer_base):
def __init__(self, K):
@@ -356,10 +416,13 @@ class Code_fragment(klammer_base.Klammer_base):
# without a beginning) and the typographically active characters
# hidden, so a "--" in code stays two hyphens.
result = klammer_base.hide_typographic(quote_specials(self.code_text.strip()))
# Surround code symbols with ‹ and ›, though there are conflicts in Swiss
# German, Italian and some Scandinavian languages. Should be clear in
# context, though.
result = "\u2039" + result + "\u203A"
# If *nd* ("no delimiters) is false (the default), surround code symbols
# with the characters ‹ and ›. There are conflicts in Swiss German,
# Italian and some Scandinavian languages, but the meaning should be
# clear in context.
if not self.nd:
result = "\u2039" + result + "\u203A"
return result
def extract_marked_region(src, marker, filename):
@@ -424,6 +487,9 @@ class Source(Code):
# not exist until the file has been read. File content carries no
# whitespace markers anyway, since those come from the katomizer.
klammer_base.Klammer_base.__init__(self, K)
# The filename arrives with the target's escapes as markers -- under
# tex an underscore is one -- and a marker names no file.
self.filename = klammer_base.unescape_ktesc(self.filename)
try:
with open(self.filename) as fp:
text = fp.read()
+5 -1
View File
@@ -30,7 +30,11 @@
.code_block {
display: flex;
align-items: center;
margin: 0 0 0 1rem;
/* No left margin: it was 1rem, an html-only inset with no tex
counterpart, which set the code a rem to the right of its own
flush-left caption and of the same listing's pdf. A listing's
inset is :offset, in both targets (the hpos_margin precedent). */
margin: 0;
padding: 0;
}
+9 -1
View File
@@ -4,6 +4,14 @@
--serif: Libre Baskerville, serif;
--sans-serif: Open Sans, sans-serif;
--monospace: Inconsolata, monospace;
/* The pitch of a line of body text, in em of the NOMINAL font size.
It is 1.5 rather than LaTeX's 1.2 because font-size-adjust (below)
draws the type larger than its nominal size -- EB Garamond by a
fifth -- and line-height is computed from the nominal one. It is
the html counterpart of \baselineskip, and a klammer whose spacing
is stated in lines of text multiplies THIS (@table :leading), so a
table and the paragraphs around it cannot drift apart. */
--text_line_height: 1.5;
}
body {
@@ -32,7 +40,7 @@ h1, h2, h3, h4, h5 {
place); this file assigns only the family. */
p {
line-height: 1.5;
line-height: var(--text_line_height);
}
.plain {
+1 -1
View File
@@ -195,7 +195,7 @@ other structures. The ^:offset is used only if the ^:hpos value is "left" or
:number.bool true
:caption_side.side
:caption_style.style_code i
:caption_size.float .9
:caption_size.float 1
: Arguments that define a caption for a block element.
@@
+2 -2
View File
@@ -2,8 +2,8 @@
# the front and back matter of a book):
@@section_args.o :id :n.int -1 :
Common arguments for section headings. If *n* is not the default, it is used
as the section number.
Common arguments for section headings. If *n* is not the default,
it is used as the section number.
@@
@@preface.k :id :name Preface : Preface text @@
+14
View File
@@ -58,8 +58,21 @@ td {
--cell-border: 1px black solid;
}
/* The space between a rule and the text on either side of it: a cell is
padded on the side that CARRIES the rule, so a row with no rule is
untouched, a partial :hline pads only the cells it crosses, and the
table's outer rules are padded on the inner side alone. The amounts
began as booktabs' defaults (0.4ex above a rule, 0.65ex below one) and
were tuned by eye, 2026-09-20: half of the space below moved above, so
0.725ex above and 0.325ex below, the total unchanged. The tex target's
pair is in table.py (TEX_RULE_ABOVE/_BELOW) and differs on purpose -- a
TeX row and an html row distribute their own height differently around
the text. --rulesep is @table's :rulesep, a multiplier, set on the
<table> by table.py. "ex" is the x-height as RENDERED
(font-size-adjust), so the space tracks the visible type. */
.Bt {
border-top: var(--cell-border);
padding-top: calc(var(--rulesep, 1) * 0.325ex);
}
.Br {
@@ -68,6 +81,7 @@ td {
.Bb {
border-bottom: var(--cell-border);
padding-bottom: calc(var(--rulesep, 1) * 0.725ex);
}
.Bl {
+11 -1
View File
@@ -213,7 +213,8 @@
:calc.table_calc
:format.table_format
:decimal.decimal_mark
:leading.float 1.3
:leading.float 1.0
:rulesep.float 1
:colsep 4pt
:
A table of rows of cells. The cells of a row are separated by "|" and the
@@ -226,6 +227,15 @@ draw lines, ^:colspan and ^:rowspan merge cells, ^:justify and ^:cell_hpos
place text within them, ^:calc computes cells from other cells and ^:format
formats them, and ^:hpos and ^:offset place the whole table in the text
column.
^:leading is the height of a row in lines of the surrounding text, the same
in html and pdf. ^:rulesep is the space between a horizontal line and the
text on either side of it, as a multiple of the standard amount (slightly
more below a line than above it); 0 sets the lines against the rows. The
lines at the top and the bottom of the table are spaced on their inner side
only. A table with vertical lines gets no such space in a pdf, where it
would break them. Neither argument has a meaning in plain text, where a row
is a line of characters and a horizontal line already has a line to itself.
@@
@@table.html,tex,txt :: @eval table.Table(K) eval@ @@
+77 -29
View File
@@ -575,23 +575,39 @@ class Table(klammer_base.Klammer_base):
# would wrap it as a stray <p> inside the table.
return E("colgroup").body(cols.strip(), newline=False).str(), layout, table_width
def html_style(self):
# :leading and :colsep under html (2026-09-18; both were tex-only).
# \arraystretch multiplies the row strut, which is \baselineskip,
# 1.2 times the font size -- so :leading L is a 1.2 L em row, and
# the html equivalent is that line-height on the table, inherited
# by every cell. The cells' vertical padding went with it
# (table.css): an html-only extra with no tex counterpart, which
# kept a :leading 1 table 0.3rem looser per row than its pdf.
# :colsep is a custom property the td rule reads for its horizontal
# padding (padding does not inherit); the flush-edge classes still
# zero the outer ones. Measured 2026-09-18: rows of :leading 1 and
# :leading 2 in the ratio 1:2 in both targets.
line_height = f"{1.2 * float(self.leading):.4g}"
def html_style(self, layout=""):
# The WHOLE style attribute of the <table>, written once: "layout" is
# the width declarations the caller has worked out (max-width for a
# fill table, table-layout/width for fractions). E.attr() appends,
# so a second .attr("style", ...) made a second attribute, which a
# browser discards -- from 2026-09-18, when this function arrived,
# to 2026-09-20 every width declaration was lost that way.
# :leading and :colsep under html. ":leading L" is L times the pitch
# of a line of the surrounding TEXT, in every target. tex has that
# by construction: \arraystretch multiplies the row strut, which is
# the \baselineskip in force. The html counterpart of \baselineskip
# is --text_line_height (font.css, 1.5), so the table's line-height
# is L times that, inherited by every cell. It was 1.2 L from
# 2026-09-18 to 09-20, LaTeX's ratio of \baselineskip to font size:
# wrong in html, where font-size-adjust draws the type larger than
# the nominal size line-height is computed from, so a :leading 1
# table sat 16% tighter than the paragraphs around it (measured:
# 20.2px rows beside 24px text, where the pdf had 14.2pt beside
# 14.2pt). The fallback serves a fragment without the stylesheet.
# The cells have no vertical padding (table.css): an html-only extra
# with no tex counterpart. :colsep is a custom property the td rule
# reads for its horizontal padding (padding does not inherit); the
# flush-edge classes still zero the outer ones.
line_height = f"calc({float(self.leading):.4g} * var(--text_line_height, 1.5))"
# Hidden: the result is re-read and the html target's transforms
# would turn the property's "--" into an en dash (font.py's trap).
return klammer_base.hide_typographic(
f"line-height: {line_height}; --colsep: {self.colsep}")
# :rulesep is a custom property too: the .Bt/.Bb rules (table.css)
# read it for the padding on the side of a cell that carries a rule.
style = (f"line-height: {line_height}; --colsep: {self.colsep}; "
f"--rulesep: {float(self.rulesep):.4g}")
if layout:
style += "; " + layout.strip().rstrip(";")
return klammer_base.hide_typographic(style)
def html(self):
result = ""
@@ -610,8 +626,10 @@ class Table(klammer_base.Klammer_base):
# algorithm), matching the tex \ratio division. The max-width
# cap goes on the caption wrapper when there is one (the table's
# own percentage would be circular in a shrink-to-fit wrapper).
result = E("table").body(result).attr("style", self.html_style())
if self.number or self.caption:
captioned = self.number or self.caption
result = E("table").body(result).attr(
"style", self.html_style("" if captioned else "max-width: 100%"))
if captioned:
result = html_util.add_caption(
result, "Table", self.number, self.caption,
self.caption_style, hpos=self.hpos,
@@ -619,17 +637,19 @@ class Table(klammer_base.Klammer_base):
font_size=self.caption_size, max_width="100%",
offset=self.offset)
else:
result.attr("style", "max-width: 100%")
result = html_util.hpos_container(result, self.hpos, self.offset).str()
return result
colgroup, layout, table_width = self.html_colgroup()
result = E("table").body(colgroup + result).attr("style", self.html_style())
if self.number or self.caption:
captioned = self.number or self.caption
table_layout = ""
if table_width:
table_layout = f"{layout}width: " + ("100%" if captioned else table_width)
result = E("table").body(colgroup + result).attr(
"style", self.html_style(table_layout))
if captioned:
# The caption wrapper carries the width (a percentage on the
# shrink-to-fit wrapper itself would be circular) and the table
# fills it -- which also makes the caption track the table.
if table_width:
result.attr("style", f"{layout}width: 100%")
result = html_util.add_caption(
result, "Table", self.number, self.caption, self.caption_style,
hpos=self.hpos, side=self.caption_side,
@@ -638,8 +658,6 @@ class Table(klammer_base.Klammer_base):
else:
# An uncaptioned table still gets the position container, so
# html and tex agree on where the table sits.
if table_width:
result.attr("style", f"{layout}width: {table_width}")
result = html_util.hpos_container(result, self.hpos, self.offset).str()
return result
@@ -761,9 +779,32 @@ class Table(klammer_base.Klammer_base):
# print("tex_column_spec:", "".join(parts))
return "".join(parts)
TEX_RULE_ABOVE = 0.4 # ex; booktabs' \aboverulesep
TEX_RULE_BELOW = 0.35 # 468 # ex; booktabs' \belowrulesep (0.65) less 20%, less 10%
def tex_rule_separations(self, top, bottom):
# The space above and below a rule, as booktabs' \specialrule and
# \cmidrule take them: :rulesep times TEX_RULE_ABOVE/_BELOW. They
# began as booktabs' defaults (0.4ex above a rule, 0.65ex below) and
# were tuned by eye, per target, 2026-09-20 (Andy): the amounts that
# LOOK alike differ, because a TeX row carries its strut's depth
# under the text and an html row its half-leading. The html pair is
# in table.css (.Bt/.Bb). The table's outer rules are padded on the
# INNER side only.
rulesep = float(self.rulesep)
above = "0pt" if top else f"{self.TEX_RULE_ABOVE * rulesep:.4g}ex"
below = "0pt" if bottom else f"{self.TEX_RULE_BELOW * rulesep:.4g}ex"
return above, below
def tex_hline(self, index):
# Contiguous cell borders coalesce into single \cline runs; a
# full-width line becomes \hline.
# Contiguous cell borders coalesce into single rule runs. The rules
# are booktabs' -- an \hline sits flush against the strut of the
# rows on either side, and the only way to open space there is a
# strut in every neighbouring row -- EXCEPT in a table with vertical
# lines: a booktabs rule is set between rows, so it breaks every
# vertical line it crosses (its author provides for none), and such
# a table keeps \hline/\cline and gets no space at its rules.
top = index == 0
bottom = index == self.row_count
if bottom:
index -= 1
@@ -773,7 +814,11 @@ class Table(klammer_base.Klammer_base):
# No line through the interior of a merged (rowspan) cell.
flags = [flag and (index, col_i) not in self.rowspan_covered
for col_i, flag in enumerate(flags)]
spaced = not self.s_vline.by_index and float(self.rulesep) > 0
above, below = self.tex_rule_separations(top, bottom)
if flags and all(flags):
if spaced:
return f"\\specialrule{{\\arrayrulewidth}}{{{above}}}{{{below}}}\n"
return "\\hline\n"
hline = ""
start = None
@@ -781,7 +826,10 @@ class Table(klammer_base.Klammer_base):
if flag and start is None:
start = i
elif not flag and start is not None:
hline += f"\\cline{{{start + 1}-{i}}} "
if spaced:
hline += f"\\cmidrule[\\arrayrulewidth]{{{start + 1}-{i}}} "
else:
hline += f"\\cline{{{start + 1}-{i}}} "
start = None
return hline.strip() + "\n"
@@ -1063,7 +1111,7 @@ class Table(klammer_base.Klammer_base):
if len(rows) in self.s_hline.by_index:
result += rule
result = txt_util.add_caption(
result, "Table", self.number, self.caption, self.caption_side, \
int(self.Target_txt_indent))
result, "Table", self.number, self.caption, self.caption_side,
len(indent) // len(txt_util.NBSP))
return result
+12
View File
@@ -103,6 +103,18 @@
font-style: italic;
}
/* A caption flush with its element's left edge (html_util.add_caption,
caption_align "left"): @code's caption when the listing is placed left,
as the tex target sets it. AFTER the two rules above, which it must
override at equal specificity. */
.caption_flush_left {
align-items: flex-start;
}
.caption_flush_left .caption {
text-align: left;
}
.caption_left {
display: inline-flex;
flex-direction: row;
+8 -1
View File
@@ -238,7 +238,12 @@ def hpos_container(element, hpos, offset=None):
def add_caption(element, caption_label, number, caption_text,
font_symbol="i", hpos="center", side="bottom", as_string=True,
font_size=.9, width=None, max_width=None, offset=None):
font_size=.9, width=None, max_width=None, offset=None,
caption_align=None):
# caption_align "left" sets the caption flush with the element's left
# edge, where the default centres it over the element: a caption above
# a left-placed listing, which the tex target sets as a paragraph on the
# listing's own indent.
tag = element_tag(element)
# Caption
caption = ""
@@ -276,6 +281,8 @@ def add_caption(element, caption_label, number, caption_text,
#print("-"*80)
element = E("div").cls("caption_" + side).attr("data-label", caption_label).body(element)
if caption_align == "left" and side in {"top", "bottom"}:
element.cls("caption_flush_left")
if width:
# The element's width (e.g. a table's :column_width total) lives
# on this wrapper: the element fills it, and the caption tracks
+69 -20
View File
@@ -147,6 +147,36 @@ def tex_nl_marker():
def tex_resolve_markers(text, K):
return text.replace(tex_nl_marker(), "\\newline")
def txt_runs(par):
"""A paragraph as runs of lines: [(laid_out, text), ...].
"Laid out or prose" was decided ONCE per paragraph, by its first line,
so a laid-out block written directly after prose -- no blank line
between them -- was re-flowed with it, every one of its lines marked
or not: a generator whose prose ran straight into its @table put the
table's header on the last line of the sentence (2026-09-20). A blank
line should not be what protects a block that has already said, line
by line, that it is laid out.
So a line that BEGINS WITH THE VERBATIM MARKER is laid out wherever it
stands, and the paragraph divides into runs at each change. Only the
marker divides: it is explicit and per line (txt_lay_out). A leading
space or nbsp keeps the older, whole-paragraph meaning and only on the
FIRST line, since a later line of prose may legitimately begin with
one (a footnote's hanging indent).
"""
if par.startswith((" ", txt_nbsp_marker())):
return [(True, par)]
mark = txt_verbatim_marker()
runs = []
for line in par.split("\n"):
laid_out = line.startswith(mark)
if runs and runs[-1][0] == laid_out:
runs[-1][1].append(line)
else:
runs.append((laid_out, [line]))
return [(laid_out, "\n".join(lines)) for laid_out, lines in runs]
def txt_justify_blocks(text, K):
delim = '__DIVIDE__'
text = re.sub("\n\n+", delim, text)
@@ -178,13 +208,20 @@ def txt_justify_blocks(text, K):
# verbatim paragraph was not left alone, it was DELETED: the one
# branch that had nothing to do also did not copy the text through.
# An indented paragraph vanished from the output with no error.
#
# The decision is per RUN of lines, not per paragraph (txt_runs):
# prose is filled, a laid-out run is copied through, and the runs
# stay on consecutive lines as they were written.
nbsp = txt_nbsp_marker()
if par and not txt_is_laid_out(par):
label = re.match("(?:" + re.escape(nbsp) + r")*\[\d+\] ", par)
par = "\n".join(txt_wrap(
par, int(K.Target_txt_width),
subsequent_indent=" " * len(label.group(0)) if label else ""))
result += par + "\n\n"
filled = []
for laid_out, run in txt_runs(par):
if run and not laid_out:
label = re.match("(?:" + re.escape(nbsp) + r")*\[\d+\] ", run)
run = "\n".join(txt_wrap(
run, int(K.Target_txt_width),
subsequent_indent=" " * len(label.group(0)) if label else ""))
filled.append(run)
result += "\n".join(filled) + "\n\n"
result = re.sub(" *" + txt_vspace_marker() + " *", "\n", result)
result = re.sub(r"\n? *" + txt_nl_marker() + r" *\n?", "\n", result)
result = result.replace(txt_verbatim_marker(), "")
@@ -192,33 +229,45 @@ def txt_justify_blocks(text, K):
return result
def txt_insert_captions(text, K):
# The caption's INDENT is the block's, read off the run of nbsp markers
# written before the caption marker (txt_util.add_caption) -- not
# Target_txt_indent, which put the caption of a flush block two columns
# to the right of the block it captions. Every line produced here is
# marked laid-out: a flush caption begins with neither a space nor an
# nbsp, and an unmarked first line made the justification pass re-flow
# the caption, its rule and the whole block under it into one paragraph.
mark = kutil.caption_delimiter()
caption_rgx = re.compile(f"{mark}(.*?){mark}(.*?){mark}", re.S)
caption_number = 0
nbsp = txt_nbsp_marker()
caption_rgx = re.compile(
f"((?:{re.escape(nbsp)})*){mark}(.*?){mark}(.*?){mark}", re.S)
# One sequence PER LABEL, as in html and tex: a listing after a table is
# "Listing 1", not "Listing 2". It was one counter for every label.
caption_numbers = {}
width = int(K.Target_txt_width)
indent = int(K.Target_txt_indent)
indented_width = width - indent
def replace(m):
nonlocal caption_number
name, text = m.groups()
indent_run, name, text = m.groups()
indent = len(indent_run) // len(nbsp)
indented_width = max(1, width - indent)
# An empty label means an unnumbered caption (":number false"). It
# must NOT take a number from the sequence: a reader counting the
# captions they can see would then find the next one a number
# ahead. The same is true of html and tex, where an unnumbered
# caption emits no marker and so never reaches their counter.
if name:
caption_number += 1
caption = f"{name} {caption_number} - {text}"
caption_numbers[name] = caption_numbers.get(name, 0) + 1
caption = f"{name} {caption_numbers[name]} - {text}"
else:
caption = text
# Visible widths throughout: a caption may carry style markers.
lines = [caption]
if txt_visible_length(caption) > indented_width:
caption = "\n".join(txt_wrap(
caption, indented_width,
subsequent_indent=txt_nbsp_marker()*indent))
line_length = max([txt_visible_length(e) for e in caption.split("\n")])
line = txt_nbsp_marker() * indent + "-" * line_length
return caption + "\n" + line
lines = txt_wrap(caption, indented_width)
line_length = max(txt_visible_length(e) for e in lines)
lines.append("-" * line_length)
lead = txt_verbatim_marker() + indent_run
# The first line's marker is already in the text, before the indent
# run this match begins with (add_caption); the others get theirs.
return indent_run + ("\n" + lead).join(lines)
result = caption_rgx.sub(replace, text)
return result
+5 -1
View File
@@ -101,7 +101,11 @@ def add_caption(element, caption_label, number, caption_text,
caption = kutil.caption_marker("", caption_text, None)
else:
return element
caption = indent_text(re.sub(r"\s*\n\s*", " ", caption.strip()), indent)
# Marked laid-out, like the block: with no indent the caption begins
# with neither a space nor an nbsp, and the justification pass would
# re-flow it together with everything under it.
caption = phases.txt_verbatim_marker() + indent_text(
re.sub(r"\s*\n\s*", " ", caption.strip()), indent)
if side in {"top", "left"}:
return caption + "\n" + element
return element + "\n" + caption