添加链接和文本注释
本示例会添加三种交互元素:跳转到另一页的内部链接、打开 URL 的外部链接,以及一则文本注释(又称便签)。它沿用 examples/17-links.php 与 examples/29-annotations.php。
可点击的链接是一个 ISO 32000-2 链接注释,也就是指向某个目标或动作的超链接。便签则是一种文本注释:关闭时显示为图标,打开时显示为弹出窗口。
composer require nextpdf/core:^3不需要任何可选扩展。链接与注释 API 自 1.0.0 起保持稳定,并可在 8.1–8.4 的 backport 兼容矩阵上运行。
概念总览
标题为“概念总览”的章节内部链接采用支持前向引用的三步调用模式:
addLink()会保留一个链接标识符(int)。link($x, $y, $w, $h, $id)会放置一个绑定到该 id 的可点击矩形。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 接口
标题为“API 接口”的章节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 在链接注释中为 Link。 | ISO 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 限制。