fix: Fehltage-Spalten liefen bei pt-Vorlagen weit über die Boxbreite hinaus

Die Spaltenpositionen/-breiten wurden fälschlich zusätzlich mit
millimeterScale multipliziert, so als wären es eigene mm-Maße - bei einer in
"pt" deklarierten Vorlage (millimeterScale ≈ 2.83) lag z.B. die Status-Spalte
dadurch bei x≈323 in einer nur 170 Einheiten breiten Box, weit außerhalb des
sichtbaren Bereichs. Das erklärt die "zerschossene" Tabelle.

Spalten sind jetzt Anteile von contentWidth (das bereits die reale, korrekte
Boxbreite in der Koordinaten-Einheit der Vorlage ist) statt eigener,
zusätzlich umzurechnender mm-Werte - nur echte Y-Maße (Zeilenhöhe, Abstände)
brauchen weiterhin millimeterScale.

Zusätzlich wie beim Anwesenheitskalender: kein eigener "Fehltage"-Titel mehr,
der Name bleibt klein/hellgrau nur zur Kontrolle - reduziert den Höhen-
Overhead pro Seite etwas, hilft aber nur begrenzt gegen mehr benötigte Seiten
bei vielen Fehltagen, da Zeilen jetzt korrekt physisch groß (statt vorher zu
klein) gerendert werden.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Sonnet 5 committed 2026-09-17 22:52:03 +02:00
1 parent 9dc7ebab0e
commit 3c74e3873a
2 files changed
+53 -17

No files matched your search

@@ -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<DrawStringEx>().ToList();
var rectangles = drawing.Commands.OfType<DrawRectangle>().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()
{
@@ -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<DrawingCommand>();
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;
}