TPTruPDF

Annotations

50+ tools across annotation, shapes, insert, measure, redact, edit, fill-and-sign, and form groups. Full XFDF round-trip, undo/redo, replies and status, presets, clipboard, multi-page selection, stylus input.

Tool catalogue

Use ToolController.setTool(kind) (or click the toolbar) to switch between:

Text markup (operate on text runs)

  • highlight — yellow by default, color configurable
  • underline
  • strikethrough
  • squiggly

Free text

  • sticky — Post-it note with icon (Note / Comment / Help / Insert / Key / NewParagraph / Paragraph)
  • freetext — plain textbox
  • callout — textbox with arrow leader (FreeTextCallout PDF type)
  • typewriter — type-as-you-go single-line text
  • caret — insertion-point marker

Shapes

  • rectangle, ellipse
  • polygon, polyline — vertex list
  • line, arrow, arc
  • cloud — polygon with cloud border (intensity 1 or 2; emitted via /BE per ISO 32000-2 §12.5.4 so Chrome / Edge render correctly)

Ink / handwritten

  • freehand — pressure-aware pencil stroke
  • freehand-highlight — transparent highlight stroke
  • handwritten-signature
  • pencil — variant (thin stroke)

Stamps

  • rubber-stamp — built-in (Approved, Draft, Urgent, Confidential, …) or custom labels
  • image-stamp — arbitrary PNG / JPEG
  • date-stamp — auto-filled

File & media

  • file-attachment/FileAttachment annotation with icon (Graph / Paperclip / PushPin / Tag)
  • sound — embedded audio

Quick fill-and-sign

  • fs-cross, fs-tick, fs-dot, fs-date

Measurement (calibrated)

  • distance — 2-point with units label
  • arc-measurement — curved 2-point
  • perimeter — polyline length
  • area, rectangle-area, ellipse-area
  • count — incremental tally

Redaction (Phase 8)

  • redact-area — drag a rectangle to mark
  • redact-text — mark text run; burned later by RedactionEngine

Form widgets

  • signature — signature field
  • form-text, form-checkbox, form-radio, form-combobox, form-listbox, form-signature

Other

  • select, pan, eraser, text-select
  • crop, snipping, new-image, add-paragraph, edit-content

AnnotationManager

The single source of truth. UI subscribes to events and derives view state — never duplicates records.

import { AnnotationManager, deserializeXFDF } from '@trupdf/viewer';
 
const m = new AnnotationManager({ defaultAuthor: 'Reviewer' });
 
const id = m.add({
  kind: 'highlight',
  page: 1,
  rect: { x: 100, y: 200, width: 80, height: 14 },
  quadPoints: [...],
  color: { r: 1, g: 0.85, b: 0 },
});
 
m.update(id, { color: { r: 1, g: 0, b: 0 } });
m.undo();   m.redo();   m.remove(id);
 
m.on('annotation.added',   (a) => /* … */);
m.on('annotation.updated', (a) => /* … */);
m.on('annotation.removed', ({ id }) => /* … */);

XFDF round-trip

import { serializeXFDF, deserializeXFDF } from '@trupdf/viewer';
 
const xml = serializeXFDF(m.list());
const records = deserializeXFDF(xml);

The emitter uses the Adobe-namespaced root with a trupdf: extension namespace for fields XFDF doesn't model (status, customData, font overrides, measurement units). Other compliant readers ignore the extension fields and read the spec-compliant /Annot dict; reading TruPDF-saved files in TruPDF preserves everything.

PDF spec compliance (ISO 32000-2 §12.5) is enforced so a TruPDF-authored annotation renders identically in Acrobat, Chrome, Firefox, Edge, and Apple Preview. Every /BS is paired with the legacy /Border array, cloud borders go in /BE not /BS, /IC is always emitted for FreeText / Polygon / Line, and /LE follows the spec form per annotation kind.

JSON wire format (REST APIs)

import { serializeJSON, deserializeJSON } from '@trupdf/viewer';
 
const json = serializeJSON(m.list());
const back = deserializeJSON(json);

Same record shape as the in-memory AnnotationRecord discriminated union.

Presets

PresetStore lets users save and re-apply tool styles:

import { PresetStore } from '@trupdf/viewer';
 
const presets = new PresetStore();
presets.add({ name: 'My yellow highlight', kind: 'highlight', style: {…} });
presets.apply(toolController, 'My yellow highlight');

Clipboard

import { AnnotationClipboard } from '@trupdf/viewer';
 
const cb = new AnnotationClipboard(manager);
cb.copy(selectedIds);
cb.paste({ pageOffset: 1 }); // paste on next page
cb.pasteOption({ target: 'all-pages' });

Replies & status

Replies thread via inReplyTo. Status uses the XFDF state model (accepted, rejected, cancelled, completed, none).

m.add({ kind: 'reply', parentId: '…', author: 'Alice', contents: 'Agreed' });
m.setStatus(id, 'accepted');

Measurement

Calibrate once, then every measurement annotation on that document uses the same units-per-point factor.

import {
  calibrate,
  measureDistance,
  measurePerimeter,
  measureArea,
} from '@trupdf/viewer';
 
const factor = calibrate(pointA, pointB, 12, 'in'); // 12 inches between the two points
measureDistance(p1, p2, factor);
measurePerimeter(points, factor, /* closed */ false);
measureArea(polygon, factor); // squared units

Stylus input

bindStylusInput(host, opts) enables Apple Pencil / S Pen / Wacom pressure, tilt, twist, coalesced events, and palm rejection. applyPressureCurve ships soft / linear / firm curves.

import { bindStylusInput, applyPressureCurve } from '@trupdf/viewer';
 
bindStylusInput(canvas, {
  pressureCurve: applyPressureCurve('firm'),
  palmRejection: true,
  coalesce: true,
});

Selection styling

Override the on-canvas selection chrome:

<DocViewer
  source="…"
  selectionStyle={{
    handleColor: '#ff6b35',
    handleShape: 'circle',
    handleSize: 12,
    borderColor: '#ff6b35',
    padding: 4,
    showRotationHandle: true,
  }}
/>

Quick menus

Both menus accept a subset to show — pass [] to hide entirely.

// Read-only review build: only commenting.
<DocViewer source={src} quickMenuItems={['comment']} />
 
// Read-only build: only text copy.
<DocViewer source={src} textSelectionMenuItems={['copy']} />

Events emitted

Via the AnnotationManager event bus AND the SDK bus:

EventPayload
annotation.added{ id, kind, page }
annotation.updated{ id, kind, page }
annotation.removed{ id }
annotations.clearedvoid
tool.changed{ tool: ToolKind }
selection.changed{ ids: string[] }