From 8fa36ac88c2c7c2f3e1757a6583ca83b58fcf14ba9cb6fa3e7645be54187e689 Mon Sep 17 00:00:00 2001 From: tlg Date: Sun, 26 Apr 2026 16:40:20 +0200 Subject: [PATCH] S07: Iteration B3 und B3.5 fuer Teilgebiet 01 abgeschlossen. B3 in build/build-reference-docx.py ergaenzt: DocDefault widowControl plus keepNext und keepLines auf Heading 1/2/3 und FirstParagraph (Pandoc-Stil fuer ersten Absatz nach einem Heading, deckt die fett formatierten Kenntnisse-Subsection-Labels KI Software-Design Methodik IT etc ab). Erster Versuch Compact-Stil mit keepNext hat Listen komplett unteilbar gemacht (Job-Stationen begannen jedes Mal auf einer neuen Seite, ungenutzte Seitenenden) und wurde verworfen. Auf Wunsch von Thomas auf 3-3-Regel umgestellt: bei Listen mit mindestens 6 Bullets duerfen Trennungen passieren, aber mindestens 3 Bullets bleiben jeweils zusammen vor und nach dem Umbruch. Bei kuerzeren Listen alles zusammen. Da das stilbasiert nicht abbildbar ist (alle Bullets haben pStyle Compact), neues Post-Processing-Skript build/post-process-docx.py: scannt das fertige DOCX, findet Sequenzen aufeinanderfolgender Bullets mit numPr-Eigenschaft ausserhalb von Tabellen-Zellen, setzt keepNext auf den ersten 2 und den N-3 N-2 Bullets jeder Liste mit n groesser gleich 6 (bei n kleiner 6 alle keepNext). build.ps1 erweitert auf 3 Schritte und ruft das Post-Processing-Skript automatisch nach erfolgreichem DOCX-Build auf, mit Console-Output und Log-Statistiken (Anzahl Listen Bullets keepNext-Markierungen). Sandbox-Verifikation 26 Listen 184 Bullets 93 keepNext, Pattern fuer 11-Bullet-Liste KK......KK.. Auf Thomas System visuell bestaetigt: Listen werden an guten Stellen getrennt, keine ungenutzten Seitenenden, keine einzelnen Bullets allein am Seitenrand. teilgebiete/01-lebenslauf.md um B3- und B3.5-Bloecke ergaenzt sowie Naechste-Schritte-Liste auf B4 C D umstrukturiert. agent-prompt.md Aktueller-Stand-Abschnitt fortgeschrieben mit B3 und B3.5, Hinweis auf 3-stufige DOCX-Pipeline und Edit-Tool-Truncation an build.ps1 ergaenzt. Naechste Session startet mit B4 (Heading-Farben oder Trennlinien analog PDF). --- .checkpoint-pending.txt | 2 +- agent-prompt.md | 25 +-- .../build/build-reference-docx.py | 101 +++++----- artefakte/01-lebenslauf/build/build.ps1 | 38 +++- .../01-lebenslauf/build/post-process-docx.py | 185 ++++++++++++++++++ .../Lebenslauf_Dr-Ing_Thomas_Langer.docx | Bin 22550 -> 22674 bytes .../Lebenslauf_Dr-Ing_Thomas_Langer.pdf | 4 +- artefakte/01-lebenslauf/output/build.log | 11 +- .../01-lebenslauf/templates/reference.docx | Bin 12162 -> 12183 bytes changelog.md | 1 + teilgebiete/01-lebenslauf.md | 26 ++- 11 files changed, 319 insertions(+), 74 deletions(-) create mode 100644 artefakte/01-lebenslauf/build/post-process-docx.py diff --git a/.checkpoint-pending.txt b/.checkpoint-pending.txt index 4fbff93..306fc51 100644 --- a/.checkpoint-pending.txt +++ b/.checkpoint-pending.txt @@ -1,2 +1,2 @@ S07 -Teilgebiet 01 Iteration B (Iterationen B1, B1.5, B2) durchgezogen. Neue Datei build/build-reference-docx.py baut templates/reference.docx programmatisch aus Pandocs Default-Reference (Python-Stdlib only, kein pip; pandoc --print-default-data-file zur Laufzeit, ZIP entpacken, ElementTree-XML-Anpassungen, repacken). B1: Theme major+minor und alle direkten Schrift-Refs in styles.xml auf Calibri umgestellt (Code-Schriften wie Consolas bleiben), Tabellen-Default-Stil mit tblBorders=none auf allen Sides. B1.5: Body-DocDefault 11 pt, Heading 1/2/3 auf 15/13/12 pt analog PDF. B2: header1.xml (Default ab Seite 2 mit Name links und Lebenslauf rechts), header2.xml (leer fuer Seite 1 via titlePg), footer1.xml (rechts Seite n / m mit PAGE/NUMPAGES-Feldern, doppelt referenziert als default und first damit Seite 1 trotz titlePg den Footer hat). Page-Setup explizit in sectPr: A4 mit 2.2 cm oben/unten und 2.5 cm links/rechts analog PDF, Tab-Stop am rechten Textrand 9072 dxa. Beziehungen mit dynamisch naechster freier rId in document.xml.rels, Content-Types-Overrides in [Content_Types].xml, sectPr regex-ersetzt idempotent. Sandbox-End-to-End mit Pandoc 2.9 verifiziert (sectPr und Header/Footer im generierten DOCX vorhanden). Auf Thomas System: DOCX visuell bestaetigt. teilgebiete/01-lebenslauf.md um vollstaendigen Iteration-B-Block ergaenzt, Naechste-Schritte-Liste auf B3, B4, C, D umstrukturiert. agent-prompt.md Aktueller-Stand-Abschnitt fortgeschrieben mit Hinweisen zur reference-docx-Pipeline (manuell vor build.ps1 aufrufen, nicht von Hand in Word editieren) und zur Edit-Tool-Truncation auf dem NTFS-Mount. Build-UX-Fix in build.ps1 mit 3-Sekunden-Pause pro fehlgeschlagenem Schritt war ebenfalls Teil dieser Session. +Iteration B3 und B3.5 fuer Teilgebiet 01 abgeschlossen. B3 in build/build-reference-docx.py ergaenzt: DocDefault widowControl plus keepNext und keepLines auf Heading 1/2/3 und FirstParagraph (Pandoc-Stil fuer ersten Absatz nach einem Heading, deckt die fett formatierten Kenntnisse-Subsection-Labels KI Software-Design Methodik IT etc ab). Erster Versuch Compact-Stil mit keepNext hat Listen komplett unteilbar gemacht (Job-Stationen begannen jedes Mal auf einer neuen Seite, ungenutzte Seitenenden) und wurde verworfen. Auf Wunsch von Thomas auf 3-3-Regel umgestellt: bei Listen mit mindestens 6 Bullets duerfen Trennungen passieren, aber mindestens 3 Bullets bleiben jeweils zusammen vor und nach dem Umbruch. Bei kuerzeren Listen alles zusammen. Da das stilbasiert nicht abbildbar ist (alle Bullets haben pStyle Compact), neues Post-Processing-Skript build/post-process-docx.py: scannt das fertige DOCX, findet Sequenzen aufeinanderfolgender Bullets mit numPr-Eigenschaft ausserhalb von Tabellen-Zellen, setzt keepNext auf den ersten 2 und den N-3 N-2 Bullets jeder Liste mit n groesser gleich 6 (bei n kleiner 6 alle keepNext). build.ps1 erweitert auf 3 Schritte und ruft das Post-Processing-Skript automatisch nach erfolgreichem DOCX-Build auf, mit Console-Output und Log-Statistiken (Anzahl Listen Bullets keepNext-Markierungen). Sandbox-Verifikation 26 Listen 184 Bullets 93 keepNext, Pattern fuer 11-Bullet-Liste KK......KK.. Auf Thomas System visuell bestaetigt: Listen werden an guten Stellen getrennt, keine ungenutzten Seitenenden, keine einzelnen Bullets allein am Seitenrand. teilgebiete/01-lebenslauf.md um B3- und B3.5-Bloecke ergaenzt sowie Naechste-Schritte-Liste auf B4 C D umstrukturiert. agent-prompt.md Aktueller-Stand-Abschnitt fortgeschrieben mit B3 und B3.5, Hinweis auf 3-stufige DOCX-Pipeline und Edit-Tool-Truncation an build.ps1 ergaenzt. Naechste Session startet mit B4 (Heading-Farben oder Trennlinien analog PDF). diff --git a/agent-prompt.md b/agent-prompt.md index 3307407..5b1d7d1 100644 --- a/agent-prompt.md +++ b/agent-prompt.md @@ -88,19 +88,19 @@ Setze zwischen sinnvollen Zwischenständen Checkpoints (z.B. nach "Marketing.md **Letzte Session:** S07 (2026-04-26) **Was wurde gemacht:** -- **PDF-Build-Fehler endgültig behoben.** Pandoc-3.x-`\def\LTcaptype{none}`-Bug ([Issue #11201](https://github.com/jgm/pandoc/issues/11201)). Fix: `\newcounter{none}` im Template, sandbox-reproduziert. PDF läuft auf Thomas' System, Ausbildungs-Layout visuell bestätigt. **Iteration A damit inhaltlich abgeschlossen.** -- **Build-UX-Fix:** `build/build.ps1` mit `Start-Sleep -Seconds 3` pro fehlschlagendem Schritt, damit das PowerShell-Fenster bei Fehler nicht zu schnell schließt. -- **Iteration B durchgezogen — `reference.docx` programmatisch via `build/build-reference-docx.py` (Python-Stdlib only, kein pip).** Holt Pandoc-Default-Reference per `pandoc --print-default-data-file`, entpackt die DOCX als ZIP, modifiziert XML mit ElementTree, repackt. - - **B1 — Schriften:** Theme `majorFont` und `minorFont` beide auf Calibri (Pandoc 3.x setzt Defaults auf Aptos Display / Aptos). Defensive Maßnahme: alle direkten ``-Referenzen außerhalb von Code-Schriften (Consolas, Courier, ...) auf Calibri. - - **B1 — Tabellen:** Stil `Table` mit `` auf allen Sides. Word-Editor zeigt weiterhin Tabellen-Anzeige-Hilfslinien (kein Druck-Rendering); Druckansicht und PDF-Export sind sauber rahmenlos. - - **B1.5 — Schriftgrößen analog PDF:** DocDefault Body 11 pt, Heading 1/2/3 auf 15/13/12 pt. DOCX schrumpft von 10 auf 9 Seiten. - - **B2 — Header, Footer, Page-Setup:** `header1.xml` (Default ab Seite 2: Name links, „Lebenslauf" rechts), `header2.xml` (leer für Seite 1 via `titlePg`), `footer1.xml` (rechts „Seite n / m" mit `PAGE`/`NUMPAGES`-Feldern, einmal als `default`, einmal als `first` referenziert, damit Seite 1 trotz titlePg den Footer hat). Page-Setup explizit: A4 mit 2.2 cm oben/unten, 2.5 cm links/rechts (analog PDF). Tab-Stop am rechten Textrand 9072 dxa = 16 cm. Beziehungen werden mit dynamisch ermittelter nächster freier `rId` registriert; Content-Types-Overrides ergänzt; sectPr regex-basiert ersetzt (idempotent gegen `` und längere Varianten). Pandoc 2.9 und 3.x übernehmen die sectPr ins generierte DOCX (in der Sandbox end-to-end verifiziert). DOCX-Layout von Thomas visuell bestätigt: Seite 1 ohne Header und mit Footer, Seite 2 ff. Header und Footer wie gewünscht, Tab-Stops bündig am rechten Textrand. +- **PDF-Build-Fehler endgültig behoben.** Pandoc-3.x-`\def\LTcaptype{none}`-Bug ([Issue #11201](https://github.com/jgm/pandoc/issues/11201)). Fix: `\newcounter{none}` im Template. **Iteration A inhaltlich abgeschlossen.** +- **Build-UX-Fix:** `build/build.ps1` mit `Start-Sleep -Seconds 3` pro fehlschlagendem Schritt. +- **Iteration B durchgezogen — `reference.docx` programmatisch via `build/build-reference-docx.py`** (Python-Stdlib, holt Pandoc-Default-Reference, entpackt ZIP, modifiziert XML mit ElementTree, repackt). Inhalt: + - **B1 — Schriften, Tabellen:** Theme major+minor auf Calibri (Pandoc-3.x-Default war Aptos Display/Aptos), Stil `Table` mit `tblBorders=none` auf allen Sides. + - **B1.5 — Schriftgrößen analog PDF:** DocDefault Body 11 pt, Heading 1/2/3 auf 15/13/12 pt. + - **B2 — Header, Footer, Page-Setup:** Header (Name links, Lebenslauf rechts) ab Seite 2, leerer Header für Seite 1 via `titlePg`. Footer (Seite n / m) auf allen Seiten inkl. Seite 1 (zwei `footerReference`-Einträge, default + first auf gleicher rId). Page-Setup A4 mit 2.2/2.5 cm Rändern, Tab-Stop 9072 dxa. + - **B3 — Schusterjungen/Witwen-Schutz für Headings:** DocDefault `widowControl`, Heading 1/2/3 und `FirstParagraph` (für Kenntnisse-Subsection-Labels) mit `keepNext` + `keepLines`. +- **Iteration B3.5 — 3-3-Regel für Listen-Bullets** über neues Post-Processing-Skript `build/post-process-docx.py`. Erster Versuch (Compact-Stil mit keepNext) hat Listen komplett unteilbar gemacht — Folge: Job-Stationen begannen jedes Mal auf neuer Seite, ungenutzte Seitenenden. Auf Wunsch von Thomas Per-Bullet-Logik: bei Listen mit ≥ 6 Bullets bekommen die ersten 2 und die N-3-/N-2-Bullets `keepNext`, dazwischen darf getrennt werden (= mind. 3 Bullets vor und nach jedem Umbruch). Bei < 6 Bullets bleibt alles zusammen. `build.ps1` ruft das Skript automatisch nach DOCX-Build als Schritt [3/3] auf. Sandbox-Verifikation: 26 Listen, 184 Bullets, 93 keepNext-Markierungen, Pattern z.B. `KK......KK.` für 11-Bullet-Liste. Auf Thomas' System visuell bestätigt: Listen werden sauber an guten Stellen getrennt, keine ungenutzten Seitenenden mehr. **Nächste Aufgabe:** Teilgebiet 01 — verbleibende Iterationen: -1. **B3 — Keep with next + Widow/Orphan-Control für DOCX.** Schusterjungen-Schutz analog `\widowpenalty`/`needspace` im PDF. Auf Stilebene `` für Headings und `` als DocDefault. -2. **B4 (optional) — Heading-Farben auf DesTEngS-Blau und/oder Trennlinien analog PDF.** Eher Kosmetik bei Vorlage für Consulting-Agenturen. -3. **C — Foto-Einbindung** in cv.md mit Pandoc-Image-Syntax und Template-Anpassung für Position/Größe (z.B. oben rechts neben Name, ca. 3 cm). -4. **D — Hyphenation-Feintuning für PDF** — kurze Wortteile am Zeilenanfang mit höherer Penalty oder `\hyphenation`-Ausnahmen reduzieren. +1. **B4 — Heading-Farben auf DesTEngS-Blau und/oder Trennlinien analog PDF.** Bringt das DOCX optisch näher ans PDF (für Direktverwendung; bei Consulting-Agenturen, die das Layout ohnehin überschreiben, eher Kosmetik). Erstes Aufgabe der nächsten Session. +2. **C — Foto-Einbindung** in cv.md mit Pandoc-Image-Syntax und Template-Anpassung für Position/Größe (z.B. oben rechts neben Name, ca. 3 cm). +3. **D — Hyphenation-Feintuning für PDF** — kurze Wortteile am Zeilenanfang mit höherer Penalty oder `\hyphenation`-Ausnahmen reduzieren. Nach D): Status von Teilgebiet 01 in `zentral-index.md` auf „abgeschlossen" setzen (R2-OK von Thomas). Anschließend nächstes Teilgebiet nach Priorität (laut Index Teilgebiet 02 „Zeugnis von ASMPT"). @@ -114,4 +114,5 @@ Nach D): Status von Teilgebiet 01 in `zentral-index.md` auf „abgeschlossen" se - **`checkpoint.ps1` ist robust** gegen Anführungszeichen, Pipes, Whitespace-Anomalien und Index-Lock-Reste. `.checkpoint-pending.txt` darf ganz normal Sonderzeichen enthalten. - **`build.ps1` pausiert bei Fehler 3 Sekunden pro fehlgeschlagenem Schritt.** Nicht überrascht sein, wenn ein fehlerhafter Lauf entsprechend länger braucht. - **`build/build-reference-docx.py` muss VOR `build.ps1` manuell aufgerufen werden, wenn `templates/reference.docx` neu gebaut werden soll.** Das Skript ist nicht in `build.ps1` integriert (würde jeden Build verlangsamen und Pandoc-Default-Reference jedes Mal neu ziehen). Wenn jemand die `reference.docx` von Hand in Word editiert, gehen die Änderungen beim nächsten Skript-Lauf verloren — Stile gehören also ins Skript, nicht in Word. -- **Edit-Tool kann Dateien beim Schreiben über den NTFS-Mount truncatieren** (mehrfach in S07 erlebt am Python-Skript). `mcp__workspace__bash` mit `cat <<'EOF' > path` ist die zuverlässige Alternative für längere Dateien (>~150 Zeilen). Nach jedem Edit auf NTFS-Mount-Datei: `wc -l` und `tail -c` zur Verifikation. +- **Edit-Tool kann Dateien beim Schreiben über den NTFS-Mount truncatieren** (mehrfach in S07 erlebt am Python-Skript und an `build.ps1`). `mcp__workspace__bash` mit `cat <<'EOF' > path` ist die zuverlässige Alternative für längere Dateien (>~150 Zeilen). Nach jedem Edit auf NTFS-Mount-Datei: `wc -l` und `tail -c 60` zur Verifikation. +- **DOCX-Pipeline ist jetzt 3-stufig:** (1) `build/build-reference-docx.py` baut die `reference.docx` (manuell aufrufen, wenn Stile geändert werden sollen), (2) `build/build.ps1` baut PDF und DOCX, (3) `build/post-process-docx.py` wird automatisch aus `build.ps1` aufgerufen für die 3-3-Listen-Bullet-Regel. Wer das Bullet-Verhalten ändern will, fasst das Post-Processing-Skript an, nicht die reference.docx. diff --git a/artefakte/01-lebenslauf/build/build-reference-docx.py b/artefakte/01-lebenslauf/build/build-reference-docx.py index b5d5c99..df7494d 100644 --- a/artefakte/01-lebenslauf/build/build-reference-docx.py +++ b/artefakte/01-lebenslauf/build/build-reference-docx.py @@ -6,7 +6,7 @@ build-reference-docx.py Baut die templates/reference.docx fuer die Pandoc-DOCX-Pipeline aus der Pandoc-Default-Reference, mit gezielten Anpassungen. -Iteration B1 + B1.5 + B2 (aktuell): +Iteration B1 + B1.5 + B2 + B3 (aktuell): B1 - Theme-Schriften (majorFont und minorFont) beide auf Calibri. B1 - Direkte Schriftnamen-Referenzen in styles.xml auf Calibri (Code-Schriften wie Consolas bleiben). @@ -14,24 +14,20 @@ Iteration B1 + B1.5 + B2 (aktuell): B1.5 - Body-DocDefault 11pt, Heading 1/2/3 auf 15/13/12 pt. B2 - Header (Name links, "Lebenslauf" rechts) ab Seite 2; Seite 1 mit leerem Header (titlePg-Mechanik). Footer (rechts: Seite n / m) auf - allen Seiten inkl. Seite 1 (footer-Ref fuer "first" zeigt auf den - gleichen Footer wie "default"). Page-Setup explizit: A4, Raender - analog PDF (top/bottom 2.2 cm, left/right 2.5 cm). Damit ist der - Tab-Stop deterministisch unabhaengig von Word-Locale-Defaults. + allen Seiten inkl. Seite 1. Page-Setup explizit: A4, Raender + analog PDF (top/bottom 2.2 cm, left/right 2.5 cm). + B3 - DocDefault widowControl. Heading 1/2/3 mit keepNext + keepLines. + Zusaetzlich 'FirstParagraph' (Pandoc-Stil fuer den ersten Absatz + nach einem Heading) — deckt die fett formatierten Kenntnisse- + Subsection-Labels ab. Hinweis: Listen-Bullet-Schutz (3-3-Regel) + passiert nicht hier, sondern im Post-Processing + (build/post-process-docx.py), das auf das fertige DOCX angewendet + wird — ein Stil kann keine Per-Bullet-Logik abbilden. Geplant in Folge-Iterationen: - B3 - Heading-Stile mit "keep with next", Widow/Orphan-Control auf Stilebene B4 - optional Heading-Farben auf DesTEngS-Blau analog PDF - -Vorgehen: - 1. Pandoc-Default-Reference per `pandoc --print-default-data-file - reference.docx` extrahieren. - 2. Als ZIP entpacken. - 3. Relevante XML-Dateien anpassen, neue Header/Footer-XMLs anlegen, - Beziehungen und ContentTypes ergaenzen, sectPr setzen. - 4. Als neue ZIP-Datei (templates/reference.docx) speichern. - -Voraussetzungen: nur Python-Stdlib + Pandoc im PATH. + C - Foto-Einbindung + D - Hyphenation-Feintuning fuer PDF """ from __future__ import annotations @@ -44,15 +40,11 @@ import zipfile from pathlib import Path from xml.etree import ElementTree as ET -# --- Pfade ----------------------------------------------------------------- - SCRIPT_DIR = Path(__file__).resolve().parent BASE_DIR = SCRIPT_DIR.parent TEMPLATES_DIR = BASE_DIR / "templates" OUTPUT_FILE = TEMPLATES_DIR / "reference.docx" -# --- XML-Namespaces -------------------------------------------------------- - NS = { "w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main", "a": "http://schemas.openxmlformats.org/drawingml/2006/main", @@ -67,8 +59,6 @@ ET.register_namespace("", NS["rel"]) W = "{%s}" % NS["w"] A = "{%s}" % NS["a"] -# --- Konfiguration --------------------------------------------------------- - CODE_FONTS = {"consolas", "courier", "courier new", "liberation mono", "monaco", "menlo", "fira mono", "fira code"} TARGET_FONT = "Calibri" @@ -80,25 +70,23 @@ SIZE_HEADING3 = 24 HEADING_SIZES = {"Heading1": SIZE_HEADING1, "Heading2": SIZE_HEADING2, "Heading3": SIZE_HEADING3} +# Compact NICHT mehr in dieser Liste — Listen-Bullet-Schutz uebernimmt das +# Post-Processing-Skript pro-Bullet. +KEEP_STYLES = ("Heading1", "Heading2", "Heading3", "FirstParagraph") -# Page-Setup (in DXA, 1cm = 566.929 dxa; 1 inch = 1440 dxa) -# A4: 21.0 x 29.7 cm -PAGE_W = 11906 # A4 Breite -PAGE_H = 16838 # A4 Hoehe -MARGIN_TOP = 1247 # 2.2 cm -MARGIN_BOT = 1247 # 2.2 cm -MARGIN_LEFT = 1417 # 2.5 cm -MARGIN_RIGHT = 1417 # 2.5 cm -HEADER_POS = 720 # 1.27 cm vom oberen Seitenrand -FOOTER_POS = 720 # 1.27 cm vom unteren Seitenrand -# Tab-Stop am rechten Textrand: PAGE_W - LEFT - RIGHT = 9072 dxa = 16 cm +PAGE_W = 11906 +PAGE_H = 16838 +MARGIN_TOP = 1247 +MARGIN_BOT = 1247 +MARGIN_LEFT = 1417 +MARGIN_RIGHT = 1417 +HEADER_POS = 720 +FOOTER_POS = 720 HEADER_RIGHT_TAB = PAGE_W - MARGIN_LEFT - MARGIN_RIGHT HEADER_LEFT = "Dr.-Ing. Thomas Langer" HEADER_RIGHT = "Lebenslauf" -# --- Hilfsfunktionen ------------------------------------------------------- - def log(msg: str) -> None: print(f"[build-reference-docx] {msg}", flush=True) @@ -178,8 +166,6 @@ def replace_direct_fonts_in_styles(styles_xml: Path) -> None: f" gesetzt (Code-Fonts unangetastet: {skipped})") write_xml(tree, styles_xml) -# --- B1: Tabellen ---------------------------------------------------------- - def set_table_borders_none(styles_xml: Path) -> None: tree = ET.parse(styles_xml) root = tree.getroot() @@ -201,8 +187,6 @@ def set_table_borders_none(styles_xml: Path) -> None: log(" Style 'Table': tblBorders=none auf allen Sides") write_xml(tree, styles_xml) -# --- B1.5: Schriftgroessen ------------------------------------------------ - def set_default_body_size(styles_xml: Path) -> None: tree = ET.parse(styles_xml) root = tree.getroot() @@ -230,7 +214,35 @@ def set_heading_sizes(styles_xml: Path) -> None: log(f" Stil {sid!r}: Schriftgroesse {target/2} pt") write_xml(tree, styles_xml) -# --- B2: Header und Footer ------------------------------------------------ +def set_widow_control_default(styles_xml: Path) -> None: + tree = ET.parse(styles_xml) + root = tree.getroot() + docDefaults = root.find(f"{W}docDefaults") or ET.SubElement(root, f"{W}docDefaults") + pPrDefault = docDefaults.find(f"{W}pPrDefault") or ET.SubElement(docDefaults, f"{W}pPrDefault") + pPr = pPrDefault.find(f"{W}pPr") or ET.SubElement(pPrDefault, f"{W}pPr") + if pPr.find(f"{W}widowControl") is None: + ET.SubElement(pPr, f"{W}widowControl") + log(" pPrDefault: widowControl aktiviert") + write_xml(tree, styles_xml) + +def set_keep_next_styles(styles_xml: Path) -> None: + tree = ET.parse(styles_xml) + root = tree.getroot() + seen = set() + for style in root.findall(f"{W}style"): + sid = style.get(f"{W}styleId") + if sid not in KEEP_STYLES: + continue + pPr = style.find(f"{W}pPr") or ET.SubElement(style, f"{W}pPr") + for tag in (f"{W}keepNext", f"{W}keepLines"): + if pPr.find(tag) is None: + ET.SubElement(pPr, tag) + log(f" Stil {sid!r}: keepNext + keepLines") + seen.add(sid) + missing = set(KEEP_STYLES) - seen + if missing: + log(f" Hinweis: Stil(e) {sorted(missing)!r} nicht gefunden, uebersprungen") + write_xml(tree, styles_xml) def header_default_xml() -> bytes: return ( @@ -305,9 +317,6 @@ def update_sectpr_with_headers(document_xml: Path, header_default_rid: str, header_first_rid: str, footer_default_rid: str) -> None: - """Ersetzt sectPr durch Page-Setup + Header/Footer-Refs + titlePg. - Footer-Ref wird zweimal eingebaut (default und first), beide auf - den gleichen Footer — dann hat Seite 1 trotz titlePg den Footer.""" text = document_xml.read_text(encoding="utf-8") new_sectpr = ( f'' @@ -359,8 +368,6 @@ def add_header_footer(unpacked: Path) -> None: update_sectpr_with_headers(doc_xml, rid_h_def, rid_h_first, rid_f_def) -# --- Hauptablauf ----------------------------------------------------------- - def main() -> int: log(f"Ziel: {OUTPUT_FILE}") TEMPLATES_DIR.mkdir(parents=True, exist_ok=True) @@ -387,6 +394,10 @@ def main() -> int: set_default_body_size(styles_xml) log("Anpassung: Heading-Schriftgroessen 15/13/12 pt") set_heading_sizes(styles_xml) + log("Anpassung: Widow/Orphan-Control im DocDefault (B3)") + set_widow_control_default(styles_xml) + log("Anpassung: keepNext + keepLines auf Heading 1/2/3 + FirstParagraph (B3)") + set_keep_next_styles(styles_xml) log("Anpassung: Header und Footer einbauen (B2)") add_header_footer(unpacked) diff --git a/artefakte/01-lebenslauf/build/build.ps1 b/artefakte/01-lebenslauf/build/build.ps1 index 3310710..1c60ea7 100644 --- a/artefakte/01-lebenslauf/build/build.ps1 +++ b/artefakte/01-lebenslauf/build/build.ps1 @@ -7,17 +7,24 @@ Deterministischer Build ohne GUI oder Komfortfunktionen. - PDF via Pandoc + LuaLaTeX, nutzt templates/template.tex - DOCX via Pandoc, nutzt templates/reference.docx + - DOCX-Post-Processing via build/post-process-docx.py + (Listen-Bullet-Schutz nach 3-3-Regel) - Log in output/build.log (ueberschrieben pro Build) - Fortschritt wird zusaetzlich in der Konsole angezeigt - - Exit-Code 0 = beide Ausgaben erfolgreich, 1 = ein oder beide Schritte fehlgeschlagen + - Exit-Code 0 = alle Schritte erfolgreich, 1 = mindestens ein Fehler .NOTES Voraussetzungen auf dem System: - Pandoc (im PATH) - MiKTeX mit LuaLaTeX + - Python 3 (im PATH) fuer Post-Processing - System-Fonts: IBM Plex Sans und IBM Plex Mono fuer Windows installiert MiKTeX mit "Install missing packages on the fly: Yes" zieht fehlende LaTeX-Pakete beim ersten Lauf automatisch. + + Hinweis: templates/reference.docx wird NICHT bei jedem Build neu gebaut. + Bei Stiländerungen vorher manuell `python build/build-reference-docx.py` + aufrufen. #> $ErrorActionPreference = 'Continue' @@ -77,7 +84,7 @@ if ($overallExit -ne 0) { # --- PDF-Build --------------------------------------------------------------- Write-Host "" -Write-Host "[1/2] PDF wird erzeugt (Pandoc + LuaLaTeX) ..." -ForegroundColor Yellow +Write-Host "[1/3] PDF wird erzeugt (Pandoc + LuaLaTeX) ..." -ForegroundColor Yellow Write-Log "--- Pandoc -> PDF (LuaLaTeX) ---" $pdfArgs = @( '--from=markdown+smart', @@ -99,13 +106,12 @@ if ($pdfExit -eq 0 -and (Test-Path $outputPdf)) { Write-Host " PDF FEHLER (Exit $pdfExit) - Details siehe build.log" -ForegroundColor Red Write-Log "PDF FEHLER (Exit $pdfExit)" $overallExit = 1 - # Kurz pausieren, damit die rote Fehlerzeile lesbar bleibt, falls das Fenster danach zugeht Start-Sleep -Seconds 3 } # --- DOCX-Build -------------------------------------------------------------- Write-Host "" -Write-Host "[2/2] DOCX wird erzeugt (Pandoc) ..." -ForegroundColor Yellow +Write-Host "[2/3] DOCX wird erzeugt (Pandoc) ..." -ForegroundColor Yellow Write-Log "--- Pandoc -> DOCX ---" $docxArgs = @( '--from=markdown+smart', @@ -122,11 +128,33 @@ if ($docxExit -eq 0 -and (Test-Path $outputDocx)) { $sizeKB = [math]::Round((Get-Item $outputDocx).Length / 1KB, 1) Write-Host " DOCX OK ($sizeKB KB): $outputDocx" -ForegroundColor Green Write-Log "DOCX OK: $outputDocx ($sizeKB KB)" + + # --- Post-Processing: Listen-Bullet-Schutz (3-3-Regel) ------------------ + Write-Host "" + Write-Host "[3/3] DOCX-Post-Processing (Listen-Bullet-Schutz) ..." -ForegroundColor Yellow + Write-Log "--- Post-Process DOCX ---" + $postScript = Join-Path $scriptDir 'post-process-docx.py' + if (Test-Path $postScript) { + $ppOutput = & python $postScript 2>&1 + $ppExit = $LASTEXITCODE + $ppOutput | ForEach-Object { + Write-Log ([string]$_) + Write-Host " $_" + } + if ($ppExit -ne 0) { + Write-Host " POST-PROCESS FEHLER (Exit $ppExit)" -ForegroundColor Red + Write-Log "POST-PROCESS FEHLER (Exit $ppExit)" + $overallExit = 1 + Start-Sleep -Seconds 3 + } + } else { + Write-Host " Hinweis: $postScript nicht vorhanden, uebersprungen" -ForegroundColor Yellow + Write-Log "Hinweis: post-process-docx.py nicht vorhanden, uebersprungen" + } } else { Write-Host " DOCX FEHLER (Exit $docxExit) - Details siehe build.log" -ForegroundColor Red Write-Log "DOCX FEHLER (Exit $docxExit)" $overallExit = 1 - # Kurz pausieren, damit die rote Fehlerzeile lesbar bleibt, falls das Fenster danach zugeht Start-Sleep -Seconds 3 } diff --git a/artefakte/01-lebenslauf/build/post-process-docx.py b/artefakte/01-lebenslauf/build/post-process-docx.py new file mode 100644 index 0000000..eb4b848 --- /dev/null +++ b/artefakte/01-lebenslauf/build/post-process-docx.py @@ -0,0 +1,185 @@ +#!/usr/bin/env python3 +""" +post-process-docx.py +==================== + +Wird auf das von Pandoc erzeugte DOCX angewendet, NACH `build.ps1`. Setzt +Per-Bullet-keepNext-Markierungen, die ein Stil nicht abbilden kann: + +3-3-Regel fuer Listen-Bullets: + - Eine Liste ist eine Sequenz aufeinanderfolgender Absaetze mit + -Eigenschaft im Body (nicht innerhalb von Tabellen-Zellen). + - Bei einer Liste mit weniger als 6 Bullets: alle Bullets bekommen + (Liste bleibt unteilbar — bei <6 ist die 3-3-Regel + sowieso nur durch Zusammenhalten aller erfuellbar). + - Bei einer Liste mit 6 oder mehr Bullets: die ersten 2 und die + drittletzten und vorletzten Bullets bekommen . + Damit gilt: nach Bullet 1 darf nicht getrennt werden (1+2+3 zusammen), + und nach Bullet N-3 darf nicht getrennt werden (N-2+N-1+N zusammen). + Trennen ist erlaubt zwischen den Bullets in der Mitte. + +Bullets in Tabellen-Zellen werden uebersprungen — Compact wird auch fuer +Tabellen-Zellen-Inhalte verwendet, dort wollen wir kein keepNext. + +Voraussetzungen: nur Python-Stdlib. +""" + +from __future__ import annotations + +import re +import sys +import zipfile +from pathlib import Path + +SCRIPT_DIR = Path(__file__).resolve().parent +BASE_DIR = SCRIPT_DIR.parent +DOCX_FILE = BASE_DIR / "output" / "Lebenslauf_Dr-Ing_Thomas_Langer.docx" + +W_NS = "http://schemas.openxmlformats.org/wordprocessingml/2006/main" + +def log(msg: str) -> None: + print(f"[post-process-docx] {msg}", flush=True) + +def is_bullet_paragraph(p_xml: str) -> bool: + """True wenn Absatz-XML eine numPr-Eigenschaft hat (= Listen-Bullet).""" + return " bool: + return " str: + """Fuegt in das pPr-Element ein. Falls kein pPr existiert, + wird es angelegt. Idempotent (wenn schon vorhanden, unveraendert).""" + if has_keep_next(p_xml): + return p_xml + if "" in p_xml: + return p_xml.replace("", "", 1) + if "" in p_xml: + return p_xml.replace("", "", 1) + # kein pPr: vor oder vor + new_ppr = "" + if "", "" + new_ppr, 1) \ + if p_xml.startswith("") else p_xml + return p_xml.replace("", new_ppr + "", 1) + +# Regex: ein ..., optional gefolgt vom oeffnenden Marker fuer +# Tabelle () oder schliessenden Body (). Wir splitten nicht, +# sondern iterieren paragraphenweise und tracken Tabellen-Schachtelung. + +P_RE = re.compile(r"]*>.*?", re.DOTALL) +TBL_OPEN = "" +TBL_CLOSE = "" + +def process_document_xml(xml: str) -> tuple[str, dict]: + """Findet Listen-Sequenzen ausserhalb von Tabellen, wendet 3-3-Regel an. + Gibt das modifizierte XML und Statistiken zurueck.""" + # Tokenize: ...-Bereiche markieren, damit wir sie ueberspringen. + # Ansatz: wir gehen durch das XML und tracken aktuelle Tabellen-Tiefe. + # Wenn Tiefe > 0: Bullets in Tabellen-Zellen ueberspringen. + out = [] + pos = 0 + table_depth = 0 + bullet_run: list[tuple[int, str]] = [] # (out_idx, p_xml) Indizes in out + stats = {"lists": 0, "bullets_in_lists": 0, "bullets_keepnext": 0, + "skipped_in_tables": 0} + + def flush_run(): + if not bullet_run: + return + n = len(bullet_run) + stats["lists"] += 1 + stats["bullets_in_lists"] += n + if n < 6: + indices_keep = list(range(n)) + else: + indices_keep = [0, 1, n-3, n-2] + for k in indices_keep: + idx, p_xml = bullet_run[k] + new_xml = add_keep_next(p_xml) + if new_xml != p_xml: + out[idx] = new_xml + stats["bullets_keepnext"] += 1 + bullet_run.clear() + + # Wir scannen das XML linear nach ..., , + # und sammeln Bullet-Sequenzen ausserhalb von Tabellen. + # Dafuer iterieren wir mit einem regex der ALLE drei Token findet. + token_re = re.compile( + r"(?P" + re.escape(TBL_OPEN) + r")" + r"|(?P" + re.escape(TBL_CLOSE) + r")" + r"|(?P]*>.*?)", + re.DOTALL, + ) + last_end = 0 + for m in token_re.finditer(xml): + # nicht-tokenisierten Text dazwischen anhaengen + if m.start() > last_end: + out.append(xml[last_end:m.start()]) + last_end = m.end() + + if m.group("tblopen"): + flush_run() # Listen vor Tabelle abschliessen + table_depth += 1 + out.append(m.group()) + elif m.group("tblclose"): + flush_run() # innerhalb-Tabellen-Listen wir flushen, aber haben + # sie eh nicht angesammelt + table_depth -= 1 + out.append(m.group()) + else: + p_xml = m.group("para") + out.append(p_xml) + if table_depth > 0: + # Bullets in Tabellen-Zellen ignorieren + if is_bullet_paragraph(p_xml): + stats["skipped_in_tables"] += 1 + # nicht-bullet-paragraph in tabelle: kein effekt + continue + if is_bullet_paragraph(p_xml): + bullet_run.append((len(out) - 1, p_xml)) + else: + # Sequenz-Ende: 3-3-Regel anwenden + flush_run() + + # Rest hinten dranhaengen + if last_end < len(xml): + out.append(xml[last_end:]) + flush_run() # falls Liste am Body-Ende + return "".join(out), stats + +def main() -> int: + if not DOCX_FILE.exists(): + sys.stderr.write(f"FEHLER: {DOCX_FILE} existiert nicht. " + f"Erst build.ps1 laufen lassen.\n") + return 1 + log(f"Verarbeite: {DOCX_FILE}") + + # DOCX in memory einlesen + with zipfile.ZipFile(DOCX_FILE, "r") as z: + members = {name: z.read(name) for name in z.namelist()} + + doc_xml = members["word/document.xml"].decode("utf-8") + new_xml, stats = process_document_xml(doc_xml) + + if new_xml == doc_xml: + log(" keine Aenderung — keine bullet-Listen gefunden oder bereits gesetzt") + members["word/document.xml"] = new_xml.encode("utf-8") + + # DOCX zurueckschreiben (mode='w' truncatet) + with zipfile.ZipFile(DOCX_FILE, "w", zipfile.ZIP_DEFLATED) as z: + # [Content_Types].xml zuerst + order = sorted(members.keys(), + key=lambda n: (0 if n == "[Content_Types].xml" else 1, n)) + for name in order: + z.writestr(name, members[name]) + + log(f" Listen gefunden: {stats['lists']}") + log(f" Bullets in Listen: {stats['bullets_in_lists']}") + log(f" keepNext gesetzt: {stats['bullets_keepnext']}") + log(f" Bullets in Tabellen uebersprungen: {stats['skipped_in_tables']}") + log("Fertig.") + return 0 + +if __name__ == "__main__": + sys.exit(main()) diff --git a/artefakte/01-lebenslauf/output/Lebenslauf_Dr-Ing_Thomas_Langer.docx b/artefakte/01-lebenslauf/output/Lebenslauf_Dr-Ing_Thomas_Langer.docx index 09bc7589f1251401c22dd5cfa7a96b6d5a8dfea0b62bbc9084db93ff1e55b319..d4cf75f9201498d63fad459e8e52bed978734f1d2e882028694bd40e2c0fc286 100644 GIT binary patch delta 15373 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 delta 15246 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 T|D_Jb-`Bw3=<%uj>)rncU$I3Y diff --git a/artefakte/01-lebenslauf/output/Lebenslauf_Dr-Ing_Thomas_Langer.pdf b/artefakte/01-lebenslauf/output/Lebenslauf_Dr-Ing_Thomas_Langer.pdf index 3dbf06b..6cf210f 100644 --- a/artefakte/01-lebenslauf/output/Lebenslauf_Dr-Ing_Thomas_Langer.pdf +++ b/artefakte/01-lebenslauf/output/Lebenslauf_Dr-Ing_Thomas_Langer.pdf @@ -727,7 +727,7 @@ endobj << /Type /Catalog /Pages 89 0 R /Outlines 158 0 R /Names 160 0 R /PageMode/UseOutlines /OpenAction 81 0 R >> endobj 162 0 obj -<< /Author(\376\377\000D\000r\000.\000-\000I\000n\000g\000.\000\040\000T\000h\000o\000m\000a\000s\000\040\000L\000a\000n\000g\000e\000r)/Title(\376\377\000L\000e\000b\000e\000n\000s\000l\000a\000u\000f\000\040\000D\000r\000.\000-\000I\000n\000g\000.\000\040\000T\000h\000o\000m\000a\000s\000\040\000L\000a\000n\000g\000e\000r)/Subject()/Creator(\376\377\000P\000a\000n\000d\000o\000c\000\040\000+\000\040\000L\000u\000a\000L\000a\000T\000e\000X)/Keywords() /Producer (LuaTeX-1.24.0) /CreationDate (D:20260426132419+02'00') /ModDate (D:20260426132419+02'00') /Trapped /False /PTEX.FullBanner (This is LuaHBTeX, Version 1.24.0 (MiKTeX 26.2)) >> +<< /Author(\376\377\000D\000r\000.\000-\000I\000n\000g\000.\000\040\000T\000h\000o\000m\000a\000s\000\040\000L\000a\000n\000g\000e\000r)/Title(\376\377\000L\000e\000b\000e\000n\000s\000l\000a\000u\000f\000\040\000D\000r\000.\000-\000I\000n\000g\000.\000\040\000T\000h\000o\000m\000a\000s\000\040\000L\000a\000n\000g\000e\000r)/Subject()/Creator(\376\377\000P\000a\000n\000d\000o\000c\000\040\000+\000\040\000L\000u\000a\000L\000a\000T\000e\000X)/Keywords() /Producer (LuaTeX-1.24.0) /CreationDate (D:20260426162906+02'00') /ModDate (D:20260426162906+02'00') /Trapped /False /PTEX.FullBanner (This is LuaHBTeX, Version 1.24.0 (MiKTeX 26.2)) >> endobj xref 0 163 @@ -895,7 +895,7 @@ xref 0000065582 00000 n 0000065708 00000 n trailer -<< /Size 163 /Root 161 0 R /Info 162 0 R /ID [ ] >> +<< /Size 163 /Root 161 0 R /Info 162 0 R /ID [ ] >> startxref 66368 %%EOF diff --git a/artefakte/01-lebenslauf/output/build.log b/artefakte/01-lebenslauf/output/build.log index 03e1de2..e63b9c3 100644 --- a/artefakte/01-lebenslauf/output/build.log +++ b/artefakte/01-lebenslauf/output/build.log @@ -1,4 +1,4 @@ -===== Build gestartet: 2026-04-26 13:24:16 ===== +===== Build gestartet: 2026-04-26 16:29:03 ===== Source: Q:\DesTEngS\Pro\Git\marketing\claude_cowork\artefakte\01-lebenslauf\source\cv.md Template-TEX: Q:\DesTEngS\Pro\Git\marketing\claude_cowork\artefakte\01-lebenslauf\templates\template.tex Reference: Q:\DesTEngS\Pro\Git\marketing\claude_cowork\artefakte\01-lebenslauf\templates\reference.docx @@ -10,4 +10,11 @@ PDF OK: Q:\DesTEngS\Pro\Git\marketing\claude_cowork\artefakte\01-lebenslauf\outp --- Pandoc -> DOCX --- Cmd: pandoc --from=markdown+smart --reference-doc=Q:\DesTEngS\Pro\Git\marketing\claude_cowork\artefakte\01-lebenslauf\templates\reference.docx --resource-path=Q:\DesTEngS\Pro\Git\marketing\claude_cowork\artefakte\01-lebenslauf\source --output=Q:\DesTEngS\Pro\Git\marketing\claude_cowork\artefakte\01-lebenslauf\output\Lebenslauf_Dr-Ing_Thomas_Langer.docx Q:\DesTEngS\Pro\Git\marketing\claude_cowork\artefakte\01-lebenslauf\source\cv.md DOCX OK: Q:\DesTEngS\Pro\Git\marketing\claude_cowork\artefakte\01-lebenslauf\output\Lebenslauf_Dr-Ing_Thomas_Langer.docx (22 KB) -===== Build beendet: 2026-04-26 13:24:21, Exit-Code 0 ===== +--- Post-Process DOCX --- +[post-process-docx] Verarbeite: Q:\DesTEngS\Pro\Git\marketing\claude_cowork\artefakte\01-lebenslauf\output\Lebenslauf_Dr-Ing_Thomas_Langer.docx +[post-process-docx] Listen gefunden: 27 +[post-process-docx] Bullets in Listen: 186 +[post-process-docx] keepNext gesetzt: 95 +[post-process-docx] Bullets in Tabellen uebersprungen: 0 +[post-process-docx] Fertig. +===== Build beendet: 2026-04-26 16:29:09, Exit-Code 0 ===== diff --git a/artefakte/01-lebenslauf/templates/reference.docx b/artefakte/01-lebenslauf/templates/reference.docx index 2ca9064b92d69b81f195579f5c53bd551a21241a4cbebaa5b1845cfbe1d23483..c8963807e09b90f47f04759a4bbf7b354d69474e258ba21a00bff066283494c6 100644 GIT binary patch delta 2535 zcmZ9OX*kqv8^*_E&6aJ9ogsTOcBU+4jp4OK$S(U5@9$#2KUI8=P#pT&6C%%OQ(t|)mj0mq1z&-^{e5vR4Ys-4yp)bR~ zkx?mE9rpp*Ubu73U_yTX#h4bh^$;w;&89w)FU0H+_el)WV=8*;qJrdG&qGnQMw`g- zZ#0ZgYdJ+8;gF#lHOvkv)BWUQk1~X$L!l~feOokYAxccr^-7)NYXjsIwj zFHGsKj`Ljym~lZqiUcb(6b{@de{$X8$l9!Lg|R-zb~4!e`O!t&TAWDm&Y7zZqFZpI zZnV{pG*qtAZTgIJvE`9Bx>EG?@L&F+t zZd+%SU1%+JtwY8c7Jy$!*M9Ue0J<2aG62h$*Xb1r8UHRl)AETKVHiW^d8lptLi1e? zIP!08qNkUM6i0Hf&QC_6dpiQU{JKs{%~JNy`P^(TNgV8gZFlEs@T_p$;yxfQh>Qbc zYUP0wpk|&?oD*FzxW1M2qXWzmhVJ%Zn_AQH(CjT%C)j;B6bP93AX?+v7%u{ekyYZx zoUJ_-UTw_l()#;TK@6A2p{@dNpEn_wng)S8j#WF9LtaWYA}>ru@{W9F}znYfM?($7FnU$ ztbzNaVo!SO&^JD!k-uW)O2bIv(@twh#fo=-l{zk*0e&XBHXsw zY45~|IUcsd<_lnviaf1E`PV#|U%5DLjA~yo$>ijUqd_ET%{@0-%o1@BBz&dLn|Xh? zz35grD6tH^(Aq7@q1|)HsGAOWWSLVpV5=VRH#?Ck8_K@%#fH37OY47X579Lr6*9AK zCkPA}W~5n>_rZZio0c0#w!y^Gq13Loh*(9Jb29e&yHiPI^E<52ebf-$RlWxFxX+GZ-qryU=~|f2PX%(7~ey{A{ zx*e4LWxCnN_PKcplZb zxR{RgIDU~Oowmu6B+*v0VkE);mdr^#aAMXv@rgRzTJ`*0SbXNJ0Mzfz#x7IwsF^7S znyFr(0|YNTLDvHSUcPC14bX#zckaygD~Uex(P!E6Sl-=MKnIRX+91}F&JJ$eT=i7g z6OVjmwh|!pJQPYu4As_(xa#KGB_500XPBst5w3e6I?3m0^1k*jlUnb!mI{XpzMtt_ zN6gFEEtu4%0yg89t1_qxd7PwK*LLws_YD3 zTYMc~ypi(E|2H$aJ#hTMyp0lpLN~ZPVhQP zxNacC74SV|N7w5<+Zx}@x*dRrANtd~W2o+fb`FJ)x>amiR%)a*P2H!J79G)zNY(`;icF$~AR0r7=iA`?i zqU2@*G%hQW{#hf_W4$hY%&Q|t6i_2+S*cR_hG6O~WvVm$$~qw-Z!!z;TMJ<*nhM%e zG8!wdHl@<26BvYCPP5u!sz!W23X0DLe4NuWvajL&A0{mz^1?3JE1X>7i;Axv!s~3B z*^StD>3Fh8-WghB_gOa4gH&1~Fvr;3MwH)zen28dF9RE@-1WRr8#B3kud_gB#u^dH zb!A-2J&iqX?rL{b+|%)xPnqE%VbLi+4hfqyS1ms&wrz3ETIRCcd9NYz7!zg;tOjPI z6vEQO<_GD+8%Ev6-?V3U-KiFTuY7Wq1^qDN2YJhek!!hS_?Nlav88oKJ-^Okggxoq z*lCUC=*!c%7MFK*gExQ#1y+Onp=ZRUdFC=P@cidavYX9J4RQS~nehAnn@^rS*cl(j zP6q;AxB>!kfj}({Fa^4QY6@maYK#J$v=#+Fq*W<^%cxV}C!;|@jm&vQlDSDyDzb(Y z#K@j!-GA^~R+D0CT{+)-aOGTe{!u4?fP|l$;c$(AwV__PoG_&y{p*MSbhbaQHRc5j bQxD_DNF$giN7NAfR3wZQLV~tKnlkY}c22U8 delta 2431 zcmZ9Oc{~&TAIF_z&SVQC_r2ww^Goh9uv(deVkDGB{Hs6{gH?iJ zjI&Nq9leeOH!xyVp%oV1gTMlh(EA}6cTOgz47?OX3(XRk_RdZE>Q9R6wr3aa8j)K| z#EUq9g%wc^&JEYCoG(T{58e>YQpRCYH z=Et$Lg^@nJQi8TDq=t&w$;Gx+gx{XOav}`qdMCCs=(F0C49fM+3q&M2s{Q#ZR2>kA z>?eFhTlT5Nq=St!;%Fu#7Yk2m6BcA9C$qLr5-qm=c2;(6fQh+E$vey8!xiL5?Zg?px9x_=q)hABR8D$| zM<8o}fK6%Go|+vhqT<-?Q93n>{BSO!f))KeI41v!X-h;ZVmNY_TNH?A7fNuv^eiH$ zXhO*V4_CuZT;8%O?VHv~^F>2al-n)J9KCP}lbXy)d__OQ|KZxAacdWsz$3=M%!9? zPke0)`jn8wJ>FMhMAg_i5x1f=|Du5!g>Mj_8e)bDyBT4Ux{$-ihvIDCgV;8fKmJ9U z12=dDY$^1&N#zDU(!DvT=VSTfOOJt{%$m{^ONdVk+w+%8SG_oE^UPMaYLENl16JhFIhqE0z-kVndmG{yQTFjb+}n$Y>J>V-|N47Bj35GKT-EmFSNPC zm$YK(DBg)ujYqz2R7fOG`rn^cyXD*Vgy;4S(Rz3$$gqEdm#f3($+*_2|K+es@E1sz z-$5EnekuC{W zD%h@v1LG8{5Gq;rxu*S5q~&){*PZg}NUbkrA2pGrKh8?WYKk;`P;U~+ng$j27+p%-1)(NWa zdv=E0>VzOBqS|#a-I*Sz%sCtfs1B|qEBwvV5OhPGXfp+K90ex~=k;0Y7bh1uF7VLU zcm1Z<`7IL5pf!EGp8o*ZSuQ2j$%K%_)L!9 z%pktSodA^-5?D6se5TMme==o;9|)xxF3>)KHteQu7w6W~)gwdD5G z>DeWvr#MQ^*024(+0tqGmA{)0-W7P^8t;NQX!o`~_j04i9$6C{bDqE&jmyL9-wW1V zMAV{xq@_Ma_|yRi@-MPo&n-MUjTD7UoW3e5(!5e~v~^$Nosd^;3Jl zJWioN&DGaVqB3oqY{L8`R(sppd zutU+lMKES#9h0(Oz&ZS0!Kwz3@NzHRHsm4PqJA7+S5-7vfa0yaDsdlv4r^nj{>gzl z3hZ@8%dzUi)k?z3cyy~01FS1v?^{I%8Ix|6SxLZiT4Q0i;676Zj<8JmgTGI%#up?r5hv3F!r^Gl%R|AKiXD%}d_mjn6Pro?qOU zPaoQq9?&@~Q)s?Y#Yb_{4AxWz_b#I&ve96Lg-`*5255ctV6L{C`M_tLtSXn$bRSuh zj|h9C2NuxO%RMF_WB_0;TWg$Mzu@spiicRE^Y{ABgtbBCv(`Dn$PBqu$jw~2F5%i0 zPtGJiW7u|5eoL=+-cNUnQqxx^D|T@oSzyAVCbjW-+qKG!ksWzh4 zPd4G?Y$R*uMJ{-+(z-d%xO7X)ooP&+;R3gNYtbZ|eD~zF4~ZADQ<6uk8Vc%SlQ+d2 zUg9$4Mtejbz#N{6%kQXa>aoq-F+ZPzx4_>Y}X uVTAT7j3*f?hX1!` aktiviert (klassische Witwen/Waisen-Logik in mehrzeiligen Absätzen). +- Heading 1/2/3 und `FirstParagraph` (Pandoc-Stil für ersten Absatz nach einem Heading — deckt unsere Kenntnisse-Subsection-Labels ab) bekommen `` und ``. Damit bleibt jede Überschrift mit dem nachfolgenden Inhalt zusammen. + +**B3.5 — 3-3-Regel für Listen-Bullets:** + +- Erster Versuch (Compact-Stil mit `keepNext+keepLines`) hat Listen komplett unteilbar gemacht — Folge: Job-Stationen begannen jedes Mal auf einer neuen Seite, Seitenenden ungenutzt. Auf Wunsch von Thomas auf eine 3-3-Regel umgestellt: bei Listen mit ≥ 6 Bullets darf getrennt werden, aber mindestens 3 Bullets bleiben jeweils zusammen vor und nach dem Umbruch. Bei Listen mit < 6 Bullets bleibt alles zusammen (sonst nicht erfüllbar). +- Da das stilbasiert nicht abbildbar ist (alle Bullets haben pStyle="Compact"), läuft die Logik in einem **Post-Processing-Skript** `build/post-process-docx.py`, das nach dem Pandoc-DOCX-Build die `document.xml` modifiziert: Sequenzen aufeinanderfolgender Listen-Bullets (Absätze mit ``) werden gefunden, pro Sequenz bekommen die ersten 2 und die N-3-/N-2-Bullets ``. Bullets in Tabellen-Zellen werden defensiv ausgenommen (faktisch bei uns leer, weil unsere Tabellen-Zellen Compact-Absätze ohne numPr enthalten). +- `build.ps1` ruft das Skript automatisch nach erfolgreichem DOCX-Build auf (Schritt [3/3]), Console-Output und Log enthalten Statistiken (Anzahl Listen, Bullets, gesetzte keepNext-Markierungen). +- Sandbox-Verifikation: 26 Listen, 184 Bullets, 93 keepNext-Markierungen, Pattern für Listen mit n ≥ 6 z.B. `KK......KK.` (Liste mit 11 Bullets: erste 2 + Bullets 9 und 10 keepNext). Auf Thomas' System visuell bestätigt: Stationen-Listen werden jetzt sauber an guter Stelle getrennt, keine ungenutzten Seitenenden mehr, kein einzelner Bullet alleine am Seitenrand. + ## Nächste Schritte -1. **Iteration B3 — Heading-Stile mit „keep with next" und Widow/Orphan-Control:** Schusterjungen-Schutz für DOCX analog zu `\widowpenalty`/`needspace` im PDF. Auf Stilebene über `` für Headings und `` als DocDefault. -2. **Iteration B4 (optional)** — Heading-Farben auf DesTEngS-Blau und/oder Trennlinien analog PDF, falls das DOCX optisch näher ans PDF heran soll. Bei Vorlage für Consulting-Agenturen, die das Layout ohnehin überschreiben, ist das aber eher Kosmetik. -3. **Iteration C — Foto-Einbindung:** Portraitfoto in `source/cv.md` einbetten (Pandoc-Image-Syntax), Position und Größe im Template absichern (z.B. oben rechts neben Name, ca. 3 cm). -4. **Iteration D — Hyphenation-Feintuning für PDF:** Kurze Wortteile am Zeilenanfang mit höherer Penalty oder gezielten `\hyphenation`-Ausnahmen reduzieren. Iterativ. -5. Teilgebiet nach erfolgreichem Output und Freigabe durch Thomas abschließen (R2-OK von Thomas: Status auf „abgeschlossen" im zentral-index.md). +1. **Iteration B4 — Heading-Farben auf DesTEngS-Blau und/oder Trennlinien analog PDF.** Eher Kosmetik bei Vorlage für Consulting-Agenturen, aber für eigene Direktverwendung des DOCX (Website-Download, persönliche Bewerbungen) sinnvoll. +2. **Iteration C — Foto-Einbindung:** Portraitfoto in `source/cv.md` einbetten (Pandoc-Image-Syntax), Position und Größe im Template absichern (z.B. oben rechts neben Name, ca. 3 cm). +3. **Iteration D — Hyphenation-Feintuning für PDF:** Kurze Wortteile am Zeilenanfang mit höherer Penalty oder gezielten `\hyphenation`-Ausnahmen reduzieren. Iterativ. +4. Teilgebiet nach erfolgreichem Output und Freigabe durch Thomas abschließen (R2-OK von Thomas: Status auf „abgeschlossen" im zentral-index.md). ## Artefakte @@ -188,8 +199,9 @@ Die in S04 mit docx-js erstellte Version hatte strukturelle typographische Mäng - `artefakte/01-lebenslauf/source/foto-wrba_2026_6782_1.jpg` — Portraitfoto (umbenannt, noch nicht in cv.md eingebunden). - `artefakte/01-lebenslauf/templates/template.tex` — Pandoc-LaTeX-Template für LuaLaTeX (Iteration A inkl. Pandoc-3.x-Hotfix `\newcounter{none}`). - `artefakte/01-lebenslauf/templates/reference.docx` — Pandoc-Reference-Doc, **automatisch erzeugt** durch `build/build-reference-docx.py`. Nicht von Hand editieren — Änderungen würden beim nächsten Skript-Lauf überschrieben. -- `artefakte/01-lebenslauf/build/build-reference-docx.py` — Python-Skript zum Bauen der `reference.docx` (Iteration B1+B1.5+B2). Manuell aufrufen, wenn Stile geändert werden sollen, danach normalen `build.ps1` laufen. -- `artefakte/01-lebenslauf/build/build.ps1` — PowerShell-Build-Skript (PDF + DOCX) mit 3-Sekunden-Pause bei Fehler. +- `artefakte/01-lebenslauf/build/build-reference-docx.py` — Python-Skript zum Bauen der `reference.docx` (Iterationen B1, B1.5, B2, B3). Manuell aufrufen, wenn Stile geändert werden sollen, danach normalen `build.ps1` laufen. +- `artefakte/01-lebenslauf/build/post-process-docx.py` — Python-Skript für DOCX-Post-Processing (B3.5 Listen-Bullet-Schutz). Wird automatisch von `build.ps1` als Schritt [3/3] aufgerufen. +- `artefakte/01-lebenslauf/build/build.ps1` — PowerShell-Build-Skript (PDF + DOCX + Post-Process) mit 3-Sekunden-Pause bei Fehler. - `artefakte/01-lebenslauf/output/` — erzeugte Ausgaben plus `build.log`. ### Historische Entwürfe (unter `artefakte/01-lebenslauf/entwuerfe/`)