Files
admin ab1feba0f0
CI / build-and-test (push) Canceled after 0s
Merge remote-tracking branch 'origin/main'
# Conflicts:
#	LehrerApp.TemplateDesigner.Tests/ProjectLifecycleTests.cs
#	LehrerApp.TemplateDesigner/DesignerViewModel.cs
#	LehrerApp.TemplateDesigner/MainWindow.axaml
#	LehrerApp.Templating/LayoutParser.cs
#	LehrerApp.Templating/QuestTemplateRenderer.cs
2026-09-01 10:15:54 +02:00
..
2026-08-31 23:47:48 +02:00

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:

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:

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:

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:

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:

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 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.

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:

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:

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:

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:

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.