/** * pdfmake accepts ONLY PNG and JPEG images ("Unknown image format" otherwise). * Customer logos come from AD_Image uploads (any format — the lead page sniffs * JPEG vs "PNG" only, so a WebP/GIF/SVG/AVIF logo used to reach pdfmake as a * mislabelled PNG and killed the whole quote preview). Website screenshots * can be WebP too. This normalises any data URL to a PNG/JPEG data URL: * real PNG/JPEG pass through (with the mime corrected), everything else is * converted with sharp; anything unusable returns null so the caller simply * leaves the image out instead of failing the PDF. */ const sniff = (buf: Buffer): 'png' | 'jpeg' | 'other' | 'empty' => { if (!buf || buf.length < 12) return 'empty' if (buf[0] === 0x89 && buf[1] === 0x50 && buf[2] === 0x4e && buf[3] === 0x47) return 'png' if (buf[0] === 0xff && buf[1] === 0xd8 && buf[2] === 0xff) return 'jpeg' return 'other' } export const parseDataUrl = (dataUrl: any): { mime: string; buffer: Buffer } | null => { const s = String(dataUrl || '') const m = s.match(/^data:([^;,]*)(;[^,]*)?,(.*)$/s) if (!m) return null const isB64 = /;base64/i.test(m[2] || '') try { const buffer = isB64 ? Buffer.from(m[3], 'base64') : Buffer.from(decodeURIComponent(m[3]), 'utf8') return { mime: (m[1] || '').toLowerCase(), buffer } } catch { return null } } /** PNG/JPEG data URL for pdfmake, or null when the input cannot be used. */ export const normalizePdfImage = async (dataUrl: any): Promise => { const parsed = parseDataUrl(dataUrl) if (!parsed) return null const kind = sniff(parsed.buffer) if (kind === 'empty') return null if (kind === 'png') return `data:image/png;base64,${parsed.buffer.toString('base64')}` if (kind === 'jpeg') return `data:image/jpeg;base64,${parsed.buffer.toString('base64')}` try { const sharp = (await import('sharp')).default const png = await sharp(parsed.buffer, { animated: false }).png().toBuffer() return `data:image/png;base64,${png.toString('base64')}` } catch (err: any) { console.warn('[pdfImages] image dropped — cannot convert to PNG:', err?.message || err) return null } }