# PDF generation workflows

Use this guide when the task is to **create a new PDF** — reports, invoices, summaries, formatted documents — rather than read or fill an existing one.

## Choosing a library

| Library | Best for | Install |
|---------|----------|---------|
| **reportlab** | Precise layout, tables, charts, multi-page reports | `pip install reportlab` |
| **weasyprint** | HTML/CSS → PDF (easiest for rich formatting) | `pip install weasyprint` |
| **fpdf2** | Simple text/table documents, lightweight | `pip install fpdf2` |

---

## reportlab — precise layout

### Minimal multi-page report

```python
from reportlab.lib.pagesizes import letter, A4
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import inch
from reportlab.lib import colors
from reportlab.platypus import (
    SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, PageBreak, HRFlowable
)

doc = SimpleDocTemplate(
    "report.pdf",
    pagesize=letter,
    leftMargin=inch,
    rightMargin=inch,
    topMargin=inch,
    bottomMargin=inch,
)

styles = getSampleStyleSheet()
story = []

# Title
title_style = ParagraphStyle(
    "Title", parent=styles["Heading1"], fontSize=20, spaceAfter=12, textColor=colors.HexColor("#1E3A8A")
)
story.append(Paragraph("Quarterly Report", title_style))
story.append(HRFlowable(width="100%", thickness=1, color=colors.grey))
story.append(Spacer(1, 0.2 * inch))

# Body paragraph
story.append(Paragraph(
    "This report summarizes financial performance for Q1 2025.",
    styles["BodyText"]
))
story.append(Spacer(1, 0.15 * inch))

# Table
data = [
    ["Region", "Revenue", "Cost", "Margin"],
    ["North", "$120,000", "$80,000", "33%"],
    ["South", "$95,000", "$70,000", "26%"],
    ["West", "$140,000", "$90,000", "36%"],
]
table = Table(data, colWidths=[1.8 * inch, 1.5 * inch, 1.5 * inch, 1.2 * inch])
table.setStyle(TableStyle([
    ("BACKGROUND", (0, 0), (-1, 0), colors.HexColor("#1E3A8A")),
    ("TEXTCOLOR",  (0, 0), (-1, 0), colors.white),
    ("FONTNAME",   (0, 0), (-1, 0), "Helvetica-Bold"),
    ("FONTSIZE",   (0, 0), (-1, -1), 10),
    ("ALIGN",      (1, 0), (-1, -1), "RIGHT"),
    ("ROWBACKGROUNDS", (0, 1), (-1, -1), [colors.white, colors.HexColor("#F1F5F9")]),
    ("GRID",       (0, 0), (-1, -1), 0.5, colors.grey),
    ("TOPPADDING", (0, 0), (-1, -1), 6),
    ("BOTTOMPADDING", (0, 0), (-1, -1), 6),
]))
story.append(table)
story.append(PageBreak())

# Second page
story.append(Paragraph("Detail", styles["Heading2"]))
story.append(Paragraph("Further analysis goes here.", styles["BodyText"]))

doc.build(story)
print("Created report.pdf")
```

### Common reportlab patterns

**Headers and footers** — pass `onFirstPage` and `onLaterPages` callbacks to `SimpleDocTemplate.build()`:

```python
def add_page_number(canvas, doc):
    canvas.saveState()
    canvas.setFont("Helvetica", 9)
    canvas.setFillColor(colors.grey)
    canvas.drawRightString(letter[0] - inch, 0.5 * inch, f"Page {doc.page}")
    canvas.restoreState()

doc.build(story, onFirstPage=add_page_number, onLaterPages=add_page_number)
```

**Images:**

```python
from reportlab.platypus import Image
story.append(Image("logo.png", width=2 * inch, height=1 * inch))
```

**Hyperlinks in paragraphs:**

```python
story.append(Paragraph('<a href="https://example.com">Visit site</a>', styles["BodyText"]))
```

---

## weasyprint — HTML/CSS → PDF

Ideal when you already have HTML templates or want CSS-driven layout (flexbox, grid, @page rules).

```python
import weasyprint

html = """
<!DOCTYPE html>
<html>
<head>
<style>
  body { font-family: Arial, sans-serif; margin: 40px; color: #222; }
  h1   { color: #1E3A8A; border-bottom: 2px solid #1E3A8A; padding-bottom: 6px; }
  table { border-collapse: collapse; width: 100%; margin-top: 16px; }
  th   { background: #1E3A8A; color: white; padding: 8px 12px; text-align: left; }
  td   { padding: 7px 12px; border-bottom: 1px solid #e2e8f0; }
  tr:nth-child(even) td { background: #f8fafc; }
  @page { size: A4; margin: 2cm; }
</style>
</head>
<body>
  <h1>Quarterly Report</h1>
  <p>Performance summary for Q1 2025.</p>
  <table>
    <tr><th>Region</th><th>Revenue</th><th>Cost</th><th>Margin</th></tr>
    <tr><td>North</td><td>$120,000</td><td>$80,000</td><td>33%</td></tr>
    <tr><td>South</td><td>$95,000</td><td>$70,000</td><td>26%</td></tr>
  </table>
</body>
</html>
"""

weasyprint.HTML(string=html).write_pdf("report.pdf")
print("Created report.pdf")
```

**From a file:** `weasyprint.HTML(filename="report.html").write_pdf("report.pdf")`
**From a URL:** `weasyprint.HTML(url="https://example.com/report").write_pdf("report.pdf")`

**Note:** weasyprint requires system libraries (`libpango`, `libcairo`). On Linux: `apt install libpango-1.0-0 libpangocairo-1.0-0`. On macOS: `brew install pango`.

---

## fpdf2 — simple lightweight reports

Good for invoices, receipts, and simple text-heavy documents without complex layout needs.

```python
from fpdf import FPDF

class PDF(FPDF):
    def header(self):
        self.set_font("Helvetica", "B", 14)
        self.set_text_color(30, 58, 138)  # #1E3A8A
        self.cell(0, 10, "Quarterly Report", align="C", new_x="LMARGIN", new_y="NEXT")
        self.set_draw_color(150, 150, 150)
        self.line(self.l_margin, self.get_y(), self.w - self.r_margin, self.get_y())
        self.ln(4)

    def footer(self):
        self.set_y(-15)
        self.set_font("Helvetica", "I", 8)
        self.set_text_color(128)
        self.cell(0, 10, f"Page {self.page_no()}", align="C")

pdf = PDF()
pdf.add_page()
pdf.set_margins(20, 20, 20)

# Body text
pdf.set_font("Helvetica", size=11)
pdf.set_text_color(34, 34, 34)
pdf.multi_cell(0, 7, "This report summarizes Q1 2025 financial performance.")
pdf.ln(6)

# Simple table
headers = ["Region", "Revenue", "Cost", "Margin"]
col_w = [50, 40, 40, 30]
rows = [("North", "$120,000", "$80,000", "33%"),
        ("South", "$95,000",  "$70,000", "26%"),
        ("West",  "$140,000", "$90,000", "36%")]

# Header row
pdf.set_fill_color(30, 58, 138)
pdf.set_text_color(255, 255, 255)
pdf.set_font("Helvetica", "B", 10)
for h, w in zip(headers, col_w):
    pdf.cell(w, 8, h, border=1, fill=True)
pdf.ln()

# Data rows
pdf.set_font("Helvetica", size=10)
fill = False
for row in rows:
    pdf.set_fill_color(241, 245, 249) if fill else pdf.set_fill_color(255, 255, 255)
    pdf.set_text_color(34, 34, 34)
    for val, w in zip(row, col_w):
        pdf.cell(w, 7, val, border=1, fill=True)
    pdf.ln()
    fill = not fill

pdf.output("report.pdf")
print("Created report.pdf")
```

---

## Choosing the right approach

| Scenario | Recommended |
|----------|-------------|
| Report with tables, precise layout, multi-page | **reportlab** |
| Rich design (brand colors, complex CSS) | **weasyprint** |
| Simple invoice / receipt / text report | **fpdf2** |
| Fill an existing PDF form | See `form_workflows.md` (AcroForm path) |
| Annotate a flat PDF | See `form_workflows.md` (annotation overlay path) |
| Merge / split existing PDFs | `burst_pdf_pages.py` / `pypdf` directly |

---

## Merging generated PDFs with existing ones

After generating a new PDF, use `pypdf` to merge it with other files:

```python
from pypdf import PdfWriter, PdfReader

writer = PdfWriter()
for path in ["cover.pdf", "report.pdf", "appendix.pdf"]:
    reader = PdfReader(path)
    for page in reader.pages:
        writer.add_page(page)

with open("final.pdf", "wb") as f:
    writer.write(f)
```
