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