# 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 bold and italic", 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", 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