Spaces:
Sleeping
Sleeping
| const express = require("express"); | |
| const cors = require("cors"); | |
| const puppeteer = require("puppeteer"); | |
| const app = express(); | |
| const PORT = process.env.PORT || 7860; // Hugging Face uses port 7860 | |
| // Middleware | |
| app.use(cors()); | |
| app.use(express.json({ limit: "5mb" })); | |
| // Root endpoint - Health check | |
| app.get("/", (_req, res) => { | |
| res.json({ | |
| status: "ok", | |
| message: "HTML to PDF API is running", | |
| version: "1.0.0", | |
| endpoints: { | |
| health: "GET / or GET /health", | |
| convert: "POST /api/html-to-pdf" | |
| }, | |
| usage: { | |
| method: "POST", | |
| url: "/api/html-to-pdf", | |
| body: { | |
| html_code: "Your HTML content here (with inline styles and complete structure)", | |
| pdf_options: { | |
| format: "A4", | |
| printBackground: true, | |
| margin: { | |
| top: "0mm", | |
| right: "0mm", | |
| bottom: "0mm", | |
| left: "0mm" | |
| } | |
| } | |
| }, | |
| notes: [ | |
| "HTML should include all styles inline or in <style> tags", | |
| "Use @page CSS rules for page-specific styling", | |
| "Margins default to 0mm - add padding in your HTML instead", | |
| "Charts/images will be rendered - allow 1.5s load time" | |
| ] | |
| } | |
| }); | |
| }); | |
| // Health check endpoint | |
| app.get("/health", (_req, res) => { | |
| res.json({ | |
| status: "healthy", | |
| timestamp: new Date().toISOString(), | |
| uptime: process.uptime() | |
| }); | |
| }); | |
| // Main PDF conversion endpoint | |
| app.post("/api/html-to-pdf", async (req, res) => { | |
| let browser; | |
| const startTime = Date.now(); | |
| try { | |
| const { html_code: htmlContent, pdf_options: pdfOptions } = req.body || {}; | |
| // Validate input | |
| if (!htmlContent || !htmlContent.trim()) { | |
| return res.status(400).json({ | |
| error: "html_code is required", | |
| success: false | |
| }); | |
| } | |
| console.log(`[${new Date().toISOString()}] Starting PDF generation...`); | |
| // Launch browser - using Puppeteer's bundled Chromium | |
| browser = await puppeteer.launch({ | |
| args: [ | |
| "--no-sandbox", | |
| "--disable-setuid-sandbox", | |
| "--disable-dev-shm-usage", | |
| "--disable-gpu", | |
| "--disable-software-rasterizer", | |
| "--disable-extensions", | |
| "--disable-background-networking", | |
| "--disable-default-apps", | |
| "--disable-sync", | |
| "--metrics-recording-only", | |
| "--mute-audio", | |
| "--no-first-run" | |
| ], | |
| headless: true | |
| // Remove executablePath - let Puppeteer use its bundled Chromium | |
| }); | |
| const page = await browser.newPage(); | |
| // Set content with PDF-optimized styling | |
| await page.setContent( | |
| `<!DOCTYPE html> | |
| <html> | |
| <head> | |
| <meta charset="utf-8" /> | |
| <style> | |
| @page { | |
| margin: 0; | |
| size: A4; | |
| } | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| html, body { | |
| width: 100%; | |
| height: 100%; | |
| margin: 0; | |
| padding: 0; | |
| } | |
| body { | |
| -webkit-print-color-adjust: exact; | |
| print-color-adjust: exact; | |
| color-adjust: exact; | |
| } | |
| img, svg, canvas { | |
| max-width: 100%; | |
| height: auto; | |
| display: block; | |
| } | |
| table { | |
| border-collapse: collapse; | |
| width: 100%; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| ${htmlContent} | |
| </body> | |
| </html>`, | |
| { | |
| waitUntil: ["load", "domcontentloaded", "networkidle0"], | |
| timeout: 30000 | |
| } | |
| ); | |
| // Wait for fonts and images to load | |
| await page.evaluateHandle('document.fonts.ready'); | |
| // Wait for any dynamic content (charts, images, etc.) | |
| await new Promise(resolve => setTimeout(resolve, 1500)); | |
| // Generate PDF with optimized options | |
| const defaultPdfOptions = { | |
| format: "A4", | |
| printBackground: true, | |
| margin: { | |
| top: "0mm", | |
| right: "0mm", | |
| bottom: "0mm", | |
| left: "0mm" | |
| }, | |
| preferCSSPageSize: true | |
| }; | |
| const pdfBuffer = await page.pdf({ | |
| ...defaultPdfOptions, | |
| ...(pdfOptions || {}), | |
| encoding: 'binary' | |
| }); | |
| await browser.close(); | |
| browser = null; | |
| const processingTime = Date.now() - startTime; | |
| console.log(`[${new Date().toISOString()}] PDF generated successfully in ${processingTime}ms`); | |
| // Convert Buffer to base64 properly | |
| const pdfBase64 = Buffer.from(pdfBuffer).toString('base64'); | |
| res.json({ | |
| pdf_base64: pdfBase64, | |
| success: true, | |
| processing_time_ms: processingTime, | |
| pdf_size_bytes: pdfBuffer.length | |
| }); | |
| } catch (err) { | |
| console.error(`[${new Date().toISOString()}] PDF Generation Error:`, err); | |
| if (browser) { | |
| try { | |
| await browser.close(); | |
| } catch (closeErr) { | |
| console.error("Browser close error:", closeErr); | |
| } | |
| } | |
| res.status(500).json({ | |
| error: "Failed to generate PDF", | |
| details: err.message, | |
| success: false | |
| }); | |
| } | |
| }); | |
| // 404 handler | |
| app.use((_req, res) => { | |
| res.status(404).json({ | |
| error: "Endpoint not found", | |
| available_endpoints: [ | |
| "GET /", | |
| "GET /health", | |
| "POST /api/html-to-pdf" | |
| ] | |
| }); | |
| }); | |
| // Start server | |
| app.listen(PORT, "0.0.0.0", () => { | |
| console.log(` | |
| ββββββββββββββββββββββββββββββββββββββββββ | |
| β HTML to PDF API Server β | |
| β Status: Running β β | |
| β Port: ${PORT} β | |
| β URL: http://0.0.0.0:${PORT} β | |
| ββββββββββββββββββββββββββββββββββββββββββ | |
| `); | |
| }); | |
| // Graceful shutdown | |
| process.on('SIGTERM', () => { | |
| console.log('SIGTERM signal received: closing HTTP server'); | |
| process.exit(0); | |
| }); | |
| process.on('SIGINT', () => { | |
| console.log('SIGINT signal received: closing HTTP server'); | |
| process.exit(0); | |
| }); |