pdf-visual-diff
Version:
Visual Regression Testing for PDFs in JavaScript
134 lines • 6.1 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.snapshotsDirName = exports.SNAPSHOTS_DIR_NAME = void 0;
exports.comparePdfToSnapshot = comparePdfToSnapshot;
const path = require("node:path");
const promises_1 = require("node:fs/promises");
const pdf2png_1 = require("./pdf2png/pdf2png");
const compare_images_1 = require("./compare-images");
const jimp_1 = require("jimp");
const types_1 = require("./types");
const imageUtils_1 = require("./imageUtils");
const colorToNum = {
Red: 0xff0000ff,
Green: 0x00ff00ff,
Blue: 0x0000ffff,
White: 0x00000000,
Cyan: 0x00ffffff,
Magenta: 0xff00ffff,
Yellow: 0xffff00ff,
Black: 0x000000ff,
Gray: 0xbfbfbfff,
};
const maskImgWithRegions = (maskRegions) => (images) => {
images.forEach((img, idx) => {
;
(maskRegions(idx + 1) || []).forEach(({ type, x, y, width, height, color }) => {
if (type === 'rectangle-mask') {
img.composite(new jimp_1.Jimp({ width, height, color: colorToNum[color] }), x, y);
}
});
});
return images;
};
/**
* Compares a PDF to a persisted snapshot, with behavior for handling missing snapshots
* controlled by the `failOnMissingSnapshot` option.
*
* @remarks
* The function has the following **side effects**:
* - If no snapshot exists:
* - If `failOnMissingSnapshot` is `false` (default), the PDF is converted to an image,
* saved as a new snapshot, and the function returns `true`.
* - If `failOnMissingSnapshot` is `true`, the function returns `false` without creating a new snapshot.
* - If a snapshot exists, the PDF is converted to an image and compared to the snapshot:
* - If they differ, the function returns `false` and creates two additional images
* next to the snapshot: one with the suffix `new` (the current view of the PDF as an image)
* and one with the suffix `diff` (showing the difference between the snapshot and the `new` image).
* - If they are equal, the function returns `true`. If `new` and `diff` versions are present, they are deleted.
*
* @param pdf - Path to the PDF file or a Buffer containing the PDF.
* @param snapshotDir - Path to the directory where the `__snapshots__` folder will be created.
* @param snapshotName - Unique name for the snapshot within the specified path.
* @param options - Options for comparison, including tolerance, mask regions, and behavior
* regarding missing snapshots. See {@link CompareOptions} for more details.
*
* @returns
* A promise that resolves to `true` if the PDF matches the snapshot or if the behavior
* allows for missing snapshots. Resolves to `false` if the PDF differs from the snapshot
* or if `failOnMissingSnapshot` is `true` and no snapshot exists.
*/
async function comparePdfToSnapshot(pdf, snapshotDir, snapshotName, options) {
const mergedOptions = mergeOptionsWithDefaults(options);
const snapshotContext = await createSnapshotContext(snapshotDir, snapshotName);
try {
// Check if snapshot exits and handle accordingly
await (0, promises_1.access)(snapshotContext.path);
return compareWithSnapshot(pdf, snapshotContext, mergedOptions);
}
catch (_a) {
return handleMissingSnapshot(pdf, snapshotContext, mergedOptions);
}
}
function mergeOptionsWithDefaults(options) {
var _a, _b, _c, _d;
return {
maskRegions: (_a = options === null || options === void 0 ? void 0 : options.maskRegions) !== null && _a !== void 0 ? _a : (() => []),
pdf2PngOptions: (_b = options === null || options === void 0 ? void 0 : options.pdf2PngOptions) !== null && _b !== void 0 ? _b : { dpi: types_1.Dpi.High },
failOnMissingSnapshot: (_c = options === null || options === void 0 ? void 0 : options.failOnMissingSnapshot) !== null && _c !== void 0 ? _c : false,
tolerance: (_d = options === null || options === void 0 ? void 0 : options.tolerance) !== null && _d !== void 0 ? _d : 0,
};
}
exports.SNAPSHOTS_DIR_NAME = '__snapshots__';
/**
* @deprecated Use SNAPSHOTS_DIR_NAME instead.
*/
exports.snapshotsDirName = exports.SNAPSHOTS_DIR_NAME;
/** Generates the snapshot context and creates the folder if it doesn’t already exist. */
async function createSnapshotContext(snapshotDir, snapshotName) {
const dirPath = path.join(snapshotDir, exports.SNAPSHOTS_DIR_NAME);
try {
await (0, promises_1.access)(dirPath);
}
catch (_a) {
await (0, promises_1.mkdir)(dirPath, { recursive: true });
}
const basePath = path.join(dirPath, snapshotName);
return {
name: snapshotName,
dirPath,
path: `${basePath}.png`,
diffPath: `${basePath}.diff.png`,
newPath: `${basePath}.new.png`,
};
}
async function handleMissingSnapshot(pdf, snapshotContext, { failOnMissingSnapshot, maskRegions, pdf2PngOptions }) {
if (failOnMissingSnapshot) {
return false;
}
// Generate snapshot if missing
const images = await (0, pdf2png_1.pdf2png)(pdf, pdf2PngOptions).then(maskImgWithRegions(maskRegions));
await (0, imageUtils_1.writeImages)(snapshotContext.path)(images);
return true;
}
async function compareWithSnapshot(pdf, snapshotContext, { maskRegions, pdf2PngOptions, tolerance }) {
const images = await (0, pdf2png_1.pdf2png)(pdf, pdf2PngOptions).then(maskImgWithRegions(maskRegions));
const result = await (0, compare_images_1.compareImages)(snapshotContext.path, images, { tolerance });
if (result.equal) {
await removeIfExists(snapshotContext.diffPath);
await removeIfExists(snapshotContext.newPath);
return true;
}
await (0, imageUtils_1.writeImages)(snapshotContext.newPath)(images);
await (0, imageUtils_1.writeImages)(snapshotContext.diffPath)(result.diffs.map((x) => x.diff));
return false;
}
async function removeIfExists(filePath) {
try {
await (0, promises_1.unlink)(filePath);
}
catch (_a) {
// File doesn't exist, no need to remove
}
}
//# sourceMappingURL=compare-pdf-to-snapshot.js.map