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.
Annotation gallery
Section titled “Annotation gallery”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');📄 View the sample PDF · View the full script on GitHub ↗
Text markup
Section titled “Text markup”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');📄 View the sample PDF · View the full script on GitHub ↗
File attachments
Section titled “File attachments”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');📄 View the sample PDF · View the full script on GitHub ↗
Redactions
Section titled “Redactions”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');