TPTruPDF

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 in a, the first / original document
  • Blue (colorB, default [25, 118, 210]) — ink present only in b, 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.