diff --git a/LehrerApp.Templating.Tests/TemplatingTests.cs b/LehrerApp.Templating.Tests/TemplatingTests.cs index cae6111..ef80079 100644 --- a/LehrerApp.Templating.Tests/TemplatingTests.cs +++ b/LehrerApp.Templating.Tests/TemplatingTests.cs @@ -197,6 +197,25 @@ public sealed class TemplatingTests : IDisposable || issue.Message.Contains("keine Zahl", StringComparison.OrdinalIgnoreCase)); } + [Fact] + public void LayoutParser_LiestTextBreiteFuerZentrierung() + { + var layout = new LayoutParser().Parse("PAGE 210 297 mm\nTEXT 0 20 \"Titel $Name\" w=210 align=center"); + + var text = Assert.IsType(Assert.Single(layout.Elements)); + Assert.Equal("210", text.Attributes["w"]); + } + + [Theory] + [InlineData("w=0")] + [InlineData("w=-5")] + [InlineData("w=abc")] + public void LayoutParser_LehntUngueltigeTextBreiteAb(string attribute) + { + Assert.Throws(() => + new LayoutParser().Parse($"PAGE 210 297 mm\nTEXT 0 20 \"Titel\" {attribute}")); + } + [Fact] public void LayoutParser_MeldetAlleSyntaxfehlerMitZeilen() { diff --git a/LehrerApp.Templating/LayoutParser.cs b/LehrerApp.Templating/LayoutParser.cs index 716bd8d..6991316 100644 --- a/LehrerApp.Templating/LayoutParser.cs +++ b/LehrerApp.Templating/LayoutParser.cs @@ -123,9 +123,11 @@ public sealed class LayoutParser Require(tokens, flowText ? 2 : 4); var textContent = tokens[flowText ? 1 : 3]; var textRef = Reference(textContent); + var textAttributes = Attributes(tokens, flowText ? 2 : 4); + if (textAttributes.ContainsKey("w")) RequiredPositiveNumber(textAttributes, "w"); parsedElement = new TextElement(lineNumber, flowText ? 0 : Number(tokens[1]), flowText ? 0 : Number(tokens[2]), textContent, textRef.Name, textRef.Format, - Attributes(tokens, flowText ? 2 : 4)); break; + textAttributes); break; case "TEXTBOX": var flowBox = currentFlowName is not null; Require(tokens, flowBox ? 2 : 6); diff --git a/LehrerApp.Templating/QuestTemplateRenderer.cs b/LehrerApp.Templating/QuestTemplateRenderer.cs index 6ce3818..eb0f115 100644 --- a/LehrerApp.Templating/QuestTemplateRenderer.cs +++ b/LehrerApp.Templating/QuestTemplateRenderer.cs @@ -321,9 +321,13 @@ public sealed class QuestTemplateRenderer : ITemplateRenderer, ITemplatePreviewR Position(root, image, unit, imageScale).Image(GetAsset(template, image.Path)).FitArea(); break; case TextElement text: + // w= begrenzt die Zeile auf eine feste Breite, damit align=center/right darin wirkt. + var textWidth = text.Attributes.ContainsKey("w") + ? ParseFloat(text.Attributes, "w", 0) + : Math.Max(0, template.Layout.Width - text.X); var textContainer = root.TranslateX(UnitConverter.Points(text.X, unit)) .TranslateY(UnitConverter.Points(text.Y, unit)) - .Width(UnitConverter.Points(Math.Max(0, template.Layout.Width - text.X), unit)) + .Width(UnitConverter.Points(textWidth, unit)) .Height(QuestTemplateRenderer.ParseFloat(text.Attributes, "size", 11) * 1.8f); RenderResolvedText(textContainer, text.Content, text.Placeholder, text.Format, values, template.Manifest, text.Attributes); diff --git a/LehrerApp.Templating/README.md b/LehrerApp.Templating/README.md index 389e815..9bf800c 100644 --- a/LehrerApp.Templating/README.md +++ b/LehrerApp.Templating/README.md @@ -116,6 +116,31 @@ Login-, Bearer-Token-, Guthaben- und Abrechnungsmechanismus wie die übrigen KI- Passwort wird vom eigenständigen Designer nicht gespeichert. Tabellen-/Chart-Erkennung und die automatische Rekonstruktion mehrseitiger Vorlagen sind bewusst nicht Teil von v1. +## Zentrierte und rechtsbündige Zeilen + +`TEXT` richtet `align=center` bzw. `align=right` innerhalb seiner Zeilenbreite aus. Ohne Angabe +reicht diese von `x` bis zum rechten Seitenrand, sodass Zentrierung nicht auf der Seitenmitte liegt. +Mit `w=` wird die Breite explizit gesetzt; Variablen im Text verschieben die Mitte dann nicht: + +```text +PAGE 210 297 mm +TEXT 0 20 "Elternbrief $Group.Name" w=210 align=center size=16 bold=true +TEXT 20 30 "Klasse $Group.Name" w=170 align=center +``` + +`x=0 w=` zentriert auf der Seite, `x= w=` innerhalb der +Ränder. In einem `content-flow` ist `w` nicht nötig: dort gilt die Breite des Flows. + +Weitere Hinweise: + +- `w` muss eine positive Zahl in der Einheit des Layouts sein; andernfalls meldet der Parser einen Fehler. +- `w` gilt nur für `TEXT`. `TEXTBOX` und `FLOWBOX` besitzen bereits eine Breite und richten + `align=left|center|right` darin aus. +- `align` und `w` wirken auch bei Systemvariablen (`"Seite $$curPage von $$maxPageNum"`) und bei + Platzhaltern mit Format (`$Datum|dd.MM.yyyy`). +- Der Koordinateneditor des Designers kennt `w` bei `TEXT` nicht; das Attribut wird beim Verschieben + erhalten, aber nur in der DSL-Zeile bearbeitet. + ## Mehrseitiger Fließtext `TEXTBOX` bleibt ein absolut positionierter Bereich mit fester Höhe. Für Texte unbekannter Länge