Document compare
Text diff (word / line), pixel-XOR visual diff, positioned text diff with PDF coordinates, and three pre-built UI layouts.
Text diff
import { diffWords, diffLines, changesOnly } from '@trupdf/viewer';
const segments = diffWords('hello world', 'hello brave new world');
// [
// { type: 'equal', text: 'hello' },
// { type: 'insert', text: ' brave new' },
// { type: 'equal', text: ' world' },
// ]
const onlyChanges = changesOnly(segments); // strips 'equal' segments
const byLine = diffLines(left, right);Each DiffSegment is { type: 'equal' | 'insert' | 'delete', text }.
diffWords tokenises to word boundaries so segments align cleanly;
diffLines operates per-line for page-by-page outputs.
Visual diff
import { visualDiff } from '@trupdf/viewer';
const out = visualDiff(leftCanvas, rightCanvas, {
threshold: 4, // per-channel delta below which pixels count as equal
contextOpacity: 0.15,
});
document.body.appendChild(out);Pixel-XOR with luminance-aware colouring — inputs are two canvases
(or ImageData) of the same dimensions, output is a new canvas:
- Red (
colorA, default[211, 47, 47]) — ink present only ina, the first / original document - Blue (
colorB, default[25, 118, 210]) — ink present only inb, the second document - Greyscale low-opacity — unchanged pixels, kept for context
Throws on dimension mismatch. O(width × height) — roughly 6 ms for a 1.5 MP page.
Positioned text diff
diffPositionedText diffs two token sequences extracted from page
text content and returns the tokens unique to each side — with their
PDF user-space positions intact, so highlights survive zoom and
rotation:
import { diffPositionedText } from '@trupdf/viewer';
// PositionedToken: { text, transform: [a, b, c, d, e, f], width, height }
const { onlyInA, onlyInB } = diffPositionedText(tokensA, tokensB);
for (const token of onlyInA) {
// token.transform holds the baseline origin at (e, f);
// token.width / token.height give the box in PDF user space.
}The diff is order-aware, so reordered paragraphs flag the moved tokens cleanly, and repeated words match by sequence position rather than as a set.
Highlight masks & registration overlay
Two lower-level primitives from @trupdf/compare power the pre-built
layouts and are available for custom UIs:
import { computeHighlightMask, composeImageOverlay } from '@trupdf/compare';
// Translucent mask of pixels present in `mine` but not `other`;
// composite it over the page canvas. Options: threshold, alpha, dilate.
const mask = computeHighlightMask(mineImageData, otherImageData, [211, 47, 47], {
dilate: 4, // marker-swipe look
});
// Registration-style overlay: shared content passes through unchanged,
// A-only ink tints red, B-only ink tints blue. Great for drawings,
// blueprints, and scans. Options: colorA, colorB, threshold, inkPaperThreshold.
const overlay = composeImageOverlay(imageA, imageB);Both take ImageData of matching dimensions and throw on mismatch.
UI layouts
CompareWorkspace
Full workspace with toolbar and a text / overlay compare toggle:
import { CompareWorkspace } from '@trupdf/viewer';
<CompareWorkspace
sourceA="/a.pdf"
sourceB="/b.pdf"
type="text" // 'text' | 'overlay'
labelA="v1"
labelB="v2"
/>;SideBySideCompare
Two synced viewers with per-pane diff highlights:
import { SideBySideCompare } from '@trupdf/viewer';
<SideBySideCompare
sourceA="/a.pdf"
sourceB="/b.pdf"
labelA="Original"
labelB="Revised"
initialSyncLocked // page/zoom/rotation lock, default true
initialHighlightsOn // diff overlay, default true
/>;Internally each pane mounts DocViewer with mode="compare" —
read-only chrome with annotation tools and AI panels suppressed. You
can pass the same prop when embedding viewers in your own compare UI.
OverlayCompare
Registration overlay blend — document A drives navigation, document B is loaded headlessly for the per-page diff:
import { OverlayCompare } from '@trupdf/viewer';
<OverlayCompare sourceA="/a.pdf" sourceB="/b.pdf" />;ComparePanel
Dockable panel that compares the live viewer document against a second source — mounts into the same chrome as the comments panel:
import { ComparePanel } from '@trupdf/viewer';
<ComparePanel engine={engine} source="/b.pdf" page={1} scale={1.5} />;Sync hooks
import { useSyncedViewers, useSyncedScroll, useCompareHighlights } from '@trupdf/viewer';
// Mirror page / zoom / rotation between two engines.
useSyncedViewers(engineA, engineB, { syncPage: true, syncScale: true, syncRotation: true });
// Mirror scroll position between the two pane elements.
useSyncedScroll(paneARef, paneBRef, { enabled: true });
// Paint per-pane diff-highlight masks onto each viewer's pages.
useCompareHighlights(engineA, engineB, { colorA: [211, 47, 47], colorB: [25, 118, 210], dilate: 4 });License gating
diffWords, diffLines, visualDiff, diffPositionedText,
computeHighlightMask, and composeImageOverlay each call
gate.require('compare') on entry. changesOnly is not gated —
it's a pure filter on an already-gated result. Trial tokens cannot
compare.