# 通过叠加 PDF 页面来比较 PDF

从 9.0.0 版本开始,Foxit PDF SDK for Web 提供了 API 来支持通过叠加 PDF 页面来对比 PDF,并生成新的比较结果图片。通过该图片,可以很直观地看出两个 PDF 页面的差异。

# 一个简单的示例

本节将提供一个简单的示例来展示如何通过叠加 PDF 页面来比较 PDF,其包含:打开文档,创建文档,获取 PDF 页面,比较页面,生成结果页面和导出结果文档。

首先,创建一个空白项目,然后基于该项目完成后续操作。

const libPath = window.top.location.origin + '/lib';
const pdfViewer = new PDFViewCtrl.PDFViewer({
    libPath: libPath,
    jr: {
        licenseSN: licenseSN,
        licenseKey: licenseKey
    }
});
pdfViewer.init(document.body);

# 加载待比较的文档

async function loadFiles() {
    const sourcePDFDoc = await pdfViewer.loadPDFDocByHttpRangeRequest({
        range: {
            url: '/assets/test-doc1.pdf'
        }
    });
    const targetPDFDoc = await pdfViewer.loadPDFDocByHttpRangeRequest({
        range: {
            url: '/assets/test-doc2.pdf'
        }
    });
    return { sourcePDFDoc, targetPDFDoc };
}

Foxit PDF SDK for Web 提供了两种方法加载文档:

  1. loadPDFDocByHttpRangeRequest (opens new window) : 可以从远程异步按需加载 PDF 文档。

  2. loadPDFDocByFile (opens new window) : 可以加载内存中的文件流,或者通过 <input type="file"> 选择硬盘上的文件。

加载文档的目的是可以在不渲染文件的情况下对其进行编辑和读取操作。这里是为了演示,所以使用这个方法。在实际项目中,您也可以通过获取当前打开的文档 (PDFViewer.getCurrentPDFDoc (opens new window)),来进行更进一步的操作。

# 创建一个空白文档

该空白文档将用来保存比较的结果。

function createBlankDoc() {
    return pdfViewer.createNewDoc();
}

# 获取待比较页面的 bitmap

async function getPageBitmaps(loadedFiles) {
    const { sourcePDFDoc, targetPDFDoc } = loadedFiles;

    const sourcePage = await sourcePDFDoc.getPageByIndex(0);
    const sourceBitmap = await sourcePage.render(1);
    
    const targetPage = await targetPDFDoc.getPageByIndex(0);
    const targetBitmap = await targetPage.render(1);
    
    return { sourceBitmap, targetBitmap };
}

如果您需要使用其他 options, 例如 (缩放和旋转),请在 render 方法中输入参数 (scale, rotate),并参考 API Reference 中的 PDFPage.render (opens new window) 接口。

# 开始比较文档

function comparePageBitmap(sourceBitmap, targetBitmap) {
    const DiffColor = PDFViewCtrl.overlayComparison.DiffColor;
    const service = pdfViewer.getOverlayComparisonService();
    const resultCanvas = service.compareImageData({
        sourceBitmap,
        targetBitmap,
        combinePixelsOptions: {
            showDiffColor: true,
            sourceDiffColor: DiffColor.RED,
            targetDiffColor: DiffColor.BLUE,
            sourceOpacity: 0xFF,
            targetOpacity: 0xFF
        },
        transformation: {
            translateX: 0,
            translateY: 0,
            rotate: 2 / 180 * Math.PI
        }
    });
    return new Promise(resolve => {
        resultCanvas.toBlob(blob => {
            const fr = new FileReader();
            fr.onloadend = () => {
                resolve({
                    buffer: fr.result,
                    width: resultCanvas.width,
                    height: resultCanvas.height
                });
            };
            fr.readAsArrayBuffer(blob);
        });
    })
}

备注:

  1. shouldShowDiff 指定为 true 时才会标记文档之间的差异。
  2. sourceDiffColortargetDiffColor 不支持任意颜色,必须选择 DiffColor 枚举中的某一项颜色。
  3. sourceOpacitytargetOpacity 的取值范围为 0~0xFF

# 将比较结果插入到 PDF 页面中

async function insertResultIntoNewDoc(newDoc, resultImageData) {
    const page = await newDoc.getPageByIndex(0);
    
    // resultImageData is the return object of the comparePageBitmap function mentioned in the above example.
    // Convert the unit of width and height from pixel to point. 
    const newPageWidth = resultImageData.width / 4 * 3;
    const newPageHeight = resultImageData.height / 4 * 3;
    // Reset the size of PDF page to make it the same size as the comparison image.
    await page.setPageSize(newPageWidth, newPageHeight);
    // Last, insert it into the PDF page as a PDF image object.
    await page.addImage(resultImageData.buffer, {
        left: 0,
        right: newPageWidth,
        bottom:0,
        top: newPageHeight
    });
}

# 导出结果文档

async function exportResultPDFDocFile(newDoc) {
    return newDoc.getFile();
}

PDFDoc.getFile() (opens new window) 接口将返回文档的 Blob 对象。您可以使用常规方法下载这个文件流或者通过 PDFViewer.openPDFByFile (opens new window) 接口打开文档来查看效果。

# 最终效果

整合上述代码,最终效果如下,请点击 run 按钮运行示例:

备注:

为了尽量减少不相干代码的干扰,保证代码更加直观,上面的示例使用了 ESNext 语法编写,请使用现代浏览器打开 developer guide 文档和运行示例。如果需要兼容旧版本浏览器,请在您的项目中使用 JavaScript 转译器,例如 babel。