pdf-visual-diff
Version:
Visual Regression Testing for PDFs in JavaScript
166 lines • 7.09 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.snapshotsDirName = exports.SNAPSHOTS_DIR_NAME = void 0;
exports.comparePdfToSnapshot = comparePdfToSnapshot;
const path = __importStar(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 {
return handleMissingSnapshot(pdf, snapshotContext, mergedOptions);
}
}
function mergeOptionsWithDefaults(options) {
return {
maskRegions: options?.maskRegions ?? (() => []),
pdf2PngOptions: options?.pdf2PngOptions ?? { dpi: types_1.Dpi.High },
failOnMissingSnapshot: options?.failOnMissingSnapshot ?? false,
tolerance: options?.tolerance ?? 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 {
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 {
// File doesn't exist, no need to remove
}
}
//# sourceMappingURL=compare-pdf-to-snapshot.js.map