放置内容前先检查版面边界框
在放置内容前,先读取文档当前的版面几何数据。本 recipe(范例)会查询页面边界框、边距、所有禁写区域以及当前光标位置。这样,你的放置逻辑就能依据真实数值运行,而不是依赖猜测坐标。本范例由 examples/34-inspect-layout-boxes.php 及其 tests/Cookbook/Php/InspectLayoutBoxesRecipeTest.php 测试载具支撑。
composer require nextpdf/core:^3你不需要 Pro 或 Enterprise 软件包。版面查询接口属于 Core 的一部分,可在 PHP 8.1 到 8.4 上运行。
概念总览
标题为“概念总览”的章节PDF 页面具有边界框。最基本的是 MediaBox,也就是定义页面范围的矩形(ISO 32000-2 §7.7.3.3)。内容在用户空间(user space)中定位。默认情况下,用户空间的原点位于左下角,一个单位等于 1/72 英寸(§8.3.2)。NextPDF 提供对作者友好的左上角视角,并通过只读查询方法公开这些几何数据:
getPageWidth()/getPageHeight()— 页面边界框的尺寸。getMargins()— 当前生效的Margin值对象(上/右/下/左)。getPageRegions()— 已声明的禁写区域(PageRegion[])。每个区域都是不可变矩形,禁止在其中放置内容。getX()/getY()— 作者空间中的当前光标。
这些都是 幂等读取。它们不会输出内容、推进光标或改变状态。你可以用它们计算剩余垂直空间,再决定是继续写入,还是调用 addPage()。也可以用它们让某个区块相对于禁写区域排版,而不是写死偏移量。
API 接口
标题为“API 接口”的章节API 接口由 PHPDoc 生成。主要入口点位于 \NextPDF\Core\Concerns\HasPages 与 HasLayout 这两个 trait 中:
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
代码范例 — 快速上手
标题为“代码范例 — 快速上手”的章节<?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);代码范例 — 正式版
标题为“代码范例 — 正式版”的章节这是可独立运行、能在测试载具上执行的程序。它对应 范例 examples/34-inspect-layout-boxes.php。它会读取页面几何数据、声明一个页脚禁写区域,然后针对每个区块作出数据驱动的决策。如果下一个区块会与某个区域相撞,或超出下边距,它就会新增一页,而不是叠印到现有内容上。
<?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);预期的 STDOUT 如下。页面边界框的数字反映默认的 A4 页面大小,光标则从左上角的内容原点开始:
Page box: 595.28 x 841.89 ptCursor start: (<x>, <y>)Pages used: 2 (page breaks decided from layout geometry)边界情况与陷阱
标题为“边界情况与陷阱”的章节- 在页面存在之前读取。
getPageWidth()与getPageHeight()反映的是 当前 这一页,所以要在addPage()之后再调用它们。在第一页出现之前,它们返回的是默认页面大小的几何数据,而不是你尚未新增的那一页。 - 区域是作者空间中的矩形。
PageRegiony表示从左上角起算的作者空间距离,与getY()一致。不要把它和原始的左下角 PDF 坐标混用。 - 读取没有副作用。 这些查询方法都不会推进光标或输出内容。在密集循环中调用它们是安全的,成本也很低。
- 边距可能逐页变动。 如果后续某一页设置了不同的边距,请重新读取
getMargins(),而不是缓存第一次的值。 - 区域不会自动重排文字。 禁写区域是 你必须遵守的约束,而不是自动的文字换行边界。本范例展示了明确的碰撞检查。对于自由定位的写入,引擎绝不会自行把内容移出某个区域。
这里的每个方法都只是一次属性读取。它以常数时间运行,不分配内存,也没有任何输入或输出。本范例构建的小型多页文档远低于 1500 ms / 96 MB 的预算。可复现性配置文件属于 语义(semantic) 层级,因为生成的 PDF 带有 trailer 的 /ID 与创建时的元数据。本范例真正重要的是可观察的几何决策,因此测试载具会通过结构性的抽象语法树(AST)和元数据比对来验证这些决策,而不是使用字节哈希。
安全性说明
标题为“安全性说明”的章节- 数据驻留与 PII 缓解。 不适用。本范例读取几何数据,并排版调用端提供的文字,因此不会引入任何新的数据路径。请对你放置的文字应用与其他位置相同的最小化原则。
- 安全遥测与日志清理。 本范例会打印几何数字和一行固定的进度消息。它不会记录文档文字。
- 威胁模型。 不适用。这个查询接口是只读的,也不解析任何外部输入。它不是信任边界。
- FIPS 模式行为。 不适用。不会执行任何密码学运算。
符合性
标题为“符合性”的章节| 陈述 | 规格 | 条款 | 参考 ID |
|---|---|---|---|
| 页面对象通过边界框(MediaBox)定义其范围。 | ISO 32000-2 | §7.7.3.3 | |
| 页面矩形就是内容边界。 | ISO 32000-2 | §7.7.3.3 | |
| 位置以用户空间度量;一个单位是 1/72 英寸。 | ISO 32000-2 | §8.3.2 |
本 recipe 读取的几何数据,由所引用的 ISO 32000-2 页面边界框与用户空间条款定义。它并未主张全面符合 ISO 32000-2。所引用的条款正是版面查询接口所依赖的条款。