92 lines
4.2 KiB
Python
92 lines
4.2 KiB
Python
"""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:
|
||
|
|
|
||
|
|
* The indent is written with "~", the non-breaking space, 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 " " or "~" as already laid out and
|
||
|
|
leaves it alone (phases.txt_justify_blocks). So the same character
|
||
|
|
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
|
||
|
|
|
||
|
|
# The non-breaking space marker, resolved to a real space at the end of
|
||
|
|
# phases.txt_justify_blocks. Named here rather than written as "~" at
|
||
|
|
# each use, since it is the whole mechanism of this module.
|
||
|
|
NBSP = "~"
|
||
|
|
|
||
|
|
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
|
||
|
|
caption = indent_text(re.sub(r"\s*\n\s*", " ", caption.strip()), indent)
|
||
|
|
if side in {"top", "left"}:
|
||
|
|
return caption + "\n" + element
|
||
|
|
return element + "\n" + caption
|