Compare PDF documents ​
Starting in version 8.5.0, the Foxit PDF SDK for Web full package provides APIs to compare PDF documents. You can compare:
- Text
- Annotations
- Page objects (images, paths)
- Watermarks
When using the compare APIs:
- Provide two different documents as input
- Obtain a result document with detailed difference information
- Open the result document to review differences
A simple example ​
This section shows how to compare two documents with the compare APIs and control what appears in the result document. The examples assume an existing PDFViewer instance.
Load documents ​
First, load two documents with PDFViewer.loadPDFDocByFile or PDFViewer.loadPDFDocByHttpRangeRequest. Both load a PDF and return a PDFDoc without rendering it in the viewer.
javascript
const baseDoc = await pdfViewer.loadPDFDocByHttpRangeRequest({
range: {
url: '/assets/compare-base.pdf'
}
});
const otherDoc = await pdfViewer.loadPDFDocByHttpRangeRequest({
range: {
url: '/assets/compare-other.pdf'
}
});Start comparison ​
After loading, get each document id with PDFDoc.getId, then start the comparison.
javascript
const baseDocId = baseDoc.getId();
const otherDocId = otherDoc.getId();
const comparedDoc = await pdfViewer.compareDocuments(
baseDocId,
otherDocId,
{
// baseDoc file name, shown in the result document
baseFileName: 'baseFile.pdf',
// otherDoc file name, shown in the result document
otherFileName: 'otherFile.pdf',
// Result document file name
resultFileName: pdfViewer.i18n.t('comparison:resultFileName') || 'The result of comparison.pdf'
}
);Preview ​
javascript
const libPath = window.top.location.origin + '/lib';
const pdfui = new UIExtension.PDFUI({
viewerOptions: {
libPath: libPath,
jr: {
fontPath: 'http://webpdf.foxitsoftware.com/webfonts',
licenseSN: licenseSN,
licenseKey: licenseKey
}
},
renderTo: document.body,
appearance: UIExtension.appearances.adaptive,
addons: libPath + '/uix-addons/allInOne.js'
});
(async function () {
const pdfViewer = await pdfui.getPDFViewer();
const baseDoc = await pdfViewer.loadPDFDocByHttpRangeRequest({
range: {
url: '/assets/compare-base.pdf'
}
});
const otherDoc = await pdfViewer.loadPDFDocByHttpRangeRequest({
range: {
url: '/assets/compare-other.pdf'
}
});
const baseDocId = baseDoc.getId();
const otherDocId = otherDoc.getId();
const comparedDoc = await pdfViewer.compareDocuments(
baseDocId,
otherDocId,
{
baseFileName: 'baseFile.pdf',
otherFileName: 'otherFile.pdf',
resultFileName: pdfViewer.i18n.t('comparison:resultFileName') || 'The result of comparison.pdf'
}
);
const comparedDocFile = await comparedDoc.getFile();
pdfui.openPDFByFile(comparedDocFile);
})()json
{
"iframeOptions": {
"style": "height: 600px"
}
}compareDocuments parameters ​
Page ranges ​
Use the following parameters to specify page ranges in both documents:
javascript
pdfViewer.compareDocuments(
baseDocId,
otherDocId,
{
basePageRange: {
from: 0,
end: 2
},
otherPageRange: {
from: 1,
end: 3
},
options: {
// ... other options
}
}
)Page range notes
fromandendare the start and end page indexes- In the example, pages compared are:
- baseDoc:
[0, 1, 2] - otherDoc:
[1, 2, 3]
- baseDoc:
basePageRangeandotherPageRangemust specify the same number of pages
options parameter ​
options selects what to compare and how. Parameter descriptions:
javascript
{
// Whether to compare tables; default false
compareTable: false,
// Whether to detect page deletions and insertions; default false
detectPage
:
false,
// Marking line thickness (points)
lineThickness
:
{
delete
:
2,
insert
:
2,
replace
:
2
}
,
// Marking colors (0xRRGGBB, no alpha)
markingColor: {
delete
:
0xfa0505,
insert
:
0x149bff,
replace
:
0xffcc00
}
,
// Marking opacity
opacity: {
delete
:
100,
insert
:
100,
replace
:
100
}
,
// If true, compare text only; if false, also compare annotations, page objects, etc.
textOnly: false
}Example using these parameters:
html
<script>
const libPath = window.top.location.origin + '/lib';
const pdfui = new UIExtension.PDFUI({
viewerOptions: {
libPath: libPath,
jr: {
fontPath: 'http://webpdf.foxitsoftware.com/webfonts',
licenseSN: licenseSN,
licenseKey: licenseKey
}
},
renderTo: document.body,
appearance: UIExtension.appearances.adaptive,
addons: libPath + '/uix-addons/allInOne.js'
});
class ComparisonOptionsLayerComponent extends UIExtension.SeniorComponentFactory.createSuperClass({
template: `
<layer class="center fv__ui-comparison-options-dialog" @var.dialog="$component" style="width: 680px" append-to="body">
<layer-view class="fv__ui-comparison-dialog-body">
<div class="fv__ui-layout-row">
<div class="fv__ui-layout-col-1">
<fieldset class="fv__ui-comparison-fieldset">
<legend>comparison:options-dialog.include</legend>
<div class="fv__ui-comparison-fieldset-content">
<form-group label="comparison:options-dialog.compareTextOnly" direction="rtl">
<checkbox @model="dialog.currentOptions.textOnly" name="compareTextOnly"></checkbox>
</form-group>
<form-group label="comparison:options-dialog.compareTable" direction="rtl">
<checkbox @model="dialog.currentOptions.compareTable" name="compareTable"></checkbox>
</form-group>
<form-group label="comparison:options-dialog.detectPage" direction="rtl">
<checkbox @model="dialog.currentOptions.detectPage" name="detectPageDeletionsOrInserts"></checkbox>
</form-group>
</div>
</fieldset>
</div>
<div class="fv__ui-layout-col-1">
<fieldset class="fv__ui-comparison-fieldset">
<legend>comparison:options-dialog.markingColor</legend>
<div class="fv__ui-comparison-fieldset-content">
<form-group label="comparison:options-dialog.replaceObjects">
<inline-color-picker name="fv--comparison-options-replace-marking-color" @model="dialog.currentOptions.markingColor.replace|comparison:color"></inline-color-picker>
</form-group>
<form-group label="comparison:options-dialog.insertObjects">
<inline-color-picker name="fv--comparison-options-insert-marking-color" @model="dialog.currentOptions.markingColor.insert|comparison:color"></inline-color-picker>
</form-group>
<form-group label="comparison:options-dialog.deleteObjects">
<inline-color-picker name="fv--comparison-options-delete-marking-color" @model="dialog.currentOptions.markingColor.delete|comparison:color"></inline-color-picker>
</form-group>
</div>
</fieldset>
</div>
</div>
<div class="fv__ui-layout-row">
<div class="fv__ui-layout-col-1">
<fieldset class="fv__ui-comparison-fieldset">
<legend>comparison:options-dialog.opacity</legend>
<div class="fv__ui-comparison-fieldset-content fv__ui-comparison-options-checkbox-list">
<form-group label="comparison:options-dialog.replaceObjects">
<number type="number" min="0" max="100" step="1" suffix="%" @model="dialog.currentOptions.opacity.replace" name="fv--comparison-options-replace-opacity-replace"></number>
</form-group>
<form-group label="comparison:options-dialog.insertObjects">
<number type="number" min="0" max="100" step="1" suffix="%" @model="dialog.currentOptions.opacity.insert" name="fv--comparison-options-replace-opacity-insert"></number>
</form-group>
<form-group label="comparison:options-dialog.deleteObjects">
<number type="number" min="0" max="100" step="1" suffix="%" @model="dialog.currentOptions.opacity.delete" name="fv--comparison-options-replace-opacity-delete"></number>
</form-group>
</div>
</fieldset>
</div>
<div class="fv__ui-layout-col-1">
<fieldset class="fv__ui-comparison-fieldset">
<legend>comparison:options-dialog.lineThickness</legend>
<div class="fv__ui-comparison-fieldset-content fv__ui-comparison-options-checkbox-list">
<form-group label="comparison:options-dialog.replaceObjects">
<number type="number" min="1" max="12" step="1" @model="dialog.currentOptions.lineThickness.replace" name="fv--comparison-options-replace-lineThickness-replace"></number>
</form-group>
<form-group label="comparison:options-dialog.insertObjects">
<number type="number" min="1" max="12" step="1" @model="dialog.currentOptions.lineThickness.insert" name="fv--comparison-options-replace-lineThickness-insert"></number>
</form-group>
<form-group label="comparison:options-dialog.deleteObjects">
<number type="number" min="1" max="12" step="1" @model="dialog.currentOptions.lineThickness.delete" name="fv--comparison-options-replace-lineThickness-delete"></number>
</form-group>
</div>
</fieldset>
</div>
</div>
</layer-view>
<div class="fv__ui-comparison-dialog-footer fv__ui-layout-row">
<div class="fv__ui-layout-col-1 fv__ui-comparison-footer-buttons">
<xbutton text="dialog.ok" class="fv__ui-dialog-button fv__ui-dialog-ok-button" name="fv__ui-dialog-ok-button" @on.click="dialog.ok()"></xbutton>
<xbutton text="dialog.cancel" class="fv__ui-dialog-button fv__ui-dialog-cancel-button" name="fv__ui-dialog-cancel-button" @on.click="dialog.cancel()" @cannotBeDisabled></xbutton>
</div>
</div>
</layer>
`
}) {
static getName() {
return 'comparison-options-layer'
}
currentOptions = this.getDefaultOptions();
getDefaultOptions() {
return {
compareTable: false,
detectPage: false,
lineThickness: {
delete: 2,
insert: 2,
replace: 2
},
markingColor: {
delete: 0xfa0505,
insert: 0x149bff,
replace: 0xffcc00
},
opacity: {
delete: 100,
insert: 100,
replace: 100
},
textOnly: false
};
}
onOk(callback) {
this.onOkCallback = callback;
}
ok() {
if (typeof this.onOkCallback == 'function') {
this.onOkCallback(this.currentOptions);
}
this.hide();
this.currentOptions = this.getDefaultOptions();
}
cancel() {
this.hide();
this.currentOptions = this.getDefaultOptions();
}
}
UIExtension.modular.root().registerComponent(ComparisonOptionsLayerComponent);
class LegendLayerComponent extends UIExtension.SeniorComponentFactory.createSuperClass({
template: `
<layer class="fv__ui-comparison-legend-layer">
<header class="fv__ui-layout-row fv__ui-comparison-legend-header" name="fv__ui-comparison-legend-header">
<h2 class="fv__ui-layer-col-1 fv__ui-comparison-legend-title">comparison:toolbar.legend</h2>
<button class="fv__ui-layer-col-1 fv__ui-comparison-legend-close-btn" @on.native.click="hide()"></button>
</header>
test
<dl class="fv__ui-comparison-legend-item fv__ui-comparison-legend-replace-item" name="fv__ui-comparison-legend-replace-item">
<dt >ABC</dt>
<dd>comparison:toolbar.replace</dd>
</dl>
<dl class="fv__ui-comparison-legend-item fv__ui-comparison-legend-insert-item" name="fv__ui-comparison-legend-insert-item">
<dt>ABC<span class="fv__ui-comparison-legend-item-insert-mark"></span></dt>
<dd>comparison:toolbar.insert</dd>
</dl>
<dl class="fv__ui-comparison-legend-item fv__ui-comparison-legend-delete-item" name="fv__ui-comparison-legend-delete-item">
<dt>ABC<span class="fv__ui-comparison-legend-item-delete-mark"></span></dt>
<dd>comparison:toolbar.delete</dd>
</dl>
</layer>`
}) {
static getName() {
return 'legend-layer'
}
constructor(...args) {
super(...args);
}
show(options) {
super.show();
const colorConvertor = UIExtension.PDFViewCtrl.shared.colorConvertor;
this.element.querySelector(".fv__ui-comparison-legend-replace-item dt").style.textDecorationColor = colorConvertor(options.markingColor.replace, "#");
this.element.querySelector(".fv__ui-comparison-legend-insert-item dt").style.textDecorationColor = colorConvertor(options.markingColor.insert, "#");
this.element.querySelector(".fv__ui-comparison-legend-insert-item .fv__ui-comparison-legend-item-insert-mark").style.color = colorConvertor(options.markingColor.insert, "#");
this.element.querySelector(".fv__ui-comparison-legend-delete-item dt").style.textDecorationColor = colorConvertor(options.markingColor.delete, "#");
this.element.querySelector(".fv__ui-comparison-legend-delete-item .fv__ui-comparison-legend-item-delete-mark").style.color = colorConvertor(options.markingColor.delete, "#");
}
}
const module = UIExtension.modular.module("comparison");
const registry = module.getRegistry();
registry.registerComponent(LegendLayerComponent, true);
(async function () {
const root = await pdfui.getRootComponent();
root.append('<comparison-options-layer visible>');
const pdfViewer = await pdfui.getPDFViewer();
const baseDoc = await pdfViewer.loadPDFDocByHttpRangeRequest({
range: {
url: '/assets/compare-base.pdf'
}
});
const otherDoc = await pdfViewer.loadPDFDocByHttpRangeRequest({
range: {
url: '/assets/compare-other.pdf'
}
});
const baseDocId = baseDoc.getId();
const otherDocId = otherDoc.getId();
const optionsLayer = root.querySelector('@comparison-options-layer');
optionsLayer.onOk(async options => {
const comparedDoc = await pdfViewer.compareDocuments(
baseDocId,
otherDocId,
{
baseFileName: 'baseFile.pdf',
otherFileName: 'otherFile.pdf',
resultFileName: pdfViewer.i18n.t('comparison:resultFileName') || 'The result of comparison.pdf',
options: options
}
);
console.log(options);
const comparedDocFile = await comparedDoc.getFile();
pdfui.openPDFByFile(comparedDocFile).then(_ => {
const legendLayer = root.querySelector('@comparison:legend-layer')
legendLayer.show(options);
});
});
})()
</script>json
{
"iframeOptions": {
"style": "height: 600px"
}
}Comparison progress ​
NOTE
Large documents can take a long time to produce a result. The fourth argument to compareDocuments can be a callback that reports progress.
javascript
pdfViewer.compareDocuments(
baseDocId,
otherDocId,
{
...
},
(currentRate) => {
console.log(currentRate);
}
)currentRate ranges from 0 to 100. Use it to update a progress bar in your UI.
Example with a progress bar:
html
<script>
const libPath = window.top.location.origin + '/lib';
const pdfui = new UIExtension.PDFUI({
viewerOptions: {
libPath: libPath,
jr: {
fontPath: 'http://webpdf.foxitsoftware.com/webfonts',
licenseSN: licenseSN,
licenseKey: licenseKey
}
},
renderTo: document.body,
appearance: UIExtension.appearances.adaptive,
addons: libPath + '/uix-addons/allInOne.js'
});
class ProgressBarComponent extends UIExtension.SeniorComponentFactory.createSuperClass({
template: `<layer class="center" visible @var.self="$component">
@{self.currentRate + '%'}
</layer>`
}) {
static getName() {
return 'progress-bar-layer'
}
currentRate = 0;
setCurrentRate(rate) {
this.currentRate = rate;
this.digest();
if (rate >= 100) {
setTimeout(() => {
this.hide();
}, 500);
}
}
}
UIExtension.modular.root().registerComponent(ProgressBarComponent);
(async function () {
const pdfViewer = await pdfui.getPDFViewer();
const baseDoc = await pdfViewer.loadPDFDocByHttpRangeRequest({
range: {
url: '/assets/compare-base.pdf'
}
});
const otherDoc = await pdfViewer.loadPDFDocByHttpRangeRequest({
range: {
url: '/assets/compare-other.pdf'
}
});
const baseDocId = baseDoc.getId();
const otherDocId = otherDoc.getId();
const rootComponent = await pdfui.getRootComponent();
rootComponent.append('<progress-bar-layer>');
const comparedDoc = await pdfViewer.compareDocuments(
baseDocId,
otherDocId,
{
baseFileName: 'baseFile.pdf',
otherFileName: 'otherFile.pdf',
resultFileName: pdfViewer.i18n.t('comparison:resultFileName') || 'The result of comparison.pdf'
},
currentRate => {
rootComponent.querySelector('@progress-bar-layer').setCurrentRate(currentRate);
}
);
const comparedDocFile = await comparedDoc.getFile();
pdfui.openPDFByFile(comparedDocFile);
})()
</script>json
{
"iframeOptions": {
"style": "height: 600px"
}
}Detect a comparison result document ​
The dictionary of a PDF produced by PDFViewer.compareDocuments can identify a comparison result. In the SDK, use PDFDoc.isCompareDoc().
javascript
pdfViewer.eventEmitter.on(PDFViewCtrl.ViewerEvent.openFileSuccess, doc => {
doc.isCompareDoc();
})NOTE
A typical comparison result document includes dictionary data where object (1 0) has /PieceInfo pointing to object (244 0), which references /ComparePDF. You can use that to tell whether a document is a comparison result.
text
1 0 obj
<</AcroForm 110 0 R/Pages 2 0 R/ViewerPreferences <<>>/OCProperties <</OCGs [62 0 R 63 0 R 64 0 R 65 0 R 66 0 R 67 0 R 68 0 R]/D <</Order [62 0 R 63 0 R 64 0 R 65 0 R 66 0 R 67 0 R 68 0 R]/ON [62 0 R 63 0 R 64 0 R]/OFF [65 0 R 66 0 R 67 0 R 68 0 R]>>>>/Names 367 0 R/PageLayout(TwoColumnLeft)/Type/Catalog/PieceInfo 244 0 R>>
endobj
...
244 0 obj
<</ComparePDF 235 0 R>>
endobj
...
235 0 obj
<</Private 236 0 R>>
endobj
236 0 obj
<</Differences 237 0 R>>
endobj
237 0 obj
<</Nums [1 238 0 R 2 239 0 R 3 240 0 R 4 241 0 R 5 242 0 R 6 243 0 R]>>
endobj
...