ReportLab Smart Pagination
Height-aware heading protection for ReportLab PDF generation.
Prevents orphaned headings, text overflow, and cascade failures that ReportLab's built-in keepWithNext cannot handle.
Created by Hamdy Elshamy — developed for Writer's Dream AI, a book writing and publishing platform.
The Problem
ReportLab's keepWithNext=True is a rigid boolean — it forces headings to stay with their content without checking whether they'll actually fit. This causes:
- Text overlapping when carried content exceeds the next page's height
- Overflow cascades when multiple headings chain together
- Silent failures — no errors, just broken PDFs
The Solution
Smart Pagination replaces the rigid keepWithNext approach with height-aware heading protection using two percentage-based safety thresholds:
Built-in keepWithNext |
Smart Pagination | |
|---|---|---|
| Prevents orphaned headings | Yes | Yes |
| Prevents overflow | No | Yes — caps carry at 50% of page |
| Prevents empty pages | No | Yes — aborts if page < 15% filled |
| Handles chained headings | Breaks silently | Gracefully degrades |
| Splits large paragraphs | No | Yes — fills pages optimally |
Installation
pip install reportlab-smart-pagination
Quick Start
from smart_pagination import paginate
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.pagesizes import letter
from reportlab.lib.units import inch
styles = getSampleStyleSheet()
# Build your flowables as usual
flowables = [
Paragraph("Chapter 1: The Beginning", styles['Heading1']),
Paragraph("This is the first paragraph of content...", styles['Normal']),
Paragraph("Chapter 2: The Middle", styles['Heading1']),
Paragraph("More content follows here...", styles['Normal']),
]
# Calculate frame dimensions (letter page with 1-inch margins)
page_w, page_h = letter
frame_height = page_h - 2 * inch
frame_width = page_w - 2 * inch
# Paginate with heading protection
story = paginate(flowables, frame_height=frame_height, frame_width=frame_width)
# Build PDF
doc = SimpleDocTemplate("output.pdf", pagesize=letter)
doc.build(story)
Advanced Usage
Use SmartPaginator for full control over thresholds:
from smart_pagination import SmartPaginator
paginator = SmartPaginator(
frame_height=720,
frame_width=468,
carry_max=0.5, # Max 50% of page height can be carried
min_page_fill=0.15, # Page must be at least 15% filled after carry
search_depth=10, # Search last 10 items for orphaned headings
split_enabled=True, # Split large paragraphs across pages
)
story = paginator.paginate(flowables)
Parameters
| Parameter | Default | Description |
|---|---|---|
carry_max |
0.5 |
Maximum fraction of page height that can be carried to the next page. Increase for layouts with very large headings. |
min_page_fill |
0.15 |
Minimum fraction of page that must remain filled after carrying a heading. Decrease if you prefer heading protection over page aesthetics. |
search_depth |
10 |
How many flowables to search backward for an orphaned heading. |
split_enabled |
True |
Whether to split large paragraphs across pages to avoid blank gaps. |
split_min_space |
0.15 |
Minimum available page fraction before attempting a paragraph split. |
safety_margin |
10 |
Extra points reserved to prevent tight-fit overflow. |
How It Works
When a flowable would overflow the current page:
- Search backward through the last few items for a heading with
keepWithNext=True - Measure the carried content — heading + spacer + any items after it
- Safety check 1: If carrying would leave the current page less than 15% filled, abort — an orphaned heading looks better than an empty page
- Safety check 2: If the carried content exceeds 50% of the page height, abort — it would likely overflow the next page too
- If safe, remove the heading from the current page, insert a page break, and place the heading at the top of the next page
- If not safe, try splitting the overflowing paragraph across pages instead
Origin
This algorithm was developed while building the PDF export engine for Writer's Dream AI. Manuscripts with dozens of sub-headings and footnotes routinely triggered ReportLab's overflow bugs. The standard keepWithNext approach was the cause of the overlapping text, not the cure.
License
MIT License — see LICENSE for details.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file reportlab_smart_pagination-1.0.2.tar.gz.
File metadata
- Download URL: reportlab_smart_pagination-1.0.2.tar.gz
- Upload date:
- Size: 8.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.10.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ad30d66075f27586206c3e46005ddcad61275160096b6375bd7714d6cc82386d
|
|
| MD5 |
cd4848c87bc632e26341408c4331e4f7
|
|
| BLAKE2b-256 |
951f4edeacefa20084a240d3b2250f50b83a2a44675ecfab0ee04c4258b87daa
|
File details
Details for the file reportlab_smart_pagination-1.0.2-py3-none-any.whl.
File metadata
- Download URL: reportlab_smart_pagination-1.0.2-py3-none-any.whl
- Upload date:
- Size: 7.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.10.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e71243121fedb630472c156fb952d6474185a15b4cc02fb47ce83d3940e45b0b
|
|
| MD5 |
2786e136decc264abd716f6ff0343468
|
|
| BLAKE2b-256 |
b29448f2a8b89ce1790d50c20404f8c07e06e532a633566bbf4535e04df735e0
|