Planen und geplant zu werden

How to render a PDF Character sheet
This commit is contained in:
2025-12-14 22:11:30 +01:00
parent d92fb62daa
commit a32ba236e8
3 changed files with 1574 additions and 0 deletions
+238
View File
@@ -0,0 +1,238 @@
# Aufteilung der Informationen auf dem Characterbogen
Der Characterbogen besteht aus mehreren Seiten.
- Seite 1 ist die Statistikseite in der alle Werte in ihrem Grundwert eingetraqgen werden also ohne irgendwelche Boni.
- Seite 2 ist der Spielbogen. sie enthält alle werte so wie sie für das Spiel benötigt werden Boni und Mali jedweder Art sind hier in die Werte der Fertigkeiten eingerechnet. Das Gilt auch für magische Boni für Waffen, Rüstung oder ähnliches.
- Seite 3 enhält die Liste aller gelernten Zauber und aller Zauber die dem Charakter durch Artefakte zur Verfügung stehen
- Seite 4 enthält die Ausrüstung des Abenteurers. Hier ist Platz für "Am Körper getragen", "Auf dem Wagen", "Im Behältnis XYZ"
## Allgemeine Layoutwerte
- Seitengröße A4,quer: 297×210mm
- Ränder
- oben: 6 mm
- unten: 6 mm
- links: 6 mm
- rechts: 6 mm
- Spaltenraster
- Anzahl: 4
- Breite: alle gleich
- Gutter: 4 mm
- Font
- Name: DejaVu Sans
- size
- default: 16
- small: 14
- large: 18,
- lineHeightFactor
- default: 1.2
- small: 1.1
- large: 1.3
- spacing
- vertical: 0.5 mm
- horizontal: 0.5 mm
- Boxtypen
- id: 01
- name: header
- f-Size: default
- type: Label: Wert
- id: 02
- name: stats
- f-Size: large
- type: Label: Wert
- style: boxed
- id: 03
- name: table
- f-Size: default
- type: Label: Wert
- style: nobr, shorten
Wenn anchor für Blöcke nicht angegeben ist wird als default "top" verwendet.
Wenn heightMode für Blöcke nicht angegeben ist wird als default "fixed" verwendet.
- In jedem Spaltenbereich:
- zuerst alle anchor=bottom-Blöcke von unten platzieren,
- dann alle anchor=top-Blöcke von oben,
- für heightMode=rows-Tabellen:
- maxRows = floor(availableHeight / rowHeight).
Rowheight wird wie folgt berechnet:
rowHeight_mm = fontSize_pt * lineHeightFactor * ptToMm + spacing.vertical
## Strukturierung der Seiten A4 Quer
Jede seite ist prinzipiell 4 spaltig angelegt. Die Informationen sind in Blöcken organisiert die bei Bedarf mehrere Spalten zusammen fassen können. Wenn nich anders festgelegt laufen die Informationen in den Blöcken von links oben nach rechts unten. „anders festgelegt“ bedeutet:
- anchor=bottom ist eine Ausnahme,
- ansonsten ist die Reihenfolge in der Seitendefinition die Flow-Reihenfolge.
## Seite 1 (Statistikseite)
- Block 1, Spalte 1,2,3,4, Darstellung Label: Wert
- Name des Charakters
- Name des Spielers
- Block 2, Spalte 1, Darstellung Label: Wert
- Typ, Grad
- Spezialisierung
- Block 3, Spalte 1, Darstellung Box Label: Wert
- St, Gs, Gw
- Ko, In, Zt
- Au, pA, Wk
- B, Raufen
- Block 4, Spalte 1, Darstellung Label: Wert
- "persönliche Boni für:"
- Ausdauer, Schaden, Angriff
- Abweht, Resistenz, Zaubern
- Block 5, Spalte 1,2, Darstellung Box Label: Wert
- LP-Max, AP-Max, GG, SG
- Block 6, Spalte 1,2, Darstellung Tabelle
- Tabelle Spielergebnisse
- Block 7, Spalte 2, Darstellung Label: Wert
- Geburtsdatum
- Alter Händigkeit
- Größe
- Gestalt, Gewicht
- Stand
- Heimat
- Glaube
- Besondere Merkmale
- Block 8, Spalte 3,4
- "Liste der gelernten und angeborenen Fertigkeiten"
- Block 9, Spalte 3, Darstellung Tabelle, heightMode: rows
- Tabelle Fertigkeiten
- Block 10, Spalte 3,4, Darstellung Label: Wert, anchor: bottom
- Sehen, Nachtsicht, Hören, Riechen/Schmecken, Sechster Sinn
- Block 11, Spalte 4, Darstellung Tabelle, heightMode: rows
- Fortsetzung Tabelle Fertigkeiten
## Seite 2 (Spielbogen).
- Block 1, Spalte 1,2,3,4, Darstellung Label: Wert
- Name des Charakters, Grad
- Typ, GG, SG
- Block 2, Spalte 1,2, Darstellung Box Label: Wert
- St, Gs, Gw, Ko, In,
- Zt, Au, pA, Wk, B
- Block 3, Spalte 1,2, Darstellung Label: Wert
- Abwehr, Abwehr mit Verteidigungswaffe, Resistenz, Zaubern
- Block 4, Spalte 1, Darstellung Tabelle, heightMode: rows
- Tabelle Fertigkeiten
- Block 5, Spalte 2, Darstellung Tabelle
- Tabelle ungelernte Fertigkeiten
- Block 6, Spalte 2, Darstellung Tabelle, heightMode: rows
- Fortsetzung Tabelle Fertigkeiten
- Block 7, Spalte 3,4, Darstellung Tabelle
- Tabelle LP/AP
- Block 8, Spalte 3, Darstellung Label: Wert
- "Bonus für"
- "Schaden", "Angriff", "Abwehr"
- Schaden, Angriff, Abwehr
- "Mit Rüstung", Angriff, Abwehr
- Block 9 ,Spalte 3, Darstellung Tabelle, heightMode: rows
- Tabelle Waffen
- Block 10, Spalte 3,4, Darstellung Label: Wert, anchor: bottom
- Sehen, Nachtsicht, Hören, Riechen/Schmecken, Sechster Sinn
- Block 11, Spalte 4, Darstellung Label: Wert
- "RK", rk-lp-bonus
- , mit Rüstung", B, Gw
- Block 12 ,Spalte 4, Darstellung Tabelle, heightMode: rows
- Fortsetzung Tabelle Waffen
- Block 13, Spalte 4, Darstellung Tabelle
- Tabelle PP Zauber
## Seite 3 (Zauber )
- Block 1, Spalte 1,2,3,4, Darstellung Label: Wert
- Name des Charakters, Grad
- Typ, Zaubern
- Block 2. Spalte 1,2, Darstellung Tabelle, heightMode: rows
- Tabelle Zauber
- Block 3. Spalte 3,4, Darstellung Tabelle, heightMode: rows
- Fortsetzung Tabelle Zauber
- Block 4, Spalte 3,4, Darstellung Tabelle, heightMode: rows
- Tabelle magische Gegenstände /Ausrüstung
## Seite 4 (Ausrüstung)
- Block 1, Spalte 1,2,3,4, Darstellung Label: Wert
- Name des Charakters, Grad
- Typ
- Block 2. Spalte 1,2, Darstellung Tabelle, heightMode: rows
- Tabelle Ausrüstung (Am Körper getragen)
- Block 3. Spalte 1,2, Darstellung Tabelle, heightMode: rows
- Tabelle Behälter 1 (Im Wagen)
- Block 4. Spalte 3,4, Darstellung Tabelle, heightMode: rows
- Tabelle Behälter 2
- Block 5. Spalte 3,4, Darstellung Tabelle, heightMode: rows
- Tabelle Behälter 2
## Seite 2.1 (Fortsetzung Spielbogen).
Wenn die Anzahl der Fertigkeiten oder die Anzahl der Waffen den Platz in der Liste überschreitet wird eine oder mehrere Seiten angefügt. Diese enthält:
- Block 1, Spalte 1,2,3,4, Darstellung Label: Wert
- Name des Charakters, Grad
- Typ, GG, SG
- Block 2, Spalte 1, Darstellung Tabelle, heightMode: rows
- Fortsetzung Tabelle Fertigkeiten
- Block 3, Spalte 2, Darstellung Tabelle, heightMode: rows
- Fortsetzung Tabelle Fertigkeiten
- Block 4 ,Spalte 3, Darstellung Tabelle, heightMode: rows
- Fortsetzung Tabelle Fertigkeiten
- Block 5 ,Spalte 4, Darstellung Tabelle, heightMode: rows
- Fortsetzung Tabelle Waffen
## Seite 3.1 (Forsetung Zauber)
Wenn die Anzahl der Zauber den Platz in der Liste überschreitet wird eine oder mehrere identische Seiten angefügt, Block 4 wird dann auf die von Seite 3 auf Seite 3.1 verschoben.
- Block 1, Spalte 1,2,3,4, Darstellung Label: Wert
- Name des Charakters, Grad
- Typ, Zaubern
- Block 2. Spalte 1,2, Darstellung Tabelle, heightMode: rows
- Fortsetzung Tabelle Zauber
- Block 3. Spalte 3,4, Darstellung Tabelle, heightMode: rows
- Fortsetzung Tabelle Zauber
- Block 4, Spalte 3,4, Darstellung Tabelle, heightMode: rows
- Tabelle magische Gegenstände /Ausrüstung
## Definition der Tabellen
### Tabelle Fertigkeiten
| Fertigkeit | EW | PP |
| ---------- | -- | -- |
| Fertigkeit | EW | PP |
### Tabelle Waffen
|Waffe | EW | Schaden | Nah |
| ---- | -- | -- | -- |
|Waffe | EW | Schaden | Nah |
### Tabelle PP Zauber
| Zauber klasse | PP |
| ---------- | -- |
| Fertigkeit | PP |
### Tabelle Zauber
| AP/Prozess | Zauber | Zd/Rw | Wb/Wd | Wirkung | WirkZiel/Art |
| ---- | -------- | ---- | ---- | -------- | ---- |
| AP/Prozess | Zauber | Zd/Rw | Wb/Wd | Wirkung | WirkZiel/Art |
### Tabelle magische Gegenstände
| Gegenstande | Wirkung, Inhalt, und andere Erlä#uterungen |
| -------- | -------------------- |
| Gegenstande | Wirkung, Inhalt, und andere Erlä#uterungen |
### Tabelle Spielergebnisse
|Datum | | | | | | | | | | |
|ES | | | | | | | | | | |
|EP | | | | | | | | | | |
|Geld | | | | | | | | | | |
### Tabelle LP/AP
| LP | | | | | | |
| AP | | | | | | |
@@ -0,0 +1,449 @@
## Plan: PDF-Export für Charakterbögen
**Ziel:** Charakterbögen als mehrseitige PDF-Datei exportieren mit flexiblen Templates (A4 Hochformat, A4 Querformat) und automatischem Seitenumbruch für Listen variabler Länge.
**Tech-Stack:** Go Backend (Gin), Vue 3 Frontend, GORM Datenbank
---
### Step 1: PDF-Bibliothek integrieren
**Details:**
- **Anforderungen aus dem Layout-/Template-Design:**
- Absolut positionierbare Inhalte (Koordinaten in mm/pt).
- Unterstützung für mehrere Seiten (Seitenumbrüche, Seitengröße, Ränder).
- UTF8/Unicode für deutsche Texte inkl. Umlaute (DejaVu Sans als eingebettete Schrift).
- Zeichen primitiver Formen (Linien, Rechtecke) für Tabellenraster/Boxen.
- Möglichkeit, Text an beliebigen Positionen mit einheitlicher Schrift und Größe auszugeben.
- **Technische Auswahl (geplant):**
- Einsatz einer reinen Go-PDF-Bibliothek (z.B. `gofpdf` o.ä.), die:
- Seitengröße in mm/pt setzen kann,
- Schriften laden/registrieren kann (TrueType, DejaVu Sans),
- Textausgabe an absoluten Koordinaten unterstützt.
- **Integrationskonzept:**
- Neues Package `backend/pdf`:
- Kapselt die PDF-Bibliothek.
- Stellt einfache Operationen bereit:
- `NewDocument(pageSizeMm, marginsMm)`.
- `AddPage()`.
- `SetFont(name, sizePt, bold/normal)`.
- `DrawText(xMm, yMm, text)`.
- `DrawLine(x1Mm, y1Mm, x2Mm, y2Mm)`.
- Hilfsfunktionen für Tabellenzellen (Text mit optional mehreren Zeilen).
- Versteckt Einheitenkonvertierung (`ptToMm`) und Bibliotheksdetails.
- Fonts:
- DejaVu Sans (normal, ggf. bold) als eingebettete TTF-Fonts.
- Registrierung beim Start im `pdf`-Package.
- Minimaler Prototyp:
- Funktion, die ein einfaches Test-PDF erzeugt (eine Seite, etwas Text), um Encoding/Fonts zu validieren.
---
### Step 2: Template-System aufbauen
**Details:**
- Layout- und Blockstruktur wird in `backend/doc/Characterbogen_Aufteilung.md` beschrieben und dient als Kanon.
- Ein Template (z.B. `backend/doc/template_a4_quer.yaml`) besteht aus:
- **Globaler Seitendefinition**:
- Seitengröße (z.B. A4 quer 297×210mm)
- Seitenränder (oben/unten/links/rechts)
- Spaltenraster (Anzahl, Gutter; Breite wird abgeleitet)
- Font-Konfiguration (Name, Größen, `lineHeightFactor`, Spacing)
- **Seitentypen**:
- Seite 1: Statistikseite
- Seite 2: Spielbogen
- Seite 3: Zauberseite
- Seite 4: Ausrüstung
- Seite 2.1: Fortsetzung Spielbogen (Fertigkeiten/Waffen)
- Seite 3.1: Fortsetzung Zauber (Zauber/Magische Gegenstände)
- **Blöcken pro Seite**:
- Jeder Block hat:
- `blockId` (z.B. 113 wie in `Characterbogen_Aufteilung.md`)
- `pageType` (z.B. `page1_stats`, `page2_play`, `page2_play_cont`, `page3_spells`, …)
- Spaltenstart/-spanne (`colStart`, `colSpan`), bezogen auf 4 gleich breite Spalten mit Gutter
- `blockType`:
- `labelValue` (Label: Wert)
- `boxLabelValue` (Label: Wert, mit Rahmen)
- `table`
- `boxTypeId` (01=header, 02=stats, 03=table) → liefert Fontgröße, Style (`boxed`, `nobr`, `shorten`)
- `anchor`: `"top"` (default) oder `"bottom"`
- `heightMode`:
- `"fixed"` (default): Höhe ergibt sich aus bekannter Zeilenanzahl (z.B. Spielergebnisse, LP/AP)
- `"rows"`: Anzahl Zeilen wird dynamisch aus verfügbarem Platz berechnet (Fertigkeiten, Waffen, Zauber, Ausrüstung)
- später: `binding` (welche Daten ins Feld/Tabelle kommen)
- **Boxtypen**:
- global definiert (Fontgröße, Style, Verhalten in Tabellen):
- `header` (id: 01): Label:Wert, `f-Size: default`
- `stats` (id: 02): Label:Wert, `f-Size: large`, `style: boxed`
- `table` (id: 03): Tabellenzellen, `f-Size: default`, `style: nobr, shorten` (kein Umbruch, Text wird ggf. gekürzt)
- Alle dynamischen Listen (Fertigkeiten, Waffen, Zauber, Ausrüstung) werden ausschließlich in Blöcken mit `heightMode: rows` gerendert; alle anderen Tabellen haben eine feste, aus dem Layout bekannte Zeilenanzahl.
**Section-Renderer:**
- Aufgabe des Section-Renderers:
- Aus Template + Characterdaten → konkrete Seiten mit absoluten Koordinaten (mm/pt) erzeugen.
- Pro Seite:
- Spaltenkoordinaten berechnen
- Blöcke positionieren (Top-/Bottom-Anker)
- Für `heightMode: rows` die `maxRows` aus verfügbaren Höhen bestimmen.
- Globale Layoutwerte (aus `Characterbogen_Aufteilung.md`):
- Seitengröße A4 quer: 297×210mm
- Ränder: oben/unten/links/rechts je 6mm
- Spaltenraster:
- 4 Spalten, alle gleich breit
- Gutter: 4mm
- Font:
- Name: DejaVu Sans
- `size`:
- `default`: 16pt
- `small`: 14pt
- `large`: 18pt
- `lineHeightFactor`:
- `default`: 1.2
- `small`: 1.1
- `large`: 1.3
- `spacing`:
- `vertical`: 0.5mm
- `horizontal`: 0.5mm
- Zeilenhöhe-Berechnung:
- Konstante: `ptToMm` (z.B. 1pt ≈ 0.3528mm)
- Für jede Font-Kategorie wird eine Zeilenhöhe berechnet:
- `rowHeight_mm = fontSize_pt * lineHeightFactor * ptToMm + spacing.vertical`
- Tabellen mit `style: nobr, shorten` werden so gerendert, dass jede Tabellenzeile **immer einzeilig** ist:
- Kein automatischer Zeilenumbruch
- Text ggf. hart gekürzt (`shorten`), um die konstante Zeilenhöhe zu garantieren.
- Mehrzeilige Tabellenzellen (Zauber-Tabelle, Spalten „AP/Prozess“ und „Zd/Rw“):
- Einige Tabellenspalten bestehen logisch aus **zwei Werten**, die in derselben Zelle untereinander stehen.
- Beispiel Zauber:
- Spalte „AP/Prozess“: erste Zeile = `ap`, zweite Zeile = `prozess`
- Spalte „Zd/Rw“: erste Zeile = `zd`, zweite Zeile = `rw`
- Im Template kann eine Spalte optional als „multiline“ markiert werden:
- Schema (pro Spalte):
- `multiline.linesPerCell`: Anzahl der Textzeilen in dieser Zelle (hier: `2`)
- `multiline.lines[]`: Liste von Bindings innerhalb einer Zelle, z.B.:
- `lines[0].binding = "ap"`
- `lines[1].binding = "prozess"`
- Für Tabellen, die solche Spalten verwenden (Zauber-Tabellen), gilt:
- Die effektive Zeilenhöhe dieser Tabelle wird so berechnet:
- `rowHeight_mm_multiline = multiline.linesPerCell * (fontSize_pt * lineHeightFactor * ptToMm + spacing.vertical)`
- Der Section-Renderer verwendet für diese Tabelle `rowHeight_mm_multiline` zur Berechnung von `maxRows`.
- In der Implementierung:
- Beim Rendern einer Zelle mit `multiline`:
- Text nach den in `multiline.lines[]` angegebenen Bindings aufteilen,
- jede Zeile übereinander zeichnen (gleiche X-Position, Y-Versatz pro Zeile),
- keine automatischen Zeilenumbrüche jenseits der zwei konfigurierten Zeilen.
- Block-Flow und Anker-Regeln:
- Wenn `anchor` für einen Block nicht angegeben ist → default `"top"`.
- Wenn `heightMode` nicht angegeben ist → default `"fixed"`.
- „Flow“:
- Wenn nicht anders festgelegt, laufen die Blöcke „von links oben nach rechts unten“:
- Reihenfolge in der Seitendefinition = Flow-Reihenfolge.
- „Anders festgelegt“ bedeutet:
- `anchor = "bottom"` → Block ist von unten verankert.
- In jedem Spaltenbereich pro Seite:
1. **Spaltenbereich berechnen**:
- `colTopY = pageTopMargin` (bzw. Ende der Kopfblöcke, wenn Seite das so vorsieht)
- `colBottomY = pageHeight - bottomMargin`
2. **Bottom-Blöcke platzieren**:
- Alle Blöcke mit `anchor="bottom"` in dieser Spaltenkombination ermitteln.
- Für jeden:
- Höhe bestimmen:
- `height = fixedHeight` (für Label/Box-Blöcke mit bekannter Zeilenanzahl)
- Position:
- `blockY = currentBottomY - height`
- `currentBottomY = blockY - verticalGap`
- Ergebnis: reduzierter nutzbarer Bereich:
- `maxContentBottomY = currentBottomY`
3. **Top-Blöcke platzieren**:
- Start: `cursorY = colTopY`
- Alle `anchor="top"`-Blöcke in definierter Flow-Reihenfolge:
- Für Blöcke mit `heightMode="fixed"`:
- Höhe aus bekannter Struktur (Anzahl Label:Wert-Zeilen oder Tabellenzeilen).
- `blockY = cursorY`
- `cursorY += height + verticalGap`
- Für Tabellen mit `heightMode="rows"`:
- Verfügbarer vertikaler Platz:
- `availableHeight = maxContentBottomY - cursorY`
- `rowHeight` für diesen Block aus Font/Boxtyp
- `maxRows = floor(availableHeight / rowHeight)`
- `blockY = cursorY`
- `cursorY += maxRows * rowHeight + verticalGap`
- `maxRows` wird später in der Pagination-Logik verwendet, um „wie viele Elemente dieser Liste passen in diesen Block?“ zu beantworten.
- Spezieller Fall „Block unten, Liste oben füllt Rest“:
- Beispiel Seite 1 (Statistikseite), Spalten 34:
- Block 10 (Sinne) hat `anchor="bottom"`, feste Zeilenanzahl → feste Höhe.
- Blöcke 9/11 (Fertigkeiten-Tabellen) haben `heightMode="rows"`, `anchor="top"`.
- Renderer:
- Platziert Block 10 von unten.
- Berechnet aus dem verbleibenden vertikalen Bereich die `maxRows` für 9 (Spalte 3) und 11 (Spalte 4).
- Diese `maxRows`-Werte liefern die Kapazität der ersten Statistikseite für Fertigkeiten. Alles darüber geht in spätere Seiten (Seite 2/2.1).
---
### Step 3: API-Endpoint implementieren
**Details:**
- **Endpoint-Design:**
- HTTP-GET:
- `GET /api/characters/{id}/pdf?templateId=a4_quer_char_sheet_v1`
- Antwort:
- `Content-Type: application/pdf`
- `Content-Disposition: attachment; filename="<charname>_charbogen.pdf"`
- **Handler-Ablauf:**
1. Authentifizierung/Autorisierung prüfen.
2. Charakter inkl. aller benötigten Relationen laden:
- Basis-/Statistikwerte,
- Spielwerte,
- Fertigkeiten,
- Waffen,
- Zauber,
- magische Gegenstände,
- Ausrüstung,
- Spielergebnisse.
3. Template wählen:
- `templateId` aus Query (`template_a4_quer.yaml` als Default).
- Template aus YAML in interne Structs laden/validieren.
4. Domain-Daten in ein View-Model überführen, das zu den Template-Bindings passt (z.B. `character.attributes.st.base`, Zauberfelder `ap`, `prozess`, `zd`, `rw`, `wb`, `wd` usw.).
5. Section-Renderer + Pagination-Engine aufrufen:
- `pages := RenderCharacterSheet(template, viewModel)`.
6. PDF-Dokument mit dem `pdf`-Package erzeugen:
- Für jede Seite:
- Seite hinzufügen,
- alle Blöcke mit ihren Koordinaten und Daten rendern.
7. PDF an den Client streamen.
- **Fehlerfälle:**
- Charakter nicht gefunden → 404.
- Template nicht gefunden/ungültig → 500, Logging, ggf. Fallback auf Default-Template.
- PDF-Erzeugung fehlgeschlagen → 500, Logging.
**Route registrieren:**
- In der bestehenden Gin-Router-Konfiguration:
- z.B. in `cmd/backend/main.go`:
- Gruppe `/api/characters``GET("/:id/pdf", CharacterPDFHandler)`.
---
### Step 4: Listen-Rendering mit Pagination
**Details:**
- **Datenaufbereitung:**
- Aus dem Domänenmodell werden pro Charakter logisch getrennte Listen erzeugt:
- `statsSkills` (Fertigkeiten-Grundwerte für Seite 1),
- `playSkills` (Fertigkeiten-Spielwerte für Seite 2/2.x),
- `weapons`,
- `spells`,
- `magicItems`,
- `equipmentOnBody`, `equipmentContainer1`, `equipmentContainer2`, …,
- `gameResults`.
- Diese Listen sind unabhängig von Seiten und Blöcken.
- **Template-Auswertung:**
- Template definiert:
- Seiten (`pages[]` mit `pageType`),
- Blöcke (`blocks[]` pro Seite),
- für `heightMode="rows"`: Tabellen, deren Zeilenanzahl limitiert ist.
- Der Section-Renderer berechnet:
- Für jeden `heightMode="rows"`-Block:
- `rowHeight` (inkl. `multiline.linesPerCell` bei Zauber),
- Nutzhöhe des Blocks (unter Berücksichtigung von Top-/Bottom-Ankern),
- `maxRows` = maximale Anzahl Datensätze für diesen Block.
- **Pagination-Engine:**
- Pro Liste (Fertigkeiten, Waffen, Zauber, Ausrüstung) wird eine Blockfüllreihenfolge definiert, basierend auf `Characterbogen_Aufteilung.md`:
- Fertigkeiten:
- Grundwerte: Seite 1, Block 9 → Block 11.
- Spielwerte: Seite 2, Block 4 → Block 6 → Seite 2.1 (Block 2/3/4) → weitere 2.x-Seiten.
- Waffen:
- Seite 2, Block 9 → Block 12 → Seite 2.1 (Block 5) → weitere 2.x-Seiten.
- Zauber:
- Seite 3, Block 2 → Block 3 → 3.x (Block 2/3) → weitere 3.x-Seiten.
- Magische Gegenstände:
- Seite 3, Block 4 → 3.x (Block 4) → weitere 3.x-Seiten.
- Ausrüstung:
- Seite 4, Blöcke 25 → ggf. 4.x mit gleichen Blöcken.
- Algorithmus:
1. Für jede Liste die zugehörigen Blöcke (Slots) in Reihenfolge sammeln (mit `pageType`, `blockId`, `maxRows`).
2. Über die Einträge der Liste iterieren und in diese Slots schreiben, bis `maxRows` erreicht ist.
3. Wenn alle Slots eines Seitentyps voll sind und noch Einträge existieren:
- Neue Instanz des Fortsetzungs-Seitentyps erzeugen (z.B. `page2_play_cont`).
- Die Slots dieser neuen Seite an die Füllreihenfolge anhängen.
- Ergebnis:
- Eine Liste konkreter Seiteninstanzen, bei denen jeder `heightMode="rows"`-Block bereits die passenden Datenzeilen (Schnitt der Liste) enthält.
- **Schnittstelle Renderer/Pagination:**
- Renderer erwartet:
- Seiten in Reihenfolge mit:
- `pageType`
- Liste von Blöcken mit:
- Layout-Infos (Koordinaten, Breite, Typ),
- bereits paginierte Daten (Zeilen für Tabellen).
---
### Step 5: Frontend-Integration
**Details:**
- **Download im Charakter-UI (Vue 3):**
- In der Detailansicht eines Charakters:
- Button „Charakterbogen (PDF)“.
- Klick:
- HTTP-Request auf `GET /api/characters/{id}/pdf?templateId=a4_quer_char_sheet_v1`.
- Browser-Auslösung eines Downloads (`Content-Disposition`).
- **Template-Auswahl (optional, später):**
- Backend-Endpunkt `GET /api/pdf-templates`:
- Liefert Liste der verfügbaren Templates (`id`, `name`, Ausrichtung).
- Frontend:
- Dropdown neben Download-Button (z.B. „A4 quer“, „A4 hoch“).
- Ausgewählte `templateId` wird in den PDF-Request aufgenommen.
- **Fehler-Handling im UI:**
- Bei HTTP-Fehlern:
- Kurze Meldung („PDF-Export fehlgeschlagen“).
- Kein Client-seitiges Layout:
- Frontend kennt nur `templateId`, die komplette Renderlogik liegt im Backend.
---
### Step 6: Template-Konfiguration
**Details:**
- **Ablage & Format:**
- Templates als YAML-Dateien im Repo:
- z.B. `backend/doc/template_a4_quer.yaml` als Referenztemplate für A4 quer.
- Struktur orientiert sich an:
- `template.Id`, `name`,
- `layout` (pageSizeMm, marginsMm, columns, font, ptToMm),
- `boxTypes`,
- `pages[]` mit `blocks[]`.
- **Laden und Validieren:**
- Beim Start des Backends:
- Template-Dateien aus einem definierten Verzeichnis laden.
- Validierung:
- Pflichtfelder vorhanden (Id, layout, pages, blocks).
- `boxTypeId` verweist auf existierenden `boxTypes`-Eintrag.
- Spaltenbereiche sind konsistent (keine negativen `colStart/colSpan`, max 4 Spalten).
- Gültige Templates werden in einer Registry gehalten:
- Lookup per `templateId`.
- **MVP-Einschränkungen:**
- Templates nur als Files im Repo pflegen (kein UI zum Hochladen/Ändern).
- Änderungen am Layout erfolgen durch Anpassen der YAML-Dateien und Deployment.
**In-Code-Konstanten (KISS-Prinzip):**
- `defaultTemplateId = "a4_quer_char_sheet_v1"`.
- Pfad zum Template-Verzeichnis (z.B. `./backend/doc` oder eigener Ordner).
- Fallback-Logik:
- Wenn angefragtes Template nicht gefunden wird:
- Auf `defaultTemplateId` zurückfallen (sofern gültig).
**Font-Konfiguration:**
- Zentral im `pdf`-Package:
- Registrierung der verwendeten Fonts (DejaVu Sans normal/bold).
- Mappings:
- BoxType → Fontgröße (`default/small/large`).
- Sicherstellen, dass:
- Umlaute (ä, ö, ü, ß) und Sonderzeichen korrekt dargestellt werden,
- gleiche Fontkonfiguration wie in Template/Plan (`Characterbogen_Aufteilung.md`).
---
## Implementierungs-Reihenfolge
1. **PDF-Bibliothek integrieren (Step 1):**
- `pdf`-Package bauen, Test-PDF generieren.
2. **Template-Lader und -Validator (Step 2 & 6):**
- YAML-Struktur für Templates definieren.
- `template_a4_quer.yaml` laden/prüfen.
3. **Section-Renderer (Layout-Math, Step 2):**
- Spalten-/Koordinatenberechnung, Top-/Bottom-Anker, `rowHeight`-Berechnung.
4. **Pagination-Engine (Step 4):**
- Blockfüllreihenfolgen gemäß `Characterbogen_Aufteilung.md` implementieren.
- Unit-Tests mit künstlichen Listen (viele Skills, viele Zauber, etc.).
5. **View-Model-Mapping:**
- Domänenstrukturen → Bindings im Template (Attribute, Fertigkeiten, Waffen, Zauber …).
6. **PDF-Render-Pipeline verbinden:**
- ViewModel → Pagination → Section-Renderer → `pdf`-Package.
7. **API-Endpoint & Routing (Step 3):**
- Handler implementieren, PDF streamen.
8. **Frontend-Integration (Step 5):**
- Download-Button, optional Template-Auswahl.
9. **Feintuning & Tests:**
- Layout gegen Originalbogen prüfen.
- `maxRows` ggf. feinjustieren.
- Tests aus der Testing-Checkliste durchgehen.
---
## Further Considerations
### Mehrsprachigkeit
- **Aktuell:** Templates fest auf Deutsch
- **Später:** Feldnamen aus i18n-Config laden
- **KISS:** Start mit Deutsch, i18n bei Bedarf nachrüsten
### Template-Customization
- **Option A (komplex):** Benutzer eigene Templates hochladen lassen
- **Option B (KISS):** 23 vordefinierte Templates fest im Code
- **Empfehlung:** Option B für MVP, Option A als Feature später
### Caching
- **On-the-fly (Start):** PDF bei jedem Request neu generieren
- **Mit Cache (später):** PDF cachen, Invalidierung bei Character-Update
- **KISS:** Start ohne Cache, Performance-Optimierung bei Bedarf
### Fehlerbehandlung
- Charakter nicht gefunden → 404
- PDF-Generierung fehlgeschlagen → 500 mit Fehlerlog
- Ungültiges Template → Fallback auf Default-Template
### Logging
- PDF-Export-Anfragen loggen (Character-ID, Template, User)
- Generierungs-Dauer messen für Performance-Monitoring
- Fehler detailliert loggen für Debugging
---
## Testing-Checkliste
- [ ] Charakter mit wenigen Skills (1 Seite)
- [ ] Charakter mit vielen Skills (Seitenumbruch)
- [ ] Charakter mit allen Listen gefüllt (Multi-Page)
- [ ] A4-Quer-Template (`template_a4_quer.yaml`)
- [ ] (später) A4-Hoch-Template
- [ ] Ungültiges Template (Fallback zu Default)
- [ ] Charakter nicht gefunden (404)
- [ ] Nicht authentifiziert (401)
- [ ] Download-Filename korrekt
- [ ] PDF öffnet korrekt in verschiedenen PDF-Readern
- [ ] Umlaute (ä, ö, ü, ß) korrekt dargestellt
- ...
---
## Dateien zum Erstellen/Ändern
- `backend/doc/Characterbogen_Aufteilung.md` (Layout-Referenz)
- `backend/doc/template_a4_quer.yaml` (konkretes Template)
- `backend/pdf/*` (PDF-Wrapper)
- `backend/pdfrender/*` oder ähnliches (Section-Renderer + Pagination)
- API-Handler im Backend (z.B. `backend/api/character_pdf.go`)
- Frontend-Komponenten für Download/Template-Auswahl
+887
View File
@@ -0,0 +1,887 @@
template:
Id: a4_quer_char_sheet_v1
name: "A4 Querformat Charakterbogen V1"
layout:
pageSizeMm:
width: 297
height: 210
marginsMm:
top: 6
bottom: 6
left: 6
right: 6
columns:
count: 4
equalWidth: true
gutterMm: 4
font:
name: "DejaVu Sans"
sizePt:
default: 16
small: 14
large: 18
lineHeightFactor:
default: 1.2
small: 1.1
large: 1.3
spacingMm:
vertical: 0.5
horizontal: 0.5
ptToMm: 0.3528
boxTypes:
- id: 01
name: header
type: labelValue
fontSize: default
style: {}
- id: 02
name: stats
type: labelValue
fontSize: large
style:
boxed: true
- id: 03
name: table
type: table
fontSize: default
style:
nobr: true
shorten: true
pages:
# ---------------------------------------------------------
# Seite 1 Statistikseite
# ---------------------------------------------------------
- pageType: page1_stats
blocks:
- blockId: 1
colStart: 1
colSpan: 4
boxTypeId: 01
anchor: top
heightMode: fixed
type: labelValue
binding:
rows:
- label: "Name des Charakters"
binding: "" # TODO
- label: "Name des Spielers"
binding: "" # TODO
- blockId: 2
colStart: 1
colSpan: 1
boxTypeId: 01
anchor: top
heightMode: fixed
type: labelValue
binding:
rows:
- label: "Typ, Grad"
binding: "" # TODO (z.B. character.type, character.level)
- label: "Spezialisierung"
binding: "" # TODO
- blockId: 3
colStart: 1
colSpan: 1
boxTypeId: 02 # stats, Box Label:Wert
anchor: top
heightMode: fixed
type: labelValue
binding:
rows:
- # Zeile 1
- label: "St"
binding: "character.attributes.st.base" # Stärke
- label: "Gs"
binding: "character.attributes.gs.base" # Geschicklichkeit
- label: "Gw"
binding: "character.attributes.gw.base" # Gewandheit
- # Zeile 2
- label: "Ko"
binding: "character.attributes.ko.base" # Konstitution
- label: "In"
binding: "character.attributes.in.base" # Intelligenz
- label: "Zt"
binding: "character.attributes.zt.base" # Zaubertalent
- # Zeile 3
- label: "Au"
binding: "character.attributes.au.base" # Ausdauer
- label: "pA"
binding: "character.attributes.pa.base" # persönliche Ausstrahlung
- label: "Wk"
binding: "character.attributes.wk.base" # Willenskraft
- # Zeile 4
- label: "B"
binding: "character.attributes.b.base" # Bewegungsweite / Basis-Bewegung
- label: "Raufen"
binding: "character.skills.raufen.base" # Grundwert Raufen (als Fertigkeit)
- blockId: 4
colStart: 1
colSpan: 1
boxTypeId: 01
anchor: top
heightMode: fixed
type: labelValue
binding:
rows:
- label: "persönliche Boni für:"
binding: "" # rein beschreibend
- label: "Ausdauer"
binding: "" # TODO
- label: "Schaden"
binding: "" # TODO
- label: "Angriff"
binding: "" # TODO
- label: "Abwehr"
binding: "" # TODO
- label: "Resistenz"
binding: "" # TODO
- label: "Zaubern"
binding: "" # TODO
- blockId: 5
colStart: 1
colSpan: 2
boxTypeId: 02
anchor: top
heightMode: fixed
type: labelValue
binding:
rows:
- label: "LP-Max"
binding: "" # TODO
- label: "AP-Max"
binding: "" # TODO
- label: "GG"
binding: "" # TODO
- label: "SG"
binding: "" # TODO
- blockId: 6
colStart: 1
colSpan: 2
boxTypeId: 03
anchor: top
heightMode: fixed
type: table
table:
columns:
- key: "date"
label: "Datum"
- key: "es"
label: "ES"
- key: "ep"
label: "EP"
- key: "geld"
label: "Geld"
- key: "c1"
label: ""
- key: "c2"
label: ""
- key: "c3"
label: ""
- key: "c4"
label: ""
- key: "c5"
label: ""
- key: "c6"
label: ""
- key: "c7"
label: ""
binding:
list: "" # TODO (z.B. character.gameResults)
- blockId: 7
colStart: 2
colSpan: 1
boxTypeId: 01
anchor: top
heightMode: fixed
type: labelValue
binding:
rows:
- label: "Geburtsdatum"
binding: "" # TODO
- label: "Alter / Händigkeit"
binding: "" # TODO
- label: "Größe"
binding: "" # TODO
- label: "Gestalt / Gewicht"
binding: "" # TODO
- label: "Stand"
binding: "" # TODO
- label: "Heimat"
binding: "" # TODO
- label: "Glaube"
binding: "" # TODO
- label: "Besondere Merkmale"
binding: "" # TODO
- blockId: 8
colStart: 3
colSpan: 2
boxTypeId: 01
anchor: top
heightMode: fixed
type: staticText
text: "Liste der gelernten und angeborenen Fertigkeiten"
- blockId: 9
colStart: 3
colSpan: 1
boxTypeId: 03
anchor: top
heightMode: rows
type: table
table:
columns:
- key: "name"
label: "Fertigkeit"
- key: "ew"
label: "EW"
- key: "pp"
label: "PP"
style:
backgroundColor: "#fff0b3" # hellgelb als Beispiel
binding:
list: "" # TODO (z.B. character.skills.stats)
- blockId: 10
colStart: 3
colSpan: 2
boxTypeId: 01
anchor: bottom
heightMode: fixed
type: labelValue
binding:
rows:
- label: "Sehen"
binding: "" # TODO
- label: "Nachtsicht"
binding: "" # TODO
- label: "Hören"
binding: "" # TODO
- label: "Riechen/Schmecken"
binding: "" # TODO
- label: "Sechster Sinn"
binding: "" # TODO
- blockId: 11
colStart: 4
colSpan: 1
boxTypeId: 03
anchor: top
heightMode: rows
type: table
table:
columns:
- key: "name"
label: "Fertigkeit"
- key: "ew"
label: "EW"
- key: "pp"
label: "PP"
style:
backgroundColor: "#fff0b3" # hellgelb als Beispiel
binding:
list: "" # TODO (Fortsetzung Statistik-Fertigkeiten)
# ---------------------------------------------------------
# Seite 2 Spielbogen
# ---------------------------------------------------------
- pageType: page2_play
blocks:
- blockId: 1
colStart: 1
colSpan: 4
boxTypeId: 01
anchor: top
heightMode: fixed
type: labelValue
binding:
rows:
- label: "Name des Charakters, Grad"
binding: "" # TODO
- label: "Typ, GG, SG"
binding: "" # TODO
- blockId: 2
colStart: 1
colSpan: 2
boxTypeId: 02
anchor: top
heightMode: fixed
type: labelValue
binding: {} # St, Gs, Gw, Ko, In, Zt, Au, pA, Wk, B TODO
- blockId: 3
colStart: 1
colSpan: 2
boxTypeId: 01
anchor: top
heightMode: fixed
type: labelValue
binding: {} # Abwehr / Resistenz / Zaubern TODO
- blockId: 4
colStart: 1
colSpan: 1
boxTypeId: 03
anchor: top
heightMode: rows
type: table
table:
columns:
- key: "name"
label: "Fertigkeit"
- key: "ew"
label: "EW"
- key: "pp"
label: "PP"
style:
backgroundColor: "#fff0b3" # hellgelb als Beispiel
binding:
list: "" # TODO (Spiel-Fertigkeiten)
- blockId: 5
colStart: 2
colSpan: 1
boxTypeId: 03
anchor: top
heightMode: fixed
type: table
table:
columns:
- key: "name"
label: "Fertigkeit"
- key: "ew"
label: "EW"
binding:
list: "" # TODO (ungelernte Fertigkeiten)
- blockId: 6
colStart: 2
colSpan: 1
boxTypeId: 03
anchor: top
heightMode: rows
type: table
table:
columns:
- key: "name"
label: "Fertigkeit"
- key: "ew"
label: "EW"
- key: "pp"
label: "PP"
style:
backgroundColor: "#fff0b3" # hellgelb als Beispiel
binding:
list: "" # TODO (Fortsetzung Spiel-Fertigkeiten)
- blockId: 7
colStart: 3
colSpan: 2
boxTypeId: 03
anchor: top
heightMode: fixed
type: table
table:
columns:
- key: "lp"
label: "LP"
style:
backgroundColor: "#fff0b3" # hellgelb als Beispiel
- key: "c1"
label: ""
- key: "c2"
label: ""
- key: "c3"
label: ""
- key: "c4"
label: ""
- key: "c5"
label: ""
- key: "c6"
label: ""
binding:
list: "" # TODO (LP/AP-Tabelle)
- blockId: 8
colStart: 3
colSpan: 1
boxTypeId: 01
anchor: top
heightMode: fixed
type: labelValue
binding: {} # Bonus für Schaden/Angriff/Abwehr TODO
- blockId: 9
colStart: 3
colSpan: 1
boxTypeId: 03
anchor: top
heightMode: rows
type: table
table:
columns:
- key: "name"
label: "Waffe"
- key: "ew"
label: "EW"
- key: "damage"
label: "Schaden"
- key: "range"
label: "Nah"
binding:
list: "" # TODO (Waffen)
- blockId: 10
colStart: 3
colSpan: 2
boxTypeId: 01
anchor: bottom
heightMode: fixed
type: labelValue
binding: {} # Sinne (wie Seite 1 Block 10) TODO
- blockId: 11
colStart: 4
colSpan: 1
boxTypeId: 01
anchor: top
heightMode: fixed
type: labelValue
binding: {} # RK / mit Rüstung, B, Gw TODO
- blockId: 12
colStart: 4
colSpan: 1
boxTypeId: 03
anchor: top
heightMode: rows
type: table
table:
columns:
- key: "name"
label: "Waffe"
- key: "ew"
label: "EW"
- key: "damage"
label: "Schaden"
- key: "range"
label: "Nah"
binding:
list: "" # TODO (Fortsetzung Waffen)
- blockId: 13
colStart: 4
colSpan: 1
boxTypeId: 03
anchor: top
heightMode: fixed
type: table
table:
columns:
- key: "spellClass"
label: "Zauberklasse"
- key: "pp"
label: "PP"
style:
backgroundColor: "#fff0b3" # hellgelb als Beispiel
binding:
list: "" # TODO (PP-Zauber)
# ---------------------------------------------------------
# Seite 3 Zauber
# ---------------------------------------------------------
- pageType: page3_spells
blocks:
- blockId: 1
colStart: 1
colSpan: 4
boxTypeId: 01
anchor: top
heightMode: fixed
type: labelValue
binding: {} # Name/Grad/Typ/Zaubern TODO
- blockId: 2
colStart: 1
colSpan: 2
boxTypeId: 03
anchor: top
heightMode: rows
type: table
table:
columns:
- key: "ap_prozess"
label: "AP/Prozess"
multiline:
linesPerCell: 2
lines:
- binding: "ap" # erste Zeile: AP
- binding: "prozess" # zweite Zeile: Prozess
- key: "name"
label: "Zauber"
- key: "zd_rw"
label: "Zauberdauer/Reichweite"
multiline:
linesPerCell: 2
lines:
- binding: "zd" # erste Zeile: Zd
- binding: "rw" # zweite Zeile: Rw
- key: "wb_wd"
label: "Wirkungsbereich/Wirkungsdauer"
multiline:
linesPerCell: 2
lines:
- binding: "wb" # erste Zeile: Wb
- binding: "wd" # zweite Zeile: Wd
- key: "effect"
label: "Wirkung"
- key: "target"
label: "WirkZiel/Art"
multiline:
linesPerCell: 2
lines:
- binding: "Wirkziel" # erste Zeile: Wirkziel
- binding: "Art" # zweite Zeile: Art
binding:
list: "" # TODO (Zauber)
- blockId: 3
colStart: 3
colSpan: 2
boxTypeId: 03
anchor: top
heightMode: rows
type: table
table:
columns:
- key: "ap_prozess"
label: "AP/Prozess"
multiline:
linesPerCell: 2
lines:
- binding: "ap" # erste Zeile: AP
- binding: "prozess" # zweite Zeile: Prozess
- key: "name"
label: "Zauber"
- key: "zd_rw"
label: "Zauberdauer/Reichweite"
multiline:
linesPerCell: 2
lines:
- binding: "zd" # erste Zeile: Zd
- binding: "rw" # zweite Zeile: Rw
- key: "wb_wd"
label: "Wirkungsbereich/Wirkungsdauer"
multiline:
linesPerCell: 2
lines:
- binding: "wb" # erste Zeile: Wb
- binding: "wd" # zweite Zeile: Wd
- key: "effect"
label: "Wirkung"
- key: "target"
label: "WirkZiel/Art"
multiline:
linesPerCell: 2
lines:
- binding: "Wirkziel" # erste Zeile: Wirkziel
- binding: "Art"
binding:
list: "" # TODO (Fortsetzung Zauber)
- blockId: 4
colStart: 3
colSpan: 2
boxTypeId: 03
anchor: top
heightMode: rows
type: table
table:
columns:
- key: "item"
label: "Gegenstand"
- key: "description"
label: "Wirkung, Inhalt, Erläuterungen"
binding:
list: "" # TODO (magische Gegenstände/Ausrüstung)
# ---------------------------------------------------------
# Seite 4 Ausrüstung
# ---------------------------------------------------------
- pageType: page4_equipment
blocks:
- blockId: 1
colStart: 1
colSpan: 4
boxTypeId: 01
anchor: top
heightMode: fixed
type: labelValue
binding: {} # Name/Grad/Typ TODO
- blockId: 2
colStart: 1
colSpan: 2
boxTypeId: 03
anchor: top
heightMode: rows
type: table
table:
columns:
- key: "item"
label: "Gegenstand"
- key: "description"
label: "Wirkung / Notizen"
binding:
list: "" # TODO (Ausrüstung am Körper)
- blockId: 3
colStart: 1
colSpan: 2
boxTypeId: 03
anchor: top
heightMode: rows
type: table
table:
columns:
- key: "item"
label: "Gegenstand"
- key: "description"
label: "Wirkung / Notizen"
binding:
list: "" # TODO (Behälter 1 Im Wagen)
- blockId: 4
colStart: 3
colSpan: 2
boxTypeId: 03
anchor: top
heightMode: rows
type: table
table:
columns:
- key: "item"
label: "Gegenstand"
- key: "description"
label: "Wirkung / Notizen"
binding:
list: "" # TODO (Behälter 2)
- blockId: 5
colStart: 3
colSpan: 2
boxTypeId: 03
anchor: top
heightMode: rows
type: table
table:
columns:
- key: "item"
label: "Gegenstand"
- key: "description"
label: "Wirkung / Notizen"
binding:
list: "" # TODO (weiterer Behälter)
# ---------------------------------------------------------
# Seite 2.1 ff. Fortsetzung Spielbogen (Fertigkeiten/Waffen)
# ---------------------------------------------------------
- pageType: page2_play_cont
repeatable: true
blocks:
- blockId: 1
colStart: 1
colSpan: 4
boxTypeId: 01
anchor: top
heightMode: fixed
type: labelValue
binding: {} # Kopf wie Seite 2 TODO
- blockId: 2
colStart: 1
colSpan: 1
boxTypeId: 03
anchor: top
heightMode: rows
type: table
table:
columns:
- key: "name"
label: "Fertigkeit"
- key: "ew"
label: "EW"
- key: "pp"
label: "PP"
style:
backgroundColor: "#fff0b3" # hellgelb als Beispiel
binding:
list: "" # TODO (Fortsetzung Spiel-Fertigkeiten)
- blockId: 3
colStart: 2
colSpan: 1
boxTypeId: 03
anchor: top
heightMode: rows
type: table
table:
columns:
- key: "name"
label: "Fertigkeit"
- key: "ew"
label: "EW"
- key: "pp"
label: "PP"
style:
backgroundColor: "#fff0b3" # hellgelb als Beispiel
binding:
list: "" # TODO (Fortsetzung Spiel-Fertigkeiten)
- blockId: 4
colStart: 3
colSpan: 1
boxTypeId: 03
anchor: top
heightMode: rows
type: table
table:
columns:
- key: "name"
label: "Fertigkeit"
- key: "ew"
label: "EW"
- key: "pp"
label: "PP"
style:
backgroundColor: "#fff0b3" # hellgelb als Beispiel
binding:
list: "" # TODO (Fortsetzung Spiel-Fertigkeiten)
- blockId: 5
colStart: 4
colSpan: 1
boxTypeId: 03
anchor: top
heightMode: rows
type: table
table:
columns:
- key: "name"
label: "Waffe"
- key: "ew"
label: "EW"
- key: "damage"
label: "Schaden"
- key: "range"
label: "Nah"
binding:
list: "" # TODO (Fortsetzung Waffen)
# ---------------------------------------------------------
# Seite 3.1 ff. Fortsetzung Zauber
# ---------------------------------------------------------
- pageType: page3_spells_cont
repeatable: true
blocks:
- blockId: 1
colStart: 1
colSpan: 4
boxTypeId: 01
anchor: top
heightMode: fixed
type: labelValue
binding: {} # Kopf wie Seite 3 TODO
- blockId: 2
colStart: 1
colSpan: 2
boxTypeId: 03
anchor: top
heightMode: rows
type: table
table:
columns:
- key: "ap_prozess"
label: "AP/Prozess"
multiline:
linesPerCell: 2
lines:
- binding: "ap"
- binding: "prozess"
- key: "name"
label: "Zauber"
- key: "zd_rw"
label: "Zd/Rw"
multiline:
linesPerCell: 2
lines:
- binding: "zd"
- binding: "rw"
- key: "wbWd"
label: "Wb/Wd"
- key: "effect"
label: "Wirkung"
- key: "target"
label: "WirkZiel/Art"
binding:
list: "" # TODO (Fortsetzung Zauber)
- blockId: 3
colStart: 3
colSpan: 2
boxTypeId: 03
anchor: top
heightMode: rows
type: table
table:
columns:
- key: "ap_prozess"
label: "AP/Prozess"
multiline:
linesPerCell: 2
lines:
- binding: "ap"
- binding: "prozess"
- key: "name"
label: "Zauber"
- key: "zd_rw"
label: "Zd/Rw"
multiline:
linesPerCell: 2
lines:
- binding: "zd"
- binding: "rw"
- key: "wbWd"
label: "Wb/Wd"
- key: "effect"
label: "Wirkung"
- key: "target"
label: "WirkZiel/Art"
binding:
list: "" # TODO (Fortsetzung Zauber)
- blockId: 4
colStart: 3
colSpan: 2
boxTypeId: 03
anchor: top
heightMode: rows
type: table
table:
columns:
- key: "item"
label: "Gegenstand"
- key: "description"
label: "Wirkung, Inhalt, Erläuterungen"
binding:
list: "" # TODO (magische Gegenstände/Ausrüstung)