# PDF Generation: Create PDFs from HTML

# PDF Generation from HTML: Puppeteer and Libraries

## Problem

Converting HTML to PDF is essential for generating reports, invoices, certificates, and documents programmatically. Manual conversion is time-consuming, and browser-based solutions lack automation. You need a reliable, scalable way to transform HTML content into professional PDFs.

## Solution

Use **Puppeteer** (headless Chrome automation) combined with specialized libraries for robust PDF generation. This approach handles complex layouts, CSS, JavaScript rendering, and provides fine-grained control over output.

## Code

### 1. Basic Puppeteer PDF Generation

```javascript
const puppeteer = require('puppeteer');
const fs = require('fs');

async function generatePDF() {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  
  // Load HTML content
  const htmlContent = `
    <!DOCTYPE html>
    <html>
      <head>
        <style>
          body { font-family: Arial, sans-serif; margin: 20px; }
          h1 { color: #333; }
          .footer { margin-top: 40px; border-top: 1px solid #ccc; }
        </style>
      </head>
      <body>
        <h1>Invoice #12345</h1>
        <p>Date: ${new Date().toLocaleDateString()}</p>
        <table border="1" cellpadding="10">
          <tr><th>Item</th><th>Qty</th><th>Price</th></tr>
          <tr><td>Widget A</td><td>2</td><td>$50</td></tr>
          <tr><td>Widget B</td><td>1</td><td>$75</td></tr>
        </table>
        <div class="footer">
          <p>Thank you for your business!</p>
        </div>
      </body>
    </html>
  `;
  
  await page.setContent(htmlContent);
  await page.pdf({ path: 'invoice.pdf', format: 'A4' });
  
  await browser.close();
  console.log('PDF generated: invoice.pdf');
}

generatePDF().catch(console.error);
```

### 2. Advanced Configuration with Headers/Footers

```javascript
const puppeteer = require('puppeteer');

async function generateAdvancedPDF() {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  
  await page.setContent(`
    <html>
      <head>
        <style>
          body { font-size: 14px; line-height: 1.6; }
          .content { padding: 20px; }
        </style>
      </head>
      <body>
        <div class="content">
          <h1>Annual Report 2024</h1>
          <p>This is a comprehensive report with multiple pages.</p>
          <p>Lorem ipsum dolor sit amet...</p>
        </div>
      </body>
    </html>
  `);
  
  await page.pdf({
    path: 'report.pdf',
    format: 'A4',
    margin: {
      top: '1cm',
      right: '1cm',
      bottom: '1cm',
      left: '1cm'
    },
    headerTemplate: `
      <div style="font-size: 12px; width: 100%; text-align: center;">
        Annual Report
      </div>
    `,
    footerTemplate: `
      <div style="font-size: 12px; width: 100%; text-align: center;">
        Page <span class="pageNumber"></span> of <span class="totalPages"></span>
      </div>
    `,
    displayHeaderFooter: true,
    printBackground: true
  });
  
  await browser.close();
}

generateAdvancedPDF().catch(console.error);
```

### 3. HTML File to PDF

```javascript
const puppeteer = require('puppeteer');
const path = require('path');

async function htmlFileToPDF(htmlFilePath, outputPath) {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  
  // Load from file with proper file:// URL
  const fileUrl = `file://${path.resolve(htmlFilePath)}`;
  await page.goto(fileUrl, { waitUntil: 'networkidle2' });
  
  await page.pdf({
    path: outputPath,
    format: 'A4',
    scale: 1,
    printBackground: true
  });
  
  await browser.close();
  console.log(`PDF saved to: ${outputPath}`);
}

htmlFileToPDF('./template.html', './output.pdf').catch(console.error);
```

### 4. Dynamic Content with Wait Conditions

```javascript
const puppeteer = require('puppeteer');

async function generateDynamicPDF(data) {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  
  const htmlContent = `
    <!DOCTYPE html>
    <html>
      <head>
        <style>
          body { font-family: 'Segoe UI', sans-serif; }
          .card { border: 1px solid #ddd; padding: 15px; margin: 10px 0; }
          .price { font-size: 24px; color: #27ae60; font-weight: bold; }
        </style>
      </head>
      <body>
        <h1>Product Catalog</h1>
        <div id="products"></div>
        <script>
          const data = ${JSON.stringify(data)};
          const html = data.map(p => \`
            <div class="card">
              <h3>\${p.name}</h3>
              <p>\${p.description}</p>
              <div class="price">\${p.price}</div>
            </div>
          \`).join('');
          document.getElementById('products').innerHTML = html;
        </script>
      </body>
    </html>
  `;
  
  await page.setContent(htmlContent);
  
  // Wait for JavaScript to render
  await page.waitForSelector('#products', { timeout: 5000 });
  
  await page.pdf({
    path: 'catalog.pdf',
    format: 'A4',
    printBackground: true
  });
  
  await browser.close();
}

const productData = [
  { name: 'Laptop', description: 'High-performance device', price: '$999' },
  { name: 'Mouse', description: 'Wireless mouse', price: '$29' }
];

generateDynamicPDF(productData).catch(console.error);
```

### 5. Using html2pdf Library (Client-Side Alternative)

```html
<!DOCTYPE html>
<html>
<head>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js"></script>
</head>
<body>
  <div id="content">
    <h1>Invoice</h1>
    <p>Amount: $500</p>
  </div>
  
  <button onclick="generatePDF()">Download PDF</button>
  
  <script>
    function generatePDF() {
      const element = document.getElementById('content');
      const opt = {
        margin: 10,
        filename: 'invoice.pdf',
        image: { type: 'jpeg', quality: 0.98 },
        html2canvas: { scale: 2 },
        jsPDF: { orientation: 'portrait', unit: 'mm', format: 'a4' }
      };
      html2pdf().set(opt).from(element).save();
    }
  </script>
</body>
</html>
```

### 6. Batch PDF Generation

```javascript
const puppeteer = require('puppeteer');
const fs = require('fs').promises;
const path = require('path');

async function batchGeneratePDFs(documents) {
  const browser = await puppeteer.launch();
  
  for (const doc of documents) {
    const page = await browser.newPage();
    await page.setContent(doc.html);
    
    await page.pdf({
      path: path.join('pdfs', `${doc.id}.pdf`),
      format: 'A4'
    });
    
    await page.close();
    console.log(`Generated: ${doc.id}.pdf`);
  }
  
  await browser.close();
}

const documents = [
  { id: 'invoice-001', html: '<h1>Invoice 001</h1>' },
  { id: 'invoice-002', html: '<h1>Invoice 002</h1>' }
];

batchGeneratePDFs(documents).catch(console.error);
```

### 7. Express.js Integration

```javascript
const express = require('express');
const puppeteer = require('puppeteer');
const app = express();

app.use(express.json());

app.post('/generate-pdf', async (req, res) => {
  try {
    const { html, filename } = req.body;
    
    const browser = await puppeteer.launch();
    const page = await browser.newPage();
    
    await page.setContent(html);
    const pdfBuffer = await page.pdf({ format: 'A4' });
    
    await browser.close();
    
    res.setHeader('Content-Type', 'application/pdf');
    res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
    res.send(pdfBuffer);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

app.listen(3000, () => console.log('Server running on port 3000'));
```

## Tips

### Performance Optimization
- **Reuse browser instance**: Create one browser and multiple pages instead of launching new browsers
- **Disable unnecessary features**: Use `--no-sandbox`, `--disable-setuid-sandbox` for production
- **Set viewport**: `page.setViewport({ width: 1920, height: 1080 })` for consistent rendering

### Quality & Rendering
- **Wait for content**: Use `waitForNavigation()`, `waitForSelector()`, or `waitForFunction()` before PDF generation
- **Print background**: Set `printBackground: true` for colored backgrounds and images
- **Scale factor**: Adjust `scale` (0.5-2) for resolution control

### Error Handling
```javascript
try {
  // PDF generation code
} catch (error) {
  console.error('PDF generation failed:', error);
  // Fallback or retry logic
}
```

### Library Comparison
| Library | Best For | Pros | Cons |
|---------|----------|------|------|
| **Puppeteer** | Complex layouts, JS rendering | Full browser control, accurate | Resource-heavy |
| **html2pdf** | Client-side, simple docs | Lightweight, no server needed | Limited CSS support |
| **pdfkit** | Custom PDFs, low-level control | Lightweight, flexible | Requires manual layout |
| **wkhtmltopdf** | Server-side, legacy | Fast, stable | Deprecated, maintenance issues |

### Security Considerations
- Sanitize HTML input to prevent injection attacks
- Validate file paths to prevent directory traversal
- Implement rate limiting for PDF generation endpoints
- Use sandboxed environments for untrusted content

### Memory Management
```javascript
// Close resources properly
await page.close();
await browser.close();

// For batch operations, reuse browser
const browser = await puppeteer.launch();
// ... multiple pages
await browser.close();
