import puppeteer from 'puppeteer'; import { resolve, basename, dirname, extname } from 'path'; import { existsSync, mkdirSync } from 'fs'; import { fileURLToPath } from 'url'; import { createServer } from 'http'; import { readFile } from 'fs/promises'; const __dirname = dirname(fileURLToPath(import.meta.url)); const PROJECT_ROOT = resolve(__dirname, '..'); const OUTPUT_DIR = resolve(PROJECT_ROOT, 'output/pdf'); const MIME_TYPES = { '.html': 'text/html', '.css': 'text/css', '.js': 'application/javascript', '.mjs': 'application/javascript', '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.webp': 'image/webp', '.svg': 'image/svg+xml', '.json': 'application/json', '.woff': 'font/woff', '.woff2': 'font/woff2', '.ttf': 'font/ttf', }; async function generatePdf(htmlPath, outDirOverride) { const absolutePath = resolve(htmlPath); if (!existsSync(absolutePath)) { console.error(`File not found: ${absolutePath}`); process.exit(1); } const outDir = outDirOverride ? resolve(outDirOverride) : OUTPUT_DIR; mkdirSync(outDir, { recursive: true }); const pdfName = basename(absolutePath, '.html') + '.pdf'; const pdfPath = resolve(outDir, pdfName); // Serve the project root so the HTML can load /templates/cv-style.css and any other assets. const server = createServer(async (req, res) => { const filePath = resolve( PROJECT_ROOT, decodeURIComponent(req.url).replace(/^\//, ''), ); try { const data = await readFile(filePath); const ext = extname(filePath); res.writeHead(200, { 'Content-Type': MIME_TYPES[ext] || 'application/octet-stream', }); res.end(data); } catch { res.writeHead(404); res.end('Not found'); } }); await new Promise((r) => server.listen(0, '127.0.0.1', r)); const port = server.address().port; const relPath = absolutePath.replace(PROJECT_ROOT + '/', ''); const browser = await puppeteer.launch({ headless: true, args: ['--no-sandbox'], }); const page = await browser.newPage(); await page.goto(`http://127.0.0.1:${port}/${relPath}`, { waitUntil: 'networkidle0', timeout: 30000, }); // Wait for web fonts (if any) to be ready before rasterizing. await page.evaluate(async () => { if (document.fonts && document.fonts.ready) { await document.fonts.ready; } }); await page.pdf({ path: pdfPath, format: 'A4', printBackground: true, margin: { top: 0, right: 0, bottom: 0, left: 0 }, preferCSSPageSize: true, }); await browser.close(); server.close(); console.log(`PDF generated: ${pdfPath}`); return pdfPath; } const args = process.argv.slice(2); if (args.length === 0) { console.error('Usage: node generate-pdf.mjs [out-dir]'); process.exit(1); } const [htmlFile, outDir] = args; generatePdf(htmlFile, outDir);