/** * exportPng.ts * Renders each slide to a PNG at 1080x1080 using Puppeteer. * Returns an array of file paths for the generated PNGs. */ import fs from 'fs'; import path from 'path'; import type { SlideContent } from '../schema/carouselSchema'; import type { Theme } from '../render/themes'; import { renderSlideToHtml } from '../render/renderSlides'; /** * Export all slides to individual PNG files. * Creates the outputDir/slides_preview/ subdirectory. * * @param slides - Ordered array of SlideContent objects * @param theme - Visual theme * @param outputDir - Base output directory (e.g. "output/job-carousel") * @returns - Array of absolute file paths to generated PNGs */ export async function exportToPngs( slides: SlideContent[], theme: Theme, outputDir: string ): Promise { // Lazy-import puppeteer to avoid hard crash if not installed let puppeteer: typeof import('puppeteer'); try { puppeteer = await import('puppeteer'); } catch (err) { throw new Error( 'Puppeteer is not installed. Run: npm install puppeteer\n' + 'If you cannot install it, use --no-render to skip PNG/PDF export.' ); } const previewDir = path.join(outputDir, 'slides_preview'); fs.mkdirSync(previewDir, { recursive: true }); const browser = await puppeteer.launch({ headless: true, args: [ '--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage', '--font-render-hinting=none', ], }); const pngPaths: string[] = []; try { const page = await browser.newPage(); await page.setViewport({ width: 1080, height: 1080, deviceScaleFactor: 2 }); for (let i = 0; i < slides.length; i++) { const slide = slides[i]; const html = renderSlideToHtml(slide, theme, i); // Load HTML content directly (no file write needed for rendering) await page.setContent(html, { waitUntil: 'networkidle0', // Wait for Google Fonts to load timeout: 30000, }); // Give fonts a moment to render (belt-and-suspenders) await page.evaluate(() => document.fonts.ready); const filename = `slide_${String(i + 1).padStart(2, '0')}.png`; const filepath = path.join(previewDir, filename); await page.screenshot({ path: filepath as `${string}.png`, type: 'png', clip: { x: 0, y: 0, width: 1080, height: 1080 }, omitBackground: false, }); pngPaths.push(filepath); console.log(` ✓ Exported: ${filename}`); } } finally { await browser.close(); } return pngPaths; }