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 El-Shamha — 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.0.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.0-py3-none-any.whl (7.9 kB view details)

Uploaded Python 3

File details

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

File metadata

File hashes

Hashes for reportlab_smart_pagination-1.0.0.tar.gz
Algorithm Hash digest
SHA256 e5b138557dbaf84fd326c5da024b0488cca050811b1b9f2ea8eb1df1fbe64bad
MD5 f9cce2471f0ae1d9682c10a35319bf51
BLAKE2b-256 db6d3b2b2ea88b88eaf3c09f754296775a6f5ea6e52a52045596a4e644e76add

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for reportlab_smart_pagination-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 967ae2c7e6752468a5c4c1c0337ddfd6091d67daeb6a4bb4c2bb280cbc735fbf
MD5 97fbeff9a5568dc8080da985a48cdc1d
BLAKE2b-256 051b7bdefd2ade7c27bbf4638b9eb11a83535050d5439e5ef90262cafcae8ae1

See more details on using hashes here.

Release history Release notifications | RSS feed

1.0.2

2 files

This release

1.0.0 This release

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