#!/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(`HTML File: ${htmlFilePath}`); console.log(`Aspect Ratio: ${aspectRatio}`); // Get absolute paths const absoluteHtmlPath = path.resolve(htmlFilePath); const htmlDir = path.dirname(absoluteHtmlPath); console.log(`HTML Directory: ${htmlDir}`); // Read HTML content let htmlContent = fs.readFileSync(absoluteHtmlPath, 'utf8'); // List image files in the directory const imageFiles = fs.readdirSync(htmlDir).filter(file => /\.(jpg|jpeg|png|gif|svg|webp|bmp)$/i.test(file) ); console.log(`Found ${imageFiles.length} image file(s) in directory:`, imageFiles); // Launch browser with system Chromium const browserOptions = { headless: true, 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', '--disable-software-rasterizer', '--disable-dev-tools', '--no-first-run', '--no-default-browser-check', '--disable-breakpad', '--disable-crash-reporter', '--no-crashpad', '--disable-features=Crashpad', '--crash-dumps-dir=/tmp', '--disable-component-update' ] }; browserOptions.env = { ...process.env, CHROME_CRASHPAD_PIPE_NAME: 'NONE' }; // 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: 1 // Changed from 2 to 1 to preserve original sizing }); console.log(`Viewport set to: ${viewportWidth}x${viewportHeight}`); // Construct file:// URL for the HTML directory const baseUrl = 'file://' + htmlDir + '/'; console.log(`Base URL: ${baseUrl}`); // Load HTML content with file:// protocol console.log('Loading HTML content...'); await page.goto(baseUrl + path.basename(absoluteHtmlPath), { waitUntil: ['networkidle0', 'domcontentloaded'], timeout: 30000 }); // 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")); console.log('Found', selectors.length, 'img tags'); await Promise.all([ document.fonts.ready, ...selectors.map((img) => { console.log('Image src:', img.src, 'Complete:', img.complete); if (img.complete) return Promise.resolve(); return new Promise((resolve) => { img.addEventListener("load", () => { console.log('Loaded:', img.src); resolve(); }); img.addEventListener("error", () => { console.log('Error loading:', img.src); resolve(); }); setTimeout(() => { console.log('Timeout:', img.src); resolve(); }, 5000); }); }), ]); }); // Check which images loaded successfully const imageStatus = await page.evaluate(() => { const images = Array.from(document.querySelectorAll('img')); return images.map(img => ({ src: img.src, complete: img.complete, naturalWidth: img.naturalWidth, naturalHeight: img.naturalHeight, width: img.width, height: img.height })); }); console.log('Image loading status:', JSON.stringify(imageStatus, null, 2)); // 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, margin: { top: '0mm', right: '0mm', bottom: '0mm', left: '0mm' }, displayHeaderFooter: false, scale: 1.0 }; // Set dimensions based on aspect ratio if (aspectRatio === '16:9') { pdfOptions.width = '288mm'; pdfOptions.height = '162mm'; pdfOptions.landscape = false; console.log('PDF format: Custom 16:9 (288mm x 162mm)'); } 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.width = '162mm'; pdfOptions.height = '288mm'; pdfOptions.landscape = false; console.log('PDF format: Custom 9:16 (162mm x 288mm)'); } else { pdfOptions.width = '297mm'; pdfOptions.height = '210mm'; 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); });