Aller au contenu

Ajouter des liens et annotations de texte

Cette recette ajoute trois éléments interactifs : un lien interne qui mène vers une autre page, un lien externe qui ouvre une URL et une annotation de texte, aussi appelée note autocollante. Elle fait suite à examples/17-links.php et examples/29-annotations.php.

Un lien cliquable est une annotation de lien ISO 32000-2 : un lien hypertexte vers une destination ou vers une action. Une note autocollante est une annotation de texte : elle affiche une icône lorsqu’elle est fermée et une fenêtre contextuelle lorsqu’elle est ouverte.

Fenêtre de terminal
composer require nextpdf/core:^3

Aucune extension optionnelle n’est requise. L’API des liens et des annotations est stable depuis la version 1.0.0 et fonctionne sur la matrice de backport 8.1–8.4.

Les liens internes utilisent un modèle en trois appels qui prend en charge les références anticipées :

  1. addLink() réserve un identifiant de lien (un int).
  2. link($x, $y, $w, $h, $id) place un rectangle cliquable lié à cet identifiant.
  3. setLink($id, $pageIndex, $y) lie l’identifiant à une page de destination (indexée à partir de zéro) et à une position Y.

Appelle l’étape 3 après l’étape 2 pour cibler une page qui n’existe pas encore. La destination est exprimée sous forme de destination explicite. ISO 32000-2 §12.3.2.2 définit [page /XYZ left top zoom], où un composant null conserve la valeur courante du lecteur.

Pour un lien externe, passe une chaîne d’URL à link() au lieu d’un int. NextPDF émet alors une action URI dont l’URI est une chaîne ASCII UTF-8 obligatoire. Le raccourci write($height, $text, $link) dessine du texte en ligne avec une URL attachée, et annotation($x, $y, $w, $h, $text) place une note autocollante de sous-type Text. ISO 32000-2 exige SubtypeLink pour une annotation de lien et définit l’icône de l’annotation de texte ainsi que le comportement de la fenêtre contextuelle.

La surface de l’API est générée automatiquement à partir de la PHPDoc. Cette recette s’appuie sur ces méthodes :

  • addLink(): int — réserve un identifiant de lien interne.
  • setLink(int $linkId, int $pageIndex = -1, float $y = 0): static — lie un identifiant à une page de destination (indexée à partir de zéro) et à un Y.
  • link(float $x, float $y, float $w, float $h, string|int $link): static — rectangle cliquable ; un identifiant int est interne, une chaîne est une URL externe.
  • write(float $height, string $text, string $link = ''): static — texte en ligne avec une URL optionnelle.
  • annotation(float $x, float $y, float $w, float $h, string $text, string $subtype = 'Text'): static — annotation de type note autocollante.
<?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');

Voici l’exemple complet prêt pour le harnais de test. Il respecte NEXTPDF_COOKBOOK_OUTPUT et n’introduit aucune entropie de son côté.

<?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 est indexé à partir de zéro. setLink($id, pageIndex: 2, …) cible la troisième page. Un décalage d’un cran ici est l’erreur la plus courante.
  • Chaîne ou int sur link(). Un int est un identifiant de destination interne issu de addLink(). Une chaîne est une URL externe. Si tu passes le mauvais type, tu obtiens le mauvais genre de lien sans déclencher d’erreur.
  • Lie chaque identifiant réservé. Un identifiant addLink() que tu ne lies jamais avec setLink() n’a aucune destination. Le rectangle est cliquable, mais inerte. Lie-le avant save().
  • La zone cliquable est le rectangle, pas le texte. link() prend des x, y, w, h explicites. Dimensionne-le pour couvrir le texte visible. Le moteur ne mesure pas les glyphes à ta place.
  • Les liens externes ne sont pas validés. NextPDF stocke l’URI tel quel. Il ne vérifie pas que la cible se résout ni qu’elle est sûre. C’est le lecteur qui la résout.

Chaque lien ou annotation ajoute un dictionnaire d’annotation à la page. Le coût est en O(1) par élément. Des centaines par page restent largement dans le budget de 2000 ms / 64 Mo.

Les cibles de liens externes sont stockées telles quelles et résolues par le lecteur, pas par la bibliothèque. Traite les URL fournies par l’utilisateur comme non fiables. Restreins le schéma au moyen d’une liste blanche, généralement https. Rejette javascript: et file: avant de les passer à link(). Le texte de l’annotation est affiché dans l’interface du lecteur ; borne donc sa longueur et assainis le contenu de la note contrôlé par l’utilisateur. Il n’y a ni analyse d’entrée ni accès réseau dans cette recette.

ÉnoncéSpécificationClausereference_id
Une annotation de lien est un lien hypertexte vers une destination ou vers une action.ISO 32000-2§12.5.6.5
Subtype vaut Link pour une annotation de lien.ISO 32000-2§12.5.6.5
L’URI d’une action URI est une chaîne ASCII UTF-8 obligatoire.ISO 32000-2§12.6.4.8
Une annotation de texte est une note autocollante (fermée = icône, ouverte = fenêtre contextuelle).ISO 32000-2§12.5.6.4
Destination explicite [page /XYZ left top zoom] ; null conserve la valeur courante.ISO 32000-2§12.3.2.2

Profil de reproductibilité — structurel. L’/ID du trailer et les atomes de date varient à chaque sauvegarde. Le harnais de test retire ces atomes puis compare la structure normalisée par qpdf. Cette recette décrit comment NextPDF produit la structure. Elle n’affirme pas la conformité ISO 32000-2 comme une garantie générale.

Sans objet. Les liens et les annotations de texte sont des fonctionnalités du noyau, sans verrou Premium.