#!/usr/bin/env node const puppeteer = require('puppeteer'); const path = require('path'); const fs = require('fs'); // Get command line arguments const args = process.argv.slice(2); if (args.length < 2) { console.error('Usage: node puppeteer_pdf.js '); process.exit(1); } const htmlFilePath = args[0]; const aspectRatio = args[1]; // Check if HTML file exists if (!fs.existsSync(htmlFilePath)) { console.error(`Error: HTML file not found at ${htmlFilePath}`); process.exit(1); } // Main conversion function async function convertToPDF() { let browser; try { console.log('Starting PDF conversion...'); console.log(`Aspect Ratio: ${aspectRatio}`); // Launch browser with system Chromium const browserOptions = { headless: 'new', args: [ '--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage', '--disable-web-security', '--disable-features=IsolateOrigins', '--disable-site-isolation-trials', '--disable-accelerated-2d-canvas', '--disable-gpu', '--single-process', '--no-zygote', '--disable-software-rasterizer', '--disable-dev-tools', '--no-first-run', '--no-default-browser-check', '--disable-breakpad', '--disable-crash-reporter', '--crash-dumps-dir=/tmp', '--disable-component-update' ] }; // Try to use system Chromium if available const chromiumPaths = [ '/usr/bin/chromium', '/usr/bin/chromium-browser', '/usr/bin/google-chrome', '/usr/bin/google-chrome-stable' ]; for (const chromiumPath of chromiumPaths) { if (fs.existsSync(chromiumPath)) { browserOptions.executablePath = chromiumPath; console.log(`Using Chromium at: ${chromiumPath}`); break; } } browser = await puppeteer.launch(browserOptions); const page = await browser.newPage(); // Set viewport for better rendering based on aspect ratio let viewportWidth = 1920; let viewportHeight = 1080; if (aspectRatio === '9:16') { viewportWidth = 1080; viewportHeight = 1920; } else if (aspectRatio === '1:1') { viewportWidth = 1080; viewportHeight = 1080; } await page.setViewport({ width: viewportWidth, height: viewportHeight, deviceScaleFactor: 2 }); console.log(`Viewport set to: ${viewportWidth}x${viewportHeight}`); // Load the HTML file const absoluteHtmlPath = path.resolve(htmlFilePath); const htmlContent = fs.readFileSync(absoluteHtmlPath, 'utf8'); // Set base URL for relative paths const baseUrl = 'file://' + path.dirname(absoluteHtmlPath) + '/'; console.log('Loading HTML content...'); await page.setContent(htmlContent, { waitUntil: ['networkidle0', 'domcontentloaded'], timeout: 30000 }); // Inject base tag for relative paths await page.evaluate((baseUrl) => { const base = document.createElement('base'); base.href = baseUrl; document.head.appendChild(base); }, baseUrl); // Wait for all images and fonts to load console.log('Waiting for images and fonts...'); await page.evaluate(async () => { const selectors = Array.from(document.querySelectorAll("img")); await Promise.all([ document.fonts.ready, ...selectors.map((img) => { if (img.complete) return Promise.resolve(); return new Promise((resolve) => { img.addEventListener("load", resolve); img.addEventListener("error", resolve); setTimeout(resolve, 5000); }); }), ]); }); // Count pages for logging const pageCount = await page.evaluate(() => { const pages = document.querySelectorAll('.page, .slide, section.page, article.page'); return pages.length || 'unknown'; }); console.log(`Detected ${pageCount} page element(s) in HTML`); // Emulate screen media (not print) to preserve CSS await page.emulateMediaType('screen'); // Additional wait for rendering and animations console.log('Waiting for final render...'); await new Promise(resolve => setTimeout(resolve, 2000)); // Generate PDF path const pdfPath = htmlFilePath.replace('.html', '.pdf'); // Configure PDF options based on aspect ratio let pdfOptions = { path: pdfPath, printBackground: true, preferCSSPageSize: true, // CRITICAL: Respect CSS @page rules margin: { top: '0mm', right: '0mm', bottom: '0mm', left: '0mm' }, displayHeaderFooter: false, scale: 1.0 // No scaling to preserve exact dimensions }; // Set dimensions based on aspect ratio if (aspectRatio === '16:9') { pdfOptions.format = 'A4'; pdfOptions.landscape = true; pdfOptions.width = '297mm'; pdfOptions.height = '210mm'; console.log('PDF format: A4 Landscape (297mm x 210mm)'); } else if (aspectRatio === '1:1') { pdfOptions.width = '210mm'; pdfOptions.height = '210mm'; pdfOptions.landscape = false; console.log('PDF format: Square (210mm x 210mm)'); } else if (aspectRatio === '9:16') { pdfOptions.format = 'A4'; pdfOptions.landscape = false; pdfOptions.width = '210mm'; pdfOptions.height = '297mm'; console.log('PDF format: A4 Portrait (210mm x 297mm)'); } else { pdfOptions.format = 'A4'; pdfOptions.landscape = true; console.log('PDF format: A4 Landscape (default)'); } // Generate the PDF console.log('Generating PDF...'); await page.pdf(pdfOptions); // Verify PDF was created if (fs.existsSync(pdfPath)) { const stats = fs.statSync(pdfPath); console.log(`Successfully converted ${htmlFilePath} to ${pdfPath}`); console.log(`PDF size: ${(stats.size / 1024).toFixed(2)} KB`); process.exit(0); } else { console.error('PDF file was not created'); process.exit(1); } } catch (error) { console.error(`PDF conversion failed: ${error.message}`); console.error(error.stack); process.exit(1); } finally { if (browser) { await browser.close(); } } } // Run conversion convertToPDF().catch(error => { console.error('Unhandled error:', error); process.exit(1); });