220 lines
11 KiB
Markdown
220 lines
11 KiB
Markdown
# 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.
|
||
|
||
## 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.
|