跳转到内容

添加链接和文本注释

本示例会添加三种交互元素:跳转到另一页的内部链接、打开 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 字符串(而非 int)传给 link()。随后,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。 通过 addLink() 获取、却从未用 setLink() 绑定的 id 将没有目标。该矩形虽可点击,却毫无作用。请在 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
Subtype 在链接注释中为 LinkISO 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 限制。