UNPKG

compare-pdf-plus

Version:

Standalone node module that compares PDFs

392 lines (389 loc) 13.4 kB
import fs3 from 'fs-extra'; import path3 from 'path'; import filter from 'lodash/filter'; import { PNG } from 'pngjs'; import pixelmatch from 'pixelmatch'; // src/comparePdf.ts var copyJsonObject = (jsonObject) => { return JSON.parse(JSON.stringify(jsonObject)); }; var ensureAndCleanupPath = (filepath) => { fs3.ensureDirSync(filepath); fs3.emptyDirSync(filepath); }; var ensurePathsExist = (config2) => { fs3.ensureDirSync(config2.paths.actualPdfRootFolder); fs3.ensureDirSync(config2.paths.baselinePdfRootFolder); }; var comparePdfByBase64 = async ({ actualPdfFilename, baselinePdfFilename, actualPdfBuffer, baselinePdfBuffer }) => { return new Promise((resolve) => { const actualPdfBaseName = path3.parse(actualPdfFilename).name; const baselinePdfBaseName = path3.parse(baselinePdfFilename).name; const actualPdfBase64 = Buffer.from(actualPdfBuffer).toString("base64"); const baselinePdfBase64 = Buffer.from(baselinePdfBuffer).toString("base64"); if (actualPdfBase64 !== baselinePdfBase64) { resolve({ status: "failed", message: `${actualPdfBaseName}.pdf is not the same as ${baselinePdfBaseName}.pdf compared by their base64 values.` }); } else { resolve({ status: "passed" }); } }); }; var comparePngs = async (actual, baseline, diff, config2) => { return new Promise((resolve) => { try { const actualPng = PNG.sync.read(fs3.readFileSync(actual)); const baselinePng = PNG.sync.read(fs3.readFileSync(baseline)); const { width, height } = actualPng; const diffPng = new PNG({ width, height }); const threshold = config2.settings?.threshold ?? 0.05; const tolerance = config2.settings?.tolerance ?? 0; const numDiffPixels = pixelmatch( actualPng.data, baselinePng.data, diffPng.data, width, height, { threshold } ); if (numDiffPixels > tolerance) { fs3.writeFileSync(diff, PNG.sync.write(diffPng)); resolve({ status: "failed", numDiffPixels, diffPng: diff }); } else { resolve({ status: "passed" }); } } catch (error) { resolve({ status: "failed", actual, error }); } }); }; var comparePdfByImage = async ({ actualPdfFilename, baselinePdfFilename, actualPdfBuffer, baselinePdfBuffer, config: config2, opts }) => { return new Promise(async (resolve) => { try { const imageEngine = await (config2.settings.imageEngine === "graphicsMagick" ? import('./graphicsMagick-KNDP2FGH.js') : import('./native-3O5WQ2H7.js')); const actualPdfBaseName = path3.parse(actualPdfFilename).name; const baselinePdfBaseName = path3.parse(baselinePdfFilename).name; if (config2.paths.actualPngRootFolder && config2.paths.baselinePngRootFolder && config2.paths.diffPngRootFolder) { const actualPngDirPath = `${config2.paths.actualPngRootFolder}/${actualPdfBaseName}`; const baselinePngDirPath = `${config2.paths.baselinePngRootFolder}/${baselinePdfBaseName}`; const diffPngDirPath = `${config2.paths.diffPngRootFolder}/${actualPdfBaseName}`; ensureAndCleanupPath(actualPngDirPath); ensureAndCleanupPath(baselinePngDirPath); ensureAndCleanupPath(diffPngDirPath); const actualPngFilePath = `${actualPngDirPath}/${actualPdfBaseName}.png`; const baselinePngFilePath = `${baselinePngDirPath}/${baselinePdfBaseName}.png`; const actualPdfDetails = { filename: actualPdfFilename, buffer: actualPdfBuffer }; await imageEngine.pdfToPng(actualPdfDetails, actualPngFilePath, config2); const baselinePdfDetails = { filename: baselinePdfFilename, buffer: baselinePdfBuffer }; await imageEngine.pdfToPng( baselinePdfDetails, baselinePngFilePath, config2 ); const actualPngs = fs3.readdirSync(actualPngDirPath).filter( (pngFile) => path3.parse(pngFile).name.startsWith(actualPdfBaseName) ); const baselinePngs = fs3.readdirSync(baselinePngDirPath).filter( (pngFile) => path3.parse(pngFile).name.startsWith(baselinePdfBaseName) ); if (config2.settings.matchPageCount === true) { if (actualPngs.length !== baselinePngs.length) { resolve({ status: "failed", message: `Actual pdf page count (${actualPngs.length}) is not the same as Baseline pdf (${baselinePngs.length}).` }); return; } } const comparisonResults = []; for (let index = 0; index < baselinePngs.length; index++) { let suffix = ""; if (baselinePngs.length > 1) { suffix = `-${index}`; } const actualPng = actualPngs.length > 1 ? `${actualPngDirPath}/${actualPdfBaseName}${suffix}.png` : `${actualPngDirPath}/${actualPdfBaseName}.png`; const baselinePng = `${baselinePngDirPath}/${baselinePdfBaseName}${suffix}.png`; const diffPng = `${diffPngDirPath}/${actualPdfBaseName}_diff${suffix}.png`; if (opts.skipPageIndexes?.length && opts.skipPageIndexes.includes(index)) { continue; } if (opts.onlyPageIndexes?.length && !opts.onlyPageIndexes.includes(index)) { continue; } if (opts.masks) { const pageMasks = filter(opts.masks, { pageIndex: index }); if (pageMasks?.length) { for (const pageMask of pageMasks) { await imageEngine.applyMask( actualPng, pageMask.coordinates, pageMask.color ); await imageEngine.applyMask( baselinePng, pageMask.coordinates, pageMask.color ); } } } if (opts.crops?.length) { const pageCroppings = filter(opts.crops, { pageIndex: index }); if (pageCroppings?.length) { for (let cropIndex = 0; cropIndex < pageCroppings.length; cropIndex++) { await imageEngine.applyCrop( actualPng, pageCroppings[cropIndex]?.coordinates, cropIndex ); await imageEngine.applyCrop( baselinePng, pageCroppings[cropIndex]?.coordinates, cropIndex ); comparisonResults.push( await comparePngs( actualPng.replace(".png", `-${cropIndex}.png`), baselinePng.replace(".png", `-${cropIndex}.png`), diffPng, config2 ) ); } } } else { comparisonResults.push( await comparePngs(actualPng, baselinePng, diffPng, config2) ); } } if (config2.settings.cleanPngPaths) { ensureAndCleanupPath(config2.paths.actualPngRootFolder); ensureAndCleanupPath(config2.paths.baselinePngRootFolder); } const failedResults = filter( comparisonResults, (res) => res.status === "failed" ); if (failedResults.length > 0) { resolve({ status: "failed", message: `${actualPdfBaseName}.pdf is not the same as ${baselinePdfBaseName}.pdf compared by their images.`, details: failedResults }); } else { resolve({ status: "passed" }); } } else { resolve({ status: "failed", message: "PNG directory is not set. Please define correctly then try again." }); } } catch (error) { resolve({ status: "failed", message: `An error occurred. ${error}` }); } }); }; var config = { paths: { actualPdfRootFolder: path3.resolve(process.cwd(), "data/actualPdfs"), baselinePdfRootFolder: path3.join(process.cwd(), "data/baselinePdfs"), actualPngRootFolder: path3.join(process.cwd(), "data/actualPngs"), baselinePngRootFolder: path3.join(process.cwd(), "data/baselinePngs"), diffPngRootFolder: path3.join(process.cwd(), "data/diffPngs") }, settings: { imageEngine: "graphicsMagick", density: 100, quality: 70, tolerance: 0, threshold: 0.05, cleanPngPaths: false, matchPageCount: true, disableFontFace: true, verbosity: 0 } }; var config_default = config; // src/comparePdf.ts var ComparePdf = class { config; opts; result; baselinePdfBufferData; actualPdfBufferData; baselinePdf; actualPdf; constructor(config2 = copyJsonObject(config_default)) { this.config = config2; ensurePathsExist(this.config); this.opts = { masks: [], crops: [], onlyPageIndexes: [], skipPageIndexes: [] }; this.result = { status: "not executed" }; } baselinePdfBuffer(baselinePdfBuffer, baselinePdfFilename) { if (baselinePdfBuffer) { this.baselinePdfBufferData = baselinePdfBuffer; if (baselinePdfFilename) { this.baselinePdf = baselinePdfFilename; } } else { this.result = { status: "failed", message: "Baseline pdf buffer is invalid or filename is missing. Please define correctly then try again." }; } return this; } baselinePdfFile(baselinePdf) { if (baselinePdf) { const baselinePdfBaseName = path3.parse(baselinePdf).name; if (fs3.existsSync(baselinePdf)) { this.baselinePdf = baselinePdf; } else if (fs3.existsSync( `${this.config.paths.baselinePdfRootFolder}/${baselinePdfBaseName}.pdf` )) { this.baselinePdf = `${this.config.paths.baselinePdfRootFolder}/${baselinePdfBaseName}.pdf`; } else { this.result = { status: "failed", message: "Baseline pdf file path does not exists. Please define correctly then try again." }; } } else { this.result = { status: "failed", message: "Baseline pdf file path was not set. Please define correctly then try again." }; } return this; } actualPdfBuffer(actualPdfBuffer, actualPdfFilename) { if (actualPdfBuffer) { this.actualPdfBufferData = actualPdfBuffer; if (actualPdfFilename) { this.actualPdf = actualPdfFilename; } } else { this.result = { status: "failed", message: "Actual pdf buffer is invalid or filename is missing. Please define correctly then try again." }; } return this; } actualPdfFile(actualPdf) { if (actualPdf) { const actualPdfBaseName = path3.parse(actualPdf).name; if (fs3.existsSync(actualPdf)) { this.actualPdf = actualPdf; } else if (fs3.existsSync( `${this.config.paths.actualPdfRootFolder}/${actualPdfBaseName}.pdf` )) { this.actualPdf = `${this.config.paths.actualPdfRootFolder}/${actualPdfBaseName}.pdf`; } else { this.result = { status: "failed", message: "Actual pdf file path does not exists. Please define correctly then try again." }; } } else { this.result = { status: "failed", message: "Actual pdf file path was not set. Please define correctly then try again." }; } return this; } addMask(pageIndex, coordinates = { x0: 0, y0: 0, x1: 0, y1: 0 }, color = "black") { this.opts.masks.push({ pageIndex, coordinates, color }); return this; } addMasks(masks) { this.opts.masks = [...this.opts.masks, ...masks]; return this; } onlyPageIndexes(pageIndexes) { this.opts.onlyPageIndexes = [...this.opts.onlyPageIndexes, ...pageIndexes]; return this; } skipPageIndexes(pageIndexes) { this.opts.skipPageIndexes = [...this.opts.skipPageIndexes, ...pageIndexes]; return this; } cropPage(pageIndex, coordinates = { width: 0, height: 0, x: 0, y: 0 }) { this.opts.crops.push({ pageIndex, coordinates }); return this; } cropPages(cropPagesList) { this.opts.crops = [...this.opts.crops, ...cropPagesList]; return this; } async compare(comparisonType = "byImage") { if (this.result.status === "not executed" || this.result.status !== "failed") { if (!this.actualPdf || !this.baselinePdf) { throw new Error("PDF files not properly initialized"); } const compareDetails = { actualPdfFilename: this.actualPdf, baselinePdfFilename: this.baselinePdf, actualPdfBuffer: this.actualPdfBufferData ?? fs3.readFileSync(this.actualPdf), baselinePdfBuffer: this.baselinePdfBufferData ?? fs3.readFileSync(this.baselinePdf), config: this.config, opts: this.opts }; switch (comparisonType) { case "byBase64": this.result = await comparePdfByBase64(compareDetails); break; default: this.result = await comparePdfByImage(compareDetails); break; } } return this.result; } }; export { ComparePdf }; //# sourceMappingURL=index.js.map //# sourceMappingURL=index.js.map