Files
LehrerApp/LehrerApp.Templating/README.md
T
admin c6efdab9ef
CI / build-and-test (push) Canceled after 0s
TemplateDesigner: SystemVars
2026-08-31 23:47:48 +02:00

240 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# LehrerApp Templating
## Dynamische Zeichenflächen für externe Apps
Externe `ITemplateDataProvider` können einen Platzhalter vom Typ `Drawing` mit einem
`DrawingValue` befüllen. Dabei wird kein QuestPDF-/Skia-Canvas nach außen gegeben. Die portable
Form besteht stattdessen aus einer geprüften, serialisierbaren Befehlsliste:
```csharp
var drawing = new DrawingValue(
[
new DrawRectangle(0, 0, 160, 24, "#2563EB", 0.8f, "#EFF6FF"),
new DrawString(4, 4, "Dynamischer Bericht", 11, "#1E3A8A", Bold: true),
new MoveTo(4, 19),
new LineTo(156, 19, "#93C5FD", 0.6f),
new DrawImage(120, 2, 30, 18, pngBytes, "image/png")
],
ContentHeight: 24);
```
Koordinaten und Längen verwenden die Einheit des Layouts; `DrawString.FontSize` wird wie bei
`TEXT` in Punkt angegeben. Unterstützt werden `DrawString`, `MoveTo`, `LineTo`, `DrawLine`,
`DrawRectangle` und `DrawImage` (PNG/JPEG). Die Befehle gelangen über den normalen
`ITemplateDataProvider`, beispielsweise als `values["ExternerBericht"] = drawing`.
`DrawStringEx(x, y, height, width, ...)` ergänzt eine geclippte Textbox mit `AlignLeft`,
`AlignCenter` oder `AlignRight`. Farbe und Schriftfamilie können bei beiden Textbefehlen gesetzt
werden. Ohne Schriftangabe wird `sans-serif` verwendet. Für portable Schriften registriert die
integrierende App TTF-/OTF-Daten einmal vor dem Rendern:
```csharp
DrawingFontRegistry.RegisterFont("MeineSchulschrift", fontBytes);
canvas.DrawStringEx(0, 0, 12, context.Width, "Zentrierte Überschrift",
DrawingTextAlignment.AlignCenter, 11, "MeineSchulschrift", "#1E3A8A", bold: true);
```
Explizite Zeilenumbrüche werden berücksichtigt; Text außerhalb von `height`/`width` wird
abgeschnitten. Eine automatische Worttrennung findet in dieser elementaren Zeichenfunktion nicht
statt.
Im Layout stehen zwei Varianten zur Verfügung:
```text
DRAWBOX 20 40 170 80 $ExternerBericht
FLOWDRAWBOX 20 40 170 237 $LangesProtokoll
```
`DRAWBOX` ist ein fester, geclippter Viewport. Inhalte außerhalb seiner Breite oder Höhe werden
nicht angezeigt. `FLOWDRAWBOX` zerlegt den vertikalen Zeichenraum anhand von `ContentHeight` in
gleich hohe Seitenfenster und setzt ihn auf Folgeseiten fort. Wie bei `FLOWBOX` darf ein Layout
höchstens ein fließendes Element enthalten; bei einem eigenen Folgeseitenlayout müssen Typ,
Position und Größe übereinstimmen.
Ein Vorlagenpaket kann keinen Callback und keinen Typ aus einer fremden Assembly einschleusen.
Farben, Zahlen, Bildformate, Bildgröße und Gesamtzahl der portablen Befehle werden validiert.
Damit bleibt die paketfähige Schnittstelle deterministisch. Ein `PagedDrawingValue` ist dagegen
ein ausdrücklich vom vertrauenswürdigen In-Process-`ITemplateDataProvider` übergebener Delegate.
Für umfangreiche In-Process-Integrationen gibt es zusätzlich den klassischen seitenweisen
Callback `PagedDrawingValue`. Er wird vor QuestPDFs Layout vollständig in deklarative Seitenlisten
aufgezeichnet und daher nicht durch interne Layoutdurchläufe mehrfach ausgeführt:
```csharp
var value = new PagedDrawingValue(context =>
{
var nextRow = context.State is int row ? row : 0;
// Nur vollständige Strukturen zeichnen, die noch in die zugewiesene Box passen.
while (nextRow < rows.Count && PasstNochVollstaendig(rows[nextRow], context))
ZeichneZeile(context.Canvas, rows[nextRow++]);
context.State = nextRow;
return nextRow == rows.Count; // true = fertig, false = weitere FLOWDRAWBOX
}, InitialState: 0);
```
`DrawingPageContext` stellt `Width`, `Height`, `PageNumber`, `Canvas` und ein über alle Aufrufe
weitergereichtes `State`-Objekt bereit. So kann der Zeichner Tabellenzeilen, Diagrammgruppen oder
andere unteilbare Strukturen bewusst auf die nächste Seite verschieben. Ein `DRAWBOX`-Callback
wird genau einmal aufgerufen; bei `FLOWDRAWBOX` fordert `false` eine weitere Seite an. `MaxPages`
verhindert Endlosschleifen. Delegates funktionieren nur innerhalb desselben .NET-Prozesses;
prozessübergreifend bleibt `DrawingValue` die Übergabeform.
## PDF-Import im TemplateDesigner
Der Menüpunkt **Einfügen → PDF als Vorlage importieren** rekonstruiert einseitige PDF-Vorlagen.
Der präzisere Modus verwendet ein leeres Template zusammen mit einem ausgefüllten Beispiel und
ermittelt variable Textbereiche über einen toleranten Geometrie-Diff. Mit nur einem PDF werden
Datum, Zahlen und typische Adressbereiche lokal heuristisch vorerkannt.
PdfPig extrahiert Text, Bounding-Box, Schriftgröße und verfügbare Schriftmerkmale. Die optionale
KI-Klassifikation erhält ausschließlich diese strukturierte Zwischenrepräsentation und darf nur
Placeholder-Namen, Typ, Gruppierung und Konfidenz liefern. Koordinaten werden nicht an die KI
delegiert. Das gerasterte PDF bleibt als Hintergrund erhalten; erkannte variable Bereiche werden
deterministisch mit einem weißen Asset maskiert und anschließend als `TEXT` oder `TEXTBOX`
eingefügt. Der Nutzer prüft alle Vorschläge im Importdialog und muss die Übernahme ausdrücklich
bestätigen. Das Paket wird dabei noch nicht gespeichert.
Die serverseitige Klassifikation liegt in `ai-backend/pdf-template.php` und verwendet denselben
Login-, Bearer-Token-, Guthaben- und Abrechnungsmechanismus wie die übrigen KI-Funktionen. Das
Passwort wird vom eigenständigen Designer nicht gespeichert. Tabellen-/Chart-Erkennung und die
automatische Rekonstruktion mehrseitiger Vorlagen sind bewusst nicht Teil von v1.
## Mehrseitiger Fließtext
`TEXTBOX` bleibt ein absolut positionierter Bereich mit fester Höhe. Für Texte unbekannter Länge
steht `FLOWBOX` mit derselben Syntax zur Verfügung:
```text
PAGE 210 297 mm
FLOWBOX 20 45 170 232 $Klassenbucheintraege size=11
```
Der Inhalt wird innerhalb dieses Bereichs umbrochen und bei Bedarf auf beliebig vielen Seiten
fortgesetzt. Pro Layout ist höchstens eine `FLOWBOX` zulässig. Das optionale Manifestfeld
`continuationLayoutFile` verweist auf ein zweites Layout im Paket, das ab Seite 2 verwendet wird.
Damit können Folgeseiten beispielsweise einen kleineren Briefkopf oder einen eigenen Hintergrund
haben. Haupt- und Folgeseitenlayout müssen dieselbe Seitengröße besitzen; ihre `FLOWBOX` muss aus
technischen Gründen dieselbe Position und Größe haben. Ohne Folgeseitenlayout werden die statischen
Elemente der ersten Seite auf jeder erzeugten Seite wiederholt.
## Systemvariablen
Textinhalte können drei vom Renderer bereitgestellte Variablen verwenden. Sie werden nicht im
Manifest deklariert und nicht vom `ITemplateDataProvider` geliefert:
- `$$today` aktuelles lokales Datum im deutschen Kurzformat
- `$$curPage` aktuelle Seitenzahl
- `$$maxPageNum` Gesamtzahl der Seiten
Sie können allein oder innerhalb eines Literals stehen, beispielsweise:
```text
TEXT 20 10 "Stand: $$today" size=9
TEXT 145 285 "Seite $$curPage von $$maxPageNum" size=9 align=right
```
QuestPDF löst aktuelle und gesamte Seitenzahl während der Dokumenterzeugung auf. Dafür ist kein
zusätzlicher Renderdurchlauf durch die Anwendung erforderlich. Die Variablen funktionieren auch in
konstanten Rich-Text-Platzhaltern.
## Konstante Platzhalter und Hervorhebung
Ein Platzhalter kann seinen Wert vollständig im Vorlagenpaket tragen. `IsConstant=true` bewirkt,
dass `ConstantValue` beim Rendern immer verwendet wird; ein gleichnamiger Wert aus dem externen
`ITemplateDataProvider` wird bewusst ignoriert. Damit eignen sich Konstanten besonders für lange
Textbausteine in `TEXTBOX`, rechtliche Hinweise oder wiederkehrende Fußtexte.
```csharp
new PlaceholderDefinition(
"Datenschutzhinweis",
PlaceholderType.Multiline,
IsConstant: true,
ConstantValue: "Dieser längere Text wird im Paket gespeichert.",
Bold: true,
Italic: false,
Underline: false);
```
Konstante Werte werden für `Text`, `Multiline`, `Date` und `Number` unterstützt. Die Eigenschaften
`Bold`, `Italic` und `Underline` wirken, wenn der Platzhalter direkt von einem `TEXT`- oder
`TEXTBOX`-Element referenziert wird. In der Layout-DSL kann Unterstreichung außerdem direkt mit
`underline=true` gesetzt werden.
Konstante Text- und Multiline-Werte unterstützen zusätzlich abschnittsweise Hervorhebung und
eingebettete externe Platzhalter:
```text
Sehr geehrte Familie [b]${Student.LastName}[/b],
bitte geben Sie die [u]unterschriebene Erklärung[/u] bis [i]Freitag[/i] zurück.
```
Unterstützt werden `[b]…[/b]`, `[i]…[/i]` und `[u]…[/u]`, auch verschachtelt. Die Klammerform
`${Name}` ist in Fließtext vorzuziehen; `${Datum|dd.MM.yyyy}` erlaubt zusätzlich ein Format.
Eingebettete Platzhalter müssen im Manifest als externe Platzhalter deklariert sein. Ihre gelieferten
Werte werden immer als reiner Text behandelt und können deshalb kein Markup einschleusen. Mit
`\[`, `\$` und `\\` lassen sich die Steuerzeichen wörtlich ausgeben.
## Freie Paketmetadaten
Jedes neu gespeicherte `.lavorlage`-Paket enthält eine lesbare `metadata.txt`. Pro Zeile steht ein
frei wählbares Schlüssel-Wert-Paar; leere Zeilen und mit `#` beginnende Kommentare werden beim
Einlesen ignoriert:
```text
language=de-DE
report-type=parent-letter
school-year=2026/27
```
Werte dürfen ein weiteres `=` enthalten. Schlüssel sind ohne Beachtung der Groß-/Kleinschreibung
eindeutig und Werte bleiben einzeilig. Der Loader stellt sie der Anwendung direkt über
`loadedTemplate.Manifest.Metadata` zur Verfügung. Bestehende Pakete ohne `metadata.txt` werden
weiterhin mit einer leeren Metadatensammlung geladen. Die empfohlenen Standardschlüssel stehen
zusätzlich als `TemplateMetadataKeys.Language` und `TemplateMetadataKeys.ReportType` bereit.
## Bildskalierung
`IMG` unterstützt neben dem festen Begrenzungsrahmen eine optionale prozentuale Skalierung:
```text
IMG logo.png 15 15 30 12 scale=50%
```
Der Rahmen von `30 × 12` Layout-Einheiten wird dabei auf `15 × 6` skaliert. `x` und `y`
bleiben unverändert. Das Bild wird mit erhaltenem Seitenverhältnis in diesen Rahmen eingepasst.
Ohne `scale` gilt wie bisher `100%`. Zulässig sind Werte größer als `0` bis einschließlich
`1000%`.
## Wiederverwendbare Ausgangsvorlagen
Der TemplateDesigner verwaltet lokale Ausgangsvorlagen im Benutzerprofil. Eine Ausgangsvorlage
ist weiterhin ein normales `.lavorlage`-Paket und kann deshalb importiert oder exportiert werden.
- **Bearbeiten** öffnet die Ausgangsvorlage mit ihrer stabilen ID. Erneutes Speichern aktualisiert sie.
- **Als neues Projekt** kopiert Layout, Platzhalter und sämtliche Assets, vergibt aber eine neue ID.
- **Duplizieren** erzeugt eine weitere unabhängige Ausgangsvorlage.
- **Vorschau** rendert die Vorlage mit typgerechten Beispieldaten, ohne das aktuelle Projekt zu ändern.
Die Bibliothek liegt unter `LehrerApp/TemplateDesigner/starter-templates` im plattformspezifischen
Anwendungsdatenverzeichnis und wird nicht in das LehrerApp-Repository oder Release eingebettet.
## Visueller Koordinateneditor
Die QuestPDF-Vorschau dient gleichzeitig als maßstabsgetreue Zeichenfläche. Der Designer bildet
das tatsächlich sichtbare Seitenrechteck unabhängig von Zoom und freien Rändern auf die `PAGE`-
Koordinaten ab:
```text
dslX = (mausX - seitenrandLinks) / angezeigteSeitenbreite * pageWidth
dslY = (mausY - seitenrandOben) / angezeigteSeitenhöhe * pageHeight
```
Im Messmodus übernimmt ein Klick `x/y`; ein aufgezogener Bereich übernimmt zusätzlich Breite und
Höhe ins Elementformular. Im Bearbeitungsmodus lassen sich vorhandene Elemente verschieben und -
außer einzeiligem `TEXT` - am rechten unteren Anfasser skalieren. Rasterfang und Pfeiltasten sind
für Feinkorrekturen verfügbar. Änderungen werden in die ursprüngliche DSL-Zeile zurückgeschrieben,
wobei Inhalte, Platzhalter, Formatangaben und Attribute erhalten bleiben.