mirror of
http://172.20.10.11:3000/gitadmin/INSIGHT-MVP.git
synced 2026-06-25 00:16:41 +02:00
fix(core): PDF-Export Seitenumbruch und Sonderzeichen repariert
- pageBottom von 800 auf 740 reduziert: verhindert PDFKit-Auto-Seitenumbrüche mitten in Projekt-Einträgen (bisher: Datum/Rolle/Firma je auf eigener Seite) - Vor jedem Projekt-Eintrag Header-Höhe (Datum+Rolle+Firma) vorberechnen und Seitenumbruch proaktiv auslösen, bevor der Header gezeichnet wird - sanitizePdfText() Hilfsmethode: ersetzt €→EUR sowie Zeichen außerhalb Latin-1 die Helvetica (WinAnsiEncoding) nicht rendern kann (bisher: Zeichensalat) - sanitizePdfText auf Projekt-Texte und Zertifizierungs-Texte angewendet Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
90a0388b22
commit
9d7dcaaaea
1 changed files with 39 additions and 13 deletions
|
|
@ -187,6 +187,19 @@ export class ProfileExportService {
|
|||
.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ersetzt Sonderzeichen, die PDFKit's eingebauter Helvetica-Font (WinAnsiEncoding)
|
||||
* nicht darstellen kann (z.B. €-Zeichen → Zeichensalat).
|
||||
*/
|
||||
private sanitizePdfText(text: string): string {
|
||||
return text
|
||||
.replace(/€/g, 'EUR')
|
||||
.replace(/£/g, 'GBP')
|
||||
.replace(/¥/g, 'JPY')
|
||||
// Weiterer Fallback: alle Zeichen außerhalb Latin-1 (U+0100+) durch '?' ersetzen
|
||||
.replace(/[\u0100-\uFFFF]/g, '?');
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// PDF Export
|
||||
// ============================================================
|
||||
|
|
@ -248,7 +261,9 @@ export class ProfileExportService {
|
|||
const leftColX = 40;
|
||||
const rightColX = leftColX + leftColWidth + 20;
|
||||
const rightColWidth = pageWidth - rightColX - 40;
|
||||
const pageBottom = 800;
|
||||
// pageBottom deutlich unter der echten Seitengrenze (~841px) halten,
|
||||
// damit PDFKit keine automatischen Seitenumbrüche mitten in Einträgen auslöst
|
||||
const pageBottom = 740;
|
||||
|
||||
let yLeft = 40;
|
||||
let yRight = 40;
|
||||
|
|
@ -388,12 +403,12 @@ export class ProfileExportService {
|
|||
|
||||
// Titel
|
||||
doc.font('Helvetica-Bold').fontSize(9).fillColor(accentColor);
|
||||
doc.text(cert.title, certContentX, yLeft, { width: certContentWidth });
|
||||
yLeft += doc.heightOfString(cert.title, { width: certContentWidth }) + 2;
|
||||
doc.text(this.sanitizePdfText(cert.title), certContentX, yLeft, { width: certContentWidth });
|
||||
yLeft += doc.heightOfString(this.sanitizePdfText(cert.title), { width: certContentWidth }) + 2;
|
||||
|
||||
// Zertifizierungsstelle
|
||||
doc.font('Helvetica').fontSize(8).fillColor('#555555');
|
||||
doc.text(cert.issuingBody, certContentX, yLeft, { width: certContentWidth });
|
||||
doc.text(this.sanitizePdfText(cert.issuingBody), certContentX, yLeft, { width: certContentWidth });
|
||||
yLeft += 11;
|
||||
|
||||
yLeft += 4;
|
||||
|
|
@ -445,8 +460,21 @@ export class ProfileExportService {
|
|||
for (let i = 0; i < profile.projects.length; i++) {
|
||||
const proj = profile.projects[i];
|
||||
|
||||
// Seitenumbruch prüfen
|
||||
if (yRight > pageBottom) {
|
||||
// Vor jedem Eintrag: Höhe des Headers schätzen (Datum + Rolle + Firma)
|
||||
// damit der komplette Header auf einer Seite bleibt
|
||||
const dateRange = this.formatDateRange(proj.fromMonth, proj.fromYear, proj.toMonth, proj.toYear, proj.isCurrent);
|
||||
const roleText = this.sanitizePdfText(proj.role);
|
||||
const companyLine = proj.company
|
||||
? this.sanitizePdfText([proj.company, proj.industry].filter(Boolean).join(' \u00b7 '))
|
||||
: '';
|
||||
doc.font('Helvetica-Bold').fontSize(10);
|
||||
const roleH = doc.heightOfString(roleText, { width: contentWidth });
|
||||
doc.fontSize(9);
|
||||
const companyH = companyLine ? doc.heightOfString(companyLine, { width: contentWidth }) + 2 : 0;
|
||||
const headerH = 14 + roleH + 2 + companyH + 8; // Datum + Rolle + Firma + Puffer
|
||||
|
||||
// Seitenumbruch wenn Header nicht mehr passt
|
||||
if (yRight + headerH > pageBottom) {
|
||||
doc.addPage();
|
||||
yRight = 40;
|
||||
yRight = this.pdfSectionTitle(doc, 'BERUFSERFAHRUNG / PROJEKTE (Forts.)', rightColX, yRight, rightColWidth, accentColor);
|
||||
|
|
@ -459,19 +487,17 @@ export class ProfileExportService {
|
|||
doc.circle(timelineX, entryStartY + 4, 3.5).fill(accentColor);
|
||||
|
||||
// Zeitraum (fett, größer)
|
||||
const dateRange = this.formatDateRange(proj.fromMonth, proj.fromYear, proj.toMonth, proj.toYear, proj.isCurrent);
|
||||
doc.font('Helvetica-Bold').fontSize(10).fillColor('#555555');
|
||||
doc.text(dateRange, contentX, yRight, { width: contentWidth });
|
||||
doc.text(this.sanitizePdfText(dateRange), contentX, yRight, { width: contentWidth });
|
||||
yRight += 14;
|
||||
|
||||
// Rolle
|
||||
doc.font('Helvetica-Bold').fontSize(10).fillColor(accentColor);
|
||||
doc.text(proj.role, contentX, yRight, { width: contentWidth });
|
||||
yRight += doc.heightOfString(proj.role, { width: contentWidth }) + 2;
|
||||
doc.text(roleText, contentX, yRight, { width: contentWidth });
|
||||
yRight += doc.heightOfString(roleText, { width: contentWidth }) + 2;
|
||||
|
||||
// Firma + Branche (fett)
|
||||
if (proj.company) {
|
||||
const companyLine = [proj.company, proj.industry].filter(Boolean).join(' · ');
|
||||
if (companyLine) {
|
||||
doc.font('Helvetica-Bold').fontSize(9).fillColor('#333333');
|
||||
doc.text(companyLine, contentX, yRight, { width: contentWidth });
|
||||
yRight += doc.heightOfString(companyLine, { width: contentWidth }) + 2;
|
||||
|
|
@ -491,7 +517,7 @@ export class ProfileExportService {
|
|||
const hasBullet = /^[•\u2022]\s/.test(raw) || /^\d+\.\s/.test(raw);
|
||||
const cleaned = this.normalizeTaskLine(raw);
|
||||
if (!cleaned) continue;
|
||||
const displayText = hasBullet ? `\u2022 ${cleaned}` : cleaned;
|
||||
const displayText = `\u2022 ${this.sanitizePdfText(hasBullet ? cleaned : cleaned)}`;
|
||||
doc.text(displayText, contentX + 4, yRight, { width: contentWidth - 8 });
|
||||
yRight += doc.heightOfString(displayText, { width: contentWidth - 8 }) + 1;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue