---
name: office-files
description: Generate or edit docx, xlsx and PDF deliverables — Word documents, spreadsheets, reports — including editing a file the user uploaded.
---

## Setup — run these two lines first

```bash
pip install --break-system-packages --quiet pypandoc_binary typst openpyxl python-docx
export PATH="$(python3 -c 'import pypandoc,os;print(os.path.dirname(pypandoc.get_pandoc_path()))'):$PATH"
```

Takes about 6 seconds; nothing is preinstalled. Both lines are required — the system Python is PEP 668 externally managed, and the wheel ships the pandoc binary inside the package directory rather than on `PATH`.

If the install fails, deliver Markdown or a hosted HTML view instead and say the toolchain was unavailable. Never ship a worse format without saying so.

## Pick your flow

Every deliverable is **rendered from a source you author**. You never write a `.docx` or a `.pdf` directly:

```
prose  →  you write doc.md  →  pandoc                →  .docx
                             →  pandoc -t typst  →  .pdf
data   →  you build rows in Python  →  openpyxl  →  .xlsx
```

| Situation | Flow |
| --- | --- |
| Word document, user supplied a template | `pandoc doc.md --reference-doc=theirs.docx -o out.docx` — see **docx** |
| Word document, no template | `pandoc doc.md -o out.docx` — see **docx** |
| Word document needing a header, footer or page number, no template | build a reference doc first — see **docx** |
| PDF | `pandoc doc.md -t typst -s -V papersize=a4` then `typst.compile` — see **PDF** |
| Spreadsheet | openpyxl — see **xlsx** |
| Edit a file the user sent | **Start from the user's file** first, then the matching row above |

Styling never comes from the source you author — it comes from a `.docx` passed to pandoc, or from openpyxl. Never author a spreadsheet as a Markdown table, and never hand-build docx XML.

## Start from the user's file

```bash
pandoc theirs.docx -t markdown --wrap=none > doc.md
```

Edit the Markdown, then render it back with the same `.docx` as `--reference-doc` so their styling survives the round trip. For xlsx, read with openpyxl — pandoc lists xlsx as an input format but fails on many real files.

## docx

**Step 1 — write the content as Markdown into `doc.md`.** Headings become Word heading styles, Markdown tables become Word tables, and `**bold**` becomes bold. This file is the source of truth; the docx is a render of it.

**Step 2 — render it:**

```bash
pandoc doc.md --reference-doc=theme.docx -o out.docx
pandoc doc.md --reference-doc=theme.docx --toc --toc-depth=2 -o out.docx   # with a table of contents
```

`--reference-doc` is where headers, footers, page numbers, margins, paper size, fonts and numbering come from: the output inherits `word/header1.xml` and `word/footer1.xml`, including a live `PAGE` field. **Never write headers, footers or page numbers into the Markdown.**

- **User supplied a Word file** — use it as the reference doc. Their branding comes across for free.
- **No template** — omit the flag. The result is clean but unbranded, with no header, footer or page number. Say that in one line when you deliver it, and offer to match their house style if they send you a Word file.
- **No template, but the request needs a header, footer or page numbers** — build one from pandoc's own default, never from a blank document:

```bash
pandoc --print-default-data-file reference.docx > theme.docx
```

Then open `theme.docx` with python-docx, set `section.header`, add a `PAGE` field to `section.footer`, save, and pass it with `--reference-doc`. A blank `python-docx.Document()` lacks the styles pandoc emits, so Word silently renders them as Normal.

## PDF

Same `doc.md` as the docx flow — write the Markdown first, then render through typst:

```bash
pandoc doc.md -t typst -s -V papersize=a4 -o doc.typ
python3 -c "import typst; typst.compile('doc.typ', output='doc.pdf')"
```

For Chinese, Japanese or Korean text, add the matching font — `-V mainfont="Noto Sans CJK SC"` (use `JP` or `KR` for those languages).

Three things that fail quietly here:

- **`-s` is mandatory for any `-V` to apply.** Without it pandoc emits a fragment whose `.typ` contains no `set page` or `set text` at all, so every variable you pass is silently dropped.
- **Pin `papersize`.** Without `-s` you get typst's own default of A4; with `-s` and no `papersize` you get pandoc's template default of `us-letter`. Same document, different paper.
- **CJK without `mainfont` renders in the wrong script.** Chinese text falls back to `NotoSansCJKjp`, so you get Japanese glyph forms with no error and no missing characters. Verify with `pdffonts doc.pdf` — the embedded name must end in `sc` for Simplified Chinese.

Variable names come from `pandoc --print-default-template=typst`: `mainfont`, `mathfont`, `codefont`, `fontsize`, `papersize`.

**`--reference-doc` does not apply to PDF** — only docx, pptx and ODT support it, so this path produces a clean but unbranded PDF. When the user needs their branding on a PDF, produce the docx with their reference doc and tell them the PDF export has to happen on their side; the sandbox cannot convert docx to PDF.

## xlsx

```python
import openpyxl

wb = openpyxl.Workbook()
ws = wb.active
ws.title = "Q3"
ws.append(["Region", "Q2", "Q3", "Delta"])
ws.append(["APAC", 120, 148])
ws["D2"] = "=C2-B2"          # formulas are written as strings
wb.create_sheet("Notes")
wb.save("book.xlsx")
```

Excel evaluates formulas when the file opens, so when the file itself must already carry computed values, compute them yourself and write both the formula and the number.

## Deliver

`okou web upload-file`. When prose is final and being sent onward, attach both the PDF and the docx source: the recipient gets something fixed and something they can still edit.

## Never use

- `soffice` / LibreOffice conversion — the sandbox installs only `libreoffice-impress` and `libreoffice-draw`, so the Writer and Calc filters do not exist and every convert fails with `Error: source file could not be loaded`.
- `chromium --headless --print-to-pdf` — produces no file.
- `weasyprint` as a pandoc `--pdf-engine` — exits non-zero.
- `pandoc -o out.xlsx` — pandoc has no xlsx writer.

## Worked example — one report delivered as docx and PDF

```bash
pandoc --print-default-data-file reference.docx > theme.docx
python3 - <<'PY'
import docx
from docx.oxml.ns import qn
from docx.oxml import OxmlElement

d = docx.Document("theme.docx")
s = d.sections[0]
s.header.paragraphs[0].text = "ACME — Internal"
p = s.footer.paragraphs[0]
p.text = "Page "
r = p.add_run()
f = OxmlElement("w:fldSimple")
f.set(qn("w:instr"), "PAGE")
r._r.addnext(f)
d.save("theme.docx")
PY
pandoc report.md --reference-doc=theme.docx --toc --toc-depth=2 -o report.docx
pandoc report.md -t typst -s -V papersize=a4 -o report.typ
python3 -c "import typst; typst.compile('report.typ', output='report.pdf')"
```
