Coverage for src / rtflite / rtf / syntax.py: 67%
63 statements
« prev ^ index » next coverage.py v7.12.0, created at 2025-11-28 05:09 +0000
« prev ^ index » next coverage.py v7.12.0, created at 2025-11-28 05:09 +0000
1"""RTF syntax generation utilities."""
3from collections.abc import Mapping, Sequence
4from typing import Any
6from ..core.constants import RTFConstants
9class RTFSyntaxGenerator:
10 """Central RTF syntax generator for common RTF operations."""
12 @staticmethod
13 def generate_document_start() -> str:
14 """Generate RTF document start code."""
15 return "{\\rtf1\\ansi\\deff0"
17 @staticmethod
18 def generate_document_end() -> str:
19 """Generate RTF document end code."""
20 return "}"
22 @staticmethod
23 def generate_font_table() -> str:
24 """Generate RTF font table using system fonts.
26 Returns:
27 RTF font table string
28 """
29 from ..row import Utils
31 font_types = Utils._font_type()
32 font_rtf = [f"\\f{i}" for i in range(10)]
33 font_style = font_types["style"]
34 font_name = font_types["name"]
35 font_charset = font_types["charset"]
37 font_table = RTFConstants.Control.FONT_TABLE_START
38 for rtf, style, name, charset in zip(
39 font_rtf, font_style, font_name, font_charset, strict=True
40 ):
41 font_table = (
42 font_table + "{" + rtf + style + charset + "\\fprq2 " + name + ";}\n"
43 )
44 font_table += "}"
45 return font_table
47 @staticmethod
48 def generate_color_table(used_colors: Sequence[str] | None = None) -> str:
49 """Generate RTF color table using comprehensive 657-color support.
51 Args:
52 used_colors: List of color names used in the document.
53 If None, includes all 657 colors.
55 Returns:
56 RTF color table string
57 """
58 from ..services.color_service import color_service
60 return color_service.generate_rtf_color_table(used_colors)
62 @staticmethod
63 def generate_page_settings(
64 width: float,
65 height: float,
66 margins: Sequence[float],
67 orientation: str = "portrait",
68 ) -> str:
69 """Generate RTF page settings.
71 Args:
72 width: Page width in inches
73 height: Page height in inches
74 margins: Margins [left, right, top, bottom, header, footer] in inches
75 orientation: Page orientation ('portrait' or 'landscape')
77 Returns:
78 RTF page settings string
79 """
80 from ..row import Utils
82 # Convert to twips
83 width_twips = int(Utils._inch_to_twip(width))
84 height_twips = int(Utils._inch_to_twip(height))
86 margin_twips = [int(Utils._inch_to_twip(m)) for m in margins]
88 # Add landscape command if needed
89 landscape_cmd = "\\landscape " if orientation == "landscape" else ""
91 return (
92 f"\\paperw{width_twips}\\paperh{height_twips}{landscape_cmd}\n"
93 f"\\margl{margin_twips[0]}\\margr{margin_twips[1]}"
94 f"\\margt{margin_twips[2]}\\margb{margin_twips[3]}"
95 f"\\headery{margin_twips[4]}\\footery{margin_twips[5]}"
96 )
98 @staticmethod
99 def generate_page_break() -> str:
100 """Generate RTF page break."""
101 return "\\page"
103 @staticmethod
104 def generate_paragraph_break() -> str:
105 """Generate RTF paragraph break."""
106 return "\\par"
108 @staticmethod
109 def generate_line_break() -> str:
110 """Generate RTF line break."""
111 return "\\line"
114class RTFDocumentAssembler:
115 """Assembles complete RTF documents from components."""
117 def __init__(self):
118 self.syntax = RTFSyntaxGenerator()
120 def assemble_document(self, components: Mapping[str, Any]) -> str:
121 """Assemble a complete RTF document from components.
123 Args:
124 components: Dictionary containing document components
126 Returns:
127 Complete RTF document string
128 """
129 parts = []
131 # Document start
132 parts.append(self.syntax.generate_document_start())
134 # Font table
135 if "fonts" in components:
136 parts.append(self.syntax.generate_font_table(components["fonts"]))
138 # Page settings
139 if "page_settings" in components:
140 settings = components["page_settings"]
141 parts.append(
142 self.syntax.generate_page_settings(
143 settings["width"],
144 settings["height"],
145 settings["margins"],
146 settings.get("orientation", "portrait"),
147 )
148 )
150 # Content sections
151 content_sections = [
152 "page_header",
153 "page_footer",
154 "title",
155 "subline",
156 "column_headers",
157 "body",
158 "footnotes",
159 "sources",
160 ]
162 for section in content_sections:
163 if section in components and components[section]:
164 if isinstance(components[section], list):
165 parts.extend(components[section])
166 else:
167 parts.append(components[section])
169 # Document end
170 parts.append(self.syntax.generate_document_end())
172 # Join with newlines, filtering out None/empty values
173 return "\n".join(str(part) for part in parts if part)