跳到內容

新增連結與文字註解

這個範例會新增三種互動元素:跳至另一頁的內部連結、開啟 URL 的外部連結,以及一則文字註解(也就是便利貼)。它延續自 examples/17-links.phpexamples/29-annotations.php

可點擊的連結是一種 ISO 32000-2 連結註解,也就是指向某個目的地或動作的超連結。便利貼則是一則文字註解;關閉時顯示圖示,開啟時顯示彈出視窗。

Terminal window
composer require nextpdf/core:^3

不需要任何選用擴充功能。連結與註解 API 自 1.0.0 起即為穩定,並可在 8.1–8.4 的 backport 相容矩陣上執行。

內部連結採用支援前向參照的三個步驟呼叫模式:

  1. addLink() 會保留一個連結識別碼(int)。
  2. link($x, $y, $w, $h, $id) 會放置一個綁定至該 id 的可點擊矩形。
  3. setLink($id, $pageIndex, $y) 會將該 id 綁定到目的地頁面(從零起算)與 Y 座標。

在步驟 2 之後再呼叫步驟 3,即可連到尚未建立的頁面。該目的地採用明確目的地(explicit destination)的形式。ISO 32000-2 §12.3.2.2 定義了 [page /XYZ left top zoom],其中為 null 的元件會沿用讀取器目前的數值。

若要建立外部連結,請將 URL 字串 傳給 link(),而非 int。NextPDF 接著會產生一個 URI 動作,其 URI 是必填的 UTF-8 ASCII 字串。也可以使用捷徑:write($height, $text, $link) 會繪製附帶 URL 的行內文字,而 annotation($x, $y, $w, $h, $text) 則會放置一個 Text 子類型的便利貼。ISO 32000-2 要求連結註解必須使用 SubtypeLink,並定義了文字註解的圖示與彈出視窗行為。

API 介面由 PHPDoc 自動產生。這個範例會用到下列方法:

  • addLink(): int — 保留一個內部連結識別碼。
  • setLink(int $linkId, int $pageIndex = -1, float $y = 0): static — 將某個 id 綁定到目的地頁面(從零起算)與 Y 座標。
  • link(float $x, float $y, float $w, float $h, string|int $link): static — 可點擊矩形;int id 代表內部連結,string 則是外部 URL。
  • write(float $height, string $text, string $link = ''): static — 行內文字,可選擇附帶 URL。
  • annotation(float $x, float $y, float $w, float $h, string $text, string $subtype = 'Text'): static — 便利貼式註解。
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();
$jump = $doc->addLink(); // 1. reserve an id (forward reference)
$doc->addPage();
$doc->setFont('helvetica', 'B', 12);
$x = $doc->getX();
$y = $doc->getY();
$doc->cell(60, 10, 'Go to page 2', newLine: true);
$doc->link($x, $y, 60, 10, $jump); // 2. clickable rectangle -> id
$doc->link($doc->getX(), $doc->getY(), 80, 10, 'https://nextpdf.dev'); // external
$doc->addPage();
$doc->setLink($jump, pageIndex: 1, y: 0); // 3. bind id to page 2 (index 1)
$doc->cell(0, 10, 'Destination (page 2).', newLine: true);
$doc->annotation(x: 180, y: 20, w: 10, h: 10, text: 'A reviewer note.');
$doc->save(getenv('NEXTPDF_COOKBOOK_OUTPUT') ?: __DIR__ . '/links.pdf');

這是可直接用於測試載具的完整範例。它會遵循 NEXTPDF_COOKBOOK_OUTPUT,且不會自行加入任何隨機性。

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();
$doc->setTitle('Links and Annotations');
// Reserve the internal-link id before its destination page exists.
$linkToPage3 = $doc->addLink();
// Page 1 — source of the internal and external links.
$doc->addPage();
$doc->setFont('helvetica', 'B', 20);
$doc->cell(0, 14, 'Links and Annotations', newLine: true);
$doc->ln(6);
$doc->setFont('helvetica', 'B', 12);
$doc->setTextColor(0, 51, 153);
$linkX = $doc->getX();
$linkY = $doc->getY();
$doc->cell(60, 10, 'Go to Page 3', newLine: true);
$doc->link($linkX, $linkY, 60, 10, $linkToPage3); // internal: int id
$doc->setTextColor(0);
$doc->ln(6);
$doc->setFont('helvetica', 'B', 12);
$doc->setTextColor(0, 102, 204);
$doc->write(10, 'Visit https://nextpdf.dev', link: 'https://nextpdf.dev');
$doc->setTextColor(0);
$doc->ln(6);
$urlX = $doc->getX();
$urlY = $doc->getY();
$doc->cell(80, 10, 'NextPDF on GitHub', newLine: true);
$doc->link($urlX, $urlY, 80, 10, 'https://github.com/nextpdf-labs/nextpdf');
// Page 2 — intermediate.
$doc->addPage();
$doc->setFont('helvetica', '', 11);
$doc->multiCell(0, 7, 'Internal links can jump across pages; this page is '
. 'skipped by the link on page 1.');
// Page 3 — destination + a sticky note.
$doc->addPage();
$doc->setLink($linkToPage3, pageIndex: 2, y: 0); // bind id to page 3 (index 2)
$doc->setFont('helvetica', 'B', 18);
$doc->cell(0, 14, 'Page 3 — Link Target', newLine: true);
$doc->ln(4);
$doc->setFont('helvetica', '', 11);
$doc->multiCell(0, 7, 'You arrived via the internal link on page 1.');
$doc->annotation(
x: 185, y: 40, w: 10, h: 10,
text: 'Sticky note: appears as an icon; click to read this text.',
);
$out = getenv('NEXTPDF_COOKBOOK_OUTPUT') ?: __DIR__ . '/links.pdf';
$doc->save($out);
echo "Created links.pdf\n";
  • pageIndex 從零起算。 setLink($id, pageIndex: 2, …) 指向的是第三頁。這裡最常見的是差一錯誤。
  • link() 中 string 與 int 的差異。 int 是來自 addLink() 的內部目的地 id。string 則是外部 URL。傳入錯誤的型別會建立錯誤類型的連結,且不會出現任何錯誤提示。
  • 綁定每一個保留的 id。 如果 id 是由 addLink() 取得,卻從未以 setLink() 綁定,就不會有目的地。該矩形雖可點擊,卻毫無作用。請在 save() 之前完成綁定。
  • 可點擊的區域是矩形,而非文字。 link() 接受明確的 x, y, w, h。請將大小調整為足以覆蓋可見文字。引擎不會替你量測字元。
  • 外部連結不會被驗證。 NextPDF 會原封不動地儲存該 URI。它並不會驗證目標是否能解析(resolve)或是否安全。解析的工作由讀取器負責。

每個連結或註解都會為頁面增加一個註解字典。每個元素的成本為 O(1)。即使每頁數百個,也仍遠在 2000 ms/64 MB 的預算之內。

外部連結的目標會原封不動地儲存,並由讀取器(而非函式庫)負責解析。請將使用者提供的 URL 視為不可信任。請使用允許清單限制協定,通常為 https。在將 javascript:file: 傳入 link() 之前,請先予以拒絕。註解文字會顯示在讀取器的使用者介面中,因此請對使用者可控的註解內容設定長度上限並加以清理。這個範例不會進行任何輸入剖析或網路存取。

陳述規範條款參考 ID
連結註解是指向某個目的地或動作的超連結。ISO 32000-2§12.5.6.5
連結註解中的 SubtypeLinkISO 32000-2§12.5.6.5
URI 動作的 URI 是必填的 UTF-8 ASCII 字串。ISO 32000-2§12.6.4.8
文字註解是一則便利貼(關閉時為圖示,開啟時為彈出視窗)。ISO 32000-2§12.5.6.4
明確目的地 [page /XYZ left top zoom];為 null 時沿用目前的數值。ISO 32000-2§12.3.2.2

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

不適用。連結與文字註解屬於核心功能,不受 Premium gate 限制。