Skip to content

Commit 172eb9b

Browse files
committed
Fix wording section rendering
1 parent ca4d112 commit 172eb9b

2 files changed

Lines changed: 95 additions & 1 deletion

File tree

scrivener/lib/renderer.py

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ def __init__(self, style, body_cmap, content_width, md_dir,
4141
self.headings = []
4242
self.seen_h1 = False
4343
self._in_heading = False
44+
self._wording_context = None
45+
self._in_ins = False
46+
self._in_del = False
4447
self.has_fm_title = has_fm_title
4548
self._build_styles()
4649

@@ -191,9 +194,12 @@ def render(self, tokens):
191194

192195
flowables = []
193196
for div_class, toks in segments:
197+
if div_class and div_class.startswith("wording"):
198+
self._wording_context = div_class
194199
flows = []
195200
for tok in toks:
196201
flows.extend(self._render_token(tok))
202+
self._wording_context = None
197203
if div_class and div_class.startswith("wording"):
198204
flowables.extend(self._wrap_wording(div_class, flows))
199205
else:
@@ -483,11 +489,33 @@ def build_toc_flowables(self):
483489
h1_h = self.ps["h1"].fontSize
484490
return [Spacer(1, h1_h), box, Spacer(1, self.gap * 2)]
485491

492+
def _wording_text_color(self):
493+
"""Return the text color for the current wording context, or None."""
494+
ctx = self._wording_context
495+
if ctx == "wording-add":
496+
return self.ins_color
497+
if ctx == "wording-remove":
498+
return self.del_color
499+
return None
500+
501+
def _wording_wrap(self, text):
502+
"""Apply wording-context color and strikethrough to inline markup."""
503+
color = self._wording_text_color()
504+
if color:
505+
text = f'<font color="{color}">{text}</font>'
506+
wcfg = self.style.get("wording", {})
507+
variant = wcfg.get(self._wording_context, {})
508+
if variant.get("strikethrough"):
509+
text = f"<strike>{text}</strike>"
510+
return text
511+
486512
def _render_paragraph(self, tok):
487513
text = self._inline_children(tok.get("children", []))
488514
if text.strip() == "\\newpage":
489515
return [PageBreak()]
490516
text = self._inject_cjk_fallback(text)
517+
if self._wording_context:
518+
text = self._wording_wrap(text)
491519
return [Paragraph(text, self.ps["body"])]
492520

493521
def _render_mermaid(self, code):
@@ -544,6 +572,21 @@ def _render_block_code(self, tok):
544572
result = self._render_mermaid(raw)
545573
if result:
546574
return result
575+
576+
if self._wording_context:
577+
ctx = self._wording_context
578+
if ctx in ("wording-add", "wording-remove"):
579+
markup = escape_xml(raw)
580+
else:
581+
syntax = self.style.get("syntax", {})
582+
markup = highlight(raw, lang, syntax)
583+
color = self._wording_text_color()
584+
style = ParagraphStyle(
585+
"code_block_wording", parent=self.ps["code_block"],
586+
textColor=parse_color(color) if color else self.ps["code_block"].textColor)
587+
pre = XPreformatted(markup, style)
588+
return [Spacer(1, self.gap_sm), pre, Spacer(1, self.gap_sm)]
589+
547590
syntax = self.style.get("syntax", {})
548591
markup = highlight(raw, lang, syntax)
549592
cb = self.style["code_block"]
@@ -857,7 +900,51 @@ def _render_blank_line(self, tok):
857900

858901
def _render_block_html(self, tok):
859902
raw = tok.get("raw", "")
860-
return [Paragraph(escape_xml(raw), self.ps["body"])]
903+
pre_m = re.match(
904+
r'<pre><code>(.*?)</code></pre>\s*$', raw, re.DOTALL)
905+
if pre_m:
906+
return self._render_pre_code_block(pre_m.group(1))
907+
text = escape_xml(raw)
908+
if self._wording_context:
909+
text = self._wording_wrap(text)
910+
return [Paragraph(text, self.ps["body"])]
911+
912+
def _render_pre_code_block(self, inner):
913+
"""Render a <pre><code>...</code></pre> HTML block as monospace,
914+
with <ins>/<del> converted to ReportLab color markup. HTML entities
915+
like &lt; &gt; &amp; pass through as valid XML."""
916+
ensure_code_family()
917+
markup = inner
918+
markup = re.sub(
919+
r'<ins>(.*?)</ins>',
920+
lambda m: f'<u><font color="{self.ins_color}">{m.group(1)}</font></u>',
921+
markup, flags=re.DOTALL)
922+
markup = re.sub(
923+
r'<del>(.*?)</del>',
924+
lambda m: f'<strike><font color="{self.del_color}">{m.group(1)}</font></strike>',
925+
markup, flags=re.DOTALL)
926+
927+
if self._wording_context:
928+
color = self._wording_text_color()
929+
style = ParagraphStyle(
930+
"pre_code_wording", parent=self.ps["code_block"],
931+
textColor=parse_color(color) if color else self.ps["code_block"].textColor)
932+
else:
933+
style = self.ps["code_block"]
934+
935+
pre = XPreformatted(markup, style)
936+
if self._wording_context:
937+
return [Spacer(1, self.gap_sm), pre, Spacer(1, self.gap_sm)]
938+
939+
cb = self.style["code_block"]
940+
bg = parse_color(self.style["code_bg"])
941+
accent = parse_color(self.style["accent_saturated"])
942+
box = AccentBox(
943+
pre, bg, accent, cb["bar_width"],
944+
cb["left_padding"], cb["right_padding"],
945+
cb["vertical_padding"], radius=0,
946+
cap_shift=self.cap_shift)
947+
return [Spacer(1, self.gap_sm), box, Spacer(1, self.ps["h1"].fontSize)]
861948

862949
def _inline_children(self, children):
863950
if not children:
@@ -900,6 +987,8 @@ def _inline_codespan(self, tok):
900987
sz = self.ps[f"h{self._heading_level}"].fontSize * 0.85
901988
return f'<font name="Code-Bold" size="{sz}">{raw}</font>'
902989
sz = self.ps["code_block"].fontSize
990+
if self._wording_context or self._in_ins or self._in_del:
991+
return f'<font name="Code" size="{sz}">{raw}</font>'
903992
return (f'<font name="Code" size="{sz}" color="{self.code_inline_fg}">'
904993
f'<span backColor="{self.code_inline_bg}">{raw}</span></font>')
905994

@@ -937,12 +1026,16 @@ def _inline_inline_html(self, tok):
9371026
inner = escape_xml(unescape(del_m.group(1)))
9381027
return f'<strike><font color="{self.del_color}">{inner}</font></strike>'
9391028
if raw.startswith("<ins>"):
1029+
self._in_ins = True
9401030
return f'<u><font color="{self.ins_color}">'
9411031
if raw.startswith("</ins>"):
1032+
self._in_ins = False
9421033
return '</font></u>'
9431034
if raw.startswith("<del>"):
1035+
self._in_del = True
9441036
return f'<strike><font color="{self.del_color}">'
9451037
if raw.startswith("</del>"):
1038+
self._in_del = False
9461039
return '</font></strike>'
9471040
if raw.startswith("<sup>"):
9481041
return "<super>"

scrivener/styles/default.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ wording:
174174
wording-remove:
175175
bg: "#fdf2f2"
176176
bar_color: "#A91C21"
177+
strikethrough: true
177178

178179
# --- Front matter visual settings ---
179180
front_matter:

0 commit comments

Comments
 (0)