import satori from 'satori'; import { Resvg } from '@resvg/resvg-js'; import { readFileSync } from 'node:fs'; import { join } from 'node:path'; interface OgImageProps { title: string; description?: string; engine?: string; } function loadFont(): ArrayBuffer { // In prod: dist/client/fonts/ In dev: public/fonts/ const candidates = [ join(process.cwd(), 'dist', 'client', 'fonts', 'Inter-SemiBold.ttf'), join(process.cwd(), 'public', 'fonts', 'Inter-SemiBold.ttf'), ]; for (const path of candidates) { try { return readFileSync(path).buffer as ArrayBuffer; } catch { // try next } } throw new Error('Inter-SemiBold.ttf not found'); } let fontData: ArrayBuffer | null = null; function getFont(): ArrayBuffer { if (!fontData) { try { fontData = loadFont(); } catch (err) { console.error('[og-renderer] Font load failed:', err); throw err; } } return fontData; } // Small waveform logo — a sine-like path rendered as SVG text function WaveformLogo() { return (
SpiceBook
); } export async function renderOgImage({ title, description, engine, }: OgImageProps): Promise { const svg = await satori(
{/* Top: branding */} {/* Center: title + description */}
40 ? '42px' : '52px', fontWeight: 600, color: '#f1f5f9', // slate-100 lineHeight: 1.2, maxWidth: '1000px', }} > {title}
{description && (
{description.length > 120 ? description.slice(0, 117) + '...' : description}
)}
{/* Bottom: engine badge + accent bar */}
{engine ? (
{engine}
circuit simulation
) : (
)}
spicebook.warehack.ing
{/* Accent bar at very bottom */}
, { width: 1200, height: 630, fonts: [ { name: 'Inter', data: getFont(), weight: 600, style: 'normal', }, ], } ); const resvg = new Resvg(svg, { fitTo: { mode: 'width', value: 1200 }, }); const pngBuffer = resvg.render().asPng(); return new Uint8Array(pngBuffer); }