Paginate long HTML across pages
At a glance
Section titled “At a glance”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.
Install
Section titled “Install”composer require nextpdf/core:^3Use this constraint for the nextpdf/core package. The example runs on PHP 8.4.
Conceptual overview
Section titled “Conceptual overview”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.
API surface
Section titled “API surface”setAutoPageBreak(bool $enabled, float $margin = 20): static—NextPDF\Core\Concerns\HasPages.bookmark(string $title, int $level = 0, float $y = -1): static—NextPDF\Core\Concerns\HasNavigation.multiCell(...)/writeHtml(string $html): static—NextPDF\Core\Concerns\HasTextOutput.
The full PHPDoc table is generated from the source.
Code sample — Quick start
Section titled “Code sample — Quick start”<?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');Code sample — Production
Section titled “Code sample — Production”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.pdfEdge cases & gotchas
Section titled “Edge cases & gotchas”- 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.
levelis the nesting depth. A child atlevel: 1must follow alevel: 0parent. Otherwise, the reader flattens the outline tree.
Performance
Section titled “Performance”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 module | Level | Status | Evidence |
|---|---|---|---|
CSS Fragmentation (css_break_3) | 3 | Verified | src/Html/Fragmentation/, tests/Unit/Html/PagedMedia/ |
CSS Table (css_tables_3) | 3 | Verified | src/Html/Table/ + golden PDFs |
CSS Cascading and Inheritance (css_cascade_3) | 3 | Verified | src/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.
Layer contracts (ADR-010)
Section titled “Layer contracts (ADR-010)”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.
Memory budget for large documents
Section titled “Memory budget for large documents”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.
Security notes
Section titled “Security notes”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.
Conformance
Section titled “Conformance”| Statement | Spec | Clause | reference_id |
|---|---|---|---|
| Each outline item may be associated with a destination so the user jumps directly to it. | ISO 32000-2 | iso32000_2_sec12#x1.x5.p4 | |
| An outline item’s Dest entry names the destination displayed when the item is activated. | ISO 32000-2 | iso32000_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.
Commercial context
Section titled “Commercial context”Not applicable.