Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 08:30:10 +08:00
commit f0bd18fb4e
824 changed files with 331919 additions and 0 deletions

621
skills/reportlab/SKILL.md Normal file
View File

@@ -0,0 +1,621 @@
---
name: reportlab
description: "PDF generation toolkit. Create invoices, reports, certificates, forms, charts, tables, barcodes, QR codes, Canvas/Platypus APIs, for professional document automation."
---
# ReportLab PDF Generation
## Overview
ReportLab is a powerful Python library for programmatic PDF generation. Create anything from simple documents to complex reports with tables, charts, images, and interactive forms.
**Two main approaches:**
- **Canvas API** (low-level): Direct drawing with coordinate-based positioning - use for precise layouts
- **Platypus** (high-level): Flowing document layout with automatic page breaks - use for multi-page documents
**Core capabilities:**
- Text with rich formatting and custom fonts
- Tables with complex styling and cell spanning
- Charts (bar, line, pie, area, scatter)
- Barcodes and QR codes (Code128, EAN, QR, etc.)
- Images with transparency
- PDF features (links, bookmarks, forms, encryption)
## Choosing the Right Approach
### Use Canvas API when:
- Creating labels, business cards, certificates
- Precise positioning is critical (x, y coordinates)
- Single-page documents or simple layouts
- Drawing graphics, shapes, and custom designs
- Adding barcodes or QR codes at specific locations
### Use Platypus when:
- Creating multi-page documents (reports, articles, books)
- Content should flow automatically across pages
- Need headers/footers that repeat on each page
- Working with paragraphs that can split across pages
- Building complex documents with table of contents
### Use Both when:
- Complex reports need both flowing content AND precise positioning
- Adding headers/footers to Platypus documents (use `onPage` callback with Canvas)
- Embedding custom graphics (Canvas) within flowing documents (Platypus)
## Quick Start Examples
### Simple Canvas Document
```python
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import letter
from reportlab.lib.units import inch
c = canvas.Canvas("output.pdf", pagesize=letter)
width, height = letter
# Draw text
c.setFont("Helvetica-Bold", 24)
c.drawString(inch, height - inch, "Hello ReportLab!")
# Draw a rectangle
c.setFillColorRGB(0.2, 0.4, 0.8)
c.rect(inch, 5*inch, 4*inch, 2*inch, fill=1)
# Save
c.showPage()
c.save()
```
### Simple Platypus Document
```python
from reportlab.lib.pagesizes import letter
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.units import inch
doc = SimpleDocTemplate("output.pdf", pagesize=letter)
story = []
styles = getSampleStyleSheet()
# Add content
story.append(Paragraph("Document Title", styles['Title']))
story.append(Spacer(1, 0.2*inch))
story.append(Paragraph("This is body text with <b>bold</b> and <i>italic</i>.", styles['BodyText']))
# Build PDF
doc.build(story)
```
## Common Tasks
### Creating Tables
Tables work with both Canvas (via Drawing) and Platypus (as Flowables):
```python
from reportlab.platypus import Table, TableStyle
from reportlab.lib import colors
from reportlab.lib.units import inch
# Define data
data = [
['Product', 'Q1', 'Q2', 'Q3', 'Q4'],
['Widget A', '100', '150', '130', '180'],
['Widget B', '80', '120', '110', '160'],
]
# Create table
table = Table(data, colWidths=[2*inch, 1*inch, 1*inch, 1*inch, 1*inch])
# Apply styling
style = TableStyle([
# Header row
('BACKGROUND', (0, 0), (-1, 0), colors.darkblue),
('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
# Data rows
('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, colors.lightgrey]),
('GRID', (0, 0), (-1, -1), 1, colors.black),
])
table.setStyle(style)
# Add to Platypus story
story.append(table)
# Or draw on Canvas
table.wrapOn(c, width, height)
table.drawOn(c, x, y)
```
**Detailed table reference:** See `references/tables_reference.md` for cell spanning, borders, alignment, and advanced styling.
### Creating Charts
Charts use the graphics framework and can be added to both Canvas and Platypus:
```python
from reportlab.graphics.shapes import Drawing
from reportlab.graphics.charts.barcharts import VerticalBarChart
from reportlab.lib import colors
# Create drawing
drawing = Drawing(400, 200)
# Create chart
chart = VerticalBarChart()
chart.x = 50
chart.y = 50
chart.width = 300
chart.height = 125
# Set data
chart.data = [[100, 150, 130, 180, 140]]
chart.categoryAxis.categoryNames = ['Q1', 'Q2', 'Q3', 'Q4', 'Q5']
# Style
chart.bars[0].fillColor = colors.blue
chart.valueAxis.valueMin = 0
chart.valueAxis.valueMax = 200
# Add to drawing
drawing.add(chart)
# Use in Platypus
story.append(drawing)
# Or render directly to PDF
from reportlab.graphics import renderPDF
renderPDF.drawToFile(drawing, 'chart.pdf', 'Chart Title')
```
**Available chart types:** Bar (vertical/horizontal), Line, Pie, Area, Scatter
**Detailed charts reference:** See `references/charts_reference.md` for all chart types, styling, legends, and customization.
### Adding Barcodes and QR Codes
```python
from reportlab.graphics.barcode import code128
from reportlab.graphics.barcode.qr import QrCodeWidget
from reportlab.graphics.shapes import Drawing
from reportlab.graphics import renderPDF
# Code128 barcode (general purpose)
barcode = code128.Code128("ABC123456789", barHeight=0.5*inch)
# On Canvas
barcode.drawOn(c, x, y)
# QR Code
qr = QrCodeWidget("https://example.com")
qr.barWidth = 2*inch
qr.barHeight = 2*inch
# Wrap in Drawing for Platypus
d = Drawing()
d.add(qr)
story.append(d)
```
**Supported formats:** Code128, Code39, EAN-13, EAN-8, UPC-A, ISBN, QR, Data Matrix, and 20+ more
**Detailed barcode reference:** See `references/barcodes_reference.md` for all formats and usage examples.
### Working with Text and Fonts
```python
from reportlab.platypus import Paragraph
from reportlab.lib.styles import ParagraphStyle
from reportlab.lib.enums import TA_JUSTIFY
# Create custom style
custom_style = ParagraphStyle(
'CustomStyle',
fontSize=12,
leading=14, # Line spacing
alignment=TA_JUSTIFY,
spaceAfter=10,
textColor=colors.black,
)
# Paragraph with inline formatting
text = """
This paragraph has <b>bold</b>, <i>italic</i>, and <u>underlined</u> text.
You can also use <font color="blue">colors</font> and <font size="14">different sizes</font>.
Chemical formula: H<sub>2</sub>O, Einstein: E=mc<sup>2</sup>
"""
para = Paragraph(text, custom_style)
story.append(para)
```
**Using custom fonts:**
```python
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
# Register TrueType font
pdfmetrics.registerFont(TTFont('CustomFont', 'CustomFont.ttf'))
# Use in Canvas
c.setFont('CustomFont', 12)
# Use in Paragraph style
style = ParagraphStyle('Custom', fontName='CustomFont', fontSize=12)
```
**Detailed text reference:** See `references/text_and_fonts.md` for paragraph styles, font families, Asian languages, Greek letters, and formatting.
### Adding Images
```python
from reportlab.platypus import Image
from reportlab.lib.units import inch
# In Platypus
img = Image('photo.jpg', width=4*inch, height=3*inch)
story.append(img)
# Maintain aspect ratio
img = Image('photo.jpg', width=4*inch, height=3*inch, kind='proportional')
# In Canvas
c.drawImage('photo.jpg', x, y, width=4*inch, height=3*inch)
# With transparency (mask white background)
c.drawImage('logo.png', x, y, mask=[255,255,255,255,255,255])
```
### Creating Forms
```python
from reportlab.pdfgen import canvas
from reportlab.lib.colors import black, white, lightgrey
c = canvas.Canvas("form.pdf")
# Text field
c.acroForm.textfield(
name="name",
tooltip="Enter your name",
x=100, y=700,
width=200, height=20,
borderColor=black,
fillColor=lightgrey,
forceBorder=True
)
# Checkbox
c.acroForm.checkbox(
name="agree",
x=100, y=650,
size=20,
buttonStyle='check',
checked=False
)
# Dropdown
c.acroForm.choice(
name="country",
x=100, y=600,
width=150, height=20,
options=[("United States", "US"), ("Canada", "CA")],
forceBorder=True
)
c.save()
```
**Detailed PDF features reference:** See `references/pdf_features.md` for forms, links, bookmarks, encryption, and metadata.
### Headers and Footers
For Platypus documents, use page callbacks:
```python
from reportlab.platypus import BaseDocTemplate, PageTemplate, Frame
def add_header_footer(canvas, doc):
"""Called on each page"""
canvas.saveState()
# Header
canvas.setFont('Helvetica', 9)
canvas.drawString(inch, height - 0.5*inch, "Document Title")
# Footer
canvas.drawRightString(width - inch, 0.5*inch, f"Page {doc.page}")
canvas.restoreState()
# Set up document
doc = BaseDocTemplate("output.pdf")
frame = Frame(doc.leftMargin, doc.bottomMargin, doc.width, doc.height, id='normal')
template = PageTemplate(id='normal', frames=[frame], onPage=add_header_footer)
doc.addPageTemplates([template])
# Build with story
doc.build(story)
```
## Helper Scripts
This skill includes helper scripts for common tasks:
### Quick Document Generator
Use `scripts/quick_document.py` for rapid document creation:
```python
from scripts.quick_document import create_simple_document, create_styled_table
# Simple document from content blocks
content = [
{'type': 'heading', 'content': 'Introduction'},
{'type': 'paragraph', 'content': 'Your text here...'},
{'type': 'bullet', 'content': 'Bullet point'},
]
create_simple_document("output.pdf", "My Document", content_blocks=content)
# Styled tables with presets
data = [['Header1', 'Header2'], ['Data1', 'Data2']]
table = create_styled_table(data, style_name='striped') # 'default', 'striped', 'minimal', 'report'
```
## Template Examples
Complete working examples in `assets/`:
### Invoice Template
`assets/invoice_template.py` - Professional invoice with:
- Company and client information
- Itemized table with calculations
- Tax and totals
- Terms and notes
- Logo placement
```python
from assets.invoice_template import create_invoice
create_invoice(
filename="invoice.pdf",
invoice_number="INV-2024-001",
invoice_date="January 15, 2024",
due_date="February 15, 2024",
company_info={'name': 'Acme Corp', 'address': '...', 'phone': '...', 'email': '...'},
client_info={'name': 'Client Name', ...},
items=[
{'description': 'Service', 'quantity': 1, 'unit_price': 500.00},
...
],
tax_rate=0.08,
notes="Thank you for your business!",
)
```
### Report Template
`assets/report_template.py` - Multi-page business report with:
- Cover page
- Table of contents
- Multiple sections with subsections
- Charts and tables
- Headers and footers
```python
from assets.report_template import create_report
report_data = {
'title': 'Quarterly Report',
'subtitle': 'Q4 2023',
'author': 'Analytics Team',
'sections': [
{
'title': 'Executive Summary',
'content': 'Report content...',
'table_data': {...},
'chart_data': {...}
},
...
]
}
create_report("report.pdf", report_data)
```
## Reference Documentation
Comprehensive API references organized by feature:
- **`references/canvas_api.md`** - Low-level Canvas: drawing primitives, coordinates, transformations, state management, images, paths
- **`references/platypus_guide.md`** - High-level Platypus: document templates, frames, flowables, page layouts, TOC
- **`references/text_and_fonts.md`** - Text formatting: paragraph styles, inline markup, custom fonts, Asian languages, bullets, sequences
- **`references/tables_reference.md`** - Tables: creation, styling, cell spanning, borders, alignment, colors, gradients
- **`references/charts_reference.md`** - Charts: all chart types, data handling, axes, legends, colors, rendering
- **`references/barcodes_reference.md`** - Barcodes: Code128, QR codes, EAN, UPC, postal codes, and 20+ formats
- **`references/pdf_features.md`** - PDF features: links, bookmarks, forms, encryption, metadata, page transitions
## Best Practices
### Coordinate System (Canvas)
- Origin (0, 0) is **lower-left corner** (not top-left)
- Y-axis points **upward**
- Units are in **points** (72 points = 1 inch)
- Always specify page size explicitly
```python
from reportlab.lib.pagesizes import letter
from reportlab.lib.units import inch
width, height = letter
margin = inch
# Top of page
y_top = height - margin
# Bottom of page
y_bottom = margin
```
### Choosing Page Size
```python
from reportlab.lib.pagesizes import letter, A4, landscape
# US Letter (8.5" x 11")
pagesize=letter
# ISO A4 (210mm x 297mm)
pagesize=A4
# Landscape
pagesize=landscape(letter)
# Custom
pagesize=(6*inch, 9*inch)
```
### Performance Tips
1. **Use `drawImage()` over `drawInlineImage()`** - caches images for reuse
2. **Enable compression for large files:** `canvas.Canvas("file.pdf", pageCompression=1)`
3. **Reuse styles** - create once, use throughout document
4. **Use Forms/XObjects** for repeated graphics
### Common Patterns
**Centering text on Canvas:**
```python
text = "Centered Text"
text_width = c.stringWidth(text, "Helvetica", 12)
x = (width - text_width) / 2
c.drawString(x, y, text)
# Or use built-in
c.drawCentredString(width/2, y, text)
```
**Page breaks in Platypus:**
```python
from reportlab.platypus import PageBreak
story.append(PageBreak())
```
**Keep content together (no split):**
```python
from reportlab.platypus import KeepTogether
story.append(KeepTogether([
heading,
paragraph1,
paragraph2,
]))
```
**Alternate row colors:**
```python
style = TableStyle([
('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, colors.lightgrey]),
])
```
## Troubleshooting
**Text overlaps or disappears:**
- Check Y-coordinates - remember origin is bottom-left
- Ensure text fits within page bounds
- Verify `leading` (line spacing) is greater than `fontSize`
**Table doesn't fit on page:**
- Reduce column widths
- Decrease font size
- Use landscape orientation
- Enable table splitting with `repeatRows`
**Barcode not scanning:**
- Increase `barHeight` (try 0.5 inch minimum)
- Set `quiet=1` for quiet zones
- Test print quality (300+ DPI recommended)
- Validate data format for barcode type
**Font not found:**
- Register TrueType fonts with `pdfmetrics.registerFont()`
- Use font family name exactly as registered
- Check font file path is correct
**Images have white background:**
- Use `mask` parameter to make white transparent
- Provide RGB range to mask: `mask=[255,255,255,255,255,255]`
- Or use PNG with alpha channel
## Example Workflows
### Creating an Invoice
1. Start with invoice template from `assets/invoice_template.py`
2. Customize company info, logo path
3. Add items with descriptions, quantities, prices
4. Set tax rate if applicable
5. Add notes and payment terms
6. Generate PDF
### Creating a Report
1. Start with report template from `assets/report_template.py`
2. Define sections with titles and content
3. Add tables for data using `create_styled_table()`
4. Add charts using graphics framework
5. Build with `doc.multiBuild(story)` for TOC
### Creating a Certificate
1. Use Canvas API for precise positioning
2. Load custom fonts for elegant typography
3. Add border graphics or image background
4. Position text elements (name, date, achievement)
5. Optional: Add QR code for verification
### Creating Labels with Barcodes
1. Use Canvas with custom page size (label dimensions)
2. Calculate grid positions for multiple labels per page
3. Draw label content (text, images)
4. Add barcode at specific position
5. Use `showPage()` between labels or grids
## Installation
```bash
uv pip install reportlab
# For image support
uv pip install pillow
# For charts
uv pip install reportlab[renderPM]
# For barcode support (included in reportlab)
# QR codes require: uv pip install qrcode
```
## When to Use This Skill
This skill should be used when:
- Generating PDF documents programmatically
- Creating invoices, receipts, or billing documents
- Building reports with tables and charts
- Generating certificates, badges, or credentials
- Creating shipping labels or product labels with barcodes
- Designing forms or fillable PDFs
- Producing multi-page documents with consistent formatting
- Converting data to PDF format for archival or distribution
- Creating custom layouts that require precise positioning
This skill provides comprehensive guidance for all ReportLab capabilities, from simple documents to complex multi-page reports with charts, tables, and interactive elements.

View File

@@ -0,0 +1,256 @@
#!/usr/bin/env python3
"""
Invoice Template - Complete example of a professional invoice
This template demonstrates:
- Company header with logo placement
- Client information
- Invoice details table
- Calculations (subtotal, tax, total)
- Professional styling
- Terms and conditions footer
"""
from reportlab.lib.pagesizes import letter
from reportlab.lib.units import inch
from reportlab.lib import colors
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, Image
)
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_LEFT, TA_RIGHT, TA_CENTER
from datetime import datetime
def create_invoice(
filename,
invoice_number,
invoice_date,
due_date,
company_info,
client_info,
items,
tax_rate=0.0,
notes="",
terms="Payment due within 30 days.",
logo_path=None
):
"""
Create a professional invoice PDF.
Args:
filename: Output PDF filename
invoice_number: Invoice number (e.g., "INV-2024-001")
invoice_date: Date of invoice (datetime or string)
due_date: Payment due date (datetime or string)
company_info: Dict with company details
{'name': 'Company Name', 'address': 'Address', 'phone': 'Phone', 'email': 'Email'}
client_info: Dict with client details (same structure as company_info)
items: List of dicts with item details
[{'description': 'Item', 'quantity': 1, 'unit_price': 100.00}, ...]
tax_rate: Tax rate as decimal (e.g., 0.08 for 8%)
notes: Additional notes to client
terms: Payment terms
logo_path: Path to company logo image (optional)
"""
# Create document
doc = SimpleDocTemplate(filename, pagesize=letter,
rightMargin=0.5*inch, leftMargin=0.5*inch,
topMargin=0.5*inch, bottomMargin=0.5*inch)
# Container for elements
story = []
styles = getSampleStyleSheet()
# Create custom styles
title_style = ParagraphStyle(
'InvoiceTitle',
parent=styles['Heading1'],
fontSize=24,
textColor=colors.HexColor('#2C3E50'),
spaceAfter=12,
)
header_style = ParagraphStyle(
'Header',
parent=styles['Normal'],
fontSize=10,
textColor=colors.HexColor('#34495E'),
)
# --- HEADER SECTION ---
header_data = []
# Company info (left side)
company_text = f"""
<b><font size="14">{company_info['name']}</font></b><br/>
{company_info.get('address', '')}<br/>
Phone: {company_info.get('phone', '')}<br/>
Email: {company_info.get('email', '')}
"""
# Invoice title and number (right side)
invoice_text = f"""
<b><font size="16" color="#2C3E50">INVOICE</font></b><br/>
<font size="10">Invoice #: {invoice_number}</font><br/>
<font size="10">Date: {invoice_date}</font><br/>
<font size="10">Due Date: {due_date}</font>
"""
if logo_path:
logo = Image(logo_path, width=1.5*inch, height=1*inch)
header_data = [[logo, Paragraph(company_text, header_style), Paragraph(invoice_text, header_style)]]
header_table = Table(header_data, colWidths=[1.5*inch, 3*inch, 2.5*inch])
else:
header_data = [[Paragraph(company_text, header_style), Paragraph(invoice_text, header_style)]]
header_table = Table(header_data, colWidths=[4.5*inch, 2.5*inch])
header_table.setStyle(TableStyle([
('VALIGN', (0, 0), (-1, -1), 'TOP'),
('ALIGN', (-1, 0), (-1, -1), 'RIGHT'),
]))
story.append(header_table)
story.append(Spacer(1, 0.3*inch))
# --- CLIENT INFORMATION ---
client_label = Paragraph("<b>Bill To:</b>", header_style)
client_text = f"""
<b>{client_info['name']}</b><br/>
{client_info.get('address', '')}<br/>
Phone: {client_info.get('phone', '')}<br/>
Email: {client_info.get('email', '')}
"""
client_para = Paragraph(client_text, header_style)
client_table = Table([[client_label, client_para]], colWidths=[1*inch, 6*inch])
story.append(client_table)
story.append(Spacer(1, 0.3*inch))
# --- ITEMS TABLE ---
# Table header
items_data = [['Description', 'Quantity', 'Unit Price', 'Amount']]
# Calculate items
subtotal = 0
for item in items:
desc = item['description']
qty = item['quantity']
price = item['unit_price']
amount = qty * price
subtotal += amount
items_data.append([
desc,
str(qty),
f"${price:,.2f}",
f"${amount:,.2f}"
])
# Create items table
items_table = Table(items_data, colWidths=[3.5*inch, 1*inch, 1.5*inch, 1*inch])
items_table.setStyle(TableStyle([
# Header row
('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#34495E')),
('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
('ALIGN', (0, 0), (-1, 0), 'CENTER'),
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
('FONTSIZE', (0, 0), (-1, 0), 11),
('BOTTOMPADDING', (0, 0), (-1, 0), 12),
# Data rows
('BACKGROUND', (0, 1), (-1, -1), colors.white),
('ALIGN', (1, 1), (-1, -1), 'RIGHT'),
('ALIGN', (0, 1), (0, -1), 'LEFT'),
('FONTNAME', (0, 1), (-1, -1), 'Helvetica'),
('FONTSIZE', (0, 1), (-1, -1), 10),
('TOPPADDING', (0, 1), (-1, -1), 6),
('BOTTOMPADDING', (0, 1), (-1, -1), 6),
('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
]))
story.append(items_table)
story.append(Spacer(1, 0.2*inch))
# --- TOTALS SECTION ---
tax_amount = subtotal * tax_rate
total = subtotal + tax_amount
totals_data = [
['Subtotal:', f"${subtotal:,.2f}"],
]
if tax_rate > 0:
totals_data.append([f'Tax ({tax_rate*100:.1f}%):', f"${tax_amount:,.2f}"])
totals_data.append(['<b>Total:</b>', f"<b>${total:,.2f}</b>"])
totals_table = Table(totals_data, colWidths=[5*inch, 2*inch])
totals_table.setStyle(TableStyle([
('ALIGN', (0, 0), (-1, -1), 'RIGHT'),
('FONTNAME', (0, 0), (-1, -2), 'Helvetica'),
('FONTNAME', (0, -1), (-1, -1), 'Helvetica-Bold'),
('FONTSIZE', (0, 0), (-1, -1), 11),
('TOPPADDING', (0, 0), (-1, -1), 6),
('LINEABOVE', (1, -1), (1, -1), 2, colors.HexColor('#34495E')),
]))
story.append(totals_table)
story.append(Spacer(1, 0.4*inch))
# --- NOTES ---
if notes:
notes_style = ParagraphStyle('Notes', parent=styles['Normal'], fontSize=9)
story.append(Paragraph(f"<b>Notes:</b><br/>{notes}", notes_style))
story.append(Spacer(1, 0.2*inch))
# --- TERMS ---
terms_style = ParagraphStyle('Terms', parent=styles['Normal'],
fontSize=9, textColor=colors.grey)
story.append(Paragraph(f"<b>Payment Terms:</b><br/>{terms}", terms_style))
# Build PDF
doc.build(story)
return filename
# Example usage
if __name__ == "__main__":
# Sample data
company = {
'name': 'Acme Corporation',
'address': '123 Business St, Suite 100\nNew York, NY 10001',
'phone': '(555) 123-4567',
'email': 'info@acme.com'
}
client = {
'name': 'John Doe',
'address': '456 Client Ave\nLos Angeles, CA 90001',
'phone': '(555) 987-6543',
'email': 'john@example.com'
}
items = [
{'description': 'Web Design Services', 'quantity': 1, 'unit_price': 2500.00},
{'description': 'Content Writing (10 pages)', 'quantity': 10, 'unit_price': 50.00},
{'description': 'SEO Optimization', 'quantity': 1, 'unit_price': 750.00},
{'description': 'Hosting Setup', 'quantity': 1, 'unit_price': 200.00},
]
create_invoice(
filename="sample_invoice.pdf",
invoice_number="INV-2024-001",
invoice_date="January 15, 2024",
due_date="February 15, 2024",
company_info=company,
client_info=client,
items=items,
tax_rate=0.08,
notes="Thank you for your business! We appreciate your prompt payment.",
terms="Payment due within 30 days. Late payments subject to 1.5% monthly fee.",
logo_path=None # Set to your logo path if available
)
print("Invoice created: sample_invoice.pdf")

View File

@@ -0,0 +1,343 @@
#!/usr/bin/env python3
"""
Report Template - Complete example of a professional multi-page report
This template demonstrates:
- Cover page
- Table of contents
- Multiple sections with headers
- Charts and graphs integration
- Tables with data
- Headers and footers
- Professional styling
"""
from reportlab.lib.pagesizes import letter
from reportlab.lib.units import inch
from reportlab.lib import colors
from reportlab.platypus import (
BaseDocTemplate, PageTemplate, Frame, Paragraph, Spacer,
Table, TableStyle, PageBreak, KeepTogether, TableOfContents
)
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_LEFT, TA_RIGHT, TA_CENTER, TA_JUSTIFY
from reportlab.graphics.shapes import Drawing
from reportlab.graphics.charts.barcharts import VerticalBarChart
from reportlab.graphics.charts.linecharts import HorizontalLineChart
from datetime import datetime
def header_footer(canvas, doc):
"""Draw header and footer on each page (except cover)"""
canvas.saveState()
# Skip header/footer on cover page (page 1)
if doc.page > 1:
# Header
canvas.setFont('Helvetica', 9)
canvas.setFillColor(colors.grey)
canvas.drawString(inch, letter[1] - 0.5*inch, "Quarterly Business Report")
canvas.line(inch, letter[1] - 0.55*inch, letter[0] - inch, letter[1] - 0.55*inch)
# Footer
canvas.drawString(inch, 0.5*inch, f"Generated: {datetime.now().strftime('%B %d, %Y')}")
canvas.drawRightString(letter[0] - inch, 0.5*inch, f"Page {doc.page - 1}")
canvas.restoreState()
def create_report(filename, report_data):
"""
Create a comprehensive business report.
Args:
filename: Output PDF filename
report_data: Dict containing report information
{
'title': 'Report Title',
'subtitle': 'Report Subtitle',
'author': 'Author Name',
'date': 'Date',
'sections': [
{
'title': 'Section Title',
'content': 'Section content...',
'subsections': [...],
'table': {...},
'chart': {...}
},
...
]
}
"""
# Create document with custom page template
doc = BaseDocTemplate(filename, pagesize=letter,
rightMargin=72, leftMargin=72,
topMargin=inch, bottomMargin=inch)
# Define frame for content
frame = Frame(doc.leftMargin, doc.bottomMargin, doc.width, doc.height - 0.5*inch, id='normal')
# Create page template with header/footer
template = PageTemplate(id='normal', frames=[frame], onPage=header_footer)
doc.addPageTemplates([template])
# Get styles
styles = getSampleStyleSheet()
# Custom styles
title_style = ParagraphStyle(
'ReportTitle',
parent=styles['Title'],
fontSize=28,
textColor=colors.HexColor('#2C3E50'),
spaceAfter=20,
alignment=TA_CENTER,
)
subtitle_style = ParagraphStyle(
'ReportSubtitle',
parent=styles['Normal'],
fontSize=14,
textColor=colors.grey,
alignment=TA_CENTER,
spaceAfter=30,
)
heading1_style = ParagraphStyle(
'CustomHeading1',
parent=styles['Heading1'],
fontSize=18,
textColor=colors.HexColor('#2C3E50'),
spaceAfter=12,
spaceBefore=12,
)
heading2_style = ParagraphStyle(
'CustomHeading2',
parent=styles['Heading2'],
fontSize=14,
textColor=colors.HexColor('#34495E'),
spaceAfter=10,
spaceBefore=10,
)
body_style = ParagraphStyle(
'ReportBody',
parent=styles['BodyText'],
fontSize=11,
alignment=TA_JUSTIFY,
spaceAfter=12,
leading=14,
)
# Build story
story = []
# --- COVER PAGE ---
story.append(Spacer(1, 2*inch))
story.append(Paragraph(report_data['title'], title_style))
story.append(Paragraph(report_data.get('subtitle', ''), subtitle_style))
story.append(Spacer(1, inch))
# Cover info table
cover_info = [
['Prepared by:', report_data.get('author', '')],
['Date:', report_data.get('date', datetime.now().strftime('%B %d, %Y'))],
['Period:', report_data.get('period', 'Q4 2023')],
]
cover_table = Table(cover_info, colWidths=[2*inch, 4*inch])
cover_table.setStyle(TableStyle([
('ALIGN', (0, 0), (0, -1), 'RIGHT'),
('ALIGN', (1, 0), (1, -1), 'LEFT'),
('FONTNAME', (0, 0), (0, -1), 'Helvetica-Bold'),
('FONTSIZE', (0, 0), (-1, -1), 11),
('TOPPADDING', (0, 0), (-1, -1), 6),
]))
story.append(cover_table)
story.append(PageBreak())
# --- TABLE OF CONTENTS ---
toc = TableOfContents()
toc.levelStyles = [
ParagraphStyle(name='TOCHeading1', fontSize=14, leftIndent=20, spaceBefore=10, spaceAfter=5),
ParagraphStyle(name='TOCHeading2', fontSize=12, leftIndent=40, spaceBefore=3, spaceAfter=3),
]
story.append(Paragraph("Table of Contents", heading1_style))
story.append(toc)
story.append(PageBreak())
# --- SECTIONS ---
for section in report_data.get('sections', []):
# Section heading
section_title = section['title']
story.append(Paragraph(f'<a name="{section_title}"/>{section_title}', heading1_style))
# Add to TOC
toc.addEntry(0, section_title, doc.page)
# Section content
if 'content' in section:
for para in section['content'].split('\n\n'):
if para.strip():
story.append(Paragraph(para.strip(), body_style))
story.append(Spacer(1, 0.2*inch))
# Subsections
for subsection in section.get('subsections', []):
story.append(Paragraph(subsection['title'], heading2_style))
if 'content' in subsection:
story.append(Paragraph(subsection['content'], body_style))
story.append(Spacer(1, 0.1*inch))
# Add table if provided
if 'table_data' in section:
table = create_section_table(section['table_data'])
story.append(table)
story.append(Spacer(1, 0.2*inch))
# Add chart if provided
if 'chart_data' in section:
chart = create_section_chart(section['chart_data'])
story.append(chart)
story.append(Spacer(1, 0.2*inch))
story.append(Spacer(1, 0.3*inch))
# Build PDF (twice for TOC to populate)
doc.multiBuild(story)
return filename
def create_section_table(table_data):
"""Create a styled table for report sections"""
data = table_data['data']
table = Table(data, colWidths=table_data.get('colWidths'))
table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#34495E')),
('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), 11),
('BOTTOMPADDING', (0, 0), (-1, 0), 12),
('BACKGROUND', (0, 1), (-1, -1), colors.white),
('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, colors.lightgrey]),
('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
('FONTNAME', (0, 1), (-1, -1), 'Helvetica'),
('FONTSIZE', (0, 1), (-1, -1), 10),
]))
return table
def create_section_chart(chart_data):
"""Create a chart for report sections"""
chart_type = chart_data.get('type', 'bar')
drawing = Drawing(400, 200)
if chart_type == 'bar':
chart = VerticalBarChart()
chart.x = 50
chart.y = 30
chart.width = 300
chart.height = 150
chart.data = chart_data['data']
chart.categoryAxis.categoryNames = chart_data.get('categories', [])
chart.valueAxis.valueMin = 0
# Style bars
for i in range(len(chart_data['data'])):
chart.bars[i].fillColor = colors.HexColor(['#3498db', '#e74c3c', '#2ecc71'][i % 3])
elif chart_type == 'line':
chart = HorizontalLineChart()
chart.x = 50
chart.y = 30
chart.width = 300
chart.height = 150
chart.data = chart_data['data']
chart.categoryAxis.categoryNames = chart_data.get('categories', [])
# Style lines
for i in range(len(chart_data['data'])):
chart.lines[i].strokeColor = colors.HexColor(['#3498db', '#e74c3c', '#2ecc71'][i % 3])
chart.lines[i].strokeWidth = 2
drawing.add(chart)
return drawing
# Example usage
if __name__ == "__main__":
report = {
'title': 'Quarterly Business Report',
'subtitle': 'Q4 2023 Performance Analysis',
'author': 'Analytics Team',
'date': 'January 15, 2024',
'period': 'October - December 2023',
'sections': [
{
'title': 'Executive Summary',
'content': """
This report provides a comprehensive analysis of our Q4 2023 performance.
Overall, the quarter showed strong growth across all key metrics, with
revenue increasing by 25% year-over-year and customer satisfaction
scores reaching an all-time high of 4.8/5.0.
Key highlights include the successful launch of three new products,
expansion into two new markets, and the completion of our digital
transformation initiative.
""",
'subsections': [
{
'title': 'Key Achievements',
'content': 'Successfully launched Product X with 10,000 units sold in first month.'
}
]
},
{
'title': 'Financial Performance',
'content': """
The financial results for Q4 exceeded expectations across all categories.
Revenue growth was driven primarily by strong product sales and increased
market share in key regions.
""",
'table_data': {
'data': [
['Metric', 'Q3 2023', 'Q4 2023', 'Change'],
['Revenue', '$2.5M', '$3.1M', '+24%'],
['Profit', '$500K', '$680K', '+36%'],
['Expenses', '$2.0M', '$2.4M', '+20%'],
],
'colWidths': [2*inch, 1.5*inch, 1.5*inch, 1*inch]
},
'chart_data': {
'type': 'bar',
'data': [[2.5, 3.1], [0.5, 0.68], [2.0, 2.4]],
'categories': ['Q3', 'Q4']
}
},
{
'title': 'Market Analysis',
'content': """
Market conditions remained favorable throughout the quarter, with
strong consumer confidence and increasing demand for our products.
""",
'chart_data': {
'type': 'line',
'data': [[100, 120, 115, 140, 135, 150]],
'categories': ['Oct', 'Nov', 'Dec', 'Oct', 'Nov', 'Dec']
}
},
]
}
create_report("sample_report.pdf", report)
print("Report created: sample_report.pdf")

View File

@@ -0,0 +1,504 @@
# Barcodes Reference
Comprehensive guide to creating barcodes and QR codes in ReportLab.
## Available Barcode Types
ReportLab supports a wide range of 1D and 2D barcode formats.
### 1D Barcodes (Linear)
- **Code128** - Compact, encodes full ASCII
- **Code39** (Standard39) - Alphanumeric, widely supported
- **Code93** (Standard93) - Compressed Code39
- **EAN-13** - European Article Number (retail)
- **EAN-8** - Short form of EAN
- **EAN-5** - 5-digit add-on (pricing)
- **UPC-A** - Universal Product Code (North America)
- **ISBN** - International Standard Book Number
- **Code11** - Telecommunications
- **Codabar** - Blood banks, FedEx, libraries
- **I2of5** (Interleaved 2 of 5) - Warehouse/distribution
- **MSI** - Inventory control
- **POSTNET** - US Postal Service
- **USPS_4State** - US Postal Service
- **FIM** (A, B, C, D) - Facing Identification Mark (mail sorting)
### 2D Barcodes
- **QR** - QR Code (widely used for URLs, contact info)
- **ECC200DataMatrix** - Data Matrix format
## Using Barcodes with Canvas
### Code128 (Recommended for General Use)
Code128 is versatile and compact - encodes full ASCII character set with mandatory checksum.
```python
from reportlab.pdfgen import canvas
from reportlab.graphics.barcode import code128
from reportlab.lib.units import inch
c = canvas.Canvas("barcode.pdf")
# Create barcode
barcode = code128.Code128("HELLO123")
# Draw on canvas
barcode.drawOn(c, 1*inch, 5*inch)
c.save()
```
### Code128 Options
```python
barcode = code128.Code128(
value="ABC123", # Required: data to encode
barWidth=0.01*inch, # Width of narrowest bar
barHeight=0.5*inch, # Height of bars
quiet=1, # Add quiet zones (margins)
lquiet=None, # Left quiet zone width
rquiet=None, # Right quiet zone width
stop=1, # Show stop symbol
)
# Draw with specific size
barcode.drawOn(canvas, x, y)
# Get dimensions
width = barcode.width
height = barcode.height
```
### Code39 (Standard39)
Supports: 0-9, A-Z (uppercase), space, and special chars (-.$/+%*).
```python
from reportlab.graphics.barcode import code39
barcode = code39.Standard39(
value="HELLO",
barWidth=0.01*inch,
barHeight=0.5*inch,
quiet=1,
checksum=0, # 0 or 1
)
barcode.drawOn(canvas, x, y)
```
### Extended Code39
Encodes full ASCII (pairs of Code39 characters).
```python
from reportlab.graphics.barcode import code39
barcode = code39.Extended39(
value="Hello World!", # Can include lowercase and symbols
barWidth=0.01*inch,
barHeight=0.5*inch,
)
barcode.drawOn(canvas, x, y)
```
### Code93
```python
from reportlab.graphics.barcode import code93
# Standard93 - uppercase, digits, some symbols
barcode = code93.Standard93(
value="HELLO93",
barWidth=0.01*inch,
barHeight=0.5*inch,
)
# Extended93 - full ASCII
barcode = code93.Extended93(
value="Hello 93!",
barWidth=0.01*inch,
barHeight=0.5*inch,
)
barcode.drawOn(canvas, x, y)
```
### EAN-13 (European Article Number)
13-digit barcode for retail products.
```python
from reportlab.graphics.barcode import eanbc
# Must be exactly 12 digits (13th is calculated checksum)
barcode = eanbc.Ean13BarcodeWidget(
value="123456789012"
)
# Draw
from reportlab.graphics import renderPDF
from reportlab.graphics.shapes import Drawing
d = Drawing()
d.add(barcode)
renderPDF.draw(d, canvas, x, y)
```
### EAN-8
Short form, 8 digits.
```python
from reportlab.graphics.barcode import eanbc
# Must be exactly 7 digits (8th is calculated)
barcode = eanbc.Ean8BarcodeWidget(
value="1234567"
)
```
### UPC-A
12-digit barcode used in North America.
```python
from reportlab.graphics.barcode import usps
# 11 digits (12th is checksum)
barcode = usps.UPCA(
value="01234567890"
)
barcode.drawOn(canvas, x, y)
```
### ISBN (Books)
```python
from reportlab.graphics.barcode.widgets import ISBNBarcodeWidget
# 10 or 13 digit ISBN
barcode = ISBNBarcodeWidget(
value="978-0-123456-78-9"
)
# With pricing (EAN-5 add-on)
barcode = ISBNBarcodeWidget(
value="978-0-123456-78-9",
price=True,
)
```
### QR Codes
Most versatile 2D barcode - can encode URLs, text, contact info, etc.
```python
from reportlab.graphics.barcode.qr import QrCodeWidget
from reportlab.graphics.shapes import Drawing
from reportlab.graphics import renderPDF
# Create QR code
qr = QrCodeWidget("https://example.com")
# Size in pixels (QR codes are square)
qr.barWidth = 100 # Width in points
qr.barHeight = 100 # Height in points
# Error correction level
# L = 7% recovery, M = 15%, Q = 25%, H = 30%
qr.qrVersion = 1 # Auto-size (1-40, or None for auto)
qr.errorLevel = 'M' # L, M, Q, H
# Draw
d = Drawing()
d.add(qr)
renderPDF.draw(d, canvas, x, y)
```
### QR Code - More Options
```python
# URL QR Code
qr = QrCodeWidget("https://example.com")
# Contact information (vCard)
vcard_data = """BEGIN:VCARD
VERSION:3.0
FN:John Doe
TEL:+1-555-1234
EMAIL:john@example.com
END:VCARD"""
qr = QrCodeWidget(vcard_data)
# WiFi credentials
wifi_data = "WIFI:T:WPA;S:NetworkName;P:Password;;"
qr = QrCodeWidget(wifi_data)
# Plain text
qr = QrCodeWidget("Any text here")
```
### Data Matrix (ECC200)
Compact 2D barcode for small items.
```python
from reportlab.graphics.barcode.datamatrix import DataMatrixWidget
barcode = DataMatrixWidget(
value="DATA123"
)
d = Drawing()
d.add(barcode)
renderPDF.draw(d, canvas, x, y)
```
### Postal Barcodes
```python
from reportlab.graphics.barcode import usps
# POSTNET (older format)
barcode = usps.POSTNET(
value="55555-1234", # ZIP or ZIP+4
)
# USPS 4-State (newer)
barcode = usps.USPS_4State(
value="12345678901234567890", # 20-digit routing code
routing="12345678901"
)
barcode.drawOn(canvas, x, y)
```
### FIM (Facing Identification Mark)
Used for mail sorting.
```python
from reportlab.graphics.barcode import usps
# FIM-A, FIM-B, FIM-C, or FIM-D
barcode = usps.FIM(
value="A" # A, B, C, or D
)
barcode.drawOn(canvas, x, y)
```
## Using Barcodes with Platypus
For flowing documents, wrap barcodes in Flowables.
### Simple Approach - Drawing Flowable
```python
from reportlab.graphics.shapes import Drawing
from reportlab.graphics.barcode.qr import QrCodeWidget
from reportlab.lib.units import inch
# Create drawing
d = Drawing(2*inch, 2*inch)
# Create barcode
qr = QrCodeWidget("https://example.com")
qr.barWidth = 2*inch
qr.barHeight = 2*inch
qr.x = 0
qr.y = 0
d.add(qr)
# Add to story
story.append(d)
```
### Custom Flowable Wrapper
```python
from reportlab.platypus import Flowable
from reportlab.graphics.barcode import code128
from reportlab.lib.units import inch
class BarcodeFlowable(Flowable):
def __init__(self, code, barcode_type='code128', width=2*inch, height=0.5*inch):
Flowable.__init__(self)
self.code = code
self.barcode_type = barcode_type
self.width_val = width
self.height_val = height
# Create barcode
if barcode_type == 'code128':
self.barcode = code128.Code128(code, barWidth=width/100, barHeight=height)
# Add other types as needed
def draw(self):
self.barcode.drawOn(self.canv, 0, 0)
def wrap(self, availWidth, availHeight):
return (self.barcode.width, self.barcode.height)
# Use in story
story.append(BarcodeFlowable("PRODUCT123"))
```
## Complete Examples
### Product Label with Barcode
```python
from reportlab.pdfgen import canvas
from reportlab.graphics.barcode import code128
from reportlab.lib.pagesizes import letter
from reportlab.lib.units import inch
def create_product_label(filename, product_code, product_name):
c = canvas.Canvas(filename, pagesize=(4*inch, 2*inch))
# Product name
c.setFont("Helvetica-Bold", 14)
c.drawCentredString(2*inch, 1.5*inch, product_name)
# Barcode
barcode = code128.Code128(product_code)
barcode_width = barcode.width
barcode_height = barcode.height
# Center barcode
x = (4*inch - barcode_width) / 2
y = 0.5*inch
barcode.drawOn(c, x, y)
# Code text
c.setFont("Courier", 10)
c.drawCentredString(2*inch, 0.3*inch, product_code)
c.save()
create_product_label("label.pdf", "ABC123456789", "Premium Widget")
```
### QR Code Contact Card
```python
from reportlab.pdfgen import canvas
from reportlab.graphics.barcode.qr import QrCodeWidget
from reportlab.graphics.shapes import Drawing
from reportlab.graphics import renderPDF
from reportlab.lib.units import inch
def create_contact_card(filename, name, phone, email):
c = canvas.Canvas(filename, pagesize=(3.5*inch, 2*inch))
# Contact info
c.setFont("Helvetica-Bold", 12)
c.drawString(0.5*inch, 1.5*inch, name)
c.setFont("Helvetica", 10)
c.drawString(0.5*inch, 1.3*inch, phone)
c.drawString(0.5*inch, 1.1*inch, email)
# Create vCard data
vcard = f"""BEGIN:VCARD
VERSION:3.0
FN:{name}
TEL:{phone}
EMAIL:{email}
END:VCARD"""
# QR code
qr = QrCodeWidget(vcard)
qr.barWidth = 1.5*inch
qr.barHeight = 1.5*inch
d = Drawing()
d.add(qr)
renderPDF.draw(d, c, 1.8*inch, 0.2*inch)
c.save()
create_contact_card("contact.pdf", "John Doe", "+1-555-1234", "john@example.com")
```
### Shipping Label with Multiple Barcodes
```python
from reportlab.pdfgen import canvas
from reportlab.graphics.barcode import code128
from reportlab.lib.units import inch
def create_shipping_label(filename, tracking_code, zip_code):
c = canvas.Canvas(filename, pagesize=(6*inch, 4*inch))
# Title
c.setFont("Helvetica-Bold", 16)
c.drawString(0.5*inch, 3.5*inch, "SHIPPING LABEL")
# Tracking barcode
c.setFont("Helvetica", 10)
c.drawString(0.5*inch, 2.8*inch, "Tracking Number:")
tracking_barcode = code128.Code128(tracking_code, barHeight=0.5*inch)
tracking_barcode.drawOn(c, 0.5*inch, 2*inch)
c.setFont("Courier", 9)
c.drawString(0.5*inch, 1.8*inch, tracking_code)
# Additional info can be added
c.save()
create_shipping_label("shipping.pdf", "1Z999AA10123456784", "12345")
```
## Barcode Selection Guide
**Choose Code128 when:**
- General purpose encoding
- Need to encode numbers and letters
- Want compact size
- Widely supported
**Choose Code39 when:**
- Older systems require it
- Don't need lowercase letters
- Want maximum compatibility
**Choose QR Code when:**
- Need to encode URLs
- Want mobile device scanning
- Need high data capacity
- Want error correction
**Choose EAN/UPC when:**
- Retail product identification
- Need industry-standard format
- Global distribution
**Choose Data Matrix when:**
- Very limited space
- Small items (PCB, electronics)
- Need 2D compact format
## Best Practices
1. **Test scanning** early with actual barcode scanners/readers
2. **Add quiet zones** (white space) around barcodes - set `quiet=1`
3. **Choose appropriate height** - taller barcodes are easier to scan
4. **Include human-readable text** below barcode for manual entry
5. **Use Code128** as default for general purpose - it's compact and versatile
6. **For URLs, use QR codes** - much easier for mobile users
7. **Check barcode standards** for your industry (retail uses EAN/UPC)
8. **Test print quality** - low DPI can make barcodes unscannable
9. **Validate data** before encoding - wrong check digits cause issues
10. **Consider error correction** for QR codes - use 'M' or 'H' for important data

View File

@@ -0,0 +1,241 @@
# Canvas API Reference
The Canvas API provides low-level, precise control over PDF generation using coordinate-based drawing.
## Coordinate System
- Origin (0, 0) is at the **lower-left corner** (not top-left like web graphics)
- X-axis points right, Y-axis points upward
- Units are in points (72 points = 1 inch)
- Default page size is A4; explicitly specify page size for consistency
## Basic Setup
```python
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import letter, A4
from reportlab.lib.units import inch
# Create canvas
c = canvas.Canvas("output.pdf", pagesize=letter)
# Get page dimensions
width, height = letter
# Draw content
c.drawString(100, 100, "Hello World")
# Finish page and save
c.showPage() # Complete current page
c.save() # Write PDF to disk
```
## Text Drawing
### Basic String Methods
```python
# Basic text placement
c.drawString(x, y, text) # Left-aligned at x, y
c.drawRightString(x, y, text) # Right-aligned at x, y
c.drawCentredString(x, y, text) # Center-aligned at x, y
# Font control
c.setFont(fontname, size) # e.g., "Helvetica", 12
c.setFillColor(color) # Text color
```
### Text Objects (Advanced)
For complex text operations with multiple lines and precise control:
```python
t = c.beginText(x, y)
t.setFont("Times-Roman", 14)
t.textLine("First line")
t.textLine("Second line")
t.setTextOrigin(x, y) # Reset position
c.drawText(t)
```
## Drawing Primitives
### Lines
```python
c.line(x1, y1, x2, y2) # Single line
c.lines([(x1,y1,x2,y2), (x3,y3,x4,y4)]) # Multiple lines
c.grid(xlist, ylist) # Grid from coordinate lists
```
### Shapes
```python
c.rect(x, y, width, height, stroke=1, fill=0)
c.roundRect(x, y, width, height, radius, stroke=1, fill=0)
c.circle(x_ctr, y_ctr, r, stroke=1, fill=0)
c.ellipse(x1, y1, x2, y2, stroke=1, fill=0)
c.wedge(x, y, radius, startAng, extent, stroke=1, fill=0)
```
### Bezier Curves
```python
c.bezier(x1, y1, x2, y2, x3, y3, x4, y4)
```
## Path Objects
For complex shapes, use path objects:
```python
p = c.beginPath()
p.moveTo(x, y) # Move without drawing
p.lineTo(x, y) # Draw line to point
p.curveTo(x1, y1, x2, y2, x3, y3) # Bezier curve
p.arc(x1, y1, x2, y2, startAng, extent)
p.arcTo(x1, y1, x2, y2, startAng, extent)
p.close() # Close path to start point
# Draw the path
c.drawPath(p, stroke=1, fill=0)
```
## Colors
### RGB (Screen Display)
```python
from reportlab.lib.colors import red, blue, Color
c.setFillColorRGB(r, g, b) # r, g, b are 0-1
c.setStrokeColorRGB(r, g, b)
c.setFillColor(red) # Named colors
c.setStrokeColor(blue)
# Custom with alpha transparency
c.setFillColor(Color(0.5, 0, 0, alpha=0.5))
```
### CMYK (Professional Printing)
```python
from reportlab.lib.colors import CMYKColor, PCMYKColor
c.setFillColorCMYK(c, m, y, k) # 0-1 range
c.setStrokeColorCMYK(c, m, y, k)
# Integer percentages (0-100)
c.setFillColor(PCMYKColor(100, 50, 0, 0))
```
## Line Styling
```python
c.setLineWidth(width) # Thickness in points
c.setLineCap(mode) # 0=butt, 1=round, 2=square
c.setLineJoin(mode) # 0=miter, 1=round, 2=bevel
c.setDash(array, phase) # e.g., [3, 3] for dotted line
```
## Coordinate Transformations
**IMPORTANT:** Transformations are incremental and cumulative.
```python
# Translation (move origin)
c.translate(dx, dy)
# Rotation (in degrees, counterclockwise)
c.rotate(theta)
# Scaling
c.scale(xscale, yscale)
# Skewing
c.skew(alpha, beta)
```
### State Management
```python
# Save current graphics state
c.saveState()
# ... apply transformations and draw ...
# Restore previous state
c.restoreState()
```
**Note:** State cannot be preserved across `showPage()` calls.
## Images
```python
from reportlab.lib.utils import ImageReader
# Preferred method (with caching)
c.drawImage(image_source, x, y, width=None, height=None,
mask=None, preserveAspectRatio=False)
# image_source can be:
# - Filename string
# - PIL Image object
# - ImageReader object
# For transparency, specify RGB mask range
c.drawImage("logo.png", 100, 500, mask=[255, 255, 255, 255, 255, 255])
# Inline (inefficient, no caching)
c.drawInlineImage(image_source, x, y, width=None, height=None)
```
## Page Management
```python
# Complete current page
c.showPage()
# Set page size for next page
c.setPageSize(size) # e.g., letter, A4
# Page compression (smaller files, slower generation)
c = canvas.Canvas("output.pdf", pageCompression=1)
```
## Common Patterns
### Margins and Layout
```python
from reportlab.lib.units import inch
from reportlab.lib.pagesizes import letter
width, height = letter
margin = inch
# Draw within margins
content_width = width - 2*margin
content_height = height - 2*margin
# Text at top margin
c.drawString(margin, height - margin, "Header")
# Text at bottom margin
c.drawString(margin, margin, "Footer")
```
### Headers and Footers
```python
def draw_header_footer(c, width, height):
c.saveState()
c.setFont("Helvetica", 9)
c.drawString(inch, height - 0.5*inch, "Company Name")
c.drawRightString(width - inch, 0.5*inch, f"Page {c.getPageNumber()}")
c.restoreState()
# Call on each page
draw_header_footer(c, width, height)
c.showPage()
```
## Best Practices
1. **Always specify page size** - Different platforms have different defaults
2. **Use variables for measurements** - `margin = inch` instead of hardcoded values
3. **Match saveState/restoreState** - Always balance these calls
4. **Apply transformations externally** for engineering drawings to prevent line width scaling
5. **Use drawImage over drawInlineImage** for better performance with repeated images
6. **Draw from bottom-up** - Remember Y-axis points upward

View File

@@ -0,0 +1,624 @@
# Charts and Graphics Reference
Comprehensive guide to creating charts and data visualizations in ReportLab.
## Graphics Architecture
ReportLab's graphics system provides platform-independent drawing:
- **Drawings** - Container for shapes and charts
- **Shapes** - Primitives (rectangles, circles, lines, polygons, paths)
- **Renderers** - Convert to PDF, PostScript, SVG, or bitmaps (PNG, GIF, JPG)
- **Coordinate System** - Y-axis points upward (like PDF, unlike web graphics)
## Quick Start
```python
from reportlab.graphics.shapes import Drawing
from reportlab.graphics.charts.barcharts import VerticalBarChart
from reportlab.graphics import renderPDF
# Create drawing (canvas for chart)
drawing = Drawing(400, 200)
# Create chart
chart = VerticalBarChart()
chart.x = 50
chart.y = 50
chart.width = 300
chart.height = 125
chart.data = [[100, 150, 130, 180]]
chart.categoryAxis.categoryNames = ['Q1', 'Q2', 'Q3', 'Q4']
# Add chart to drawing
drawing.add(chart)
# Render to PDF
renderPDF.drawToFile(drawing, 'chart.pdf', 'Chart Title')
# Or add as flowable to Platypus document
story.append(drawing)
```
## Available Chart Types
### Bar Charts
```python
from reportlab.graphics.charts.barcharts import (
VerticalBarChart,
HorizontalBarChart,
)
# Vertical bar chart
chart = VerticalBarChart()
chart.x = 50
chart.y = 50
chart.width = 300
chart.height = 150
# Single series
chart.data = [[100, 150, 130, 180, 140]]
# Multiple series (grouped bars)
chart.data = [
[100, 150, 130, 180], # Series 1
[80, 120, 110, 160], # Series 2
]
# Categories
chart.categoryAxis.categoryNames = ['Q1', 'Q2', 'Q3', 'Q4']
# Colors for each series
chart.bars[0].fillColor = colors.blue
chart.bars[1].fillColor = colors.red
# Bar spacing
chart.barWidth = 10
chart.groupSpacing = 10
chart.barSpacing = 2
```
### Stacked Bar Charts
```python
from reportlab.graphics.charts.barcharts import VerticalBarChart
chart = VerticalBarChart()
# ... set position and size ...
chart.data = [
[100, 150, 130, 180], # Bottom layer
[50, 70, 60, 90], # Top layer
]
chart.categoryAxis.categoryNames = ['Q1', 'Q2', 'Q3', 'Q4']
# Enable stacking
chart.barLabelFormat = 'values'
chart.valueAxis.visible = 1
```
### Horizontal Bar Charts
```python
from reportlab.graphics.charts.barcharts import HorizontalBarChart
chart = HorizontalBarChart()
chart.x = 50
chart.y = 50
chart.width = 300
chart.height = 150
chart.data = [[100, 150, 130, 180]]
chart.categoryAxis.categoryNames = ['Product A', 'Product B', 'Product C', 'Product D']
# Horizontal charts use valueAxis horizontally
chart.valueAxis.valueMin = 0
chart.valueAxis.valueMax = 200
```
### Line Charts
```python
from reportlab.graphics.charts.linecharts import HorizontalLineChart
chart = HorizontalLineChart()
chart.x = 50
chart.y = 50
chart.width = 300
chart.height = 150
# Multiple lines
chart.data = [
[100, 150, 130, 180, 140], # Line 1
[80, 120, 110, 160, 130], # Line 2
]
chart.categoryAxis.categoryNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May']
# Line styling
chart.lines[0].strokeColor = colors.blue
chart.lines[0].strokeWidth = 2
chart.lines[1].strokeColor = colors.red
chart.lines[1].strokeWidth = 2
# Show/hide points
chart.lines[0].symbol = None # No symbols
# Or use symbols from makeMarker()
```
### Line Plots (X-Y Plots)
```python
from reportlab.graphics.charts.lineplots import LinePlot
chart = LinePlot()
chart.x = 50
chart.y = 50
chart.width = 300
chart.height = 150
# Data as (x, y) tuples
chart.data = [
[(0, 0), (1, 1), (2, 4), (3, 9), (4, 16)], # y = x^2
[(0, 0), (1, 2), (2, 4), (3, 6), (4, 8)], # y = 2x
]
# Both axes are value axes (not category)
chart.xValueAxis.valueMin = 0
chart.xValueAxis.valueMax = 5
chart.yValueAxis.valueMin = 0
chart.yValueAxis.valueMax = 20
# Line styling
chart.lines[0].strokeColor = colors.blue
chart.lines[1].strokeColor = colors.red
```
### Pie Charts
```python
from reportlab.graphics.charts.piecharts import Pie
chart = Pie()
chart.x = 100
chart.y = 50
chart.width = 200
chart.height = 200
chart.data = [25, 35, 20, 20]
chart.labels = ['Q1', 'Q2', 'Q3', 'Q4']
# Slice colors
chart.slices[0].fillColor = colors.blue
chart.slices[1].fillColor = colors.red
chart.slices[2].fillColor = colors.green
chart.slices[3].fillColor = colors.yellow
# Pop out a slice
chart.slices[1].popout = 10
# Label positioning
chart.slices.strokeColor = colors.white
chart.slices.strokeWidth = 2
```
### Pie Chart with Side Labels
```python
from reportlab.graphics.charts.piecharts import Pie
chart = Pie()
# ... set position, data, labels ...
# Side label mode (labels in columns beside pie)
chart.sideLabels = 1
chart.sideLabelsOffset = 0.1 # Distance from pie
# Simple labels (not fancy layout)
chart.simpleLabels = 1
```
### Area Charts
```python
from reportlab.graphics.charts.areacharts import HorizontalAreaChart
chart = HorizontalAreaChart()
chart.x = 50
chart.y = 50
chart.width = 300
chart.height = 150
# Areas stack on top of each other
chart.data = [
[100, 150, 130, 180], # Bottom area
[50, 70, 60, 90], # Top area
]
chart.categoryAxis.categoryNames = ['Q1', 'Q2', 'Q3', 'Q4']
# Area colors
chart.strands[0].fillColor = colors.lightblue
chart.strands[1].fillColor = colors.pink
```
### Scatter Charts
```python
from reportlab.graphics.charts.lineplots import ScatterPlot
chart = ScatterPlot()
chart.x = 50
chart.y = 50
chart.width = 300
chart.height = 150
# Data points
chart.data = [
[(1, 2), (2, 3), (3, 5), (4, 4), (5, 6)], # Series 1
[(1, 1), (2, 2), (3, 3), (4, 3), (5, 4)], # Series 2
]
# Hide lines, show points only
chart.lines[0].strokeColor = None
chart.lines[1].strokeColor = None
# Marker symbols
from reportlab.graphics.widgets.markers import makeMarker
chart.lines[0].symbol = makeMarker('Circle')
chart.lines[1].symbol = makeMarker('Square')
```
## Axes Configuration
### Category Axis (XCategoryAxis)
For categorical data (labels, not numbers):
```python
# Access via chart
axis = chart.categoryAxis
# Labels
axis.categoryNames = ['Jan', 'Feb', 'Mar', 'Apr']
# Label angle (for long labels)
axis.labels.angle = 45
axis.labels.dx = 0
axis.labels.dy = -5
# Label formatting
axis.labels.fontSize = 10
axis.labels.fontName = 'Helvetica'
# Visibility
axis.visible = 1
```
### Value Axis (YValueAxis)
For numeric data:
```python
# Access via chart
axis = chart.valueAxis
# Range
axis.valueMin = 0
axis.valueMax = 200
axis.valueStep = 50 # Tick interval
# Or auto-configure
axis.valueSteps = [0, 50, 100, 150, 200] # Explicit steps
# Label formatting
axis.labels.fontSize = 10
axis.labelTextFormat = '%d%%' # Add percentage sign
# Grid lines
axis.strokeWidth = 1
axis.strokeColor = colors.black
```
## Styling and Customization
### Colors
```python
from reportlab.lib import colors
# Named colors
colors.blue, colors.red, colors.green, colors.yellow
# RGB
colors.Color(0.5, 0.5, 0.5) # Grey
# With alpha
colors.Color(1, 0, 0, alpha=0.5) # Semi-transparent red
# Hex colors
colors.HexColor('#FF5733')
```
### Line Styling
```python
# For line charts
chart.lines[0].strokeColor = colors.blue
chart.lines[0].strokeWidth = 2
chart.lines[0].strokeDashArray = [2, 2] # Dashed line
```
### Bar Labels
```python
# Show values on bars
chart.barLabels.nudge = 5 # Offset from bar top
chart.barLabels.fontSize = 8
chart.barLabelFormat = '%d' # Number format
# For negative values
chart.barLabels.dy = -5 # Position below bar
```
## Legends
Charts can have associated legends:
```python
from reportlab.graphics.charts.legends import Legend
# Create legend
legend = Legend()
legend.x = 350
legend.y = 150
legend.columnMaximum = 10
# Link to chart (share colors)
legend.colorNamePairs = [
(chart.bars[0].fillColor, 'Series 1'),
(chart.bars[1].fillColor, 'Series 2'),
]
# Add to drawing
drawing.add(legend)
```
## Drawing Shapes
### Basic Shapes
```python
from reportlab.graphics.shapes import (
Drawing, Rect, Circle, Ellipse, Line, Polygon, String
)
from reportlab.lib import colors
drawing = Drawing(400, 200)
# Rectangle
rect = Rect(50, 50, 100, 50)
rect.fillColor = colors.blue
rect.strokeColor = colors.black
rect.strokeWidth = 1
drawing.add(rect)
# Circle
circle = Circle(200, 100, 30)
circle.fillColor = colors.red
drawing.add(circle)
# Line
line = Line(50, 150, 350, 150)
line.strokeColor = colors.black
line.strokeWidth = 2
drawing.add(line)
# Text
text = String(50, 175, "Label Text")
text.fontSize = 12
text.fontName = 'Helvetica'
drawing.add(text)
```
### Paths (Complex Shapes)
```python
from reportlab.graphics.shapes import Path
path = Path()
path.moveTo(50, 50)
path.lineTo(100, 100)
path.curveTo(120, 120, 140, 100, 150, 50)
path.closePath()
path.fillColor = colors.lightblue
path.strokeColor = colors.blue
path.strokeWidth = 2
drawing.add(path)
```
## Rendering Options
### Render to PDF
```python
from reportlab.graphics import renderPDF
# Direct to file
renderPDF.drawToFile(drawing, 'output.pdf', 'Chart Title')
# As flowable in Platypus
story.append(drawing)
```
### Render to Image
```python
from reportlab.graphics import renderPM
# PNG
renderPM.drawToFile(drawing, 'chart.png', fmt='PNG')
# GIF
renderPM.drawToFile(drawing, 'chart.gif', fmt='GIF')
# JPG
renderPM.drawToFile(drawing, 'chart.jpg', fmt='JPG')
# With specific DPI
renderPM.drawToFile(drawing, 'chart.png', fmt='PNG', dpi=150)
```
### Render to SVG
```python
from reportlab.graphics import renderSVG
renderSVG.drawToFile(drawing, 'chart.svg')
```
## Advanced Customization
### Inspect Properties
```python
# List all properties
print(chart.getProperties())
# Dump properties (for debugging)
chart.dumpProperties()
# Set multiple properties
chart.setProperties({
'width': 400,
'height': 200,
'data': [[100, 150, 130]],
})
```
### Custom Colors for Series
```python
# Define color scheme
from reportlab.lib.colors import PCMYKColor
colors_list = [
PCMYKColor(100, 67, 0, 23), # Blue
PCMYKColor(0, 100, 100, 0), # Red
PCMYKColor(66, 13, 0, 22), # Green
]
# Apply to chart
for i, color in enumerate(colors_list):
chart.bars[i].fillColor = color
```
## Complete Examples
### Sales Report Bar Chart
```python
from reportlab.graphics.shapes import Drawing
from reportlab.graphics.charts.barcharts import VerticalBarChart
from reportlab.graphics.charts.legends import Legend
from reportlab.lib import colors
drawing = Drawing(400, 250)
# Create chart
chart = VerticalBarChart()
chart.x = 50
chart.y = 50
chart.width = 300
chart.height = 150
# Data
chart.data = [
[120, 150, 180, 200], # 2023
[100, 130, 160, 190], # 2022
]
chart.categoryAxis.categoryNames = ['Q1', 'Q2', 'Q3', 'Q4']
# Styling
chart.bars[0].fillColor = colors.HexColor('#3498db')
chart.bars[1].fillColor = colors.HexColor('#e74c3c')
chart.valueAxis.valueMin = 0
chart.valueAxis.valueMax = 250
chart.categoryAxis.labels.fontSize = 10
chart.valueAxis.labels.fontSize = 10
# Add legend
legend = Legend()
legend.x = 325
legend.y = 200
legend.columnMaximum = 2
legend.colorNamePairs = [
(chart.bars[0].fillColor, '2023'),
(chart.bars[1].fillColor, '2022'),
]
drawing.add(chart)
drawing.add(legend)
# Add to story or save
story.append(drawing)
```
### Multi-Line Trend Chart
```python
from reportlab.graphics.shapes import Drawing
from reportlab.graphics.charts.linecharts import HorizontalLineChart
from reportlab.lib import colors
drawing = Drawing(400, 250)
chart = HorizontalLineChart()
chart.x = 50
chart.y = 50
chart.width = 320
chart.height = 170
# Data
chart.data = [
[10, 15, 12, 18, 20, 25], # Product A
[8, 10, 14, 16, 18, 22], # Product B
[12, 11, 13, 15, 17, 19], # Product C
]
chart.categoryAxis.categoryNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun']
# Line styling
chart.lines[0].strokeColor = colors.blue
chart.lines[0].strokeWidth = 2
chart.lines[1].strokeColor = colors.red
chart.lines[1].strokeWidth = 2
chart.lines[2].strokeColor = colors.green
chart.lines[2].strokeWidth = 2
# Axes
chart.valueAxis.valueMin = 0
chart.valueAxis.valueMax = 30
chart.categoryAxis.labels.angle = 0
chart.categoryAxis.labels.fontSize = 9
chart.valueAxis.labels.fontSize = 9
drawing.add(chart)
story.append(drawing)
```
## Best Practices
1. **Set explicit dimensions** for Drawing to ensure consistent sizing
2. **Position charts** with enough margin (x, y at least 30-50 from edge)
3. **Use consistent color schemes** throughout document
4. **Set valueMin and valueMax** explicitly for consistent scales
5. **Test with realistic data** to ensure labels fit and don't overlap
6. **Add legends** for multi-series charts
7. **Angle category labels** if they're long (45° works well)
8. **Keep it simple** - fewer data series are easier to read
9. **Use appropriate chart types** - bars for comparisons, lines for trends, pies for proportions
10. **Consider colorblind-friendly palettes** - avoid red/green combinations

View File

@@ -0,0 +1,561 @@
# PDF Features Reference
Advanced PDF capabilities: links, bookmarks, forms, encryption, and metadata.
## Document Metadata
Set PDF document properties viewable in PDF readers.
```python
from reportlab.pdfgen import canvas
c = canvas.Canvas("output.pdf")
# Set metadata
c.setAuthor("John Doe")
c.setTitle("Annual Report 2024")
c.setSubject("Financial Analysis")
c.setKeywords("finance, annual, report, 2024")
c.setCreator("MyApp v1.0")
# ... draw content ...
c.save()
```
With Platypus:
```python
from reportlab.platypus import SimpleDocTemplate
doc = SimpleDocTemplate(
"output.pdf",
title="Annual Report 2024",
author="John Doe",
subject="Financial Analysis",
)
doc.build(story)
```
## Bookmarks and Destinations
Create internal navigation structure.
### Simple Bookmarks
```python
from reportlab.pdfgen import canvas
c = canvas.Canvas("output.pdf")
# Create bookmark for current page
c.bookmarkPage("intro") # Internal key
c.addOutlineEntry("Introduction", "intro", level=0)
c.showPage()
# Another bookmark
c.bookmarkPage("chapter1")
c.addOutlineEntry("Chapter 1", "chapter1", level=0)
# Sub-sections
c.bookmarkPage("section1_1")
c.addOutlineEntry("Section 1.1", "section1_1", level=1) # Nested
c.save()
```
### Bookmark Levels
```python
# Create hierarchical outline
c.bookmarkPage("ch1")
c.addOutlineEntry("Chapter 1", "ch1", level=0)
c.bookmarkPage("ch1_s1")
c.addOutlineEntry("Section 1.1", "ch1_s1", level=1)
c.bookmarkPage("ch1_s1_1")
c.addOutlineEntry("Subsection 1.1.1", "ch1_s1_1", level=2)
c.bookmarkPage("ch2")
c.addOutlineEntry("Chapter 2", "ch2", level=0)
```
### Destination Fit Modes
Control how the page displays when navigating:
```python
# bookmarkPage with fit mode
c.bookmarkPage(
key="chapter1",
fit="Fit" # Fit entire page in window
)
# Or use bookmarkHorizontalAbsolute
c.bookmarkHorizontalAbsolute(key="section", top=500)
# Available fit modes:
# "Fit" - Fit whole page
# "FitH" - Fit horizontally
# "FitV" - Fit vertically
# "FitR" - Fit rectangle
# "XYZ" - Specific position and zoom
```
## Hyperlinks
### External Links
```python
from reportlab.pdfgen import canvas
from reportlab.lib.units import inch
c = canvas.Canvas("output.pdf")
# Draw link rectangle
c.linkURL(
"https://www.example.com",
rect=(1*inch, 5*inch, 3*inch, 5.5*inch), # (x1, y1, x2, y2)
relative=0, # 0 for absolute positioning
thickness=1,
color=(0, 0, 1), # Blue
dashArray=None
)
# Draw text over link area
c.setFillColorRGB(0, 0, 1) # Blue text
c.drawString(1*inch, 5.2*inch, "Click here to visit example.com")
c.save()
```
### Internal Links
Link to bookmarked locations within the document:
```python
# Create destination
c.bookmarkPage("target_section")
# Later, create link to that destination
c.linkRect(
"Link Text",
"target_section", # Bookmark key
rect=(1*inch, 3*inch, 2*inch, 3.2*inch),
relative=0
)
```
### Links in Paragraphs
For Platypus documents:
```python
from reportlab.platypus import Paragraph
# External link
text = '<link href="https://example.com" color="blue">Visit our website</link>'
para = Paragraph(text, style)
# Internal link (to anchor)
text = '<link href="#section1" color="blue">Go to Section 1</link>'
para1 = Paragraph(text, style)
# Create anchor
text = '<a name="section1"/>Section 1 Heading'
para2 = Paragraph(text, heading_style)
story.append(para1)
story.append(para2)
```
## Interactive Forms
Create fillable PDF forms.
### Text Fields
```python
from reportlab.pdfgen import canvas
from reportlab.pdfbase import pdfform
from reportlab.lib.colors import black, white
c = canvas.Canvas("form.pdf")
# Create text field
c.acroForm.textfield(
name="name",
tooltip="Enter your name",
x=100,
y=700,
width=200,
height=20,
borderColor=black,
fillColor=white,
textColor=black,
forceBorder=True,
fontSize=12,
maxlen=100, # Maximum character length
)
# Label
c.drawString(100, 725, "Name:")
c.save()
```
### Checkboxes
```python
# Create checkbox
c.acroForm.checkbox(
name="agree",
tooltip="I agree to terms",
x=100,
y=650,
size=20,
buttonStyle='check', # 'check', 'circle', 'cross', 'diamond', 'square', 'star'
borderColor=black,
fillColor=white,
textColor=black,
forceBorder=True,
checked=False, # Initial state
)
c.drawString(130, 655, "I agree to the terms and conditions")
```
### Radio Buttons
```python
# Radio button group - only one can be selected
c.acroForm.radio(
name="payment", # Same name for group
tooltip="Credit Card",
value="credit", # Value when selected
x=100,
y=600,
size=15,
selected=False,
)
c.drawString(125, 603, "Credit Card")
c.acroForm.radio(
name="payment", # Same name
tooltip="PayPal",
value="paypal",
x=100,
y=580,
size=15,
selected=False,
)
c.drawString(125, 583, "PayPal")
```
### List Boxes
```python
# Listbox with multiple options
c.acroForm.listbox(
name="country",
tooltip="Select your country",
value="US", # Default selected
x=100,
y=500,
width=150,
height=80,
borderColor=black,
fillColor=white,
textColor=black,
forceBorder=True,
options=[
("United States", "US"),
("Canada", "CA"),
("Mexico", "MX"),
("Other", "OTHER"),
], # List of (label, value) tuples
multiple=False, # Allow multiple selections
)
```
### Choice (Dropdown)
```python
# Dropdown menu
c.acroForm.choice(
name="state",
tooltip="Select state",
value="CA",
x=100,
y=450,
width=150,
height=20,
borderColor=black,
fillColor=white,
textColor=black,
forceBorder=True,
options=[
("California", "CA"),
("New York", "NY"),
("Texas", "TX"),
],
)
```
### Complete Form Example
```python
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import letter
from reportlab.lib.colors import black, white, lightgrey
from reportlab.lib.units import inch
def create_registration_form(filename):
c = canvas.Canvas(filename, pagesize=letter)
c.setFont("Helvetica-Bold", 16)
c.drawString(inch, 10*inch, "Registration Form")
y = 9*inch
c.setFont("Helvetica", 12)
# Name field
c.drawString(inch, y, "Full Name:")
c.acroForm.textfield(
name="fullname",
x=2*inch, y=y-5, width=4*inch, height=20,
borderColor=black, fillColor=lightgrey, forceBorder=True
)
# Email field
y -= 0.5*inch
c.drawString(inch, y, "Email:")
c.acroForm.textfield(
name="email",
x=2*inch, y=y-5, width=4*inch, height=20,
borderColor=black, fillColor=lightgrey, forceBorder=True
)
# Age dropdown
y -= 0.5*inch
c.drawString(inch, y, "Age Group:")
c.acroForm.choice(
name="age_group",
x=2*inch, y=y-5, width=2*inch, height=20,
borderColor=black, fillColor=lightgrey, forceBorder=True,
options=[("18-25", "18-25"), ("26-35", "26-35"),
("36-50", "36-50"), ("51+", "51+")]
)
# Newsletter checkbox
y -= 0.5*inch
c.acroForm.checkbox(
name="newsletter",
x=inch, y=y-5, size=15,
buttonStyle='check', borderColor=black, forceBorder=True
)
c.drawString(inch + 25, y, "Subscribe to newsletter")
c.save()
create_registration_form("registration.pdf")
```
## Encryption and Security
Protect PDFs with passwords and permissions.
### Basic Encryption
```python
from reportlab.pdfgen import canvas
c = canvas.Canvas("secure.pdf")
# Encrypt with user password
c.encrypt(
userPassword="user123", # Password to open
ownerPassword="owner456", # Password to change permissions
canPrint=1, # Allow printing
canModify=0, # Disallow modifications
canCopy=1, # Allow text copying
canAnnotate=0, # Disallow annotations
strength=128, # 40 or 128 bit encryption
)
# ... draw content ...
c.save()
```
### Permission Settings
```python
c.encrypt(
userPassword="user123",
ownerPassword="owner456",
canPrint=1, # 1 = allow, 0 = deny
canModify=0, # Prevent content modification
canCopy=1, # Allow text/graphics copying
canAnnotate=0, # Prevent comments/annotations
strength=128, # Use 128-bit encryption
)
```
### Advanced Encryption
```python
from reportlab.lib.pdfencrypt import StandardEncryption
# Create encryption object
encrypt = StandardEncryption(
userPassword="user123",
ownerPassword="owner456",
canPrint=1,
canModify=0,
canCopy=1,
canAnnotate=1,
strength=128,
)
# Use with canvas
c = canvas.Canvas("secure.pdf")
c._doc.encrypt = encrypt
# ... draw content ...
c.save()
```
### Platypus with Encryption
```python
from reportlab.platypus import SimpleDocTemplate
doc = SimpleDocTemplate("secure.pdf")
# Set encryption
doc.encrypt = True
doc.canPrint = 1
doc.canModify = 0
# Or use encrypt() method
doc.encrypt = encrypt_object
doc.build(story)
```
## Page Transitions
Add visual effects for presentations.
```python
from reportlab.pdfgen import canvas
c = canvas.Canvas("presentation.pdf")
# Set transition for current page
c.setPageTransition(
effectname="Wipe", # Transition effect
duration=1, # Duration in seconds
direction=0 # Direction (effect-specific)
)
# Available effects:
# "Split", "Blinds", "Box", "Wipe", "Dissolve",
# "Glitter", "R" (Replace), "Fly", "Push", "Cover",
# "Uncover", "Fade"
# Direction values (effect-dependent):
# 0, 90, 180, 270 for most directional effects
# Example: Slide with fade transition
c.setFont("Helvetica-Bold", 24)
c.drawString(100, 400, "Slide 1")
c.setPageTransition("Fade", 0.5)
c.showPage()
c.drawString(100, 400, "Slide 2")
c.setPageTransition("Wipe", 1, 90)
c.showPage()
c.save()
```
## PDF/A Compliance
Create archival-quality PDFs.
```python
from reportlab.pdfgen import canvas
c = canvas.Canvas("pdfa.pdf")
# Enable PDF/A-1b compliance
c.setPageCompression(0) # PDF/A requires uncompressed
# Note: Full PDF/A requires additional XMP metadata
# This is simplified - full compliance needs more setup
# ... draw content ...
c.save()
```
## Compression
Control file size vs generation speed.
```python
# Enable page compression
c = canvas.Canvas("output.pdf", pageCompression=1)
# Compression reduces file size but slows generation
# 0 = no compression (faster, larger files)
# 1 = compression (slower, smaller files)
```
## Forms and XObjects
Reusable graphics elements.
```python
from reportlab.pdfgen import canvas
c = canvas.Canvas("output.pdf")
# Begin form (reusable object)
c.beginForm("logo")
c.setFillColorRGB(0, 0, 1)
c.rect(0, 0, 100, 50, fill=1)
c.setFillColorRGB(1, 1, 1)
c.drawString(10, 20, "LOGO")
c.endForm()
# Use form multiple times
c.doForm("logo") # At current position
c.translate(200, 0)
c.doForm("logo") # At translated position
c.translate(200, 0)
c.doForm("logo")
c.save()
# Benefits: Smaller file size, faster rendering
```
## Best Practices
1. **Always set metadata** for professional documents
2. **Use bookmarks** for documents > 10 pages
3. **Make links visually distinct** (blue, underlined)
4. **Test forms** in multiple PDF readers (behavior varies)
5. **Use strong encryption (128-bit)** for sensitive data
6. **Set both user and owner passwords** for full security
7. **Enable printing** unless specifically restricted
8. **Test page transitions** - some readers don't support all effects
9. **Use meaningful bookmark titles** for navigation
10. **Consider PDF/A** for long-term archival needs
11. **Validate form field names** - must be unique and valid identifiers
12. **Add tooltips** to form fields for better UX

View File

@@ -0,0 +1,343 @@
# Platypus Guide - High-Level Page Layout
Platypus ("Page Layout and Typography Using Scripts") provides high-level document layout for complex, flowing documents with minimal code.
## Architecture Overview
Platypus uses a layered design:
1. **DocTemplates** - Document container with page formatting rules
2. **PageTemplates** - Specifications for different page layouts
3. **Frames** - Regions where content flows
4. **Flowables** - Content elements (paragraphs, tables, images, spacers)
5. **Canvas** - Underlying rendering engine (usually hidden)
## Quick Start
```python
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
# Create document
doc = SimpleDocTemplate("output.pdf", pagesize=letter,
rightMargin=72, leftMargin=72,
topMargin=72, bottomMargin=18)
# Create story (list of flowables)
story = []
styles = getSampleStyleSheet()
# Add content
story.append(Paragraph("Title", styles['Title']))
story.append(Spacer(1, 0.2*inch))
story.append(Paragraph("Body text here", styles['BodyText']))
story.append(PageBreak())
# Build PDF
doc.build(story)
```
## Core Components
### DocTemplates
#### SimpleDocTemplate
Most common template for standard documents:
```python
doc = SimpleDocTemplate(
filename,
pagesize=letter,
rightMargin=72, # 1 inch = 72 points
leftMargin=72,
topMargin=72,
bottomMargin=18,
title=None, # PDF metadata
author=None,
subject=None
)
```
#### BaseDocTemplate (Advanced)
For complex documents with multiple page layouts:
```python
from reportlab.platypus import BaseDocTemplate, PageTemplate, Frame
from reportlab.lib.pagesizes import letter
doc = BaseDocTemplate("output.pdf", pagesize=letter)
# Define frames (content regions)
frame1 = Frame(doc.leftMargin, doc.bottomMargin,
doc.width/2-6, doc.height, id='col1')
frame2 = Frame(doc.leftMargin+doc.width/2+6, doc.bottomMargin,
doc.width/2-6, doc.height, id='col2')
# Create page template
template = PageTemplate(id='TwoCol', frames=[frame1, frame2])
doc.addPageTemplates([template])
# Build with story
doc.build(story)
```
### Frames
Frames define regions where content flows:
```python
from reportlab.platypus import Frame
frame = Frame(
x1, y1, # Lower-left corner
width, height, # Dimensions
leftPadding=6, # Internal padding
bottomPadding=6,
rightPadding=6,
topPadding=6,
id=None, # Optional identifier
showBoundary=0 # 1 to show frame border (debugging)
)
```
### PageTemplates
Define page layouts with frames and optional functions:
```python
def header_footer(canvas, doc):
"""Called on each page for headers/footers"""
canvas.saveState()
canvas.setFont('Helvetica', 9)
canvas.drawString(inch, 0.75*inch, f"Page {doc.page}")
canvas.restoreState()
template = PageTemplate(
id='Normal',
frames=[frame],
onPage=header_footer, # Function called for each page
onPageEnd=None,
pagesize=letter
)
```
## Flowables
Flowables are content elements that flow through frames.
### Common Flowables
```python
from reportlab.platypus import (
Paragraph, Spacer, PageBreak, FrameBreak,
Image, Table, KeepTogether, CondPageBreak
)
# Spacer - vertical whitespace
Spacer(width, height)
# Page break - force new page
PageBreak()
# Frame break - move to next frame
FrameBreak()
# Conditional page break - break if less than N space remaining
CondPageBreak(height)
# Keep together - prevent splitting across pages
KeepTogether([flowable1, flowable2, ...])
```
### Paragraph Flowable
See `text_and_fonts.md` for detailed Paragraph usage.
```python
from reportlab.platypus import Paragraph
from reportlab.lib.styles import ParagraphStyle
style = ParagraphStyle(
'CustomStyle',
fontSize=12,
leading=14,
alignment=0 # 0=left, 1=center, 2=right, 4=justify
)
para = Paragraph("Text with <b>bold</b> and <i>italic</i>", style)
story.append(para)
```
### Image Flowable
```python
from reportlab.platypus import Image
# Auto-size to fit
img = Image('photo.jpg')
# Fixed size
img = Image('photo.jpg', width=2*inch, height=2*inch)
# Maintain aspect ratio with max width
img = Image('photo.jpg', width=4*inch, height=3*inch,
kind='proportional')
story.append(img)
```
### Table Flowable
See `tables_reference.md` for detailed Table usage.
```python
from reportlab.platypus import Table
data = [['Header1', 'Header2'],
['Row1Col1', 'Row1Col2'],
['Row2Col1', 'Row2Col2']]
table = Table(data, colWidths=[2*inch, 2*inch])
story.append(table)
```
## Page Layouts
### Single Column Document
```python
doc = SimpleDocTemplate("output.pdf", pagesize=letter)
story = []
# Add flowables...
doc.build(story)
```
### Two-Column Layout
```python
from reportlab.platypus import BaseDocTemplate, PageTemplate, Frame
doc = BaseDocTemplate("output.pdf", pagesize=letter)
width, height = letter
margin = inch
# Two side-by-side frames
frame1 = Frame(margin, margin, width/2 - 1.5*margin, height - 2*margin, id='col1')
frame2 = Frame(width/2 + 0.5*margin, margin, width/2 - 1.5*margin, height - 2*margin, id='col2')
template = PageTemplate(id='TwoCol', frames=[frame1, frame2])
doc.addPageTemplates([template])
story = []
# Content flows left column first, then right column
# Add flowables...
doc.build(story)
```
### Multiple Page Templates
```python
from reportlab.platypus import NextPageTemplate
# Define templates
cover_template = PageTemplate(id='Cover', frames=[cover_frame])
body_template = PageTemplate(id='Body', frames=[body_frame])
doc.addPageTemplates([cover_template, body_template])
story = []
# Cover page content
story.append(Paragraph("Cover", title_style))
story.append(NextPageTemplate('Body')) # Switch to body template
story.append(PageBreak())
# Body content
story.append(Paragraph("Chapter 1", heading_style))
# ...
doc.build(story)
```
## Headers and Footers
Headers and footers are added via `onPage` callback functions:
```python
def header_footer(canvas, doc):
"""Draw header and footer on each page"""
canvas.saveState()
# Header
canvas.setFont('Helvetica-Bold', 12)
canvas.drawCentredString(letter[0]/2, letter[1] - 0.5*inch,
"Document Title")
# Footer
canvas.setFont('Helvetica', 9)
canvas.drawString(inch, 0.75*inch, "Left Footer")
canvas.drawRightString(letter[0] - inch, 0.75*inch,
f"Page {doc.page}")
canvas.restoreState()
# Apply to template
template = PageTemplate(id='Normal', frames=[frame], onPage=header_footer)
```
## Table of Contents
```python
from reportlab.platypus import TableOfContents
from reportlab.lib.styles import ParagraphStyle
# Create TOC
toc = TableOfContents()
toc.levelStyles = [
ParagraphStyle(name='TOC1', fontSize=14, leftIndent=0),
ParagraphStyle(name='TOC2', fontSize=12, leftIndent=20),
]
story = []
story.append(toc)
story.append(PageBreak())
# Add entries
story.append(Paragraph("Chapter 1<a name='ch1'/>", heading_style))
toc.addEntry(0, "Chapter 1", doc.page, 'ch1')
# Must call build twice for TOC to populate
doc.build(story)
```
## Document Properties
```python
from reportlab.lib.pagesizes import letter, A4
from reportlab.lib.units import inch, cm, mm
# Page sizes
letter # US Letter (8.5" x 11")
A4 # ISO A4 (210mm x 297mm)
landscape(letter) # Rotate to landscape
# Units
inch # 72 points
cm # 28.35 points
mm # 2.835 points
# Custom page size
custom_size = (6*inch, 9*inch)
```
## Best Practices
1. **Use SimpleDocTemplate** for most documents - it handles common layouts
2. **Build story list** completely before calling `doc.build(story)`
3. **Use Spacer** for vertical spacing instead of empty Paragraphs
4. **Group related content** with KeepTogether to prevent awkward page breaks
5. **Test page breaks** early with realistic content amounts
6. **Use styles consistently** - create style once, reuse throughout document
7. **Set showBoundary=1** on Frames during development to visualize layout
8. **Headers/footers go in onPage** callback, not in story
9. **For long documents**, use BaseDocTemplate with multiple page templates
10. **Build TOC documents twice** to properly populate table of contents

View File

@@ -0,0 +1,442 @@
# Tables Reference
Comprehensive guide to creating and styling tables in ReportLab.
## Basic Table Creation
```python
from reportlab.platypus import Table, TableStyle
from reportlab.lib import colors
# Simple data (list of lists or tuples)
data = [
['Header 1', 'Header 2', 'Header 3'],
['Row 1, Col 1', 'Row 1, Col 2', 'Row 1, Col 3'],
['Row 2, Col 1', 'Row 2, Col 2', 'Row 2, Col 3'],
]
# Create table
table = Table(data)
# Add to story
story.append(table)
```
## Table Constructor
```python
table = Table(
data, # Required: list of lists/tuples
colWidths=None, # List of column widths or single value
rowHeights=None, # List of row heights or single value
style=None, # TableStyle object
splitByRow=1, # Split across pages by rows (not columns)
repeatRows=0, # Number of header rows to repeat
repeatCols=0, # Number of header columns to repeat
rowSplitRange=None, # Tuple (start, end) of splittable rows
spaceBefore=None, # Space before table
spaceAfter=None, # Space after table
cornerRadii=None, # [TL, TR, BL, BR] for rounded corners
)
```
### Column Widths
```python
from reportlab.lib.units import inch
# Equal widths
table = Table(data, colWidths=2*inch)
# Different widths per column
table = Table(data, colWidths=[1.5*inch, 2*inch, 1*inch])
# Auto-calculate widths (default)
table = Table(data)
# Percentage-based (of available width)
table = Table(data, colWidths=[None, None, None]) # Equal auto-sizing
```
## Cell Content Types
### Text and Newlines
```python
# Newlines work in cells
data = [
['Line 1\nLine 2', 'Single line'],
['Another\nmulti-line\ncell', 'Text'],
]
```
### Paragraph Objects
```python
from reportlab.platypus import Paragraph
from reportlab.lib.styles import getSampleStyleSheet
styles = getSampleStyleSheet()
data = [
[Paragraph("Formatted <b>bold</b> text", styles['Normal']),
Paragraph("More <i>italic</i> text", styles['Normal'])],
]
table = Table(data)
```
### Images
```python
from reportlab.platypus import Image
data = [
['Description', Image('logo.png', width=1*inch, height=1*inch)],
['Product', Image('product.jpg', width=2*inch, height=1.5*inch)],
]
table = Table(data)
```
### Nested Tables
```python
# Create inner table
inner_data = [['A', 'B'], ['C', 'D']]
inner_table = Table(inner_data)
# Use in outer table
outer_data = [
['Label', inner_table],
['Other', 'Content'],
]
outer_table = Table(outer_data)
```
## TableStyle
Styles are applied using command lists:
```python
from reportlab.platypus import TableStyle
from reportlab.lib import colors
style = TableStyle([
# Command format: ('COMMAND', (startcol, startrow), (endcol, endrow), *args)
('GRID', (0, 0), (-1, -1), 1, colors.black), # Grid over all cells
('BACKGROUND', (0, 0), (-1, 0), colors.grey), # Header background
('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke), # Header text color
])
table = Table(data)
table.setStyle(style)
```
### Cell Coordinate System
- Columns and rows are 0-indexed: `(col, row)`
- Negative indices count from end: `-1` is last column/row
- `(0, 0)` is top-left cell
- `(-1, -1)` is bottom-right cell
```python
# Examples:
(0, 0), (2, 0) # First three cells of header row
(0, 1), (-1, -1) # All cells except header
(0, 0), (-1, -1) # Entire table
```
## Styling Commands
### Text Formatting
```python
style = TableStyle([
# Font name
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
# Font size
('FONTSIZE', (0, 0), (-1, -1), 10),
# Text color
('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
('TEXTCOLOR', (0, 1), (-1, -1), colors.black),
# Combined font command
('FONT', (0, 0), (-1, 0), 'Helvetica-Bold', 12), # name, size
])
```
### Alignment
```python
style = TableStyle([
# Horizontal alignment: LEFT, CENTER, RIGHT, DECIMAL
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
('ALIGN', (0, 1), (0, -1), 'LEFT'), # First column left
('ALIGN', (1, 1), (-1, -1), 'RIGHT'), # Other columns right
# Vertical alignment: TOP, MIDDLE, BOTTOM
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
('VALIGN', (0, 0), (-1, 0), 'BOTTOM'), # Header bottom-aligned
])
```
### Cell Padding
```python
style = TableStyle([
# Individual padding
('LEFTPADDING', (0, 0), (-1, -1), 12),
('RIGHTPADDING', (0, 0), (-1, -1), 12),
('TOPPADDING', (0, 0), (-1, -1), 6),
('BOTTOMPADDING', (0, 0), (-1, -1), 6),
# Or set all at once by setting each
])
```
### Background Colors
```python
style = TableStyle([
# Solid background
('BACKGROUND', (0, 0), (-1, 0), colors.blue),
('BACKGROUND', (0, 1), (-1, -1), colors.lightgrey),
# Alternating row colors
('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, colors.lightblue]),
# Alternating column colors
('COLBACKGROUNDS', (0, 0), (-1, -1), [colors.white, colors.lightgrey]),
])
```
### Gradient Backgrounds
```python
from reportlab.lib.colors import Color
style = TableStyle([
# Vertical gradient (top to bottom)
('BACKGROUND', (0, 0), (-1, 0), colors.blue),
('VERTICALGRADIENT', (0, 0), (-1, 0),
[colors.blue, colors.lightblue]),
# Horizontal gradient (left to right)
('HORIZONTALGRADIENT', (0, 1), (-1, 1),
[colors.red, colors.yellow]),
])
```
### Lines and Borders
```python
style = TableStyle([
# Complete grid
('GRID', (0, 0), (-1, -1), 1, colors.black),
# Box/outline only
('BOX', (0, 0), (-1, -1), 2, colors.black),
('OUTLINE', (0, 0), (-1, -1), 2, colors.black), # Same as BOX
# Inner grid only
('INNERGRID', (0, 0), (-1, -1), 0.5, colors.grey),
# Directional lines
('LINEABOVE', (0, 0), (-1, 0), 2, colors.black), # Header border
('LINEBELOW', (0, 0), (-1, 0), 1, colors.black), # Header bottom
('LINEBEFORE', (0, 0), (0, -1), 1, colors.black), # Left border
('LINEAFTER', (-1, 0), (-1, -1), 1, colors.black), # Right border
# Thickness and color
('LINEABOVE', (0, 1), (-1, 1), 0.5, colors.grey), # Thin grey line
])
```
### Cell Spanning
```python
data = [
['Spanning Header', '', ''], # Span will merge these
['A', 'B', 'C'],
['D', 'E', 'F'],
]
style = TableStyle([
# Span 3 columns in first row
('SPAN', (0, 0), (2, 0)),
# Center the spanning cell
('ALIGN', (0, 0), (2, 0), 'CENTER'),
])
table = Table(data)
table.setStyle(style)
```
**Important:** Cells that are spanned over must contain empty strings `''`.
### Advanced Spanning Examples
```python
# Span multiple rows and columns
data = [
['A', 'B', 'B', 'C'],
['A', 'D', 'E', 'F'],
['A', 'G', 'H', 'I'],
]
style = TableStyle([
# Span rows in column 0
('SPAN', (0, 0), (0, 2)), # Merge A cells vertically
# Span columns in row 0
('SPAN', (1, 0), (2, 0)), # Merge B cells horizontally
('GRID', (0, 0), (-1, -1), 1, colors.black),
])
```
## Special Commands
### Rounded Corners
```python
table = Table(data, cornerRadii=[5, 5, 5, 5]) # [TL, TR, BL, BR]
# Or in style
style = TableStyle([
('ROUNDEDCORNERS', [10, 10, 0, 0]), # Rounded top corners only
])
```
### No Split
Prevent table from splitting at specific locations:
```python
style = TableStyle([
# Don't split between rows 0 and 2
('NOSPLIT', (0, 0), (-1, 2)),
])
```
### Split-Specific Styling
Apply styles only to first or last part when table splits:
```python
style = TableStyle([
# Style for first part after split
('LINEBELOW', (0, 'splitfirst'), (-1, 'splitfirst'), 2, colors.red),
# Style for last part after split
('LINEABOVE', (0, 'splitlast'), (-1, 'splitlast'), 2, colors.blue),
])
```
## Repeating Headers
```python
# Repeat first row on each page
table = Table(data, repeatRows=1)
# Repeat first 2 rows
table = Table(data, repeatRows=2)
```
## Complete Examples
### Styled Report Table
```python
from reportlab.platypus import Table, TableStyle
from reportlab.lib import colors
from reportlab.lib.units import inch
data = [
['Product', 'Quantity', 'Unit Price', 'Total'],
['Widget A', '10', '$5.00', '$50.00'],
['Widget B', '5', '$12.00', '$60.00'],
['Widget C', '20', '$3.00', '$60.00'],
['', '', 'Subtotal:', '$170.00'],
]
table = Table(data, colWidths=[2.5*inch, 1*inch, 1*inch, 1*inch])
style = TableStyle([
# Header row
('BACKGROUND', (0, 0), (-1, 0), colors.darkblue),
('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
('FONTSIZE', (0, 0), (-1, 0), 12),
('ALIGN', (0, 0), (-1, 0), 'CENTER'),
('BOTTOMPADDING', (0, 0), (-1, 0), 12),
# Data rows
('BACKGROUND', (0, 1), (-1, -2), colors.beige),
('GRID', (0, 0), (-1, -2), 0.5, colors.grey),
('ALIGN', (1, 1), (-1, -1), 'RIGHT'),
('ALIGN', (0, 1), (0, -1), 'LEFT'),
# Total row
('BACKGROUND', (0, -1), (-1, -1), colors.lightgrey),
('LINEABOVE', (0, -1), (-1, -1), 2, colors.black),
('FONTNAME', (2, -1), (-1, -1), 'Helvetica-Bold'),
])
table.setStyle(style)
```
### Alternating Row Colors
```python
data = [
['Name', 'Age', 'City'],
['Alice', '30', 'New York'],
['Bob', '25', 'Boston'],
['Charlie', '35', 'Chicago'],
['Diana', '28', 'Denver'],
]
table = Table(data, colWidths=[2*inch, 1*inch, 1.5*inch])
style = TableStyle([
# Header
('BACKGROUND', (0, 0), (-1, 0), colors.darkslategray),
('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
# Alternating rows (zebra striping)
('ROWBACKGROUNDS', (0, 1), (-1, -1),
[colors.white, colors.lightgrey]),
# Borders
('BOX', (0, 0), (-1, -1), 2, colors.black),
('LINEBELOW', (0, 0), (-1, 0), 2, colors.black),
# Padding
('LEFTPADDING', (0, 0), (-1, -1), 12),
('RIGHTPADDING', (0, 0), (-1, -1), 12),
('TOPPADDING', (0, 0), (-1, -1), 6),
('BOTTOMPADDING', (0, 0), (-1, -1), 6),
])
table.setStyle(style)
```
## Best Practices
1. **Set colWidths explicitly** for consistent layout
2. **Use repeatRows** for multi-page tables with headers
3. **Apply padding** for better readability (especially LEFTPADDING and RIGHTPADDING)
4. **Use ROWBACKGROUNDS** for alternating colors instead of styling each row
5. **Put empty strings** in cells that will be spanned
6. **Test page breaks** early with realistic data amounts
7. **Use Paragraph objects** in cells for complex formatted text
8. **Set VALIGN to MIDDLE** for better appearance with varying row heights
9. **Keep tables simple** - complex nested tables are hard to maintain
10. **Use consistent styling** - define once, apply to all tables

View File

@@ -0,0 +1,394 @@
# Text and Fonts Reference
Comprehensive guide to text formatting, paragraph styles, and font handling in ReportLab.
## Text Encoding
**IMPORTANT:** All text input should be UTF-8 encoded or Python Unicode objects (since ReportLab 2.0).
```python
# Correct - UTF-8 strings
text = "Hello 世界 مرحبا"
para = Paragraph(text, style)
# For legacy data, convert first
import codecs
decoded_text = codecs.decode(legacy_bytes, 'latin-1')
```
## Paragraph Styles
### Creating Styles
```python
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_RIGHT, TA_JUSTIFY
from reportlab.lib.colors import black, blue, red
from reportlab.lib.units import inch
# Get default styles
styles = getSampleStyleSheet()
normal = styles['Normal']
heading = styles['Heading1']
# Create custom style
custom_style = ParagraphStyle(
'CustomStyle',
parent=normal, # Inherit from another style
# Font properties
fontName='Helvetica',
fontSize=12,
leading=14, # Line spacing (should be > fontSize)
# Indentation (in points)
leftIndent=0,
rightIndent=0,
firstLineIndent=0, # Positive = indent, negative = outdent
# Spacing
spaceBefore=0,
spaceAfter=0,
# Alignment
alignment=TA_LEFT, # TA_LEFT, TA_CENTER, TA_RIGHT, TA_JUSTIFY
# Colors
textColor=black,
backColor=None, # Background color
# Borders
borderWidth=0,
borderColor=None,
borderPadding=0,
borderRadius=None,
# Bullets
bulletFontName='Helvetica',
bulletFontSize=12,
bulletIndent=0,
bulletText=None, # Text for bullets (e.g., '•')
# Advanced
wordWrap=None, # 'CJK' for Asian languages
allowWidows=1, # Allow widow lines
allowOrphans=0, # Prevent orphan lines
endDots=None, # Trailing dots for TOC entries
splitLongWords=1,
hyphenationLang=None, # 'en_US', etc. (requires pyphen)
)
# Add to stylesheet
styles.add(custom_style)
```
### Built-in Styles
```python
styles = getSampleStyleSheet()
# Common styles
styles['Normal'] # Body text
styles['BodyText'] # Similar to Normal
styles['Heading1'] # Top-level heading
styles['Heading2'] # Second-level heading
styles['Heading3'] # Third-level heading
styles['Title'] # Document title
styles['Bullet'] # Bulleted list items
styles['Definition'] # Definition text
styles['Code'] # Code samples
```
## Paragraph Formatting
### Basic Paragraph
```python
from reportlab.platypus import Paragraph
para = Paragraph("This is a paragraph.", style)
story.append(para)
```
### Inline Formatting Tags
```python
text = """
<b>Bold text</b>
<i>Italic text</i>
<u>Underlined text</u>
<strike>Strikethrough text</strike>
<strong>Strong (bold) text</strong>
"""
para = Paragraph(text, normal_style)
```
### Font Control
```python
text = """
<font face="Courier" size="14" color="blue">
Custom font, size, and color
</font>
<font color="#FF0000">Hex color codes work too</font>
"""
para = Paragraph(text, normal_style)
```
### Superscripts and Subscripts
```python
text = """
H<sub>2</sub>O is water.
E=mc<super>2</super> or E=mc<sup>2</sup>
X<sub><i>i</i></sub> for subscripted variables
"""
para = Paragraph(text, normal_style)
```
### Greek Letters
```python
text = """
<greek>alpha</greek>, <greek>beta</greek>, <greek>gamma</greek>
<greek>epsilon</greek>, <greek>pi</greek>, <greek>omega</greek>
"""
para = Paragraph(text, normal_style)
```
### Links
```python
# External link
text = '<link href="https://example.com" color="blue">Click here</link>'
# Internal link (to bookmark)
text = '<link href="#section1" color="blue">Go to Section 1</link>'
# Anchor for internal links
text = '<a name="section1"/>Section 1 Heading'
para = Paragraph(text, normal_style)
```
### Inline Images
```python
text = """
Here is an inline image: <img src="icon.png" width="12" height="12" valign="middle"/>
"""
para = Paragraph(text, normal_style)
```
### Line Breaks
```python
text = """
First line<br/>
Second line<br/>
Third line
"""
para = Paragraph(text, normal_style)
```
## Font Handling
### Standard Fonts
ReportLab includes 14 standard PDF fonts (no embedding needed):
```python
# Helvetica family
'Helvetica'
'Helvetica-Bold'
'Helvetica-Oblique'
'Helvetica-BoldOblique'
# Times family
'Times-Roman'
'Times-Bold'
'Times-Italic'
'Times-BoldItalic'
# Courier family
'Courier'
'Courier-Bold'
'Courier-Oblique'
'Courier-BoldOblique'
# Symbol and Dingbats
'Symbol'
'ZapfDingbats'
```
### TrueType Fonts
```python
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
# Register single font
pdfmetrics.registerFont(TTFont('CustomFont', 'CustomFont.ttf'))
# Use in Canvas
canvas.setFont('CustomFont', 12)
# Use in Paragraph style
style = ParagraphStyle('Custom', fontName='CustomFont', fontSize=12)
```
### Font Families
Register related fonts as a family for bold/italic support:
```python
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.lib.fonts import addMapping
# Register fonts
pdfmetrics.registerFont(TTFont('Vera', 'Vera.ttf'))
pdfmetrics.registerFont(TTFont('VeraBd', 'VeraBd.ttf'))
pdfmetrics.registerFont(TTFont('VeraIt', 'VeraIt.ttf'))
pdfmetrics.registerFont(TTFont('VeraBI', 'VeraBI.ttf'))
# Map family (normal, bold, italic, bold-italic)
addMapping('Vera', 0, 0, 'Vera') # normal
addMapping('Vera', 1, 0, 'VeraBd') # bold
addMapping('Vera', 0, 1, 'VeraIt') # italic
addMapping('Vera', 1, 1, 'VeraBI') # bold-italic
# Now <b> and <i> tags work with this family
style = ParagraphStyle('VeraStyle', fontName='Vera', fontSize=12)
para = Paragraph("Normal <b>Bold</b> <i>Italic</i> <b><i>Both</i></b>", style)
```
### Font Search Paths
```python
from reportlab.pdfbase.ttfonts import TTFSearchPath
# Add custom font directory
TTFSearchPath.append('/path/to/fonts/')
# Now fonts in this directory can be found by name
pdfmetrics.registerFont(TTFont('MyFont', 'MyFont.ttf'))
```
### Asian Language Support
#### Using Adobe Language Packs (no embedding)
```python
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.cidfonts import UnicodeCIDFont
# Register CID fonts
pdfmetrics.registerFont(UnicodeCIDFont('HeiseiMin-W3')) # Japanese
pdfmetrics.registerFont(UnicodeCIDFont('STSong-Light')) # Chinese (Simplified)
pdfmetrics.registerFont(UnicodeCIDFont('MSung-Light')) # Chinese (Traditional)
pdfmetrics.registerFont(UnicodeCIDFont('HYSMyeongJo-Medium')) # Korean
# Use in styles
style = ParagraphStyle('Japanese', fontName='HeiseiMin-W3', fontSize=12)
para = Paragraph("日本語テキスト", style)
```
#### Using TrueType Fonts with Asian Characters
```python
# Register TrueType font with full Unicode support
pdfmetrics.registerFont(TTFont('SimSun', 'simsun.ttc'))
style = ParagraphStyle('Chinese', fontName='SimSun', fontSize=12, wordWrap='CJK')
para = Paragraph("中文文本", style)
```
Note: Set `wordWrap='CJK'` for proper line breaking in Asian languages.
## Numbering and Sequences
Auto-numbering using `<seq>` tags:
```python
# Simple numbering
text = "<seq id='chapter'/> Introduction" # Outputs: 1 Introduction
text = "<seq id='chapter'/> Methods" # Outputs: 2 Methods
# Reset counter
text = "<seq id='figure' reset='yes'/>"
# Formatting templates
text = "Figure <seq template='%(chapter)s-%(figure+)s' id='figure'/>"
# Outputs: Figure 1-1, Figure 1-2, etc.
# Multi-level numbering
text = "Section <seq template='%(chapter)s.%(section+)s' id='section'/>"
```
## Bullets and Lists
### Using Bullet Style
```python
bullet_style = ParagraphStyle(
'Bullet',
parent=normal_style,
leftIndent=20,
bulletIndent=10,
bulletText='', # Unicode bullet
bulletFontName='Helvetica',
)
story.append(Paragraph("First item", bullet_style))
story.append(Paragraph("Second item", bullet_style))
story.append(Paragraph("Third item", bullet_style))
```
### Custom Bullet Characters
```python
# Different bullet styles
bulletText='' # Filled circle
bulletText='' # Open circle
bulletText='' # Square
bulletText='' # Triangle
bulletText='' # Arrow
bulletText='1.' # Numbers
bulletText='a)' # Letters
```
## Text Measurement
```python
from reportlab.pdfbase.pdfmetrics import stringWidth
# Measure string width
width = stringWidth("Hello World", "Helvetica", 12)
# Check if text fits in available width
max_width = 200
if stringWidth(text, font_name, font_size) > max_width:
# Text is too wide
pass
```
## Best Practices
1. **Always use UTF-8** for text input
2. **Set leading > fontSize** for readability (typically fontSize + 2)
3. **Register font families** for proper bold/italic support
4. **Escape HTML** if displaying user content: use `<` for < and `>` for >
5. **Use getSampleStyleSheet()** as a starting point, don't create all styles from scratch
6. **Test Asian fonts** early if supporting multi-language content
7. **Set wordWrap='CJK'** for Chinese/Japanese/Korean text
8. **Use stringWidth()** to check if text fits before rendering
9. **Define styles once** at document start, reuse throughout
10. **Enable hyphenation** for justified text: `hyphenationLang='en_US'` (requires pyphen package)

View File

@@ -0,0 +1,229 @@
#!/usr/bin/env python3
"""
Quick Document Generator - Helper for creating simple ReportLab documents
This script provides utility functions for quickly creating common document types
without writing boilerplate code.
"""
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, PageBreak,
Table, TableStyle, Image, KeepTogether
)
from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_RIGHT, TA_JUSTIFY
from datetime import datetime
def create_simple_document(filename, title, author="", content_blocks=None, pagesize=letter):
"""
Create a simple document with title and content blocks.
Args:
filename: Output PDF filename
title: Document title
author: Document author (optional)
content_blocks: List of dicts with 'type' and 'content' keys
type can be: 'heading', 'paragraph', 'bullet', 'space'
pagesize: Page size (default: letter)
Example content_blocks:
[
{'type': 'heading', 'content': 'Introduction'},
{'type': 'paragraph', 'content': 'This is a paragraph.'},
{'type': 'bullet', 'content': 'Bullet point item'},
{'type': 'space', 'height': 0.2}, # height in inches
]
"""
if content_blocks is None:
content_blocks = []
# Create document
doc = SimpleDocTemplate(
filename,
pagesize=pagesize,
rightMargin=72,
leftMargin=72,
topMargin=72,
bottomMargin=18,
title=title,
author=author
)
# Get styles
styles = getSampleStyleSheet()
story = []
# Add title
story.append(Paragraph(title, styles['Title']))
story.append(Spacer(1, 0.3*inch))
# Process content blocks
for block in content_blocks:
block_type = block.get('type', 'paragraph')
content = block.get('content', '')
if block_type == 'heading':
story.append(Paragraph(content, styles['Heading1']))
story.append(Spacer(1, 0.1*inch))
elif block_type == 'heading2':
story.append(Paragraph(content, styles['Heading2']))
story.append(Spacer(1, 0.1*inch))
elif block_type == 'paragraph':
story.append(Paragraph(content, styles['BodyText']))
story.append(Spacer(1, 0.1*inch))
elif block_type == 'bullet':
story.append(Paragraph(content, styles['Bullet']))
elif block_type == 'space':
height = block.get('height', 0.2)
story.append(Spacer(1, height*inch))
elif block_type == 'pagebreak':
story.append(PageBreak())
# Build PDF
doc.build(story)
return filename
def create_styled_table(data, col_widths=None, style_name='default'):
"""
Create a styled table with common styling presets.
Args:
data: List of lists containing table data
col_widths: List of column widths (None for auto)
style_name: 'default', 'striped', 'minimal', 'report'
Returns:
Table object ready to add to story
"""
table = Table(data, colWidths=col_widths)
if style_name == 'striped':
style = 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),
('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, colors.lightgrey]),
('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
])
elif style_name == 'minimal':
style = TableStyle([
('ALIGN', (0, 0), (-1, -1), 'LEFT'),
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
('LINEABOVE', (0, 0), (-1, 0), 2, colors.black),
('LINEBELOW', (0, 0), (-1, 0), 1, colors.black),
('LINEBELOW', (0, -1), (-1, -1), 2, colors.black),
])
elif style_name == 'report':
style = TableStyle([
('BACKGROUND', (0, 0), (-1, 0), colors.grey),
('TEXTCOLOR', (0, 0), (-1, 0), colors.black),
('ALIGN', (0, 0), (-1, 0), 'CENTER'),
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
('FONTSIZE', (0, 0), (-1, 0), 11),
('BACKGROUND', (0, 1), (-1, -1), colors.beige),
('GRID', (0, 0), (-1, -1), 1, colors.grey),
('LEFTPADDING', (0, 0), (-1, -1), 12),
('RIGHTPADDING', (0, 0), (-1, -1), 12),
])
else: # default
style = TableStyle([
('BACKGROUND', (0, 0), (-1, 0), colors.grey),
('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.white),
('GRID', (0, 0), (-1, -1), 1, colors.black),
])
table.setStyle(style)
return table
def add_header_footer(canvas, doc, header_text="", footer_text=""):
"""
Callback function to add headers and footers to each page.
Usage:
from functools import partial
callback = partial(add_header_footer, header_text="My Document", footer_text="Confidential")
template = PageTemplate(id='normal', frames=[frame], onPage=callback)
"""
canvas.saveState()
# Header
if header_text:
canvas.setFont('Helvetica', 9)
canvas.drawString(inch, doc.pagesize[1] - 0.5*inch, header_text)
# Footer
if footer_text:
canvas.setFont('Helvetica', 9)
canvas.drawString(inch, 0.5*inch, footer_text)
# Page number
canvas.drawRightString(doc.pagesize[0] - inch, 0.5*inch, f"Page {doc.page}")
canvas.restoreState()
# Example usage
if __name__ == "__main__":
# Example 1: Simple document
content = [
{'type': 'heading', 'content': 'Introduction'},
{'type': 'paragraph', 'content': 'This is a sample paragraph with some text.'},
{'type': 'space', 'height': 0.2},
{'type': 'heading', 'content': 'Main Content'},
{'type': 'paragraph', 'content': 'More content here with <b>bold</b> and <i>italic</i> text.'},
{'type': 'bullet', 'content': 'First bullet point'},
{'type': 'bullet', 'content': 'Second bullet point'},
]
create_simple_document(
"example_document.pdf",
"Sample Document",
author="John Doe",
content_blocks=content
)
print("Created: example_document.pdf")
# Example 2: Document with styled table
doc = SimpleDocTemplate("table_example.pdf", pagesize=letter)
story = []
styles = getSampleStyleSheet()
story.append(Paragraph("Sales Report", styles['Title']))
story.append(Spacer(1, 0.3*inch))
# Create table
data = [
['Product', 'Q1', 'Q2', 'Q3', 'Q4'],
['Widget A', '100', '150', '130', '180'],
['Widget B', '80', '120', '110', '160'],
['Widget C', '90', '110', '100', '140'],
]
table = create_styled_table(data, col_widths=[2*inch, 1*inch, 1*inch, 1*inch, 1*inch], style_name='striped')
story.append(table)
doc.build(story)
print("Created: table_example.pdf")