跳到內容

新增書籤與目錄

這個 recipe(範例)會新增一份階層式的文件大綱,也就是書籤。閱讀器應用程式會把這些項目列在導覽側欄中,讓它們同時成為可點按的目錄。巢狀深度由整數層級控制。本範例依循 examples/12-bookmarks-and-toc.php

ISO 32000-2 將這類結構稱為文件大綱:一棵由大綱項目組成的樹狀階層,作為視覺化的目錄。

Terminal window
composer require nextpdf/core:^3

不需要任何選用擴充功能。書籤 API 自 1.0.0 起即為穩定版,可在 8.1–8.4 backport 相容矩陣上執行。

bookmark($title, $level, $y) 會新增一個繫結到目前頁面的大綱項目。這個繫結會使用目前的 Y 座標,或使用你明確指定的 Y 座標。$level 設定巢狀深度:層級 0 是最上層的章,層級 1 是隸屬於最近一個層級 0 項目之下的節,依此類推。引擎會替你建立大綱樹。ISO 32000-2 在每一層以 Prev/Next 串接項目,透過 First/Last 形成巢狀,並以 catalog 的 Outlines 項目作為根節點。

每個項目都帶有目的地,因此當你點按書籤時,閱讀器會跳到正確的頁面。ISO 32000-2 §12.3.2 規定目的地可以與大綱項目產生關聯。同一個呼叫也會傳給 NextPDF 的目錄建構器,因此大綱與算繪出的目錄會保持同步。

API 介面由 PHPDoc 自動產生。這個範例只依賴一個方法:

  • bookmark(string $title, int $level = 0, float $y = -1): static — 在 $level 新增一個繫結到目前頁面的大綱項目。$y = -1 會使用目前游標的 Y 座標。傳入一個非負的 Y 值,即可釘選精確的目的地。
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();
$doc->addPage();
$doc->bookmark('Chapter 1', level: 0);
$doc->setFont('helvetica', 'B', 18);
$doc->cell(0, 12, 'Chapter 1', newLine: true);
$doc->bookmark('Section 1.1', level: 1); // nested under Chapter 1
$doc->setFont('helvetica', '', 11);
$doc->multiCell(0, 7, 'Section body.');
$doc->addPage();
$doc->bookmark('Chapter 2', level: 0);
$doc->setFont('helvetica', 'B', 18);
$doc->cell(0, 12, 'Chapter 2', newLine: true);
$doc->save(getenv('NEXTPDF_COOKBOOK_OUTPUT') ?: __DIR__ . '/bookmarks.pdf');

這是完整、可直接用於測試載具的範例。它會遵循 NEXTPDF_COOKBOOK_OUTPUT,且本身不會引入任何不確定性。

<?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);
// Chapter 1
$doc->addPage();
$doc->bookmark('Chapter 1: Introduction', level: 0);
$doc->setFont('helvetica', 'B', 18);
$doc->cell(0, 12, 'Chapter 1: Introduction', newLine: true);
$doc->ln(3);
$doc->bookmark('What is NextPDF?', level: 1);
$doc->setFont('helvetica', 'B', 14);
$doc->cell(0, 10, 'What is NextPDF?', newLine: true);
$doc->setFont('helvetica', '', 11);
$doc->multiCell(0, 7, 'NextPDF is a modern PDF 2.0 library for PHP 8.4+. '
. 'It generates standards-targeting documents with typography, '
. 'graphics, and signatures.');
$doc->ln(5);
$doc->bookmark('Key Features', level: 1);
$doc->setFont('helvetica', 'B', 14);
$doc->cell(0, 10, 'Key Features', newLine: true);
$doc->setFont('helvetica', '', 11);
$doc->multiCell(0, 7, 'HTML rendering, barcodes, PAdES signatures, '
. 'and a worker-safe architecture.');
// Chapter 2
$doc->addPage();
$doc->bookmark('Chapter 2: Getting Started', level: 0);
$doc->setFont('helvetica', 'B', 18);
$doc->cell(0, 12, 'Chapter 2: Getting Started', newLine: true);
$doc->ln(3);
$doc->bookmark('Installation', level: 1);
$doc->setFont('helvetica', 'B', 14);
$doc->cell(0, 10, 'Installation', newLine: true);
$doc->setFont('helvetica', '', 11);
$doc->multiCell(0, 7, 'Install via Composer: composer require nextpdf/core');
$out = getenv('NEXTPDF_COOKBOOK_OUTPUT') ?: __DIR__ . '/bookmarks.pdf';
$doc->save($out);
echo "Created bookmarks.pdf with a 2-level outline\n";
  • 層級跳躍。 從層級 0 直接跳到層級 2、中間沒有層級 1,可能會讓某些閱讀器呈現出看似結構錯誤的階層。每次最多只將層級增加一階。
  • 在內容之前設定書籤。 在你希望目的地所在的位置呼叫 bookmark()。多數情況下,請把這個呼叫放在標題正前方。若把呼叫放在標題之後,目的地會設在標題稍微下方的位置。
  • 以明確的 Y 值取得精確目的地。$y = -1 時,目的地即為目前游標的 Y 座標。傳入明確的 Y 值,可取得穩定的目的地。舉例來說,明確的 Y 值能把一節的頂端釘選住,無論它前面有多少內容都不受影響。
  • 大綱支援是普遍的,但呈現方式各異。 閱讀器可能會以收合或展開的形式呈現大綱。這個 API 無法強制每個項目的展開或收合狀態。

每次 bookmark() 呼叫都會附加一個大綱項目與一筆目錄項目,這是 O(1) 的工作量。大綱樹只會在 save() 時定案一次。數百個書籤仍可輕鬆維持在 2000 ms / 64 MB 的預算之內。

書籤標題會顯示在閱讀器的導覽介面中。如果標題包含使用者可控制的資料,請像處理任何會被顯示的字串一樣,對它套用長度限制與淨化。這個範例不會執行任何輸入剖析,也不會進行任何網路存取。

陳述規格條款參考 ID
文件大綱是一棵由大綱項目組成的樹,作為視覺化的目錄。ISO 32000-2§12.3.3
大綱項目透過 Prev/Next 串接,並透過 First/Last 形成巢狀。ISO 32000-2§12.3.3
大綱字典是 catalog 的 Outlines 項目。ISO 32000-2§7.7.2
目的地可以與大綱項目產生關聯。ISO 32000-2§12.3.2

可重現性設定檔 — 結構層級。 trailer 的 /ID 與日期原子每次儲存都會變動,因此測試載具會把這些原子剝除,再比對經 qpdf 正規化後的結構。這個範例說明 NextPDF 如何產生這類結構。它並不主張對 ISO 32000-2 的全面符合性。

不適用。文件大綱與目錄都是 Core 的功能,沒有 Premium 限制。