Coverage for src/rtflite/rtf/syntax.py: 67%
63 statements
« prev ^ index » next coverage.py v7.10.5, created at 2025-08-25 22:35 +0000
« prev ^ index » next coverage.py v7.10.5, created at 2025-08-25 22:35 +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
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 document. If None, includes all 657 colors.
54 Returns:
55 RTF color table string
56 """
57 from ..services.color_service import color_service
59 return color_service.generate_rtf_color_table(used_colors)
61 @staticmethod
62 def generate_page_settings(
63 width: float,
64 height: float,
65 margins: Sequence[float],
66 orientation: str = "portrait",
67 ) -> str:
68 """Generate RTF page settings.
70 Args:
71 width: Page width in inches
72 height: Page height in inches
73 margins: Margins [left, right, top, bottom, header, footer] in inches
74 orientation: Page orientation ('portrait' or 'landscape')
76 Returns:
77 RTF page settings string
78 """
79 from ..row import Utils
81 # Convert to twips
82 width_twips = int(Utils._inch_to_twip(width))
83 height_twips = int(Utils._inch_to_twip(height))
85 margin_twips = [int(Utils._inch_to_twip(m)) for m in margins]
87 # Add landscape command if needed
88 landscape_cmd = "\\landscape " if orientation == "landscape" else ""
90 return (
91 f"\\paperw{width_twips}\\paperh{height_twips}{landscape_cmd}\n"
92 f"\\margl{margin_twips[0]}\\margr{margin_twips[1]}"
93 f"\\margt{margin_twips[2]}\\margb{margin_twips[3]}"
94 f"\\headery{margin_twips[4]}\\footery{margin_twips[5]}"
95 )
97 @staticmethod
98 def generate_page_break() -> str:
99 """Generate RTF page break."""
100 return "\\page"
102 @staticmethod
103 def generate_paragraph_break() -> str:
104 """Generate RTF paragraph break."""
105 return "\\par"
107 @staticmethod
108 def generate_line_break() -> str:
109 """Generate RTF line break."""
110 return "\\line"
113class RTFDocumentAssembler:
114 """Assembles complete RTF documents from components."""
116 def __init__(self):
117 self.syntax = RTFSyntaxGenerator()
119 def assemble_document(self, components: Mapping[str, Any]) -> str:
120 """Assemble a complete RTF document from components.
122 Args:
123 components: Dictionary containing document components
125 Returns:
126 Complete RTF document string
127 """
128 parts = []
130 # Document start
131 parts.append(self.syntax.generate_document_start())
133 # Font table
134 if "fonts" in components:
135 parts.append(self.syntax.generate_font_table(components["fonts"]))
137 # Page settings
138 if "page_settings" in components:
139 settings = components["page_settings"]
140 parts.append(
141 self.syntax.generate_page_settings(
142 settings["width"],
143 settings["height"],
144 settings["margins"],
145 settings.get("orientation", "portrait"),
146 )
147 )
149 # Content sections
150 content_sections = [
151 "page_header",
152 "page_footer",
153 "title",
154 "subline",
155 "column_headers",
156 "body",
157 "footnotes",
158 "sources",
159 ]
161 for section in content_sections:
162 if section in components and components[section]:
163 if isinstance(components[section], list):
164 parts.extend(components[section])
165 else:
166 parts.append(components[section])
168 # Document end
169 parts.append(self.syntax.generate_document_end())
171 # Join with newlines, filtering out None/empty values
172 return "\n".join(str(part) for part in parts if part)