Update docx_builder.py
Browse files- docx_builder.py +40 -9
docx_builder.py
CHANGED
|
@@ -147,20 +147,18 @@ def _add_run(par, token: str, font_size=None):
|
|
| 147 |
if font_size:
|
| 148 |
run.font.size = font_size
|
| 149 |
|
|
|
|
|
|
|
| 150 |
def add_paragraph_with_tokens(doc, raw, style_name="Normal", indent=0, alignment=None, spacing_before=0, spacing_after=0):
|
|
|
|
|
|
|
|
|
|
| 151 |
try:
|
| 152 |
p = doc.add_paragraph(style=style_name)
|
| 153 |
except KeyError:
|
| 154 |
p = doc.add_paragraph(style="Normal")
|
| 155 |
|
| 156 |
-
|
| 157 |
-
p.paragraph_format.left_indent = Inches(indent)
|
| 158 |
-
if alignment:
|
| 159 |
-
p.alignment = alignment
|
| 160 |
-
if spacing_before:
|
| 161 |
-
p.paragraph_format.space_before = Pt(spacing_before)
|
| 162 |
-
if spacing_after:
|
| 163 |
-
p.paragraph_format.space_after = Pt(spacing_after)
|
| 164 |
for part in INLINE_RE.split(raw):
|
| 165 |
if not part:
|
| 166 |
continue
|
|
@@ -168,6 +166,33 @@ def add_paragraph_with_tokens(doc, raw, style_name="Normal", indent=0, alignment
|
|
| 168 |
_add_run(p, part)
|
| 169 |
else:
|
| 170 |
p.add_run(part)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 171 |
return p
|
| 172 |
|
| 173 |
# βββββββββββββββββββββββββ table helper βββββββββββββββββββββ
|
|
@@ -281,6 +306,12 @@ def create_docx_with_layout(text: str, title=None, author=None) -> BytesIO:
|
|
| 281 |
elif align_line.startswith("<right>") and align_line.endswith("</right>"):
|
| 282 |
alignment = WD_ALIGN_PARAGRAPH.RIGHT
|
| 283 |
line = align_line[7:-8].strip()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 284 |
|
| 285 |
# ββ fenced code ββ
|
| 286 |
fence = re.match(r"^```(\w+)?\s*$", line)
|
|
@@ -333,7 +364,7 @@ def create_docx_with_layout(text: str, title=None, author=None) -> BytesIO:
|
|
| 333 |
if m:
|
| 334 |
level = len(m.group(1))
|
| 335 |
heading_text = m.group(2).strip()
|
| 336 |
-
heading_style = f"Heading {level}"
|
| 337 |
add_paragraph_with_tokens(doc, heading_text, style_name=heading_style, alignment=alignment)
|
| 338 |
toc_entries.append((level, heading_text, "1"))
|
| 339 |
continue
|
|
|
|
| 147 |
if font_size:
|
| 148 |
run.font.size = font_size
|
| 149 |
|
| 150 |
+
|
| 151 |
+
# βββββββββββββββββββββββ enhanced paragraph builder βββββββββββββββββββββββ
|
| 152 |
def add_paragraph_with_tokens(doc, raw, style_name="Normal", indent=0, alignment=None, spacing_before=0, spacing_after=0):
|
| 153 |
+
"""
|
| 154 |
+
Adds a paragraph preserving inline formatting and enforced alignment.
|
| 155 |
+
"""
|
| 156 |
try:
|
| 157 |
p = doc.add_paragraph(style=style_name)
|
| 158 |
except KeyError:
|
| 159 |
p = doc.add_paragraph(style="Normal")
|
| 160 |
|
| 161 |
+
# Add text content first
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 162 |
for part in INLINE_RE.split(raw):
|
| 163 |
if not part:
|
| 164 |
continue
|
|
|
|
| 166 |
_add_run(p, part)
|
| 167 |
else:
|
| 168 |
p.add_run(part)
|
| 169 |
+
|
| 170 |
+
# Apply paragraph formatting
|
| 171 |
+
fmt = p.paragraph_format
|
| 172 |
+
if indent:
|
| 173 |
+
fmt.left_indent = Inches(indent)
|
| 174 |
+
if spacing_before:
|
| 175 |
+
fmt.space_before = Pt(spacing_before)
|
| 176 |
+
if spacing_after:
|
| 177 |
+
fmt.space_after = Pt(spacing_after)
|
| 178 |
+
|
| 179 |
+
# Force alignment after adding runs
|
| 180 |
+
if alignment is not None:
|
| 181 |
+
p.alignment = alignment
|
| 182 |
+
else:
|
| 183 |
+
p.alignment = WD_ALIGN_PARAGRAPH.LEFT
|
| 184 |
+
|
| 185 |
+
# XML-level enforcement for safety
|
| 186 |
+
pPr = p._p.get_or_add_pPr()
|
| 187 |
+
jc = OxmlElement("w:jc")
|
| 188 |
+
jc.set(qn("w:val"), {
|
| 189 |
+
WD_ALIGN_PARAGRAPH.LEFT: "left",
|
| 190 |
+
WD_ALIGN_PARAGRAPH.CENTER: "center",
|
| 191 |
+
WD_ALIGN_PARAGRAPH.RIGHT: "right",
|
| 192 |
+
WD_ALIGN_PARAGRAPH.JUSTIFY: "both"
|
| 193 |
+
}.get(p.alignment, "left"))
|
| 194 |
+
pPr.append(jc)
|
| 195 |
+
|
| 196 |
return p
|
| 197 |
|
| 198 |
# βββββββββββββββββββββββββ table helper βββββββββββββββββββββ
|
|
|
|
| 306 |
elif align_line.startswith("<right>") and align_line.endswith("</right>"):
|
| 307 |
alignment = WD_ALIGN_PARAGRAPH.RIGHT
|
| 308 |
line = align_line[7:-8].strip()
|
| 309 |
+
elif align_line.startswith("<left>") and align_line.endswith("</left>"):
|
| 310 |
+
alignment = WD_ALIGN_PARAGRAPH.LEFT
|
| 311 |
+
line = align_line[6:-7].strip()
|
| 312 |
+
elif align_line.startswith("<justify>") and align_line.endswith("</justify>"):
|
| 313 |
+
alignment = WD_ALIGN_PARAGRAPH.JUSTIFY
|
| 314 |
+
line = align_line[9:-10].strip()
|
| 315 |
|
| 316 |
# ββ fenced code ββ
|
| 317 |
fence = re.match(r"^```(\w+)?\s*$", line)
|
|
|
|
| 364 |
if m:
|
| 365 |
level = len(m.group(1))
|
| 366 |
heading_text = m.group(2).strip()
|
| 367 |
+
heading_style = f"Heading {level}"
|
| 368 |
add_paragraph_with_tokens(doc, heading_text, style_name=heading_style, alignment=alignment)
|
| 369 |
toc_entries.append((level, heading_text, "1"))
|
| 370 |
continue
|