Files
klammertext/sks/block/block.py
T
ackandClaude Fable 5.1 0ead40f272 Width and height argtypes, kdesc --argtypes from ref/, txt @define/@left_right/@columns, kdiag --check value validation (from dev d6993110e9b3)
Also: the klammertext argtype (descriptions kept as source), txt table
wrapping and :hpos/:offset, @c between ‹ ›, txt section headings, the
ktext newline-only trim, capitalize(), and the argtype test suite.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 16:59:49 +02:00

190 lines
7.0 KiB
Python

import re
import textwrap
import pprint
import klammer_base
import phases
import kutil
import latex_util
import html_util
from html_util import E
import color
class Indent(klammer_base.Klammer_base):
"""An indented block (@indent). :left and :right are widths: a bare
number is characters in every target (2026-09-16); :right defaults to
:left."""
def __init__(self, K):
super().__init__(K)
if not self.right:
self.right = self.left
def html(self):
text_align = "justify" if not self.ragged else "left"
left = kutil.parse_width("html", self.left)[0]
right = kutil.parse_width("html", self.right)[0]
return E("div").body(self.s)\
.sty("margin", f"0.5rem {right} 0.5rem {left}")\
.sty("text-align", text_align)\
.str()
def tex(self):
if self.ragged:
self.s = "\\raggedright " + self.s
left = kutil.parse_width("tex", self.left)[0]
right = kutil.parse_width("tex", self.right)[0]
result = rf"\hspace*{{{left}}}" + latex_util.minipage(
self.s, rf"\linewidth - {left} - {right}", center=False)
result = latex_util.block(result)
return result
def txt(self):
left = int(kutil.parse_width("txt", self.left)[0])
right = int(kutil.parse_width("txt", self.right)[0])
indent = phases.txt_nbsp_marker() * left # a space that survives
width = int(self.Target_txt_width) - left - right
result = re.sub("KK0022", '"', self.s)
return phases.txt_lay_out(indent + f"\n{indent}".join(phases.txt_wrap(result, width)))
class Hspace(klammer_base.Klammer_base):
"""Horizontal space (@hspace): a width, so a bare number is characters
in every target."""
def __init__(self, K):
super().__init__(K)
def html(self):
return f'<span style="display: inline-block; width: {kutil.parse_width("html", self.width)[0]}"></span>'
def tex(self):
return f"\\hspace{{{kutil.parse_width('tex', self.width)[0]}}}"
def txt(self):
# The nbsp marker, not spaces: an @eval result of only whitespace
# is trimmed away entirely.
return phases.txt_nbsp_marker() * int(kutil.parse_width("txt", self.width)[0])
class Note(klammer_base.Klammer_base):
def __init__(self, K):
super().__init__(K)
def html(self):
result = E("div").cls("box")\
.sty("width", f"{self.width*100}%")\
.sty("background-color", html_util.color(self.color))\
.sty("border-color", html_util.color(self.border_color))\
.sty("margin-left", "auto")\
.body(f"<b>{self.label}:</b> {self.s}")\
.str()
return result
def tex(self):
return latex_util.color_box(
f"\\textbf{{{self.label}:}} {self.s}",
self.width, self.color, self.border_color)
class Block(klammer_base.Klammer_base):
def __init__(self, K):
super().__init__(K)
def tex(self):
# NOT latex_util.block(): a textblock is absolutely positioned and
# does not participate in the normal flow, so breaking the paragraph
# around it would move the surrounding text.
#
# The trailing \par\kern0pt gives the block's vbox zero depth.
# textpos anchors :point against the box HEIGHT alone, so content
# whose last line has depth -- an @image, whose [c] minipage sits
# half below the baseline -- was anchored a quarter of its height
# too low (2in for a page-high picture) until 2026-09-08.
to_x, to_y = [float(e) for e in self.to.split()]
pt_x, pt_y = [float(e) for e in self.point.split()]
result = f"""
\\begin{{textblock}}{{{self.width}}}[{pt_x},{pt_y}]({to_x},{to_y})
\\setlength{{\\textwidth}}{{\\linewidth}}
\\vspace*{{-1\\parskip}}
{self.content.strip()}
\\par\\kern0pt
\\end{{textblock}}
"""
return result
class Lines(klammer_base.Klammer_base):
def __init__(self, K):
super().__init__(K)
self.lines = self.s.split("\n")
def html(self):
result = ""
for line in self.lines:
result += line + "<br/>\n"
return result
def tex(self):
return "\\\\\n".join(self.lines) + "\n"
def txt(self):
return self.lines
class Vspace(klammer_base.Klammer_base):
"""Vertical space (@vspace): the first parameter of the height type. A
bare number is lines in every target, as it always was here; a unit
(2em, 12pt) is now accepted as well, under html and tex."""
def __init__(self, K):
super().__init__(K)
def html(self):
height = kutil.parse_height("html", self.lines)[0]
return f'<div style="{kutil.css_height("height", height)}"></div>'
def tex(self):
return f"\\vspace{{{kutil.parse_height('tex', self.lines)[0]}}}"
def txt(self):
# One marker per line; the justification phase turns them into
# blank lines after paragraph normalization (a whitespace-only
# @eval result would be trimmed away, hence the markers).
return phases.txt_vspace_marker() * int(kutil.parse_height("txt", self.lines)[0])
class Left_right(klammer_base.Klammer_base):
def __init__(self, K):
super().__init__(K)
self.rows = list(zip(self.parts[0], self.parts[1]))
def html(self):
result = ""
for left, right in self.rows:
result += E("div").body(
E("span").cls("left_right").body(left).str() + \
E("span").cls("left_right").sty("text-align", "right").body(right).str()).str();
result = E("p").body(result).str()
return result
def tex(self):
result = ""
for left, right in self.rows:
result += rf"\parbox{{\linewidth}}{{{left} \hfill {right}}}" + " \\\\\n"
result = result[:-3]
return result
def txt(self):
# Plain text: the left text at the block indent, the right text ending
# at Target_txt_width, the gap filled with the nbsp marker (the only
# space that survives to the output, and what marks the block as laid
# out). A pair that cannot share a line -- less than two columns
# between them -- goes on two lines, the right text still flush right.
nbsp = phases.txt_nbsp_marker()
indent = int(self.Target_txt_indent)
width = int(self.Target_txt_width)
lines = []
for left, right in self.rows:
left, right = left.strip(), right.strip()
gap = width - indent - phases.txt_visible_length(left) - phases.txt_visible_length(right)
if gap >= 2:
lines.append(nbsp * indent + left + nbsp * gap + right)
else:
lines.append(nbsp * indent + left)
lines.append(nbsp * max(indent, width - phases.txt_visible_length(right)) + right)
return phases.txt_lay_out("\n".join(lines))