@@ -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 < > & 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>"
0 commit comments