import re import textwrap import kutil # Emitted by @vspace.txt (sks/block/block.k), one marker per line of space. # An @eval result consisting only of whitespace is trimmed away by the # Klammermachine, so vertical space must travel as markers and become # newlines here, after blank-line runs have been normalized. def txt_nl_marker(): return "__KTEXTNEWLINE__" def txt_vspace_marker(): return "__KTEXTVSPACE__" def txt_justify_blocks(text, K): delim = '__DIVIDE__' text = re.sub("\n\n+", delim, text) result = "" for par in text.split(delim): stripped = par.strip() n = stripped.count(txt_vspace_marker()) if n and stripped == txt_vspace_marker() * n: # A paragraph of only @vspace markers: n blank lines in addition # to the normal paragraph separation. result += "\n" * n continue # A paragraph BEGINNING WITH WHITESPACE is verbatim: it is filled to # no width and keeps the line breaks it was written with. The same # in-text rule the error messages use (justify() in mac/util.cpp), # and the one a klammer relies on to emit lines that must not # re-flow -- a @table's rows, a listing. # # "~" counts as that whitespace, and MUST: a klammer's indent cannot # be a real space, because Eval::eval TRIMS the result katoms, so # the leading spaces a klammer emits never reach this pass at all # (the "~" is the non-breaking space marker, resolved to a space at # the end of this function -- the only space that survives). A # writer's own "~" has become a real space long before this, in the # target's transform table, so there is no ambiguity here. # # The emission below was INSIDE this "if" until 2026-08-26, so a # 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. if par and par[0] not in ' ~': label = re.match(r"~*\[\d+\] ", par) par = "\n".join(textwrap.wrap( par, width=int(K.Target_txt_width), break_long_words=False, subsequent_indent=" " * len(label.group(0)) if label else "")) result += par + "\n\n" result = re.sub(" *" + txt_vspace_marker() + " *", "\n", result) result = re.sub(r"\n? *" + txt_nl_marker() + r" *\n?", "\n", result) result = re.sub("~", " ", result) return result def txt_insert_captions(text, K): mark = kutil.caption_delimiter() caption_rgx = re.compile(f"{mark}(.*?){mark}(.*?){mark}", re.S) caption_number = 0 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() # 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}" else: caption = text caption_width = len(caption) + indent if len(caption) > indented_width: caption = textwrap.wrap( caption, indented_width, #initial_indent ="~"*indent, subsequent_indent="~"*indent) caption = "\n".join(caption) caption_width = indented_width line_length = max([len(e) for e in caption.split("\n")]) line = "~" * indent + "-" * line_length return caption + "\n" + line result = caption_rgx.sub(replace, text) return result def txt_footnote_marker(): return "KTEXTFOOTNOTE" def txt_format_footnotes(text, K): footnote_number = 0 footnotes = [] def replace(m): nonlocal footnote_number, footnotes footnote_number += 1 footnotes.append(m.group(1)) return f"[{footnote_number}]" start_mark = "__" + txt_footnote_marker() end_mark = txt_footnote_marker() + "__" footnote_rgx = re.compile(rf"\s*{start_mark}\s+(.*?)\s+{end_mark}", re.S) result = footnote_rgx.sub(replace, text).rstrip() + "\n\n" if footnotes: rule_count = int(round(float(K.Target_txt_width) * 0.4)) result += "-" * rule_count + "\n\n" # Calculate maximum width for right-justified numbers: width = len(f"[{len(footnotes)}]") for n, footnote in enumerate(footnotes, 1): label = f"[{n}]" result += "~" * (width - len(label)) + label + " " + footnote + "\n\n" return result def html_footnote_marker(): return "kt-footnote" def html_format_footnotes(text, K=None): fnum = 0 footnotes = [] def replace(m): nonlocal fnum, footnotes fnum += 1 footnotes.append(m.group(1)) href = f'href ="#_footnote_{fnum}"' id = f'id="_footnote_src_{fnum}"' return f'{fnum}' #return f'{fnum}' start_mark = f"<{html_footnote_marker()}>" end_mark = f"" footnote_rgx = re.compile(rf"\s*{start_mark}\s*(.*?)\s*{end_mark}", re.S) result = footnote_rgx.sub(replace, text).rstrip() + "\n\n" if footnotes: result += '
\n' for n, footnote in enumerate(footnotes, 1): result += f'{n} {footnote}
\n' # Temporarily add room for footnotes to move to the top of the window for the test: result += '
\n' return result