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 configurableunderlinestrikethroughsquiggly
Free text
sticky— Post-it note with icon (Note / Comment / Help / Insert / Key / NewParagraph / Paragraph)freetext— plain textboxcallout— textbox with arrow leader (FreeTextCalloutPDF type)typewriter— type-as-you-go single-line textcaret— insertion-point marker
Shapes
rectangle,ellipsepolygon,polyline— vertex listline,arrow,arccloud— polygon with cloud border (intensity 1 or 2; emitted via/BEper ISO 32000-2 §12.5.4 so Chrome / Edge render correctly)
Ink / handwritten
freehand— pressure-aware pencil strokefreehand-highlight— transparent highlight strokehandwritten-signaturepencil— variant (thin stroke)
Stamps
rubber-stamp— built-in (Approved, Draft, Urgent, Confidential, …) or custom labelsimage-stamp— arbitrary PNG / JPEGdate-stamp— auto-filled
File & media
file-attachment—/FileAttachmentannotation 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 labelarc-measurement— curved 2-pointperimeter— polyline lengtharea,rectangle-area,ellipse-areacount— incremental tally
Redaction (Phase 8)
redact-area— drag a rectangle to markredact-text— mark text run; burned later by RedactionEngine
Form widgets
signature— signature fieldform-text,form-checkbox,form-radio,form-combobox,form-listbox,form-signature
Other
select,pan,eraser,text-selectcrop,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 unitsStylus 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:
| Event | Payload |
|---|---|
annotation.added | { id, kind, page } |
annotation.updated | { id, kind, page } |
annotation.removed | { id } |
annotations.cleared | void |
tool.changed | { tool: ToolKind } |
selection.changed | { ids: string[] } |