Skip to content

Annotations

Annotations live on a page and let viewers interact with the document — leaving notes, highlighting text, jumping to links, attaching files, or flagging content for redaction. Every annotation in the spec is exposed as a Phpdftk\Pdf\Core\Annotation\* class.

A single page exercising the six most common annotation types — sticky note, highlight, link, stamp, ink scribble, and file attachment.

use Phpdftk\Pdf\Core\Annotation\FileAttachmentAnnotation;
use Phpdftk\Pdf\Core\Annotation\HighlightAnnotation;
use Phpdftk\Pdf\Core\Annotation\InkAnnotation;
use Phpdftk\Pdf\Core\Annotation\LinkAnnotation;
use Phpdftk\Pdf\Core\Annotation\StampAnnotation;
use Phpdftk\Pdf\Core\Annotation\TextAnnotation;
use Phpdftk\Pdf\Core\FileSpec\EmbeddedFile;
use Phpdftk\Pdf\Core\FileSpec\FileSpec;
use Phpdftk\Pdf\Core\Font\StandardFont;
use Phpdftk\Pdf\Core\Font\Type1Font;
use Phpdftk\Pdf\Core\PdfArray;
use Phpdftk\Pdf\Core\PdfDictionary;
use Phpdftk\Pdf\Core\PdfName;
use Phpdftk\Pdf\Core\PdfNumber;
use Phpdftk\Pdf\Core\PdfReference;
use Phpdftk\Pdf\Core\PdfString;
use Phpdftk\Pdf\Writer\PdfWriter;
$writer = new PdfWriter();
$page = $writer->addPage();
$body = $writer->addFont(new Type1Font(StandardFont::Helvetica));
$bold = $writer->addFont(new Type1Font(StandardFont::HelveticaBold));
// Helper: build a /Rect array.
$rect = static fn (float $x1, float $y1, float $x2, float $y2) => new PdfArray([
new PdfNumber($x1), new PdfNumber($y1), new PdfNumber($x2), new PdfNumber($y2),
]);
// Helper: build a /QuadPoints array describing one rectangle.
$quad = static fn (float $x1, float $y1, float $x2, float $y2) => new PdfArray([
new PdfNumber($x1), new PdfNumber($y2), new PdfNumber($x2), new PdfNumber($y2),
new PdfNumber($x1), new PdfNumber($y1), new PdfNumber($x2), new PdfNumber($y1),
]);
$attach = static function (object $annot) use ($writer, $page): void {
$writer->register($annot);
$page->corePage()->annots[] = new PdfReference($annot->objectNumber);
};
// Page labels and descriptive text.
$cs = $writer->addContentStream($page);
$cs->beginText()->setFont($bold, 22)->moveTextPosition(72, 740)
->showText('Annotation Gallery')->endText();
$labels = [
[700, 'Sticky note — click the icon to expand.'],
[640, 'Highlighted text — visible in any PDF viewer.'],
[580, 'Click this link to open phpdftk.dev ->'],
[520, 'Approved stamp (below):'],
[440, 'Ink scribble (freehand annotation):'],
[340, 'Paperclip — embedded text file attached.'],
];
foreach ($labels as [$y, $text]) {
$cs->beginText()->setFont($body, 11)->moveTextPosition(72, $y)
->showText($text)->endText();
}
// 1. Sticky note (TextAnnotation) ----------------------------------------
$note = new TextAnnotation($rect(330, 695, 350, 715));
$note->contents = new PdfString('A reviewer left this note for you.');
$note->name = new PdfName('Note');
$note->open = false;
$note->c = new PdfArray([new PdfNumber(1), new PdfNumber(0.85), new PdfNumber(0.2)]);
$attach($note);
// 2. Highlight over an imagined text run ---------------------------------
$highlight = new HighlightAnnotation(
$rect(72, 632, 380, 652),
$quad(72, 632, 380, 652),
);
$highlight->contents = new PdfString('Highlighted passage');
$highlight->c = new PdfArray([new PdfNumber(1), new PdfNumber(1), new PdfNumber(0)]);
$attach($highlight);
// 3. Link annotation with a URI action -----------------------------------
$link = new LinkAnnotation($rect(72, 572, 280, 588));
$link->h = new PdfName('I'); // invert highlight on click
$link->a = new PdfDictionary([
'Type' => new PdfName('Action'),
'S' => new PdfName('URI'),
'URI' => new PdfString('https://phpdftk.dev'),
]);
$attach($link);
// 4. Approved stamp ------------------------------------------------------
$stamp = new StampAnnotation($rect(220, 500, 380, 540));
$stamp->name = new PdfName('Approved');
$stamp->contents = new PdfString('Approved by the editorial team.');
$attach($stamp);
// 5. Ink annotation — a hand-drawn wavy line -----------------------------
$inkPath = [];
for ($x = 72; $x <= 360; $x += 8) {
$inkPath[] = new PdfNumber($x);
$inkPath[] = new PdfNumber(400 + 10 * sin(($x - 72) / 18));
}
$ink = new InkAnnotation($rect(72, 380, 360, 420), new PdfArray([new PdfArray($inkPath)]));
$ink->contents = new PdfString('Hand-drawn ink scribble.');
$ink->c = new PdfArray([new PdfNumber(0.1), new PdfNumber(0.4), new PdfNumber(0.85)]);
$attach($ink);
// 6. File attachment — embed a small text file beside a paperclip --------
$embedded = new EmbeddedFile("Hello from a real embedded file!\n");
$writer->register($embedded);
$fileSpec = new FileSpec('hello.txt');
$fileSpec->ef = new PdfDictionary([
'F' => new PdfReference($embedded->objectNumber),
]);
$writer->register($fileSpec);
$attachment = new FileAttachmentAnnotation($rect(72, 310, 92, 330));
$attachment->name = new PdfName('Paperclip');
$attachment->contents = new PdfString('Click to download hello.txt');
$attachment->fs = new PdfReference($fileSpec->objectNumber);
$attach($attachment);
$writer->save('gallery.pdf');

Highlight, Underline, StrikeOut, and Squiggly all mark spans of text using /QuadPoints. Note that HighlightAnnotation takes the quad-points in its constructor, while the other three expose it as a property.

use Phpdftk\Pdf\Core\Annotation\HighlightAnnotation;
use Phpdftk\Pdf\Core\Annotation\SquigglyAnnotation;
use Phpdftk\Pdf\Core\Annotation\StrikeOutAnnotation;
use Phpdftk\Pdf\Core\Annotation\UnderlineAnnotation;
use Phpdftk\Pdf\Core\Font\StandardFont;
use Phpdftk\Pdf\Core\Font\Type1Font;
use Phpdftk\Pdf\Core\PdfArray;
use Phpdftk\Pdf\Core\PdfNumber;
use Phpdftk\Pdf\Core\PdfReference;
use Phpdftk\Pdf\Core\PdfString;
use Phpdftk\Pdf\Writer\PdfWriter;
$writer = new PdfWriter();
$page = $writer->addPage();
$body = $writer->addFont(new Type1Font(StandardFont::Helvetica))->getResourceName();
$bold = $writer->addFont(new Type1Font(StandardFont::HelveticaBold))->getResourceName();
// Page header.
$cs = $writer->addContentStream($page);
$cs->beginText()->setFont($bold, 22)->moveTextPosition(72, 740)
->showText('Text Markup Annotations')->endText();
// Four labeled lines, each one will get a markup overlay.
$lines = [
[680, 'Highlight', 'A highlight marks important text in yellow.'],
[620, 'Underline', 'Underline draws a line below the marked text.'],
[560, 'Strike-out', 'Strike-out crosses out text without removing it.'],
[500, 'Squiggly', 'Squiggly underlines for spelling or grammar.'],
];
foreach ($lines as [$y, $label, $text]) {
$cs->beginText()->setFont($bold, 11)->moveTextPosition(72, $y)
->showText($label . ':')->endText();
$cs->beginText()->setFont($body, 12)->moveTextPosition(160, $y)
->showText($text)->endText();
}
$rect = static fn (float $x1, float $y1, float $x2, float $y2) => new PdfArray([
new PdfNumber($x1), new PdfNumber($y1), new PdfNumber($x2), new PdfNumber($y2),
]);
$quad = static fn (float $x1, float $y1, float $x2, float $y2) => new PdfArray([
new PdfNumber($x1), new PdfNumber($y2), new PdfNumber($x2), new PdfNumber($y2),
new PdfNumber($x1), new PdfNumber($y1), new PdfNumber($x2), new PdfNumber($y1),
]);
$attach = static function (object $annot) use ($writer, $page): void {
$writer->register($annot);
$page->corePage()->annots[] = new PdfReference($annot->objectNumber);
};
// Apply one markup type per line at the line's baseline.
$markups = [
HighlightAnnotation::class => 680,
UnderlineAnnotation::class => 620,
StrikeOutAnnotation::class => 560,
SquigglyAnnotation::class => 500,
];
foreach ($markups as $class => $y) {
$r = $rect(160, $y - 2, 460, $y + 12);
$q = $quad(160, $y - 2, 460, $y + 12);
// Highlight requires quadPoints in its constructor; the others expose it as a property.
$annot = $class === HighlightAnnotation::class ? new $class($r, $q) : new $class($r);
if (property_exists($annot, 'quadPoints') && $annot->quadPoints === null) {
$annot->quadPoints = $q;
}
$annot->contents = new PdfString(str_replace('Annotation', '', substr($class, strrpos($class, '\\') + 1)));
$attach($annot);
}
$writer->save('markup.pdf');

A FileAttachmentAnnotation ties an EmbeddedFile stream and FileSpec to a clickable hotspot on the page. The file rides inside the PDF and can be saved out from any viewer.

use Phpdftk\Pdf\Core\Annotation\FileAttachmentAnnotation;
use Phpdftk\Pdf\Core\FileSpec\EmbeddedFile;
use Phpdftk\Pdf\Core\FileSpec\FileSpec;
use Phpdftk\Pdf\Core\Font\StandardFont;
use Phpdftk\Pdf\Core\Font\Type1Font;
use Phpdftk\Pdf\Core\PdfArray;
use Phpdftk\Pdf\Core\PdfName;
use Phpdftk\Pdf\Core\PdfNumber;
use Phpdftk\Pdf\Core\PdfReference;
use Phpdftk\Pdf\Core\PdfString;
use Phpdftk\Pdf\Writer\PdfWriter;
$writer = new PdfWriter();
$page = $writer->addPage();
$body = $writer->addFont(new Type1Font(StandardFont::Helvetica))->getResourceName();
$bold = $writer->addFont(new Type1Font(StandardFont::HelveticaBold))->getResourceName();
$cs = $writer->addContentStream($page);
$cs->beginText()->setFont($bold, 22)->moveTextPosition(72, 740)
->showText('File Attachment Annotations')->endText();
$cs->beginText()->setFont($body, 11)->moveTextPosition(72, 710)
->showText('Each paperclip below is a real embedded file. Click to download in any viewer.')
->endText();
$attachments = [
['hello.txt', 'Plain text greeting', "Hello from a real embedded file!\n"],
['readme.md', 'Markdown notes', "# Notes\n\n- The host PDF is the container.\n- This file rides inside.\n"],
['data.csv', 'CSV data table', "name,score\nAda,42\nGrace,73\nMargaret,99\n"],
];
$y = 660;
foreach ($attachments as [$filename, $label, $payload]) {
// 1) Embed the bytes as an EmbeddedFile stream.
$embedded = new EmbeddedFile($payload);
$writer->register($embedded);
// 2) Wrap the stream in a FileSpec that names the embedded file.
$fs = new FileSpec($filename);
$fs->desc = new PdfString($label);
$fs->attachEmbeddedFile(new PdfReference($embedded->objectNumber));
$writer->register($fs);
// 3) Place a FileAttachment annotation on the page that references the FileSpec.
$rect = new PdfArray([
new PdfNumber(72), new PdfNumber($y - 4),
new PdfNumber(92), new PdfNumber($y + 16),
]);
$annot = new FileAttachmentAnnotation($rect);
$annot->name = new PdfName('Paperclip');
$annot->contents = new PdfString($label);
$annot->fs = new PdfReference($fs->objectNumber);
$writer->register($annot);
$page->corePage()->annots[] = new PdfReference($annot->objectNumber);
// 4) Label the attachment so the page reads well even without hovering.
$cs->beginText()->setFont($bold, 11)->moveTextPosition(110, $y)
->showText($filename)->endText();
$cs->beginText()->setFont($body, 11)->moveTextPosition(220, $y)
->showText($label)->endText();
$y -= 36;
}
$writer->save('file-attachment.pdf');

A RedactAnnotation marks content as pending redaction. A redaction-capable viewer can then “apply” it — replacing the underlying content with the overlay text and fill colour you specified.

use Phpdftk\Pdf\Core\Annotation\RedactAnnotation;
use Phpdftk\Pdf\Core\Font\StandardFont;
use Phpdftk\Pdf\Core\Font\Type1Font;
use Phpdftk\Pdf\Core\PdfArray;
use Phpdftk\Pdf\Core\PdfNumber;
use Phpdftk\Pdf\Core\PdfReference;
use Phpdftk\Pdf\Core\PdfString;
use Phpdftk\Pdf\Writer\PdfWriter;
$writer = new PdfWriter();
$page = $writer->addPage();
$body = $writer->addFont(new Type1Font(StandardFont::Helvetica))->getResourceName();
$bold = $writer->addFont(new Type1Font(StandardFont::HelveticaBold))->getResourceName();
$cs = $writer->addContentStream($page);
$cs->beginText()->setFont($bold, 22)->moveTextPosition(72, 740)
->showText('Redaction Annotations')->endText();
$cs->beginText()->setFont($body, 11)->moveTextPosition(72, 710)
->showText('Pending redactions are marked on the page. A redaction-aware viewer can')
->endText();
$cs->beginText()->setFont($body, 11)->moveTextPosition(72, 694)
->showText('apply them, replacing the underlying content with the overlay below.')
->endText();
// Sensitive paragraph that we want to flag for redaction.
$lines = [
[640, 'Patient: John Doe — SSN 123-45-6789'],
[620, 'Date of birth: 1979-03-14'],
[600, 'Visit notes: routine checkup, all vitals nominal.'],
];
foreach ($lines as [$y, $text]) {
$cs->beginText()->setFont($body, 12)->moveTextPosition(72, $y)
->showText($text)->endText();
}
// Mark the SSN and DOB as pending redactions with black fill and "REDACTED" overlay.
$targets = [
['rect' => [220, 636, 350, 652], 'overlay' => 'REDACTED'],
['rect' => [200, 616, 330, 632], 'overlay' => 'REDACTED'],
];
foreach ($targets as $target) {
[$x1, $y1, $x2, $y2] = $target['rect'];
$rect = new PdfArray([
new PdfNumber($x1), new PdfNumber($y1),
new PdfNumber($x2), new PdfNumber($y2),
]);
$annot = new RedactAnnotation($rect);
$annot->quadPoints = new PdfArray([
new PdfNumber($x1), new PdfNumber($y2), new PdfNumber($x2), new PdfNumber($y2),
new PdfNumber($x1), new PdfNumber($y1), new PdfNumber($x2), new PdfNumber($y1),
]);
// Black fill so the redaction is visible before application.
$annot->ic = new PdfArray([new PdfNumber(0), new PdfNumber(0), new PdfNumber(0)]);
$annot->overlayText = new PdfString($target['overlay']);
$annot->contents = new PdfString('Pending redaction — apply in a redaction-capable viewer.');
$writer->register($annot);
$page->corePage()->annots[] = new PdfReference($annot->objectNumber);
}
$writer->save('redact.pdf');