Introduction

The introductory post covers annotations briefly: highlights, underlines, sticky notes, and stamps, added through Page.AddHighlight(), Page.AddUnderline(), and similar methods. That is a small slice of what a Page actually supports — the annotation gallery in this library covers markup, freehand shapes, links with real navigation and form-submission actions, and searching, all reachable directly from Page.

This post goes through the rest of that surface: the shape and freehand annotation methods, what a Link annotation’s action can actually do, and the two different ways to search annotation content depending on whether the text you’re looking for lives in the annotation’s appearance or its /Contents comment.

Every annotation method returns a typed handle scoped to the annotation it just created, and every annotation on a page is readable afterward through page.Annotations.


What’s Included

Markup Annotations

Page.AddHighlight(), Page.AddUnderline(), Page.AddSquiggly(), and Page.AddStrikeOut() all take the same shape of options — a set of quad points marking the text region and an optional color — and all four return a MarkupAnnotation. The quad points are typically derived from a text search or from a known text rectangle.

const markup = (body: [number, number, number, number], sample: string,
  add: (quads: number[]) => void): void => {
  const yMid = (body[1] + body[3]) / 2 - 6;
  const textRect: [number, number, number, number] = [body[0] + 4, yMid, body[2] - 4, yMid + 16];
  page.AddText(sample, textRect[0], textRect[1], { fontSize: 12 });
  add(rectToQuads(textRect));
};

markup(cardBody, 'Highlight this phrase', (quads) =>
  page.AddHighlight({ quads, color: [1, 1, 0], contents: 'Yellow highlight' }));
markup(cardBody, 'Underline this phrase', (quads) =>
  page.AddUnderline({ quads, color: [0, 0, 1] }));
markup(cardBody, 'Squiggle this phrase', (quads) =>
  page.AddSquiggly({ quads, color: [1, 0.5, 0] }));
markup(cardBody, 'Strike this phrase out', (quads) =>
  page.AddStrikeOut({ quads, color: [1, 0, 0] }));

Notes and Free-Standing Text

Page.AddTextNote() adds a classic sticky-note icon that opens a comment popup when clicked, returning a TextAnnotation. Page.AddFreeText() instead draws its text directly on the page inside a bordered box, with its own font size, alignment, and fill color — useful for callouts that should be visible without opening anything.

page.AddTextNote({
  rect: [x, y, x + 20, y + 20], icon: 'Note', author: 'Reviewer',
  contents: 'This is a sticky-note annotation.',
});

page.AddFreeText({
  rect: [220, 60, 520, 96],
  contents: 'FreeText sample', fontSize: 10, align: 'center',
  fill: [1, 1, 0.8], width: 1,
});

Shapes and Freehand Ink

Page.AddSquare() and Page.AddCircle() draw a bordered rectangle or ellipse, each with an optional interior fill. Page.AddLine() draws a straight line between two points, with independently selectable arrowhead styles (startEnding/endEnding, e.g. 'OpenArrow'/'ClosedArrow') at each end. Page.AddPolygon() and Page.AddPolyline() draw closed and open multi-point shapes from a flat list of coordinates, and Page.AddInk() records one or more freehand pen strokes as paths of point lists.

page.AddSquare({ rect: [50, 400, 130, 435], color: [0.8, 0, 0], fill: [1, 1, 0.5], width: 2 });
page.AddCircle({ rect: [150, 400, 230, 435], color: [0, 0.5, 0], width: 2 });

page.AddLine({
  line: [260, 417, 420, 417],
  color: [0, 0, 0.7], width: 2,
  startEnding: 'OpenArrow', endEnding: 'ClosedArrow',
});

const stroke: number[] = [];
[-8, 6, -4, 10, -2, 8, -6].forEach((dy, i) => stroke.push(440 + i * 12, 417 + dy));
page.AddInk({ paths: [stroke], color: [0.6, 0, 0.6], width: 2 });

Page.AddLink() returns a LinkAnnotation whose action decides what happens on click: { type: 'uri', uri: '...' } opens an external URL, { type: 'goto', page: N } jumps to a page within the same document, and { type: 'submit', url: '...', format: 'html' } submits the current form-field values to an endpoint. An out-of-range goto page number or a malformed action object is rejected at creation time rather than silently accepted.

const uriLink = page.AddLink({
  rect: [10, 10, 100, 30], action: { type: 'uri', uri: 'https://example.com' },
});

const gotoLink = page.AddLink({
  rect: [10, 40, 100, 60], action: { type: 'goto', page: 2 },
});

const submitLink = page.AddLink({
  rect: [10, 70, 100, 90],
  action: { type: 'submit', url: 'https://example.com/post', format: 'html' },
});

// page.AddLink({ rect: [...], action: { type: 'goto', page: 99 } }) throws RangeError
// when 99 is out of range for the document.

Searching Annotation Content

Two different methods search annotation text, and they are deliberately disjoint: Page.SearchAnnotations() searches the text an annotation’s own appearance draws — the visible label on a FreeText or stamp, for example — while Page.SearchAnnotationText() searches the text carried in the annotation’s /Contents entry, such as a sticky note’s comment body. A match in one does not imply a match in the other.

page.SearchAnnotations('bravo');          // finds it if the annotation's drawn appearance contains it
page.SearchAnnotationText('bravo');       // finds it if the annotation's /Contents comment contains it

Annotations no longer needed can be removed directly with Page.RemoveAnnotation(), passing either the annotation handle or its underlying dictionary.


Quick Start

Install the package, then add a highlight, a link, and a sticky note to an existing page:

asposefoss/pdf is not yet published — build from source until it ships. See the project README for build instructions.
import { Document } from '@asposefoss/pdf';

const doc = Document.OpenFile('input.pdf');
const page = doc.Pages[0];

page.AddHighlight({ quads: [72, 700, 300, 700, 72, 715, 300, 715], color: [1, 1, 0] });

page.AddLink({
  rect: [72, 670, 200, 690], action: { type: 'uri', uri: 'https://example.com' },
});

page.AddTextNote({
  rect: [320, 670, 340, 690], icon: 'Note', author: 'Reviewer',
  contents: 'Please double-check this figure.',
});

doc.WriteTo('annotated.pdf');

Supported Formats

FormatExtensionReadWrite
PDFpdf
Markdownmd
SVGsvg
TIFFtiff
DOCXdocx
HTMLhtml
PNGpng
EPUBepub

Open Source & Licensing

Aspose.PDF FOSS for TypeScript is released under the MIT license, with source published on GitHub. There is no evaluation watermark, usage limit, or separate license file to manage, and the library may be used in commercial products without royalties.

The package is currently at version 0.1.0, reflecting active early-stage development. Node.js (>=22) is the only runtime requirement, and the package has no other third-party dependencies.


Getting Started