/** * Copyright (c) 2026 Niolla * All rights reserved. */ // app/utils/pdfGenerator.js const path = require("path"); const ejs = require("ejs"); const puppeteer = require("puppeteer"); const fs = require("fs"); const { imageToBase64 } = require("./imageToBase64.utill"); const { log, runningEnvironment } = require("./consoleLog.utill"); let browser; let executablePath = null; const isProd = runningEnvironment(); if (isProd) { executablePath = "/usr/bin/chromium"; } else { executablePath = puppeteer.executablePath(); } /** * Launch or reuse Puppeteer browser safely */ async function getBrowser() { if (!browser || !browser.isConnected?.()) { console.log("🚀 Launching Chromium from Docker system path..."); browser = await puppeteer.launch({ headless: "new", executablePath: executablePath, args: [ "--no-sandbox", "--disable-setuid-sandbox", "--disable-dev-shm-usage", "--disable-gpu", "--no-zygote" ], }); console.log("✅ Browser launched"); } return browser; } /** * Close browser safely */ async function closeBrowser() { if (browser) { try { await browser.close(); } catch (e) {} browser = null; } } /** * Core PDF renderer */ async function renderPdf(templateFile, data = {}, opts = {}) { const templatePath = path.join(__dirname, "../templates", templateFile); console.log("📝 Rendering:", templateFile); // Convert image to base64 if needed if (data.logoUrl && !data.logoBase64) { try { data.logoBase64 = await imageToBase64(data.logoUrl); } catch (err) { console.error("❌ Logo conversion failed:", err.message); data.logoBase64 = null; } } const html = await ejs.renderFile(templatePath, data); const browserInstance = await getBrowser(); const page = await browserInstance.newPage(); try { // Safety timeouts await page.setDefaultTimeout(60000); await page.setDefaultNavigationTimeout(60000); await page.setViewport({ width: 1200, height: 800 }); console.log("📄 Loading HTML..."); await page.setContent(html, { waitUntil: "networkidle0" }); await page.emulateMediaType("print"); // Wait for fonts await page.evaluate(() => document.fonts?.ready || Promise.resolve()); const margin = { top: 20, bottom: 20, left: 10, right: 10, ...opts.margin, }; // Optional: measure content (kept for logging only) const contentHeightPx = await page.evaluate(() => { const body = document.body; const htmlEl = document.documentElement; return Math.max( body.scrollHeight, body.offsetHeight, htmlEl.scrollHeight, htmlEl.offsetHeight ); }); console.log("📏 Content height:", contentHeightPx, "px"); // ❗ REMOVED scaling logic (causes PDF distortion in production) const showPageNumbers = opts.showPageNumbers === true; console.log("⚙️ Generating PDF..."); const pdfBuffer = await page.pdf({ format: "A4", printBackground: true, preferCSSPageSize: true, margin: { top: `${margin.top}mm`, bottom: `${margin.bottom}mm`, left: `${margin.left}mm`, right: `${margin.right}mm`, }, displayHeaderFooter: showPageNumbers, headerTemplate: `
`, footerTemplate: showPageNumbers ? `