ReportLab is the industry-standard library for generating PDF documents programmatically in Python. It's widely used for creating invoices, reports, certificates, and automated documentation.
Installation
Install ReportLab from PyPI:
pip install reportlabBasic PDF with Canvas
The Canvas object provides a drawing surface where you position elements using X, Y coordinates. The origin (0, 0) is at the bottom-left corner:
from reportlab.pdfgen import canvas
def create_simple_pdf(filename):
c = canvas.Canvas(filename)
c.drawString(100, 750, "Hello World from Python!")
c.line(100, 740, 400, 740)
c.save()
create_simple_pdf("hello.pdf")Working with Page Sizes
Use predefined page sizes for consistent document dimensions:
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import letter, A4
from reportlab.lib.units import inch, cm
def create_letter_pdf(filename):
c = canvas.Canvas(filename, pagesize=letter)
width, height = letter
c.drawString(1 * inch, height - 1 * inch, "Top of the page")
c.drawString(1 * inch, 1 * inch, "Bottom of the page")
c.save()
create_letter_pdf("letter_size.pdf")Styling Text with Fonts
Control font family, size, and color:
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import letter
from reportlab.lib.colors import navy, red, black
def create_styled_pdf(filename):
c = canvas.Canvas(filename, pagesize=letter)
width, height = letter
c.setFont("Helvetica-Bold", 24)
c.setFillColor(navy)
c.drawString(100, height - 100, "Monthly Report")
c.setFont("Helvetica", 14)
c.setFillColor(black)
c.drawString(100, height - 130, "Generated automatically")
c.setFont("Times-Roman", 12)
c.drawString(100, height - 180, "This is the report content.")
c.save()
create_styled_pdf("styled.pdf")Multiline Text with TextObject
For paragraphs and multiline content, use beginText():
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import letter
def create_multiline_pdf(filename):
c = canvas.Canvas(filename, pagesize=letter)
width, height = letter
text = c.beginText(50, height - 50)
text.setFont("Helvetica", 12)
text.setLeading(16) # Line spacing
paragraphs = [
"Executive Summary",
"",
"This report covers the quarterly performance metrics.",
"Key findings include a 15% increase in efficiency.",
"Recommendations are provided in the final section.",
]
for line in paragraphs:
text.textLine(line)
c.drawText(text)
c.save()
create_multiline_pdf("multiline.pdf")Drawing Shapes
Add rectangles, circles, and other shapes:
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import letter
from reportlab.lib.colors import lightblue, darkblue
def create_shapes_pdf(filename):
c = canvas.Canvas(filename, pagesize=letter)
c.setFillColor(lightblue)
c.rect(100, 600, 200, 100, fill=True, stroke=True)
c.setFillColor(darkblue)
c.circle(450, 650, 50, fill=True)
c.setStrokeColor(darkblue)
c.setLineWidth(3)
c.line(100, 550, 500, 550)
c.save()
create_shapes_pdf("shapes.pdf")Adding Images
Include images in your PDF:
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import letter
from reportlab.lib.units import inch
def create_pdf_with_image(filename, image_path):
c = canvas.Canvas(filename, pagesize=letter)
width, height = letter
c.drawImage(
image_path,
x=100,
y=height - 300,
width=3 * inch,
height=2 * inch,
preserveAspectRatio=True
)
c.drawString(100, height - 320, "Figure 1: Sample Image")
c.save()Creating Tables with PLATYPUS
For complex layouts with tables and flowing content, use the PLATYPUS engine:
from reportlab.lib import colors
from reportlab.lib.pagesizes import letter
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph
from reportlab.lib.styles import getSampleStyleSheet
def create_table_pdf(filename):
doc = SimpleDocTemplate(filename, pagesize=letter)
elements = []
styles = getSampleStyleSheet()
title = Paragraph("Sales Report", styles['Heading1'])
elements.append(title)
data = [
['Product', 'Q1', 'Q2', 'Q3', 'Q4'],
['Widget A', '100', '150', '200', '180'],
['Widget B', '80', '90', '110', '130'],
['Widget C', '200', '210', '190', '220'],
]
table = Table(data)
table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (-1, 0), colors.darkblue),
('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
('FONTSIZE', (0, 0), (-1, 0), 12),
('BOTTOMPADDING', (0, 0), (-1, 0), 12),
('BACKGROUND', (0, 1), (-1, -1), colors.beige),
('GRID', (0, 0), (-1, -1), 1, colors.black),
]))
elements.append(table)
doc.build(elements)
create_table_pdf("table_report.pdf")Multi-Page Documents
Handle automatic page breaks with PLATYPUS:
from reportlab.lib.pagesizes import letter
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, PageBreak
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.units import inch
def create_multipage_pdf(filename):
doc = SimpleDocTemplate(filename, pagesize=letter)
styles = getSampleStyleSheet()
elements = []
for i in range(1, 4):
elements.append(Paragraph(f"Chapter {i}", styles['Heading1']))
elements.append(Spacer(1, 0.25 * inch))
content = "Lorem ipsum dolor sit amet. " * 50
elements.append(Paragraph(content, styles['Normal']))
if i < 3:
elements.append(PageBreak())
doc.build(elements)
create_multipage_pdf("multipage.pdf")Practical Example: Invoice Generator
A complete invoice generation example:
from reportlab.lib.pagesizes import letter
from reportlab.lib import colors
from reportlab.lib.units import inch
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
def generate_invoice(filename, invoice_data):
doc = SimpleDocTemplate(filename, pagesize=letter)
styles = getSampleStyleSheet()
elements = []
elements.append(Paragraph("INVOICE", styles['Heading1']))
elements.append(Paragraph(f"Invoice #: {invoice_data['number']}", styles['Normal']))
elements.append(Paragraph(f"Date: {invoice_data['date']}", styles['Normal']))
elements.append(Spacer(1, 0.5 * inch))
table_data = [['Description', 'Quantity', 'Unit Price', 'Total']]
for item in invoice_data['items']:
total = item['quantity'] * item['price']
table_data.append([
item['description'],
str(item['quantity']),
f"${item['price']:.2f}",
f"${total:.2f}"
])
grand_total = sum(i['quantity'] * i['price'] for i in invoice_data['items'])
table_data.append(['', '', 'Total:', f"${grand_total:.2f}"])
table = Table(table_data, colWidths=[3*inch, 1*inch, 1*inch, 1*inch])
table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (-1, 0), colors.grey),
('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
('ALIGN', (1, 0), (-1, -1), 'RIGHT'),
('GRID', (0, 0), (-1, -2), 0.5, colors.grey),
('FONTNAME', (-2, -1), (-1, -1), 'Helvetica-Bold'),
]))
elements.append(table)
doc.build(elements)
invoice = {
'number': 'INV-2026-001',
'date': '2026-01-15',
'items': [
{'description': 'Web Development', 'quantity': 10, 'price': 150.00},
{'description': 'Design Services', 'quantity': 5, 'price': 100.00},
]
}
generate_invoice("invoice.pdf", invoice)Method Comparison
| Approach | Use Case | Complexity |
|---|---|---|
| Canvas | Simple graphics, precise positioning | Low |
| TextObject | Multiline text blocks | Low |
| PLATYPUS | Tables, flowing content, multi-page | Medium |
| Templates | Repeating layouts, headers/footers | High |
Use the Canvas API for simple layouts with precise control, and PLATYPUS for complex documents requiring tables, automatic pagination, and flowing text.