Перейти к содержимому

Добавление ссылок и текстовых аннотаций

В этом рецепте показано, как добавить три интерактивных элемента: внутреннюю ссылку для перехода на другую страницу, внешнюю ссылку для открытия URL (Uniform Resource Locator) и текстовую аннотацию, также называемую заметкой. Он следует примерам examples/17-links.php и examples/29-annotations.php.

Кликабельная ссылка — это аннотация-ссылка по ISO 32000-2: гипертекстовая ссылка на адресат или действие. Заметка — это текстовая аннотация. В закрытом виде она отображается как значок, а в открытом — как всплывающее окно.

Окно терминала
composer require nextpdf/core:^3

Дополнительное расширение не требуется. API (application programming interface) для ссылок и аннотаций стабилен начиная с версии 1.0.0 и работает в матрице обратной совместимости 8.1–8.4.

Для внутренних ссылок используется шаблон из трёх вызовов, который поддерживает опережающие ссылки:

  1. addLink() резервирует идентификатор ссылки (int).
  2. link($x, $y, $w, $h, $id) размещает кликабельный прямоугольник, привязанный к этому id.
  3. setLink($id, $pageIndex, $y) привязывает id к странице назначения (нумерация с нуля) и координате Y.

Выполняйте шаг 3 после шага 2, если ссылаетесь на страницу, которой ещё не существует. Адресат задаётся как явный адресат. ISO 32000-2 §12.3.2.2 определяет [page /XYZ left top zoom], где компонент null оставляет текущее значение средства просмотра без изменений.

Для внешней ссылки передайте строку URL в link() вместо int. После этого NextPDF создаёт действие URI (Uniform Resource Identifier), у которого поле 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 — создать кликабельный прямоугольник; id типа int задаёт внутреннюю ссылку, строка — внешний 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, …) указывает на третью страницу. Ошибка на единицу здесь встречается чаще всего.
  • String или int в link(). int — это id внутреннего адресата, полученный от addLink(). Строка — это внешний URL. Если передать неверный тип, вы получите ссылку не того вида без какой-либо ошибки.
  • Привязывайте каждый зарезервированный id. id, полученный от addLink(), который вы так и не привязали через setLink(), не имеет адресата. Прямоугольник кликабелен, но бездействует. Привяжите его до save().
  • Кликабельная область — это прямоугольник, а не текст. link() принимает явные x, y, w, h. Задайте размер так, чтобы область покрывала видимый текст. Движок не измеряет глифы за вас.
  • Внешние ссылки не проверяются. NextPDF сохраняет URI дословно. Он не проверяет, что адресат разрешается или является безопасным. За его разрешение отвечает средство просмотра.

Каждая ссылка или аннотация добавляет на страницу один словарь аннотации. Затраты составляют O(1) на элемент. Сотни элементов на странице остаются в пределах бюджета 2000 ms / 64 MB с большим запасом.

Адресаты внешних ссылок сохраняются дословно и разрешаются средством просмотра, а не библиотекой. Считайте URL, предоставленные пользователем, недоверенными. Разрешайте только схемы из списка разрешённых — обычно это https. Отклоняйте javascript: и file: перед их передачей в link(). Текст аннотации отображается в пользовательском интерфейсе (UI) средства просмотра, поэтому ограничивайте длину и очищайте содержимое заметок, поступившее от пользователя. В этом рецепте не происходит ни разбора входных данных, ни доступа к сети.

УтверждениеСпецификацияРазделИдентификатор источника (reference_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

Профиль воспроизводимости — структурный. Атомы /ID трейлера и даты меняются при каждом сохранении. Тестовая обвязка удаляет эти атомы, а затем сравнивает структуру, нормализованную qpdf. Этот рецепт описывает, как NextPDF создаёт эту структуру. Он не заявляет о полном соответствии ISO 32000-2.

Неприменимо. Ссылки и текстовые аннотации — это возможности Core без ограничений Premium.