All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 2m32s
1305 lines
56 KiB
Python
1305 lines
56 KiB
Python
"""
|
|
Generate COMPLEX test files for B1/B2/B3 stress-testing.
|
|
|
|
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
|
|
|
|
Output: sample_files/test_corpus/test_*.{docx,xlsx,pptx,pdf}
|
|
"""
|
|
import os
|
|
import io
|
|
import zipfile
|
|
import shutil
|
|
from pathlib import Path
|
|
|
|
from docx import Document
|
|
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_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, 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, Emu
|
|
from pptx.enum.shapes import MSO_SHAPE_TYPE
|
|
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)
|
|
|
|
|
|
# ===========================================================================
|
|
# Helpers
|
|
# ===========================================================================
|
|
|
|
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,
|
|
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink",
|
|
is_external=True,
|
|
)
|
|
hyperlink = OxmlElement("w:hyperlink")
|
|
hyperlink.set(qn("r:id"), r_id)
|
|
|
|
new_run = OxmlElement("w:r")
|
|
rPr = OxmlElement("w:rPr")
|
|
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)
|
|
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 _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)
|
|
|
|
|
|
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):
|
|
"""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 = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n'
|
|
fn_xml += '<w:footnotes xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">\n'
|
|
fn_xml += ' <w:footnote w:type="separator" w:id="-1"><w:p><w:r><w:separator/></w:r></w:p></w:footnote>\n'
|
|
fn_xml += ' <w:footnote w:type="continuationSeparator" w:id="0"><w:p><w:r><w:continuationSeparator/></w:r></w:p></w:footnote>\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' <w:footnote w:id="{i}">\n'
|
|
fn_xml += ' <w:p><w:pPr><w:pStyle w:val="FootnoteText"/></w:pPr>\n'
|
|
fn_xml += ' <w:r><w:rPr><w:rStyle w:val="FootnoteReference"/></w:rPr><w:footnoteRef/></w:r>\n'
|
|
fn_xml += f' <w:r><w:t xml:space="preserve"> {text}</w:t></w:r>\n'
|
|
fn_xml += ' </w:p>\n'
|
|
fn_xml += ' </w:footnote>\n'
|
|
fn_xml += '</w:footnotes>\n'
|
|
|
|
en_xml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n'
|
|
en_xml += '<w:endnotes xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">\n'
|
|
en_xml += ' <w:endnote w:type="separator" w:id="-1"><w:p><w:r><w:separator/></w:r></w:p></w:endnote>\n'
|
|
en_xml += ' <w:endnote w:type="continuationSeparator" w:id="0"><w:p><w:r><w:continuationSeparator/></w:r></w:p></w:endnote>\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' <w:endnote w:id="{i}">\n'
|
|
en_xml += ' <w:p><w:pPr><w:pStyle w:val="EndnoteText"/></w:pPr>\n'
|
|
en_xml += ' <w:r><w:rPr><w:rStyle w:val="EndnoteReference"/></w:rPr><w:endnoteRef/></w:r>\n'
|
|
en_xml += f' <w:r><w:t xml:space="preserve"> {text}</w:t></w:r>\n'
|
|
en_xml += ' </w:p>\n'
|
|
en_xml += ' </w:endnote>\n'
|
|
en_xml += '</w:endnotes>\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'<Override PartName="/{part}" ContentType="{ctype}"/>'
|
|
if override not in ct:
|
|
ct = ct.replace("</Types>", f"{override}</Types>")
|
|
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'<Relationship Id="{rid}" Type="{rtype}" Target="{target}"/>'
|
|
rels = rels.replace("</Relationships>", f"{new_rel}</Relationships>")
|
|
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 page ============
|
|
title = doc.add_heading("Annual Translation Quality Report 2024", level=0)
|
|
title.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
|
|
|
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)
|
|
|
|
# ============ 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")
|
|
|
|
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(
|
|
"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)
|
|
_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")
|
|
|
|
|
|
# ===========================================================================
|
|
# COMPLEX Excel — B1 stress test
|
|
# ===========================================================================
|
|
|
|
def generate_excel():
|
|
out = OUT_DIR / "test_excel.xlsx"
|
|
wb = Workbook()
|
|
|
|
# ============ 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")
|
|
|
|
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"]
|
|
|
|
# 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}")
|
|
|
|
# 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"
|
|
)
|
|
|
|
# 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")
|
|
|
|
# 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")
|
|
|
|
# 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] {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")
|
|
|
|
|
|
# ===========================================================================
|
|
# COMPLEX PPTX — B2 stress test
|
|
# ===========================================================================
|
|
|
|
def generate_pptx():
|
|
out = OUT_DIR / "test_pptx.pptx"
|
|
prs = Presentation()
|
|
prs.slide_width = PInches(13.333) # Widescreen
|
|
prs.slide_height = PInches(7.5)
|
|
|
|
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
|
|
|
|
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
|
|
|
|
# ============ 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"
|
|
|
|
# ============ 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",
|
|
])
|
|
|
|
# ============ 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)
|
|
|
|
# Inject three SmartArt diagrams (one per slide that has them)
|
|
diagram_xml_1 = """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
<dgm:dataModel xmlns:dgm="http://schemas.openxmlformats.org/drawingml/2006/diagram"
|
|
xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
|
|
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
|
|
<dgm:ptLst>
|
|
<dgm:pt modelId="0" type="doc">
|
|
<dgm:t><a:p><a:r><a:t>Engineering Team</a:t></a:r></a:p></dgm:t>
|
|
</dgm:pt>
|
|
<dgm:pt modelId="1">
|
|
<dgm:t><a:p><a:r><a:t>Quality Squad</a:t></a:r></a:p></dgm:t>
|
|
</dgm:pt>
|
|
<dgm:pt modelId="2">
|
|
<dgm:t><a:p><a:r><a:t>Performance Squad</a:t></a:r></a:p></dgm:t>
|
|
</dgm:pt>
|
|
<dgm:pt modelId="3">
|
|
<dgm:t><a:p><a:r><a:t>Format Squad</a:t></a:r></a:p></dgm:t>
|
|
</dgm:pt>
|
|
<dgm:pt modelId="4">
|
|
<dgm:t><a:p><a:r><a:t>Infrastructure Squad</a:t></a:r></a:p></dgm:t>
|
|
</dgm:pt>
|
|
<dgm:pt modelId="5">
|
|
<dgm:t><a:p><a:r><a:t>Product Squad</a:t></a:r></a:p></dgm:t>
|
|
</dgm:pt>
|
|
</dgm:ptLst>
|
|
</dgm:dataModel>
|
|
"""
|
|
diagram_xml_2 = """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
<dgm:dataModel xmlns:dgm="http://schemas.openxmlformats.org/drawingml/2006/diagram"
|
|
xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
|
|
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
|
|
<dgm:ptLst>
|
|
<dgm:pt modelId="0" type="doc">
|
|
<dgm:t><a:p><a:r><a:t>Document Upload</a:t></a:r></a:p></dgm:t>
|
|
</dgm:pt>
|
|
<dgm:pt modelId="1">
|
|
<dgm:t><a:p><a:r><a:t>Quality Check</a:t></a:r></a:p></dgm:t>
|
|
</dgm:pt>
|
|
<dgm:pt modelId="2">
|
|
<dgm:t><a:p><a:r><a:t>Translation</a:t></a:r></a:p></dgm:t>
|
|
</dgm:pt>
|
|
<dgm:pt modelId="3">
|
|
<dgm:t><a:p><a:r><a:t>Format Preservation</a:t></a:r></a:p></dgm:t>
|
|
</dgm:pt>
|
|
<dgm:pt modelId="4">
|
|
<dgm:t><a:p><a:r><a:t>Delivery to User</a:t></a:r></a:p></dgm:t>
|
|
</dgm:pt>
|
|
</dgm:ptLst>
|
|
</dgm:dataModel>
|
|
"""
|
|
diagram_xml_3 = """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
<dgm:dataModel xmlns:dgm="http://schemas.openxmlformats.org/drawingml/2006/diagram"
|
|
xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
|
|
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
|
|
<dgm:ptLst>
|
|
<dgm:pt modelId="0" type="doc">
|
|
<dgm:t><a:p><a:r><a:t>Load Balancer</a:t></a:r></a:p></dgm:t>
|
|
</dgm:pt>
|
|
<dgm:pt modelId="1">
|
|
<dgm:t><a:p><a:r><a:t>API Gateway</a:t></a:r></a:p></dgm:t>
|
|
</dgm:pt>
|
|
<dgm:pt modelId="2">
|
|
<dgm:t><a:p><a:r><a:t>Translation Workers</a:t></a:r></a:p></dgm:t>
|
|
</dgm:pt>
|
|
<dgm:pt modelId="3">
|
|
<dgm:t><a:p><a:r><a:t>PostgreSQL Database</a:t></a:r></a:p></dgm:t>
|
|
</dgm:pt>
|
|
<dgm:pt modelId="4">
|
|
<dgm:t><a:p><a:r><a:t>Redis Cache</a:t></a:r></a:p></dgm:t>
|
|
</dgm:pt>
|
|
<dgm:pt modelId="5">
|
|
<dgm:t><a:p><a:r><a:t>Object Storage</a:t></a:r></a:p></dgm:t>
|
|
</dgm:pt>
|
|
<dgm:pt modelId="6">
|
|
<dgm:t><a:p><a:r><a:t>Monitoring Stack</a:t></a:r></a:p></dgm:t>
|
|
</dgm:pt>
|
|
</dgm:ptLst>
|
|
</dgm:dataModel>
|
|
"""
|
|
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_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")
|
|
|
|
ct = items["[Content_Types].xml"].decode("utf-8")
|
|
for i in range(1, 4):
|
|
override = (
|
|
f'<Override PartName="/ppt/diagrams/data{i}.xml" '
|
|
f'ContentType="application/vnd.openxmlformats-officedocument.'
|
|
f'drawingml+xml+diagram"/>'
|
|
)
|
|
if override not in ct:
|
|
ct = ct.replace("</Types>", f"{override}</Types>")
|
|
items["[Content_Types].xml"] = ct.encode("utf-8")
|
|
|
|
for n, data in items.items():
|
|
zout.writestr(n, data)
|
|
tmp.unlink()
|
|
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)")
|
|
|
|
|
|
# ===========================================================================
|
|
# COMPLEX PDF — B3 stress test
|
|
# ===========================================================================
|
|
|
|
def generate_pdf():
|
|
out = OUT_DIR / "test_pdf.pdf"
|
|
doc = fitz.open()
|
|
|
|
# Create all pages upfront
|
|
for _ in range(8):
|
|
doc.new_page()
|
|
|
|
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, 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)
|
|
|
|
# ============ Page 2: Introduction ============
|
|
p2 = doc[1]
|
|
p2.insert_text((72, 80), "1. Introduction", fontsize=20, color=(0.1, 0.2, 0.5))
|
|
p2.insert_text(
|
|
(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,
|
|
)
|
|
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, 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, 340, 372, 520), stream=buf.getvalue())
|
|
except ImportError:
|
|
p2.draw_rect(fitz.Rect(72, 340, 372, 520), color=(0.7, 0.7, 0.9), fill=(0.85, 0.85, 1.0))
|
|
|
|
# ============ 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,
|
|
)
|
|
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(70, 425, 380, 445),
|
|
"uri": "https://docs.office-translator.example.com/v3",
|
|
})
|
|
|
|
# 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")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
print(f"Output directory: {OUT_DIR.resolve()}\n")
|
|
generate_word()
|
|
generate_excel()
|
|
generate_pptx()
|
|
generate_pdf()
|
|
print("\n[OK] Test corpus generated.")
|
|
print("Each file is now substantial enough to catch real format-preservation bugs.")
|