2026-08-26 23:00:58 +02:00
|
|
|
"""Layout helpers for the txt target — the plain-text sibling of
|
|
|
|
|
html_util.py and latex_util.py.
|
|
|
|
|
|
|
|
|
|
What is target-specific about plain text is that it has no elements to
|
|
|
|
|
hang anything on: a block is set off from the prose around it by being
|
|
|
|
|
INDENTED, and by having laid itself out before the target sees it. Two
|
|
|
|
|
consequences run through this module:
|
|
|
|
|
|
2026-09-07 22:37:05 +02:00
|
|
|
* The indent is written with the NBSP MARKER, never with a real space.
|
|
|
|
|
Eval::eval TRIMS a klammer's result, so leading spaces never survive
|
|
|
|
|
to the output; and the txt target's justification pass treats a
|
|
|
|
|
paragraph beginning with a space or that marker as already laid out
|
|
|
|
|
and leaves it alone (phases.txt_justify_blocks). So the same token
|
2026-08-26 23:00:58 +02:00
|
|
|
both survives and declares "do not re-flow this".
|
|
|
|
|
|
|
|
|
|
* A block takes the indent out of its own measure. The page has
|
|
|
|
|
Target_txt_width columns; a block indented by N has that minus N, and
|
|
|
|
|
a block laid out to the page's width runs past the margin with
|
|
|
|
|
nothing to report it.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import os
|
|
|
|
|
import re
|
|
|
|
|
import sys
|
|
|
|
|
import textwrap
|
|
|
|
|
sys.path.append(f'{os.environ.get("KLAMMERTEXT_HOME")}/sks/kutil')
|
|
|
|
|
import kutil
|
2026-09-07 22:37:05 +02:00
|
|
|
import klammer_base
|
|
|
|
|
import phases
|
2026-08-26 23:00:58 +02:00
|
|
|
|
|
|
|
|
# The non-breaking space marker, resolved to a real space at the end of
|
2026-09-07 22:37:05 +02:00
|
|
|
# phases.txt_justify_blocks. Stated once there, with the other txt
|
|
|
|
|
# markers, and referred to rather than spelled out here.
|
|
|
|
|
NBSP = phases.txt_nbsp_marker()
|
|
|
|
|
|
|
|
|
|
def visible_length(s):
|
|
|
|
|
"""How many columns s will occupy once the markers in it resolve.
|
|
|
|
|
|
|
|
|
|
len() is the wrong measure for anything laid out: a KTESC marker is
|
|
|
|
|
fourteen characters and one glyph, and an nbsp marker is thirteen and
|
|
|
|
|
one space. Measuring the unresolved text sizes a column to a width
|
|
|
|
|
the reader never sees -- and then pads to it, so the padding is wrong
|
|
|
|
|
twice. (The same fault as abbreviate() in mac/util.cpp, which counted
|
|
|
|
|
escape sequences as visible width, and the reason escape resolution
|
|
|
|
|
runs BEFORE the txt phases.)
|
|
|
|
|
"""
|
2026-09-12 18:11:48 +02:00
|
|
|
return phases.txt_visible_length(s)
|
2026-08-26 23:00:58 +02:00
|
|
|
|
|
|
|
|
def indent_text(text, indent):
|
|
|
|
|
"""Every line of text prefixed with `indent` columns of NBSP."""
|
|
|
|
|
if indent <= 0:
|
|
|
|
|
return text
|
|
|
|
|
prefix = NBSP * indent
|
|
|
|
|
return "\n".join(prefix + line for line in text.split("\n"))
|
|
|
|
|
|
|
|
|
|
def block_width(text_width, indent):
|
|
|
|
|
"""The measure available to a block indented by `indent`."""
|
|
|
|
|
return max(1, int(text_width) - indent)
|
|
|
|
|
|
|
|
|
|
def add_caption(element, caption_label, number, caption_text,
|
|
|
|
|
side="top", indent=0):
|
|
|
|
|
"""Attach a caption to an already-laid-out block.
|
|
|
|
|
|
|
|
|
|
`element` is the block's text, indented by `indent` columns of NBSP.
|
|
|
|
|
The caption is placed above it (`side` "top", the default, and
|
|
|
|
|
"left") or below it ("bottom", "right"), on the same indent, so the
|
|
|
|
|
two read as one block and no blank line separates the caption from
|
|
|
|
|
what it captions.
|
|
|
|
|
|
|
|
|
|
Captions go ABOVE a table or a listing and BELOW a figure, and the
|
|
|
|
|
reason is not convention: a figure is taken in at a glance and the
|
|
|
|
|
caption then names what the reader has already seen, while a table or
|
|
|
|
|
a listing is READ, and a reader who meets the rows before the subject
|
|
|
|
|
has to read them twice. ("left"/"right" have no plain-text meaning —
|
|
|
|
|
a caption beside a block would need a second column — so they fall
|
|
|
|
|
back to the vertical side they are nearest.)
|
|
|
|
|
|
|
|
|
|
THE CAPTION IS NOT WRAPPED HERE, and must not be: when `number` is
|
|
|
|
|
true the text placed now is a __CAPTION__ marker, which the numbering
|
|
|
|
|
pass later replaces with something of a quite different length
|
|
|
|
|
("Table 1.2 - ..."), so any line breaks chosen now would be measured
|
|
|
|
|
against text that never reaches the reader. Wrapping the caption is
|
|
|
|
|
the numbering pass's job, once it holds the final text; it can read
|
|
|
|
|
the indent back off the leading NBSP run.
|
|
|
|
|
|
|
|
|
|
`caption_style` and `caption_size` are accepted by the html and tex
|
|
|
|
|
siblings and are absent here on purpose: plain text has one face at
|
|
|
|
|
one size, so there is nothing for them to select. That is stated in
|
|
|
|
|
@table's and @code's descriptions rather than left as a silent
|
|
|
|
|
difference between targets.
|
|
|
|
|
"""
|
|
|
|
|
# BOTH cases go out as a marker, the unnumbered one with an empty
|
|
|
|
|
# label. Everything the numbering pass does to a caption -- wrapping
|
|
|
|
|
# it to the block's measure, ruling it off -- it does inside the
|
|
|
|
|
# marker substitution, so a caption that arrived there as plain text
|
|
|
|
|
# got none of it: ":number false" silently lost its rule. The label
|
|
|
|
|
# is what says "number me", not the presence of the marker.
|
|
|
|
|
if number:
|
|
|
|
|
caption = kutil.caption_marker(caption_label, caption_text, None)
|
|
|
|
|
elif caption_text:
|
|
|
|
|
caption = kutil.caption_marker("", caption_text, None)
|
|
|
|
|
else:
|
|
|
|
|
return element
|
2026-09-21 22:37:13 +02:00
|
|
|
# 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)
|
2026-08-26 23:00:58 +02:00
|
|
|
if side in {"top", "left"}:
|
|
|
|
|
return caption + "\n" + element
|
|
|
|
|
return element + "\n" + caption
|