放置內容前先檢視版面邊界框
在放置內容前,先讀取文件的即時版面幾何資料。這個 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。所引用的條款正是版面查詢介面所依賴的那些。