Spaces:
Sleeping
Sleeping
| 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 <html_file_path> <aspect_ratio>'); | |
| 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); | |
| }); |