跳到內容

為長篇 HTML 自動分頁

使用自動分頁,讓長篇內容延展至多頁。加上大綱後,讀者就能在各章節之間跳轉。這則 recipe(範例)依循 examples/12-bookmarks-and-toc.php

Terminal window
composer require nextpdf/core:^3

這個版本約束對應 nextpdf/core 套件。這個範例在 PHP 8.4 上執行。

setAutoPageBreak(true, $margin) 會讓引擎在內容即將超出下邊界閾值時另起新頁。引擎會在該邊界處,切分透過 multiCell()writeHtml() 寫入的長文字。CSS 分段(Fragmentation)模組(css_break_3)在支援矩陣中評為 Verified,並支撐 HTML 管線的斷頁行為。

bookmark($title, $level) 會新增一個指向目前位置的大綱項目。PDF 大綱項目會與一個目標位置關聯,讓使用者能直接跳到某一頁(ISO 32000-2)。引擎會把該目標位置寫成項目的 Dest 項目(ISO 32000-2)。level 引數會將項目以巢狀方式排入閱讀器側邊欄中的階層式目錄。

管線維持單一遍歷(ADR-001)。分頁是在串流(stream)輸出的同時決定的,而非仰賴保留下來的版面樹。

  • setAutoPageBreak(bool $enabled, float $margin = 20): staticNextPDF\Core\Concerns\HasPages
  • bookmark(string $title, int $level = 0, float $y = -1): staticNextPDF\Core\Concerns\HasNavigation
  • multiCell(...) / writeHtml(string $html): staticNextPDF\Core\Concerns\HasTextOutput

完整的 PHPDoc 表格由原始碼產生。

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();
$doc->setAutoPageBreak(true, margin: 25);
$doc->addPage();
$doc->bookmark('Section 1', level: 0);
$doc->setFont('helvetica', '', 11);
for ($i = 1; $i <= 80; $i++) {
$doc->multiCell(0, 7, "Paragraph {$i} of a long flowing document.");
}
$doc->save(__DIR__ . '/out.pdf');

這個範例可獨立運作,也能在測試載具中執行。它會建立一份多章節文件,搭配巢狀大綱與自動分頁,並對應 examples/12-bookmarks-and-toc.php

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();
$doc->setTitle('Bookmarks and Navigation');
$doc->setPrintHeader(false);
$doc->setPrintFooter(false);
$doc->setAutoPageBreak(true, margin: 25);
$chapters = [
'Chapter 1: Introduction' => ['What is NextPDF?', 'Key Features'],
'Chapter 2: Getting Started' => ['Installation', 'Your First PDF'],
'Chapter 3: Advanced Topics' => ['Worker-safe Architecture', 'Streaming Output'],
];
$body = 'NextPDF is a modern PDF 2.0 library for PHP. This paragraph is '
. 'repeated so the content overflows the page and the engine inserts '
. 'an automatic page break at the bottom-margin threshold.';
foreach ($chapters as $chapter => $sections) {
$doc->addPage();
$doc->bookmark($chapter, level: 0);
$doc->setFont('helvetica', 'B', 18);
$doc->cell(0, 12, $chapter, newLine: true);
$doc->ln(3);
foreach ($sections as $section) {
$doc->bookmark($section, level: 1);
$doc->setFont('helvetica', 'B', 14);
$doc->cell(0, 10, $section, newLine: true);
$doc->setFont('helvetica', '', 11);
for ($i = 0; $i < 12; $i++) {
$doc->multiCell(0, 7, $body);
}
$doc->ln(4);
}
}
$out = getenv('NEXTPDF_COOKBOOK_OUTPUT');
$doc->save($out !== false ? $out : __DIR__ . '/paginate-long-html.pdf');
echo "Wrote paginate-long-html.pdf\n";

預期的 STDOUT:

Wrote paginate-long-html.pdf
  • 停用後忘了重開。 停用自動分頁時,引擎會把超出下邊界的內容裁切掉,而不會讓它流到下一頁。在輸出長篇內容之前,要先重新啟用它。
  • 無法切分的內容。 單一區塊若高於可用頁面高度,可能會引發 UnsplittableContentException。高度很高的表格列或大型影像就是原因之一。請把來源內容拆開。
  • 先設書籤再寫內容。 在你希望目標位置指向的地方呼叫 bookmark()。請在你想指定的那一頁上,緊接著要寫入的標題之前呼叫它。
  • 頁首與頁尾會占用空間。 列印頁首或頁尾會縮減可用的內容高度,斷頁閾值也會計入這部分空間。如同這個範例的做法,把兩者都停用,就能取得完整的內文高度。
  • 大綱巢狀。 level 是巢狀深度。位於 level: 1 的子項目,必須接在 level: 0 的父項目之後。否則閱讀器會把大綱樹攤平。

引擎會在單一輸出遍歷期間決定分頁。成本與內容長度呈線性,O(n)。預算為 wall_ms: 2000, peak_mb: 96。由於需要組裝多頁 xref 與大綱,實際耗時會比單頁範例略高。串流模型讓記憶體維持在有界範圍,而大綱只是一份小型的扁平清單。

CSS 支援矩陣節錄(僅列 Verified 的列)

標題為「CSS 支援矩陣節錄(僅列 Verified 的列)」的區段

此處只重現評為 Verified 的列,來源為已通過真實性稽核的 CSS 支援矩陣

W3C 模組層級狀態佐證
CSS 分段(css_break_33已驗證src/Html/Fragmentation/tests/Unit/Html/PagedMedia/
CSS 表格(css_tables_33已驗證src/Html/Table/ 加上 golden PDF
CSS Cascading and Inheritance(層疊與繼承)(css_cascade_33已驗證src/Html/Cascade/

@page 具名頁面選擇器屬於 CSS Paged Media。在依賴它之前,請先查閱矩陣,確認該模組目前的評等。

引擎會隨串流輸出斷頁。沒有保留下來的樹可供重新排版,因此斷頁決定一旦做出就是最終結果。有些內容需要在排版後才能得到最終頁碼,例如交叉參照。這類內容受到限制,因此撰寫時要把這項限制納入考量。

分頁由斷頁控制器(controller)負責,而非由 parser(剖析器)負責。parser 不會輸出原始的換頁運算子,而是透過控制器合約請求斷頁。

串流模型只持有目前的頁面緩衝區加上扁平的大綱清單,而非一次持有所有頁面。很長的文件也會維持在 ADR-020 的上限內,因為引擎會把已完成的頁面寫出。表格與 flex 容器仍受 5,000 個節點/每個排版脈絡的上限約束。

惡意文件無法強迫記憶體無界成長。元素與巢狀上限(ADR-001)以及每個脈絡的節點預算(ADR-020)會限制其工作量。請驗證使用者提供的長篇內容長度與結構。引擎會把攻擊者可控制的大綱標題當作文字繪製,絕不對其加以解讀。

陳述規範條款參考 ID
每個大綱項目都可與一個目標位置關聯,讓使用者直接跳到該位置。ISO 32000-2iso32000_2_sec12#x1.x5.p4(第 12 節)
大綱項目的 Dest 項目會指定該項目被啟用時所顯示的目標位置。ISO 32000-2iso32000_2_sec12#x1.x11.p30(第 12 節)

這則 recipe 示範 NextPDF 如何讓長篇內容流動並建立大綱。 CSS 分段在支援矩陣中評為 Verified。

不適用。