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

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)