Initial commit
This commit is contained in:
504
skills/reportlab/references/barcodes_reference.md
Normal file
504
skills/reportlab/references/barcodes_reference.md
Normal 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
|
||||
241
skills/reportlab/references/canvas_api.md
Normal file
241
skills/reportlab/references/canvas_api.md
Normal 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
|
||||
624
skills/reportlab/references/charts_reference.md
Normal file
624
skills/reportlab/references/charts_reference.md
Normal 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
|
||||
561
skills/reportlab/references/pdf_features.md
Normal file
561
skills/reportlab/references/pdf_features.md
Normal 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
|
||||
343
skills/reportlab/references/platypus_guide.md
Normal file
343
skills/reportlab/references/platypus_guide.md
Normal 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
|
||||
442
skills/reportlab/references/tables_reference.md
Normal file
442
skills/reportlab/references/tables_reference.md
Normal 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
|
||||
394
skills/reportlab/references/text_and_fonts.md
Normal file
394
skills/reportlab/references/text_and_fonts.md
Normal 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)
|
||||
Reference in New Issue
Block a user