compare-pdf-plus
Version:
Standalone node module that compares PDFs
392 lines (389 loc) • 13.4 kB
JavaScript
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