Inspect layout boxes before placing content
At a glance
Section titled “At a glance”Read a document’s live layout geometry before you place content. This recipe
reads the page box, margins, no-write regions, and current cursor. Your
placement logic can then use real numbers instead of guessed coordinates. The
recipe is backed by examples/34-inspect-layout-boxes.php and its
tests/Cookbook/Php/InspectLayoutBoxesRecipeTest.php harness.
Install
Section titled “Install”composer require nextpdf/core:^3You do not need a Pro or Enterprise package. The layout query surface is part of Core and runs on PHP 8.1 through 8.4.
Conceptual overview
Section titled “Conceptual overview”A Portable Document Format (PDF) page has boundary boxes. The minimum is the
MediaBox, the rectangle that defines the page extent (ISO 32000-2 §7.7.3.3).
Content is positioned in user space. By default, user space starts at the
lower-left corner, and one unit equals 1/72 inch (§8.3.2). NextPDF gives you an
author-friendly top-left view and exposes the geometry through read-only query
methods:
getPageWidth()/getPageHeight()— page box dimensions.getMargins()— the activeMarginvalue object (top/right/bottom/left).getPageRegions()— the declared no-write zones (PageRegion[]). Each is an immutable rectangle where content placement is prohibited.getX()/getY()— the live cursor in author space.
These are idempotent reads. They do not emit content, advance the cursor,
or change state. Use them to compute the remaining vertical space, then decide
whether to keep writing or call addPage(). You can also lay out a block
relative to a no-write region instead of using a hard-coded offset.
API surface
Section titled “API surface”The application programming interface (API) surface is generated from PHPDoc.
The main entry points live in the \NextPDF\Core\Concerns\HasPages and
HasLayout traits:
Document::getPageWidth(): float/Document::getPageHeight(): floatDocument::getMargins(): \NextPDF\ValueObjects\MarginDocument::getPageRegions(): array(list<\NextPDF\Layout\PageRegion>)Document::addPageRegion(float $x, float $y, float $w, float $h): staticDocument::getX(): float/Document::getY(): float
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->addPage();
$pageHeight = $doc->getPageHeight();$margins = $doc->getMargins();$cursorY = $doc->getY();
// Vertical space left before the bottom margin.$remaining = $pageHeight - $margins->bottom - $cursorY;
// Geometry is in user-space units (PDF points; 1 pt = 1/72 in).echo sprintf("Page height: %.2f pt\n", $pageHeight);echo sprintf("Bottom margin: %.2f pt\n", $margins->bottom);echo sprintf("Cursor Y: %.2f pt\n", $cursorY);echo sprintf("Remaining: %.2f pt\n", $remaining);Code sample — Production
Section titled “Code sample — Production”This self-contained program runs under the harness. It mirrors
examples/34-inspect-layout-boxes.php.
It reads the page geometry, declares a footer no-write region, and makes a
data-driven decision for each block. If the next block would collide with a
region or run past the bottom margin, it adds a page instead of overprinting.
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();$doc->setTitle('Layout Box Inspection Demo');$doc->setLanguage('en');$doc->addPage();
// Read the geometry (idempotent, no side effects). Values are in// user-space units (PDF points; 1 pt = 1/72 in).$pageWidth = $doc->getPageWidth();$pageHeight = $doc->getPageHeight();$margins = $doc->getMargins();
echo sprintf("Page box: %.2f x %.2f pt\n", $pageWidth, $pageHeight);echo sprintf("Cursor start: (%.2f, %.2f)\n", $doc->getX(), $doc->getY());
// A 15 pt tall footer no-write zone across the text column.$footerHeight = 15.0;$footerTop = $pageHeight - $margins->bottom - $footerHeight;$doc->addPageRegion( $margins->left, $footerTop, $pageWidth - $margins->left - $margins->right, $footerHeight,);
$blocks = [ 'Section 1. Each block measures the live cursor against the page box ' . 'and any no-write region before it is placed.', 'Section 2. The lowest Y at which any region starts is the hard floor ' . 'for new content; crossing it forces a page break.', 'Section 3. Reading geometry is idempotent — the query methods never ' . 'advance the cursor, so they are safe in the placement loop.', 'Section 4. This final block forces a second page when the column is ' . 'already near the footer keep-out zone.',];
$blockHeight = 42.0;$pagesUsed = 1;
foreach ($blocks as $index => $text) { $cursorY = $doc->getY();
// Lowest Y a region starts at — the hard floor for new content. $regionFloor = $pageHeight - $margins->bottom; foreach ($doc->getPageRegions() as $region) { $regionFloor = min($regionFloor, $region->y); }
if ($cursorY + $blockHeight > $regionFloor) { $doc->addPage(); ++$pagesUsed; // A fresh page resets the region set; re-declare the footer zone. $footerTop = $doc->getPageHeight() - $doc->getMargins()->bottom - $footerHeight; $doc->addPageRegion( $doc->getMargins()->left, $footerTop, $doc->getPageWidth() - $doc->getMargins()->left - $doc->getMargins()->right, $footerHeight, ); }
$doc->setFont('helvetica', 'B', 12); $doc->cell(0, 8, 'Block ' . ($index + 1), newLine: true); $doc->setFont('helvetica', '', 11); $doc->multiCell(0, 7, $text); $doc->ln(6);}
// The harness sets NEXTPDF_COOKBOOK_OUTPUT and runs this script under the// semantic profile (validated by structural AST + metadata, not a byte hash).$out = getenv('NEXTPDF_COOKBOOK_OUTPUT');$doc->save($out !== false && $out !== '' ? $out : __DIR__ . '/inspect-layout-boxes.pdf');
echo sprintf("Pages used: %d (page breaks decided from layout geometry)\n", $pagesUsed);Expected standard output (STDOUT). The page-box numbers reflect the default A4 page size, and the cursor starts at the top-left content origin:
Page box: 595.28 x 841.89 ptCursor start: (<x>, <y>)Pages used: 2 (page breaks decided from layout geometry)Edge cases & gotchas
Section titled “Edge cases & gotchas”- Read before the page exists.
getPageWidth()andgetPageHeight()reflect the current page, so call them afteraddPage(). Before the first page, they return the default page-size geometry, not a page you have not added yet. - Regions are author-space rectangles. A
PageRegionyis the top-left author distance, consistent withgetY(). Do not mix it with raw lower-left PDF coordinates. - Reads are side-effect free. None of the query methods advance the cursor or emit content. Calling them in a tight loop is safe and costs little.
- Margins can change per page. If a later page sets different margins,
re-read
getMargins()instead of caching the first value. - Regions do not auto-reflow text. A no-write region is a constraint you must honor, not an automatic text-wrap boundary. The recipe shows the explicit collision check. For free-positioned writes, the engine never moves content out of a region on its own.
Performance
Section titled “Performance”Every method here is a property read. It runs in constant time, with no
allocation and no input or output. The example builds a small multi-page
document well within the 1500 ms / 96 MB budget. The reproducibility profile is
semantic, because the generated PDF carries the trailer /ID and creation
metadata. The example’s observable geometry decisions matter most, so the
harness validates those decisions through a structural abstract syntax tree
(AST) plus metadata comparison rather than a byte hash.
Security notes
Section titled “Security notes”- Data Residency & PII Mitigations. Not applicable. The recipe reads geometry and lays out caller-supplied text, so it introduces no new data path. Apply the same minimization to the text you place as you would anywhere else.
- Safe Telemetry & Log Scrubbing. The example prints geometry numbers and a fixed progress line. It does not log document text.
- Threat model. Not applicable. The query surface is read-only and parses no external input. It is not a trust boundary.
- FIPS-mode behavior. Not applicable. No cryptographic operation runs.
Conformance
Section titled “Conformance”| Statement | Spec | Clause | reference_id |
|---|---|---|---|
| A page object defines its extent through boundary boxes (MediaBox). | ISO 32000-2 | §7.7.3.3 | |
| The page rectangle is the content boundary. | ISO 32000-2 | §7.7.3.3 | |
| Positions are measured in user space; one unit is 1/72 inch. | ISO 32000-2 | §8.3.2 |
This recipe reads the geometry defined by the cited ISO 32000-2 page-box and user-space clauses. It does not assert blanket ISO 32000-2 conformance. The layout query surface relies on the cited clauses.