跳到內容

版面配置:頁首、頁尾、欄、書冊與頁面管理員

Layout 模組收納各種頁面裝飾引擎,Document 外觀(facade)會將工作委派給這些引擎:頁首與頁尾繪製、多欄版面、騎馬釘書冊拼版,以及結構性頁面操作。這是一個小而穩定的模組,共有六個類別,全部都是 @since 1.0.0

Terminal window
composer require nextpdf/core:^3

Layoutsrc/Layout/,六個類別,@since 1.0.0)是 HasLayout 這個 concern 底下的引擎層。應用程式碼會呼叫 Document 上的外觀方法;trait 會將每次呼叫路由到其中一個引擎。在 manifest 中,這個模組標記為 standard 風險與 internal 穩定度,唯一的相依方是 Core。你透過外觀使用它,而不是直接建構這些類別。

HeaderFooter 負責繪製重複出現的頁首與頁尾。它會為每個區帶保留標題、描述、標誌、字型、邊界與色彩等狀態,並透過 renderHeader()renderFooter() 視需要產生 PDF 內容串流運算子。預設頁尾會印出一個靠右對齊的 "page / total" 字串。setHeaderCallback()setFooterCallback() 會以呼叫端提供的 closure 取代預設版面。getHeaderContentHeight() 會回報頁首佔用的垂直空間,讓頁面本文能從其下方開始排版。當文件處於 tagged-PDF 模式時,自動頁首會由上游的 HasPages 抑制,因為頁首內容位於結構樹之外。

ColumnLayout 負責管理多欄流式排版。setEqualColumns(int $count, float $totalWidth, float $gap = 5) 會把可用寬度均分成等寬的欄。setColumnsArray() 則接受明確的 ColumnDefinition 位置與寬度。游標可透過 selectColumn() 選取某一欄,或透過 nextColumn() 前進到下一欄。getCurrentColumnX() / getCurrentColumnWidth() 會回傳目前作用中欄的幾何資訊。欄數無效、間距為負,或計算出的欄寬非正值時,會引發 PageLayoutException

BookletLayout 會為騎馬釘(中央對折)裝訂重新排序頁面。reorderPages() 會把頁面清單補齊到四的倍數(一張書冊紙容納四個頁面格位),再由外而內拼版,讓紙張對折並裝訂後能依序閱讀。getMarginAdjustments() 會回傳指定位置左右兩側的內側(書脊)與外側(邊緣)邊界。getSheetCount() 會回報某個頁數需要多少張雙面紙張。重新排序只會改變內容的擺放位置。底層的 PDF 頁面序列維持線性,與頁面樹一致;頁面樹定義了文件中頁面的排列順序(ISO 32000-2 §7.7)。

PageManager 提供獨立於內容繪製之外的結構性頁面操作。movePage()copyPage()deletePage() 會以參考方式對一個 PageData 陣列進行操作。頁面區域(addPageRegion()isInRegion()getRegionOffset())用來定義禁止寫入的區帶。頁面群組(startPageGroup()getGroupPageNo())支援分節編頁。PageRegionColumnDefinition 是這些引擎使用的兩個數值載體。Writer 模組會把產生的頁面序列化成頁面樹;其 Kids 項目是一個陣列,內含指向該頁面樹節點直屬子節點的間接參考(ISO 32000-2 §7.7.3.2)。

符號種類穩定度
HeaderFooter::setHeaderData(string, string, string, float): self方法穩定1.0.0
HeaderFooter::setHeaderFont(string, float): self / setHeaderMargin(float): self方法穩定1.0.0
HeaderFooter::setFooterFont(string, float): self / setFooterMargin(float): self方法穩定1.0.0
HeaderFooter::setHeaderCallback(Closure): self / setFooterCallback(Closure): self方法穩定1.0.0
HeaderFooter::getHeaderContentHeight(): float方法穩定1.0.0
HeaderFooter::renderHeader(float, float, float, float, int, int): string方法穩定1.0.0
HeaderFooter::renderFooter(float, float, float, float, int, int, string): string方法穩定1.0.0
ColumnLayout::setEqualColumns(int, float, float): self方法穩定1.0.0
ColumnLayout::setColumnsArray(array): self / resetColumns(): self方法穩定1.0.0
ColumnLayout::selectColumn(int): self / nextColumn(): bool方法穩定1.0.0
ColumnLayout::getCurrentColumnX(float): float / getCurrentColumnWidth(float): float方法穩定1.0.0
BookletLayout::setBooklet(bool, float, float): void方法穩定1.0.0
BookletLayout::reorderPages(array): array方法穩定1.0.0
BookletLayout::getMarginAdjustments(int): array{left: float, right: float}方法穩定1.0.0
BookletLayout::getSheetCount(int): int方法穩定1.0.0
PageManager::movePage(int, int, array): void / copyPage(int, array): void / deletePage(int, array): void方法穩定1.0.0
PageManager::addPageRegion(float, float, float, float): void / isInRegion(float, float): bool方法穩定1.0.0
PageManager::getRegionOffset(float, float, float, float): float方法穩定1.0.0
PageManager::startPageGroup(): void / getGroupPageNo(int): int方法穩定1.0.0
PageRegion / ColumnDefinition數值載體穩定1.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');

來源:examples/13-header-footer.php。頁首會在每次 addPage() 時繪製;頁尾則在頁面排清時繪製。

頁碼文字由頁尾 callback 掌管,雙欄本文則由欄引擎驅動。這兩個引擎都透過外觀使用。

<?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');

來源:取自 examples/13-header-footer.php 的模式。

  • setEqualColumns() 會拒絕欄數小於 1、間距為負,或計算後欄寬非正的版面。上述每一種情況都會引發 PageLayoutException,而不會回傳降級的版面。
  • selectColumn() 會忽略超出範圍的 Index(索引)並維持目前的欄;它絕不會因索引錯誤而擲出例外。當 nextColumn() 已位於最後一欄時,會回傳 false
  • BookletLayout::reorderPages() 會用空白頁補齊到四的倍數,這些空白頁的尺寸會從最後一頁複製而來。空的頁面清單會回傳空陣列。重新排序只影響擺放位置;movePage() 的索引仍然指涉邏輯順序。
  • PageManager::movePage()copyPage()deletePage() 在索引超出範圍時會靜默地不做任何事;它們以 isset() 驗證,並在不修改陣列的情況下直接返回。當缺頁屬於呼叫端的錯誤時,請自行驗證索引。
  • getHeaderContentHeight() 在頁首被停用,或既無標題也無描述時,會回傳 0.0。此時頁面本文會從上邊界開始。
  • 在 tagged-PDF 模式下,自動頁首會在上游被抑制。請為無障礙文件建構具結構感知的版面。

頁首與頁尾繪製會以 O(裝飾內容) 的成本,把運算子附加到作用中的頁面緩衝區;成本與寫入的標題、描述與分隔線成正比,而非與文件大小成正比。欄的計算每次呼叫都是 O(1)。BookletLayout::reorderPages() 在頁數上是 O(n),外加一次性的補齊處理;拼版迴圈會對每個補齊後的格位各處理一次。PageManager 對每個點的區域測試是 O(區域數);頁面操作則是 O(n) 的陣列切分與拼接。這些引擎都不會跨整份文件保留各頁狀態,因此在長文件上不會造成記憶體成長。主要的記憶體成本來自累積的內容串流,這正是 串流與記憶體概念 所涵蓋的內容。HTML 管線的延遲與記憶體預算 gate 記載於 PERFORMANCE-BUDGETS;它的範圍限於 HTML 繪製路徑,並不會直接對這些版面引擎設下 gate。這個 performance_budget 的 1500 ms / 64 MB 是快速上手範例的整體 canvas 預算上限,而非每次呼叫的契約。

這些引擎會處理呼叫端提供的字串與一個標誌路徑。標誌路徑會流經影像引擎,影像引擎會在檔案嵌入前先驗證。renderHeader()renderFooter() 會在標題、描述與頁碼文字進入內容串流之前,先透過集中式的 PDF 字串轉義器加以轉義,因此呼叫端文字無法跳脫字面字串的語法。頁首或頁尾 callback 會執行呼叫端程式碼,其信任程度與文件其餘部分相同;請據此看待它所讀取的任何外部資料。PageManager 的頁面操作只是移動指向既有 PageData 的參考;它們不會剖析不受信任的位元組。

聲明事項標準條款佐證
為書冊輸出而重新排序頁面只會改變擺放位置;頁面樹仍然定義文件中頁面的線性排列順序。ISO 32000-2§7.7
序列化後的頁面樹節點,其 Kids 項目是一個陣列,內含指向該節點直屬子節點的間接參考。ISO 32000-2§7.7.3.2

以上條款皆為改寫,並已對應到詞彙表;未重現任何規範性內文。