mirror of
http://172.20.10.11:3000/gitadmin/INSIGHT-MVP.git
synced 2026-06-24 22:46:39 +02:00
fix: Timeline-Linie am Anfang der nächsten Iteration zeichnen (Deferred-Draw)
Vorherige Ansätze berechneten die Ziel-Header-Höhe am Ende des Eintrags neu (fehleranfällig durch doppelte Font-State-Operationen). Neuer Ansatz: Linie für Entry i wird am ANFANG von Entry i+1 gezeichnet, BEVOR der Seitenumbruch-Check läuft — mit demselben headerH der bereits berechnet wurde. Eine einzige Bedingung entscheidet konsistent ob Linie gezeichnet wird UND ob ein Seitenumbruch folgt, ohne Redundanz oder State-Probleme. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
fb57f5a4dc
commit
a7cf59ae20
1 changed files with 22 additions and 19 deletions
|
|
@ -491,6 +491,11 @@ export class ProfileExportService {
|
|||
const contentX = rightColX + 18;
|
||||
const contentWidth = rightColWidth - 18;
|
||||
|
||||
// Vorgemerkter Linien-Start vom vorherigen Eintrag
|
||||
// (wird am Anfang der nächsten Iteration gezeichnet, wenn kein Seitenumbruch nötig ist)
|
||||
let pendingLineStartY: number | null = null;
|
||||
let pendingLineEndY: number | null = null;
|
||||
|
||||
for (let i = 0; i < profile.projects.length; i++) {
|
||||
const proj = profile.projects[i];
|
||||
|
||||
|
|
@ -507,6 +512,19 @@ export class ProfileExportService {
|
|||
const companyH = companyLine ? doc.heightOfString(companyLine, { width: contentWidth }) + 2 : 0;
|
||||
const headerH = 14 + roleH + 2 + companyH + 8; // Datum + Rolle + Firma + Puffer
|
||||
|
||||
// Linie des vorherigen Eintrags zeichnen — JETZT, bevor der Seitenumbruch-Check läuft.
|
||||
// Nur wenn dieser Eintrag auf derselben Seite bleibt (kein Seitenumbruch nötig),
|
||||
// sind Quell-Dot und Ziel-Dot auf derselben Seite → Linie korrekt.
|
||||
if (pendingLineStartY !== null && pendingLineEndY !== null) {
|
||||
if (yRight + headerH <= pageBottom) {
|
||||
doc.moveTo(timelineX, pendingLineStartY + 8)
|
||||
.lineTo(timelineX, pendingLineEndY)
|
||||
.strokeColor(accentColor).lineWidth(1).stroke();
|
||||
}
|
||||
pendingLineStartY = null;
|
||||
pendingLineEndY = null;
|
||||
}
|
||||
|
||||
// Seitenumbruch wenn Header nicht mehr passt
|
||||
if (yRight + headerH > pageBottom) {
|
||||
doc.addPage();
|
||||
|
|
@ -567,25 +585,10 @@ export class ProfileExportService {
|
|||
|
||||
yRight += 12;
|
||||
|
||||
// Timeline-Linie: nur zeichnen wenn kein Seitenumbruch im Eintrag
|
||||
// UND der nächste Eintrag noch auf dieser Seite passt (exakte Header-Höhe prüfen)
|
||||
if (i < profile.projects.length - 1 && !pageBreakOccurred) {
|
||||
const nextProj = profile.projects[i + 1];
|
||||
const nextRoleText = this.sanitizePdfText(nextProj.role);
|
||||
const nextCompanyLine = nextProj.company
|
||||
? this.sanitizePdfText([nextProj.company, nextProj.industry].filter(Boolean).join(' \u00b7 '))
|
||||
: '';
|
||||
doc.font('Helvetica-Bold').fontSize(10);
|
||||
const nextRoleH = doc.heightOfString(nextRoleText, { width: contentWidth });
|
||||
doc.fontSize(9);
|
||||
const nextCompanyH = nextCompanyLine
|
||||
? doc.heightOfString(nextCompanyLine, { width: contentWidth }) + 2
|
||||
: 0;
|
||||
const nextHeaderH = 14 + nextRoleH + 2 + nextCompanyH + 8;
|
||||
if (yRight + nextHeaderH <= pageBottom) {
|
||||
doc.moveTo(timelineX, entryStartY + 8).lineTo(timelineX, yRight - 4)
|
||||
.strokeColor(accentColor).lineWidth(1).stroke();
|
||||
}
|
||||
// Linie für nächste Iteration vormerken — nur wenn kein Seitenumbruch innerhalb des Eintrags
|
||||
if (!pageBreakOccurred) {
|
||||
pendingLineStartY = entryStartY;
|
||||
pendingLineEndY = yRight - 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue