import React from 'react'; import * as fs from 'fs'; import { Document, Page, Text, View, StyleSheet, Font, Image, Svg, Path, G, Rect, ClipPath, Circle, } from '@react-pdf/renderer'; import * as path from 'path'; import { TruckBlueprint } from './TruckBlueprint'; // Register embedded fonts so Ghostscript -dNoOutputFonts can convert them to // outlines (curves). Built-in PDF fonts like Helvetica are NEVER embedded and // therefore cannot be outlined — always use a physical font file here. Font.register({ family: 'Inter', fonts: [ { src: path.join(process.cwd(), 'public', 'fonts', 'Inter-Regular.woff'), fontWeight: 'normal', }, { src: path.join(process.cwd(), 'public', 'fonts', 'Inter-Bold.woff'), fontWeight: 'bold', }, ], }); export interface PersonData { name: string; role: string; phone: string; email: string; } export interface PDFBusinessCardProps { person: PersonData; qrCodeDataUrl: string; variant?: 'blue' | 'white' | 'black'; isViaprinto?: boolean; withTruck?: boolean; withCropMarks?: boolean; } // Die Buchstaben K, L, Z im SVG (inklusive dem kleinen Stück vom Z oben links) export const KLZ_PATHS = [25, 26, 27, 28, 29]; const COLORS = { primary: '#000a66', // Brand Dark Blue (from website) blue: '#000a66', green: '#82ed20', // Brand Accent Green darkGreen: '#4A9E00', // Darker green for white background visibility white: '#ffffff', grayText: '#6c757d', black: '#0a0a0a', neutralDark: '#263336', lightGray: '#f8f9fa', }; const CropMarks = () => ( {/* Top Left */} {/* Top Right */} {/* Bottom Left */} {/* Bottom Right */} ); const styles = StyleSheet.create({ pageBleed: { width: '91mm', height: '61mm', backgroundColor: COLORS.primary, position: 'relative', fontFamily: 'Inter', overflow: 'hidden', }, pageBleedBack: { width: '91mm', height: '61mm', backgroundColor: COLORS.white, position: 'relative', fontFamily: 'Inter', overflow: 'hidden', }, // -- BLUE PAGE (Rückseite) -- truckWrapper: { position: 'absolute', top: 0, left: 0, right: 0, bottom: '20mm', // Truck stops above the footer area to avoid text conflict zIndex: 1, }, frontBackgroundImage: { width: '100%', height: '100%', opacity: 0.4, // Less transparent, more contrast }, frontContainer: { flex: 1, paddingTop: '3mm', // Compensate for top bleed for perfect optical centering paddingBottom: '20mm', // Leave space for the footer area justifyContent: 'center', alignItems: 'center', }, frontLogo: { width: 145, // Reduced for a more elegant, premium look with more negative space zIndex: 100, }, frontTagline: { color: COLORS.white, fontSize: 5.5, marginTop: 8, letterSpacing: 2, fontWeight: 'bold', textTransform: 'uppercase', zIndex: 100, }, frontFooterArea: { position: 'absolute', bottom: 0, left: 0, right: 0, height: '22mm', // Increased height to accommodate more bottom padding paddingBottom: '10mm', // Increased bottom padding to move icons further from edge zIndex: 50, flexDirection: 'column', justifyContent: 'center', alignItems: 'center', gap: 6, // Harmonious spacing to icons }, frontIconsGroup: { flexDirection: 'row', gap: 14, // Wider gaps between icon groups for visual clarity at small size }, frontBrandIconWrapper: { alignItems: 'center', gap: 1.5, }, frontBrandIcon: { width: 12, // Smaller for compact bar height: 12, opacity: 0.85, }, frontBrandText: { fontSize: 4, // Tiny but readable at 300dpi print color: COLORS.white, textTransform: 'uppercase', letterSpacing: 0.6, // Full opacity — white on blue needs to pop cleanly }, frontTaglineBar: { color: COLORS.green, fontSize: 7, // Larger — as requested letterSpacing: 0.5, // Harmonious tracking for mixed case fontWeight: 'bold', }, // -- WHITE PAGE (Vorderseite) -- backContainer: { flex: 1, paddingTop: '6mm', // Pushed down as requested paddingLeft: '6mm', paddingRight: '6mm', flexDirection: 'column', }, backHeader: { flexDirection: 'row', alignItems: 'center', marginBottom: '5mm', // Generous breathing room width: '100%', }, backLogo: { width: 60, }, accentLineHorizontal: { flex: 1, height: 1.5, backgroundColor: COLORS.primary, // Brand blue line marginLeft: 12, marginRight: 12, }, accentDot: { width: 4, height: 4, borderRadius: 2, backgroundColor: COLORS.green, marginRight: 20, // Keep away from the edge! }, accentBoxEdge: { width: 3, height: 25, // Sleek vertical bar on the edge backgroundColor: COLORS.green, position: 'absolute', right: '-8mm', // Bleed edge top: -5, }, backBody: { flex: 1, flexDirection: 'column', justifyContent: 'flex-start', alignItems: 'flex-start', width: '100%', }, leftColumn: { width: '100%', // Take full width since right column is removed flexDirection: 'column', }, nameSection: { marginBottom: '2.5mm', // Reduced to fix the 'Lücke' under the role }, nameText: { fontSize: 13, fontWeight: 'bold', color: COLORS.primary, marginBottom: 1.5, letterSpacing: -0.15, }, roleText: { fontSize: 6.5, fontWeight: 'bold', color: COLORS.darkGreen, letterSpacing: 1.2, textTransform: 'uppercase', }, contactSection: { flexDirection: 'column', gap: 0, }, contactRow: { flexDirection: 'row', alignItems: 'center', marginBottom: 5, // Even rhythm between contact lines }, iconWrapper: { width: 12, justifyContent: 'center', alignItems: 'flex-start', }, contactText: { fontSize: 7.5, color: COLORS.primary, fontWeight: 'normal', paddingTop: 0.5, letterSpacing: 0.1, }, rightColumn: { flexDirection: 'column', alignItems: 'flex-end', justifyContent: 'flex-start', }, // Ultra Minimalist QR Area qrContainer: { flexDirection: 'column', alignItems: 'center', gap: 3, }, qrCodeWrapper: { width: '10mm', height: '10mm', border: `0.75pt solid ${COLORS.darkGreen}`, padding: 2, backgroundColor: '#ffffff', }, qrCode: { width: '100%', height: '100%', }, addressFooter: { width: '100%', flexDirection: 'row', justifyContent: 'flex-start', alignItems: 'center', position: 'absolute', bottom: '7mm', // 3mm bleed + 4mm safe margin left: '6mm', // Match backContainer paddingLeft for flush alignment }, addressFooterText: { fontSize: 6.5, color: COLORS.primary, letterSpacing: 0.4, fontWeight: 'normal', }, }); // SVG Icons const PhoneIcon = () => ( ); const EmailIcon = () => ( ); const GlobeIcon = () => ( ); const LocationIcon = () => ( ); const ScanIcon = () => ( ); const parseSvgPaths = (filePath: string) => { try { const raw = fs.readFileSync(filePath, 'utf8'); const paths = []; const pathRegex = /]+)>/g; let match; while ((match = pathRegex.exec(raw)) !== null) { const attrs = match[1]; const dMatch = attrs.match(/d="([^"]+)"/); if (!dMatch) continue; let fill = 'white'; const fillMatch = attrs.match(/fill(?:[:=])"?([^;"\s>]+)/); if (fillMatch) { fill = fillMatch[1].replace(/"$/, ''); if (fill === 'none') continue; } paths.push({ d: dMatch[1], fill }); } return paths; } catch (e) { console.error('Could not parse SVG:', filePath); return []; } }; const VectorLogo = ({ paths, style, overrideColor, overrideWhiteColor, overrideBlackColor, pathOverrides, solidPaths, solidBackgroundColor = '#000a66', }: { paths: { d: string; fill: string }[]; style: any; overrideColor?: string; overrideWhiteColor?: string; overrideBlackColor?: string; pathOverrides?: Record; solidPaths?: number[]; solidBackgroundColor?: string; }) => ( {/* Background layer: solid fills to prevent see-through */} {solidPaths && solidBackgroundColor && paths.map((p, i) => { if (!solidPaths.includes(i)) return null; let d = p.d; const zIndex = d.toUpperCase().indexOf('Z'); if (zIndex !== -1) { d = d.substring(0, zIndex + 1); } return ; })} {paths.map((p, i) => { let fill = overrideColor || p.fill; const d = p.d; if (pathOverrides && pathOverrides[i]) { fill = pathOverrides[i]; } else if ( overrideWhiteColor && (p.fill === 'white' || p.fill === '#ffffff' || p.fill === '#fff') ) { fill = overrideWhiteColor; } else if ( overrideBlackColor && (p.fill === 'black' || p.fill === '#000000' || p.fill === '#0a0a0a' || p.fill === '#000') ) { fill = overrideBlackColor; } return ; })} ); export const IconSolar = ({ style, color = 'white' }: { style: any; color?: string }) => ( ); export const AbstractCable = ({ size = 10, color = COLORS.green, }: { size?: number; color?: string; }) => ( ); export const IconLowVoltage = ({ style, color = 'white' }: { style: any; color?: string }) => ( ); export const IconMediumVoltage = ({ style, color = 'white' }: { style: any; color?: string }) => ( ); export const IconHighVoltage = ({ style, color = 'white' }: { style: any; color?: string }) => ( ); export const PDFBusinessCard: React.FC = ({ person, qrCodeDataUrl, variant = 'blue', isViaprinto = false, withTruck = true, withCropMarks = false, }) => { const isWhiteVariant = variant === 'white' || variant === 'black'; const logoWhitePath = path.join(process.cwd(), 'public', 'logo-green-text.svg'); const logoBluePath = path.join(process.cwd(), 'public', 'logo-green-text-blue.svg'); const logoBlueWithTaglinePath = path.join( process.cwd(), 'public', 'logo-green-text-blue-with-tagline.svg', ); const logoBlackWithTaglinePath = path.join( process.cwd(), 'public', 'logo-green-text-black-with-tagline.svg', ); const logoWhitePaths = parseSvgPaths(logoWhitePath); const logoBluePaths = parseSvgPaths(logoBluePath); const logoBlueWithTaglinePaths = parseSvgPaths(logoBlueWithTaglinePath); const logoBlackWithTaglinePaths = parseSvgPaths(logoBlackWithTaglinePath); const getFrontLogoPaths = () => { if (variant === 'black') return logoBlackWithTaglinePaths; if (variant === 'white') return logoBlueWithTaglinePaths; return logoWhitePaths; }; const getBackLogoPaths = () => { // We use the original black logo to preserve the exact colors of the Truck and KLZ. // The tagline will be hidden via pathOverrides in the JSX. return logoBlackWithTaglinePaths; }; const bgImagePath = isWhiteVariant ? path.join(process.cwd(), 'public', 'media', 'cable_drums_truck.png') : path.join(process.cwd(), 'public', 'cable_drums_bg.png'); const mmToPt = (mm: number) => (mm * 72) / 25.4; const renderPageWrapper = (children: React.ReactNode, bleedStyle: any) => { if (withCropMarks) { return ( {children} ); } return ( {children} ); }; return ( {/* VORDERSEITE (Blue page with logo, truck and tagline) */} {renderPageWrapper( <> Von 0,6/1,0 kV bis 64/110 kV Niederspannung Mittelspannung Hochspannung Solar {/* Base Logo: text is white, truck is green. Fill KLZ background to hide truck, keep outline white */} , [styles.pageBleed, isWhiteVariant && { backgroundColor: '#ffffff' }], )} {renderPageWrapper( , idx) => ({ ...acc, [idx]: 'none' }), {} as Record, ) as any), // Paths 0-24 are the "Cables for a greener tomorrow" tagline in the 40-path logo. ...(Array.from({ length: 25 }).reduce( (acc: Record, _, i) => ({ ...acc, [i]: 'none' }), {} as Record, ) as any), }} /> {person.name} {person.role} {person.phone} {person.email.toLowerCase()} klz-cables.com KLZ Vertriebs GmbH · Raiffeisenstraße 22 · 73630 Remshalden , styles.pageBleedBack, )} ); };