fix: improve PDF contact icons - smartphone, envelope and map pin shapes

Replaced rough vector shapes with proper recognizable icons:
- Phone: smartphone outline with display area and home button
- Email: envelope with cleaner V-flap proportions
- Location: smooth teardrop map pin using bezier curves

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Thomas Reitz 2026-03-09 12:14:32 +01:00
parent 4d5aa84ac9
commit 82af9cb1f0

View file

@ -173,24 +173,25 @@ export class ProfileExportService {
// --- KONTAKT --- // --- KONTAKT ---
yLeft = this.pdfSectionTitle(doc, 'KONTAKT', leftColX, yLeft, leftColWidth, accentColor); yLeft = this.pdfSectionTitle(doc, 'KONTAKT', leftColX, yLeft, leftColWidth, accentColor);
const iconTextOffset = 18; // Abstand Icon → Text
if (data.phone) { if (data.phone) {
this.drawPhoneIcon(doc, leftColX, yLeft + 1, accentColor); this.drawPhoneIcon(doc, leftColX + 1, yLeft, accentColor);
yLeft = this.pdfContactText(doc, data.phone, leftColX + 16, yLeft, leftColWidth - 16); yLeft = this.pdfContactText(doc, data.phone, leftColX + iconTextOffset, yLeft, leftColWidth - iconTextOffset);
} }
if (data.mobile) { if (data.mobile) {
this.drawPhoneIcon(doc, leftColX, yLeft + 1, accentColor); this.drawPhoneIcon(doc, leftColX + 1, yLeft, accentColor);
yLeft = this.pdfContactText(doc, data.mobile, leftColX + 16, yLeft, leftColWidth - 16); yLeft = this.pdfContactText(doc, data.mobile, leftColX + iconTextOffset, yLeft, leftColWidth - iconTextOffset);
} }
if (data.email) { if (data.email) {
this.drawEmailIcon(doc, leftColX, yLeft + 1, accentColor); this.drawEmailIcon(doc, leftColX, yLeft + 1, accentColor);
yLeft = this.pdfContactText(doc, data.email, leftColX + 16, yLeft, leftColWidth - 16); yLeft = this.pdfContactText(doc, data.email, leftColX + iconTextOffset, yLeft, leftColWidth - iconTextOffset);
} }
if (data.street || data.city) { if (data.street || data.city) {
this.drawLocationIcon(doc, leftColX + 1, yLeft, accentColor); this.drawLocationIcon(doc, leftColX, yLeft, accentColor);
const line1 = data.street || ''; const line1 = data.street || '';
const line2 = [data.postalCode, data.city].filter(Boolean).join(' '); const line2 = [data.postalCode, data.city].filter(Boolean).join(' ');
const addressText = [line1, line2].filter(Boolean).join('\n'); const addressText = [line1, line2].filter(Boolean).join('\n');
yLeft = this.pdfContactText(doc, addressText, leftColX + 16, yLeft, leftColWidth - 16); yLeft = this.pdfContactText(doc, addressText, leftColX + iconTextOffset, yLeft, leftColWidth - iconTextOffset);
} }
yLeft += 10; yLeft += 10;
@ -732,33 +733,47 @@ export class ProfileExportService {
private drawPhoneIcon(doc: PDFKit.PDFDocument, x: number, y: number, color: string): void { private drawPhoneIcon(doc: PDFKit.PDFDocument, x: number, y: number, color: string): void {
doc.save(); doc.save();
// Telefon-Hörer: abgerundetes Rechteck // Smartphone-Icon: abgerundetes Rechteck mit Display und Home-Button
doc.roundedRect(x + 1, y, 4, 8, 1.5).fillAndStroke(color, color); const w = 7;
// Hörer-Bogen oben const h = 10;
doc.moveTo(x, y + 1).quadraticCurveTo(x - 1, y + 4, x + 1, y + 7) doc.roundedRect(x, y - 1, w, h, 1.5).strokeColor(color).lineWidth(1).stroke();
.strokeColor(color).lineWidth(1.5).stroke(); // Display-Bereich (inneres Rechteck)
doc.rect(x + 1.2, y + 1, w - 2.4, h - 4.5).fill(color);
// Home-Button (kleiner Kreis unten)
doc.circle(x + w / 2, y + h - 2, 0.8).fill(color);
doc.restore(); doc.restore();
} }
private drawEmailIcon(doc: PDFKit.PDFDocument, x: number, y: number, color: string): void { private drawEmailIcon(doc: PDFKit.PDFDocument, x: number, y: number, color: string): void {
doc.save(); doc.save();
// Briefumschlag const w = 11;
doc.rect(x, y + 1, 10, 7).strokeColor(color).lineWidth(1).stroke(); const h = 8;
// V-Linie oben // Briefumschlag-Körper
doc.moveTo(x, y + 1).lineTo(x + 5, y + 5).lineTo(x + 10, y + 1) doc.rect(x, y, w, h).strokeColor(color).lineWidth(0.8).stroke();
// Klappen-Linien (V-Form von oben + Ecken unten)
doc.moveTo(x, y).lineTo(x + w / 2, y + h * 0.55).lineTo(x + w, y)
.strokeColor(color).lineWidth(0.8).stroke(); .strokeColor(color).lineWidth(0.8).stroke();
doc.restore(); doc.restore();
} }
private drawLocationIcon(doc: PDFKit.PDFDocument, x: number, y: number, color: string): void { private drawLocationIcon(doc: PDFKit.PDFDocument, x: number, y: number, color: string): void {
doc.save(); doc.save();
// Pin-Kopf (Kreis) // Map-Pin als Tropfenform mit Bezier-Kurven
doc.circle(x + 4, y + 4, 3).fillAndStroke(color, color); const cx = x + 5; // Mittelpunkt X
// Pin-Spitze (Dreieck) const top = y - 1; // Oberkante
doc.moveTo(x + 1.5, y + 5.5).lineTo(x + 4, y + 11).lineTo(x + 6.5, y + 5.5) const r = 4; // Radius des Kopfes
const tip = y + 11; // Spitze des Pins
// Tropfenform: Start oben, links herum, Spitze unten, rechts hoch
doc.moveTo(cx, top)
.bezierCurveTo(cx - r * 1.1, top, cx - r, top + r * 0.6, cx - r, top + r)
.bezierCurveTo(cx - r, top + r * 1.8, cx - r * 0.3, tip - 2, cx, tip)
.bezierCurveTo(cx + r * 0.3, tip - 2, cx + r, top + r * 1.8, cx + r, top + r)
.bezierCurveTo(cx + r, top + r * 0.6, cx + r * 1.1, top, cx, top)
.fill(color); .fill(color);
// Innerer Kreis (weiß)
doc.circle(x + 4, y + 4, 1.2).fill('#ffffff'); // Innerer Kreis (weiß) für Pin-Look
doc.circle(cx, top + r, 1.5).fill('#ffffff');
doc.restore(); doc.restore();
} }