Pandoc → DOCX → PDF Pipeline

Wenn ein Markdown-Dokument als ansehnliches PDF gerendert werden muss (Kunden-Angebote, Anlagen, Vertragsdokumente). Funktioniert lokal, ohne Cloud-Tool, ohne Browser-Print.

Use-Case

  • Ein Markdown-Dokument liegt vor (oder wird gerade geschrieben)
  • Es soll als professionelles PDF an einen Kunden gehen
  • Hauptdokument hat optisch konsistenten Look (gleiche Schrift, blaue Headlines, saubere Tabellen)
  • Browser-Print-to-PDF ist nicht reproduzierbar (Tabs gehen verloren, kein versionierbarer Build)

Originaler Trigger: Becker-Angebot V3 Anlagen 06.05.2026 — die alte angebot-v3-anlagen.pdf wurde aus einer temporär bearbeiteten Browser-HTML gerendert die nicht im Repo lag, danach war keine reproduzierbare Source mehr da.

Pipeline

angebot.md
    ↓ pandoc + theme-font-patch
angebot.docx
    ↓ LibreOffice headless
angebot.pdf

Drei Schritte, ca. 30 Sekunden Render-Zeit.

Schritt 1 — pandoc Markdown → DOCX

pandoc angebot.md -o angebot.docx --from markdown+raw_tex

Wichtig:

  • Kein --reference-doc wenn das Reference-Dokument keine Table-Styles definiert. Pandoc rendert dann Markdown-Tabellen zu flachen Listen (Spalten untereinander statt nebeneinander). Default-Stiles sind robuster, weil pandoc seine eigenen Table-Styles mitbringt.
  • --from markdown+raw_tex damit \newpage als Page-Break interpretiert wird (statt als Literal-Text).
  • YAML-Frontmatter wird ignoriert wenn er korrekt mit --- umschlossen ist. Kein extra Cleanup nötig.

Schritt 2 — Theme-Font auf Calibri patchen

Pandoc-Default verwendet Theme-Schrift “Aptos Display” + “Aptos” (Microsoft-365-Default). LibreOffice fällt auf Times-Roman zurück wenn diese Fonts nicht installiert sind. Auf Mac und Linux nicht installiert. Lösung: Theme-Schrift im docx auf Calibri setzen.

# Unpack
python3 ~/.claude/plugins/marketplaces/anthropic-agent-skills/skills/docx/scripts/office/unpack.py \
  angebot.docx /tmp/unpacked/
 
# In /tmp/unpacked/word/theme/theme1.xml
# - <a:majorFont>...<a:latin typeface="Aptos Display" .../>  → typeface="Calibri" panose="020F0302020204030204"
# - <a:minorFont>...<a:latin typeface="Aptos" .../>          → typeface="Calibri" panose="020F0302020204030204"
 
# Pack
python3 ~/.claude/plugins/marketplaces/anthropic-agent-skills/skills/docx/scripts/office/pack.py \
  /tmp/unpacked/ angebot.docx --original angebot.docx

Calibri ist auf macOS, Windows, Linux universell verfügbar (oder hat einen ordentlichen Fallback wie Liberation Sans). Konsistente Sans-Serif-Optik.

Wenn das Hauptdokument einen anderen Font verwendet (z.B. Helvetica, Inter), den entsprechenden Font hier eintragen.

Schritt 3 — LibreOffice DOCX → PDF

/Applications/LibreOffice.app/Contents/MacOS/soffice \
  --headless --convert-to pdf \
  --outdir <output-dir> \
  angebot.docx

Render-Zeit ca. 5 Sekunden für ein 13-Seiten-Dokument.

Sanity-Check vor Versand

Immer mit Read auf das gerenderte PDF mit pages: 1-3:

  • Tabellen rendern als Tabellen, nicht als flache Liste
  • Schrift ist Calibri oder gleichwertig (nicht Times Roman)
  • Headlines sind blau und linksbündig (Pandoc-Default mit Calibri sieht ordentlich aus)
  • Keine Korrekturen verloren gegangen
  • Page-Breaks an den richtigen Stellen (nach \newpage in MD)

Wenn Layout schief: zurück zu Schritt 1, prüfen ob --reference-doc das Problem war.

Layout-Optionen

Pandoc-Default sieht für Anlagen-Dokumente und Vertragsdokumente sauber aus: blaue Headlines, schwarze Body-Schrift, Sans-Serif, einfache Tabellen mit grauen Trennlinien.

Wenn mehr Marketing-Look gewünscht (KPI-Boxen, Hero-Header, Logo-Wortmarke, mehrspaltiges Layout): pandoc reicht nicht. Dann HTML+CSS+Print-CSS via Headless-Chrome oder direkt InDesign / Affinity Publisher. Aufwand deutlich höher.

Default-Pandoc-Pipeline ist der Sweet-Spot für substanzielle Anlagen-Dokumente wo Inhalt > Optik. Marketing-Hauptdokumente bleiben in ihrem nativen Tool (z.B. Word direkt, oder dem HTML-Renderer der die Original-PDF erzeugt hat).

Wann diese Pipeline NICHT nehmen

  • Wenn das Dokument PNG-Logos, SVG-Diagramme oder komplexe Multi-Column-Layouts enthält (KPI-Boxen, Side-Floats, Brand-Header) — das geht in pandoc nicht zuverlässig
  • Wenn das Hauptdokument in einem anderen Tool gepflegt wird (das bestehende Tool nutzen, nicht parallele Source aufbauen)
  • Wenn die Quelle nicht in Markdown gepflegt werden soll (z.B. weil der Kunde im DOCX-Format gegenliest)

Lessons aus Becker-V3-Run 2026-05-06

  1. Reference-Doc-Falle: mein erster Anlauf war pandoc anlagen.md -o anlagen.docx --reference-doc=hauptdokument.docx, weil ich Stil-Konsistenz wollte. Resultat: alle Tabellen flach, Hero-Headers zentriert. Pandoc übernimmt aus Reference-Doc nur die Stiles die dort definiert sind, und das Hauptdokument war von Hand erstellt ohne Table-Styles. Default ist sicherer.
  2. Theme-Font ist mehrere Layer tief: im pandoc-default-docx steht in styles.xml nur w:asciiTheme="minorHAnsi", was auf theme1.xml verweist, was wiederum die Schrift definiert. Wenn man die Schrift nur in styles.xml ändern will, muss man die Theme-Referenzen alle ersetzen — viele Edits. Theme-Patch in theme1.xml ist ein einziger Edit pro Font (major + minor) und propagiert automatisch.
  3. Calibri ist der pragmatische Default-Font. Inter wäre Brand-konformer (siehe assets/firma/email-signatures/), ist aber auf den meisten Systemen nicht installiert und Fallback-Verhalten ist unvorhersehbar. Calibri als Standard-Office-Font ist universell verfügbar oder hat sauberen Fallback.
  • SKILL §6b — wann diese Pipeline im Email-Review-Kontext greift
  • anthropic-skillsdocx-Skill liefert die unpack.py / pack.py Helper
  • _index — Becker-V3-Run vom 2026-05-06/07 (Pipeline erstmals produktiv genutzt)