import * as fs from 'fs'; import * as path from 'path'; type SharpLike = (input?: unknown, options?: unknown) => { png: () => { toBuffer: () => Promise } }; let sharpFn: SharpLike | null = null; async function getSharp(): Promise { if (sharpFn) return sharpFn; // eslint-disable-next-line @typescript-eslint/no-explicit-any const mod: any = await import('sharp'); sharpFn = (mod?.default || mod) as SharpLike; return sharpFn; } const PUBLIC_DIR = path.join(process.cwd(), 'public'); async function fetchBytes(url: string): Promise { const res = await fetch(url); if (!res.ok) throw new Error(`Failed to fetch ${url}: ${res.status} ${res.statusText}`); return new Uint8Array(await res.arrayBuffer()); } async function readBytesFromPublic(localPath: string): Promise { const abs = path.join(PUBLIC_DIR, localPath.replace(/^\//, '')); return new Uint8Array(fs.readFileSync(abs)); } function transformLogoSvgToPrintBlack(svg: string): string { return svg .replace(/fill\s*:\s*white/gi, 'fill:#000000') .replace(/fill\s*=\s*"white"/gi, 'fill="#000000"') .replace(/fill\s*=\s*'white'/gi, "fill='#000000'") .replace(/fill\s*:\s*#[0-9a-fA-F]{6}/gi, 'fill:#000000') .replace(/fill\s*=\s*"#[0-9a-fA-F]{6}"/gi, 'fill="#000000"') .replace(/fill\s*=\s*'#[0-9a-fA-F]{6}'/gi, "fill='#000000'"); } async function toPngBytes(inputBytes: Uint8Array, inputHint: string): Promise { const ext = (path.extname(inputHint).toLowerCase() || '').replace('.', ''); if (ext === 'png') return inputBytes; if (ext === 'svg' && (/\/media\/logo\.svg$/i.test(inputHint) || /\/logo-blue\.svg$/i.test(inputHint))) { const svg = Buffer.from(inputBytes).toString('utf8'); inputBytes = new Uint8Array(Buffer.from(transformLogoSvgToPrintBlack(svg), 'utf8')); } const sharp = await getSharp(); return new Uint8Array(await sharp(Buffer.from(inputBytes)).png().toBuffer()); } function toDataUrlPng(bytes: Uint8Array): string { return `data:image/png;base64,${Buffer.from(bytes).toString('base64')}`; } export async function loadImageAsPngDataUrl(src: string | null): Promise { if (!src) return null; try { if (src.startsWith('/')) { const bytes = await readBytesFromPublic(src); const png = await toPngBytes(bytes, src); return toDataUrlPng(png); } const bytes = await fetchBytes(src); const png = await toPngBytes(bytes, src); return toDataUrlPng(png); } catch { return null; } } export async function loadQrAsPngDataUrl(data: string): Promise { try { const safe = encodeURIComponent(data); const url = `https://api.qrserver.com/v1/create-qr-code/?size=180x180&data=${safe}`; const bytes = await fetchBytes(url); const png = await toPngBytes(bytes, url); return toDataUrlPng(png); } catch { return null; } }