Pop PDF
Build

Links & Annotations

An annotation is a clickable rectangle laid over a page, and Page places one with addUrl() or addLink() the same way addImage() places an image. Both annotation types share the same border, radius and dash settings, covered once in Border Appearance rather than twice.

Linking to a URL#

Annotation\Url takes a width, a height and a destination URL, in that order:

PHP
use Pop\Pdf\Document;
use Pop\Pdf\Document\Page;
use Pop\Pdf\Document\Page\Annotation\Url;
use Pop\Pdf\Pdf;

$page = new Page(Page::LETTER);
$url  = new Url(120, 20, 'https://example.com/report');
$page->addUrl($url, x: 72, y: 700);

Pdf::writeToFile(new Document($page), filename: __DIR__ . '/report.pdf');

addUrl($url, $x, $y) places the rectangle's bottom left corner at $x, $y; both default to 0. The compiled annotation dictionary carries the URL exactly as passed:

TEXT
/Type /Annot
/Subtype /Link
/Rect [72 700 192 720]
/Border [0 0 0]
/A <</S /URI /URI (https://example.com/report)>>

setUrl() and getUrl() change and read the destination after construction.

addUrl() returns the page, so a page carries as many URL links as it needs, each added in the same chain:

PHP
use Pop\Pdf\Document\Page;
use Pop\Pdf\Document\Page\Annotation\Url;

$page = new Page(Page::LETTER);
$page->addUrl(new Url(150, 20, 'https://example.com/docs'), x: 72, y: 700)
     ->addUrl(new Url(150, 20, 'https://example.com/support'), x: 72, y: 660);

Linking Within the Document#

Annotation\Link takes a width, a height and an $xTarget, $yTarget pair — the point on the destination page the reader lands on:

PHP
use Pop\Pdf\Document;
use Pop\Pdf\Document\Page;
use Pop\Pdf\Document\Page\Annotation\Link;
use Pop\Pdf\Pdf;

$page1 = new Page(Page::LETTER);
$page2 = new Page(Page::LETTER);

$link = new Link(120, 20, 100, 400);
$link->setPageTarget(2);
$page1->addLink($link, x: 72, y: 660);

$doc = new Document();
$doc->addPage($page1);
$doc->addPage($page2);

Pdf::writeToFile($doc, filename: __DIR__ . '/linked.pdf');

setPageTarget() takes the destination page's 1-based position in the document — 2 here means the second page added, regardless of that page's own index. Reading the compiled annotation back confirms it: the link sits in page one's /Annots array, and its /Dest entry points at page two's own object, not page one's:

TEXT
/Dest [5 0 R /XYZ 100 400 0]

Leave setPageTarget() uncalled and the link targets the page it's added to — the same document without the setPageTarget(2) line points /Dest at page one's own object instead, turning the link into an in-page jump.

setXTarget() and setYTarget() set the point the destination page scrolls to, matching the constructor's third and fourth arguments. setZTarget() sets the destination zoom level and defaults to 0, which under the PDF /XYZ format means "leave the reader's current zoom unchanged" rather than setting a zoom of zero:

PHP
use Pop\Pdf\Document\Page\Annotation\Link;

$link = new Link(120, 20, 100, 400);
$link->setZTarget(1.5);
echo $link->getZTarget(); // 1.5

getXTarget(), getYTarget(), getZTarget() and getPageTarget() read the four values back. A table of contents page is the usual home for several Link annotations at once, one per entry, each with its own setPageTarget() pointing further into the document.

Sizing the Clickable Area#

The width and height passed to Annotation\Url or Annotation\Link's constructor set the clickable rectangle's size, and setWidth() / setHeight() change it afterward — both are inherited from AbstractAnnotation, so they work identically on either annotation type:

PHP
use Pop\Pdf\Document\Page\Annotation\Url;

$url = new Url(120, 20, 'https://example.com/report');
$url->setWidth(200)->setHeight(30);
echo $url->getWidth() . ' x ' . $url->getHeight(); // 200 x 30

The rectangle's origin is the $x, $y passed to addUrl() or addLink(), defaulting to 0; width and height extend up and to the right from there, so a 120 by 20 annotation added at 72, 700 produces [72, 700, 192, 720] — x, y plus x + width, y + height. There is no default width or height; both are required constructor arguments.

Border Appearance#

setBorderWidth(), setHRadius(), setVRadius(), setDashLength() and setDashGap() all default to 0, and a 0 border width draws nothing — an annotation left at its defaults has no visible outline. Annotation\Link inherits the same five setters, so a link between pages carries a border exactly as a URL link does:

TEXT
/Border [6 6 2]

is what setBorderWidth(2)->setHRadius(6)->setVRadius(6) on a Link compiles to — the same [hRadius vRadius width] shape a Url annotation produces. Setting a width is what makes either one's rectangle appear:

PHP
use Pop\Pdf\Document\Page\Annotation\Url;

$url = new Url(150, 40, 'https://example.com/a');
$url->setBorderWidth(3);

setHRadius() and setVRadius() write a horizontal and vertical corner radius into the same border definition, but rounding the corners is left to the reader displaying the PDF — a border rendered by this documentation's toolchain stays square-cornered at any radius, so treat the radii as data a capable reader can act on rather than a guaranteed visual.

setDashLength() and setDashGap() turn a solid border into a dashed one, and both have to be set together — setting only one leaves the border solid. Set both, along with a border width, and the rectangle renders as alternating dashes and gaps:

PHP
use Pop\Pdf\Document\Page\Annotation\Url;

$url = new Url(150, 40, 'https://example.com/b');
$url->setBorderWidth(2)->setDashLength(6)->setDashGap(3);

The compiled border definition writes the pair as [dashGap dashLength] — [3 6] for the call above — so the gap comes first in the emitted array even though setDashLength() is called first in the chain:

TEXT
/Border [0 0 2 [3 6]]

getBorderWidth(), getHRadius(), getVRadius(), getDashLength() and getDashGap() read every value back, in the order you set them rather than the order the compiled array writes them in.

Leaving every border setter at its default is the common case. Reach for setBorderWidth() when the clickable area has nothing under it and needs its own visible edge.

See Also#