2026-07-18 18:48:23 +02:00
|
|
|
import re
|
|
|
|
|
import kutil
|
|
|
|
|
|
|
|
|
|
def unescape_ktesc(s):
|
|
|
|
|
"""Resolve KTESC escape markers back to original characters.
|
|
|
|
|
Use this for argument values used programmatically (filenames, etc.)."""
|
|
|
|
|
def replace(match):
|
|
|
|
|
hex_chars = match.group(1)
|
|
|
|
|
result = ''
|
|
|
|
|
for i in range(0, len(hex_chars), 4):
|
|
|
|
|
result += chr(int(hex_chars[i:i+4], 16))
|
|
|
|
|
return result
|
|
|
|
|
return re.sub(r'KTESC([0-9a-f]+)KTESC', replace, s)
|
|
|
|
|
|
2026-08-23 20:48:26 +02:00
|
|
|
def escape_ktesc(s):
|
|
|
|
|
"""A KTESC marker for the characters of s -- the mirror of
|
|
|
|
|
Target::escape_marker() in mac/target.cpp (4 lowercase hex digits per
|
|
|
|
|
byte). The marker is inert through every re-read and through the
|
|
|
|
|
target's typographic transform pass, and decodes back to the characters
|
|
|
|
|
at final escape resolution (ktesc_resolve)."""
|
|
|
|
|
return 'KTESC' + ''.join(f'{ord(c):04x}' for c in s) + 'KTESC'
|
|
|
|
|
|
|
|
|
|
# The characters the SKS targets' typographic transforms act on: the
|
|
|
|
|
# hyphen runs (-- and ---), the quote conventions (` ' `` ''), and ~.
|
|
|
|
|
# Verbatim text (@c, @code, @source_listing) must reach the output exactly
|
|
|
|
|
# as written, so the code klammers hide each of these as a KTESC marker --
|
|
|
|
|
# a transform source can then never match -- and the markers decode after
|
|
|
|
|
# the transform pass has run.
|
|
|
|
|
# SYNC: the transform tables of the html and txt targets in
|
|
|
|
|
# sks/target/target.k. A transform built from a new character needs it
|
|
|
|
|
# added here, or verbatim text will show the transformed form.
|
|
|
|
|
TYPOGRAPHIC_CHARS = "-'`~"
|
|
|
|
|
|
|
|
|
|
def hide_typographic(s):
|
|
|
|
|
"""Hide the typographically active characters of s as KTESC markers so
|
|
|
|
|
the target's transform pass cannot change verbatim text."""
|
|
|
|
|
for c in TYPOGRAPHIC_CHARS:
|
|
|
|
|
s = s.replace(c, escape_ktesc(c))
|
|
|
|
|
return s
|
|
|
|
|
|
2026-07-18 18:48:23 +02:00
|
|
|
class Klammer_base:
|
|
|
|
|
def __init__(self, K):
|
|
|
|
|
args = {k:v for k, v in K.__dict__.items()
|
|
|
|
|
if not k.startswith('__')}
|
|
|
|
|
for key in args:
|
|
|
|
|
setattr(self, key, args[key])
|
|
|
|
|
|
|
|
|
|
def html(self):
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
def tex(self):
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
def txt(self):
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
def show(self, label=""):
|
|
|
|
|
if label:
|
|
|
|
|
print(label)
|
|
|
|
|
pprint.pprint(self.__dict__)
|
|
|
|
|
|
2026-09-12 18:11:48 +02:00
|
|
|
# A target that :includes another renders through the included one's
|
|
|
|
|
# method: pdf is tex compiled, ansi is txt with escape sequences. The
|
|
|
|
|
# Python side cannot see :includes, so the relation is restated here.
|
|
|
|
|
# SYNC: the @@@target declarations in sks/target/target.k.
|
|
|
|
|
TARGET_METHOD = {"html": "html",
|
|
|
|
|
"tex": "tex", "pdf": "tex",
|
|
|
|
|
"txt": "txt", "ansi": "txt"}
|
|
|
|
|
|
2026-07-18 18:48:23 +02:00
|
|
|
def __str__(self):
|
|
|
|
|
result = None
|
2026-09-12 18:11:48 +02:00
|
|
|
method = Klammer_base.TARGET_METHOD.get(self.K_target)
|
|
|
|
|
if method == 'html':
|
2026-07-18 18:48:23 +02:00
|
|
|
result = self.html()
|
2026-09-12 18:11:48 +02:00
|
|
|
elif method == 'tex':
|
2026-07-18 18:48:23 +02:00
|
|
|
result = kutil.escape(self.tex())
|
2026-09-12 18:11:48 +02:00
|
|
|
elif method == 'txt':
|
2026-07-18 18:48:23 +02:00
|
|
|
result = self.txt()
|
|
|
|
|
if result is None:
|
|
|
|
|
raise Exception(
|
2026-09-12 18:11:48 +02:00
|
|
|
f'Target "{self.K_target}" is undefined for klammer class '
|
|
|
|
|
f'"{self.__class__.__name__}"')
|
2026-07-18 18:48:23 +02:00
|
|
|
return result
|