#!/usr/bin/env node /** * Fixed PDF Generation with Proper Page Breaks and A4 Format * * Fixes: * 1. No unexpected blank pages * 2. Correct page size (A4) * 3. Proper handling of page breaks using CSS * 4. Page numbers in footer should match actual HTML pages */ const express = require('express'); const puppeteer = require('puppeteer'); const bodyParser = require('body-parser'); const cors = require('cors'); const fs = require('fs'); const path = require('path'); const crypto = require('crypto'); const app = express(); const PORT = process.env.PORT || 8001; // Different port to avoid conflicts // Configure temp folder const TEMP_FOLDER = 'temp'; if (!fs.existsSync(TEMP_FOLDER)) { fs.mkdirSync(TEMP_FOLDER, { recursive: true }); } // Middleware app.use(cors()); app.use(bodyParser.json({ limit: '100mb' })); app.use(bodyParser.urlencoded({ extended: true, limit: '100mb' })); // Serve static files from temp folder app.use('/download', express.static(TEMP_FOLDER)); /** * Generate A4 PDF with proper page breaks and no extra pages */ async function generateFixedA4PDF(htmlContent, outputPDF) { let browser = null; try { console.log("🚀 Starting FIXED PDF generation..."); // Launch browser browser = await puppeteer.launch({ headless: true, args: [ '--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage', '--disable-accelerated-2d-canvas', '--no-first-run', '--no-zygote', '--disable-gpu' ] }); const page = await browser.newPage(); // Set viewport to A4 dimensions (210mm x 297mm = 794px x 1123px at 96 DPI) await page.setViewport({ width: 794, height: 1123 }); page.setDefaultTimeout(0); // Infinite timeout // Create improved HTML with proper page break CSS const fixedHTML = `
${htmlContent.replace(/]*>|<\/body>/gi, '')}
`; // Set content with fixed HTML await page.setContent(fixedHTML, { waitUntil: 'networkidle0', timeout: 0 // Infinite timeout }); // Wait for content to load completely await page.waitForTimeout(3000); // Wait 3 seconds for content to settle // Wait for all resources to load try { await page.waitForNetworkIdle({ timeout: 0 }); } catch (error) { // Ignore timeout errors for networkidle } // Inject additional CSS to fix any remaining page break issues await page.addStyleTag({ content: ` /* Additional fixes for page breaks */ * { -webkit-print-color-adjust: exact !important; color-adjust: exact !important; } /* Remove any elements that might cause extra pages */ .hidden, .invisible, [style*="display: none"] { display: none !important; } /* Ensure proper page flow */ body { overflow: visible !important; } /* Fix for any floating elements */ .float-clear { clear: both; } ` }); console.log("📄 Generating FIXED A4 PDF..."); const pdfOptions = { path: outputPDF, printBackground: true, margin: { top: '0.5in', right: '0.5in', bottom: '0.8in', // Extra space for footer left: '0.5in' }, scale: 1.0, format: 'A4', preferCSSPageSize: true, // Use CSS page size displayHeaderFooter: true, // Enable header/footer for page numbers headerTemplate: '
', // Empty header footerTemplate: `
Page of
`, omitBackground: false }; // Generate PDF await page.pdf(pdfOptions); await browser.close(); // Get PDF file size for logging const pdfStats = fs.statSync(outputPDF); const pdfSizeMB = pdfStats.size / (1024 * 1024); console.log(`✅ FIXED A4 PDF generated: ${outputPDF}`); console.log(`📐 Format: A4 (210mm x 297mm)`); console.log(`📏 Margins: 0.5in all sides, 0.8in bottom for footer`); console.log(`🔍 Scale: 1.0`); console.log(`📊 File size: ${pdfSizeMB.toFixed(2)} MB`); console.log(`📄 Page numbers: Enabled in footer`); } catch (error) { if (error.name === 'TimeoutError') { throw new Error("Timeout: Page took too long to load."); } console.error(`❌ PDF generation error: ${error.message}`); throw error; } finally { // Cleanup if (browser) { try { await browser.close(); } catch (error) { console.error('Error closing browser:', error); } } } } /** * Main PDF generation endpoint with fixes */ app.post('/generate-fixed-pdf', async (req, res) => { try { // Get request data let inputContent = null; let outputName = null; let returnDownloadLink = false; if (req.is('application/json')) { try { const data = req.body; if (data) { if (data.input) { inputContent = data.input; outputName = data.output || null; returnDownloadLink = data.return_download_link || false; } else if (data.html) { inputContent = data.html; outputName = data.filename || null; returnDownloadLink = data.return_download_link || false; } else if (data.content) { inputContent = data.content; outputName = data.name || null; returnDownloadLink = data.return_download_link || false; } } } catch (parseError) { console.error('JSON parse error:', parseError); return res.status(400).json({ error: 'Invalid JSON in request body' }); } } else if (req.is('application/x-www-form-urlencoded')) { inputContent = req.body.input || req.body.html || req.body.content; outputName = req.body.output || req.body.filename || req.body.name; returnDownloadLink = req.body.return_download_link === 'true' || req.body.return_download_link === true; } else { try { const rawData = JSON.parse(req.body); inputContent = rawData.input || rawData.html || rawData.content; outputName = rawData.output || rawData.filename || rawData.name; returnDownloadLink = rawData.return_download_link || false; } catch (error) { return res.status(400).json({ error: 'Invalid request format' }); } } // Validate input if (!inputContent) { return res.status(400).json({ error: 'No HTML content provided' }); } console.log(`📥 Received FIXED PDF request - Download Link: ${returnDownloadLink}`); // Generate unique filename const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const randomId = crypto.randomBytes(4).toString('hex'); const filename = outputName ? `${outputName.replace(/[^a-zA-Z0-9-_]/g, '_')}_fixed_${randomId}.pdf` : `property_brochure_fixed_${randomId}.pdf`; const outputPDF = path.join(TEMP_FOLDER, filename); // Generate PDF with fixes await generateFixedA4PDF(inputContent, outputPDF); if (returnDownloadLink) { // Return download link const downloadUrl = `https://salesforce.tech4biz.io/download/${filename}`; const pdfStats = fs.statSync(outputPDF); const pdfSizeMB = pdfStats.size / (1024 * 1024); const result = { success: true, message: "FIXED PDF generated successfully - No extra pages, proper A4 size, correct page breaks, and accurate page numbers", download_url: downloadUrl, filename: filename, file_size_mb: pdfSizeMB.toFixed(2), expires_at: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(), generated_at: new Date().toISOString(), pdf_id: randomId, status: "download_ready", fixes_applied: [ "No unexpected blank pages", "Correct A4 page size", "Proper CSS page breaks", "Accurate page numbers in footer" ] }; console.log(`🔗 FIXED PDF ready for download: ${downloadUrl}`); console.log(`📊 File size: ${pdfSizeMB.toFixed(2)} MB`); res.json(result); } else { // Return base64 content const pdfBuffer = fs.readFileSync(outputPDF); const base64Content = pdfBuffer.toString('base64'); // Clean up the file if (fs.existsSync(outputPDF)) { fs.unlinkSync(outputPDF); } res.setHeader('Content-Type', 'application/pdf'); res.setHeader('Content-Disposition', `attachment; filename="${filename}"`); res.send(Buffer.from(base64Content, 'base64')); } } catch (error) { console.error(`❌ FIXED PDF API Error: ${error.message}`); res.status(500).json({ error: error.message }); } }); /** * Health check endpoint */ app.get('/health', (req, res) => { res.json({ status: 'healthy', timestamp: new Date().toISOString(), version: '1.0.0-fixed', features: [ 'no_extra_blank_pages', 'correct_a4_size', 'proper_page_breaks', 'accurate_page_numbers', 'download_links', 'base64_response' ] }); }); // Start server app.listen(PORT, () => { console.log(`🚀 FIXED PDF Generator API running on port ${PORT}`); console.log(`📁 Temp folder: ${TEMP_FOLDER}`); console.log(`🔗 Download endpoint: http://localhost:${PORT}/download/:filename`); console.log(`📊 Health check: http://localhost:${PORT}/health`); console.log(`✨ FIXES: No extra pages, A4 size, proper breaks, accurate page numbers`); }); module.exports = app;