Skip to content

Layout: headers, footers, columns, booklet, page manager

The Layout module contains the page-furniture engines behind the Document facade: headers, footers, multi-column layout, saddle-stitch booklet imposition, and structural page operations. It is small and stable: six classes, all @since 1.0.0.

Terminal window
composer require nextpdf/core:^3

Layout (src/Layout/, six classes, @since 1.0.0) is the engine layer under the HasLayout concern. Your application calls facade methods on Document; the trait routes each call to one of these engines. The manifest marks the module as standard risk and internal stability, with Core as its only dependent. Use it through the facade, not by constructing the classes directly.

HeaderFooter renders the repeating header and footer. It stores the title, description, logo, font, margin, and color state for each band. It emits Portable Document Format (PDF) content-stream operators on demand through renderHeader() and renderFooter(). The default footer prints a right-aligned "page / total" string. setHeaderCallback() and setFooterCallback() replace the default layout with a caller-supplied closure. getHeaderContentHeight() reports the vertical space the header consumes, so the page body can start below it. When the document is in tagged-PDF mode, HasPages suppresses the automatic header upstream because header content sits outside the structure tree.

ColumnLayout manages multi-column flow. setEqualColumns(int $count, float $totalWidth, float $gap = 5) divides the available width into equal columns. setColumnsArray() accepts explicit ColumnDefinition positions and widths. Use selectColumn() to choose a column, or nextColumn() to advance. getCurrentColumnX() / getCurrentColumnWidth() return the active column geometry. An invalid column count, negative gap, or non-positive computed column width raises PageLayoutException.

BookletLayout reorders pages for saddle-stitch (center-fold) binding. reorderPages() pads the page list to a multiple of four because a booklet sheet holds four page slots, then imposes pages outside-in so the folded, stapled sheets read in order. getMarginAdjustments() returns the inner (spine) and outer (edge) margins for each side at a given position. getSheetCount() reports how many double-sided sheets a page count needs. Reordering changes content placement only. The underlying PDF page sequence stays linear, consistent with the page tree, which defines the ordering of pages in the document (ISO 32000-2 §7.7).

PageManager provides structural page operations separate from content rendering. movePage(), copyPage(), and deletePage() operate on a PageData array by reference. Page regions (addPageRegion(), isInRegion(), getRegionOffset()) define no-write zones. Page groups (startPageGroup(), getGroupPageNo()) support per-section page numbering. PageRegion and ColumnDefinition are the two value carriers these engines use. The Writer module serializes the resulting pages into a page tree whose Kids entry is an array of indirect references to the immediate children of a page tree node (ISO 32000-2 §7.7.3.2).

SymbolKindStabilitySince
HeaderFooter::setHeaderData(string, string, string, float): selfmethodstable1.0.0
HeaderFooter::setHeaderFont(string, float): self / setHeaderMargin(float): selfmethodstable1.0.0
HeaderFooter::setFooterFont(string, float): self / setFooterMargin(float): selfmethodstable1.0.0
HeaderFooter::setHeaderCallback(Closure): self / setFooterCallback(Closure): selfmethodstable1.0.0
HeaderFooter::getHeaderContentHeight(): floatmethodstable1.0.0
HeaderFooter::renderHeader(float, float, float, float, int, int): stringmethodstable1.0.0
HeaderFooter::renderFooter(float, float, float, float, int, int, string): stringmethodstable1.0.0
ColumnLayout::setEqualColumns(int, float, float): selfmethodstable1.0.0
ColumnLayout::setColumnsArray(array): self / resetColumns(): selfmethodstable1.0.0
ColumnLayout::selectColumn(int): self / nextColumn(): boolmethodstable1.0.0
ColumnLayout::getCurrentColumnX(float): float / getCurrentColumnWidth(float): floatmethodstable1.0.0
BookletLayout::setBooklet(bool, float, float): voidmethodstable1.0.0
BookletLayout::reorderPages(array): arraymethodstable1.0.0
BookletLayout::getMarginAdjustments(int): array{left: float, right: float}methodstable1.0.0
BookletLayout::getSheetCount(int): intmethodstable1.0.0
PageManager::movePage(int, int, array): void / copyPage(int, array): void / deletePage(int, array): voidmethodstable1.0.0
PageManager::addPageRegion(float, float, float, float): void / isInRegion(float, float): boolmethodstable1.0.0
PageManager::getRegionOffset(float, float, float, float): floatmethodstable1.0.0
PageManager::startPageGroup(): void / getGroupPageNo(int): intmethodstable1.0.0
PageRegion / ColumnDefinitionvalue carrierstable1.0.0
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();
$doc->setTitle('Header and Footer');
$doc->setHeaderData(
title: 'NextPDF Example',
description: 'Header and Footer Demonstration',
);
$doc->setHeaderFont('helvetica', 10);
$doc->setHeaderMargin(5);
$doc->setFooterFont('helvetica', 8);
$doc->setFooterMargin(10);
$doc->addPage();
$doc->setFont('helvetica', 'B', 16);
$doc->cell(0, 12, 'Document with Header and Footer', newLine: true);
$doc->save(__DIR__ . '/output/13-header-footer.pdf');

Source: examples/13-header-footer.php. The header renders on each addPage() call; the footer renders when the page flushes.

A footer callback controls the page-number text, and the column engine drives a two-column body. You reach both engines through the facade.

<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();
$doc->setTitle('Two-Column Report');
$doc->setFooterCallback(static function (Document $d): void {
$d->setFont('helvetica', '', 8);
$d->text(180.0, 285.0, 'Page ' . ($d->getPage() + 1));
});
$doc->addPage();
$doc->setEqualColumns(2, gap: 8);
$doc->selectColumn(0);
$doc->setFont('helvetica', '', 10);
$doc->multiCell(0, 6, 'Left column flows here.');
$doc->selectColumn(1);
$doc->multiCell(0, 6, 'Right column flows here.');
$doc->save(__DIR__ . '/output/two-column-report.pdf');

Source: pattern from examples/13-header-footer.php.

  • setEqualColumns() rejects a column count below 1, a negative gap, or any layout whose computed column width is not positive. It raises PageLayoutException instead of returning a degraded layout.
  • selectColumn() ignores an out-of-range index and keeps the current column; it never throws for an invalid index. nextColumn() returns false when it is already on the last column.
  • BookletLayout::reorderPages() pads to a multiple of four with blank pages cloned from the last page’s dimensions. An empty page list returns an empty array. Reordering affects placement only; movePage() indices still refer to logical order.
  • PageManager::movePage(), copyPage(), and deletePage() silently do nothing for an out-of-range index; they validate with isset() and return without modifying the array. Verify the index yourself when a missing page is a caller error.
  • getHeaderContentHeight() returns 0.0 when the header is disabled or has neither title nor description. The page body then starts at the top margin.
  • In tagged-PDF mode, the automatic header is suppressed upstream. Build a structure-aware layout for accessible documents.

Header and footer rendering appends operators to the active page buffer in O(furniture content); cost scales with the title, description, and separator written, not with document size. Column math is O(1) per call. BookletLayout::reorderPages() is O(n) in page count, with a one-time padding pass; the imposition loop touches each padded slot once. PageManager region tests are O(regions) per point, and page operations are O(n) array splices. These engines retain no per-page state across the document, so they do not add memory growth on long documents. The dominant memory cost is the accumulated content stream, which the streaming-and-memory concept covers. The HyperText Markup Language (HTML) pipeline latency and memory budget gate is documented in PERFORMANCE-BUDGETS; it is scoped to the HTML render path and does not gate these layout engines directly. The performance_budget of 1500 ms / 64 MB is the canvas envelope for the quick-start, not a per-call contract.

These engines consume caller-supplied strings and a caller-supplied logo path. The logo path flows through the image engine, which validates the file before embedding it. renderHeader() and renderFooter() escape title, description, and page-number text through the centralized PDF string escaper before they reach the content stream, so caller text cannot break out of the literal-string grammar. A header or footer callback runs caller code with the same trust as the rest of the document; treat any external data it reads accordingly. PageManager page operations move references to existing PageData; they do not parse untrusted bytes.

ClaimStandardClauseEvidence
Page reordering for booklet output changes placement only; the page tree still defines the linear ordering of pages in the document.ISO 32000-2§7.7
The serialized page tree node’s Kids entry is an array of indirect references to the immediate children of that node.ISO 32000-2§7.7.3.2

The table paraphrases each clause and keeps glossary terms pinned; it does not reproduce normative text.