Add KDP 6×9 grayscale print pipeline and ship print PDF.

Parallel to the A4 eBook build: print assets, pdf/print templates,
print QA mode, /pdf-print skill, and rebuilt interior PDF.
This commit is contained in:
grok-bot committed 2026-08-08 17:07:57 +02:00
1 parent 0624756b2d
commit bd721b3df1
21 files changed
+1498 -49

No files matched your search

+17
View File
@@ -0,0 +1,17 @@
---
description: Baut die KDP-Print-Interior-PDF (6×9 Grayscale) und führt Print-QA aus
argument-hint: "[full|assets-only|qa-only]"
---
# Print-PDF (KDP Interior) bauen und prüfen
Führe den PDF-Print-Skill aus. Lade `.grok/skills/pdf-print/SKILL.md`.
Argument:
| Wert | Aktion |
|------|--------|
| `full` (default) | `just pdf-print` + `just qa-print` |
| `assets-only` | nur `just print-assets` |
| `qa-only` | nur `just qa-print` |
Die eBook-PDF (`just pdf`) bleibt unberührt, sofern nicht `just clean` verlangt wird.
+69 -12
View File
@@ -9,14 +9,19 @@ Build-Regeln für die PDF-Pipeline dieses Buchs.
| Kommando | Wirkung | Wann |
|----------|---------|------|
| `just assemble` | `00–18-*.md` → `build/book.md` | Debug Assembler / Callouts |
| `just pdf` | assemble + pandoc/lualatex → `dist/grok-build-praxisleitfaden.pdf` | nach inhaltlichen Änderungen (wenn Build verlangt) |
| `just pdf` | assemble + pandoc/lualatex → `dist/grok-build-praxisleitfaden.pdf` | eBook A4 Farbe (wenn Build verlangt) |
| `just pdf-draft` | Draft-PDF mit Wasserzeichen | Review-Runden |
| `just pdf-plain` | ohne gestyltes Template | schneller Layout-Vergleich |
| `just qa` | `scripts/check_pdf.py` | nach `just pdf` |
| `just all` | pdf + qa | Release-nahe Prüfung |
| `just pdf-print` | assemble + Grayscale-Assets + KDP 6×9 → `dist/…-print.pdf` | Paperback-Interior |
| `just print-assets` | nur `scripts/prepare_print_assets.py` | Debug Bild-Prep |
| `just pdf-print-optimize` | optional GS Gray/prepress auf Print-PDF | vor KDP-Upload |
| `just qa` | `scripts/check_pdf.py` (eBook) | nach `just pdf` |
| `just qa-print` | `scripts/check_pdf.py --mode print` | nach `just pdf-print` |
| `just all` | pdf + qa | Release-nahe Prüfung eBook |
| `just all-print` | pdf-print + qa-print | Release-nahe Prüfung Print |
| `just clean` | `build/` + dist-PDF-Artefakte | sauberer Neu-Build |
PDF nur bauen, wenn der Task es verlangt.
PDF nur bauen, wenn der Task es verlangt. eBook und Print sind **parallele** Outputs.
## Pipeline
@@ -31,7 +36,7 @@ PDF nur bauen, wenn der Task es verlangt.
-H pdf/styles/guide-colors.tex
--metadata-file=pdf/metadata.yaml
--lua-filter=pdf/filters/tables.lua
--resource-path=.:beispiele:vorlagen:assets:assets/illustrations:assets/cover
--resource-path=.:beispiele:vorlagen:assets:…
│
▼
dist/grok-build-praxisleitfaden.pdf
@@ -40,6 +45,40 @@ PDF nur bauen, wenn der Task es verlangt.
QA: Existenz, Seitenzahl (Soft 40–280), Keywords, Fonts
```
### Print / KDP (Paperback Interior)
```text
build/book.md (gleicher Assembler)
│
▼ scripts/prepare_print_assets.py
build/print-assets/ (Grayscale-JPGs, MD-Pfad-Spiegel assets/…)
│
▼ pandoc --pdf-engine=lualatex
--template=pdf/templates/guide.tex (print-interior: true)
-H pdf/print/print-colors.tex
--metadata-file=pdf/print/metadata.yaml
--lua-filter=pdf/filters/tables.lua (-V printmode=true)
--syntax-highlighting=monochrome (Code ohne RGB)
resource-path: build/print-assets:… zuerst
│
▼
dist/grok-build-praxisleitfaden-print.pdf
│
▼ scripts/check_pdf.py --mode print
QA: 6×9, Fonts, Seiten, Keywords + Gray-Stage + manuelle Hinweise
```
| Print-Datei | Rolle |
|-------------|--------|
| `pdf/print/metadata.yaml` | 6×9 geometry, twoside, 10 pt, linestretch 1,22 |
| `pdf/print/print-colors.tex` | Grayscale-Palette (gleiche Farbnamen) |
| `pdf/print/print-preamble.tex` | Sans-Body, Kopf/Fuß außen, Callouts B/W, Cover-Titelseite |
| `scripts/prepare_print_assets.py` | RGB → Grayscale + Kontrast |
| `pdf/print/QA-NOTES.md` | Scorecard + manuelle Prüfpunkte |
**KDP-Spec (Interior):** Trim 6×9 in · White Paper · kein Bleed · inner **0,65"** · outer **0,60"** · top **0,70"** · bottom **0,65"** · Titelseite Cover-Safe-Frame **0,30"** · Figuren Gin **0,87** (1280 px ≈ 300 DPI) · openany · Body **10 pt** (TeX Gyre Heros Scale=1.0) · linestretch **1,22** · Box/Code 9 pt · Schriften embedded. Optional: `just pdf-print-optimize` (Ghostscript Gray/prepress).
**Cover-PDF** (Bleed/Spine) ist **nicht** Teil dieser Pipeline. Interior-Titelseite nutzt `assets/cover/cover-front-print.jpg` (Fallback: `COVER-01B-full.jpg` / `cover-front.jpg`).
## Callout-Transform (Assembler)
`scripts/assemble_book.py` mappt:
@@ -56,29 +95,47 @@ Diese Marker im Manuskript nicht „verschönern“ oder in andere Markdown-Kons
|------|-------|-------------|
| `00–18-*.md` | Quellkapitel | ✅ ja |
| `scripts/assemble_book.py` | Concat + Callouts | ⚠️ nur mit Auftrag |
| `scripts/check_pdf.py` | QA | ⚠️ nur mit Auftrag |
| `pdf/templates/*`, `pdf/filters/*`, `pdf/styles/*` | Layout | ⚠️ nur mit Auftrag |
| `pdf/metadata.yaml`, `pdf/front/*` | Metadaten / Impressum | ✅ bei Meta-Tasks |
| `assets/**` | Bilder | ✅ bei Illustrations-Tasks |
| `scripts/check_pdf.py` | QA eBook + Print (`--mode`) | ⚠️ nur mit Auftrag |
| `scripts/prepare_print_assets.py` | Grayscale-Assets für Print | ⚠️ nur mit Auftrag |
| `pdf/templates/*`, `pdf/filters/*`, `pdf/styles/*` | Layout eBook (+ Template-Schalter Print) | ⚠️ nur mit Auftrag |
| `pdf/print/*` | Layout KDP Interior | ⚠️ nur mit Auftrag |
| `pdf/metadata.yaml` | Metadaten eBook | ✅ bei Meta-Tasks |
| `pdf/print/metadata.yaml` | Metadaten Print | ✅ bei Print-Meta-Tasks |
| `pdf/front/*` | Impressum | ✅ bei Meta-Tasks |
| `assets/**` | Cover/Bilder | ✅ bei Illustrations-Tasks |
| `build/`, `dist/*` | generiert | ❌ neu bauen, nicht handpatchen |
## QA (`just qa`)
## QA (`just qa` / `just qa-print`)
**eBook (`just qa`):**
- PDF existiert und ist nicht leer
- Soft-Seitenzahl: ca. 40–280 (Warnung außerhalb; wächst mit Kapitel-Füllung)
- Soft-Seitenzahl: ca. 40–280
- Keywords u. a.: Grok Build, AGENTS.md, config.toml, MCP, Plan Mode
- Font-Embedding, falls `pdffonts` verfügbar
**Print (`just qa-print`):**
- wie eBook, plus **Page size ≈ 6×9 in** (432×648 pt)
- Soft-Seitenzahl: ca. 100–320
- Gray-Stage / optional `pdfimages` RGB-Fail
- Ausgabe manueller Prüfpunkte (Diagramme, Tabellen, DPI)
## Fehlerbehandlung
| Symptom | Prüfen |
|---------|--------|
| `pandoc: command not found` | PATH / Installation |
| `lualatex: command not found` | TeX Live auf dem Host |
| Font not found (TeX Gyre Heros) | TeX Live Fonts / TeX Gyre |
| Assembler-Fehler | Kapiteldateien in `CHAPTER_ORDER` vorhanden? |
| fehlende Bilder im PDF | Pfade unter `assets/`, `resource-path` im justfile |
| Print-Bilder farbig | Stage muss MD-Pfade spiegeln (`build/print-assets/assets/…`); resource-path `build/print-assets` zuerst; QA: `pdfimages -list` → color=gray |
| Print-Code farbig | `--syntax-highlighting=monochrome` in `just pdf-print` (eBook-Default bleibt farbig) |
| Print nicht 6×9 | `pdf/print/metadata.yaml` geometry; Template `print-interior` |
| Callout fehlt im PDF | Label-Schreibweise im Markdown |
| PIL/Pillow missing | `pip install pillow` für `print-assets` |
## Voraussetzungen
`pandoc`, `just`, TeX Live (LuaLaTeX, KOMA-Script `scrbook`), Fonts Charter + DejaVu.
`pandoc`, `just`, TeX Live (LuaLaTeX, KOMA-Script `scrbook`), Fonts Charter + DejaVu (eBook), TeX Gyre Heros (Print), optional Pillow + Ghostscript + poppler (`pdffonts`/`pdfinfo`/`pdfimages`).
+3 -1
View File
@@ -21,11 +21,13 @@ Pipeline: Assembler → Pandoc → LuaLaTeX → QA. Details: `.grok/rules/bau-pi
| Art | Kommando | Output |
|-----|----------|--------|
| full (default) | `just pdf` | `dist/grok-build-praxisleitfaden.pdf` |
| full (default) | `just pdf` | `dist/grok-build-praxisleitfaden.pdf` (A4 eBook) |
| draft | `just pdf-draft` | `dist/grok-build-praxisleitfaden-draft.pdf` |
| plain | `just pdf-plain` | `dist/grok-build-praxisleitfaden-plain.pdf` |
| nur assemblieren | `just assemble` | `build/book.md` |
**KDP Print (6×9 Grayscale):** separater Skill `pdf-print` / `just pdf-print` — nicht dieser Skill.
## Ablauf
### 1. Klären und vorbereiten
+129
View File
@@ -0,0 +1,129 @@
---
name: pdf-print
description: >
Baut die druckfertige KDP-Paperback-Interior-PDF (6×9 in, Grayscale) für
„Grok Build – Der umfassende Praxisleitfaden“ und führt Print-QA aus.
Nutzen bei: „Print-PDF“, „KDP Interior“, „Paperback bauen“, „6x9“,
„pdf-print“, /pdf-print.
---
# PDF-Print-Skill — KDP Interior (6×9 Grayscale)
Parallele Pipeline zur eBook-PDF. eBook bleibt `just pdf` / Skill `pdf-bauen`.
Details: `.grok/rules/bau-pipeline.md` → Abschnitt „Print / KDP“.
## Prinzipien
1. Denken vor dem Schreiben — Fehler analysieren, bevor blind neu gebaut wird.
2. Einfachheit — nur die Build-Art, die nötig ist.
3. Chirurgisch — während des Bauens keine Manuskript-Edits „nebenbei“.
4. Zielgetrieben — fertig erst, wenn Print-PDF und Print-QA stehen.
## Spec (KDP Interior)
| Parameter | Wert |
|-----------|------|
| Trim | 6 × 9 in |
| Farbe | Grayscale (White Paper); Code: `--syntax-highlighting=monochrome` |
| Bleed | nein |
| Ränder | inner **0,65"** · outer **0,60"** · top **0,70"** · bottom **1,00"** (Footer-Ink ≈ **0,65"**) |
| Typo | Body **10 pt** (Scale=1.0) · Leading ~14,6 pt · Box/Code **9 pt** |
| Titelseite | Cover im **0,30"**-Safe-Frame (KDP-Außen ≥ 0,25") |
| Figuren | Gin `0,87\linewidth` → 1280 px ≈ 300 DPI |
| Output | `dist/grok-build-praxisleitfaden-print.pdf` |
| PDF | 1.4–1.7, Schriften embedded; optional GS Gray (`just pdf-print-optimize`) |
## Build-Arten
| Art | Kommando | Output |
|-----|----------|--------|
| full (default) | `just pdf-print` | assemble + grayscale assets + Print-PDF |
| nur Assets | `just print-assets` | `build/print-assets/` |
| GS-Optimize | `just pdf-print-optimize` | Gray/prepress-Nachlauf auf Print-PDF |
| nur QA | `just qa-print` | Checks auf existierendes Print-PDF |
| full + QA | `just all-print` | pdf-print + qa-print |
**eBook (unabhängig):** `just pdf` → `dist/grok-build-praxisleitfaden.pdf`
## Ablauf
### 1. Klären und vorbereiten
- Default: full Print-Build + QA.
- Optional: `just clean` nur wenn sauberer Neu-Build gewünscht (löscht auch eBook-PDF in `dist/`).
### 2. Bauen
```bash
just pdf-print
```
Pipeline intern:
1. `scripts/assemble_book.py` → `build/book.md`
2. `scripts/prepare_print_assets.py` → Grayscale-JPGs unter `build/print-assets/assets/…` (MD-Pfad-Spiegel)
3. pandoc + LuaLaTeX mit `pdf/print/*` (6×9, twoside, hidelinks, monochrome Syntax)
**Typische Fehler:**
| Fehler | Ursache | nächster Schritt |
|--------|---------|------------------|
| `pandoc` / `lualatex` missing | Tool fehlt im PATH | Installation / Host-TeX |
| Font not found (TeX Gyre Heros) | TeX Live unvollständig | `texlive-fonts-recommended` / TeX Gyre |
| LaTeX-Error | print-preamble, geometry | Log in Terminal lesen |
| PIL/Pillow missing | Asset-Prep | `pip install pillow` |
| Overfull hbox | schmale Tabellen/Code | `tables.lua` / Code umbrechen |
→ Verifikation: `dist/grok-build-praxisleitfaden-print.pdf` existiert und > 0 Bytes.
### 3. QA
```bash
just qa-print
```
Prüft u. a.: Existenz, Seitenzahl (Soft 100–320), Font-Embedding, Keywords, **Page size ≈ 6×9 in**, Gray-Stage.
### 4. Ergebnis melden
```text
## Build-Ergebnis (Print / KDP)
**Datei:** dist/grok-build-praxisleitfaden-print.pdf
**Format:** 6×9 in · Grayscale · twoside
**Seiten:** …
**QA:** bestanden / Warnungen / Fehler
**eBook unverändert:** dist/grok-build-praxisleitfaden.pdf (falls nicht mit-clean)
### Änderungs-Checkliste (Pipeline)
- [ ] Trim 6×9, Ränder inner 0,65", outer 0,60", top 0,70", bottom 1,00" (Footer-Ink ≈ 0,65")
- [ ] Titelseite Cover-Safe-Frame ≥ 0,30"
- [ ] Grayscale-Palette + grayscale Assets + monochrome Code-Highlight
- [ ] Figuren Gin 0,87 → ~300 DPI bei 1280 px
- [ ] Sans-Body TeX Gyre Heros **10 pt Scale=1.0**, Mono/Box/Code ~9 pt
- [ ] Seitenzahlen außen; Kolumnentitel dezent
- [ ] Schriften embedded; kein Media-Box-Overflow
- [ ] Keine unnötigen Kapitel-Leerseiten (openany)
### Manuelle Nacharbeit (bei Bedarf)
- Diagramme mit reinem Farbkanal (K-01, K-05, M-*, S-*)
- Cover: natives ≥1800×2700 (cover-front-print.jpg)
- Sehr breite Tabellen im schmalen Satzspiegel
- Optional: just pdf-print-optimize vor KDP-Upload
```
## Aufräumen
```bash
just clean
```
Löscht `build/` (inkl. print-assets) und `dist/*.pdf`.
## Abgrenzung
| Skill / Command | Rolle |
|-----------------|--------|
| `pdf-bauen` / `/pdf` | A4 eBook (Farbe) |
| **`pdf-print` / `/pdf-print`** | KDP Interior 6×9 B/W |
| KDP-**Cover** | separat (Bleed + Spine) — nicht dieser Skill |
+22 -2
View File
@@ -6,6 +6,7 @@ Dieses Dokument hält fest, was aus den Schwesterbüchern übernommen und was f
|--------|----------------|
| `../ki-claude` | Der Claude-Code-Harness |
| `../ki-codex` | Der Codex-Harness |
| `../ki-eit` | Agents mit Angular, Spring Boot & Quarkus |
Stand der Adaption: **August 2026**.
@@ -129,8 +130,27 @@ Der Auftrag nannte u. a. `book/` und `examples/`. Zugunsten der Schwesterproje
| | Claude | Codex | Grok |
|-|--------|-------|------|
| Dateiname | `dist/claude-code-harness.pdf` | `dist/codex-harness.pdf` | `dist/grok-build-praxisleitfaden.pdf` |
| Soft-Seitenzahl QA | 30–120 | ähnlich schlank | 40–280 (19 Kapitel) |
| Dateiname eBook | `dist/claude-code-harness.pdf` | `dist/codex-harness.pdf` | `dist/grok-build-praxisleitfaden.pdf` |
| Dateiname Print | — | — | `dist/grok-build-praxisleitfaden-print.pdf` (KDP 6×9) |
| Soft-Seitenzahl QA | 30–120 | ähnlich schlank | eBook 40–280 · Print 100–320 |
---
## 3b. Übernommen aus `ki-eit` (Print / KDP Interior, August 2026)
Parallele Paperback-Interior-Pipeline (nicht eBook-ersetzend).
| Element | Übernahme / Anpassung |
|---------|------------------------|
| `just pdf-print`, `print-assets`, `qa-print`, `all-print`, `pdf-print-optimize` | ja — Output-Slug Grok |
| `pdf/print/*` (metadata, colors, preamble, QA-NOTES) | ja — Titel/Cover-Pfade Grok |
| `scripts/prepare_print_assets.py` | ja — Cover-Picker: `cover-front-print.jpg` → `COVER-01B-full.jpg` → `cover-front.jpg` |
| `check_pdf.py --mode print` | ja — Keywords/Soft-Seiten Grok |
| `guide.tex` `print-interior` + geometry-Zweig | ja |
| `tables.lua` `printmode` | ja |
| Skill `pdf-print` + `/pdf-print` | ja — Buchtitel/Slug angepasst |
| TeX Gyre Heros 10 pt Body (Print) | ja (eBook bleibt Charter) |
| Manuskript-Tabellen-Splits à la EIT Anhang C | **nicht** — nur Pipeline; Overflows separat |
### Was (noch) nicht mitgenommen wurde
+8 -5
View File
@@ -45,7 +45,7 @@ Projekt-Regeln für Coding-Agents. Karpathy-Prinzipien, adaptiert auf ein deutsc
**Autoren-Harness vs. Leser-Vorlagen:** Root-`AGENTS.md` / `CLAUDE.md` / `.grok/` steuern **Agenten, die am Buch arbeiten**. `vorlagen/` und `beispiele/` sind **Content für Leser** – nicht mit dem Autoren-Harness vermischen.
**Autoren-Skills (`.grok/skills/`):** `pdf-bauen`, `lektorat`, `kapitel-review`, `humanizer`, `update-pics`. Slash-Commands: `.grok/commands/` (`/pdf`, `/lektorat`, `/review`, `/status`, `/update-pics`). Schreib- und Build-Details: `.grok/rules/`.
**Autoren-Skills (`.grok/skills/`):** `pdf-bauen`, `pdf-print`, `lektorat`, `kapitel-review`, `humanizer`, `update-pics`. Slash-Commands: `.grok/commands/` (`/pdf`, `/pdf-print`, `/lektorat`, `/review`, `/status`, `/update-pics`). Schreib- und Build-Details: `.grok/rules/`.
**Intern (nicht Manuskript):** `prompts/` ist Autoren-Material.
@@ -70,15 +70,18 @@ Operativer Kurzkompass (Build, Do/Don’t): Root-`CLAUDE.md`. Details: `.grok/ru
```bash
just assemble # 00–18 → build/book.md
just pdf # assemble + pandoc/lualatex → dist/grok-build-praxisleitfaden.pdf
just pdf # assemble + pandoc/lualatex → dist/grok-build-praxisleitfaden.pdf (A4 eBook)
just pdf-draft # Draft-PDF mit Wasserzeichen
just pdf-plain # ohne gestyltes Template
just qa # scripts/check_pdf.py
just pdf-print # KDP Interior 6×9 Grayscale → dist/…-print.pdf
just qa # scripts/check_pdf.py (eBook)
just qa-print # scripts/check_pdf.py --mode print
just all # pdf + qa
just all-print # pdf-print + qa-print
just clean # build/ und dist-PDF-Artefakte
```
PDF nur bauen, wenn der Task es verlangt (nach inhaltlichen Änderungen oder explizit). Nach `just pdf` bei Bedarf `just qa`.
PDF nur bauen, wenn der Task es verlangt (nach inhaltlichen Änderungen oder explizit). Nach `just pdf` / `just pdf-print` bei Bedarf `just qa` bzw. `just qa-print`. eBook und Print sind parallele Outputs.
**Voraussetzungen:** `pandoc`, `just`, TeX Live (LuaLaTeX, KOMA `scrbook`), Fonts Charter + DejaVu.
@@ -131,7 +134,7 @@ PDF nur bauen, wenn der Task es verlangt (nach inhaltlichen Änderungen oder exp
### Nicht ohne Auftrag anfassen
- `pdf/templates/*`, `pdf/filters/*`, `pdf/styles/*` (Layout-Pipeline)
- `pdf/templates/*`, `pdf/filters/*`, `pdf/styles/*`, `pdf/print/*` (Layout-Pipeline)
- Generierte `build/`-Inhalte (kommen aus dem Assembler)
- Große Binaries unter `dist/`, `assets/` ohne konkreten Bild-/Build-Task
- `beispiele/*` nur ändern, wenn das Beispiel selbst Thema ist
+8 -5
View File
@@ -8,17 +8,20 @@ Dies ist ein **deutschsprachiges Praxisbuch** über Grok Build – kein App-Stac
```bash
just assemble # Kapitel → build/book.md
just pdf # → dist/grok-build-praxisleitfaden.pdf
just pdf # → dist/grok-build-praxisleitfaden.pdf (A4 eBook)
just pdf-draft # Draft mit Wasserzeichen
just pdf-plain # ohne gestyltes Template
just qa # PDF-Checks (Seiten, Keywords, Fonts)
just pdf-print # KDP Interior 6×9 Grayscale → dist/…-print.pdf
just qa # PDF-Checks eBook (Seiten, Keywords, Fonts)
just qa-print # PDF-Checks Print (6×9, Gray-Stage)
just all # pdf + qa
just all-print # pdf-print + qa-print
just clean
```
Voraussetzungen: `pandoc`, `just`, TeX Live (LuaLaTeX, KOMA `scrbook`), Fonts Charter + DejaVu.
Voraussetzungen: `pandoc`, `just`, TeX Live (LuaLaTeX, KOMA `scrbook`), Fonts Charter + DejaVu (eBook), TeX Gyre Heros (Print), optional Pillow.
PDF nur bauen, wenn der Task es verlangt. Nach dem Build bei Bedarf `just qa`.
PDF nur bauen, wenn der Task es verlangt. Nach dem Build bei Bedarf `just qa` bzw. `just qa-print`.
## Layout (Source of Truth)
@@ -51,4 +54,4 @@ PDF nur bauen, wenn der Task es verlangt. Nach dem Build bei Bedarf `just qa`.
- Keine erfundenen npm/poetry/React-Stacks für dieses Repo.
- `pdf/templates/*`, `pdf/filters/*`, `pdf/styles/*` nur bei explizitem Auftrag.
- Plan vor größeren Mehrdatei-Änderungen; Erfolgskriterien prüfbar formulieren.
- Details: `.grok/rules/`; Skills: `pdf-bauen`, `lektorat`, `kapitel-review`, `humanizer`, `update-pics`.
- Details: `.grok/rules/`; Skills: `pdf-bauen`, `pdf-print`, `lektorat`, `kapitel-review`, `humanizer`, `update-pics`.
+153
View File
@@ -0,0 +1,153 @@
# Handoff — Grok Build Praxisleitfaden (`ki-grok`)
Stand: **2026-08-08** · Branch: `main` · **kein Commit** der Print-Arbeit (Working Tree dirty)
Zweck: nächste Session ohne Kontextverlust fortsetzen. Autoren-Harness; kein Manuskript-Inhalt.
---
## Projekt-Kompass
| | |
|--|--|
| **Titel** | *Grok Build – Der umfassende Praxisleitfaden* |
| **Repo** | `/Volumes/SSD2TB/books/ki-grok` |
| **Kapitel** | `00-vorwort.md` … `18-anhang-schnellreferenz.md` (kanonisch) |
| **eBook PDF** | `dist/grok-build-praxisleitfaden.pdf` (A4, Farbe) |
| **Print PDF** | `dist/grok-build-praxisleitfaden-print.pdf` (6×9, Grayscale, KDP Interior) |
| **Schwestern** | `../ki-claude`, `../ki-codex`, `../ki-eit` — siehe `ADAPTIONEN.md` |
| **Fakten-Stand Buch** | Juli–Aug 2026, CLI 0.2.x |
---
## Erledigt in dieser Arbeitsspur
### 1. Print-Pipeline aus `ki-eit` portiert
Parallele KDP-Interior-Pipeline neben dem bestehenden A4-eBook.
| Artefakt | Pfad / Befehl |
|----------|----------------|
| Just-Targets | `just pdf-print`, `print-assets`, `qa-print`, `all-print`, `pdf-print-optimize` |
| Metadata / Farben / Preamble | `pdf/print/{metadata.yaml,print-colors.tex,print-preamble.tex,QA-NOTES.md}` |
| Asset-Prep | `scripts/prepare_print_assets.py` → `build/print-assets/` |
| Template | `pdf/templates/guide.tex` — Branch `print-interior` (twoside, openany) |
| Tabellen-Filter | `pdf/filters/tables.lua` — `printmode` |
| QA | `scripts/check_pdf.py --mode print` (Soft-Seiten Print **100–320**) |
| Skill / Command | `.grok/skills/pdf-print/`, `.grok/commands/pdf-print.md` |
| Doku | `AGENTS.md`, `CLAUDE.md`, `README.md`, `ADAPTIONEN.md` §3b, `.grok/rules/bau-pipeline.md`, `pdf-bauen` Skill |
**Anpassungen vs. eit (nicht 1:1):** Output-Slug Grok, Keywords Grok, Cover-Picker `cover-front-print.jpg` → `COVER-01B-full.jpg` → `cover-front.jpg`, Soft-Seiten höher, resource-path `beispiele:vorlagen`.
### 2. Unteren Rand / Seitenzahl KDP-sicher
Messung **vor** Fix: unterster Tintenabstand **27 pt ≈ 0,375″** (zu knapp).
| Parameter | Alt | Neu (wie `ki-eit`) |
|-----------|-----|---------------------|
| `bottom` | 0,65″ | **1,00″** |
| `footskip` | 0,28″ | **0,30″** |
| Footer-Ink (gemessen) | ~27 pt | **~51 pt ≈ 0,708″** |
Dateien: `pdf/print/metadata.yaml`, Kommentare in `print-preamble.tex`, `QA-NOTES.md`, Skill, `check_pdf.py`-Hinweis.
**Verify (2026-08-08):**
```text
just pdf-print && just qa-print # exit 0
Seiten: 194 · 432×648 pt · ~3.0 MB
Stichproben S. 11/51/101/150/180/190: bottom_ink = 51 pt (PASS ≥ 43)
```
Unverändert gelassen: Trim 6×9, Body 10 pt / leading, inner/outer/top, Boxen/Gin, Manuskript, eBook.
---
## Aktueller Build-Stand
| Output | Status |
|--------|--------|
| eBook A4 | vorhanden (~143 S., Build ~16:55) — Smoke nach Port OK |
| Print 6×9 | **194 S.**, Grayscale stage OK, QA exit 0 |
| Soft-WARN Print | 13/24 Figuren **< 1280 px** (DPI Soft, kein Blocker) |
| Cover stage | `COVER-01B-full` / staged 1680×2520 (QA Cover DPI OK; natives `cover-front-print.jpg` optional) |
### Wichtige geometry (Print) — nicht zurückdrehen
```yaml
# pdf/print/metadata.yaml
inner=0.65in · outer=0.60in · top=0.70in
bottom=1.00in · footskip=0.30in · headsep=0.28in
fontsize=10pt · linestretch 1.22 · toc-depth 1
```
`geometry`-Semantik: Footer-Baseline ≈ `bottom − footskip` → Ink ≈ 0,65–0,71″.
**Nicht** nur `bottom=0.65in` setzen — das drückt die Seitenzahl wieder unter 0,4″.
---
## Working Tree (uncommitted)
**Modified (u. a.):** `justfile`, `guide.tex`, `tables.lua`, `check_pdf.py`, `AGENTS.md`, `CLAUDE.md`, `README.md`, `ADAPTIONEN.md`, `.grok/rules/bau-pipeline.md`, `.grok/skills/pdf-bauen/SKILL.md`, eBook-`dist/…pdf`, evtl. `guide-preamble.tex`
**Untracked:** `pdf/print/`, `scripts/prepare_print_assets.py`, `.grok/skills/pdf-print/`, `.grok/commands/pdf-print.md`, `dist/grok-build-praxisleitfaden-print.pdf`, **diese** `HANDOFF.md`
Kein Auto-Commit vereinbart.
---
## Offene / optionale nächste Schritte
Priorisiert nach Nutzen:
1. **Commit** der Print-Pipeline + Margin-Fix (ein logischer Commit oder zwei: Pipeline / Margin)
2. **Natives Print-Cover** `assets/cover/cover-front-print.jpg` (1800×2700 gray) — vermeidet Upscale-Hinweis
3. Figuren **≥ 1280 px** Breite für 300-DPI-Ziel (Soft-WARN)
4. Optional `just pdf-print-optimize` (Ghostscript Gray/prepress)
5. Manuskript: breite Tabellen im 6×9-Spiegel (Soft; kein Umbau erzwungen)
6. eBook nur bei Inhaltsänderungen neu bauen; Print-Layout berührt eBook nicht
**Nicht anfassen ohne Auftrag:** Manuskript-Kapitel umstrukturieren, eBook-Layout, KDP-Außenumschlag (Bleed/Spine) — Interior ≠ Cover-PDF.
---
## Befehle (Cheat Sheet)
```bash
just assemble # Kapitel → build/book.md
just pdf # eBook A4
just pdf-print # Print 6×9 + prepare_print_assets
just qa / just qa-print
just all / just all-print
just pdf-print-optimize
```
Messung unterer Tintenabstand (Beispiel):
```bash
# pdftoppm -gray -r 72 -f N -l N dist/…-print.pdf /tmp/p
# unterste Zeile mit Pixel < 240 → margin_pt = 648 - y
```
---
## Einstiegs-Prompt (neue Session)
```text
Repo: /Volumes/SSD2TB/books/ki-grok, Branch main.
Lies HANDOFF.md. Print-Pipeline (ki-eit-Port) und KDP-Bottom-Fix
(bottom=1.00in, footskip=0.30in, Ink≈51pt) sind fertig, QA grün, uncommitted.
Nächste Aufgabe: [Commit | Cover print | Figuren DPI | …].
eBook-Pipeline und Manuskript nicht anfassen, außer die Aufgabe verlangt es.
```
---
## Do / Don’t (Session)
| Do | Don’t |
|----|--------|
| Print nur über `just pdf-print` | `build/` handpatchen |
| Bei Margin-Fragen geometry in `pdf/print/metadata.yaml` | Footer in scrlayer „hochschieben“ ohne bottom zu erhöhen |
| Soft-WARNs als optional tracken | Soft-DPI als Hard-Fail behandeln |
| Schwester `ki-eit` als Layout-Referenz | Produktlogik/Curriculum aus eit in Grok-Kapitel mischen |
+10 -7
View File
@@ -117,12 +117,15 @@ Installationswege, Config-Keys und Slash-Commands können sich mit Releases änd
Pipeline analog zu [`../ki-claude`](../ki-claude): `just` + `pandoc` + `lualatex` + KOMA-Template.
**Voraussetzungen:** `pandoc`, `just`, TeX Live (LuaLaTeX, KOMA-Script `scrbook`), Fonts Charter + DejaVu.
**Voraussetzungen:** `pandoc`, `just`, TeX Live (LuaLaTeX, KOMA-Script `scrbook`), Fonts Charter + DejaVu (eBook), TeX Gyre Heros (Print), optional Pillow.
```bash
just pdf # → dist/grok-build-praxisleitfaden.pdf
just qa # Seiten-/Font-/Keyword-Checks
just all # pdf + qa
just pdf # → dist/grok-build-praxisleitfaden.pdf (A4 eBook)
just pdf-print # → dist/grok-build-praxisleitfaden-print.pdf (KDP 6×9 Grayscale)
just qa # eBook-Checks
just qa-print # Print-Checks (6×9, Gray-Stage)
just all # pdf + qa
just all-print # pdf-print + qa-print
just clean
```
@@ -131,7 +134,7 @@ Weitere Targets: `just pdf-draft`, `just pdf-plain`, `just assemble`.
```text
00–18-*.md → scripts/assemble_book.py → build/book.md
↓ pandoc + template
dist/*.pdf
dist/*.pdf (eBook und/oder Print)
```
## Weiterarbeiten am Projekt (Autoren-Harness)
@@ -141,8 +144,8 @@ Weitere Targets: `just pdf-draft`, `just pdf-plain`, `just assemble`.
| [`AGENTS.md`](AGENTS.md) | Prinzipien, Dateirollen, Qualitätsregeln |
| [`CLAUDE.md`](CLAUDE.md) | Build/QA, Layout, Agent notes |
| [`ADAPTIONEN.md`](ADAPTIONEN.md) | Schwesterprojekt-Mapping |
| [`.grok/`](.grok/) | rules, skills (`pdf-bauen`, `lektorat`, `kapitel-review`), commands |
| [`.grok/`](.grok/) | rules, skills (`pdf-bauen`, `pdf-print`, `lektorat`, …), commands |
**Autoren-Harness** (Root) steuert Agents, die am Buch arbeiten. **`vorlagen/`** und **`beispiele/`** sind Content für Leser des Buchs.
Typischer Mitautor-Workflow: Kapitel editieren → bei Bedarf `just pdf && just qa` → Lektorat/Review über Skills bzw. Grok-Commands (`/pdf`, `/lektorat`, `/review`, `/status`).
Typischer Mitautor-Workflow: Kapitel editieren → bei Bedarf `just pdf && just qa` (bzw. Print) → Lektorat/Review über Skills bzw. Grok-Commands (`/pdf`, `/pdf-print`, `/lektorat`, `/review`, `/status`).
Binary file not shown.
Binary file not shown.
+50 -1
View File
@@ -1,13 +1,21 @@
# Justfile — PDF-Pipeline für „Grok Build – Der umfassende Praxisleitfaden“
#
# Analog zu ../ki-claude (pandoc + lualatex + KOMA-Template + Farbpalette).
# pandoc + lualatex + KOMA-Template.
# TeX Live (KOMA-Script / scrbook) kommt vom Host.
#
# Outputs:
# just pdf → dist/grok-build-praxisleitfaden.pdf (A4 eBook)
# just pdf-print → dist/grok-build-praxisleitfaden-print.pdf (KDP 6×9 B/W)
default: pdf
# resource-path: illustrations + cover (+ content roots)
_resource_path := ".:beispiele:vorlagen:assets:assets/illustrations:assets/cover"
# Print: grayscale stage mirrors MD paths (assets/illustrations/… under
# build/print-assets/). First path wins → gray before RGB originals.
_print_resource_path := "build/print-assets:.:beispiele:vorlagen:assets:assets/illustrations:assets/cover"
assemble:
@python3 scripts/assemble_book.py
@@ -57,11 +65,52 @@ pdf-plain: assemble
--toc \
-o dist/grok-build-praxisleitfaden-plain.pdf
# --- KDP Paperback Interior (6×9, grayscale) --------------------------------
print-assets:
@python3 scripts/prepare_print_assets.py
pdf-print: assemble print-assets
@mkdir -p dist
pandoc build/book.md \
--pdf-engine=lualatex \
--template=pdf/templates/guide.tex \
-H pdf/print/print-colors.tex \
--metadata-file=pdf/print/metadata.yaml \
--lua-filter=pdf/filters/tables.lua \
--resource-path={{_print_resource_path}} \
--syntax-highlighting=monochrome \
-V lang=de-DE \
-V printmode=true \
--toc \
-o dist/grok-build-praxisleitfaden-print.pdf
# Optional: Ghostscript grayscale/prepress pass (KDP-freundlicher, best-effort)
pdf-print-optimize:
@test -f dist/grok-build-praxisleitfaden-print.pdf || (echo "Run just pdf-print first" && exit 1)
@command -v gs >/dev/null || (echo "Ghostscript (gs) not found" && exit 1)
gs -dBATCH -dNOPAUSE -dSAFER -sDEVICE=pdfwrite \
-dCompatibilityLevel=1.5 \
-dPDFSETTINGS=/prepress \
-dColorConversionStrategy=/Gray \
-dProcessColorModel=/DeviceGray \
-dEmbedAllFonts=true \
-dSubsetFonts=true \
-sOutputFile=dist/grok-build-praxisleitfaden-print-opt.pdf \
dist/grok-build-praxisleitfaden-print.pdf
@mv dist/grok-build-praxisleitfaden-print-opt.pdf dist/grok-build-praxisleitfaden-print.pdf
@echo "optimized → dist/grok-build-praxisleitfaden-print.pdf"
qa:
@python3 scripts/check_pdf.py
qa-print:
@python3 scripts/check_pdf.py --pdf dist/grok-build-praxisleitfaden-print.pdf --mode print
all: pdf qa
all-print: pdf-print qa-print
clean:
@rm -rf build
@rm -f dist/*.pdf dist/*.aux dist/*.log dist/*.out dist/*.toc
+49 -4
View File
@@ -246,8 +246,43 @@ local function max_plain_len(plain)
return m
end
local function wrap_style(ncols, max_cell)
local function is_print_mode()
-- Set via pandoc -V printmode=true (just pdf-print)
if PANDOC_WRITER_OPTIONS and PANDOC_WRITER_OPTIONS.variables then
local v = PANDOC_WRITER_OPTIONS.variables["printmode"]
if v == true or v == "true" or v == "yes" or v == "1" then
return true
end
end
local env = os.getenv("PRINT_MODE")
return env == "1" or env == "true"
end
local function wrap_style(ncols, max_cell, print_mode)
-- Returns (font_cmd or nil, tabcolsep or nil)
-- Print (6×9): narrower text block → slightly more aggressive scaling
if print_mode then
if ncols >= 6 then
return "\\scriptsize", "1.6pt"
end
if ncols >= 5 then
return "\\scriptsize", "1.8pt"
end
if ncols >= 4 then
return "\\footnotesize", "2.2pt"
end
if ncols >= 3 then
if max_cell >= 70 then
return "\\footnotesize", "2.6pt"
end
return "\\small", "3.0pt"
end
if ncols == 2 and max_cell >= 100 then
return "\\small", "3.2pt"
end
return nil, nil
end
if ncols >= 6 then
return "\\footnotesize", "2.5pt"
end
@@ -273,6 +308,7 @@ function Table(tbl)
return nil
end
local print_mode = is_print_mode()
local plain, code = collect_column_stats(tbl, ncols)
local widths = rebalance_widths(ncols, plain, code)
apply_widths(tbl, widths)
@@ -282,7 +318,7 @@ function Table(tbl)
walk_cell_contents(cell, make_code_breakable)
end)
local font_cmd, sep = wrap_style(ncols, max_plain_len(plain))
local font_cmd, sep = wrap_style(ncols, max_plain_len(plain), print_mode)
-- Always wrap multi-column tables: emergencystretch reduces overfull
-- hboxes in narrow p-columns (German compounds, long headers).
if ncols < 2 and not font_cmd and not sep then
@@ -296,8 +332,17 @@ function Table(tbl)
if sep then
open = open .. "\\setlength{\\tabcolsep}{" .. sep .. "}"
end
if ncols >= 3 then
open = open .. "\\sloppy\\setlength{\\emergencystretch}{2.5em}"
local stretch = print_mode and "3.2em" or "2.5em"
if ncols >= 2 then
open = open .. "\\sloppy\\setlength{\\emergencystretch}{" .. stretch .. "}"
end
-- Print: ragged-right p-columns avoid word-spacing collapse that looks like
-- overlapping headers when long German compounds / paths refuse to break.
if print_mode and ncols >= 3 then
open = open
.. "\\raggedright"
.. "\\hyphenpenalty=50\\exhyphenpenalty=50"
.. "\\setlength{\\LTleft}{0pt}\\setlength{\\LTright}{0pt}"
end
return {
+81
View File
@@ -0,0 +1,81 @@
# Print-Interior QA-Notes (KDP 6×9)
Grok Build – Der umfassende Praxisleitfaden. Stand: Pipeline-Übernahme aus `ki-eit`.
## Build
| Item | Wert |
|------|------|
| Output | `dist/grok-build-praxisleitfaden-print.pdf` |
| Trim | 6 × 9 in (432 × 648 pt) |
| Soft-Seiten | 100–320 (aktuell ~194; eBook A4 separat ~143) |
| Farbe | Grayscale (Palette + Assets); Code `--syntax-highlighting=monochrome` |
| Schriften | TeX Gyre Heros **Scale=1.0** + DejaVu Sans Mono, embedded |
| PDF | LuaTeX; optional `just pdf-print-optimize` (GS Gray/prepress) |
| Ränder | inner **0,65"** · outer **0,60"** · top **0,70"** · bottom **1,00"** (Textkörper) |
| Kopf/Fuß | headsep 0,28" · footskip 0,30" (Seitenzahl-Ink ≈ **0,65"** vom Trim) |
| Titelseite | Cover im **0,30"**-Safe-Frame (KDP-Außen ≥ 0,25") |
| Figuren | Gin `width=0,87\linewidth` → 1280 px ≈ **300 DPI** (Textbreite 4,75") |
| Typo | Body **10 pt** · linestretch **1,22** (~14,6 pt) · Box/Code/Tabelle **9 pt** |
## KDP-Scorecard (Interior)
| Kriterium | KDP | Status |
|-----------|-----|--------|
| Trim 6×9, kein Bleed | ja | ✅ Pipeline |
| Gutter ≥ 0,375" (24–150 S.) | 0,65" | ✅ |
| Außen/oben/unten ≥ 0,25" | 0,60 / 0,70 / Text 1,00 · Footer-Ink ≈ 0,65 | ✅ |
| Titelseite ≥ 0,25" | 0,30" | ✅ |
| Fonts embedded | ja | QA |
| Min. Schrift ≥ 7 pt | Body 10 / Box·Code 9 | ✅ |
| Bilder ≥ 300 DPI | Gin 0,87 + Cover ≥ 1620 px | Ziel; Soft-WARN |
| Cover-PDF (Spine/Bleed) | separat | ❌ out of scope |
## Änderungs-Checkliste (Pipeline)
- [x] Parallele Print-Pipeline (`just pdf-print` / `qa-print`)
- [x] Skill `pdf-print` + Command `/pdf-print`
- [x] eBook unverändert (A4, Farbe)
- [x] twoside, openany, Seitenzahlen außen unten
- [x] Cover-Titelseite mit KDP-Safe-Frame (0,30")
- [x] Grayscale-Assets via `prepare_print_assets.py`
- [x] Code monochrome (`--syntax-highlighting=monochrome`)
- [x] Tabellen-Filter Print-Modus (`-V printmode=true`)
- [x] Cover-Picker: `cover-front-print.jpg` → `COVER-01B-full.jpg` → `cover-front.jpg`
- [x] Optional GS-Optimize (`just pdf-print-optimize`)
## Manuelle Nacharbeit empfohlen
### Abbildungen (B/W-Lesbarkeit)
Display-DPI ist per Gin abgesichert. Bei scharfer Print-Kritik:
| Priorität | IDs | Warum |
|-----------|-----|--------|
| Hoch | K-01, K-05, M-01–M-08 | Farbe oft Unterscheidungskanal |
| Mittel | S-01–S-07, N-01–N-03 | dichte Labels / Cyan-Akzente |
| OK | Abbildungen ≥ 1280 px Breite | Gin 0,87 ≈ 300 DPI |
Maßnahmen: neu rendern (Schraffur statt reine Farbe) oder Imagine ≥ 1800 px Breite, dann `just print-assets` + `just pdf-print`.
### Cover-Quelle
- Aktuell oft nur `COVER-01B-full.jpg` (kann unter 1620×2520 liegen → LANCZOS-Upscale + WARN).
- Besser: natives `assets/cover/cover-front-print.jpg` ≥ 1800×2700 ohne Upscale.
### Sonstiges
- **Seite 2** nach Titelseite: leeres Verso — üblich im Paperback.
- **KDP-Außenumschlag** (Bleed + Spine) ist **nicht** dieses Interior.
- PDF/X-1a: nicht erzwungen; `pdf-print-optimize` ist best-effort Grayscale.
- Breite Tabellen im 6×9-Spiegel: `tables.lua` skaliert Print ab 3–4 Spalten aggressiver.
## Kommandos
```bash
just pdf-print # Interior bauen
just pdf-print-optimize # optional GS Gray/prepress
just qa-print # Checks
just all-print # pdf-print + qa-print
just pdf # eBook (unabhängig)
```
+31
View File
@@ -0,0 +1,31 @@
title: "Grok Build – Der umfassende Praxisleitfaden"
subtitle: "Der Coding-Agent von xAI richtig einsetzen"
author: "Martin Richardt"
date: "Juli 2026"
lang: de-DE
documentclass: scrbook
# KDP Paperback Interior: 6×9 in, twoside, openany (keine erzwungenen Leerseiten)
print-interior: true
# KOMA key-value (bare "10.5pt" is ignored by scrbook)
classoption:
- fontsize=10pt
# 10 pt body · nominal baselineskip ~12 pt × 1.22 ≈ 14.6 pt leading
linestretch: "1.22"
geometry:
- paperwidth=6in
- paperheight=9in
# Professionelle KDP-Ränder (Typeblock ≥0.65″ vom Trim; Gutter > Außen)
- inner=0.65in
- outer=0.60in
- top=0.70in
# bottom trägt Textkörper + Footer: Footer-Baseline ≈ bottom − footskip
# Glyph-Unterkante Seitenzahl ≥ 0,65″ vom Trim → bottom 1.00, footskip 0.30
# (Baseline ≈ 0,70″; 9-pt-Ziffern ~3 pt unter Baseline → ≈ 0,65″ Ink)
- bottom=1.00in
- headheight=14pt
- headsep=0.28in
- footskip=0.30in
has-frontmatter: true
toc-title: "Inhaltsverzeichnis"
# LaTeX tocdepth: 0=nur Kapitel, 1=Kapitel+## (schmaler Spiegel)
toc-depth: 1
+18
View File
@@ -0,0 +1,18 @@
% print-colors.tex — Grayscale-Palette für KDP-Interior (White Paper)
% Gleiche Farbnamen wie guide-colors.tex → Callouts/Templates bleiben kompatibel.
% Geladen via Pandoc `-H`. Vorausgesetzt: xcolor (Template lädt es).
\IfFileExists{xcolor.sty}{\usepackage{xcolor}}{}
% --- Grayscale-Palette (druckbar, hoher Kontrast) ---------------------------
\definecolor{nexttokenblue}{gray}{0.12} % fast schwarz (H1, Kopf)
\definecolor{nexttokengold}{gray}{0.38} % mittleres Grau (Linien, Akzent)
\definecolor{nexttokenburgundy}{gray}{0.22} % dunkles Grau (H2)
\definecolor{nexttokentext}{gray}{0.08} % Fließtext
\definecolor{nexttokenslate}{gray}{0.32} % Zitate, Hinweise
\definecolor{nexttokenparchment}{gray}{0.96} % Box-Fond
\definecolor{nexttokenlink}{gray}{0.12}
\colorlet{nexttokenblue!soft}{nexttokenblue!18!white}
\colorlet{nexttokengold!soft}{nexttokengold!25!white}
\colorlet{nexttokenburgundy!soft}{nexttokenburgundy!12!white}
+317
View File
@@ -0,0 +1,317 @@
% print-preamble.tex — KDP Paperback Interior (6×9, grayscale)
% Ersetzt guide-preamble.tex im Print-Build (Template: print-interior: true).
%
% Layout-Ziel (Professionell / KDP-tauglich):
% Body TeX Gyre Heros 10 pt, Leading ~14.5–15 pt
% Ränder: inner 0.65" · outer 0.60" · top 0.70" · bottom 1.00"
% footskip 0.30" → Seitenzahl-Ink ≈ 0.65" vom Trim (KDP-sicher)
% Boxen/Tabellen/Code 9 pt; etwas mehr Weißraum um Floats & Callouts
% ===========================================================================
% 0) SPRACHE / SILBENTRENNUNG
% ===========================================================================
\usepackage[shorthands=off,main=ngerman]{babel}
% ===========================================================================
% 1) FONTS — Sans-Body (echte 10 pt), Mono für Code/Pfade
% Scale=1.0: MatchLowercase skaliert Heros auf LM-x-Höhe (~8 pt optisch) — vermeiden
% ===========================================================================
\directlua{
luaotfload.add_fallback("printfb", {"DejaVu Sans:mode=node;"})
}
\setmainfont{TeX Gyre Heros}[
Ligatures=TeX,
Scale=1.0,
RawFeature={fallback=printfb}
]
\setsansfont{TeX Gyre Heros}[
Ligatures=TeX,
Scale=1.0,
RawFeature={fallback=printfb}
]
% DejaVu Sans Mono: gute Unicode-Abdeckung für Dateipfade / CLI
\setmonofont{DejaVu Sans Mono}[
Scale=0.95,
RawFeature={fallback=printfb}
]
% Body 10 pt + linestretch 1.22 (metadata) → Leading ≈ 14.6 pt
% (kein zusätzliches \fontsize{10}{14.5}: würde mit setstretch multiplizieren)
% Lange URLs umbrechen (Impressum / Links) — vor hyperref im Template geladen
\usepackage{xurl}
% Schmaler 6×9-Spiegel: weniger Overfull-\hbox (lange Pfade/Komposita)
\sloppy
\setlength{\emergencystretch}{3em}
\tolerance=2000
% ===========================================================================
% 2) KOMA-Script Überschriften (Grayscale-Farben aus print-colors.tex)
% Chapter ~16.5 pt · Section ~12.5 pt · Subsection ~11 pt
% ===========================================================================
\addtokomafont{chapter}{\color{nexttokenblue}\sffamily\bfseries\fontsize{16.5}{20}\selectfont}
\addtokomafont{section}{\color{nexttokenblue}\sffamily\bfseries\fontsize{12.5}{15}\selectfont}
\addtokomafont{subsection}{\color{nexttokenburgundy}\sffamily\bfseries\fontsize{11}{13.5}\selectfont}
\addtokomafont{disposition}{\normalcolor}
\RedeclareSectionCommand[beforeskip=1.4\baselineskip,afterskip=.6\baselineskip]{chapter}
\RedeclareSectionCommand[beforeskip=1.1\baselineskip,afterskip=.4\baselineskip]{section}
\RedeclareSectionCommand[beforeskip=0.85\baselineskip,afterskip=.28\baselineskip]{subsection}
% ===========================================================================
% 3) Kopf-/Fußzeile — twoside: außen; Seitenzahl nicht im Gutter
% ===========================================================================
\usepackage{scrlayer-scrpage}
\clearpairofpagestyles
\ohead{\headmark}
\ihead{}
\chead{}
\cfoot{}
\ofoot*{\pagemark}
\ifoot{}
\setkomafont{pagehead}{\color{nexttokenslate}\sffamily\fontsize{8.5}{10}\selectfont}
\setkomafont{pagenumber}{\color{nexttokenblue}\sffamily\bfseries\fontsize{9}{11}\selectfont}
\KOMAoptions{headsepline=.4pt}
\addtokomafont{headsepline}{\color{nexttokengold}}
% Leerseiten (Verso nach Titel o.ä.) ohne Kolumnentitel/Seitenzahl
\KOMAoptions{cleardoublepage=empty}
% ===========================================================================
% 4) TOC
% ===========================================================================
\setcounter{tocdepth}{1}
\AtBeginDocument{\hypersetup{bookmarksdepth=2}}
\setcounter{secnumdepth}{-\maxdimen}
\setkomafont{chapterentry}{\color{nexttokenblue}\sffamily}
\KOMAoptions{toc=chapterentrywithdots}
\DeclareTOCStyleEntry[
beforeskip=.08em plus .1pt,
entryformat=\small,
pagenumberformat=\small
]{tocline}{section}
\DeclareTOCStyleEntry[beforeskip=.4em plus .1pt]{tocline}{chapter}
% ===========================================================================
% 5) Code / Shaded — 9 pt, Zeilenumbruch im Satzspiegel
% ===========================================================================
\usepackage{framed}
\usepackage{color}
\definecolor{shadecolor}{gray}{0.96}
\usepackage{fvextra}
\DefineVerbatimEnvironment{Highlighting}{Verbatim}{
breaklines=true,
breakanywhere=true,
commandchars=\\\{\},
fontsize=\fontsize{9}{11}\selectfont
}
% Inline-Code ~9 pt (Breaks in Tabellen: tables.lua; Body: emergencystretch)
\let\oldtexttt\texttt
\renewcommand{\texttt}[1]{{\fontsize{9}{11}\selectfont\oldtexttt{#1}}}
% ===========================================================================
% 6) Zitat- / Hinweis-Boxen (Callouts: Kern / Übung / Achtung) — B/W
% ===========================================================================
\usepackage{mdframed}
\usepackage{tikz}
\usepackage{tcolorbox}
\tcbuselibrary{breakable,skins}
\newmdenv[
leftline=true,
rightline=false,
topline=false,
bottomline=false,
linewidth=2.5pt,
linecolor=nexttokengold,
backgroundcolor=nexttokenparchment,
leftmargin=0pt,
rightmargin=0pt,
innerleftmargin=10pt,
innerrightmargin=6pt,
innertopmargin=5pt,
innerbottommargin=5pt,
skipabove=10pt,
skipbelow=10pt
]{guidequotebox}
\newenvironment{guidequote}
{\begin{guidequotebox}\itshape\color{nexttokenslate}}
{\end{guidequotebox}}
\renewenvironment{quote}{%
\begin{guidequotebox}\itshape\color{nexttokenslate}%
}{%
\end{guidequotebox}%
}
\newcommand{\guideIconKern}{%
\tikz[baseline=-0.28ex]{%
\draw[line width=0.65pt, color=nexttokenblue]
(0,0) circle (0.30em);%
\fill[nexttokenblue] (0,0) circle (0.07em);%
\draw[line width=0.65pt, color=nexttokenblue]
(0.10em,0.10em) -- (0.22em,0.22em);%
}%
}
\newcommand{\guideIconUebung}{%
\tikz[baseline=-0.28ex]{%
\draw[line width=0.65pt, color=nexttokengold, rounded corners=0.4pt]
(-0.02em,0) rectangle (0.50em,0.50em);%
\draw[line width=0.75pt, color=nexttokengold, line cap=round, line join=round]
(0.08em,0.24em) -- (0.20em,0.10em) -- (0.42em,0.40em);%
}%
}
\newcommand{\guideIconAchtung}{%
\tikz[baseline=-0.32ex]{%
\draw[line width=0.65pt, color=nexttokenburgundy, line join=round]
(0.28em,0.56em) -- (0.54em,0.06em) -- (0.02em,0.06em) -- cycle;%
\draw[line width=0.7pt, color=nexttokenburgundy, line cap=round]
(0.28em,0.40em) -- (0.28em,0.20em);%
\fill[nexttokenburgundy] (0.28em,0.12em) circle (0.035em);%
}%
}
\newcommand{\guideCalloutLabel}[3]{%
\noindent{\sffamily\bfseries\fontsize{9}{11}\selectfont\color{#1}%
#3\enspace\MakeUppercase{#2}}\par\vspace{0.35em}%
}
\tcbset{
guidecallout/.style={
enhanced,
frame hidden,
boxrule=0pt,
leftrule=0pt,
rightrule=0pt,
toprule=0pt,
bottomrule=0pt,
left=9pt,
right=7pt,
top=6pt,
bottom=6pt,
arc=0pt,
outer arc=0pt,
boxsep=1pt,
before skip=10pt,
after skip=10pt,
breakable,
fontupper=\fontsize{9}{12}\selectfont\color{nexttokentext}
}
}
\newtcolorbox{guidekern}{
guidecallout,
colback=nexttokenparchment,
borderline west={2.5pt}{0pt}{nexttokenblue}
}
\newtcolorbox{guideuebung}{
guidecallout,
colback=nexttokenparchment,
borderline west={2.5pt}{0pt}{nexttokengold}
}
\newtcolorbox{guideachtung}{
guidecallout,
colback=gray!8!white,
borderline west={2.5pt}{0pt}{nexttokenburgundy}
}
% ===========================================================================
% 7) Tabellen — 9 pt, etwas luftiger
% ===========================================================================
\usepackage{array}
\setlength{\tabcolsep}{4.2pt}
\renewcommand{\arraystretch}{1.18}
\setlength{\LTleft}{0pt}
\setlength{\LTright}{0pt}
% ===========================================================================
% 7b) Figures / Floats — mehr Weißraum
% ===========================================================================
\usepackage{float}
\usepackage{placeins}
\usepackage{etoolbox}
\floatplacement{figure}{H}
\BeforeBeginEnvironment{Shaded}{\FloatBarrier}
\BeforeBeginEnvironment{verbatim}{\FloatBarrier}
\BeforeBeginEnvironment{longtable}{\fontsize{9}{11}\selectfont}
\BeforeBeginEnvironment{tabular}{\fontsize{9}{11}\selectfont}
\setlength{\textfloatsep}{12pt plus 3pt minus 2pt}
\setlength{\floatsep}{10pt plus 2pt minus 2pt}
\setlength{\intextsep}{10pt plus 2pt minus 2pt}
% Abbildungen: Textbreite ≈ 4,75 in · 0,87 · 300 ≈ 1239 px → 1280-Quellen ok
\setkeys{Gin}{width=0.87\linewidth,keepaspectratio}
% ===========================================================================
% 8) Titelseite — Cover-Abbildung (Grayscale), Fallback typografisch
% KDP-Außenumschlag bleibt separat; Interior-Titelseite = Seite 1 mit Cover.
% Ohne Bleed: Cover im Safe-Frame; KDP-Außenrand ≥ 0,25" → hier 0,30".
% ===========================================================================
\newcommand{\GuideTitlePageTypography}{%
\centering
\vspace*{2.2cm}%
{\sffamily\color{nexttokengold}\large\bfseries\MakeUppercase{Praxisbuch}\par}%
\vspace{0.9cm}%
\rule{0.55\textwidth}{1.0pt}\par
\vspace{1.0cm}%
{\sffamily\color{nexttokenblue}\Huge\bfseries Grok Build\par}%
\vspace{0.35cm}%
{\sffamily\color{nexttokenblue}\LARGE\bfseries Der umfassende\par
\vspace{0.15cm} Praxisleitfaden\par}%
\vspace{0.8cm}%
\rule{0.55\textwidth}{1.0pt}\par
\vspace{1.1cm}%
{\sffamily\color{nexttokenslate}\large
Der Coding-Agent von xAI\par
\vspace{0.3cm}
richtig einsetzen\par}%
\vspace{1.8cm}%
{\sffamily\color{nexttokentext}\large Martin Richardt\par}%
\vspace{0.45cm}%
{\sffamily\color{nexttokenslate}Juli 2026 · Stand Grok Build\par}%
\vfill
{\sffamily\color{nexttokenslate}\footnotesize
Paperback-Interior · 6$\times$9 in · Grayscale\par}%
}
\newcommand{\GuideTitlePageCover}[1]{%
\null\vfill
\centering
\includegraphics[width=\textwidth,height=\textheight,keepaspectratio]{#1}\par
\vfill\null
}
\newcommand{\GuideTitlePage}{%
% Ohne titlepage-Env (twoside erzeugt sonst Leer-Verso mit Header-Artefakten).
% Safe-Frame 0,30" via newgeometry; danach restore + normaler Satzspiegel.
\clearpage
\thispagestyle{empty}%
\pagestyle{empty}%
\newgeometry{margin=0.30in}%
\thispagestyle{empty}%
% 1) Print-Assets (Grayscale) 2) eBook-Cover-Namen 3) Typografie-Fallback
\IfFileExists{build/print-assets/assets/cover/cover-front.jpg}{%
\GuideTitlePageCover{build/print-assets/assets/cover/cover-front.jpg}%
}{%
\IfFileExists{assets/cover/COVER-01B-full.jpg}{%
\GuideTitlePageCover{assets/cover/COVER-01B-full.jpg}%
}{%
\IfFileExists{assets/cover/cover-front.jpg}{%
\GuideTitlePageCover{assets/cover/cover-front.jpg}%
}{%
\GuideTitlePageTypography
}%
}%
}%
\thispagestyle{empty}%
\clearpage
\restoregeometry
\thispagestyle{empty}%
\pagestyle{scrheadings}%
}
% Fließtext-Farbe
\AtBeginDocument{\color{nexttokentext}}
+9
View File
@@ -1,6 +1,15 @@
% guide-preamble.tex — Präambel für Grok-Build-Buch
% Visuell kompatibel zu ki-hinterm-vorhang / ki-copilot (nexttoken-*).
% ===========================================================================
% 0) SPRACHE / SILBENTRENNUNG
% ===========================================================================
% Custom Pandoc-Template lädt babel nicht automatisch (trotz lang=de-DE).
% Ohne ngerman greifen englische Patterns → Fehltrennungen wie
% „Planungss-chleifen“, „zwis-chen“.
% shorthands=off: kein Konflikt mit csquotes \MakeOuterQuote{"} im Template
\usepackage[shorthands=off,main=ngerman]{babel}
% ===========================================================================
% 1) FONTS
% ===========================================================================
+17 -2
View File
@@ -4,15 +4,22 @@ $if(fontsize)$
$else$
11pt,
$endif$
$if(papersize)$
$if(geometry)$
$elseif(papersize)$
$papersize$paper,
$else$
a4paper,
$endif$
$if(print-interior)$
twoside,
openany,
BCOR=0mm,
$else$
oneside,
openany,
DIV=13,
BCOR=0mm,
$endif$
$for(classoption)$
$classoption$$sep$,
$endfor$
@@ -66,11 +73,19 @@ $endfor$
$highlighting-macros$
$if(print-interior)$
\IfFileExists{pdf/print/print-preamble.tex}{%
\input{pdf/print/print-preamble.tex}%
}{%
\InputIfFileExists{print-preamble.tex}{}{}%
}
$else$
\IfFileExists{pdf/templates/guide-preamble.tex}{%
\input{pdf/templates/guide-preamble.tex}%
}{%
\InputIfFileExists{guide-preamble.tex}{}{}%
}
$endif$
\usepackage{hyperref}
\hypersetup{
@@ -131,7 +146,7 @@ $endif$
$if(colorlinks)$
\hypersetup{linkcolor=$if(toccolor)$$toccolor$$else$nexttokenlink$endif$}
$endif$
\setcounter{tocdepth}{$if(toc-depth)$$toc-depth$$else$2$endif$}
\setcounter{tocdepth}{$if(toc-depth)$$toc-depth$$else$1$endif$}
\tableofcontents
}
$endif$
+265 -10
View File
@@ -2,7 +2,7 @@
"""Post-build quality checks for the Grok Build book PDF.
CLI:
python3 scripts/check_pdf.py [--pdf PATH]
python3 scripts/check_pdf.py [--pdf PATH] [--mode ebook|print]
"""
from __future__ import annotations
@@ -16,10 +16,18 @@ import zlib
REPO_ROOT = pathlib.Path(__file__).resolve().parent.parent
DEFAULT_PDF = REPO_ROOT / "dist" / "grok-build-praxisleitfaden.pdf"
DEFAULT_PRINT_PDF = REPO_ROOT / "dist" / "grok-build-praxisleitfaden-print.pdf"
# Soft target for a multi-chapter practice book (19 chapters → larger span)
SOFT_MIN_PAGES = 40
SOFT_MAX_PAGES = 280
PRINT_SOFT_MIN_PAGES = 100
PRINT_SOFT_MAX_PAGES = 320
# 6×9 in in PDF points (1 in = 72 pt)
PRINT_W_PT = 6 * 72 # 432
PRINT_H_PT = 9 * 72 # 648
PRINT_SIZE_TOL_PT = 1.5
KEYWORDS = [
"Grok Build",
@@ -57,7 +65,7 @@ def check_exists(pdf: pathlib.Path) -> bool:
return True
def check_pages(data: bytes) -> int:
def check_pages(data: bytes, mode: str) -> int:
text = data.decode("latin-1", "ignore")
counts = [int(m) for m in re.findall(r"/Count\s+(\d+)", text)]
if counts:
@@ -67,25 +75,28 @@ def check_pages(data: bytes) -> int:
for chunk in _decompress_streams(data):
pages += len(re.findall(r"/Type\s*/Page[^s]", chunk))
soft_min = PRINT_SOFT_MIN_PAGES if mode == "print" else SOFT_MIN_PAGES
soft_max = PRINT_SOFT_MAX_PAGES if mode == "print" else SOFT_MAX_PAGES
if pages == 0:
_print("WARN", "Page count", "konnte nicht geschätzt werden")
elif pages < SOFT_MIN_PAGES:
elif pages < soft_min:
_print(
"WARN",
"Page count",
f"~{pages} Seiten (unter Ziel {SOFT_MIN_PAGES}–{SOFT_MAX_PAGES})",
f"~{pages} Seiten (unter Ziel {soft_min}–{soft_max})",
)
elif pages > SOFT_MAX_PAGES:
elif pages > soft_max:
_print(
"WARN",
"Page count",
f"~{pages} Seiten (über Ziel {SOFT_MIN_PAGES}–{SOFT_MAX_PAGES})",
f"~{pages} Seiten (über Ziel {soft_min}–{soft_max})",
)
else:
_print(
"OK",
"Page count",
f"~{pages} Seiten (Ziel {SOFT_MIN_PAGES}–{SOFT_MAX_PAGES})",
f"~{pages} Seiten (Ziel {soft_min}–{soft_max})",
)
return pages
@@ -148,21 +159,265 @@ def check_keywords(pdf: pathlib.Path) -> None:
_print("OK", "Keywords", f"{len(KEYWORDS)} Schlüsselbegriffe gefunden")
def _pdfinfo_fields(pdf: pathlib.Path) -> dict[str, str]:
pdfinfo = shutil.which("pdfinfo")
if not pdfinfo:
return {}
try:
proc = subprocess.run(
[pdfinfo, str(pdf)],
check=True,
capture_output=True,
text=True,
)
except subprocess.CalledProcessError:
return {}
fields: dict[str, str] = {}
for line in proc.stdout.splitlines():
if ":" not in line:
continue
key, val = line.split(":", 1)
fields[key.strip()] = val.strip()
return fields
def check_page_size_print(pdf: pathlib.Path) -> None:
"""Expect ~6×9 in (432×648 pt)."""
fields = _pdfinfo_fields(pdf)
if not fields:
_print("SKIP", "Page size (print)", "pdfinfo nicht verfügbar")
return
size = fields.get("Page size", "")
# e.g. "432 x 648 pts" or "432 x 648 pts (letter)"
m = re.search(r"([\d.]+)\s*x\s*([\d.]+)\s*pts", size, re.I)
if not m:
_print("WARN", "Page size (print)", f"unparseable: {size!r}")
return
w, h = float(m.group(1)), float(m.group(2))
ok_w = abs(w - PRINT_W_PT) <= PRINT_SIZE_TOL_PT
ok_h = abs(h - PRINT_H_PT) <= PRINT_SIZE_TOL_PT
if ok_w and ok_h:
_print("OK", "Page size (print)", f"{w:.1f} × {h:.1f} pts (6×9 in)")
else:
_print(
"FAIL",
"Page size (print)",
f"{w:.1f} × {h:.1f} pts — erwartet ~{PRINT_W_PT} × {PRINT_H_PT} (6×9 in)",
)
def check_pdf_version(pdf: pathlib.Path) -> None:
fields = _pdfinfo_fields(pdf)
if not fields:
return
ver = fields.get("PDF version", "?")
pages = fields.get("Pages", "?")
_print("OK", "PDF meta", f"version={ver}, pages={pages}")
def check_print_assets() -> None:
"""Soft checks on staged/source print assets (DPI readiness + gray stage)."""
try:
from PIL import Image
except ImportError:
_print("SKIP", "Print assets DPI", "Pillow nicht verfügbar")
return
# Cover: prefer print-specific source, then staged gray, else eBook names
cover_candidates = [
REPO_ROOT / "assets" / "cover" / "cover-front-print.jpg",
REPO_ROOT / "build" / "print-assets" / "assets" / "cover" / "cover-front.jpg",
REPO_ROOT / "assets" / "cover" / "COVER-01B-full.jpg",
REPO_ROOT / "assets" / "cover" / "cover-front.jpg",
]
cover = next((p for p in cover_candidates if p.exists()), None)
# Safe frame 5.4×8.4 in @ 300 DPI
min_cover_w, min_cover_h = 1620, 2520
if cover is None:
_print("WARN", "Cover asset", "kein Cover (cover-front-print / COVER-01B) gefunden")
else:
with Image.open(cover) as im:
w, h = im.size
if w >= min_cover_w and h >= min_cover_h:
_print(
"OK",
"Cover asset DPI",
f"{cover.relative_to(REPO_ROOT)} {w}×{h} (≥ {min_cover_w}×{min_cover_h})",
)
else:
_print(
"WARN",
"Cover asset DPI",
f"{cover.relative_to(REPO_ROOT)} {w}×{h} — Ziel ≥ {min_cover_w}×{min_cover_h} "
f"(300 DPI im 0,30\"-Safe-Frame)",
)
# Figures: 1280 px @ Gin 0.87 ≈ 300 DPI; warn if many below threshold
illus = REPO_ROOT / "assets" / "illustrations"
min_fig_w = 1280 # matches prepare_print_assets MIN at Gin 0.87
low: list[str] = []
total = 0
if illus.is_dir():
for p in sorted(illus.glob("*.jpg")) + sorted(illus.glob("*.png")):
if p.name.startswith("."):
continue
total += 1
with Image.open(p) as im:
w, _h = im.size
if w < min_fig_w:
low.append(f"{p.name} ({w}px)")
if total == 0:
_print("SKIP", "Figure assets", "keine Illustrations gefunden")
elif not low:
_print("OK", "Figure assets DPI", f"{total} Datei(en) ≥ {min_fig_w}px Breite")
else:
_print(
"WARN",
"Figure assets DPI",
f"{len(low)}/{total} unter {min_fig_w}px — bei Gin 0,87 < ~300 DPI: "
+ ", ".join(low[:6])
+ ("…" if len(low) > 6 else ""),
)
# Staged grayscale must mirror MD paths and be single-channel
staged = REPO_ROOT / "build" / "print-assets" / "assets" / "illustrations"
if not staged.is_dir():
_print(
"WARN",
"Print gray stage",
"build/print-assets/assets/illustrations fehlt — just print-assets?",
)
return
not_gray: list[str] = []
staged_n = 0
for p in sorted(staged.glob("*.jpg")):
staged_n += 1
with Image.open(p) as im:
if im.mode not in ("L", "1", "LA"):
not_gray.append(f"{p.name}({im.mode})")
if staged_n == 0:
_print("WARN", "Print gray stage", "keine staged JPEGs")
elif not_gray:
_print(
"FAIL",
"Print gray stage",
f"{len(not_gray)} nicht grayscale: " + ", ".join(not_gray[:8]),
)
else:
_print("OK", "Print gray stage", f"{staged_n} staged JPEG(s), mode L")
def check_print_pdf_images_gray(pdf: pathlib.Path) -> bool:
"""Fail if embedded images are DeviceRGB (figures must be gray in print PDF).
Returns True when check fails (for exit code).
"""
if not shutil.which("pdfimages"):
_print("SKIP", "PDF images gray", "pdfimages nicht verfügbar")
return False
try:
out = subprocess.check_output(
["pdfimages", "-list", str(pdf)],
text=True,
stderr=subprocess.DEVNULL,
)
except (subprocess.CalledProcessError, OSError) as e:
_print("SKIP", "PDF images gray", f"pdfimages fehlgeschlagen: {e}")
return False
rgb = 0
gray = 0
other = 0
for line in out.splitlines():
# skip header lines; data rows start with page number
parts = line.split()
if len(parts) < 6 or not parts[0].isdigit():
continue
# columns: page num type width height color comp bpc ...
color = parts[5].lower()
if color in ("gray", "grey", "image-mask", "stencil"):
gray += 1
elif color in ("rgb", "cmyk", "icc"):
rgb += 1
else:
other += 1
total = rgb + gray + other
if total == 0:
_print("WARN", "PDF images gray", "keine eingebetteten Bilder gefunden")
return False
if rgb > 0:
_print(
"FAIL",
"PDF images gray",
f"{rgb}/{total} Bild(er) farbig (rgb/cmyk) — resource-path muss "
f"build/print-assets (assets/…-Spiegel) vor den RGB-Originalen auflösen",
)
return True
_print("OK", "PDF images gray", f"{gray}/{total} gray, 0 rgb")
return False
def print_manual_notes() -> None:
print()
print("── Print QA notes (manuell prüfen) ──────────────────────────────")
print("• Titelseite: Cover im ≥0,30\"-Safe-Frame (KDP-Außen ≥0,25\").")
print("• Body-Ränder: inner 0,65\", outer 0,60\", top 0,70\", bottom 1,00\" (Footer-Ink ≈ 0,65\").")
print("• Body 10 pt / Leading ~14,6 pt; Boxen/Code/Tabellen 9 pt.")
print("• Figuren: Gin width=0,87\\linewidth → 1280px ≈ 300 DPI.")
print("• Diagramme mit ehemals farbigem Kanal: K-01, K-05, M-*, S-*")
print(" (Kontrast und Linienunterscheidung im Grayscale-PDF).")
print("• Breite Tabellen (Vergleichskapitel, Schnellreferenz): Satzspiegel.")
print("• Lange Codezeilen und Dateipfade: innerhalb der Ränder?")
print("• Seitenzahlen außen unten; Kolumnentitel nicht im Gutter.")
print("• KDP-Außenumschlag (Bleed/Spine) ist separat — dieses PDF = Interior.")
print("• Optional: just pdf-print-optimize (Ghostscript Gray/prepress).")
print("────────────────────────────────────────────────────────────────")
def main() -> int:
parser = argparse.ArgumentParser(description="QA checks for book PDF")
parser.add_argument("--pdf", type=pathlib.Path, default=DEFAULT_PDF)
parser.add_argument("--pdf", type=pathlib.Path, default=None)
parser.add_argument(
"--mode",
choices=("ebook", "print"),
default="ebook",
help="ebook (default A4) or print (KDP 6×9)",
)
args = parser.parse_args()
mode = args.mode
pdf = args.pdf
if pdf is None:
pdf = DEFAULT_PRINT_PDF if mode == "print" else DEFAULT_PDF
ok = check_exists(pdf)
if not ok:
return 1
data = pdf.read_bytes()
check_pages(data)
check_pages(data, mode)
check_fonts(pdf)
check_keywords(pdf)
return 0
check_pdf_version(pdf)
failed = False
if mode == "print":
check_page_size_print(pdf)
check_print_assets()
if check_print_pdf_images_gray(pdf):
failed = True
# Re-run logic for exit code
fields = _pdfinfo_fields(pdf)
size = fields.get("Page size", "")
m = re.search(r"([\d.]+)\s*x\s*([\d.]+)\s*pts", size, re.I)
if m:
w, h = float(m.group(1)), float(m.group(2))
if abs(w - PRINT_W_PT) > PRINT_SIZE_TOL_PT or abs(h - PRINT_H_PT) > PRINT_SIZE_TOL_PT:
failed = True
print_manual_notes()
return 1 if failed else 0
if __name__ == "__main__":
+242
View File
@@ -0,0 +1,242 @@
#!/usr/bin/env python3
"""Prepare grayscale, high-contrast print assets for KDP interior PDF.
Writes gray JPEGs under build/print-assets/ mirroring the MD-relative paths
(assets/illustrations/…, assets/cover/…). Pandoc --resource-path puts
build/print-assets first, so ``assets/illustrations/foo.jpg`` resolves to the
gray stage instead of the RGB original under repo root.
CLI:
python3 scripts/prepare_print_assets.py [--force]
"""
from __future__ import annotations
import argparse
import pathlib
import shutil
import sys
from PIL import Image, ImageEnhance, ImageFilter, ImageOps
REPO_ROOT = pathlib.Path(__file__).resolve().parent.parent
SRC_ILLUSTRATIONS = REPO_ROOT / "assets" / "illustrations"
SRC_COVER = REPO_ROOT / "assets" / "cover"
OUT_ROOT = REPO_ROOT / "build" / "print-assets"
# Mirror MD paths: assets/illustrations/… and assets/cover/…
OUT_ILLUSTRATIONS = OUT_ROOT / "assets" / "illustrations"
OUT_COVER = OUT_ROOT / "assets" / "cover"
# Legacy layout (pre path-mirror) — removed so stale color lookups cannot win
_LEGACY_ILLUSTRATIONS = OUT_ROOT / "illustrations"
_LEGACY_COVER = OUT_ROOT / "cover"
# Geometry must stay in sync with pdf/print/metadata.yaml + print-preamble Gin
# text width = 6 − inner − outer = 6 − 0.65 − 0.60 = 4.75 in
TEXT_WIDTH_IN = 4.75
GIN_FRACTION = 0.87 # \setkeys{Gin}{width=0.87\linewidth}
FIG_DISPLAY_IN = TEXT_WIDTH_IN * GIN_FRACTION # ≈ 4.1325 in
# Cover interior: 6×9 with 0.30" safety → safe frame ≈ 5.4 × 8.4 in
COVER_SAFE_W_IN = 5.40
COVER_SAFE_H_IN = 8.40
COVER_MIN_W_PX = int(COVER_SAFE_W_IN * 300) # 1620
COVER_MIN_H_PX = int(COVER_SAFE_H_IN * 300) # 2520
TARGET_DPI = 300
MIN_WIDTH_300DPI = int(FIG_DISPLAY_IN * TARGET_DPI) # ≈ 1279
def _to_print_gray(im: Image.Image) -> Image.Image:
"""RGB/RGBA → print grayscale with contrast and mild sharpening."""
if im.mode in ("RGBA", "LA") or (im.mode == "P" and "transparency" in im.info):
rgba = im.convert("RGBA")
bg = Image.new("RGBA", rgba.size, (255, 255, 255, 255))
im = Image.alpha_composite(bg, rgba).convert("RGB")
else:
im = im.convert("RGB")
gray = ImageOps.grayscale(im)
gray = ImageOps.autocontrast(gray, cutoff=1)
gray = ImageEnhance.Contrast(gray).enhance(1.12)
gray = gray.filter(ImageFilter.UnsharpMask(radius=0.8, percent=80, threshold=2))
return gray
def _process_figure(src: pathlib.Path, dest: pathlib.Path) -> dict:
dest.parent.mkdir(parents=True, exist_ok=True)
with Image.open(src) as im:
im.load()
mode = im.mode
w, h = im.size
gray = _to_print_gray(im)
gray.save(
dest,
format="JPEG",
quality=92,
optimize=True,
dpi=(TARGET_DPI, TARGET_DPI),
)
dpi_at_display = w / FIG_DISPLAY_IN if w else 0
return {
"src": src.name,
"dest": str(dest.relative_to(REPO_ROOT)),
"size": f"{w}x{h}",
"mode_in": mode,
"kind": "figure",
"dpi_at_display": round(dpi_at_display),
"low_res": w < MIN_WIDTH_300DPI,
}
def _process_cover(src: pathlib.Path, dest: pathlib.Path) -> dict:
"""Grayscale cover; upscale to ≥300 DPI in interior safe-frame if needed."""
dest.parent.mkdir(parents=True, exist_ok=True)
with Image.open(src) as im:
im.load()
mode = im.mode
w0, h0 = im.size
gray = _to_print_gray(im)
w, h = gray.size
upscaled = False
# Ensure enough pixels for 300 DPI inside the 0.30" safety frame
need_w, need_h = COVER_MIN_W_PX, COVER_MIN_H_PX
scale = max(need_w / w, need_h / h, 1.0)
if scale > 1.0:
nw, nh = int(round(w * scale)), int(round(h * scale))
gray = gray.resize((nw, nh), Image.Resampling.LANCZOS)
gray = gray.filter(
ImageFilter.UnsharpMask(radius=1.0, percent=90, threshold=2)
)
w, h = gray.size
upscaled = True
gray.save(
dest,
format="JPEG",
quality=94,
optimize=True,
dpi=(TARGET_DPI, TARGET_DPI),
)
dpi_safe = min(w / COVER_SAFE_W_IN, h / COVER_SAFE_H_IN) if w and h else 0
return {
"src": src.name,
"dest": str(dest.relative_to(REPO_ROOT)),
"size": f"{w}x{h}",
"size_src": f"{w0}x{h0}",
"mode_in": mode,
"kind": "cover",
"dpi_at_safe_frame": round(dpi_safe),
"upscaled": upscaled,
"low_res": dpi_safe < TARGET_DPI - 5,
}
def _pick_cover_source() -> pathlib.Path | None:
"""Prefer dedicated print cover; fall back to eBook front covers."""
for name in (
"cover-front-print.jpg",
"COVER-01B-full.jpg",
"cover-front.jpg",
):
p = SRC_COVER / name
if p.exists():
return p
return None
def main() -> int:
parser = argparse.ArgumentParser(description="Prepare KDP print assets (grayscale)")
parser.add_argument(
"--force",
action="store_true",
help="Re-process even if output is newer than source",
)
args = parser.parse_args()
if not SRC_ILLUSTRATIONS.is_dir():
print(f"[FAIL] missing {SRC_ILLUSTRATIONS}", file=sys.stderr)
return 1
# Drop pre-mirror staging dirs so resource-path cannot hit stale trees.
for legacy in (_LEGACY_ILLUSTRATIONS, _LEGACY_COVER):
if legacy.is_dir():
shutil.rmtree(legacy)
OUT_ILLUSTRATIONS.mkdir(parents=True, exist_ok=True)
OUT_COVER.mkdir(parents=True, exist_ok=True)
reports: list[dict] = []
sources = sorted(SRC_ILLUSTRATIONS.glob("*.jpg")) + sorted(
SRC_ILLUSTRATIONS.glob("*.png")
)
for src in sources:
if src.name.startswith("."):
continue
# Keep extension when already JPEG; convert PNG → .jpg for print stage
dest = OUT_ILLUSTRATIONS / (src.stem + ".jpg")
if (
not args.force
and dest.exists()
and dest.stat().st_mtime >= src.stat().st_mtime
):
reports.append({"src": src.name, "skipped": True})
continue
reports.append(_process_figure(src, dest))
cover_src = _pick_cover_source()
if cover_src is not None:
# MD/eBook cover name; print source may be COVER-01B-full.jpg
cover_dest = OUT_COVER / "cover-front.jpg"
if (
args.force
or not cover_dest.exists()
or cover_dest.stat().st_mtime < cover_src.stat().st_mtime
):
reports.append(_process_cover(cover_src, cover_dest))
else:
reports.append({"src": cover_src.name, "skipped": True})
processed = [r for r in reports if not r.get("skipped")]
skipped = [r for r in reports if r.get("skipped")]
low_figs = [
r for r in processed if r.get("kind") == "figure" and r.get("low_res")
]
covers = [r for r in processed if r.get("kind") == "cover"]
print(f"[OK] print assets → {OUT_ROOT.relative_to(REPO_ROOT)}")
print(f" processed={len(processed)} skipped={len(skipped)}")
print(
f" figure display ≈ {FIG_DISPLAY_IN:.2f} in "
f"({GIN_FRACTION:.0%} · {TEXT_WIDTH_IN:.2f} in text); "
f"300 DPI needs ≥ {MIN_WIDTH_300DPI} px width"
)
if low_figs:
print(
f"[WARN] {len(low_figs)} figure(s) below ~300 DPI at print display width "
f"(not upscaled; reduce Gin or re-render):"
)
for r in low_figs:
print(
f" - {r['src']}: {r['size']} "
f"(~{r['dpi_at_display']} dpi @ {FIG_DISPLAY_IN:.2f} in)"
)
for r in covers:
flag = "upscaled" if r.get("upscaled") else "native"
print(
f"[OK] cover {r['src']} → {r['size']} "
f"(src {r.get('size_src', '?')}, {flag}, "
f"~{r.get('dpi_at_safe_frame')} dpi @ safe frame)"
)
if r.get("upscaled"):
print(
"[WARN] cover was LANCZOS-upscaled for 300 DPI interim — "
"prefer assets/cover/cover-front-print.jpg ≥ 1620×2520 native"
)
return 0
if __name__ == "__main__":
raise SystemExit(main())