diff --git a/sample_files/test_corpus/test_excel.xlsx b/sample_files/test_corpus/test_excel.xlsx
index cc68113..bd71783 100644
Binary files a/sample_files/test_corpus/test_excel.xlsx and b/sample_files/test_corpus/test_excel.xlsx differ
diff --git a/sample_files/test_corpus/test_pdf.pdf b/sample_files/test_corpus/test_pdf.pdf
index 966a7d5..adbfa53 100644
Binary files a/sample_files/test_corpus/test_pdf.pdf and b/sample_files/test_corpus/test_pdf.pdf differ
diff --git a/sample_files/test_corpus/test_pptx.pptx b/sample_files/test_corpus/test_pptx.pptx
index e5c946e..5b2c481 100644
Binary files a/sample_files/test_corpus/test_pptx.pptx and b/sample_files/test_corpus/test_pptx.pptx differ
diff --git a/sample_files/test_corpus/test_word.docx b/sample_files/test_corpus/test_word.docx
index 9f3d3e8..147332e 100644
Binary files a/sample_files/test_corpus/test_word.docx and b/sample_files/test_corpus/test_word.docx differ
diff --git a/scripts/generate_test_files.py b/scripts/generate_test_files.py
index 2d4424b..7ea2292 100644
--- a/scripts/generate_test_files.py
+++ b/scripts/generate_test_files.py
@@ -1,44 +1,52 @@
"""
-Generate test files for B1 (Word/Excel), B2 (PPTX), B3 (PDF).
+Generate COMPLEX test files for B1/B2/B3 stress-testing.
-Outputs to sample_files/test_corpus/:
- - test_word.docx (hyperlinks + footnotes + chart)
- - test_excel.xlsx (cell comments + hyperlinks + chart)
- - test_pptx.pptx (SmartArt + chart + placeholders)
- - test_pdf.pdf (hyperlinks + image + multi-page)
+These files are designed to catch real format-preservation bugs:
+ - Many hyperlinks (some nested, some with anchors)
+ - Many footnotes and endnotes
+ - Long tables with formatting
+ - Multiple charts
+ - Headers/footers with different content per section
+ - Mixed languages
+ - Images embedded in tables
+ - Complex page layouts
-Run from the project root:
- python scripts/generate_test_files.py
+Output: sample_files/test_corpus/test_*.{docx,xlsx,pptx,pdf}
"""
import os
-import sys
+import io
+import zipfile
+import shutil
from pathlib import Path
from docx import Document
-from docx.shared import Pt, Inches, RGBColor
+from docx.shared import Pt, Inches, RGBColor, Cm
from docx.oxml import OxmlElement
from docx.oxml.ns import qn
-from docx.enum.text import WD_BREAK
+from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_BREAK
+from docx.enum.section import WD_SECTION, WD_ORIENT
from openpyxl import Workbook
from openpyxl.comments import Comment
-from openpyxl.chart import BarChart, Reference
+from openpyxl.chart import BarChart, LineChart, PieChart, Reference
from openpyxl.worksheet.hyperlink import Hyperlink
+from openpyxl.formatting.rule import ColorScaleRule
+from openpyxl.styles import Font, PatternFill, Alignment
from pptx import Presentation
-from pptx.util import Inches as PInches, Pt as PPt
+from pptx.util import Inches as PInches, Pt as PPt, Emu
from pptx.enum.shapes import MSO_SHAPE_TYPE
-
-import fitz # PyMuPDF
+from pptx.dml.color import RGBColor as PRGBColor
+import fitz
OUT_DIR = Path("sample_files/test_corpus")
OUT_DIR.mkdir(parents=True, exist_ok=True)
# ===========================================================================
-# Word — B1 (W1 hyperlinks, W2 footnotes, W4 chart)
+# Helpers
# ===========================================================================
-def _add_hyperlink(paragraph, url: str, text: str):
- """Insert an external hyperlink into a paragraph."""
+def _add_hyperlink(paragraph, url: str, text: str, color="0563C1", underline=True):
+ """Add an external hyperlink to a paragraph."""
part = paragraph.part
r_id = part.relate_to(
url,
@@ -50,271 +58,828 @@ def _add_hyperlink(paragraph, url: str, text: str):
new_run = OxmlElement("w:r")
rPr = OxmlElement("w:rPr")
- rStyle = OxmlElement("w:rStyle")
- rStyle.set(qn("w:val"), "Hyperlink")
- rPr.append(rStyle)
+ rFonts = OxmlElement("w:rFonts")
+ rFonts.set(qn("w:ascii"), "Calibri")
+ rFonts.set(qn("w:hAnsi"), "Calibri")
+ rPr.append(rFonts)
+ if color:
+ c = OxmlElement("w:color")
+ c.set(qn("w:val"), color)
+ rPr.append(c)
+ if underline:
+ u = OxmlElement("w:u")
+ u.set(qn("w:val"), "single")
+ rPr.append(u)
new_run.append(rPr)
-
- new_text = OxmlElement("w:t")
- new_text.text = text
- new_text.set(qn("xml:space"), "preserve")
- new_run.append(new_text)
-
+ t = OxmlElement("w:t")
+ t.text = text
+ t.set(qn("xml:space"), "preserve")
+ new_run.append(t)
hyperlink.append(new_run)
paragraph._p.append(hyperlink)
-def _ensure_footnotes_part(doc_path: Path) -> None:
- """Inject a footnotes.xml part into a docx if it doesn't have one.
- python-docx doesn't create this part by default."""
- import zipfile
- footnotes_xml = b"""
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- This is a footnote about machine learning.
-
-
-
-
-
-
-
-
-
- A second footnote with technical details.
-
-
-
-"""
- tmp = doc_path.with_suffix(".tmp")
- with zipfile.ZipFile(doc_path, "r") as zin, \
- zipfile.ZipFile(tmp, "w", zipfile.ZIP_DEFLATED) as zout:
- names = zin.namelist()
- items = {n: zin.read(n) for n in names}
- items["word/footnotes.xml"] = footnotes_xml
+def _add_anchor_hyperlink(paragraph, anchor: str, text: str):
+ """Add an internal anchor hyperlink."""
+ hyperlink = OxmlElement("w:hyperlink")
+ hyperlink.set(qn("w:anchor"), anchor)
+ new_run = OxmlElement("w:r")
+ rPr = OxmlElement("w:rPr")
+ rStyle = OxmlElement("w:rStyle")
+ rStyle.set(qn("w:val"), "Hyperlink")
+ rPr.append(rStyle)
+ new_run.append(rPr)
+ t = OxmlElement("w:t")
+ t.text = text
+ t.set(qn("xml:space"), "preserve")
+ new_run.append(t)
+ hyperlink.append(new_run)
+ paragraph._p.append(hyperlink)
- # Update content types
- ct = items["[Content_Types].xml"].decode("utf-8")
- override = ('')
- if override not in ct:
- ct = ct.replace("", f"{override}")
- items["[Content_Types].xml"] = ct.encode("utf-8")
- # Update document rels
- rels_path = "word/_rels/document.xml.rels"
- if rels_path in items:
- rels = items[rels_path].decode("utf-8")
- new_rel = ('')
- if "rIdFootnotes" not in rels:
- rels = rels.replace("", f"{new_rel}")
- items[rels_path] = rels.encode("utf-8")
-
- for n, data in items.items():
- zout.writestr(n, data)
- tmp.replace(doc_path)
+def _add_bookmark(paragraph, name: str, bookmark_id: int):
+ """Insert a bookmark into a paragraph."""
+ start = OxmlElement("w:bookmarkStart")
+ start.set(qn("w:id"), str(bookmark_id))
+ start.set(qn("w:name"), name)
+ end = OxmlElement("w:bookmarkEnd")
+ end.set(qn("w:id"), str(bookmark_id))
+ paragraph._p.insert(0, start)
+ paragraph._p.append(end)
def _add_footnote_reference(paragraph, footnote_id: int):
- """Insert a footnote reference into a paragraph."""
+ """Add a footnote reference at end of paragraph."""
run = paragraph.add_run()
fn_ref = OxmlElement("w:footnoteReference")
fn_ref.set(qn("w:id"), str(footnote_id))
run._r.append(fn_ref)
+def _add_endnote_reference(paragraph, endnote_id: int):
+ """Add an endnote reference at end of paragraph."""
+ run = paragraph.add_run()
+ en_ref = OxmlElement("w:endnoteReference")
+ en_ref.set(qn("w:id"), str(endnote_id))
+ run._r.append(en_ref)
+
+
+def _inject_footnotes_and_endnotes(docx_path: Path, n_footnotes: int, n_endnotes: int):
+ """Inject footnotes.xml and endnotes.xml with N entries each."""
+ fn_xml = '\n'
+ fn_xml += '\n'
+ fn_xml += ' \n'
+ fn_xml += ' \n'
+
+ footnote_texts = [
+ "This footnote references a recent study on machine translation quality.",
+ "See the appendix for additional technical details on this topic.",
+ "Translation quality is often measured using BLEU or chrF scores.",
+ "Neural machine translation has largely replaced statistical methods since 2016.",
+ "Source language identification is critical for low-resource languages.",
+ "For a comprehensive review of transformer architectures, see Vaswani et al. 2017.",
+ "Back-translation is a common technique for data augmentation in MT.",
+ "Domain adaptation can significantly improve performance on specialized texts.",
+ "Low-resource languages remain a major challenge in modern MT systems.",
+ "Quality estimation (QE) predicts translation quality without reference translations.",
+ ]
+ for i in range(1, n_footnotes + 1):
+ text = footnote_texts[(i - 1) % len(footnote_texts)]
+ fn_xml += f' \n'
+ fn_xml += ' \n'
+ fn_xml += ' \n'
+ fn_xml += f' {text}\n'
+ fn_xml += ' \n'
+ fn_xml += ' \n'
+ fn_xml += '\n'
+
+ en_xml = '\n'
+ en_xml += '\n'
+ en_xml += ' \n'
+ en_xml += ' \n'
+
+ endnote_texts = [
+ "This endnote contains a URL: https://example.com/source1 for further reading.",
+ "Reference: Smith, J. et al. (2023). Advanced Translation Techniques. Publisher.",
+ "For a complete bibliography, see the References section at the end of this document.",
+ "Note that some technical terms may have multiple valid translations depending on context.",
+ "Endnotes are collected at the end of the document, unlike footnotes which appear on each page.",
+ ]
+ for i in range(1, n_endnotes + 1):
+ text = endnote_texts[(i - 1) % len(endnote_texts)]
+ en_xml += f' \n'
+ en_xml += ' \n'
+ en_xml += ' \n'
+ en_xml += f' {text}\n'
+ en_xml += ' \n'
+ en_xml += ' \n'
+ en_xml += '\n'
+
+ tmp = docx_path.with_suffix(".tmp")
+ with zipfile.ZipFile(docx_path, "r") as zin, \
+ zipfile.ZipFile(tmp, "w", zipfile.ZIP_DEFLATED) as zout:
+ items = {n: zin.read(n) for n in zin.namelist()}
+ items["word/footnotes.xml"] = fn_xml.encode("utf-8")
+ items["word/endnotes.xml"] = en_xml.encode("utf-8")
+
+ ct = items["[Content_Types].xml"].decode("utf-8")
+ for part, ctype in [
+ ("word/footnotes.xml", "application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml"),
+ ("word/endnotes.xml", "application/vnd.openxmlformats-officedocument.wordprocessingml.endnotes+xml"),
+ ]:
+ override = f''
+ if override not in ct:
+ ct = ct.replace("", f"{override}")
+ items["[Content_Types].xml"] = ct.encode("utf-8")
+
+ rels_path = "word/_rels/document.xml.rels"
+ if rels_path in items:
+ rels = items[rels_path].decode("utf-8")
+ for rid, rtype, target in [
+ ("rIdFootnotes", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/footnotes", "footnotes.xml"),
+ ("rIdEndnotes", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/endnotes", "endnotes.xml"),
+ ]:
+ if rid not in rels:
+ new_rel = f''
+ rels = rels.replace("", f"{new_rel}")
+ items[rels_path] = rels.encode("utf-8")
+
+ for n, data in items.items():
+ zout.writestr(n, data)
+ tmp.replace(docx_path)
+
+
+# ===========================================================================
+# COMPLEX Word — B1 stress test
+# ===========================================================================
+
def generate_word():
out = OUT_DIR / "test_word.docx"
doc = Document()
- # Title
- title = doc.add_heading("Office Translator — Test Document", level=1)
+ # ============ Title page ============
+ title = doc.add_heading("Annual Translation Quality Report 2024", level=0)
+ title.alignment = WD_ALIGN_PARAGRAPH.CENTER
- # Paragraph with hyperlinks (B1 W1)
- p1 = doc.add_paragraph("This document is used to test the translation pipeline. ")
- p1.add_run("Visit our ")
- _add_hyperlink(p1, "https://www.example.com", "main website")
- p1.add_run(" or check the ")
- _add_hyperlink(p1, "https://github.com/sepehr/office_translator", "GitHub repository")
- p1.add_run(" for more information.")
- p1.add_run(" You can also read the ")
- _add_hyperlink(p1, "https://en.wikipedia.org/wiki/Translation", "Wikipedia article on translation")
- p1.add_run(".")
+ p = doc.add_paragraph()
+ p.alignment = WD_ALIGN_PARAGRAPH.CENTER
+ p.add_run("Prepared by the Office Translator Quality Team").italic = True
+ p = doc.add_paragraph()
+ p.alignment = WD_ALIGN_PARAGRAPH.CENTER
+ _add_hyperlink(p, "https://www.office-translator.example.com", "www.office-translator.example.com")
+ doc.add_paragraph().add_run().add_break(WD_BREAK.PAGE)
- # Paragraph with footnotes (B1 W2)
- p2 = doc.add_paragraph(
- "Translation is the communication of the meaning of a source-language text "
- "by means of an equivalent target-language text"
- )
- _add_footnote_reference(p2, 1)
- p2.add_run(
- ". This is a complex task that requires understanding of both languages, "
- "the context, and the cultural nuances"
- )
- _add_footnote_reference(p2, 2)
- p2.add_run(".")
+ # ============ Table of contents with anchor links ============
+ doc.add_heading("Table of Contents", level=1)
+ p = doc.add_paragraph()
+ _add_anchor_hyperlink(p, "section_intro", "1. Introduction")
+ p = doc.add_paragraph()
+ _add_anchor_hyperlink(p, "section_methodology", "2. Methodology")
+ p = doc.add_paragraph()
+ _add_anchor_hyperlink(p, "section_results", "3. Results")
+ p = doc.add_paragraph()
+ _add_anchor_hyperlink(p, "section_conclusion", "4. Conclusion")
+ p = doc.add_paragraph()
+ _add_anchor_hyperlink(p, "section_appendix", "5. Appendix")
- # More text
- doc.add_paragraph(
- "The quality of a translation depends on many factors including the "
- "translator's expertise, the tools used, and the domain of the text. "
- "Modern machine translation systems use neural networks to produce "
- "increasingly accurate results."
+ doc.add_paragraph().add_run().add_break(WD_BREAK.PAGE)
+
+ # ============ Section 1: Introduction ============
+ h = doc.add_heading("1. Introduction", level=1)
+ _add_bookmark(h, "section_intro", 1)
+
+ p = doc.add_paragraph(
+ "This report summarizes the translation quality metrics collected by our "
+ "system during 2024. The data spans "
)
+ _add_hyperlink(p, "https://www.example.com/dataset-2024", "47 language pairs")
+ p.add_run(" and includes both human-rated and automated evaluations. For a full "
+ "description of the dataset, see our ")
+ _add_hyperlink(p, "https://www.example.com/paper.pdf", "technical paper")
+ p.add_run(".")
+ _add_footnote_reference(p, 1)
+ p.add_run(" The remainder of this document is organized as follows: section 2 "
+ "describes the methodology, section 3 presents the results")
+ _add_footnote_reference(p, 2)
+ p.add_run(", and section 4 concludes with recommendations.")
+
+ p = doc.add_paragraph(
+ "Key terms used throughout this document are defined in the Glossary "
+ "(see Appendix A). Readers interested in the mathematical background "
+ "should consult the supplementary material"
+ )
+ _add_endnote_reference(p, 1)
+ p.add_run(".")
+
+ # ============ Section 2: Methodology (with bullets, numbered list) ============
+ h = doc.add_heading("2. Methodology", level=1)
+ _add_bookmark(h, "section_methodology", 2)
doc.add_paragraph(
- "This test document contains multiple paragraphs with varied content to "
- "give the translation system something substantial to work with. Each "
- "paragraph should be translated as a coherent unit, with hyperlinks "
- "and footnotes preserved in their proper places."
+ "We evaluated translations using three complementary approaches:"
)
+ # Bulleted list
+ for item in [
+ "BLEU score (Papineni et al., 2002) — measures n-gram overlap with reference translations",
+ "chrF score (Popović, 2015) — character-level F-score, more robust than BLEU for morphologically rich languages",
+ "Human evaluation on a 1-5 Likert scale — performed by professional translators",
+ ]:
+ p = doc.add_paragraph(item, style="List Bullet")
+
+ # Numbered list
+ doc.add_paragraph("The evaluation pipeline consists of the following steps:")
+ for i, item in enumerate([
+ "Collect source-target pairs from production traffic",
+ "Run automatic metrics (BLEU, chrF) on each pair",
+ "Sample 500 pairs per language for human evaluation",
+ "Compute aggregate quality scores with confidence intervals",
+ ], start=1):
+ p = doc.add_paragraph(f"{item}", style="List Number")
+
+ # ============ Section 3: Results (with a real table) ============
+ h = doc.add_heading("3. Results", level=1)
+ _add_bookmark(h, "section_results", 3)
+
+ doc.add_paragraph(
+ "Table 1 summarizes the BLEU and chrF scores across the top 10 language pairs. "
+ "Higher scores indicate better translation quality. The full breakdown "
+ "is available in the supplementary CSV file"
+ )
+ _add_footnote_reference(doc.paragraphs[-1], 3)
+ doc.paragraphs[-1].add_run(".")
+
+ # A real formatted table with 11 rows
+ table = doc.add_table(rows=11, cols=5)
+ table.style = "Light Grid Accent 1"
+ table.alignment = WD_ALIGN_PARAGRAPH.CENTER
+ hdr = table.rows[0].cells
+ hdr[0].text = "Language Pair"
+ hdr[1].text = "Documents"
+ hdr[2].text = "BLEU"
+ hdr[3].text = "chrF"
+ hdr[4].text = "Human Score"
+ # Make header bold
+ for c in hdr:
+ for p in c.paragraphs:
+ for r in p.runs:
+ r.bold = True
+
+ data = [
+ ("EN → FR", "12,500", "48.2", "72.1", "4.31"),
+ ("EN → ES", "10,200", "51.7", "74.3", "4.45"),
+ ("EN → DE", "9,800", "45.9", "70.5", "4.18"),
+ ("EN → IT", "7,300", "47.1", "71.2", "4.22"),
+ ("EN → PT", "5,600", "49.3", "72.8", "4.35"),
+ ("EN → JA", "4,200", "32.4", "61.2", "3.78"),
+ ("EN → ZH", "4,100", "30.1", "58.9", "3.65"),
+ ("EN → AR", "3,800", "28.6", "55.4", "3.51"),
+ ("EN → RU", "3,200", "39.7", "65.8", "3.92"),
+ ("EN → KO", "2,900", "31.2", "60.1", "3.71"),
+ ]
+ for i, (pair, docs, bleu, chrf, human) in enumerate(data, start=1):
+ row = table.rows[i].cells
+ row[0].text = pair
+ row[1].text = docs
+ row[2].text = bleu
+ row[3].text = chrf
+ row[4].text = human
+
+ # Add a paragraph after the table
+ doc.add_paragraph(
+ "As shown in Table 1, European language pairs achieve the highest scores, "
+ "while pairs involving Asian or right-to-left scripts lag behind. This gap "
+ "is consistent with findings from the WMT evaluation campaigns"
+ )
+ _add_footnote_reference(doc.paragraphs[-1], 4)
+ doc.paragraphs[-1].add_run(".")
+
+ # Inline image
+ p = doc.add_paragraph()
+ p.add_run("Figure 1: Distribution of quality scores by language family.").italic = True
+
+ # ============ Section 4: Conclusion (with many hyperlinks) ============
+ h = doc.add_heading("4. Conclusion", level=1)
+ _add_bookmark(h, "section_conclusion", 4)
+
+ p = doc.add_paragraph(
+ "The 2024 evaluation confirms that our translation system performs best on "
+ "high-resource European language pairs, with significant room for improvement "
+ "on Asian and right-to-left languages. Future work should focus on:"
+ )
+ for item in [
+ "Increasing training data for low-resource pairs (see our data augmentation plan)",
+ "Improving handling of domain-specific terminology (legal, medical, technical)",
+ "Reducing latency for real-time translation use cases",
+ "Better support for mixed-language documents (code-switching)",
+ ]:
+ p = doc.add_paragraph(item, style="List Bullet")
+
+ p = doc.add_paragraph(
+ "For more information, consult our "
+ )
+ _add_hyperlink(p, "https://blog.office-translator.example.com/2024-review", "2024 year in review blog post")
+ p.add_run(", the ")
+ _add_hyperlink(p, "https://github.com/sepehr/office_translator", "GitHub repository")
+ p.add_run(", or contact us at ")
+ _add_hyperlink(p, "mailto:quality@office-translator.example.com", "quality@office-translator.example.com")
+ p.add_run(".")
+ _add_endnote_reference(p, 2)
+ p.add_run(" Additional benchmarks are available on our ")
+ _add_hyperlink(p, "https://huggingface.co/spaces/office-translator/benchmarks", "HuggingFace leaderboard")
+ p.add_run(".")
+
+ # ============ Section 5: Appendix ============
+ h = doc.add_heading("5. Appendix", level=1)
+ _add_bookmark(h, "section_appendix", 5)
+
+ doc.add_heading("Appendix A: Glossary", level=2)
+ glossary = doc.add_table(rows=6, cols=2)
+ glossary.style = "Light List Accent 1"
+ glossary.rows[0].cells[0].text = "Term"
+ glossary.rows[0].cells[1].text = "Definition"
+ glossary_data = [
+ ("BLEU", "Bilingual Evaluation Understudy — an n-gram overlap metric."),
+ ("chrF", "Character-level F-score — robust to morphological variation."),
+ ("QE", "Quality Estimation — predicting translation quality without references."),
+ ("MT", "Machine Translation — automated translation between natural languages."),
+ ("LM", "Language Model — a probabilistic model of text sequences."),
+ ]
+ for i, (term, defn) in enumerate(glossary_data, start=1):
+ glossary.rows[i].cells[0].text = term
+ glossary.rows[i].cells[1].text = defn
+
+ doc.add_heading("Appendix B: References", level=2)
+ p = doc.add_paragraph()
+ _add_hyperlink(p, "https://www.aclweb.org/anthology/P02-1040.pdf", "Papineni et al. (2002). BLEU.")
+ p = doc.add_paragraph()
+ _add_hyperlink(p, "https://www.statmt.org/wmt15/pdf/WMT19.pdf", "Bojar et al. (2015). WMT15.")
+ p = doc.add_paragraph()
+ _add_hyperlink(p, "https://arxiv.org/abs/1706.03762", "Vaswani et al. (2017). Attention is all you need.")
+ p = doc.add_paragraph()
+ _add_hyperlink(p, "https://www.statmt.org/wmt24/", "WMT 2024 General Translation Task")
+ p = doc.add_paragraph()
+ _add_hyperlink(p, "https://github.com/mjpost/sacrebleu", "SacreBLEU reference implementation")
+
+ doc.add_paragraph().add_run().add_break(WD_BREAK.PAGE)
+
+ # ============ Section 3: NEW SECTION - Page orientation change ============
+ # Add a new section with landscape orientation
+ new_section = doc.add_section(WD_SECTION.NEW_PAGE)
+ new_section.orientation = WD_ORIENT.LANDSCAPE
+ new_section.page_width, new_section.page_height = new_section.page_height, new_section.page_width
+
+ doc.add_heading("Appendix C: Detailed Performance Metrics (Landscape)", level=2)
+ p = doc.add_paragraph(
+ "This appendix presents detailed per-domain BLEU scores in a landscape "
+ "orientation for readability. The data covers four domains: General, Legal, "
+ "Medical, and Technical. "
+ )
+ _add_footnote_reference(p, 5)
+ p.add_run("Domain detection is performed automatically by our classifier")
+
+ # Wide table
+ domain_table = doc.add_table(rows=6, cols=6)
+ domain_table.style = "Light Grid Accent 1"
+ headers = ["Language Pair", "General", "Legal", "Medical", "Technical", "Average"]
+ for i, h in enumerate(headers):
+ domain_table.rows[0].cells[i].text = h
+ for para in domain_table.rows[0].cells[i].paragraphs:
+ for r in para.runs:
+ r.bold = True
+ domain_data = [
+ ("EN → FR", "48.2", "55.1", "52.3", "47.8", "50.9"),
+ ("EN → ES", "51.7", "58.3", "55.1", "50.2", "53.8"),
+ ("EN → DE", "45.9", "53.7", "50.2", "46.1", "49.0"),
+ ("EN → IT", "47.1", "54.2", "51.6", "47.5", "50.1"),
+ ("EN → JA", "32.4", "38.1", "36.5", "33.7", "35.2"),
+ ]
+ for i, row_data in enumerate(domain_data, start=1):
+ for j, v in enumerate(row_data):
+ domain_table.rows[i].cells[j].text = v
+
+ # Header with the doc title (for header/footer test)
+ header = doc.sections[0].header
+ header.paragraphs[0].text = "Office Translator — Confidential Quality Report"
+ header.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.RIGHT
+
+ footer = doc.sections[0].footer
+ footer.paragraphs[0].text = "© 2024 Office Translator — Internal Use Only"
+ footer.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER
+
doc.save(out)
- # Add footnotes part (python-docx doesn't create one by default)
- _ensure_footnotes_part(out)
- print(f"[OK] Generated {out} ({out.stat().st_size} bytes)")
+ _inject_footnotes_and_endnotes(out, n_footnotes=10, n_endnotes=5)
+ print(f"[OK] {out.name}: {out.stat().st_size} bytes")
+ print(f" - Headings: 5 sections + 2 appendices")
+ print(f" - Hyperlinks: ~15 external + 5 internal anchors")
+ print(f" - Footnotes: 10, Endnotes: 5")
+ print(f" - Tables: 3 (with formatting)")
+ print(f" - Lists: bulleted + numbered")
+ print(f" - Sections: portrait + landscape")
# ===========================================================================
-# Excel — B1 (E2 cell comments, E3 hyperlinks, E4 chart)
+# COMPLEX Excel — B1 stress test
# ===========================================================================
def generate_excel():
out = OUT_DIR / "test_excel.xlsx"
wb = Workbook()
- ws = wb.active
- ws.title = "Sales"
- # Header
- ws["A1"] = "Product"
- ws["B1"] = "Revenue"
- ws["C1"] = "Comments"
- ws["D1"] = "Link"
+ # ============ Sheet 1: Sales (with chart) ============
+ ws1 = wb.active
+ ws1.title = "Sales 2024"
+ headers = ["Month", "Product", "Region", "Quantity", "Unit Price (USD)", "Total (USD)", "Sales Rep", "Status", "Link"]
+ for i, h in enumerate(headers, start=1):
+ c = ws1.cell(row=1, column=i, value=h)
+ c.font = Font(bold=True, color="FFFFFF")
+ c.fill = PatternFill("solid", fgColor="2F5496")
+ c.alignment = Alignment(horizontal="center")
- # Data
- ws["A2"] = "Widget Pro"
- ws["B2"] = 1500
- ws["C2"] = "Best-seller of the year" # B1 E2: cell comment
- ws["D2"] = "Product page" # B1 E3: hyperlink label
- ws["D2"].hyperlink = Hyperlink(ref="D2", target="https://example.com/widget-pro")
+ months = ["January", "February", "March", "April", "May", "June",
+ "July", "August", "September", "October", "November", "December"]
+ products = ["Widget Pro", "Gadget Plus", "ToolKit", "SuperApp"]
+ regions = ["North", "South", "East", "West", "Central"]
+ reps = ["Alice Johnson", "Bob Smith", "Carol Davis", "Dan Brown", "Eve Wilson"]
+ statuses = ["Completed", "Pending", "Shipped", "Delivered"]
- ws["A3"] = "Gadget Plus"
- ws["B3"] = 2300
- ws["C3"] = "New release, promising growth"
- ws["D3"] = "Details"
- ws["D3"].hyperlink = Hyperlink(ref="D3", target="https://example.com/gadget")
+ # 60 rows of realistic-looking sales data
+ import random
+ random.seed(42)
+ for row in range(2, 62):
+ ws1.cell(row=row, column=1, value=random.choice(months))
+ ws1.cell(row=row, column=2, value=random.choice(products))
+ ws1.cell(row=row, column=3, value=random.choice(regions))
+ qty = random.randint(10, 500)
+ price = round(random.uniform(15.0, 350.0), 2)
+ ws1.cell(row=row, column=4, value=qty)
+ ws1.cell(row=row, column=5, value=price)
+ ws1.cell(row=row, column=6, value=qty * price)
+ ws1.cell(row=row, column=7, value=random.choice(reps))
+ ws1.cell(row=row, column=8, value=random.choice(statuses))
+ # Hyperlink in column 9
+ c = ws1.cell(row=row, column=9, value=f"Invoice #{1000 + row}")
+ c.hyperlink = Hyperlink(ref=c.coordinate, target=f"https://invoices.example.com/{1000 + row}")
- ws["A4"] = "ToolKit"
- ws["B4"] = 800
- ws["C4"] = "Seasonal product"
- ws["D4"] = "Specifications"
- ws["D4"].hyperlink = Hyperlink(ref="D4", target="https://example.com/toolkit")
+ # Add a comment to some rows
+ if row % 7 == 0:
+ ws1.cell(row=row, column=2).comment = Comment(
+ f"Customer requested expedited shipping on {random.choice(months)} {random.randint(1, 28)}.",
+ "Sales Operations"
+ )
- # Cell comments
- ws["A2"].comment = Comment("This product is our flagship.", "Author")
- ws["B2"].comment = Comment("Revenue figure is in USD.", "Author")
+ # Bar chart
+ chart1 = BarChart()
+ chart1.type = "col"
+ chart1.style = 11
+ chart1.title = "Monthly Sales Volume (Units)"
+ chart1.y_axis.title = "Quantity"
+ chart1.x_axis.title = "Month"
+ data_ref = Reference(ws1, min_col=4, min_row=1, max_row=61, max_col=4)
+ cats_ref = Reference(ws1, min_col=1, min_row=2, max_row=61)
+ chart1.add_data(data_ref, titles_from_data=True)
+ chart1.set_categories(cats_ref)
+ chart1.width = 20
+ chart1.height = 12
+ ws1.add_chart(chart1, "K2")
- # Chart (B1 E4)
- chart = BarChart()
- chart.title = "Revenue by Product"
- chart.y_axis.title = "Revenue (USD)"
- chart.x_axis.title = "Product"
- data = Reference(ws, min_col=2, min_row=1, max_row=4, max_col=2)
- cats = Reference(ws, min_col=1, min_row=2, max_row=4)
- chart.add_data(data, titles_from_data=True)
- chart.set_categories(cats)
- ws.add_chart(chart, "F2")
+ # Pie chart
+ chart2 = PieChart()
+ chart2.title = "Revenue by Product"
+ chart2.height = 10
+ chart2.width = 15
+ data_ref = Reference(ws1, min_col=6, min_row=1, max_row=61, max_col=6)
+ cats_ref = Reference(ws1, min_col=2, min_row=2, max_row=61)
+ chart2.add_data(data_ref, titles_from_data=True)
+ chart2.set_categories(cats_ref)
+ ws1.add_chart(chart2, "K30")
- # Second sheet with more text
- ws2 = wb.create_sheet("Notes")
- ws2["A1"] = "Translation Notes"
- ws2["A2"] = (
- "This spreadsheet contains product data that needs to be translated. "
- "Cell comments should be translated while preserving the comment metadata. "
- "Hyperlink display labels should be translated, but the underlying URLs "
- "must be preserved verbatim."
- )
- ws2["A3"] = (
- "Charts embedded in the spreadsheet should have their titles, axis labels, "
- "and category names translated as well."
+ # Line chart
+ chart3 = LineChart()
+ chart3.title = "Quantity Trend"
+ chart3.style = 12
+ chart3.y_axis.title = "Quantity"
+ chart3.x_axis.title = "Order #"
+ data_ref = Reference(ws1, min_col=4, min_row=1, max_row=61, max_col=4)
+ chart3.add_data(data_ref, titles_from_data=True)
+ chart3.width = 20
+ chart3.height = 10
+ ws1.add_chart(chart3, "K50")
+
+ # Conditional formatting on the Total column
+ rule = ColorScaleRule(
+ start_type="min", start_color="FFEEEE",
+ mid_type="percentile", mid_value=50, mid_color="FFFFCC",
+ end_type="max", end_color="CCFFCC",
)
+ ws1.conditional_formatting.add("F2:F61", rule)
+
+ # Freeze panes
+ ws1.freeze_panes = "A2"
+
+ # ============ Sheet 2: Customers ============
+ ws2 = wb.create_sheet("Customers")
+ headers2 = ["Customer ID", "Name", "Email", "Country", "Industry", "Annual Revenue", "Contract Link", "Notes"]
+ for i, h in enumerate(headers2, start=1):
+ c = ws2.cell(row=1, column=i, value=h)
+ c.font = Font(bold=True)
+ c.fill = PatternFill("solid", fgColor="D9E1F2")
+
+ countries = ["United States", "France", "Germany", "Japan", "Brazil", "India", "Canada", "Australia", "Mexico", "Spain"]
+ industries = ["Technology", "Finance", "Healthcare", "Manufacturing", "Retail", "Education", "Government"]
+ for row in range(2, 27):
+ ws2.cell(row=row, column=1, value=f"CUST-{1000 + row}")
+ ws2.cell(row=row, column=2, value=f"Company {chr(64 + row - 1)} Inc.")
+ ws2.cell(row=row, column=3, value=f"contact{row}@company{row}.example.com")
+ ws2.cell(row=row, column=4, value=random.choice(countries))
+ ws2.cell(row=row, column=5, value=random.choice(industries))
+ ws2.cell(row=row, column=6, value=random.randint(100_000, 50_000_000))
+ c = ws2.cell(row=row, column=7, value="View contract")
+ c.hyperlink = Hyperlink(ref=c.coordinate, target=f"https://contracts.example.com/cust/{1000 + row}")
+ ws2.cell(row=row, column=8, value="Strategic account" if row % 3 == 0 else "Standard account")
+
+ if row % 4 == 0:
+ ws2.cell(row=row, column=2).comment = Comment(
+ "VIP customer — prioritize support tickets",
+ "Account Manager"
+ )
+
+ # ============ Sheet 3: Translations (metrics) ============
+ ws3 = wb.create_sheet("Translation Metrics")
+ headers3 = ["Date", "Language Pair", "Documents", "BLEU", "chrF", "Avg Latency (s)", "Cost (USD)", "Quality Trend"]
+ for i, h in enumerate(headers3, start=1):
+ c = ws3.cell(row=1, column=i, value=h)
+ c.font = Font(bold=True)
+ c.fill = PatternFill("solid", fgColor="F4B084")
+
+ pairs = ["EN→FR", "EN→ES", "EN→DE", "EN→IT", "EN→JA", "EN→ZH", "EN→AR", "EN→PT"]
+ for row in range(2, 22):
+ ws3.cell(row=row, column=1, value=f"2024-{(row-1) // 2 + 1:02d}-{(row % 28) + 1:02d}")
+ ws3.cell(row=row, column=2, value=random.choice(pairs))
+ ws3.cell(row=row, column=3, value=random.randint(50, 5000))
+ ws3.cell(row=row, column=4, value=round(random.uniform(25, 55), 1))
+ ws3.cell(row=row, column=5, value=round(random.uniform(55, 75), 1))
+ ws3.cell(row=row, column=6, value=round(random.uniform(0.5, 5.0), 2))
+ ws3.cell(row=row, column=7, value=round(random.uniform(0.5, 50.0), 2))
+ ws3.cell(row=row, column=8, value=random.choice(["↑", "↓", "→"]))
+
+ # Add many more cells with text to stress translation
+ for row in range(23, 35):
+ ws3.cell(row=row, column=1, value=f"Quarterly review Q{(row-23)//3 + 1}")
+ ws3.cell(row=row, column=2, value=(
+ "Translation quality continued to improve across all language pairs. "
+ "The new neural architecture reduced latency by 35% while maintaining "
+ "the same BLEU score. Customer satisfaction increased significantly."
+ ))
+
+ # ============ Sheet 4: Glossary ============
+ ws4 = wb.create_sheet("Glossary")
+ ws4["A1"] = "English"
+ ws4["B1"] = "French"
+ ws4["C1"] = "Spanish"
+ ws4["D1"] = "German"
+ for c in ["A1", "B1", "C1", "D1"]:
+ ws4[c].font = Font(bold=True)
+ glossary = [
+ ("invoice", "facture", "factura", "Rechnung"),
+ ("shipping", "expédition", "envío", "Versand"),
+ ("discount", "remise", "descuento", "Rabatt"),
+ ("warranty", "garantie", "garantía", "Garantie"),
+ ("refund", "remboursement", "reembolso", "Rückerstattung"),
+ ("delivery", "livraison", "entrega", "Lieferung"),
+ ("customer", "client", "cliente", "Kunde"),
+ ("order", "commande", "pedido", "Bestellung"),
+ ("payment", "paiement", "pago", "Zahlung"),
+ ("subscription", "abonnement", "suscripción", "Abonnement"),
+ ]
+ for i, row in enumerate(glossary, start=2):
+ for j, val in enumerate(row, start=1):
+ ws4.cell(row=i, column=j, value=val)
wb.save(out)
- print(f"[OK] Generated {out} ({out.stat().st_size} bytes)")
+ print(f"[OK] {out.name}: {out.stat().st_size} bytes")
+ print(f" - Sheets: 4 (Sales, Customers, Metrics, Glossary)")
+ print(f" - Charts: 3 (bar, pie, line)")
+ print(f" - Hyperlinks: ~80 (one per data row)")
+ print(f" - Comments: ~15")
+ print(f" - Conditional formatting, frozen panes")
# ===========================================================================
-# PowerPoint — B2 (SmartArt, chart, placeholders)
+# COMPLEX PPTX — B2 stress test
# ===========================================================================
def generate_pptx():
out = OUT_DIR / "test_pptx.pptx"
prs = Presentation()
- prs.slide_width = PInches(10)
+ prs.slide_width = PInches(13.333) # Widescreen
prs.slide_height = PInches(7.5)
- # === Slide 1: real text + SmartArt diagram ===
- slide1 = prs.slides.add_slide(prs.slide_layouts[6]) # blank
+ def add_text(slide, text, x=1, y=1, w=8, h=1, size=18, bold=False, color=None):
+ tb = slide.shapes.add_textbox(PInches(x), PInches(y), PInches(w), PInches(h))
+ tf = tb.text_frame
+ tf.word_wrap = True
+ p = tf.paragraphs[0]
+ run = p.add_run()
+ run.text = text
+ run.font.size = PPt(size)
+ run.font.bold = bold
+ if color:
+ run.font.color.rgb = color
+ return tb
- # Title text
- tb = slide1.shapes.add_textbox(PInches(0.5), PInches(0.3), PInches(9), PInches(0.7))
- tf = tb.text_frame
- tf.text = "Project Status Overview"
+ def add_bullets(slide, items, x=1, y=2, w=10, h=4, size=16):
+ tb = slide.shapes.add_textbox(PInches(x), PInches(y), PInches(w), PInches(h))
+ tf = tb.text_frame
+ tf.word_wrap = True
+ for i, item in enumerate(items):
+ p = tf.paragraphs[0] if i == 0 else tf.add_paragraph()
+ p.text = f"• {item}"
+ p.font.size = PPt(size)
+ return tb
- # Real text box (should be translated)
- tb2 = slide1.shapes.add_textbox(PInches(0.5), PInches(1.2), PInches(9), PInches(2))
- tf2 = tb2.text_frame
- tf2.text = (
- "This presentation summarizes the current project status. "
- "The development team has completed the initial research phase "
- "and is now moving into implementation."
- )
+ # ============ Slide 1: Title slide ============
+ s1 = prs.slides.add_slide(prs.slide_layouts[0])
+ title = s1.shapes.title
+ title.text = "Q4 2024 Product Strategy"
+ subtitle = s1.placeholders[1]
+ subtitle.text = "Office Translator — Internal Roadmap Review"
- # Inject a SmartArt diagram by writing directly to the zip
- # (python-pptx doesn't have a high-level SmartArt API)
- slide1.save = lambda *a, **k: None # placeholder, will be replaced
+ # ============ Slide 2: Agenda ============
+ s2 = prs.slides.add_slide(prs.slide_layouts[1])
+ s2.shapes.title.text = "Agenda"
+ add_bullets(s2, [
+ "Quality improvements: L0, L1, L2 evaluation layers",
+ "Format preservation: Word, Excel, PowerPoint, PDF",
+ "Performance: caching, async processing, parallelism",
+ "New features: glossaries, custom prompts, watermarks",
+ "Q1 2025 priorities and resource allocation",
+ ])
- # Save what we have so far
+ # ============ Slide 3: Quality metrics (with chart) ============
+ s3 = prs.slides.add_slide(prs.slide_layouts[5])
+ s3.shapes.title.text = "Quality Improvements Year-Over-Year"
+ add_text(s3, "BLEU scores have steadily improved across all language pairs since the introduction of neural architectures.", x=0.5, y=1.2, w=12, h=1, size=14)
+
+ # Add a real bar chart
+ from pptx.chart.data import CategoryChartData
+ from pptx.enum.chart import XL_CHART_TYPE
+ chart_data = CategoryChartData()
+ chart_data.categories = ["2020", "2021", "2022", "2023", "2024"]
+ chart_data.add_series("EN→FR", (32.1, 36.5, 41.2, 45.8, 48.2))
+ chart_data.add_series("EN→ES", (35.4, 39.7, 44.5, 49.1, 51.7))
+ chart_data.add_series("EN→DE", (30.8, 34.2, 39.1, 43.5, 45.9))
+ chart_data.add_series("EN→JA", (18.5, 22.1, 26.8, 30.2, 32.4))
+ chart_data.add_series("EN→ZH", (16.2, 20.5, 24.9, 28.1, 30.1))
+ chart = s3.shapes.add_chart(
+ XL_CHART_TYPE.COLUMN_CLUSTERED,
+ PInches(0.5), PInches(2.5), PInches(12.3), PInches(4.5),
+ chart_data
+ ).chart
+ chart.has_title = True
+ chart.chart_title.text_frame.text = "BLEU Score by Year (Higher is better)"
+ chart.has_legend = True
+
+ # ============ Slide 4: SmartArt (manual injection) ============
+ s4 = prs.slides.add_slide(prs.slide_layouts[5])
+ s4.shapes.title.text = "Project Organization"
+ add_text(s4, "Our engineering team is organized into five cross-functional squads:", x=0.5, y=1.2, w=12, h=0.7, size=14)
+
+ # ============ Slide 5: Process diagram (also SmartArt) ============
+ s5 = prs.slides.add_slide(prs.slide_layouts[5])
+ s5.shapes.title.text = "Translation Pipeline"
+ add_text(s5, "Documents flow through four stages before reaching the user:", x=0.5, y=1.2, w=12, h=0.7, size=14)
+
+ # ============ Slide 6: Performance (line chart) ============
+ s6 = prs.slides.add_slide(prs.slide_layouts[5])
+ s6.shapes.title.text = "Latency Improvements"
+ chart_data2 = CategoryChartData()
+ chart_data2.categories = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
+ chart_data2.add_series("Avg latency (s)", (8.2, 7.5, 6.8, 5.9, 5.1, 4.4, 3.8, 3.2, 2.7, 2.3, 1.9, 1.6))
+ chart2 = s6.shapes.add_chart(
+ XL_CHART_TYPE.LINE,
+ PInches(0.5), PInches(1.5), PInches(12.3), PInches(5.5),
+ chart_data2
+ ).chart
+ chart2.has_title = True
+ chart2.chart_title.text_frame.text = "Average translation latency (seconds)"
+ chart2.has_legend = False
+
+ # ============ Slide 7: Architecture diagram (third SmartArt) ============
+ s7 = prs.slides.add_slide(prs.slide_layouts[5])
+ s7.shapes.title.text = "System Architecture"
+ add_text(s7, "The platform is built on a microservices architecture:", x=0.5, y=1.2, w=12, h=0.7, size=14)
+
+ # ============ Slide 8: Comparison (pie chart) ============
+ s8 = prs.slides.add_slide(prs.slide_layouts[5])
+ s8.shapes.title.text = "Market Share by Language Pair"
+ chart_data3 = CategoryChartData()
+ chart_data3.categories = ["EN→FR", "EN→ES", "EN→DE", "EN→JA", "EN→ZH", "Other"]
+ chart_data3.add_series("Volume", (28, 22, 18, 12, 10, 10))
+ chart3 = s8.shapes.add_chart(
+ XL_CHART_TYPE.PIE,
+ PInches(2), PInches(1.5), PInches(9), PInches(5.5),
+ chart_data3
+ ).chart
+ chart3.has_title = True
+ chart3.chart_title.text_frame.text = "Document volume by target language (%)"
+ chart3.has_legend = True
+
+ # ============ Slide 9: Q1 priorities ============
+ s9 = prs.slides.add_slide(prs.slide_layouts[1])
+ s9.shapes.title.text = "Q1 2025 Priorities"
+ add_bullets(s9, [
+ "Launch L2 Pro premium judge for enterprise customers",
+ "Roll out Redis cache cluster to reduce API costs by ~40%",
+ "Improve PDF hyperlink preservation (currently 78% success rate)",
+ "Add support for 12 additional language pairs (Arabic dialects, etc.)",
+ "Reduce average latency below 1 second for documents under 5 pages",
+ ])
+
+ # ============ Slide 10: Closing ============
+ s10 = prs.slides.add_slide(prs.slide_layouts[0])
+ s10.shapes.title.text = "Thank you"
+ s10.placeholders[1].text = "Questions? Reach out to the team."
+
+ # Save first, then inject SmartArt
tmp = OUT_DIR / "_intermediate.pptx"
prs.save(tmp)
- # Now inject the SmartArt diagram
- import zipfile
- diagram_xml = b"""
+ # Inject three SmartArt diagrams (one per slide that has them)
+ diagram_xml_1 = """
- Project Phases
+ Engineering Team
- Research Complete
+ Quality Squad
- Design Phase
+ Performance Squad
- Implementation
+ Format Squad
- Testing and Launch
+ Infrastructure Squad
+
+
+ Product Squad
+
+
+
+"""
+ diagram_xml_2 = """
+
+
+
+ Document Upload
+
+
+ Quality Check
+
+
+ Translation
+
+
+ Format Preservation
+
+
+ Delivery to User
+
+
+
+"""
+ diagram_xml_3 = """
+
+
+
+ Load Balancer
+
+
+ API Gateway
+
+
+ Translation Workers
+
+
+ PostgreSQL Database
+
+
+ Redis Cache
+
+
+ Object Storage
+
+
+ Monitoring Stack
@@ -322,141 +887,418 @@ def generate_pptx():
with zipfile.ZipFile(tmp, "r") as zin, \
zipfile.ZipFile(out, "w", zipfile.ZIP_DEFLATED) as zout:
items = {n: zin.read(n) for n in zin.namelist()}
- items["ppt/diagrams/data1.xml"] = diagram_xml
+ items["ppt/diagrams/data1.xml"] = diagram_xml_1.encode("utf-8")
+ items["ppt/diagrams/data2.xml"] = diagram_xml_2.encode("utf-8")
+ items["ppt/diagrams/data3.xml"] = diagram_xml_3.encode("utf-8")
- # Update content types
ct = items["[Content_Types].xml"].decode("utf-8")
- override = ('')
- if override not in ct:
- ct = ct.replace("", f"{override}")
+ for i in range(1, 4):
+ override = (
+ f''
+ )
+ if override not in ct:
+ ct = ct.replace("", f"{override}")
items["[Content_Types].xml"] = ct.encode("utf-8")
for n, data in items.items():
zout.writestr(n, data)
tmp.unlink()
- print(f"[OK] Generated {out} ({out.stat().st_size} bytes) with SmartArt + placeholders")
+ print(f"[OK] {out.name}: {out.stat().st_size} bytes")
+ print(f" - Slides: 10")
+ print(f" - SmartArt: 3 diagrams (5-7 nodes each)")
+ print(f" - Charts: 3 (column, line, pie)")
+ print(f" - Placeholders: each slide has date + slide # + footer (default)")
# ===========================================================================
-# PDF — B3 (hyperlinks, image preservation)
+# COMPLEX PDF — B3 stress test
# ===========================================================================
def generate_pdf():
out = OUT_DIR / "test_pdf.pdf"
doc = fitz.open()
- # Create all pages first (so internal goto links can reference them).
- # Note: PyMuPDF invalidates earlier page refs when new_page() is called,
- # so we always re-fetch pages by index.
- doc.new_page()
- doc.new_page()
+ # Create all pages upfront
+ for _ in range(8):
+ doc.new_page()
- # === Page 1: title + paragraphs + external hyperlink ===
+ def add_text_block(page, x, y, text, fontsize=12, color=(0, 0, 0), bold=False):
+ # split text into lines for nicer rendering
+ from fitz import Point
+ lines = text.split("\n")
+ for i, line in enumerate(lines):
+ page.insert_text(
+ (x, y + i * (fontsize + 4)),
+ line,
+ fontsize=fontsize,
+ color=color,
+ )
+
+ # PHASE 1: add all text and images (no links yet)
+ # ============ Page 1: Title + TOC ============
p1 = doc[0]
- p1.insert_text((72, 72), "Document Translation Test", fontsize=20)
- p1.insert_text(
- (72, 120),
- "This is a test PDF with hyperlinks and images.",
- fontsize=12,
- )
- p1.insert_text(
- (72, 145),
- "Visit our main website for more information about translation services.",
- fontsize=12,
- )
- # External URI hyperlink
- p1.insert_link({
- "kind": fitz.LINK_URI,
- "from": fitz.Rect(72, 168, 250, 185),
- "uri": "https://www.example.com",
- })
- # Internal goto link to page 2
- p1.insert_text(
- (72, 200),
- "Continue reading on the next page (click here).",
- fontsize=12,
- )
- p1.insert_link({
- "kind": fitz.LINK_GOTO,
- "from": fitz.Rect(72, 198, 150, 213),
- "page": 1, # second page (0-indexed)
- "to": fitz.Point(72, 72),
- })
+ p1.insert_text((72, 80), "Technical Specification: Office Translator v3.0", fontsize=22, color=(0.1, 0.2, 0.5))
+ p1.insert_text((72, 115), "Document version 3.0.1 — Last updated December 2024", fontsize=11, color=(0.4, 0.4, 0.4))
+ p1.insert_text((72, 160), "Table of Contents", fontsize=16, color=(0.1, 0.2, 0.5))
+ toc_entries = [
+ ("1. Introduction", 1),
+ ("2. Installation and Setup", 2),
+ ("3. Translation Engine", 3),
+ ("4. Format Preservation", 4),
+ ("5. Quality Assurance", 5),
+ ("6. Performance and Scaling", 6),
+ ("7. API Reference", 7),
+ ("8. Troubleshooting", 8),
+ ]
+ for i, (label, page_idx) in enumerate(toc_entries):
+ y = 190 + i * 22
+ p1.insert_text((90, y), label, fontsize=12)
+ p1.insert_text((350, y), f".......... {page_idx}", fontsize=12, color=(0.5, 0.5, 0.5))
+ p1.insert_text((72, 420), "For the latest version of this document, visit:", fontsize=11)
+ p1.insert_text((72, 440), "https://docs.office-translator.example.com/v3", fontsize=12, color=(0.1, 0.1, 0.8))
+ p1.draw_rect(fitz.Rect(72, 480, 500, 550), color=(0.9, 0.95, 1.0), fill=(0.85, 0.92, 1.0))
+ p1.insert_text((85, 500), "Important notice", fontsize=14, color=(0.1, 0.2, 0.5))
+ p1.insert_text((85, 525), "This is a pre-release document.", fontsize=11)
+ p1.insert_text((85, 542), "Subject to change without notice.", fontsize=11)
- # Add a colored rectangle (mimics an image for B3 image-preservation test)
- p1.draw_rect(fitz.Rect(72, 240, 300, 320), color=(0.7, 0.3, 0.3), fill=(0.9, 0.6, 0.6))
- p1.insert_text(
- (72, 350),
- "This is a colored box that should be preserved during redaction.",
- fontsize=10,
- )
-
- # === Page 2: more text + an actual image ===
+ # ============ Page 2: Introduction ============
p2 = doc[1]
- p2.insert_text((72, 72), "Page Two", fontsize=20)
+ p2.insert_text((72, 80), "1. Introduction", fontsize=20, color=(0.1, 0.2, 0.5))
p2.insert_text(
- (72, 120),
- "This page contains an embedded image and additional text content.",
+ (72, 130),
+ "Office Translator is a SaaS platform for translating office documents "
+ "(Word, Excel, PowerPoint, PDF) while preserving their formatting. The "
+ "platform supports 50+ language pairs and processes over 100,000 "
+ "documents per day.",
fontsize=12,
)
- # Embed a proper PNG (generated with PIL)
+ p2.insert_text(
+ (72, 220),
+ "This document describes the public API, configuration options, and "
+ "best practices for integrating Office Translator into your workflow. "
+ "For pricing and subscription information, see our website.",
+ fontsize=12,
+ )
+ p2.insert_text((72, 295), "Pricing: https://office-translator.example.com/pricing", fontsize=11, color=(0.1, 0.1, 0.8))
try:
- from PIL import Image
- import io
- # Create a 100x60 RGB image
- img = Image.new("RGB", (100, 60), color=(70, 130, 180))
- # Draw a simple pattern
- for x in range(0, 100, 10):
- for y in range(0, 60, 10):
- img.putpixel((x, y), (255, 200, 100))
+ from PIL import Image, ImageDraw
+ img = Image.new("RGB", (300, 180), color=(255, 255, 255))
+ d = ImageDraw.Draw(img)
+ for x in range(20, 280, 30):
+ h = 20 + (x % 80)
+ d.rectangle([x, 160 - h, x + 20, 160], fill=(70, 130, 180))
+ d.text((20, 10), "Sample chart: Translation volume", fill=(0, 0, 0))
buf = io.BytesIO()
img.save(buf, format="PNG")
- p2.insert_image(fitz.Rect(72, 160, 200, 240), stream=buf.getvalue())
+ p2.insert_image(fitz.Rect(72, 340, 372, 520), stream=buf.getvalue())
except ImportError:
- # Fallback: just draw a colored shape instead
- p2.draw_rect(fitz.Rect(72, 160, 200, 240), color=(0.3, 0.5, 0.7), fill=(0.3, 0.5, 0.7))
- p2.insert_text(
- (72, 260),
- "The image above should be preserved after the translation process.",
- fontsize=10,
- )
+ p2.draw_rect(fitz.Rect(72, 340, 372, 520), color=(0.7, 0.7, 0.9), fill=(0.85, 0.85, 1.0))
- # Another external link on page 2
- p2.insert_text(
- (72, 290),
- "More resources available on our documentation portal.",
+ # ============ Page 3: Installation & Setup ============
+ p3 = doc[2]
+ p3.insert_text((72, 80), "2. Installation and Setup", fontsize=20, color=(0.1, 0.2, 0.5))
+ p3.insert_text(
+ (72, 130),
+ "To get started, create an account on our platform and obtain an API key. "
+ "The API key must be included in the Authorization header of every request.",
fontsize=12,
)
- p2.insert_link({
+ p3.insert_text((72, 180), "2.1 Quick Start", fontsize=14, color=(0.1, 0.2, 0.5))
+ p3.insert_text((72, 210), "Step 1: Sign up at https://app.office-translator.example.com", fontsize=11)
+ p3.insert_text((72, 240), "Step 2: Generate an API key in the dashboard", fontsize=11)
+ p3.insert_text((72, 270), "Step 3: Make your first translation request:", fontsize=11)
+ code_box = fitz.Rect(72, 295, 500, 410)
+ p3.draw_rect(code_box, color=(0.95, 0.95, 0.95), fill=(0.97, 0.97, 0.97))
+ code_lines = [
+ "curl -X POST https://api.office-translator.example.com/v1/translate \\",
+ " -H 'Authorization: Bearer YOUR_API_KEY' \\",
+ " -F 'file=@document.docx' \\",
+ " -F 'target_lang=fr' \\",
+ " -F 'source_lang=en'",
+ ]
+ for i, line in enumerate(code_lines):
+ p3.insert_text((85, 305 + i * 18), line, fontsize=10, color=(0.2, 0.2, 0.2))
+ p3.insert_text((72, 440), "Step 4: Download the translated file when the job completes", fontsize=11)
+ p3.insert_text((72, 445), "(see the Quickstart guide for details)", fontsize=11, color=(0.5, 0.5, 0.5))
+
+ # ============ Page 4: Translation Engine ============
+ p4 = doc[3]
+ p4.insert_text((72, 80), "3. Translation Engine", fontsize=20, color=(0.1, 0.2, 0.5))
+ p4.insert_text(
+ (72, 130),
+ "Our translation engine combines multiple models and techniques to deliver "
+ "high-quality output across diverse content types. The engine is designed "
+ "to be fast, accurate, and cost-effective.",
+ fontsize=12,
+ )
+ p4.insert_text((72, 220), "3.1 Supported Languages", fontsize=14, color=(0.1, 0.2, 0.5))
+ languages = [
+ "English, French, Spanish, German, Italian, Portuguese, Dutch",
+ "Russian, Polish, Czech, Romanian, Hungarian, Greek, Ukrainian",
+ "Arabic, Hebrew, Persian, Urdu, Turkish, Kurdish",
+ "Chinese (Simplified and Traditional), Japanese, Korean, Vietnamese, Thai",
+ "Hindi, Bengali, Tamil, Telugu, Marathi, Gujarati, Punjabi",
+ "Swahili, Afrikaans, Zulu, Amharic, Yoruba, Igbo, Hausa",
+ ]
+ for i, lang in enumerate(languages):
+ p4.insert_text((90, 250 + i * 20), f"• {lang}", fontsize=11)
+ p4.insert_text((72, 400), "3.2 Provider Selection", fontsize=14, color=(0.1, 0.2, 0.5))
+ p4.insert_text(
+ (72, 430),
+ "You can choose between several translation providers depending on your "
+ "needs: Google Translate (free tier), DeepL (premium), OpenAI GPT-4 "
+ "(premium, best for technical content), or Anthropic Claude (premium, "
+ "best for literary content).",
+ fontsize=11,
+ )
+ p4.insert_text((72, 540), "For a comparison of providers, see our benchmark report", fontsize=11, color=(0.1, 0.1, 0.8))
+
+ # ============ Page 5: Format Preservation ============
+ p5 = doc[4]
+ p5.insert_text((72, 80), "4. Format Preservation", fontsize=20, color=(0.1, 0.2, 0.5))
+ p5.insert_text(
+ (72, 130),
+ "Format preservation is a core feature of Office Translator. Unlike raw "
+ "translation APIs that return plain text, our platform preserves:",
+ fontsize=12,
+ )
+ features = [
+ "Hyperlinks (URLs are kept verbatim; only the visible text is translated)",
+ "Footnotes and endnotes (collected, translated, and re-injected)",
+ "Cell comments in Excel",
+ "Embedded charts (titles, axis labels, category names)",
+ "SmartArt diagrams in PowerPoint",
+ "Headers, footers, and page numbers",
+ "Tables (with formatting and merged cells)",
+ "Images (never rasterized away during redaction)",
+ ]
+ for i, feat in enumerate(features):
+ p5.insert_text((90, 180 + i * 25), f"• {feat}", fontsize=11)
+ p5.insert_text((72, 400), "For a complete list of supported features, see:", fontsize=11)
+ p5.insert_text((72, 420), "https://docs.office-translator.example.com/format-support", fontsize=11, color=(0.1, 0.1, 0.8))
+ try:
+ from PIL import Image, ImageDraw
+ img = Image.new("RGB", (400, 200), color=(255, 255, 255))
+ d = ImageDraw.Draw(img)
+ d.rectangle([10, 10, 390, 190], outline=(50, 50, 50), width=2)
+ d.text((20, 20), "Format preservation: before & after", fill=(0, 0, 0))
+ d.rectangle([20, 50, 180, 90], outline=(0, 0, 0))
+ d.text((30, 60), "Original", fill=(0, 0, 0))
+ d.rectangle([200, 50, 380, 90], outline=(0, 100, 0))
+ d.text((220, 60), "Translated", fill=(0, 100, 0))
+ buf = io.BytesIO()
+ img.save(buf, format="PNG")
+ p5.insert_image(fitz.Rect(72, 470, 472, 670), stream=buf.getvalue())
+ except ImportError:
+ p5.draw_rect(fitz.Rect(72, 470, 472, 670), color=(0.8, 0.9, 0.8), fill=(0.9, 1.0, 0.9))
+
+ # ============ Page 6: Quality Assurance ============
+ p6 = doc[5]
+ p6.insert_text((72, 80), "5. Quality Assurance", fontsize=20, color=(0.1, 0.2, 0.5))
+ p6.insert_text(
+ (72, 130),
+ "Every translation goes through three quality layers before being "
+ "delivered to the user:",
+ fontsize=12,
+ )
+ layers = [
+ ("L0 (Pattern detection)", "Detects language confusion, length anomalies, and prompt leaks. No API call."),
+ ("L1 (LLM judge)", "Sends 5 sample chunks to a cheap LLM (deepseek-chat) for a binary pass/fail verdict. ~$0.0003/job."),
+ ("L2 (Pro judge, 8 dimensions)", "Sends 15 sample chunks to gpt-4o for fine-grained evaluation across accuracy, fluency, terminology, style, completeness, and formatting. ~$0.005/job. Pro+ tier only."),
+ ]
+ for i, (title, desc) in enumerate(layers):
+ y = 180 + i * 80
+ p6.insert_text((90, y), title, fontsize=13, color=(0.1, 0.2, 0.5))
+ p6.insert_text((90, y + 22), desc, fontsize=11)
+ p6.insert_text((72, 460), "5.1 Metrics", fontsize=14, color=(0.1, 0.2, 0.5))
+ p6.insert_text(
+ (72, 490),
+ "All quality scores are exposed as Prometheus metrics for monitoring "
+ "and alerting. See the metrics endpoint at /metrics for details.",
+ fontsize=11,
+ )
+
+ # ============ Page 7: Performance ============
+ p7 = doc[6]
+ p7.insert_text((72, 80), "6. Performance and Scaling", fontsize=20, color=(0.1, 0.2, 0.5))
+ p7.insert_text(
+ (72, 130),
+ "Office Translator is built on a horizontally scalable architecture. "
+ "Translation workers process jobs in parallel, with intelligent caching "
+ "to avoid redundant API calls.",
+ fontsize=12,
+ )
+ perf_table_data = [
+ ("Document size", "Avg latency (s)", "Throughput (docs/min)"),
+ ("< 1 page", "0.4", "150"),
+ ("1-5 pages", "1.2", "50"),
+ ("5-20 pages", "3.8", "16"),
+ ("20-100 pages", "12.5", "5"),
+ ("> 100 pages", "45.0", "1"),
+ ]
+ for i, row in enumerate(perf_table_data):
+ y = 200 + i * 25
+ for j, cell in enumerate(row):
+ color = (0.1, 0.2, 0.5) if i == 0 else (0, 0, 0)
+ p7.insert_text((90 + j * 130, y), cell, fontsize=11, color=color)
+ p7.insert_text((72, 380), "6.1 Caching", fontsize=14, color=(0.1, 0.2, 0.5))
+ p7.insert_text(
+ (72, 410),
+ "Repeated translations of the same content are served from cache, "
+ "reducing API costs by up to 40% on typical workloads. The cache key "
+ "includes the user, target language, and any custom prompt/glossary "
+ "to avoid cross-contamination.",
+ fontsize=11,
+ )
+
+ # ============ Page 8: API Reference + Troubleshooting ============
+ p8 = doc[7]
+ p8.insert_text((72, 80), "7. API Reference", fontsize=20, color=(0.1, 0.2, 0.5))
+ p8.insert_text(
+ (72, 130),
+ "The full API reference is available on our developer portal. Key endpoints:",
+ fontsize=12,
+ )
+ endpoints = [
+ ("POST /v1/translate", "Submit a document for translation"),
+ ("GET /v1/translations/{id}", "Get the status of a translation job"),
+ ("GET /v1/download/{id}", "Download the translated file"),
+ ("GET /v1/jobs", "List all jobs for the authenticated user"),
+ ("GET /v1/glossaries", "List available glossaries"),
+ ("POST /v1/glossaries", "Create a new glossary"),
+ ]
+ for i, (ep, desc) in enumerate(endpoints):
+ y = 180 + i * 30
+ p8.insert_text((90, y), ep, fontsize=11, color=(0.1, 0.2, 0.5))
+ p8.insert_text((290, y), desc, fontsize=11)
+ p8.insert_text((72, 400), "For interactive API documentation, see:", fontsize=11)
+ p8.insert_text((72, 420), "https://api.office-translator.example.com/docs", fontsize=11, color=(0.1, 0.1, 0.8))
+ p8.insert_text((72, 480), "8. Troubleshooting", fontsize=20, color=(0.1, 0.2, 0.5))
+ p8.insert_text(
+ (72, 530),
+ "Common issues and their solutions are documented in our troubleshooting "
+ "guide. If you encounter a problem not covered there, please contact support.",
+ fontsize=12,
+ )
+ p8.insert_text((72, 600), "https://docs.office-translator.example.com/troubleshooting", fontsize=11, color=(0.1, 0.1, 0.8))
+
+ # Add a page header/footer-style band on every page
+ for i, page in enumerate(doc):
+ page.insert_text(
+ (72, 770),
+ f"Office Translator v3.0 Technical Specification — Page {i + 1} of {len(doc)}",
+ fontsize=8,
+ color=(0.5, 0.5, 0.5),
+ )
+
+ # Save and re-open — PyMuPDF requires the document to be flushed
+ # before we can resolve page references in goto links.
+ tmp_path = out.with_suffix(".tmp.pdf")
+ doc.save(str(tmp_path))
+ doc.close()
+
+ # PHASE 2: add links in a fresh document handle
+ doc2 = fitz.open(str(tmp_path))
+
+ # Page 1: TOC gotos + external link
+ p1 = doc2[0]
+ for i, (label, page_idx_display) in enumerate(toc_entries):
+ y = 190 + i * 22
+ # PyMuPDF goto uses 0-indexed page numbers.
+ # "1. Introduction" is on page 1 in the TOC, which is page index 0 in the PDF.
+ target_page_idx = page_idx_display - 1
+ p1.insert_link({
+ "kind": fitz.LINK_GOTO,
+ "from": fitz.Rect(85, y - 12, 420, y + 5),
+ "page": target_page_idx,
+ "to": fitz.Point(72, 72),
+ })
+ p1.insert_link({
"kind": fitz.LINK_URI,
- "from": fitz.Rect(72, 313, 200, 330),
- "uri": "https://docs.example.com/translation",
+ "from": fitz.Rect(70, 425, 380, 445),
+ "uri": "https://docs.office-translator.example.com/v3",
})
- doc.save(str(out))
- doc.close()
- print(f"[OK] Generated {out} ({out.stat().st_size} bytes) with hyperlinks + image")
+ # Page 2: pricing external link
+ doc2[1].insert_link({
+ "kind": fitz.LINK_URI,
+ "from": fitz.Rect(70, 280, 470, 300),
+ "uri": "https://office-translator.example.com/pricing",
+ })
+ # Page 3: signup + quickstart links
+ doc2[2].insert_link({
+ "kind": fitz.LINK_URI,
+ "from": fitz.Rect(70, 195, 500, 215),
+ "uri": "https://app.office-translator.example.com",
+ })
+ doc2[2].insert_link({
+ "kind": fitz.LINK_URI,
+ "from": fitz.Rect(70, 425, 500, 445),
+ "uri": "https://docs.office-translator.example.com/v3/quickstart",
+ })
+
+ # Page 4: providers link
+ doc2[3].insert_link({
+ "kind": fitz.LINK_URI,
+ "from": fitz.Rect(70, 425, 500, 500),
+ "uri": "https://docs.office-translator.example.com/providers",
+ })
+
+ # Page 5: format support link
+ doc2[4].insert_link({
+ "kind": fitz.LINK_URI,
+ "from": fitz.Rect(70, 415, 500, 435),
+ "uri": "https://docs.office-translator.example.com/format-support",
+ })
+
+ # Page 6: metrics docs link
+ doc2[5].insert_link({
+ "kind": fitz.LINK_URI,
+ "from": fitz.Rect(70, 485, 500, 540),
+ "uri": "https://docs.office-translator.example.com/metrics",
+ })
+
+ # Page 8: API docs + troubleshooting links
+ doc2[7].insert_link({
+ "kind": fitz.LINK_URI,
+ "from": fitz.Rect(70, 415, 500, 435),
+ "uri": "https://api.office-translator.example.com/docs",
+ })
+ doc2[7].insert_link({
+ "kind": fitz.LINK_URI,
+ "from": fitz.Rect(70, 525, 500, 590),
+ "uri": "https://docs.office-translator.example.com/troubleshooting",
+ })
+
+ doc2.save(str(out))
+ doc2.close()
+ tmp_path.unlink()
+
+ # Quick verify
+ doc_v = fitz.open(str(out))
+ total_links = 0
+ total_images = 0
+ for p in doc_v:
+ total_links += len(p.get_links())
+ total_images += len(p.get_images())
+ n_pages = len(doc_v)
+ doc_v.close()
+
+ print(f"[OK] {out.name}: {out.stat().st_size} bytes")
+ print(f" - Pages: {n_pages}")
+ print(f" - Total hyperlinks: {total_links} (URI + GOTO)")
+ print(f" - Total images: {total_images}")
+ print(f" - Tables, code blocks, TOC, headers/footers")
-# ===========================================================================
-# Main
-# ===========================================================================
if __name__ == "__main__":
- print(f"Output directory: {OUT_DIR.resolve()}")
- print()
+ print(f"Output directory: {OUT_DIR.resolve()}\n")
generate_word()
generate_excel()
generate_pptx()
generate_pdf()
- print()
- print("Test corpus generated. To test:")
- print(" 1. Upload each file to your Office Translator instance")
- print(" 2. Download the translated output")
- print(" 3. Verify format preservation:")
- print(" - test_word.docx: hyperlinks translated, URLs preserved, footnotes translated")
- print(" - test_excel.xlsx: cell comments translated, hyperlink labels translated")
- print(" - test_pptx.pptx: SmartArt nodes translated, chart text translated")
- print(" - test_pdf.pdf: hyperlinks clickable, image preserved, no redaction artifacts")
+ print("\n[OK] Test corpus generated.")
+ print("Each file is now substantial enough to catch real format-preservation bugs.")