feat: pandoc/print.css – Druck-Layout für PDFs mit weasyprint

- A4, Seitenzahlen, jedes Kapitel auf neuer Seite, Inhaltsverzeichnis auf
  eigener Seite, Silbentrennung (nicht in Code), umbrechende Codezeilen;
  bringt Grundschrift und Titelkopf mit, da --css Pandocs Standard-CSS ersetzt
- Buch-README: zusätzlicher weasyprint-Befehl mit Filter und print.css
- Doku: Bedienungsanleitung und README um den weasyprint-Weg ergänzt

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-10-06 06:03:55 +02:00
commit 13617c8d3e
6 changed files with 52 additions and 4 deletions

View file

@ -319,7 +319,7 @@ Original-E-Books.
| Word | `pandoc meinbuch.md -o meinbuch.docx` | | Word | `pandoc meinbuch.md -o meinbuch.docx` |
| LibreOffice | `pandoc meinbuch.md -o meinbuch.odt` | | LibreOffice | `pandoc meinbuch.md -o meinbuch.odt` |
| PDF mit LaTeX | `pandoc meinbuch.md -s -o meinbuch.html && pandoc meinbuch.html --toc --pdf-engine=xelatex -o meinbuch.pdf` | | PDF mit LaTeX | `pandoc meinbuch.md -s -o meinbuch.html && pandoc meinbuch.html --toc --pdf-engine=xelatex -o meinbuch.pdf` |
| PDF ohne LaTeX | `pandoc meinbuch.md --toc --pdf-engine=weasyprint -o meinbuch.pdf` | | PDF ohne LaTeX | `pandoc meinbuch.md -s --toc --css ~/Python_Programs/epub2md/pandoc/print.css --pdf-engine=weasyprint -o meinbuch.pdf` |
| Kindle (AZW3) | `pandoc meinbuch.md --toc -o meinbuch.epub && ebook-convert meinbuch.epub meinbuch.azw3` | | Kindle (AZW3) | `pandoc meinbuch.md --toc -o meinbuch.epub && ebook-convert meinbuch.epub meinbuch.azw3` |
**Zum PDF:** **Zum PDF:**
@ -327,8 +327,11 @@ Original-E-Books.
- **Mit LaTeX** immer den Zwischenschritt über HTML nehmen, wie in der Tabelle. Beim direkten Weg von - **Mit LaTeX** immer den Zwischenschritt über HTML nehmen, wie in der Tabelle. Beim direkten Weg von
Markdown nach LaTeX lässt Pandoc komplexe Tabellen weg, und Querverweise im PDF funktionieren nicht. Bei Markdown nach LaTeX lässt Pandoc komplexe Tabellen weg, und Querverweise im PDF funktionieren nicht. Bei
Büchern mit Formeln ist der LaTeX-Weg die beste Wahl. Büchern mit Formeln ist der LaTeX-Weg die beste Wahl.
- **Mit weasyprint** genügt ein Schritt; Tabellen bleiben erhalten. Für Bücher mit vielen Formeln ist der - **Mit weasyprint** genügt ein Schritt; Tabellen, Farben, Infokästen und die Gestaltung aus `--styles html`
LaTeX-Weg sicherer. bleiben erhalten. Für Bücher mit vielen Formeln ist der LaTeX-Weg sicherer.
- **`print.css`** (im Ordner `pandoc/` von epub2md) sorgt beim weasyprint-Weg für ein Buchlayout: A4,
Seitenzahlen, jedes Kapitel auf einer neuen Seite, Silbentrennung und umbrechende lange Codezeilen. Ohne
sie übernimmt weasyprint das Bildschirmlayout, und lange Codezeilen werden am Seitenrand abgeschnitten.
Bei Büchern mit Formeln für HTML zusätzlich `--mathml` angeben, sonst bleiben Matrizen als TeX-Text stehen. Bei Büchern mit Formeln für HTML zusätzlich `--mathml` angeben, sonst bleiben Matrizen als TeX-Text stehen.
@ -342,6 +345,7 @@ F=~/Python_Programs/epub2md/pandoc/callouts.lua # Pfad zu deinem epub2md-Ord
pandoc meinbuch.md -s --toc --lua-filter $F -o meinbuch.html pandoc meinbuch.md -s --toc --lua-filter $F -o meinbuch.html
pandoc meinbuch.md --toc --lua-filter $F -o meinbuch.epub pandoc meinbuch.md --toc --lua-filter $F -o meinbuch.epub
pandoc meinbuch.md -s --lua-filter $F -o meinbuch.html && pandoc meinbuch.html --toc --lua-filter $F --pdf-engine=xelatex -o meinbuch.pdf pandoc meinbuch.md -s --lua-filter $F -o meinbuch.html && pandoc meinbuch.html --toc --lua-filter $F --pdf-engine=xelatex -o meinbuch.pdf
pandoc meinbuch.md -s --toc --lua-filter $F --css ~/Python_Programs/epub2md/pandoc/print.css --pdf-engine=weasyprint -o meinbuch.pdf
``` ```
In HTML, EPUB und im PDF über weasyprint werden daraus gerahmte, farbige Kästen mit Titel („Tipp: …“). Im In HTML, EPUB und im PDF über weasyprint werden daraus gerahmte, farbige Kästen mit Titel („Tipp: …“). Im

View file

@ -10,6 +10,11 @@ Versionsnummern nach [Semantic Versioning](https://semver.org/lang/de/).
- Seitenmarken (`--page-markers`) aus einem Kastentitel stehen jetzt vor dem Kasten statt im fetten Titel, - Seitenmarken (`--page-markers`) aus einem Kastentitel stehen jetzt vor dem Kasten statt im fetten Titel,
wie bei Überschriften. wie bei Überschriften.
### Neu
- `pandoc/print.css`: Druck-Layout für PDFs mit weasyprint (A4, Seitenzahlen, Kapitel auf neuer Seite,
Silbentrennung außer in Code, umbrechende Codezeilen). Die Buch-README enthält den passenden Befehl.
### Behoben ### Behoben
- `pandoc/callouts.lua`: Die Kästen blieben ohne Rahmen, wenn man Pandoc zusätzlich `-H`/`--include-in-header` - `pandoc/callouts.lua`: Die Kästen blieben ohne Rahmen, wenn man Pandoc zusätzlich `-H`/`--include-in-header`

View file

@ -339,6 +339,17 @@ pandoc mathematikbuch.md --mathml -s -o mathematikbuch.html
pandoc mathematikbuch.html --toc --pdf-engine=xelatex -o mathematikbuch.pdf pandoc mathematikbuch.html --toc --pdf-engine=xelatex -o mathematikbuch.pdf
``` ```
**PDF mit weasyprint** (ohne LaTeX; Farben, Kästen und `--styles html` bleiben erhalten):
```bash
pandoc buch.md -s --toc --lua-filter $F --css ~/Python_Programs/epub2md/pandoc/print.css \
--pdf-engine=weasyprint -o buch.pdf
```
`pandoc/print.css` liefert das Buchlayout: A4, Seitenzahlen, Kapitel auf neuer Seite, Silbentrennung (nicht in
Code) und umbrechende Codezeilen. Mit `--css` lässt Pandoc sein eigenes Stylesheet weg, deshalb bringt
`print.css` auch Grundschrift, Zeilenabstand und Titelkopf mit. Getestet mit dem Python-Buch (342 Seiten).
**Infokästen:** Pandoc kennt die Schreibweise `> [!TIP]` erst ab neueren Versionen (Erweiterung `alerts`); **Infokästen:** Pandoc kennt die Schreibweise `> [!TIP]` erst ab neueren Versionen (Erweiterung `alerts`);
3.1.3 (Ubuntu 24.04) zeigt „[!TIP]“ als Text. Der Filter `pandoc/callouts.lua` behebt das: In HTML und 3.1.3 (Ubuntu 24.04) zeigt „[!TIP]“ als Text. Der Filter `pandoc/callouts.lua` behebt das: In HTML und
EPUB entstehen gerahmte, farbige Kästen mit Titel („Tipp: …“, bei `lang: en` „Tip: …“), in LaTeX/PDF, Word EPUB entstehen gerahmte, farbige Kästen mit Titel („Tipp: …“, bei `lang: en` „Tip: …“), in LaTeX/PDF, Word
@ -438,7 +449,8 @@ epub2md/
└── utils.py Slugs, YAML-Emitter, Pfadhelfer └── utils.py Slugs, YAML-Emitter, Pfadhelfer
pandoc/ pandoc/
├── callouts.lua Pandoc-Filter: '> [!TIP]'-Kästen → gerahmte Kästen (HTML/EPUB) bzw. Zitatblock mit Titel ├── callouts.lua Pandoc-Filter: '> [!TIP]'-Kästen → gerahmte Kästen (HTML/EPUB) bzw. Zitatblock mit Titel
└── callouts.css Gestaltung der Kästen (vom Filter eingebunden; muss neben callouts.lua liegen) ├── callouts.css Gestaltung der Kästen (vom Filter eingebunden; muss neben callouts.lua liegen)
└── print.css Druck-Layout für PDFs mit weasyprint (A4, Seitenzahlen, Kapitel auf neuer Seite, Codeumbruch)
``` ```
Statt BeautifulSoup wird direkt lxml verwendet: Es ist für die XHTML-Bäume ausreichend, schneller und Statt BeautifulSoup wird direkt lxml verwendet: Es ist für die XHTML-Bäume ausreichend, schneller und

View file

@ -17,6 +17,7 @@ from .references import LinkResolver
from .utils import rel_link, to_yaml, url_path from .utils import rel_link, to_yaml, url_path
CALLOUT_FILTER = Path(__file__).resolve().parent.parent / "pandoc" / "callouts.lua" CALLOUT_FILTER = Path(__file__).resolve().parent.parent / "pandoc" / "callouts.lua"
PRINT_CSS = Path(__file__).resolve().parent.parent / "pandoc" / "print.css"
SPECIAL_LABELS = { SPECIAL_LABELS = {
"glossary": "Glossar", "glossary": "Glossar",
@ -146,6 +147,12 @@ def readme(md: BookMetadata, chapters: list[Chapter], endnotes: Chapter | None,
f"pandoc {shlex.quote(stem + '.md')} -s{math}{lua} -o {shlex.quote(stem + '.html')} && " f"pandoc {shlex.quote(stem + '.md')} -s{math}{lua} -o {shlex.quote(stem + '.html')} && "
f"pandoc {shlex.quote(stem + '.html')} --toc{lua} --pdf-engine=xelatex -o {shlex.quote(stem + '.pdf')}", f"pandoc {shlex.quote(stem + '.html')} --toc{lua} --pdf-engine=xelatex -o {shlex.quote(stem + '.pdf')}",
] ]
if PRINT_CSS.is_file():
commands += [
"# PDF ohne LaTeX mit weasyprint: Farben, Kästen und HTML-Stile bleiben erhalten",
f"pandoc {shlex.quote(stem + '.md')} -s --toc{math}{lua} --css {shlex.quote(str(PRINT_CSS))} "
f"--pdf-engine=weasyprint -o {shlex.quote(stem + '.pdf')}",
]
out.append("## Weiterverarbeitung") out.append("## Weiterverarbeitung")
out.append("```bash\n" + "\n".join(commands) + "\n```") out.append("```bash\n" + "\n".join(commands) + "\n```")
if callouts: if callouts:

19
pandoc/print.css Normal file
View file

@ -0,0 +1,19 @@
/* Print layout for PDFs from epub2md output via Pandoc + weasyprint:
pandoc buch.md -s --toc --lua-filter pandoc/callouts.lua --css pandoc/print.css --pdf-engine=weasyprint -o buch.pdf */
@page { size: A4; margin: 2cm 1.8cm; @bottom-center { content: counter(page); font-size: 9pt; } }
body { max-width: none; font-size: 10.5pt; line-height: 1.4; hyphens: auto; text-align: justify;
font-family: "DejaVu Serif", "Liberation Serif", Georgia, serif; }
#title-block-header, #title-block-header h1.title { text-align: center; margin-bottom: 2em; }
h1, h2, h3, h4 { text-align: left; hyphens: manual; page-break-after: avoid; }
pre, code { font-family: "DejaVu Sans Mono", "Liberation Mono", monospace; hyphens: none; }
pre { background: #f6f8fa; padding: 0.5em 0.8em; border-radius: 0.3em; page-break-inside: auto; }
blockquote { margin: 1em 0 1em 1.5em; padding-left: 0.8em; border-left: 2px solid #ccc; color: #444; }
nav#TOC ul { list-style: none; padding-left: 1.2em; }
a { color: inherit; text-decoration: none; }
pre, pre code { white-space: pre-wrap; overflow-wrap: anywhere; font-size: 8.5pt; } /* long code lines wrap */
pre { overflow: visible; }
img { max-width: 100%; height: auto; }
h1 { page-break-before: always; } /* every chapter on a new page */
#title-block-header h1, #TOC h1 { page-break-before: auto; }
nav#TOC { page-break-after: always; }
table { font-size: 9pt; }

View file

@ -190,6 +190,7 @@ def test_book_readme_suggests_callout_filter(convert):
body = '<h1>K</h1><div class="box"><h3 class="boxheading">Merke</h3><p>Wichtiger Satz.</p></div>' body = '<h1>K</h1><div class="box"><h3 class="boxheading">Merke</h3><p>Wichtiger Satz.</p></div>'
readme = (convert(Book(docs=[Doc("c.xhtml", body)])).out / "README.md").read_text(encoding="utf-8") readme = (convert(Book(docs=[Doc("c.xhtml", body)])).out / "README.md").read_text(encoding="utf-8")
assert "--lua-filter" in readme and "pandoc/callouts.lua" in readme and "1 Infokasten " in readme assert "--lua-filter" in readme and "pandoc/callouts.lua" in readme and "1 Infokasten " in readme
assert "pandoc/print.css --pdf-engine=weasyprint" in readme
plain = convert(Book(docs=[Doc("c.xhtml", body)]), "--boxes", "plain").out / "README.md" plain = convert(Book(docs=[Doc("c.xhtml", body)]), "--boxes", "plain").out / "README.md"
assert "--lua-filter" not in plain.read_text(encoding="utf-8") assert "--lua-filter" not in plain.read_text(encoding="utf-8")