diff --git a/LehrerApp.Desktop.Tests/ClassTeacherViewModelsTests.cs b/LehrerApp.Desktop.Tests/ClassTeacherViewModelsTests.cs index 50ece9c..facfa20 100644 --- a/LehrerApp.Desktop.Tests/ClassTeacherViewModelsTests.cs +++ b/LehrerApp.Desktop.Tests/ClassTeacherViewModelsTests.cs @@ -199,6 +199,33 @@ public sealed class ClassTeacherViewModelsTests Assert.DoesNotContain("09.09.2026", labels); } + // Regression: Die Spaltenpositionen wurden versehentlich zusätzlich mit millimeterScale + // multipliziert (analog zu echten mm-Maßen wie der Zeilenhöhe) - bei einer in "pt" statt "mm" + // deklarierten Vorlage (millimeterScale ≈ 2.83) lag die Status-Spalte dadurch weit außerhalb der + // tatsächlichen Box (x≈323 in einer nur 170 Einheiten breiten Box), statt nur ein Anteil von + // contentWidth zu sein. + [Fact] + public void ElternbriefFehltage_SpaltenBleibenInnerhalbDerBoxbreiteAuchBeiPunktBasierterVorlage() + { + var options = new AttendanceCalendarOptions(new DateOnly(2026, 9, 1), 1); + var absences = new[] + { + new ClassAbsenceDaySummaryRow(new DateOnly(2026, 9, 3), "Ada Müller", 1, 6, 270, + ["Deu"], [1, 2, 3, 4, 5, 6], ["entsch."], ["Krank"], null, null, true), + }; + const float contentWidth = 170f; const float millimeterScale = 2.8346f; + + var drawing = StudentAbsenceDayListDrawingBuilder.Build("Ada Müller", options, absences, + contentWidth, millimeterScale); + var textBoxes = drawing.Commands.OfType().ToList(); + var rectangles = drawing.Commands.OfType().ToList(); + + Assert.NotEmpty(textBoxes); + Assert.All(textBoxes, c => Assert.True(c.X + c.Width <= contentWidth + 0.01f, + $"'{c.Text}' reicht bis x={c.X + c.Width}, Box ist aber nur {contentWidth} breit.")); + Assert.All(rectangles, c => Assert.True(c.X + c.Width <= contentWidth + 0.01f)); + } + [Fact] public void GroupByStudentAndDay_FasstFehlstundenProSchuelerUndTagZusammen() { diff --git a/LehrerApp.Desktop/Services/StudentAttendanceCalendarDrawingBuilder.cs b/LehrerApp.Desktop/Services/StudentAttendanceCalendarDrawingBuilder.cs index 14ebd5c..7b731a3 100644 --- a/LehrerApp.Desktop/Services/StudentAttendanceCalendarDrawingBuilder.cs +++ b/LehrerApp.Desktop/Services/StudentAttendanceCalendarDrawingBuilder.cs @@ -166,9 +166,14 @@ public static class StudentAbsenceDayListDrawingBuilder AttendanceCalendarSize.Large => 1f, _ => .88f, }; - // contentWidth/millimeterScale: siehe StudentAttendanceCalendarDrawingBuilder - contentWidth - // bleibt an die tatsächliche DRAWBOX/FLOWDRAWBOX-Deklaration im Layout-Skript gebunden, - // millimeterScale rechnet das mm-entworfene Raster in die Koordinaten-Einheit der Vorlage um. + // contentWidth ist die reale DRAWBOX/FLOWDRAWBOX-Breite (bereits in der Koordinaten-Einheit + // der Vorlage) und wird für die Spalten NICHT zusätzlich mit millimeterScale multipliziert - + // die Spaltenpositionen sind Anteile von contentWidth (proportional, unitless), nicht eigene + // mm-Maße. Nur echte mm-Maße wie Zeilenhöhe/Abstände (Y-Richtung) brauchen millimeterScale, + // um bei einer in "pt" deklarierten Vorlage die richtige physische Größe zu erreichen (siehe + // StudentAttendanceCalendarDrawingBuilder). Eine frühere Version multiplizierte auch die + // Spalten mit millimeterScale, wodurch sie bei "pt"-Vorlagen weit über contentWidth + // hinausliefen (z.B. Status-Spalte bei x≈323 in einer nur 170 Einheiten breiten Box). var rowHeight = 12 * scale * millimeterScale; var start = options.NormalizedStartMonth; var end = start.AddMonths(options.NormalizedMonthCount).AddDays(-1); @@ -179,18 +184,22 @@ public static class StudentAbsenceDayListDrawingBuilder .ToList(); var commands = new List(); var y = 0f; - commands.Add(new DrawStringEx(0, y, 14 * scale * millimeterScale, contentWidth, "Fehltage", - DrawingTextAlignment.AlignLeft, 11 * scale, Color: "#1F2937", Bold: true)); - y += 14 * scale * millimeterScale; - commands.Add(new DrawStringEx(0, y, 10 * scale * millimeterScale, contentWidth, studentName, - DrawingTextAlignment.AlignLeft, 8 * scale, Color: "#6B7280")); - y += 10 * scale * millimeterScale + 2 * scale * millimeterScale; - // Spaltenbreiten so gewählt, dass auch die Datenzeilen (nicht nur die kurzen Kopfzeilen- - // Labels) hineinpassen - "Datum" als Kopfzeile ist kürzer als "dd.MM.yyyy" und wurde bei - // 31 zu schmal bemessen, wodurch das Datum am rechten Rand abgeschnitten wurde. - var dateColumnX = 2 * millimeterScale; var dateColumnWidth = 38 * millimeterScale; - var extentColumnX = 44 * millimeterScale; var extentColumnWidth = 66 * millimeterScale; - var statusColumnX = 114 * millimeterScale; var statusColumnWidth = 54 * millimeterScale; + // Kein eigener "Fehltage"-Titel mehr - analog zum Anwesenheitskalender dient der (klein und + // hellgrau gehaltene) Name nur der Kontrolle, dass die Liste zum richtigen Kind gehört. + commands.Add(new DrawStringEx(0, y, 5 * millimeterScale, contentWidth, studentName, + DrawingTextAlignment.AlignLeft, 6.5f * scale, Color: "#9CA3AF")); + y += 5 * millimeterScale + .5f * millimeterScale; + // Spaltenbreiten als Anteile von contentWidth (gegen eine 170-Einheiten-Referenz austariert), + // damit auch die Datenzeilen (nicht nur die kurzen Kopfzeilen-Labels) hineinpassen - "Datum" + // als Kopfzeile ist kürzer als "dd.MM.yyyy" und wurde bei 31 zu schmal bemessen, wodurch das + // Datum am rechten Rand abgeschnitten wurde. + const float referenceWidth = 170f; + var dateColumnX = contentWidth * (2f / referenceWidth); + var dateColumnWidth = contentWidth * (38f / referenceWidth); + var extentColumnX = contentWidth * (44f / referenceWidth); + var extentColumnWidth = contentWidth * (66f / referenceWidth); + var statusColumnX = contentWidth * (114f / referenceWidth); + var statusColumnWidth = contentWidth * (54f / referenceWidth); commands.Add(new DrawRectangle(0, y, contentWidth, rowHeight, "#CBD5E1", .4f, "#F3F4F6")); commands.Add(new DrawStringEx(dateColumnX, y + scale * millimeterScale, rowHeight - scale * millimeterScale, dateColumnWidth, "Datum", DrawingTextAlignment.AlignLeft, 7.5f * scale, Color: "#374151", Bold: true)); @@ -202,8 +211,8 @@ public static class StudentAbsenceDayListDrawingBuilder if (rows.Count == 0) { - commands.Add(new DrawStringEx(2 * millimeterScale, y + 2 * scale * millimeterScale, rowHeight, - contentWidth - 4 * millimeterScale, "Keine Fehltage im gewählten Zeitraum", + commands.Add(new DrawStringEx(dateColumnX, y + 2 * scale * millimeterScale, rowHeight, + contentWidth - 2 * dateColumnX, "Keine Fehltage im gewählten Zeitraum", DrawingTextAlignment.AlignLeft, 8 * scale, Color: "#6B7280", Italic: true)); y += rowHeight + 3 * scale * millimeterScale; }