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 09bc758..d4cf75f 100644 Binary files a/artefakte/01-lebenslauf/output/Lebenslauf_Dr-Ing_Thomas_Langer.docx and b/artefakte/01-lebenslauf/output/Lebenslauf_Dr-Ing_Thomas_Langer.docx differ 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 2ca9064..c896380 100644 Binary files a/artefakte/01-lebenslauf/templates/reference.docx and b/artefakte/01-lebenslauf/templates/reference.docx differ diff --git a/changelog.md b/changelog.md index 70fcb6d..d66121f 100644 --- a/changelog.md +++ b/changelog.md @@ -44,3 +44,4 @@ Chronologisches Log aller Entscheidungen und Prozessereignisse. 2026-04-25 22:43 | S06 | Session-Abschluss S06. Teilgebiet 01 Iteration A inhaltlich umgesetzt: Ausbildung als 2-Spalten-Layout via Pandoc-Multiline-Tabelle ohne Header, Template um array, calc, booktabs, longtable und providecommand real ergänzt, alle Linienbreiten auf 0pt. DOCX-Layout von Thomas visuell bestätigt (nur Default-Tabellenrahmen offen, Rahmen-Aus in Iteration B). PDF-Build mit Hotfix steht noch aus — erste Aufgabe der nächsten Session ist build.ps1 laufen lassen und das Ausbildungs-Layout im PDF prüfen. Tooling-Fix in checkpoint.ps1 abgeschlossen (Quoting-Bug, Pipe-Validierung, Whitespace-Normalisierung, Pre-flight-Checks, atomarer Rollback bei Fehler). agent-prompt.md Aktueller-Stand-Abschnitt für S07 fortgeschrieben mit verbleibender Iterationsreihenfolge B-C-D, neuen Lehren aus S06 dokumentiert (Sandbox-Reads über NTFS-Mount können stale sein, Sandbox-Pandoc 2.9 vs Thomas' Pandoc 3.x Output-Unterschiede). 2026-04-26 10:58 | S07 | PDF-Build-Fehler endgueltig behoben. S06-Hotfix (array, calc, providecommand real) loeste das Problem nicht; nach Rebuild kam unveraendert "! LaTeX Error: No counter 'none' defined." Eigentliche Ursache: Pandoc 3.x emittiert fuer unnummerierte Tabellen direkt vor begin{longtable} die Zeile def LTcaptype none, ohne den Counter none zu definieren. Pandocs eigene Default-Vorlage definiert ihn (commit d835461 in Pandoc 3.8.2.1), Custom-Templates muessen das selbst tun (siehe Pandoc-Issue 11201). Fix: eine Zeile newcounter none direkt nach providecommand real im Tabellen-Block des Templates. Sandbox-Reproduktion exakter Fehlertext ohne Fix, sauberes PDF mit Fix. Auf Thomas' System: PDF wird erzeugt, Ausbildungs-Layout im PDF visuell bestaetigt. Iteration A fuer Teilgebiet 01 damit inhaltlich abgeschlossen. Build-UX-Fix: build.ps1 ergaenzt um Start-Sleep -Seconds 3 nach jedem fehlschlagenden Build-Schritt (Pflichtdatei-Check, PDF-Build, DOCX-Build), damit die rote Fehlerzeile lesbar bleibt bevor das PowerShell-Fenster zugeht. teilgebiete/01-lebenslauf.md um beide Fixes ergaenzt (zweistufige Hotfix-Geschichte, PDF-Bestaetigung, UX-Fix). agent-prompt.md Aktueller-Stand-Abschnitt fuer S08 fortgeschrieben mit verbleibender Iterationsreihenfolge B-C-D, Hinweise auf Pandoc-Versionsunterschied praeziser formuliert. 2026-04-26 13:29 | 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. +2026-04-26 16:40 | 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). diff --git a/teilgebiete/01-lebenslauf.md b/teilgebiete/01-lebenslauf.md index 3d0b73f..0759e82 100644 --- a/teilgebiete/01-lebenslauf.md +++ b/teilgebiete/01-lebenslauf.md @@ -172,13 +172,24 @@ Die in S04 mit docx-js erstellte Version hatte strukturelle typographische Mäng - Seite 2 ff. mit Header (Name links, „Lebenslauf" rechts) und Footer (Seite n / m). - Tab-Stops „Lebenslauf" und Seitenzahl bündig am rechten Textrand. +**B3 — Schusterjungen- und Witwen-Schutz für DOCX:** + +- DocDefault `` 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/`)