wiki/b-reference/platform-core/document-rendering.md

226 lines
10 KiB
Markdown

<!-- status: canonical -->
<!-- lastReviewed: 2026-06-03 -->
<!-- verifiedAgainst: platform-core/modules/serviceCenter/services/serviceGeneration/ (styleDefaults.py, mainServiceGeneration.py, renderers/*) -->
# Dokumenten-Rendering & Style-System
## Ueberblick
Der **Generation-Service** (`serviceGeneration` / `mainServiceGeneration.py`) wandelt strukturierten JSON-Content in formatierte Dokumente um. Pipeline: **Markdown -> JSON -> Rendered File** (PDF, DOCX, PPTX, XLSX, HTML).
Styling wird ueber ein **4-Schichten-Modell** aufgeloest — von generischen Defaults bis zu element-spezifischen Overrides. Keine statischen Theme-Presets; stattdessen AI-getriebene kontextuelle Anpassung.
---
## Style-Resolution-Pipeline
```mermaid
flowchart TD
Default["DEFAULT_STYLE<br/>(Cursor/VS-Code-Aesthetik)"] --> Resolve["resolveStyle()<br/>deep-merge: defaults + agentStyle"]
Resolve --> AI["_enhanceStyleWithAi()<br/>AI Delta basierend auf<br/>Titel, Typ, User-Request"]
AI --> Global["Resolved Global Style<br/>(12 Sektionen)"]
Global --> Convert["_convertUnifiedStyleToInternal()<br/>Renderer-internes Format"]
Convert --> Renderer["Renderer"]
Renderer --> PerElement["Per-Element Overrides<br/>z.B. tableStyle pro Tabelle"]
PerElement --> Final["Gerendertes Element"]
```
### Schicht 1: DEFAULT_STYLE (Basis)
Datei: `styleDefaults.py`. Neutrale, moderne Basis-Aesthetik inspiriert von Cursor/VS-Code Markdown-Rendering.
| Sektion | Schluessel | Beschreibung |
|---------|-----------|-------------|
| `fonts` | `primary`, `monospace` | Calibri / Consolas |
| `colors` | `primary`, `secondary`, `accent`, `background` | GitHub-inspirierte Farbpalette (#24292e, #586069, #0366d6) |
| `documentTitle` | `sizePt`, `weight`, `color`, `align`, `spaceBeforePt`, `spaceAfterPt` | Dokumenttitel-Formatierung |
| `headings` | `h1`-`h4` mit `sizePt`, `weight`, `color`, `spaceBeforePt`, `spaceAfterPt` | Ueberschriften-Hierarchie |
| `paragraph` | `sizePt`, `lineSpacing`, `color`, `align` | Fliesstext |
| `table` | `headerBg/Fg`, `headerSizePt`, `bodySizePt`, `rowBandingEven/Odd`, `borderColor`, `borderWidthPt`, `borderStyle`, `bandingEnabled`, `cellPaddingPt` | Tabellen-Styling |
| `list` | `bulletChar`, `indentPt`, `sizePt` | Aufzaehlungen |
| `image` | `defaultWidthPt`, `maxWidthPt`, `alignment` | Bild-Defaults |
| `codeBlock` | `fontSizePt`, `background`, `borderColor` | Code-Bloecke |
| `coverPage` | `titleSizePt`, `subtitleSizePt`, `authorSizePt`, `dateSizePt`, `titleColor`, `subtitleColor` | Deckblatt |
| `caption` | `sizePt`, `color`, `italic`, `align` | Bild-/Tabellen-Beschriftungen |
| `page` | `format`, `marginsPt`, `showPageNumbers`, `headerHeight/footerHeight`, `headerLogo`, `headerText`, `footerText` | Seitenlayout |
### Schicht 2: Agent Explicit Overrides
Der Agent kann ein `style`-Objekt in `metadata.style` des JSON-Dokuments mitgeben. `resolveStyle(agentStyle)` macht ein rekursives Deep-Merge: `DEFAULT_STYLE <- agentStyle`. Nur explizit gesetzte Keys werden ueberschrieben.
### Schicht 3: AI Style Enhancement
`_enhanceStyleWithAi()` in `mainServiceGeneration.py`:
1. Empfaengt das **vollstaendige** resolved Style-Set als JSON (~920 Bytes)
2. Kontext: Dokumenttitel, Dokumenttyp, User-Request (Auszug)
3. AI liefert ein **Delta-JSON** mit nur den zu aendernden Properties
4. `deepMerge(resolvedStyle, delta)` wendet die Aenderungen an
5. Bei Fehler: graceful fallback auf den unmodifizierten Style
Beispiele fuer AI-Anpassungen:
- Rechtsschrift: Serif-Fonts, justified Paragraphs
- Finanzbericht: konservative Farben, rechtausgerichtete Zahlen-Spalten
- Marketing: kraeftige Farben, groessere Abstände
### Schicht 4: Per-Element Overrides
JSON-Content-Bloecke koennen element-spezifische Style-Overrides tragen. Aktuell unterstuetzt:
- **`tableStyle`** auf Table-Bloecken: `content.tableStyle` wird per deep-merge mit dem globalen `table`-Style zusammengefuehrt. Erlaubt pro Tabelle eigene Banding-Farben, Border-Style, Column-Alignments etc.
- **`columnAlignments`** auf Table-Bloecken: Explizite Liste (`["left", "right", "center"]`) pro Spalte.
---
## Tabellen-Rendering-Modell
### Globale Tabellen-Styles
Aus `DEFAULT_STYLE["table"]`:
| Key | Default | Beschreibung |
|-----|---------|-------------|
| `headerBg` / `headerFg` | `#f6f8fa` / `#24292e` | Header-Hintergrund und -Textfarbe |
| `headerSizePt` / `bodySizePt` | 10 / 10 | Schriftgroessen |
| `rowBandingEven` / `rowBandingOdd` | `#f6f8fa` / `#FFFFFF` | Alternierende Zeilenfarben |
| `borderColor` / `borderWidthPt` | `#e1e4e8` / 0.5 | Rahmenfarbe und -staerke |
| `borderStyle` | `"grid"` | `grid` (Vollraster), `horizontal` (nur horizontale Linien), `none` (kein Rahmen) |
| `bandingEnabled` | `true` | Aktiviert/deaktiviert Zeilenbanding |
| `cellPaddingPt` | 4 | Zellen-Innenabstand |
### Per-Table Overrides
Ein Table-Block im JSON kann ein `tableStyle`-Objekt tragen:
```json
{
"content_type": "table",
"elements": [{
"headers": ["Name", "Betrag", "Datum"],
"rows": [...],
"tableStyle": {
"borderStyle": "horizontal",
"bandingEnabled": false,
"columnAlignments": ["left", "right", "center"]
}
}]
}
```
Der Renderer mergt: `globalTableStyle <- perTableStyle` (deep-merge).
### Spaltenausrichtung
1. **Explizit:** `tableStyle.columnAlignments` (Array von `"left"` / `"center"` / `"right"`)
2. **Auto-Inferenz** (`_inferColumnAlignments` in BaseRenderer):
- Zahlenspalten (>=60% numerisch) -> `right`
- Datumsspalten (>=60% Datumsformat) -> `center`
- Sonst -> `left`
---
## Renderer-Architektur
### Basisklasse
`DocumentRendererBaseTemplate` (ABC) stellt bereit:
- `_convertUnifiedStyleToInternal(style)` — Wandelt das 12-Sektionen Unified-Style-Dict in ein flaches internes Format um
- `_inferColumnAlignments(headers, rows, tableStyle)` — Spaltenausrichtungs-Heuristik
- `_inlineRunsFromContent()` / `_inlineRunsForCell()` / `_inlineRunsForListItem()` — Inline-Run-Normalisierung
- `_lazyResolveImageBase64()` — On-Demand Bild-Aufloesung fuer Grossdokumente
### Format-Renderer
| Renderer | Bibliothek | Formate | Besonderheiten |
|----------|-----------|---------|----------------|
| `rendererPdf.py` | ReportLab | PDF | TTF-Font-Registration (`_resolveFontFamily`), Temp-File-Bilder fuer Memory-Effizienz, Emoji-Fallback (NotoEmoji) |
| `rendererDocx.py` | python-docx | DOCX | XML-basierte Tabellen-Borders, Word-Styles fuer h1-h4, Inline-Run-Formatting |
| `rendererPptx.py` | python-pptx | PPTX | Slide-Layout pro Section, RGBColor-Konvertierung, Shape-basierte Bilder |
| `rendererXlsx.py` | openpyxl | XLSX | PatternFill fuer Banding, Side/Border fuer Rahmen, Cell-Anchor fuer Bilder |
| `rendererHtml.py` | Jinja2/String | HTML | CSS-Generierung aus Unified Style, Inline-Styles |
### Font-Resolution (PDF)
`_resolveFontFamily(fontName)` in rendererPdf.py:
1. Pruefe ob TTF-Datei unter `assets/fonts/` registrierbar ist
2. Registriere bei ReportLab (`pdfmetrics.registerFont`)
3. Fallback-Kette: gewuenschter Font -> Helvetica (ReportLab Core) -> Courier
---
## AI Style Enhancement — Details
### Prompt-Struktur
```
You are a document styling expert. [...]
Document title: {docTitle}
Document type: {docType}
User request (excerpt): {userHint}
Current style (full schema):
{vollstaendiges resolvedStyle JSON}
You may adjust any property: fonts, colors, documentTitle, headings, paragraph, table, list, image, codeBlock, coverPage, caption, page.
[...]
Return ONLY a valid JSON object.
```
### Erwartete Antwort
Ein JSON-Delta-Objekt mit NUR den zu aendernden Keys:
```json
{
"fonts": {"primary": "Georgia"},
"paragraph": {"align": "justified"},
"colors": {"primary": "#1a1a2e"}
}
```
Leeres `{}` = keine Aenderungen noetig.
### Fehlerbehandlung
- Leere Antwort -> Original-Style beibehalten
- Ungültiges JSON -> Markdown-Fences strippen, `{` bis `}` extrahieren, nochmal parsen
- Parse-Fehler oder Exception -> Warning loggen, Original-Style beibehalten
- Operation Type: `DATA_GENERATE`
---
## Design-Entscheidungen
| Datum | Entscheidung | Begruendung |
|-------|-------------|-------------|
| 2026-04-29 | Kein Theme-Katalog, kein Mandate-CI-Konzept | ADR: Agent fuellt generischen Style-Block |
| 2026-06-02 | THEME_PRESETS + documentTheme temporaer eingefuehrt | A3-Reconciliation |
| 2026-06-03 | THEME_PRESETS + documentTheme entfernt, AI-Enhancement statt Presets | Zurueck zum ADR-Original; AI passt kontextbasiert an |
| 2026-06-03 | DEFAULT_STYLE: Cursor/VS-Code-Aesthetik | Moderne, neutrale Basis; GitHub-Farbpalette |
| 2026-06-03 | AI bekommt vollstaendiges Style-Set im Prompt | Alle 12 Sektionen muessen anpassbar sein |
| 2026-06-03 | Per-Table Overrides via content.tableStyle | Jede Tabelle kann eigene Styles haben |
| 2026-06-03 | Auto-Column-Alignment via _inferColumnAlignments | Zahlen rechts, Daten zentriert — ohne AI-Call |
---
## Schluessel-Dateien
| Datei | Rolle |
|-------|-------|
| `serviceGeneration/styleDefaults.py` | `DEFAULT_STYLE`, `resolveStyle()`, `deepMerge()` |
| `serviceGeneration/mainServiceGeneration.py` | `renderReport()`, `_enhanceStyleWithAi()` |
| `serviceGeneration/renderers/documentRendererBaseTemplate.py` | `BaseRenderer`, `_convertUnifiedStyleToInternal()`, `_inferColumnAlignments()` |
| `serviceGeneration/renderers/rendererPdf.py` | PDF via ReportLab |
| `serviceGeneration/renderers/rendererDocx.py` | DOCX via python-docx |
| `serviceGeneration/renderers/rendererPptx.py` | PPTX via python-pptx |
| `serviceGeneration/renderers/rendererXlsx.py` | XLSX via openpyxl |
| `serviceGeneration/renderers/rendererHtml.py` | HTML via String/Jinja |
| `serviceAgent/coreTools/_mediaTools.py` | `renderDocument` Agent-Tool |
---
## Auch wichtig
- **Grossdokumente:** Lazy File-Ref-Bilder (`_lazyResolveImageBase64`) + Temp-File-Bilder fuer PDF vermeiden OOM bei vielen eingebetteten Bildern. Siehe `c-work/4-done/2026-06-po-cleanup-neutralization-docgen.md` (A3/AC15).
- **Markdown-Renderer/Text-Renderer** ignorieren `style` (dokumentiert, kein Bug).
- **Agent-Tool `renderDocument`:** Akzeptiert optionalen `style`-Parameter im Tool-Schema; kein `documentTheme`-Parameter mehr.