Skip to content

Paginate long HTML across pages

Use automatic page breaks to flow long content across pages. Add an outline so readers can jump between sections. This recipe follows examples/12-bookmarks-and-toc.php.

Terminal window
composer require nextpdf/core:^3

Use this constraint for the nextpdf/core package. The example runs on PHP 8.4.

setAutoPageBreak(true, $margin) tells the engine to start a new page before content crosses the bottom-margin threshold. The engine fragments long text written through multiCell() or writeHtml() at that boundary. The Cascading Style Sheets (CSS) Fragmentation module (css_break_3) is graded Verified in the support matrix. It backs break behavior in the Hypertext Markup Language (HTML) pipeline.

bookmark($title, $level) adds an outline item for the current position. A Portable Document Format (PDF) outline item associates a destination so readers can jump directly to a page (ISO 32000-2). The engine writes that destination as the item’s Dest entry (ISO 32000-2). Use the level argument to nest items into a hierarchical table of contents in the reader’s sidebar.

The pipeline stays single-pass (ADR-001). The engine decides pagination as it emits the stream, without a retained layout tree.

  • setAutoPageBreak(bool $enabled, float $margin = 20): staticNextPDF\Core\Concerns\HasPages.
  • bookmark(string $title, int $level = 0, float $y = -1): staticNextPDF\Core\Concerns\HasNavigation.
  • multiCell(...) / writeHtml(string $html): staticNextPDF\Core\Concerns\HasTextOutput.

The full PHPDoc table is generated from the source.

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();
$doc->setAutoPageBreak(true, margin: 25);
$doc->addPage();
$doc->bookmark('Section 1', level: 0);
$doc->setFont('helvetica', '', 11);
for ($i = 1; $i <= 80; $i++) {
$doc->multiCell(0, 7, "Paragraph {$i} of a long flowing document.");
}
$doc->save(__DIR__ . '/out.pdf');

This self-contained example runs in the harness. It builds a multi-chapter document with a nested outline and automatic page breaks, and it mirrors examples/12-bookmarks-and-toc.php.

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();
$doc->setTitle('Bookmarks and Navigation');
$doc->setPrintHeader(false);
$doc->setPrintFooter(false);
$doc->setAutoPageBreak(true, margin: 25);
$chapters = [
'Chapter 1: Introduction' => ['What is NextPDF?', 'Key Features'],
'Chapter 2: Getting Started' => ['Installation', 'Your First PDF'],
'Chapter 3: Advanced Topics' => ['Worker-safe Architecture', 'Streaming Output'],
];
$body = 'NextPDF is a modern PDF 2.0 library for PHP. This paragraph is '
. 'repeated so the content overflows the page and the engine inserts '
. 'an automatic page break at the bottom-margin threshold.';
foreach ($chapters as $chapter => $sections) {
$doc->addPage();
$doc->bookmark($chapter, level: 0);
$doc->setFont('helvetica', 'B', 18);
$doc->cell(0, 12, $chapter, newLine: true);
$doc->ln(3);
foreach ($sections as $section) {
$doc->bookmark($section, level: 1);
$doc->setFont('helvetica', 'B', 14);
$doc->cell(0, 10, $section, newLine: true);
$doc->setFont('helvetica', '', 11);
for ($i = 0; $i < 12; $i++) {
$doc->multiCell(0, 7, $body);
}
$doc->ln(4);
}
}
$out = getenv('NEXTPDF_COOKBOOK_OUTPUT');
$doc->save($out !== false ? $out : __DIR__ . '/paginate-long-html.pdf');
echo "Wrote paginate-long-html.pdf\n";

Expected standard output (STDOUT):

Wrote paginate-long-html.pdf
  • Disable then forget. When auto page break is disabled, the engine clips content past the bottom margin instead of flowing it. Re-enable it before long content.
  • Unsplittable content. A single block taller than the usable page height can raise UnsplittableContentException. A very tall table row or large image can cause this. Split the source content.
  • Bookmark before content. Call bookmark() at the position you want the destination to point to. Place it immediately before the next heading, on the page you want.
  • Header and footer reserve space. A print header or footer reduces the usable content height, and the break threshold accounts for it. Disabling both, as this example does, gives you the full body height.
  • Outline nesting. level is the nesting depth. A child at level: 1 must follow a level: 0 parent. Otherwise, the reader flattens the outline tree.

The engine decides pagination during its single emission pass. Cost is linear in content length, O(n). The budget is wall_ms: 2000, peak_mb: 96. Wall time runs slightly higher than single-page recipes because multi-page cross-reference (xref) and outline assembly add work. The streaming model keeps memory bounded, and the outline remains a small flat list.

CSS support matrix excerpt (Verified-only rows)

Section titled “CSS support matrix excerpt (Verified-only rows)”

This table reproduces only the Verified rows from the truth-audited CSS support matrix.

W3C moduleLevelStatusEvidence
CSS Fragmentation (css_break_3)3Verifiedsrc/Html/Fragmentation/, tests/Unit/Html/PagedMedia/
CSS Table (css_tables_3)3Verifiedsrc/Html/Table/ + golden PDFs
CSS Cascading and Inheritance (css_cascade_3)3Verifiedsrc/Html/Cascade/

@page named-page selectors belong to CSS Paged Media. Before you rely on them, consult the matrix for that module’s current grade.

Single-pass streaming constraints (ADR-001)

Section titled “Single-pass streaming constraints (ADR-001)”

The engine emits page breaks as the stream flows. Because no retained tree exists to re-flow, each break decision is final. Some content, such as a cross-reference, needs its final page number after layout. That content is constrained, so write it with that limit in mind.

Pagination belongs to the page-break controller, not the parser. The parser does not emit raw page-transition operators; it requests a break through the controller contract.

The streaming model holds the current page buffer and the flat outline list, not all pages at once. A very long document stays within the ADR-020 ceiling because the engine flushes completed pages. Tables and flex containers still obey the 5,000-node-per-context bound.

A hostile document cannot force unbounded memory. The element and nesting caps (ADR-001), plus the per-context node budget (ADR-020), bound the work. Validate the length and structure of user-supplied long content. The engine renders an attacker-controlled outline title as text and never interprets it.

StatementSpecClausereference_id
Each outline item may be associated with a destination so the user jumps directly to it.ISO 32000-2iso32000_2_sec12#x1.x5.p4
An outline item’s Dest entry names the destination displayed when the item is activated.ISO 32000-2iso32000_2_sec12#x1.x11.p30

This recipe shows how NextPDF flows long content and builds an outline. The support matrix grades CSS Fragmentation as Verified.

Not applicable.