diff --git a/packages/core-service/src/core/expert-profile/profile-export.service.ts b/packages/core-service/src/core/expert-profile/profile-export.service.ts index 88ce1ab..97f807d 100644 --- a/packages/core-service/src/core/expert-profile/profile-export.service.ts +++ b/packages/core-service/src/core/expert-profile/profile-export.service.ts @@ -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; } } }