リンクとテキスト注釈を追加する
このレシピでは、3 つのインタラクティブ要素を追加します。別のページへジャンプする内部リンク、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 のバックポートマトリックスに対応しています。
概念的な概要
「概念的な概要」という見出しのセクション内部リンクでは、前方参照をサポートする 3 つの呼び出しから成るパターンを使います。
addLink()はリンク識別子(int)を予約します。link($x, $y, $w, $h, $id)は、その id にひも付くクリック可能な矩形を配置します。setLink($id, $pageIndex, $y)は、id を宛先ページ(0 始まり)と Y にひも付けます。
まだ存在しないページへリンクする場合は、ステップ 2 の後にステップ 3 を呼び出します。宛先には明示的宛先形式を使います。ISO 32000-2 §12.3.2.2 は [page /XYZ left top zoom] を定義しており、null のコンポーネントはリーダーの現在の値を保持します。
外部リンクでは、int の代わりに URL 文字列 を 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 を宛先ページ(0 始まり)と Y にひも付けます。link(float $x, float $y, float $w, float $h, string|int $link): static— クリック可能な矩形を配置します。int の id は内部、文字列は外部 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は 0 始まりです。setLink($id, pageIndex: 2, …)は 3 ページ目を対象とします。ここでの 1 つずれ(off-by-one)エラーが最もよくある間違いです。link()での文字列と int の違い。 int はaddLink()から取得した内部宛先 id です。文字列は外部 URL です。誤った型を渡すと、エラーが出ないまま誤った種類のリンクが生成されます。- 予約したすべての id をひも付けてください。
addLink()の id をsetLink()でひも付けないと、宛先がありません。矩形はクリックできますが、何も起きません。save()の前にひも付けてください。 - クリック可能な領域はテキストではなく矩形です。
link()は明示的なx, y, w, hを受け取ります。表示されているテキストを覆うようにサイズを設定してください。エンジンが自動的にグリフを測定することはありません。 - 外部リンクは検証されません。 NextPDF は URI をそのまま保存します。ターゲットが resolve(解決)可能かどうか、安全かどうかは検証しません。解決はリーダー側で行われます。
パフォーマンス
「パフォーマンス」という見出しのセクション各リンクまたは注釈は、ページに 1 つの注釈辞書を追加します。コストは要素ごとに O(1) です。ページあたり数百個でも、2000 ms / 64 MB の予算内に十分収まります。
セキュリティに関する注意
「セキュリティに関する注意」という見出しのセクション外部リンクのターゲットはそのまま保存され、ライブラリではなくリーダーによって解決されます。ユーザーから提供された URL は信頼できないものとして扱ってください。スキームを許可リスト方式で制限してください。通常は https です。javascript: と file: は、link() に渡す前に拒否してください。注釈テキストはリーダーの UI に表示されるため、ユーザーが制御するノートの内容は長さを制限し、サニタイズしてください。このレシピでは、入力の解析やネットワークアクセスは発生しません。
| ステートメント | 仕様 | 条項 | リファレンス ID |
|---|---|---|---|
| リンク注釈は、宛先またはアクションへのハイパーテキストリンク | ISO 32000-2 | §12.5.6.5 | |
リンク注釈における Subtype は Link | ISO 32000-2 | §12.5.6.5 | |
URI アクションで必須となる UTF-8 ASCII 文字列の URI | 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 |
再現性プロファイル — 構造的。 トレーラーの /ID と日付アトムは、保存するたびに変化します。ハーネスはこれらのアトムを除去したうえで、qpdf で正規化した構造を比較します。このレシピは、NextPDF がその構造をどのように出力するかを説明します。ISO 32000-2 への包括的な適合を主張するものではありません。
商用コンテキスト
「商用コンテキスト」という見出しのセクション該当なし。リンクとテキスト注釈は Core の機能であり、Premium による制限はありません。