Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
A PDF table gets stuck when its paginator encounters a row that cannot fit, then retries that same row without advancing the page, changing the row index, splitting the row, or stopping. Measure each complete row before drawing it. If it does not fit, move it to a fresh page; if it is taller than a full page, split it with a supported feature or stop with a clear error. Every pagination iteration must make progress or terminate.
Why a PDF table loops, skips rows, or draws them twice
Pagination is a state-transition problem. A table renderer has at least a current page or frame, a row index, and the row’s content. When a row is taller than the space left on the page, a faulty branch may simply try to draw it again. If nothing changes, the next iteration sees the same row in the same space and repeats forever.
Related defects can look different: the renderer may paint part of a row before breaking, revisit a row and draw it twice, or advance its bookkeeping without drawing the row and silently drop it. The common fix is to define what must happen whenever a row does not fit—and make sure the code actually performs that transition.
Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Clear out junk files and repair common Windows errorsFree Scan →- Loop: neither the page nor the row index changes after a failed fit.
- Skipped row: bookkeeping advances despite the row not being drawn or retained for the next page.
- Clipped or divided row: drawing begins before the renderer has determined whether the full row fits, and there is no deliberate splitting policy.
The invariant to enforce is simple: each pagination iteration consumes one or more rows, advances to a new page or frame, splits the current row, or stops with a diagnostic. A non-fitting branch must never return to the same state unchanged.
#1 Best Overall
Measure the full row before drawing it
First calculate the usable height in the current page or frame. Subtract the top and bottom margins, any reserved header and footer space, and the height of a repeated table header on pages where it appears. Then measure each cell at the width and style it will actually have when drawn. The row height is based on the tallest cell, with the row’s vertical padding and borders included.
For wrapped text, the measurement must use the final column widths, font, font size, line height, and padding. Explicit newlines, long unbroken tokens, images, and nested content can change a cell’s height, so test them rather than assuming that ordinary wrapped text is the only case. A hard-coded row height is unsafe unless it is calculated consistently with the content and drawing styles.
Compare the measured height with the remaining usable space before drawing any cell. If it fits, draw the complete row and advance the row index. If it does not, move to a new page when the current page already contains content, then assess the same row against that page’s usable height. If the row is still too tall, split it using a supported mechanism, change the content or layout, or stop with a controlled error.
Free tools Windows power users keep installed
One-click scans. No signup required.
Use a progress-guaranteeing pagination loop
The following Python example shows the control flow independently of any PDF library. Heights are in points, and row_heights must come from measuring the content at its final column widths and styles. The example places rows intact and raises an error if one cannot fit on an otherwise empty page; it does not draw a PDF or split rows.
def paginate_rows(row_heights, page_height, top, bottom,
repeated_header=0):
"""Return page groups of row indexes, or reject an oversized row."""
pages = []
row_index = 0
while row_index < len(row_heights):
page_rows = []
available = page_height - top - bottom - repeated_header
if available <= 0:
raise ValueError("No usable page height after reserved space")
while row_index < len(row_heights):
row_height = row_heights[row_index]
if row_height <= 0:
raise ValueError(f"Invalid height for row {row_index}")
if row_height > available:
if not page_rows:
raise ValueError(
f"Row {row_index} is taller than a full page: "
f"{row_height:.1f}pt > {available:.1f}pt"
)
break # Keep this row as the first candidate next page.
page_rows.append(row_index)
available -= row_height
row_index += 1
if not page_rows:
raise RuntimeError("Paginator made no progress")
pages.append(page_rows)
return pages
heights = [42, 88, 715, 36]
try:
print(paginate_rows(heights, page_height=792, top=36, bottom=36,
repeated_header=28))
except (ValueError, RuntimeError) as error:
print(f"PDF table pagination failed: {error}")
In this example the usable area is 692 points after reserved space. The 715-point row is therefore rejected with a diagnostic rather than retried forever. To allow a row that exceeds the page, replace that explicit rejection with a library-supported split and ensure the split produces content that fits. Do not just suppress the error: that would leave the renderer without a valid next state.
In your renderer, the drawing layer should consume the page groups or follow equivalent control flow. A page break should preserve the current row index; a successful draw should advance it. If you split a row, advance only after recording which content remains for the continuation page.
Choose a policy for rows that do not fit
- Move intact: If there is content on the current page, finish that page and retry the row on a fresh one. This is usually the least surprising result for a row that can fit on a full page.
- Split deliberately: Use the library’s supported row-splitting behavior when a single row can exceed a page. Define how cell contents divide and how borders or continuation labels appear.
- Change the layout: Reduce excessive padding or typography, widen a text column, shorten content, or move unusually large material outside the table.
- Reject with context: If the library cannot split the row and it exceeds a full page, stop and report the page, row identifier, measured height, and available height. A controlled failure is preferable to a hung job or incomplete PDF.
There is no universal numeric row-height limit. The threshold depends on the page geometry, margins, repeated content, column widths, fonts, padding, and the renderer’s splitting behavior.
What the major PDF table approaches do
| Library or approach | Relevant behavior | What to check |
|---|---|---|
| ReportLab Table and LongTable | Can calculate row heights, split across pages, and repeat leading rows. The documentation describes measurable flowables and options including repeatRows and rowSplitRange. |
Measure wrapped content with flowables such as Paragraph and final column widths. Do not assume column splitting works: the documentation says splitByRow=False requests column splitting, which is not implemented and raises NotImplementedError. |
| FPDF MultiCell reference pattern | Calculates the row height as the maximum height of its MultiCell content, checks for a page break, then draws the cells at that row height. |
Perform the page-break check before drawing any cell in the row. The example advances to a new page when the full row height would exceed the page-break trigger. |
| PDFlib fitbox table model | PDF_fit_table() places rows that fit in the current fitbox and continues in a subsequent fitbox. A last body row that does not fit is usually kept intact rather than split. |
Use the continuation behavior for rows that fit a later fitbox. The tutorial identifies minrowheight as an option when controlled splitting is desired; establish a policy for a row that cannot fit a full fitbox. |
| jsPDF-AutoTable horizontal page breaks | An issue opened May 8, 2026 reports tall wrapped-text rows being silently skipped with horizontalPageBreak: true and horizontalPageBreakBehaviour: 'immediately'. |
Treat horizontal-break mode as its own path and verify that a rejected row remains the next row to process. The issue identifies versions jspdf-autotable 5.0.2 and jspdf 3.0.0; check whether the behavior applies to the version you run. |
These are different library behaviors, not a cross-library performance or reliability ranking. Match the policy to your content: intact movement is unsuitable if a row is taller than the page, while splitting is only safe when the selected library supports it for the content involved.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Diagnose the exact branch that stops progress
- Log each failed fit. Record the page number, row index or identifier, measured row height, remaining usable height, and the branch taken.
- Check measurement inputs. Confirm the final column widths, font metrics, font size, line height, padding, borders, and cell content all match the drawing configuration.
- Recalculate new-page capacity. Include repeated headers and any other reserved area in the next page’s available-height calculation.
- Assert a state change. After each iteration, assert that the row index or page number changed, a split was recorded, or the operation stopped with an error.
- Verify row retention. For horizontal pagination or other alternate rendering paths, confirm that a non-fitting row is deferred rather than discarded.
Test at least three boundaries: a row that fits exactly, one that exceeds the remaining space by a line, and one taller than a full page. Repeat with wrapped text, explicit newlines, long tokens, images, and nested flowables. If the first two fail, inspect measurement and page-transition logic. If only the third fails, the implementation likely needs an explicit split or oversized-row policy.
Rank #4
Common failure causes and fixes
- The paginator hangs on one row: Its no-fit branch does not change page, row index, or content. Add a page transition when there is page content; on an empty page, split or raise a diagnostic.
- A row disappears in horizontal pagination: A separate code path may advance its bookkeeping after rejecting the row. Break at the first non-fitting row and leave it as the next row for the following page or segment.
- Text is clipped although the row seemed to fit: The measured height is smaller than the drawn content. Recheck widths, font settings, line spacing, padding, and borders against the final drawing style.
- The row fits on an otherwise blank page but not after the repeated heading: The next-page calculation omitted repeated-header height. Reserve that height before testing the row.
- An oversized row still fails after a page break: Moving it intact cannot help if it exceeds the full usable page. Use supported splitting, redesign the content, or stop with the row’s measured and available heights.
Or skip the browser setup
ScreenshotNeo is a website screenshot API, not a PDF table paginator. It will not fix a PDF-generation loop. If your table is rendered as a web page and you need an image of that page, you can request a capture without setting up a browser:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
See the ScreenshotNeo API documentation for request options. ScreenshotNeo accepts cookie or consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers identify the page verdict and billing status. Its MCP server offers take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients. The Free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots. These capture features are useful for web-rendered tables, but do not replace fixing the paginator that creates your PDF.
Recommended Free Tools
Sign up for 1,000 free screenshots a month, with no card required.
Quick Recap
Sources
- ReportLab, “Chapter 7: Tables – ReportLab Docs”
- FPDF, “Table with MultiCells”
- simonbengtsson/jsPDF-AutoTable issue #1109
- PDFlib GmbH, “PDFlib Tutorial 9.0.6”
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

