Build a multi-page document with automatic page breaks
At a glance
Section titled “At a glance”Build a document that spans many pages. Add content as you go. When setAutoPageBreak() is enabled, the layout engine starts a new page when the cursor reaches the bottom margin. After save(), read the final page count with getNumPages(). This recipe follows examples/05-multi-page.php.
During save(), the engine writes each page’s marks to a content stream. ISO 32000-2 §7.7.3.3 defines a page’s Contents as one stream, or as an array of streams concatenated in order. Multi-page output is therefore a sequence of page objects, not a single buffer.
Install
Section titled “Install”composer require nextpdf/core:^3No optional extension is required. This recipe runs on the PHP 8.1–8.4 backport matrix. getNumPages() and setAutoPageBreak() have both been stable since 1.0.0.
Conceptual overview
Section titled “Conceptual overview”A NextPDF document is a page tree. As you add content, an internal cursor (getY()) moves down the page. When automatic page breaks are on, the engine checks the remaining vertical space before each content block. If the block will not fit above the bottom margin, the engine flushes the current page and calls addPage() for you. The bottom margin you pass to setAutoPageBreak() is the trigger threshold.
Page-level attributes, such as the media box, are inheritable. ISO 32000-2 §7.7.3.4 specifies that an attribute omitted from a page object resolves from an ancestor page-tree node. NextPDF sets one consistent page size across the document, so every generated page uses the same geometry, and you do not repeat it per page.
API surface
Section titled “API surface”The application programming interface (API) surface is generated from PHPDoc. This recipe relies on these methods:
Document::createStandalone(): self— construct an isolated document.setAutoPageBreak(bool $enabled, float $margin = 20): static— enable automatic page breaks.$marginis the bottom-margin trigger in millimeters.addPage(?PageSize $size = null, Orientation $orientation = Orientation::Portrait): static— start the first page, and any explicit page.multiCell(...): static/cell(...): static— emit flowing or fixed text blocks. The page-break check measures these blocks.getNumPages(): int— the number of pages after layout.
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->setFont('helvetica', '', 11);for ($i = 1; $i <= 60; $i++) { $doc->multiCell(0, 7, "Line {$i}: content flows until the page is full, " . 'then the engine starts a new page automatically.');}
$doc->save(getenv('NEXTPDF_COOKBOOK_OUTPUT') ?: __DIR__ . '/multi-page.pdf');echo 'Pages: ' . $doc->getNumPages() . "\n";Code sample — Production
Section titled “Code sample — Production”This is the complete, harness-ready example. It honors NEXTPDF_COOKBOOK_OUTPUT, which the harness sets, so do not echo the PDF to STDOUT. It adds no entropy of its own. When the harness runs it, DeterministicMode pins the clock, /ID, and branding.
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();$doc->setTitle('Multi-Page Document');
// Enable automatic page breaks. The 25 mm bottom margin is the trigger:// when the cursor would cross it, the engine flushes the page and adds// a new one before the next block is drawn.$doc->setAutoPageBreak(true, margin: 25);$doc->addPage();
$doc->setFont('helvetica', 'B', 18);$doc->cell(0, 12, 'Multi-Page Document Example', newLine: true);$doc->ln(5);
for ($chapter = 1; $chapter <= 3; $chapter++) { $doc->setFont('helvetica', 'B', 14); $doc->cell(0, 10, "Chapter {$chapter}: Lorem Ipsum", newLine: true); $doc->setFont('helvetica', '', 11);
for ($para = 1; $para <= 5; $para++) { $text = "Paragraph {$para} of Chapter {$chapter}. " . 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. ' . 'Sed do eiusmod tempor incididunt ut labore et dolore magna ' . 'aliqua. Ut enim ad minim veniam, quis nostrud exercitation ' . 'ullamco laboris nisi ut aliquip ex ea commodo consequat.'; $doc->multiCell(0, 7, $text); $doc->ln(3); } $doc->ln(5);}
// The harness sets NEXTPDF_COOKBOOK_OUTPUT; honour it. STDOUT stays free// for progress text only.$out = getenv('NEXTPDF_COOKBOOK_OUTPUT') ?: __DIR__ . '/multi-page.pdf';$doc->save($out);
echo 'Created multi-page.pdf with ' . $doc->getNumPages() . " pages\n";Edge cases & gotchas
Section titled “Edge cases & gotchas”- Auto page break disabled. With
setAutoPageBreak(false, …), content past the bottom margin is clipped to the page instead of flowing, and the document stays on a single page. Enable it for flowing content. - A single block taller than the page. The engine internally splits a
multiCellwhose text exceeds the printable height. But a single indivisible block taller than the usable area, such as a tall image, is placed once and overflows. Break it yourself. - First
addPage()is still required.cell()callsaddPage()on demand when no page exists. Even so, calladdPage()explicitly so the first page’s size and orientation stay deterministic. - Margin units. The
setAutoPageBreak()margin is in millimeters in the default unit system, not in points.
Performance
Section titled “Performance”getNumPages() is O(1). It reads a counter and does not run layout again. Memory scales with retained content, not with page count. The engine flushes finished pages to the output buffer as they complete — the single-pass streaming model (ADR-001). The 2000 ms / 64 MB budget covers documents with a few hundred pages of text on the reference host.
Security notes
Section titled “Security notes”This recipe writes only the text your code supplies. It performs no input parsing, network access, or deserialization. Treat any externally sourced text as untrusted, and apply a length bound before rendering. The engine does not impose an application-level content-size limit for you.
Conformance
Section titled “Conformance”| Statement | Spec | Clause | reference_id |
|---|---|---|---|
A page’s Contents is a single stream or an ordered, concatenated array of streams. | ISO 32000-2 | §7.7.3.3 | |
| An inheritable page attribute omitted from a page object resolves from an ancestor page-tree node. | ISO 32000-2 | §7.7.3.4 | |
The trailer /ID is a two byte-string file identifier (required in PDF 2.0). | ISO 32000-2 | §7.5.5 |
Reproducibility profile — structural (why not bitwise). Every saved document carries a trailer /ID whose two byte-strings are a file identifier (ISO 32000-2 §7.5.5, above). The second element is not run-stable, so raw bytes differ between runs even for identical content. The harness compares the qpdf-normalized structure, which strips /ID, /CreationDate, and /ModDate. This recipe describes how NextPDF produces the structure. It does not assert ISO 32000-2 conformance as a blanket claim.
Commercial context
Section titled “Commercial context”Not applicable. Multi-page composition with automatic page breaks is a Core capability, with no Premium gate.