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: '#011dff', // Brand Royal Blue 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: '20mm', // Generous area for the footer paddingBottom: '3mm', // Exactly compensate for bottom bleed (3mm trim) 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: '4mm', // Reduced significantly to pull content up paddingLeft: '6mm', paddingRight: '6mm', flexDirection: 'column', }, backHeader: { flexDirection: 'row', alignItems: 'center', marginBottom: '8mm', // Generous breathing room width: '100%', }, backLogo: { width: 60, }, accentLineHorizontal: { flex: 1, height: 1.5, backgroundColor: '#011dff', // 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', // Clear separation between name block and contact block }, nameText: { fontSize: 13, fontWeight: 'bold', color: COLORS.neutralDark, marginBottom: 1.5, letterSpacing: -0.15, }, roleText: { fontSize: 6.5, fontWeight: 'bold', color: '#011dff', 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.neutralDark, 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: '12mm', height: '12mm', border: `0.75pt solid ${COLORS.darkGreen}`, padding: 2.5, backgroundColor: '#ffffff', }, qrCode: { width: '100%', height: '100%', }, addressFooter: { width: '100%', flexDirection: 'row', justifyContent: 'flex-start', alignItems: 'center', position: 'absolute', bottom: '4mm', left: '6mm', // Match backContainer paddingLeft for flush alignment }, addressFooterText: { fontSize: 6.5, color: COLORS.grayText, 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, pathOverrides, solidPaths, solidBackgroundColor = '#000a66', }: { paths: { d: string; fill: string }[]; style: any; overrideColor?: string; overrideWhiteColor?: 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; } 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 = () => { if (variant === 'black') return logoBlackWithTaglinePaths; return logoBlueWithTaglinePaths; }; const bgImagePath = isWhiteVariant ? path.join(process.cwd(), 'public', 'media', 'cable_drums_truck.png') : path.join(process.cwd(), 'public', 'cable_drums_bg.png'); const renderPageWrapper = (children: React.ReactNode, bleedStyle: any) => { if (withCropMarks) { return ( {children} ); } return ( {children} ); }; return ( {/* VORDERSEITE (Blue page with logo, truck and tagline) */} {renderPageWrapper( <> {withTruck && ( )} 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 */} {/* Letterpress Mask Layer: We use #ff00fe as a marker color. Only the "KLZ" letters! */} {isViaprinto && ( KLZ_PATHS.includes(i))} style={[styles.frontLogo, { position: 'absolute' }]} overrideColor="#ff00fe" solidPaths={KLZ_PATHS.map((_, i) => i)} /> )} , [styles.pageBleed, isWhiteVariant && { backgroundColor: '#ffffff' }], )} {renderPageWrapper( ({ ...acc, [idx]: 'none' }), {})} /> {withTruck && ( )} {person.name} {person.role} {person.phone} {person.email} klz-cables.com KLZ Vertriebs GmbH · Raiffeisenstraße 22 · 73630 Remshalden , styles.pageBleed, )} ); };