547 lines
18 KiB
JavaScript
547 lines
18 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Refined PDF Generation - NO WHITE SPACES
|
|
*
|
|
* Key Features:
|
|
* 1. Zero white spaces - comprehensive CSS reset
|
|
* 2. HTML content preprocessing to remove unwanted whitespace
|
|
* 3. Optimized page breaks with no extra spacing
|
|
* 4. Clean A4 format with minimal margins
|
|
* 5. No extra pages or blank spaces
|
|
*/
|
|
|
|
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 || 8002; // Different port
|
|
|
|
// 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));
|
|
|
|
/**
|
|
* Clean HTML content to remove unwanted whitespace
|
|
*/
|
|
function cleanHTMLContent(htmlContent) {
|
|
// Remove extra whitespace and normalize content
|
|
let cleaned = htmlContent
|
|
// Remove multiple spaces and normalize whitespace
|
|
.replace(/\s+/g, ' ')
|
|
// Remove whitespace around tags
|
|
.replace(/>\s+</g, '><')
|
|
// Remove leading/trailing whitespace
|
|
.trim()
|
|
// Remove empty lines
|
|
.replace(/\n\s*\n/g, '\n')
|
|
// Remove whitespace at start/end of lines
|
|
.replace(/^\s+|\s+$/gm, '')
|
|
// Remove multiple consecutive line breaks
|
|
.replace(/\n{3,}/g, '\n\n')
|
|
// Remove whitespace before closing tags
|
|
.replace(/\s+<\//g, '</')
|
|
// Remove whitespace after opening tags
|
|
.replace(/>\s+/g, '>')
|
|
// Remove whitespace in empty tags
|
|
.replace(/<(\w+)[^>]*>\s*<\/\1>/g, '')
|
|
// Remove comments that might contain whitespace
|
|
.replace(/<!--[\s\S]*?-->/g, '')
|
|
// Remove script and style content whitespace
|
|
.replace(/<(script|style)[^>]*>[\s\S]*?<\/\1>/gi, (match) => {
|
|
return match.replace(/\s+/g, ' ').trim();
|
|
});
|
|
|
|
return cleaned;
|
|
}
|
|
|
|
/**
|
|
* Generate PDF with ZERO white spaces
|
|
*/
|
|
async function generateNoWhitespacePDF(htmlContent, outputPDF) {
|
|
let browser = null;
|
|
|
|
try {
|
|
console.log("🚀 Starting NO-WHITESPACE PDF generation...");
|
|
|
|
// Clean the HTML content first
|
|
const cleanedHTML = cleanHTMLContent(htmlContent);
|
|
console.log("🧹 HTML content cleaned and whitespace removed");
|
|
|
|
// 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',
|
|
'--disable-web-security',
|
|
'--disable-features=VizDisplayCompositor'
|
|
]
|
|
});
|
|
|
|
const page = await browser.newPage();
|
|
|
|
// Set viewport to A4 dimensions
|
|
await page.setViewport({ width: 794, height: 1123 });
|
|
page.setDefaultTimeout(0);
|
|
|
|
// Create ZERO-WHITESPACE HTML template
|
|
const noWhitespaceHTML = `<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<style>
|
|
/* COMPLETE RESET - NO WHITESPACE */
|
|
* {
|
|
margin: 0 !important;
|
|
padding: 0 !important;
|
|
box-sizing: border-box !important;
|
|
border: 0 !important;
|
|
outline: 0 !important;
|
|
font-size: 100% !important;
|
|
vertical-align: baseline !important;
|
|
background: transparent !important;
|
|
text-decoration: none !important;
|
|
list-style: none !important;
|
|
quotes: none !important;
|
|
}
|
|
|
|
/* Remove all default spacing */
|
|
html, body, div, span, applet, object, iframe,
|
|
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
|
|
a, abbr, acronym, address, big, cite, code,
|
|
del, dfn, em, img, ins, kbd, q, s, samp,
|
|
small, strike, strong, sub, sup, tt, var,
|
|
b, u, i, center, dl, dt, dd, ol, ul, li,
|
|
fieldset, form, label, legend, table, caption,
|
|
tbody, tfoot, thead, tr, th, td, article, aside,
|
|
canvas, details, embed, figure, figcaption, footer,
|
|
header, hgroup, menu, nav, output, ruby, section,
|
|
summary, time, mark, audio, video {
|
|
margin: 0 !important;
|
|
padding: 0 !important;
|
|
border: 0 !important;
|
|
font-size: inherit !important;
|
|
vertical-align: baseline !important;
|
|
}
|
|
|
|
/* Body styling - NO SPACING */
|
|
body {
|
|
font-family: Arial, sans-serif !important;
|
|
line-height: 1.2 !important;
|
|
color: #000 !important;
|
|
background: #fff !important;
|
|
margin: 0 !important;
|
|
padding: 0 !important;
|
|
font-size: 12px !important;
|
|
overflow: visible !important;
|
|
}
|
|
|
|
/* A4 Page setup - MINIMAL MARGINS */
|
|
@page {
|
|
size: A4 !important;
|
|
margin: 0.2in !important;
|
|
}
|
|
|
|
/* Remove all spacing from containers */
|
|
.container, .content, .wrapper, .main, .section, .div {
|
|
margin: 0 !important;
|
|
padding: 0 !important;
|
|
border: none !important;
|
|
}
|
|
|
|
/* Text elements - NO SPACING */
|
|
h1, h2, h3, h4, h5, h6 {
|
|
margin: 0 !important;
|
|
padding: 0 !important;
|
|
font-weight: bold !important;
|
|
page-break-after: avoid !important;
|
|
page-break-inside: avoid !important;
|
|
}
|
|
|
|
p {
|
|
margin: 0 !important;
|
|
padding: 0 !important;
|
|
page-break-inside: avoid !important;
|
|
orphans: 1 !important;
|
|
widows: 1 !important;
|
|
}
|
|
|
|
/* Lists - NO SPACING */
|
|
ul, ol, li {
|
|
margin: 0 !important;
|
|
padding: 0 !important;
|
|
list-style: none !important;
|
|
}
|
|
|
|
/* Tables - NO SPACING */
|
|
table {
|
|
border-collapse: collapse !important;
|
|
border-spacing: 0 !important;
|
|
width: 100% !important;
|
|
margin: 0 !important;
|
|
padding: 0 !important;
|
|
page-break-inside: avoid !important;
|
|
}
|
|
|
|
td, th {
|
|
margin: 0 !important;
|
|
padding: 2px !important;
|
|
border: none !important;
|
|
vertical-align: top !important;
|
|
}
|
|
|
|
/* Images - NO SPACING */
|
|
img {
|
|
max-width: 100% !important;
|
|
height: auto !important;
|
|
display: block !important;
|
|
margin: 0 !important;
|
|
padding: 0 !important;
|
|
border: none !important;
|
|
page-break-inside: avoid !important;
|
|
}
|
|
|
|
/* Links - NO SPACING */
|
|
a {
|
|
color: inherit !important;
|
|
text-decoration: none !important;
|
|
margin: 0 !important;
|
|
padding: 0 !important;
|
|
}
|
|
|
|
/* Page breaks - NO SPACING */
|
|
.page-break, .pagebreak, .new-page {
|
|
page-break-before: always !important;
|
|
height: 0 !important;
|
|
margin: 0 !important;
|
|
padding: 0 !important;
|
|
border: none !important;
|
|
}
|
|
|
|
.no-break {
|
|
page-break-inside: avoid !important;
|
|
}
|
|
|
|
/* Remove any elements that might cause spacing */
|
|
.hidden, .invisible, [style*="display: none"], [style*="visibility: hidden"] {
|
|
display: none !important;
|
|
}
|
|
|
|
/* Remove floating elements that might cause spacing */
|
|
.clearfix::after, .clear::after {
|
|
content: "" !important;
|
|
display: table !important;
|
|
clear: both !important;
|
|
height: 0 !important;
|
|
margin: 0 !important;
|
|
padding: 0 !important;
|
|
}
|
|
|
|
/* Ensure no extra spacing from any source */
|
|
br {
|
|
line-height: 0 !important;
|
|
margin: 0 !important;
|
|
padding: 0 !important;
|
|
}
|
|
|
|
hr {
|
|
border: none !important;
|
|
height: 0 !important;
|
|
margin: 0 !important;
|
|
padding: 0 !important;
|
|
}
|
|
|
|
/* Remove any potential spacing from form elements */
|
|
input, textarea, select, button {
|
|
margin: 0 !important;
|
|
padding: 0 !important;
|
|
border: none !important;
|
|
}
|
|
|
|
/* Ensure content fits without extra pages */
|
|
.content-wrapper {
|
|
width: 100% !important;
|
|
margin: 0 !important;
|
|
padding: 0 !important;
|
|
overflow: visible !important;
|
|
}
|
|
|
|
/* Remove any potential spacing from flexbox or grid */
|
|
.flex, .grid, [style*="display: flex"], [style*="display: grid"] {
|
|
margin: 0 !important;
|
|
padding: 0 !important;
|
|
gap: 0 !important;
|
|
}
|
|
|
|
/* Force remove any remaining spacing */
|
|
*:before, *:after {
|
|
margin: 0 !important;
|
|
padding: 0 !important;
|
|
content: none !important;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="content-wrapper">${cleanedHTML.replace(/<body[^>]*>|<\/body>/gi, '')}</div>
|
|
</body>
|
|
</html>`;
|
|
|
|
// Set content with no-whitespace HTML
|
|
await page.setContent(noWhitespaceHTML, {
|
|
waitUntil: 'networkidle0',
|
|
timeout: 0
|
|
});
|
|
|
|
// Wait for content to load
|
|
await page.waitForTimeout(2000);
|
|
|
|
// Additional CSS injection to ensure no spacing
|
|
await page.addStyleTag({
|
|
content: `
|
|
/* Final spacing removal */
|
|
* {
|
|
-webkit-print-color-adjust: exact !important;
|
|
color-adjust: exact !important;
|
|
margin: 0 !important;
|
|
padding: 0 !important;
|
|
}
|
|
|
|
/* Remove any remaining spacing */
|
|
body, html {
|
|
overflow: visible !important;
|
|
margin: 0 !important;
|
|
padding: 0 !important;
|
|
}
|
|
|
|
/* Ensure no extra pages */
|
|
.page-break {
|
|
page-break-before: always !important;
|
|
height: 0 !important;
|
|
margin: 0 !important;
|
|
padding: 0 !important;
|
|
}
|
|
`
|
|
});
|
|
|
|
console.log("📄 Generating NO-WHITESPACE A4 PDF...");
|
|
|
|
const pdfOptions = {
|
|
path: outputPDF,
|
|
printBackground: true,
|
|
margin: {
|
|
top: '0.1in', // Minimal margins
|
|
right: '0.1in',
|
|
bottom: '0.1in',
|
|
left: '0.1in'
|
|
},
|
|
scale: 1.0,
|
|
format: 'A4',
|
|
preferCSSPageSize: false,
|
|
displayHeaderFooter: false, // No headers/footers to avoid spacing
|
|
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(`✅ NO-WHITESPACE A4 PDF generated: ${outputPDF}`);
|
|
console.log(`📐 Format: A4 (210mm x 297mm)`);
|
|
console.log(`📏 Margins: 0.1in all sides (minimal)`);
|
|
console.log(`🔍 Scale: 1.0`);
|
|
console.log(`📊 File size: ${pdfSizeMB.toFixed(2)} MB`);
|
|
console.log(`🚫 White spaces: ELIMINATED`);
|
|
|
|
} 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 - NO WHITESPACES
|
|
*/
|
|
app.post('/generate-no-whitespace-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 NO-WHITESPACE 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, '_')}_nowhitespace_${randomId}.pdf` :
|
|
`property_brochure_nowhitespace_${randomId}.pdf`;
|
|
|
|
const outputPDF = path.join(TEMP_FOLDER, filename);
|
|
|
|
// Generate PDF with no whitespaces
|
|
await generateNoWhitespacePDF(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: "NO-WHITESPACE PDF generated successfully - All white spaces eliminated",
|
|
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",
|
|
features: [
|
|
"Zero white spaces",
|
|
"Minimal margins (0.1in)",
|
|
"HTML content preprocessing",
|
|
"Complete CSS reset",
|
|
"No extra pages",
|
|
"Clean A4 format"
|
|
]
|
|
};
|
|
|
|
console.log(`🔗 NO-WHITESPACE 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(`❌ NO-WHITESPACE 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-no-whitespace',
|
|
features: [
|
|
'zero_white_spaces',
|
|
'minimal_margins',
|
|
'html_preprocessing',
|
|
'complete_css_reset',
|
|
'no_extra_pages',
|
|
'clean_a4_format',
|
|
'download_links',
|
|
'base64_response'
|
|
]
|
|
});
|
|
});
|
|
|
|
// Start server
|
|
app.listen(PORT, () => {
|
|
console.log(`🚀 NO-WHITESPACE 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(`🚫 WHITESPACE: ELIMINATED - Zero spacing, minimal margins, clean output`);
|
|
});
|
|
|
|
module.exports = app;
|
|
|