htmlpdf / puppeteer_pdf.js
ABDALLALSWAITI's picture
Update puppeteer_pdf.js
70df375 verified
#!/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 <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);
});