Skip to main content

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:

  1. Search backward through the last few items for a heading with keepWithNext=True
  2. Measure the carried content — heading + spacer + any items after it
  3. Safety check 1: If carrying would leave the current page less than 15% filled, abort — an orphaned heading looks better than an empty page
  4. Safety check 2: If the carried content exceeds 50% of the page height, abort — it would likely overflow the next page too
  5. If safe, remove the heading from the current page, insert a page break, and place the heading at the top of the next page
  6. 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

reportlab_smart_pagination-1.0.2.tar.gz (8.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

reportlab_smart_pagination-1.0.2-py3-none-any.whl (7.8 kB view details)

Uploaded Python 3

File details

Details for the file reportlab_smart_pagination-1.0.2.tar.gz.

File metadata

File hashes

Hashes for reportlab_smart_pagination-1.0.2.tar.gz
Algorithm Hash digest
SHA256 ad30d66075f27586206c3e46005ddcad61275160096b6375bd7714d6cc82386d
MD5 cd4848c87bc632e26341408c4331e4f7
BLAKE2b-256 951f4edeacefa20084a240d3b2250f50b83a2a44675ecfab0ee04c4258b87daa

See more details on using hashes here.

File details

Details for the file reportlab_smart_pagination-1.0.2-py3-none-any.whl.

File metadata

File hashes

Hashes for reportlab_smart_pagination-1.0.2-py3-none-any.whl
Algorithm Hash digest
SHA256 e71243121fedb630472c156fb952d6474185a15b4cc02fb47ce83d3940e45b0b
MD5 2786e136decc264abd716f6ff0343468
BLAKE2b-256 b29448f2a8b89ce1790d50c20404f8c07e06e532a633566bbf4535e04df735e0

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

1.0.2 This release

2 files

1.0.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page