v1.0.0-rc

This commit is contained in:
Ubuntu 2025-09-03 23:52:13 +05:30
commit 9a76e54764
17 changed files with 6765 additions and 0 deletions

119
.gitignore vendored Normal file
View File

@ -0,0 +1,119 @@
# Dependencies
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Coverage directory used by tools like istanbul
coverage/
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage
.grunt
# Bower dependency directory
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons
build/Release
# Dependency directories
jspm_packages/
# TypeScript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
.env.production
# parcel-bundler cache
.cache
.parcel-cache
# Next.js build output
.next
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
public
# Storybook build outputs
.out
.storybook-out
# Temporary folders
tmp/
temp/
# Logs
logs
*.log
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Generated PDFs
*.pdf
# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
# IDE files
.vscode/
.idea/
*.swp
*.swo
*~
# Docker
.dockerignore
Dockerfile
docker-compose.yml

578
download-popup.html Normal file
View File

@ -0,0 +1,578 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PDF Download Popup Interface</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Arial', sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.header {
text-align: center;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.5rem;
margin-bottom: 10px;
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
}
.header p {
font-size: 1.1rem;
opacity: 0.9;
}
.form-container {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 20px;
padding: 30px;
margin-bottom: 30px;
border: 1px solid rgba(255, 255, 255, 0.2);
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 8px;
font-weight: bold;
font-size: 1.1rem;
}
textarea {
width: 100%;
min-height: 200px;
padding: 15px;
border: none;
border-radius: 10px;
background: rgba(255, 255, 255, 0.9);
color: #333;
font-size: 14px;
resize: vertical;
font-family: 'Courier New', monospace;
}
.button-group {
display: flex;
gap: 15px;
flex-wrap: wrap;
}
.btn {
padding: 12px 25px;
border: none;
border-radius: 25px;
font-size: 1rem;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 8px;
}
.btn-primary {
background: linear-gradient(45deg, #4CAF50, #45a049);
color: white;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(76, 175, 80, 0.4);
}
.btn-secondary {
background: linear-gradient(45deg, #2196F3, #1976D2);
color: white;
}
.btn-secondary:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(33, 150, 243, 0.4);
}
.btn:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none !important;
}
/* Popup Modal Styles */
.modal {
display: none;
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.8);
backdrop-filter: blur(5px);
}
.modal-content {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
margin: 5% auto;
padding: 30px;
border-radius: 20px;
width: 90%;
max-width: 600px;
position: relative;
border: 1px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
}
.close {
color: white;
float: right;
font-size: 28px;
font-weight: bold;
cursor: pointer;
position: absolute;
top: 15px;
right: 20px;
transition: color 0.3s ease;
}
.close:hover {
color: #ff6b6b;
}
.modal-header {
text-align: center;
margin-bottom: 25px;
}
.modal-header h2 {
font-size: 2rem;
margin-bottom: 10px;
}
.modal-body {
text-align: center;
}
.download-info {
background: rgba(255, 255, 255, 0.1);
border-radius: 15px;
padding: 20px;
margin: 20px 0;
border: 1px solid rgba(255, 255, 255, 0.2);
}
.download-info h3 {
margin-bottom: 15px;
color: #4CAF50;
}
.download-link {
background: rgba(255, 255, 255, 0.9);
color: #333;
padding: 15px;
border-radius: 10px;
word-break: break-all;
font-family: 'Courier New', monospace;
font-size: 12px;
margin: 10px 0;
border: 2px solid #4CAF50;
}
.file-details {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 15px;
margin: 20px 0;
}
.file-detail {
background: rgba(255, 255, 255, 0.1);
padding: 15px;
border-radius: 10px;
text-align: center;
}
.file-detail .label {
font-size: 0.9rem;
opacity: 0.8;
margin-bottom: 5px;
}
.file-detail .value {
font-size: 1.1rem;
font-weight: bold;
color: #4CAF50;
}
.loading {
display: none;
text-align: center;
margin: 20px 0;
}
.spinner {
border: 4px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
border-top: 4px solid #4CAF50;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
margin: 0 auto 15px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.error {
background: rgba(255, 107, 107, 0.2);
border: 1px solid #ff6b6b;
color: #ff6b6b;
padding: 15px;
border-radius: 10px;
margin: 15px 0;
text-align: center;
}
.success {
background: rgba(76, 175, 80, 0.2);
border: 1px solid #4CAF50;
color: #4CAF50;
padding: 15px;
border-radius: 10px;
margin: 15px 0;
text-align: center;
}
.copy-btn {
background: linear-gradient(45deg, #FF9800, #F57C00);
color: white;
border: none;
padding: 8px 15px;
border-radius: 20px;
cursor: pointer;
font-size: 0.9rem;
margin-left: 10px;
transition: all 0.3s ease;
}
.copy-btn:hover {
transform: translateY(-1px);
box-shadow: 0 3px 10px rgba(255, 152, 0, 0.4);
}
.copy-btn.copied {
background: linear-gradient(45deg, #4CAF50, #45a049);
}
@media (max-width: 768px) {
.container {
padding: 10px;
}
.modal-content {
margin: 10% auto;
padding: 20px;
width: 95%;
}
.button-group {
flex-direction: column;
}
.btn {
width: 100%;
justify-content: center;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🚀 PDF Generator</h1>
<p>Generate PDFs with download links - No size restrictions!</p>
</div>
<div class="form-container">
<div class="form-group">
<label for="htmlContent">📝 HTML Content:</label>
<textarea id="htmlContent" placeholder="Enter your HTML content here...">
<!DOCTYPE html>
<html>
<head>
<title>Sample Property Brochure</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.header { background: linear-gradient(45deg, #667eea, #764ba2); color: white; padding: 20px; text-align: center; border-radius: 10px; }
.content { margin: 20px 0; }
.property-details { background: #f5f5f5; padding: 15px; border-radius: 8px; margin: 15px 0; }
.price { font-size: 24px; font-weight: bold; color: #4CAF50; }
</style>
</head>
<body>
<div class="header">
<h1>🏠 Luxury Property</h1>
<p>Beautiful 3-Bedroom Villa with Ocean View</p>
</div>
<div class="content">
<div class="property-details">
<h2>Property Details</h2>
<p><strong>Location:</strong> Dubai Marina, UAE</p>
<p><strong>Type:</strong> 3-Bedroom Villa</p>
<p><strong>Size:</strong> 2,500 sq ft</p>
<p><strong>Features:</strong> Ocean View, Private Pool, Garden</p>
</div>
<div class="price">
Price: AED 2,500,000
</div>
<p>This stunning property offers modern living with breathtaking ocean views. Perfect for families looking for luxury and comfort.</p>
</div>
</body>
</html>
</textarea>
</div>
<div class="button-group">
<button class="btn btn-primary" onclick="generatePDF()">
🚀 Generate PDF with Download Link
</button>
<button class="btn btn-secondary" onclick="testAPI()">
🔍 Test API Connection
</button>
</div>
</div>
<div id="loading" class="loading">
<div class="spinner"></div>
<p>🔄 Generating PDF... Please wait</p>
</div>
<div id="error" class="error" style="display: none;"></div>
<div id="success" class="success" style="display: none;"></div>
</div>
<!-- Download Popup Modal -->
<div id="downloadModal" class="modal">
<div class="modal-content">
<span class="close" onclick="closeModal()">&times;</span>
<div class="modal-header">
<h2>🎉 PDF Ready for Download!</h2>
<p>Your PDF has been generated successfully</p>
</div>
<div class="modal-body">
<div class="download-info">
<h3>📄 Download Information</h3>
<div class="file-details">
<div class="file-detail">
<div class="label">File Name</div>
<div class="value" id="fileName">-</div>
</div>
<div class="file-detail">
<div class="label">File Size</div>
<div class="value" id="fileSize">-</div>
</div>
<div class="file-detail">
<div class="label">Generated</div>
<div class="value" id="generatedAt">-</div>
</div>
<div class="file-detail">
<div class="label">Expires</div>
<div class="value" id="expiresAt">-</div>
</div>
</div>
<div style="margin: 20px 0;">
<label>🔗 Download Link:</label>
<div class="download-link" id="downloadLink">-</div>
<button class="copy-btn" onclick="copyToClipboard()">📋 Copy Link</button>
</div>
</div>
<div class="button-group">
<a id="downloadBtn" class="btn btn-primary" href="#" target="_blank">
📥 Download PDF
</a>
<button class="btn btn-secondary" onclick="openInNewTab()">
🔗 Open in New Tab
</button>
</div>
</div>
</div>
</div>
<script>
const API_BASE_URL = 'https://salesforce.tech4biz.io';
function showLoading() {
document.getElementById('loading').style.display = 'block';
document.getElementById('error').style.display = 'none';
document.getElementById('success').style.display = 'none';
}
function hideLoading() {
document.getElementById('loading').style.display = 'none';
}
function showError(message) {
hideLoading();
const errorDiv = document.getElementById('error');
errorDiv.textContent = message;
errorDiv.style.display = 'block';
}
function showSuccess(message) {
hideLoading();
const successDiv = document.getElementById('success');
successDiv.textContent = message;
successDiv.style.display = 'block';
}
async function generatePDF() {
const htmlContent = document.getElementById('htmlContent').value.trim();
if (!htmlContent) {
showError('❌ Please enter HTML content');
return;
}
showLoading();
try {
const response = await fetch(`${API_BASE_URL}/generate-pdf`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
input: htmlContent,
return_download_link: true
})
});
const result = await response.json();
if (result.success) {
showDownloadModal(result);
showSuccess('✅ PDF generated successfully!');
} else {
showError(`❌ PDF generation failed: ${result.error || result.message}`);
}
} catch (error) {
showError(`❌ Network error: ${error.message}`);
}
}
function showDownloadModal(result) {
// Populate modal with result data
document.getElementById('fileName').textContent = result.filename || 'Unknown';
document.getElementById('fileSize').textContent = result.file_size_mb ? `${result.file_size_mb} MB` : 'Unknown';
document.getElementById('generatedAt').textContent = formatDate(result.generated_at);
document.getElementById('expiresAt').textContent = formatDate(result.expires_at);
const downloadLink = result.download_url;
document.getElementById('downloadLink').textContent = downloadLink;
document.getElementById('downloadBtn').href = downloadLink;
// Show modal
document.getElementById('downloadModal').style.display = 'block';
}
function closeModal() {
document.getElementById('downloadModal').style.display = 'none';
}
function copyToClipboard() {
const downloadLink = document.getElementById('downloadLink').textContent;
navigator.clipboard.writeText(downloadLink).then(() => {
const copyBtn = document.querySelector('.copy-btn');
const originalText = copyBtn.textContent;
copyBtn.textContent = '✅ Copied!';
copyBtn.classList.add('copied');
setTimeout(() => {
copyBtn.textContent = originalText;
copyBtn.classList.remove('copied');
}, 2000);
}).catch(err => {
console.error('Failed to copy: ', err);
alert('Failed to copy link to clipboard');
});
}
function openInNewTab() {
const downloadLink = document.getElementById('downloadLink').textContent;
window.open(downloadLink, '_blank');
}
function formatDate(dateString) {
if (!dateString) return 'Unknown';
const date = new Date(dateString);
return date.toLocaleString();
}
async function testAPI() {
showLoading();
try {
const response = await fetch(`${API_BASE_URL}/health`);
const result = await response.json();
if (response.ok) {
showSuccess(`✅ API is healthy! Version: ${result.version}`);
} else {
showError('❌ API health check failed');
}
} catch (error) {
showError(`❌ API connection failed: ${error.message}`);
}
}
// Close modal when clicking outside
window.onclick = function(event) {
const modal = document.getElementById('downloadModal');
if (event.target === modal) {
closeModal();
}
}
// Close modal with Escape key
document.addEventListener('keydown', function(event) {
if (event.key === 'Escape') {
closeModal();
}
});
</script>
</body>
</html>

478
fixed-pdf-generation.js Normal file
View File

@ -0,0 +1,478 @@
#!/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 = `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
/* Reset and base styles */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: Arial, sans-serif;
line-height: 1.4;
color: #333;
background: white;
margin: 0;
padding: 0;
font-size: 12px;
}
/* A4 Page setup */
@page {
size: A4;
margin: 0.5in;
}
/* Page break controls */
.page-break-before {
page-break-before: always;
}
.page-break-after {
page-break-after: always;
}
.no-break {
page-break-inside: avoid;
}
.avoid-break-after {
page-break-after: avoid;
}
.avoid-break-before {
page-break-before: avoid;
}
/* Content containers */
.content-container {
width: 100%;
max-width: 100%;
overflow: hidden;
}
/* Images */
img {
max-width: 100%;
height: auto;
display: block;
page-break-inside: avoid;
}
/* Tables */
table {
page-break-inside: avoid;
width: 100%;
border-collapse: collapse;
}
/* Headings */
h1, h2, h3, h4, h5, h6 {
page-break-after: avoid;
page-break-inside: avoid;
margin-top: 0.5em;
margin-bottom: 0.3em;
}
/* Paragraphs */
p {
margin-bottom: 0.5em;
orphans: 2;
widows: 2;
page-break-inside: avoid;
}
/* Lists */
ul, ol {
page-break-inside: avoid;
}
li {
page-break-inside: avoid;
}
/* Footer with page numbers */
.page-footer {
position: fixed;
bottom: 0.5in;
left: 0.5in;
right: 0.5in;
height: 0.3in;
text-align: center;
font-size: 10px;
color: #666;
border-top: 1px solid #ddd;
padding-top: 0.1in;
}
/* Remove any existing page breaks that might cause issues */
.page-break,
.pagebreak,
.new-page {
page-break-before: always;
height: 0;
margin: 0;
padding: 0;
}
/* Ensure no extra spacing */
.no-margin {
margin: 0 !important;
}
.no-padding {
padding: 0 !important;
}
/* Fix for common layout issues */
.clearfix::after {
content: "";
display: table;
clear: both;
}
/* Ensure content fits properly */
.content-wrapper {
width: 100%;
min-height: 100vh;
position: relative;
}
</style>
</head>
<body>
<div class="content-wrapper">
<div class="content-container">
${htmlContent.replace(/<body[^>]*>|<\/body>/gi, '')}
</div>
<div class="page-footer">
Page <span class="page-number"></span>
</div>
</div>
<script>
// Add page numbers dynamically
document.addEventListener('DOMContentLoaded', function() {
const pageNumbers = document.querySelectorAll('.page-number');
pageNumbers.forEach((span, index) => {
span.textContent = index + 1;
});
});
</script>
</body>
</html>
`;
// 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: '<div></div>', // Empty header
footerTemplate: `
<div style="font-size: 10px; text-align: center; width: 100%; color: #666;">
Page <span class="pageNumber"></span> of <span class="totalPages"></span>
</div>
`,
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;

144
improved-pdf-generation.js Normal file
View File

@ -0,0 +1,144 @@
async function generateA4PDF(htmlContent, outputPDF) {
let browser = null;
try {
console.log("🚀 Starting 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
// Inject CSS to fix white spaces and page breaks
const improvedHTML = `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: Arial, sans-serif;
line-height: 1.4;
color: #333;
background: white;
margin: 0;
padding: 0;
}
@page {
size: A4;
margin: 0.25in;
}
.page-break {
page-break-before: always;
}
.no-break {
page-break-inside: avoid;
}
img {
max-width: 100%;
height: auto;
display: block;
}
table {
page-break-inside: avoid;
width: 100%;
}
h1, h2, h3, h4, h5, h6 {
page-break-after: avoid;
margin-top: 0.5em;
margin-bottom: 0.3em;
}
p {
margin-bottom: 0.5em;
orphans: 2;
widows: 2;
}
</style>
</head>
<body>
${htmlContent.replace(/<body[^>]*>|<\/body>/gi, '')}
</body>
</html>
`;
// Set content with improved HTML
await page.setContent(improvedHTML, {
waitUntil: 'networkidle0',
timeout: 0 // Infinite timeout
});
// Wait for content to load completely
await page.waitForTimeout(2000); // Wait 2 seconds for content to settle
// Wait for all resources to load
try {
await page.waitForNetworkIdle({ timeout: 0 });
} catch (error) {
// Ignore timeout errors for networkidle
}
console.log("📄 Generating A4 PDF...");
const pdfOptions = {
path: outputPDF,
printBackground: true,
margin: {
top: '0.25in',
right: '0.25in',
bottom: '0.25in',
left: '0.25in'
},
scale: 1.0,
format: 'A4',
preferCSSPageSize: false,
displayHeaderFooter: false,
omitBackground: false
};
// Generate PDF
await page.pdf(pdfOptions);
await browser.close();
// Get PDF file size for logging (no size restriction for download links)
const pdfStats = fs.statSync(outputPDF);
const pdfSizeMB = pdfStats.size / (1024 * 1024);
console.log(`✅ A4 PDF generated: ${outputPDF}`);
console.log(`📐 Format: A4 (210mm x 297mm)`);
console.log(`📏 Margins: 0.25in all sides`);
console.log(`🔍 Scale: 1.0`);
console.log(`📊 File size: ${pdfSizeMB.toFixed(2)} MB`);
} 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);
}
}
}
}

496
index.js Normal file
View File

@ -0,0 +1,496 @@
#!/usr/bin/env node
/**
* Property Brochure PDF Generator API - A4 Scale
* Uses consistent A4 format with proper scaling
*
* NEW FLOW: Supports both download links and direct file responses
* - return_download_link: true -> Generate PDF, save on server, return download link
* - return_download_link: false -> Generate PDF, return base64 content (old behavior)
*/
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 || 8000;
// 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 PDF with format options (A3 or A4) and minimal whitespace
*/
async function generatePDF(htmlContent, outputPDF, format = 'A4') {
let browser = null;
try {
console.log("🚀 Starting 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 based on format
const viewportDimensions = format === 'A3'
? { width: 1123, height: 1587 } // A3: 297mm x 420mm
: { width: 794, height: 1123 }; // A4: 210mm x 297mm
await page.setViewport(viewportDimensions);
page.setDefaultTimeout(0); // Infinite timeout
// Clean HTML content to remove excessive whitespace while preserving layout
const cleanedHTML = htmlContent
.replace(/\s+/g, ' ')
.replace(/>\s+</g, '><')
.trim()
.replace(/\n\s*\n/g, '\n')
.replace(/^\s+|\s+$/gm, '')
.replace(/\n{3,}/g, '\n\n')
.replace(/\s+<\//g, '</')
.replace(/>\s+/g, '>')
.replace(/<(\w+)[^>]*>\s*<\/\1>/g, '')
.replace(/<!--[\s\S]*?-->/g, '');
// Inject minimal CSS to reduce whitespace while preserving layout
const improvedHTML = `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
/* Minimal reset to reduce whitespace */
* {
box-sizing: border-box !important;
}
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;
}
/* Page setup with minimal margins */
@page {
size: ${format} !important;
margin: 0.2in !important;
}
/* Reduce excessive spacing */
h1, h2, h3, h4, h5, h6 {
margin: 0 0 10px 0 !important;
padding: 0 !important;
font-weight: bold !important;
page-break-after: avoid !important;
page-break-inside: avoid !important;
}
p {
margin: 0 0 8px 0 !important;
padding: 0 !important;
page-break-inside: avoid !important;
orphans: 1 !important;
widows: 1 !important;
}
/* Tables with minimal 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: 4px !important;
border: none !important;
vertical-align: top !important;
}
/* Images with minimal 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;
}
/* Page breaks */
.page-break, .pagebreak, .new-page {
page-break-before: always !important;
margin: 0 !important;
padding: 0 !important;
}
.no-break {
page-break-inside: avoid !important;
}
/* Hide elements that might cause spacing */
.hidden, .invisible, [style*="display: none"], [style*="visibility: hidden"] {
display: none !important;
}
</style>
</head>
<body>
${cleanedHTML.replace(/<body[^>]*>|<\/body>/gi, '')}
</body>
</html>
`;
// Set content with improved HTML
await page.setContent(improvedHTML, {
waitUntil: 'networkidle0',
timeout: 0 // Infinite timeout
});
// Wait for content to load completely
await page.waitForTimeout(0);
// Wait for all resources to load
try {
await page.waitForNetworkIdle({ timeout: 0 });
} catch (error) {
// Ignore timeout errors for networkidle
}
// Additional CSS injection to reduce spacing
await page.addStyleTag({
content: `
/* Final spacing reduction */
* {
-webkit-print-color-adjust: exact !important;
color-adjust: exact !important;
}
/* Reduce remaining spacing */
body, html {
overflow: visible !important;
}
/* Ensure proper page breaks */
.page-break {
page-break-before: always !important;
}
`
});
console.log(`📄 Generating ${format} PDF with minimal whitespace...`);
const pdfOptions = {
path: outputPDF,
printBackground: true,
margin: {
top: '0.2in', // Minimal margins
right: '0.2in',
bottom: '0.2in',
left: '0.2in'
},
scale: 1.0,
format: format, // Dynamic format (A3 or A4)
preferCSSPageSize: false, // Use our CSS settings
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 (no size restriction for download links)
const pdfStats = fs.statSync(outputPDF);
const pdfSizeMB = pdfStats.size / (1024 * 1024);
console.log(`✅ MINIMAL-WHITESPACE ${format} PDF generated: ${outputPDF}`);
console.log(`📐 Format: ${format} (${format === 'A3' ? '297mm x 420mm' : '210mm x 297mm'})`);
console.log(`📏 Margins: 0.2in (minimal margins)`);
console.log(`🔍 Scale: 1.0`);
console.log(`📊 File size: ${pdfSizeMB.toFixed(2)} MB`);
console.log(`🚫 White spaces: REDUCED while preserving layout`);
} 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 - supports both download link and base64 response
*/
app.post('/generate-pdf', async (req, res) => {
try {
// Get request data
let inputContent = null;
let outputName = null;
let returnDownloadLink = false; // Default to old behavior
let format = 'A4'; // Default format
if (req.is('application/json')) {
try {
const data = req.body;
if (data) {
// Handle different request formats
if (data.input) {
// Direct HTML input
inputContent = data.input;
outputName = data.output || null;
returnDownloadLink = data.return_download_link || false;
format = data.format || 'A4';
} else if (data.html) {
// Alternative HTML field
inputContent = data.html;
outputName = data.filename || null;
returnDownloadLink = data.return_download_link || false;
format = data.format || 'A4';
} else if (data.content) {
// Content field
inputContent = data.content;
outputName = data.name || null;
returnDownloadLink = data.return_download_link || false;
format = data.format || 'A4';
}
}
} 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')) {
// Handle form data
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;
format = req.body.format || 'A4';
} else {
// Try to get from raw body
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;
format = rawData.format || 'A4';
} 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' });
}
// Validate format
if (!['A3', 'A4'].includes(format.toUpperCase())) {
return res.status(400).json({ error: 'Invalid format. Supported formats: A3, A4' });
}
format = format.toUpperCase();
console.log(`📥 Received request - Format: ${format}, 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, '_')}_${format}_${randomId}.pdf` :
`property_brochure_${format}_${randomId}.pdf`;
const outputPDF = path.join(TEMP_FOLDER, filename);
// Generate PDF
await generatePDF(inputContent, outputPDF, format);
if (returnDownloadLink) {
// NEW FLOW: 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: `MINIMAL-WHITESPACE ${format} PDF generated successfully`,
download_url: downloadUrl,
filename: filename,
format: format,
file_size_mb: pdfSizeMB.toFixed(2),
expires_at: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(), // 7 days
generated_at: new Date().toISOString(),
pdf_id: randomId,
status: "download_ready",
features: ["reduced_whitespace", "preserved_layout", "minimal_margins"]
};
console.log(`🔗 PDF ready for download: ${downloadUrl}`);
console.log(`📊 File size: ${pdfSizeMB.toFixed(2)} MB`);
res.json(result);
} else {
// OLD FLOW: 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(`❌ API Error: ${error.message}`);
res.status(500).json({ error: error.message });
}
});
/**
* Download endpoint for PDF files
*/
app.get('/download/:filename', (req, res) => {
try {
const filename = req.params.filename;
const filePath = path.join(TEMP_FOLDER, filename);
// Security check - ensure filename is safe
if (!filename.match(/^[a-zA-Z0-9._-]+\.pdf$/)) {
return res.status(400).json({ error: 'Invalid filename' });
}
if (!fs.existsSync(filePath)) {
return res.status(404).json({ error: 'PDF file not found or expired' });
}
// Send file response
res.setHeader('Content-Type', 'application/pdf');
res.setHeader('Content-Disposition', `attachment; filename="${path.basename(filePath)}"`);
const fileStream = fs.createReadStream(filePath);
fileStream.pipe(res);
// Keep the generated PDF file (don't delete it)
fileStream.on('end', () => {
console.log(`📥 PDF downloaded: ${filePath}`);
});
} catch (error) {
console.error(`❌ Download 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: '9.1.0-minimal-whitespace-a3-a4',
supported_formats: ['A3', 'A4'],
features: [
'reduced_white_spaces',
'minimal_margins',
'html_preprocessing',
'preserved_layout',
'no_extra_pages',
'a3_a4_formats',
'clean_pdf_output',
'download_links',
'base64_response',
'infinite_timeouts',
'format_validation'
]
});
});
/**
* Clean up temporary HTML files older than 1 hour (keep PDF files)
*/
function cleanupTempFiles() {
try {
const currentTime = Date.now();
const files = fs.readdirSync(TEMP_FOLDER);
files.forEach(filename => {
const filepath = path.join(TEMP_FOLDER, filename);
const stats = fs.statSync(filepath);
// Only clean up HTML files, keep PDF files
if (stats.isFile() && filename.endsWith('.html') && (currentTime - stats.mtime.getTime()) > 3600000) { // 1 hour
try {
fs.unlinkSync(filepath);
console.log(`🗑️ Cleaned up old HTML file: ${filename}`);
} catch (error) {
console.error(`Error deleting file ${filename}:`, error);
}
}
});
} catch (error) {
console.error('Error during cleanup:', error);
}
}
// Clean up old files every hour
setInterval(cleanupTempFiles, 3600000);
// Start server
app.listen(PORT, () => {
console.log(`🚀 MINIMAL-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: REDUCED - Minimal spacing, preserved layout, clean output`);
console.log(`📐 FORMATS: A3 (297mm x 420mm) and A4 (210mm x 297mm)`);
console.log(`✨ Features: Reduced white spaces, HTML preprocessing, Preserved layout, Format options`);
});
module.exports = app;

330
index.js.backup Normal file
View File

@ -0,0 +1,330 @@
#!/usr/bin/env node
/**
* Property Brochure PDF Generator API - A4 Scale
* Uses consistent A4 format with proper scaling
*
* NEW FLOW: Supports both download links and direct file responses
* - return_download_link: true -> Generate PDF, save on server, return download link
* - return_download_link: false -> Generate PDF, return base64 content (old behavior)
*/
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 || 8000;
// 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 scaling
*/
async function generateA4PDF(htmlContent, outputPDF) {
let browser = null;
try {
console.log("🚀 Starting 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
// Set content
await page.setContent(htmlContent, {
waitUntil: 'networkidle0',
timeout: 0 // Infinite timeout
});
// Wait for content to load completely
await page.waitForTimeout(0);
// Wait for all resources to load
try {
await page.waitForNetworkIdle({ timeout: 0 });
} catch (error) {
// Ignore timeout errors for networkidle
}
console.log("📄 Generating A4 PDF...");
const pdfOptions = {
path: outputPDF,
printBackground: true,
margin: {
top: '0.5in',
right: '0.5in',
bottom: '0.5in',
left: '0.5in'
},
scale: 1.0,
format: 'A4', // Always A4
preferCSSPageSize: false
};
// Generate PDF
await page.pdf(pdfOptions);
await browser.close();
// Get PDF file size for logging (no size restriction for download links)
const pdfStats = fs.statSync(outputPDF);
const pdfSizeMB = pdfStats.size / (1024 * 1024);
console.log(`✅ A4 PDF generated: ${outputPDF}`);
console.log(`📐 Format: A4 (210mm x 297mm)`);
console.log(`📏 Margins: 0.5in all sides`);
console.log(`🔍 Scale: 1.0`);
console.log(`📊 File size: ${pdfSizeMB.toFixed(2)} MB`);
} 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 - supports both download link and base64 response
*/
app.post('/generate-pdf', async (req, res) => {
try {
// Get request data
let inputContent = null;
let outputName = null;
let returnDownloadLink = false; // Default to old behavior
if (req.is('application/json')) {
try {
const data = req.body;
if (data) {
// Handle different request formats
if (data.input) {
// Direct HTML input
inputContent = data.input;
outputName = data.output || null;
returnDownloadLink = data.return_download_link || false;
} else if (data.html) {
// Alternative HTML field
inputContent = data.html;
outputName = data.filename || null;
returnDownloadLink = data.return_download_link || false;
} else if (data.content) {
// Content field
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')) {
// Handle form data
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 to get from raw body
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 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, '_')}_${randomId}.pdf` :
`property_brochure_${randomId}.pdf`;
const outputPDF = path.join(TEMP_FOLDER, filename);
// Generate PDF
await generateA4PDF(inputContent, outputPDF);
if (returnDownloadLink) {
// NEW FLOW: 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: "PDF generated successfully",
download_url: downloadUrl,
filename: filename,
file_size_mb: pdfSizeMB.toFixed(2),
expires_at: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(), // 7 days
generated_at: new Date().toISOString(),
pdf_id: randomId,
status: "download_ready"
};
console.log(`🔗 PDF ready for download: ${downloadUrl}`);
console.log(`📊 File size: ${pdfSizeMB.toFixed(2)} MB`);
res.json(result);
} else {
// OLD FLOW: 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(`❌ API Error: ${error.message}`);
res.status(500).json({ error: error.message });
}
});
/**
* Download endpoint for PDF files
*/
app.get('/download/:filename', (req, res) => {
try {
const filename = req.params.filename;
const filePath = path.join(TEMP_FOLDER, filename);
// Security check - ensure filename is safe
if (!filename.match(/^[a-zA-Z0-9._-]+\.pdf$/)) {
return res.status(400).json({ error: 'Invalid filename' });
}
if (!fs.existsSync(filePath)) {
return res.status(404).json({ error: 'PDF file not found or expired' });
}
// Send file response
res.setHeader('Content-Type', 'application/pdf');
res.setHeader('Content-Disposition', `attachment; filename="${path.basename(filePath)}"`);
const fileStream = fs.createReadStream(filePath);
fileStream.pipe(res);
// Keep the generated PDF file (don't delete it)
fileStream.on('end', () => {
console.log(`📥 PDF downloaded: ${filePath}`);
});
} catch (error) {
console.error(`❌ Download 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: '7.0.0',
features: ['download_links', 'base64_response', 'a4_format', 'infinite_timeouts']
});
});
/**
* Clean up temporary HTML files older than 1 hour (keep PDF files)
*/
function cleanupTempFiles() {
try {
const currentTime = Date.now();
const files = fs.readdirSync(TEMP_FOLDER);
files.forEach(filename => {
const filepath = path.join(TEMP_FOLDER, filename);
const stats = fs.statSync(filepath);
// Only clean up HTML files, keep PDF files
if (stats.isFile() && filename.endsWith('.html') && (currentTime - stats.mtime.getTime()) > 3600000) { // 1 hour
try {
fs.unlinkSync(filepath);
console.log(`🗑️ Cleaned up old HTML file: ${filename}`);
} catch (error) {
console.error(`Error deleting file ${filename}:`, error);
}
}
});
} catch (error) {
console.error('Error during cleanup:', error);
}
}
// Clean up old files every hour
setInterval(cleanupTempFiles, 3600000);
// Start server
app.listen(PORT, () => {
console.log(`🚀 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(`✨ Features: Download links, Base64 response, A4 format, Infinite timeouts`);
});
module.exports = app;

332
index.js.backup2 Normal file
View File

@ -0,0 +1,332 @@
#!/usr/bin/env node
/**
* Property Brochure PDF Generator API - A4 Scale
* Uses consistent A4 format with proper scaling
*
* NEW FLOW: Supports both download links and direct file responses
* - return_download_link: true -> Generate PDF, save on server, return download link
* - return_download_link: false -> Generate PDF, return base64 content (old behavior)
*/
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 || 8000;
// 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 scaling
*/
async function generateA4PDF(htmlContent, outputPDF) {
let browser = null;
try {
console.log("🚀 Starting 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
// Set content
await page.setContent(htmlContent, {
waitUntil: 'networkidle0',
timeout: 0 // Infinite timeout
});
// Wait for content to load completely
await page.waitForTimeout(0);
// Wait for all resources to load
try {
await page.waitForNetworkIdle({ timeout: 0 });
} catch (error) {
// Ignore timeout errors for networkidle
}
console.log("📄 Generating A4 PDF...");
const pdfOptions = {
path: outputPDF,
printBackground: true,
margin: {
top: '0.5in',
right: '0.5in',
bottom: '0.5in',
left: '0.5in'
},
scale: 1.0,
format: 'A4', // Always A4
preferCSSPageSize: false,
displayHeaderFooter: false,
omitBackground: false
};
// Generate PDF
await page.pdf(pdfOptions);
await browser.close();
// Get PDF file size for logging (no size restriction for download links)
const pdfStats = fs.statSync(outputPDF);
const pdfSizeMB = pdfStats.size / (1024 * 1024);
console.log(`✅ A4 PDF generated: ${outputPDF}`);
console.log(`📐 Format: A4 (210mm x 297mm)`);
console.log(`📏 Margins: 0.5in all sides`);
console.log(`🔍 Scale: 1.0`);
console.log(`📊 File size: ${pdfSizeMB.toFixed(2)} MB`);
} 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 - supports both download link and base64 response
*/
app.post('/generate-pdf', async (req, res) => {
try {
// Get request data
let inputContent = null;
let outputName = null;
let returnDownloadLink = false; // Default to old behavior
if (req.is('application/json')) {
try {
const data = req.body;
if (data) {
// Handle different request formats
if (data.input) {
// Direct HTML input
inputContent = data.input;
outputName = data.output || null;
returnDownloadLink = data.return_download_link || false;
} else if (data.html) {
// Alternative HTML field
inputContent = data.html;
outputName = data.filename || null;
returnDownloadLink = data.return_download_link || false;
} else if (data.content) {
// Content field
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')) {
// Handle form data
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 to get from raw body
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 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, '_')}_${randomId}.pdf` :
`property_brochure_${randomId}.pdf`;
const outputPDF = path.join(TEMP_FOLDER, filename);
// Generate PDF
await generateA4PDF(inputContent, outputPDF);
if (returnDownloadLink) {
// NEW FLOW: 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: "PDF generated successfully",
download_url: downloadUrl,
filename: filename,
file_size_mb: pdfSizeMB.toFixed(2),
expires_at: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(), // 7 days
generated_at: new Date().toISOString(),
pdf_id: randomId,
status: "download_ready"
};
console.log(`🔗 PDF ready for download: ${downloadUrl}`);
console.log(`📊 File size: ${pdfSizeMB.toFixed(2)} MB`);
res.json(result);
} else {
// OLD FLOW: 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(`❌ API Error: ${error.message}`);
res.status(500).json({ error: error.message });
}
});
/**
* Download endpoint for PDF files
*/
app.get('/download/:filename', (req, res) => {
try {
const filename = req.params.filename;
const filePath = path.join(TEMP_FOLDER, filename);
// Security check - ensure filename is safe
if (!filename.match(/^[a-zA-Z0-9._-]+\.pdf$/)) {
return res.status(400).json({ error: 'Invalid filename' });
}
if (!fs.existsSync(filePath)) {
return res.status(404).json({ error: 'PDF file not found or expired' });
}
// Send file response
res.setHeader('Content-Type', 'application/pdf');
res.setHeader('Content-Disposition', `attachment; filename="${path.basename(filePath)}"`);
const fileStream = fs.createReadStream(filePath);
fileStream.pipe(res);
// Keep the generated PDF file (don't delete it)
fileStream.on('end', () => {
console.log(`📥 PDF downloaded: ${filePath}`);
});
} catch (error) {
console.error(`❌ Download 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: '7.0.0',
features: ['download_links', 'base64_response', 'a4_format', 'infinite_timeouts']
});
});
/**
* Clean up temporary HTML files older than 1 hour (keep PDF files)
*/
function cleanupTempFiles() {
try {
const currentTime = Date.now();
const files = fs.readdirSync(TEMP_FOLDER);
files.forEach(filename => {
const filepath = path.join(TEMP_FOLDER, filename);
const stats = fs.statSync(filepath);
// Only clean up HTML files, keep PDF files
if (stats.isFile() && filename.endsWith('.html') && (currentTime - stats.mtime.getTime()) > 3600000) { // 1 hour
try {
fs.unlinkSync(filepath);
console.log(`🗑️ Cleaned up old HTML file: ${filename}`);
} catch (error) {
console.error(`Error deleting file ${filename}:`, error);
}
}
});
} catch (error) {
console.error('Error during cleanup:', error);
}
}
// Clean up old files every hour
setInterval(cleanupTempFiles, 3600000);
// Start server
app.listen(PORT, () => {
console.log(`🚀 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(`✨ Features: Download links, Base64 response, A4 format, Infinite timeouts`);
});
module.exports = app;

2642
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

32
package.json Normal file
View File

@ -0,0 +1,32 @@
{
"name": "property-brochure-pdf-generator",
"version": "1.0.0",
"description": "Property Brochure PDF Generator API using Puppeteer",
"main": "index.js",
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js"
},
"keywords": [
"pdf",
"generation",
"puppeteer",
"express",
"api"
],
"author": "",
"license": "MIT",
"dependencies": {
"axios": "^1.11.0",
"body-parser": "^1.20.2",
"cors": "^2.8.5",
"express": "^4.18.2",
"puppeteer": "^21.5.2"
},
"devDependencies": {
"nodemon": "^3.0.1"
},
"engines": {
"node": ">=16.0.0"
}
}

546
refined-pdf-generation.js Normal file
View File

@ -0,0 +1,546 @@
#!/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;

View File

@ -0,0 +1,164 @@
/**
* Salesforce LWC Integration for PDF Download Popup
* Add this code to your propertyTemplateSelector.js file
*/
// Add this method to your LWC component
showDownloadPopup(pdfResult) {
// Create modal HTML
const modalHTML = `
<div class="pdf-download-modal" style="
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
">
<div class="modal-content" style="
background: white;
border-radius: 12px;
padding: 30px;
max-width: 600px;
width: 90%;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
position: relative;
">
<button class="close-btn" onclick="this.closest('.pdf-download-modal').remove()" style="
position: absolute;
top: 15px;
right: 20px;
background: none;
border: none;
font-size: 24px;
cursor: pointer;
color: #666;
">&times;</button>
<div class="modal-header" style="text-align: center; margin-bottom: 25px;">
<h2 style="color: #333; margin-bottom: 10px;">🎉 PDF Ready for Download!</h2>
<p style="color: #666;">Your PDF has been generated successfully</p>
</div>
<div class="download-info" style="
background: #f8f9fa;
border-radius: 8px;
padding: 20px;
margin: 20px 0;
border-left: 4px solid #28a745;
">
<h3 style="color: #28a745; margin-bottom: 15px;">📄 Download Information</h3>
<div class="file-details" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 15px; margin: 15px 0;">
<div class="file-detail" style="text-align: center;">
<div class="label" style="font-size: 0.9rem; color: #666; margin-bottom: 5px;">File Name</div>
<div class="value" style="font-weight: bold; color: #333;">${pdfResult.filename || 'Unknown'}</div>
</div>
<div class="file-detail" style="text-align: center;">
<div class="label" style="font-size: 0.9rem; color: #666; margin-bottom: 5px;">File Size</div>
<div class="value" style="font-weight: bold; color: #28a745;">${pdfResult.file_size_mb ? pdfResult.file_size_mb + ' MB' : 'Unknown'}</div>
</div>
<div class="file-detail" style="text-align: center;">
<div class="label" style="font-size: 0.9rem; color: #666; margin-bottom: 5px;">Generated</div>
<div class="value" style="font-weight: bold; color: #333;">${this.formatDate(pdfResult.generated_at)}</div>
</div>
<div class="file-detail" style="text-align: center;">
<div class="label" style="font-size: 0.9rem; color: #666; margin-bottom: 5px;">Expires</div>
<div class="value" style="font-weight: bold; color: #ffc107;">${this.formatDate(pdfResult.expires_at)}</div>
</div>
</div>
<div style="margin: 20px 0;">
<label style="display: block; margin-bottom: 8px; font-weight: bold; color: #333;">🔗 Download Link:</label>
<div class="download-link" style="
background: #e9ecef;
color: #333;
padding: 12px;
border-radius: 6px;
word-break: break-all;
font-family: monospace;
font-size: 12px;
border: 2px solid #28a745;
margin-bottom: 10px;
">${pdfResult.download_url}</div>
<button class="copy-btn" onclick="navigator.clipboard.writeText('${pdfResult.download_url}').then(() => { this.textContent = '✅ Copied!'; setTimeout(() => this.textContent = '<27><> Copy Link', 2000); })" style="
background: #ffc107;
color: #333;
border: none;
padding: 8px 15px;
border-radius: 20px;
cursor: pointer;
font-size: 0.9rem;
margin-right: 10px;
">📋 Copy Link</button>
</div>
</div>
<div class="button-group" style="text-align: center; margin-top: 25px;">
<a href="${pdfResult.download_url}" target="_blank" class="download-btn" style="
background: #28a745;
color: white;
padding: 12px 25px;
border-radius: 25px;
text-decoration: none;
font-weight: bold;
margin-right: 15px;
display: inline-block;
">📥 Download PDF</a>
<button onclick="window.open('${pdfResult.download_url}', '_blank')" style="
background: #007bff;
color: white;
border: none;
padding: 12px 25px;
border-radius: 25px;
cursor: pointer;
font-weight: bold;
">🔗 Open in New Tab</button>
</div>
</div>
</div>
`;
// Add modal to page
document.body.insertAdjacentHTML('beforeend', modalHTML);
}
// Helper method to format dates
formatDate(dateString) {
if (!dateString) return 'Unknown';
const date = new Date(dateString);
return date.toLocaleString();
}
// Update your existing PDF generation method to show popup
async generatePDFWithPopup() {
try {
// Show loading state
this.showLoading = true;
this.errorMessage = '';
// Call your existing PDF generation method
const result = await PDFGenerationProxy.generatePDFFromHTML(this.htmlContent, 'A4');
if (result.success) {
// Show the download popup
this.showDownloadPopup(result);
} else {
this.errorMessage = result.error || 'PDF generation failed';
}
} catch (error) {
this.errorMessage = error.message || 'An error occurred';
} finally {
this.showLoading = false;
}
}
// Add this to your LWC template to show the popup
// <template if:true={showDownloadPopup}>
// <!-- The popup will be injected via JavaScript -->
// </template>

139
test-api.js Normal file
View File

@ -0,0 +1,139 @@
#!/usr/bin/env node
/**
* Test script for the Property Brochure PDF Generator API
* Run this after starting the server to test the API endpoints
*/
const fs = require('fs');
const path = require('path');
// Test HTML content
const testHTML = fs.readFileSync('test.html', 'utf8');
// Test the API endpoints
async function testAPI() {
console.log('🧪 Testing Property Brochure PDF Generator API...\n');
// Test 1: Root endpoint
console.log('1⃣ Testing GET / endpoint...');
try {
const response = await fetch('http://localhost:8000/');
const data = await response.json();
console.log('✅ Root endpoint working:', data.message);
console.log(' Version:', data.version);
console.log(' Status:', data.status);
} catch (error) {
console.log('❌ Root endpoint failed:', error.message);
}
// Test 2: Health endpoint
console.log('\n2⃣ Testing GET /health endpoint...');
try {
const response = await fetch('http://localhost:8000/health');
const data = await response.json();
console.log('✅ Health endpoint working:', data.status);
console.log(' Service:', data.service);
} catch (error) {
console.log('❌ Health endpoint failed:', error.message);
}
// Test 3: PDF generation
console.log('\n3⃣ Testing POST /generate-pdf endpoint...');
try {
const response = await fetch('http://localhost:8000/generate-pdf', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
input: testHTML,
output: 'test_brochure.pdf'
})
});
if (response.ok) {
const buffer = await response.arrayBuffer();
const outputPath = path.join(__dirname, 'test_brochure.pdf');
fs.writeFileSync(outputPath, Buffer.from(buffer));
console.log('✅ PDF generation successful!');
console.log(' File saved as: test_brochure.pdf');
console.log(' File size:', (buffer.byteLength / 1024).toFixed(2), 'KB');
} else {
const error = await response.json();
console.log('❌ PDF generation failed:', error.error);
}
} catch (error) {
console.log('❌ PDF generation failed:', error.message);
}
// Test 4: Error handling - empty input
console.log('\n4⃣ Testing error handling (empty input)...');
try {
const response = await fetch('http://localhost:8000/generate-pdf', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
input: '',
output: 'error_test.pdf'
})
});
if (response.status === 400) {
const error = await response.json();
console.log('✅ Error handling working:', error.error);
} else {
console.log('❌ Expected error 400, got:', response.status);
}
} catch (error) {
console.log('❌ Error handling test failed:', error.message);
}
// Test 5: Alternative format (Apex compatibility)
console.log('\n5⃣ Testing alternative format (Apex compatibility)...');
try {
const response = await fetch('http://localhost:8000/generate-pdf', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
html_content: testHTML,
filename: 'apex_test.pdf'
})
});
if (response.ok) {
const buffer = await response.arrayBuffer();
const outputPath = path.join(__dirname, 'apex_test.pdf');
fs.writeFileSync(outputPath, Buffer.from(buffer));
console.log('✅ Apex format working!');
console.log(' File saved as: apex_test.pdf');
} else {
const error = await response.json();
console.log('❌ Apex format failed:', error.error);
}
} catch (error) {
console.log('❌ Apex format test failed:', error.message);
}
console.log('\n🎉 API testing completed!');
console.log('\n📁 Generated files:');
if (fs.existsSync('test_brochure.pdf')) {
console.log(' • test_brochure.pdf');
}
if (fs.existsSync('apex_test.pdf')) {
console.log(' • apex_test.pdf');
}
}
// Check if fetch is available (Node.js 18+)
if (typeof fetch === 'undefined') {
console.log('❌ This script requires Node.js 18+ or you need to install node-fetch');
console.log(' Install with: npm install node-fetch');
process.exit(1);
}
// Run tests
testAPI().catch(console.error);

258
test-fixed-pdf.js Normal file
View File

@ -0,0 +1,258 @@
#!/usr/bin/env node
/**
* Test script for the fixed PDF generation
* Demonstrates the fixes for page breaks, A4 size, and page numbers
*/
const axios = require('axios');
// Test HTML content with 3 pages
const testHTML = `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Property Brochure Test</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
line-height: 1.6;
}
.page {
min-height: 100vh;
page-break-after: always;
padding: 20px;
border: 1px solid #ddd;
margin-bottom: 20px;
}
.page:last-child {
page-break-after: avoid;
}
.header {
background: #007bff;
color: white;
padding: 20px;
text-align: center;
margin-bottom: 20px;
}
.content {
padding: 20px;
}
.image-placeholder {
width: 100%;
height: 200px;
background: #f0f0f0;
border: 2px dashed #ccc;
display: flex;
align-items: center;
justify-content: center;
margin: 20px 0;
}
.footer {
margin-top: auto;
text-align: center;
padding: 20px;
background: #f8f9fa;
border-top: 1px solid #ddd;
}
</style>
</head>
<body>
<!-- Page 1 -->
<div class="page">
<div class="header">
<h1>Property Brochure - Page 1</h1>
</div>
<div class="content">
<h2>Property Overview</h2>
<p>This is the first page of our property brochure. It contains the main property information and overview.</p>
<div class="image-placeholder">Property Image 1</div>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
<p>Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
</div>
<div class="footer">
<p>Property Details - Page 1</p>
</div>
</div>
<!-- Page 2 -->
<div class="page">
<div class="header">
<h1>Property Brochure - Page 2</h1>
</div>
<div class="content">
<h2>Property Features</h2>
<p>This is the second page showcasing the property features and amenities.</p>
<div class="image-placeholder">Property Image 2</div>
<ul>
<li>Modern kitchen with stainless steel appliances</li>
<li>Hardwood floors throughout</li>
<li>Central air conditioning</li>
<li>Private balcony with city views</li>
<li>In-unit washer and dryer</li>
</ul>
<p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.</p>
</div>
<div class="footer">
<p>Property Features - Page 2</p>
</div>
</div>
<!-- Page 3 -->
<div class="page">
<div class="header">
<h1>Property Brochure - Page 3</h1>
</div>
<div class="content">
<h2>Contact Information</h2>
<p>This is the final page with contact information and call-to-action.</p>
<div class="image-placeholder">Property Image 3</div>
<h3>Contact Details</h3>
<p><strong>Phone:</strong> (555) 123-4567</p>
<p><strong>Email:</strong> info@property.com</p>
<p><strong>Address:</strong> 123 Property Street, City, State 12345</p>
<p>Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
</div>
<div class="footer">
<p>Contact Information - Page 3</p>
</div>
</div>
</body>
</html>
`;
async function testFixedPDFGeneration() {
try {
console.log('🧪 Testing Fixed PDF Generation...');
console.log('📄 Test HTML has 3 pages');
// Test the fixed PDF generation endpoint
const response = await axios.post('http://localhost:8001/generate-fixed-pdf', {
input: testHTML,
output: 'test_property_brochure',
return_download_link: true
}, {
headers: {
'Content-Type': 'application/json'
},
timeout: 60000 // 60 second timeout
});
if (response.data.success) {
console.log('✅ Fixed PDF Generation Test PASSED!');
console.log('📊 Results:');
console.log(` - Success: ${response.data.success}`);
console.log(` - Message: ${response.data.message}`);
console.log(` - Download URL: ${response.data.download_url}`);
console.log(` - File Size: ${response.data.file_size_mb} MB`);
console.log(` - Fixes Applied: ${response.data.fixes_applied.join(', ')}`);
console.log('');
console.log('🎯 Expected Results:');
console.log(' - PDF should have exactly 3 pages (no extra blank pages)');
console.log(' - A4 page size (210mm x 297mm)');
console.log(' - Proper page breaks between content');
console.log(' - Page numbers in footer: "Page X of 3"');
console.log('');
console.log('🔗 Download the PDF to verify:');
console.log(` ${response.data.download_url}`);
} else {
console.log('❌ Fixed PDF Generation Test FAILED!');
console.log('Response:', response.data);
}
} catch (error) {
console.error('❌ Test Error:', error.message);
if (error.response) {
console.error('Response data:', error.response.data);
}
}
}
async function testOriginalPDFGeneration() {
try {
console.log('🧪 Testing Original PDF Generation (for comparison)...');
// Test the original PDF generation endpoint
const response = await axios.post('http://localhost:8000/generate-pdf', {
input: testHTML,
output: 'test_property_brochure_original',
return_download_link: true
}, {
headers: {
'Content-Type': 'application/json'
},
timeout: 60000 // 60 second timeout
});
if (response.data.success) {
console.log('📊 Original PDF Results:');
console.log(` - Download URL: ${response.data.download_url}`);
console.log(` - File Size: ${response.data.file_size_mb} MB`);
console.log('');
console.log('⚠️ Original PDF may have issues:');
console.log(' - Extra blank pages');
console.log(' - Incorrect page breaks');
console.log(' - Missing or incorrect page numbers');
}
} catch (error) {
console.error('❌ Original Test Error:', error.message);
}
}
// Run tests
async function runTests() {
console.log('🚀 Starting PDF Generation Tests...');
console.log('=====================================');
// Test original first
await testOriginalPDFGeneration();
console.log('');
console.log('=====================================');
// Test fixed version
await testFixedPDFGeneration();
console.log('');
console.log('🏁 Tests completed!');
console.log('Compare the two PDFs to see the improvements.');
}
// Check if servers are running
async function checkServers() {
try {
// Check original server
await axios.get('http://localhost:8000/health', { timeout: 5000 });
console.log('✅ Original PDF server is running on port 8000');
} catch (error) {
console.log('❌ Original PDF server is not running on port 8000');
console.log(' Start it with: node index.js');
}
try {
// Check fixed server
await axios.get('http://localhost:8001/health', { timeout: 5000 });
console.log('✅ Fixed PDF server is running on port 8001');
} catch (error) {
console.log('❌ Fixed PDF server is not running on port 8001');
console.log(' Start it with: node fixed-pdf-generation.js');
}
}
// Main execution
async function main() {
console.log('🔍 Checking servers...');
await checkServers();
console.log('');
await runTests();
}
if (require.main === module) {
main().catch(console.error);
}
module.exports = { testFixedPDFGeneration, testOriginalPDFGeneration };

80
test-no-whitespace-pdf.js Normal file
View File

@ -0,0 +1,80 @@
#!/usr/bin/env node
/**
* Test script for NO-WHITESPACE PDF generation
* Tests the refined PDF generator to ensure no white spaces
*/
const axios = require('axios');
// Test HTML content with potential whitespace issues
const testHTML = `
<div class="container">
<h1> Property Brochure Test </h1>
<p> This is a test paragraph with extra spaces. </p>
<ul>
<li> Item 1 with spaces </li>
<li> Item 2 with spaces </li>
</ul>
<table>
<tr>
<td> Cell 1 </td>
<td> Cell 2 </td>
</tr>
</table>
<div class="spacing-issue">
<p> Another paragraph </p>
</div>
</div>
`;
async function testNoWhitespacePDF() {
try {
console.log("🧪 Testing NO-WHITESPACE PDF generation...");
const response = await axios.post('http://localhost:8002/generate-no-whitespace-pdf', {
input: testHTML,
output: 'test_no_whitespace',
return_download_link: true
}, {
headers: {
'Content-Type': 'application/json'
},
timeout: 60000 // 60 second timeout
});
if (response.data.success) {
console.log("✅ NO-WHITESPACE PDF generated successfully!");
console.log(`📄 Filename: ${response.data.filename}`);
console.log(`📊 File size: ${response.data.file_size_mb} MB`);
console.log(`🔗 Download URL: ${response.data.download_url}`);
console.log(`🚫 Features: ${response.data.features.join(', ')}`);
// Test health endpoint
const healthResponse = await axios.get('http://localhost:8002/health');
console.log("🏥 Health check:", healthResponse.data.status);
console.log("🔧 Features:", healthResponse.data.features.join(', '));
} else {
console.error("❌ PDF generation failed:", response.data);
}
} catch (error) {
if (error.code === 'ECONNREFUSED') {
console.error("❌ Connection refused. Make sure the server is running on port 8002");
console.log("💡 Start the server with: node refined-pdf-generation.js");
} else {
console.error("❌ Test error:", error.message);
if (error.response) {
console.error("📄 Response data:", error.response.data);
}
}
}
}
// Run the test
testNoWhitespacePDF();

82
test-pdf-generation.js Normal file
View File

@ -0,0 +1,82 @@
#!/usr/bin/env node
/**
* Test script for the improved PDF generation API
* Tests both A3 and A4 formats with zero whitespace
*/
const fs = require('fs');
const path = require('path');
const axios = require('axios');
const API_BASE_URL = 'http://localhost:8000';
// Test HTML content from preview files
const testHTMLs = {
'grand-oak': fs.readFileSync('/home/ubuntu/salesforce/PDF_Generation_and_Automation/previews/preview-grand-oak.html', 'utf8'),
'vertice': fs.readFileSync('/home/ubuntu/salesforce/PDF_Generation_and_Automation/previews/preview-vertice.html', 'utf8'),
'serenity-house': fs.readFileSync('/home/ubuntu/salesforce/PDF_Generation_and_Automation/previews/preview-serenity-house.html', 'utf8'),
'modern-home': fs.readFileSync('/home/ubuntu/salesforce/PDF_Generation_and_Automation/previews/preview-modern-home.html', 'utf8')
};
async function testPDFGeneration() {
console.log('🧪 Testing ZERO-WHITESPACE PDF Generation API');
console.log('=' .repeat(60));
// Test health endpoint
try {
const healthResponse = await axios.get(`${API_BASE_URL}/health`);
console.log('✅ Health Check:', healthResponse.data);
console.log('📐 Supported Formats:', healthResponse.data.supported_formats);
console.log('');
} catch (error) {
console.error('❌ Health check failed:', error.message);
return;
}
// Test each HTML template with both A3 and A4 formats
for (const [templateName, htmlContent] of Object.entries(testHTMLs)) {
console.log(`\n🏠 Testing Template: ${templateName.toUpperCase()}`);
console.log('-'.repeat(40));
for (const format of ['A4', 'A3']) {
try {
console.log(`\n📄 Generating ${format} PDF...`);
const response = await axios.post(`${API_BASE_URL}/generate-pdf`, {
input: htmlContent,
output: `${templateName}-${format.toLowerCase()}`,
format: format,
return_download_link: true
}, {
headers: {
'Content-Type': 'application/json'
},
timeout: 60000 // 60 seconds timeout
});
if (response.data.success) {
console.log(`${format} PDF Generated Successfully!`);
console.log(`📁 Filename: ${response.data.filename}`);
console.log(`📊 File Size: ${response.data.file_size_mb} MB`);
console.log(`🔗 Download URL: ${response.data.download_url}`);
console.log(`🎯 Features: ${response.data.features.join(', ')}`);
} else {
console.log(`${format} PDF Generation Failed:`, response.data.message);
}
} catch (error) {
console.error(`❌ Error generating ${format} PDF for ${templateName}:`, error.response?.data?.error || error.message);
}
}
}
console.log('\n🎉 PDF Generation Testing Complete!');
console.log('=' .repeat(60));
}
// Run the test
if (require.main === module) {
testPDFGeneration().catch(console.error);
}
module.exports = { testPDFGeneration };

161
test.html Normal file
View File

@ -0,0 +1,161 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Property Brochure Test</title>
<style>
body {
font-family: 'Arial', sans-serif;
margin: 0;
padding: 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.container {
max-width: 800px;
margin: 0 auto;
background: rgba(255, 255, 255, 0.1);
padding: 30px;
border-radius: 15px;
backdrop-filter: blur(10px);
}
h1 {
text-align: center;
color: #fff;
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
margin-bottom: 30px;
}
.property-details {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin: 30px 0;
}
.detail-card {
background: rgba(255, 255, 255, 0.2);
padding: 20px;
border-radius: 10px;
text-align: center;
}
.price {
font-size: 2.5em;
font-weight: bold;
color: #4ade80;
text-shadow: 1px 1px 2px rgba(0,0,0,0.3);
}
.features {
list-style: none;
padding: 0;
}
.features li {
background: rgba(255, 255, 255, 0.1);
margin: 10px 0;
padding: 15px;
border-radius: 8px;
border-left: 4px solid #4ade80;
}
.description {
line-height: 1.6;
margin: 20px 0;
text-align: justify;
}
.contact-info {
background: rgba(255, 255, 255, 0.2);
padding: 20px;
border-radius: 10px;
text-align: center;
margin-top: 30px;
}
.logo {
text-align: center;
font-size: 3em;
margin-bottom: 20px;
}
</style>
</head>
<body>
<div class="container">
<div class="logo">🏠</div>
<h1>Luxury Property Brochure</h1>
<div class="description">
<p>Welcome to this stunning luxury property located in the heart of the city. This magnificent home offers the perfect blend of modern design and classic elegance, featuring premium finishes and thoughtful details throughout.</p>
</div>
<div class="property-details">
<div class="detail-card">
<h3>Price</h3>
<div class="price">$1,250,000</div>
</div>
<div class="detail-card">
<h3>Location</h3>
<p>Downtown Luxury District</p>
<p>Prime City Center</p>
</div>
</div>
<div class="property-details">
<div class="detail-card">
<h3>Bedrooms</h3>
<p>4 Master Suites</p>
</div>
<div class="detail-card">
<h3>Bathrooms</h3>
<p>5 Full + 2 Half</p>
</div>
</div>
<div class="property-details">
<div class="detail-card">
<h3>Square Feet</h3>
<p>4,500 sq ft</p>
</div>
<div class="detail-card">
<h3>Lot Size</h3>
<p>0.75 acres</p>
</div>
</div>
<h2>Property Features</h2>
<ul class="features">
<li>🏊‍♂️ Infinity Pool with City Views</li>
<li>🍳 Chef's Kitchen with Premium Appliances</li>
<li>🎭 Home Theater with Surround Sound</li>
<li>🏋️‍♂️ Private Fitness Center</li>
<li>🚗 3-Car Garage with Electric Charging</li>
<li>🌿 Landscaped Gardens with Water Features</li>
<li>🔒 Smart Home Security System</li>
<li>☀️ Solar Panels for Energy Efficiency</li>
</ul>
<div class="description">
<p>This exceptional property offers an unparalleled living experience with its open-concept design, high ceilings, and floor-to-ceiling windows that flood the space with natural light. The gourmet kitchen features custom cabinetry, quartz countertops, and top-of-the-line appliances.</p>
<p>The master suite is a true sanctuary with a spa-like bathroom, walk-in closet, and private balcony. Additional amenities include a wine cellar, home office, and outdoor entertainment area perfect for hosting gatherings.</p>
</div>
<div class="contact-info">
<h3>Contact Information</h3>
<p>📞 (555) 123-4567</p>
<p>📧 info@luxuryproperties.com</p>
<p>🌐 www.luxuryproperties.com</p>
<p>📍 123 Luxury Lane, Downtown, City, State 12345</p>
</div>
<div style="text-align: center; margin-top: 30px; font-size: 0.9em; opacity: 0.8;">
<p>This brochure was generated using the Property Brochure PDF Generator API</p>
<p>Generated on: <span id="current-date"></span></p>
</div>
</div>
<script>
// Add current date
document.getElementById('current-date').textContent = new Date().toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
});
</script>
</body>
</html>

184
usage-examples.md Normal file
View File

@ -0,0 +1,184 @@
# ZERO-WHITESPACE PDF Generator API - Usage Examples
## Overview
This API generates exact PDFs with zero whitespace and supports both A3 and A4 formats.
## Features
- ✅ **Zero Whitespace**: Complete elimination of unwanted spacing
- ✅ **Format Options**: A3 (297mm x 420mm) and A4 (210mm x 297mm)
- ✅ **Exact Output**: PDFs match HTML exactly without extra pages
- ✅ **Advanced HTML Processing**: Removes all unnecessary elements
- ✅ **Download Links**: Get direct download URLs for generated PDFs
## API Endpoints
### 1. Generate PDF
**POST** `/generate-pdf`
#### Request Body (JSON)
```json
{
"input": "<html>Your HTML content here</html>",
"output": "my-property-brochure",
"format": "A4",
"return_download_link": true
}
```
#### Parameters
- `input` (required): HTML content to convert to PDF
- `output` (optional): Custom filename (without extension)
- `format` (optional): "A3" or "A4" (default: "A4")
- `return_download_link` (optional): true/false (default: false)
#### Response (Download Link)
```json
{
"success": true,
"message": "ZERO-WHITESPACE A4 PDF generated successfully",
"download_url": "https://salesforce.tech4biz.io/download/property_brochure_A4_abc123.pdf",
"filename": "property_brochure_A4_abc123.pdf",
"format": "A4",
"file_size_mb": "2.45",
"expires_at": "2024-01-15T10:30:00.000Z",
"generated_at": "2024-01-08T10:30:00.000Z",
"pdf_id": "abc123",
"status": "download_ready",
"features": ["zero_whitespace", "exact_output", "minimal_margins"]
}
```
### 2. Health Check
**GET** `/health`
#### Response
```json
{
"status": "healthy",
"timestamp": "2024-01-08T10:30:00.000Z",
"version": "9.0.0-zero-whitespace-a3-a4",
"supported_formats": ["A3", "A4"],
"features": [
"zero_white_spaces",
"zero_margins",
"advanced_html_preprocessing",
"complete_css_reset",
"no_extra_pages",
"a3_a4_formats",
"exact_pdf_output",
"download_links",
"base64_response",
"infinite_timeouts",
"format_validation"
]
}
```
## Usage Examples
### Example 1: Generate A4 PDF with Download Link
```bash
curl -X POST http://localhost:8000/generate-pdf \
-H "Content-Type: application/json" \
-d '{
"input": "<html><body><h1>My Property</h1><p>Beautiful home for sale</p></body></html>",
"output": "property-brochure",
"format": "A4",
"return_download_link": true
}'
```
### Example 2: Generate A3 PDF with Download Link
```bash
curl -X POST http://localhost:8000/generate-pdf \
-H "Content-Type: application/json" \
-d '{
"input": "<html><body><h1>Large Property</h1><p>Spacious home with garden</p></body></html>",
"output": "large-property",
"format": "A3",
"return_download_link": true
}'
```
### Example 3: JavaScript/Node.js Usage
```javascript
const axios = require('axios');
async function generatePDF(htmlContent, format = 'A4') {
try {
const response = await axios.post('http://localhost:8000/generate-pdf', {
input: htmlContent,
format: format,
return_download_link: true
});
if (response.data.success) {
console.log('PDF Generated:', response.data.download_url);
return response.data.download_url;
}
} catch (error) {
console.error('Error:', error.response?.data?.error || error.message);
}
}
// Usage
const htmlContent = '<html><body><h1>My Property</h1></body></html>';
generatePDF(htmlContent, 'A4');
```
### Example 4: Python Usage
```python
import requests
import json
def generate_pdf(html_content, format='A4'):
url = 'http://localhost:8000/generate-pdf'
data = {
'input': html_content,
'format': format,
'return_download_link': True
}
response = requests.post(url, json=data)
if response.status_code == 200:
result = response.json()
if result['success']:
print(f"PDF Generated: {result['download_url']}")
return result['download_url']
else:
print(f"Error: {response.json().get('error', 'Unknown error')}")
# Usage
html_content = '<html><body><h1>My Property</h1></body></html>'
generate_pdf(html_content, 'A4')
```
## Key Improvements
### 1. Zero Whitespace
- Complete CSS reset eliminates all default browser spacing
- Advanced HTML preprocessing removes unnecessary whitespace
- Zero margins ensure exact PDF output
### 2. Format Support
- **A4**: 210mm x 297mm (standard document size)
- **A3**: 297mm x 420mm (larger format for detailed layouts)
### 3. Exact Output
- PDFs match HTML exactly without extra pages
- No unwanted spacing or margins
- Perfect page count matching
### 4. Advanced Processing
- Removes HTML comments and empty elements
- Eliminates whitespace between tags
- Optimizes content for PDF generation
## Testing
Run the test script to verify functionality:
```bash
node test-pdf-generation.js
```
This will test all preview templates with both A3 and A4 formats.