# # Compare PDF Files

Starting from version 8.5.0, Foxit PDF SDK for Web provides APIs to compare PDF files in the full package, which can compare the differences between text, annotations, page objects (images, paths), and watermarks in two PDF files. In general, to compare PDF files using the APIs, first input two different documents, then return a result document with detailed difference information, finally open the result document to intuitively see the difference information. Following will explain the usage of the APIs in detail.

## # APIs Preview

https://developers.foxit.com/developer-hub/document/developer-guide-pdf-sdk-web
(opens new window)

## # A Simple Example

In this section, we will show you how to compare two documents and control the content display in the result document. The following example will be done based on a created `PDFViewer` instance.

### # Load documents

Before you start, load two documents using `PDFViewer.loadPDFDocByFile`
(opens new window) or `PDFViewer.loadPDFDocByHttpRangeRequest`
(opens new window). These two APIs are used to load PDF documents, and then return a `PDFDoc` object, without rendering documents on the view.

    const baseDoc = await pdfViewer.loadPDFDocByHttpRangeRequest({
        range: {
            url: '/assets/compare-base.pdf'
        }
    });
    const otherDoc = await pdfViewer.loadPDFDocByHttpRangeRequest({
        range: {
            url: '/assets/compare-other.pdf'
        }
    });

### # Start to compare documents

After loading the documents, you just need to get the id values of the documents through `PDFDoc.getId`
(opens new window), and then you can start to compare documents.

    const baseDocId = baseDoc.getId();
    const otherDocId = otherDoc.getId();

    const comparedDoc = await pdfViewer.compareDocuments(
        baseDocId,
        otherDocId,
        {
            // The file name of baseDoc, which will be displayed in the result document.
            baseFileName: 'baseFile.pdf',
            // The file name of otherDoc, which will be displayed in the result document.
            otherFileName: 'otherFile.pdf',
            // The file name of the result document.
            resultFileName: pdfViewer.i18n.t('comparison:resultFileName') || 'The result of comparison.pdf'
        }
    );

Preview of the effect:

    <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'
        });
        (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);
        })()
    </script>

    {
        "iframeOptions": {
            "style": "height: 600px"
        }
    }

## # The Parameters of compareDocuments method

### # Page range

You can specify the page range of the two documents to be compared through the following two parameters:

    pdfViewer.compareDocuments(
        baseDocId,
        otherDocId,
        {
            // ...
            basePageRange: {
                from: 0,
                end: 2
            },
            otherPageRange: {
                from: 1,
                end: 3
            },
            options: {
                // ...
            }
        }
    )

Both `basePageRange` and `otherPageRange` objects should include `from` and `end` properties which represent the starting and ending page indexes of the pages to be compared. In the above example code, the final pages to be compared are: baseDoc: `[0, 1, 2]`, otherDoc: `[1, 2, 3]`. Please note that the number of pages specified in the `basePageRange` and `otherPageRange` should be same.

### # options parameters

options are used to specify the objects that need to be compared and the comparison method. The parameters are explained as follows:

    // Whether to compare tables, default is false, which means not to compare.
    compareTable: false,
    // Whether to detect page deletions and insertions, default is false, which means not to detect.
    detectPage: false,
    // The width of the outline border of the marker, and the unit is point.
    lineThickness: {
        delete: 2,
        insert: 2,
        replace: 2
    },
    // This parameter is used to set the marker color for different types of differences. The format of marker color is 0xRRGGBB without transparent channel, and the transparency needs to be specified in opacity.
    markingColor: {
        delete: 0xfa0505,
        insert: 0x149bff,
        replace: 0xffcc00
    },
    // This parameter is used to set the transparency of different types of differences.
    opacity: {
        delete: 100,
        insert: 100,
        replace: 100
    },
    // Whether only to compare text differences. If the value is true, only compare the text differences, otherwise compare the annotations, page oobjects (include text), and so on.
    textOnly: false

Following is an example with using the above parameters:

    <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,"#");
            }
        }
        UIExtension.modular.root().registerComponent(LegendLayerComponent);

        (async function() {
            const root = await pdfui.getRootComponent();
            root.append('<comparison-options-layer visible>');
            root.append('<legend-layer>');

            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('@legend-layer')
                    legendLayer.show(options);
                });
            });
        })()
    </script>

    {
        "iframeOptions": {
            "style": "height: 600px"
        }
    }

### # The progress of comparing documents

When the documents that need to be compared are large, it will take more time to generate the result document. At this time, the fourth parameter of the `compareDocuments` method can accept a callback function to get the processing progress information:

    pdfViewer.compareDocuments(
        baseDocId,
        otherDocId,
        {
            ...
        },
        (currentRate) => {
            console.log(currentRate);
        }
    )

The range of values for `currentRate` is from 0 to 100. You can use this value to update the progress bar on the UI.

An example with progress bar:

    <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>

    {
        "iframeOptions": {
            "style": "height: 600px"
        }
    }

## # Determine whether a document is a comparison result document

The dictionary information of the PDF file generated by the `PDFViewer.compareDocuments` interface can be used to determine whether a document is a comparison result document. In the SDK, it uses `PDFDoc.isCompareDoc()` method.

    pdfViewer.eventEmitter.on(PDFViewCtrl.ViewerEvent.openFileSuccess, doc => {
        doc.isCompareDoc();
    })

Below is a typical dictionary information for a comparison result document. the /PieceInfo at the end of the object (1 0) points to the (244 0) object, that is, points to the dictionary entry of /ComparePDF. So, you can determine whether a document is a comparison result document with this information.

    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
    ...