pdf-visual-compare 3.4.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +326 -72
- package/out/comparePdf.d.ts +48 -6
- package/out/comparePdf.js +44 -78
- package/out/const.d.ts +2 -2
- package/out/const.js +5 -3
- package/out/errors/ComparePdfComparisonError.d.ts +6 -0
- package/out/errors/ComparePdfComparisonError.js +10 -0
- package/out/errors/ComparePdfConfigurationError.d.ts +6 -0
- package/out/errors/ComparePdfConfigurationError.js +10 -0
- package/out/errors/ComparePdfError.d.ts +6 -0
- package/out/errors/ComparePdfError.js +13 -0
- package/out/errors/ComparePdfInputError.d.ts +6 -0
- package/out/errors/ComparePdfInputError.js +10 -0
- package/out/errors/ComparePdfRenderingError.d.ts +6 -0
- package/out/errors/ComparePdfRenderingError.js +10 -0
- package/out/index.d.ts +16 -2
- package/out/index.js +14 -2
- package/out/internal/adapters/comparePngOptions.d.ts +3 -0
- package/out/internal/adapters/comparePngOptions.js +41 -0
- package/out/internal/adapters/pdfRenderOptions.d.ts +3 -0
- package/out/internal/adapters/pdfRenderOptions.js +19 -0
- package/out/internal/comparePlannedPage.d.ts +3 -0
- package/out/internal/comparePlannedPage.js +72 -0
- package/out/internal/diffOutputGuards.d.ts +56 -0
- package/out/internal/diffOutputGuards.js +176 -0
- package/out/internal/normalizeComparisonOptions.d.ts +2 -0
- package/out/internal/normalizeComparisonOptions.js +104 -0
- package/out/internal/normalizePdfInput.d.ts +13 -0
- package/out/internal/normalizePdfInput.js +110 -0
- package/out/internal/pageComparisonPlan.d.ts +4 -0
- package/out/internal/pageComparisonPlan.js +35 -0
- package/out/internal/renderOutputFolderGuards.d.ts +22 -0
- package/out/internal/renderOutputFolderGuards.js +70 -0
- package/out/internal/renderPdfPages.d.ts +10 -0
- package/out/internal/renderPdfPages.js +105 -0
- package/out/internal/securePath.d.ts +34 -0
- package/out/internal/securePath.js +75 -0
- package/out/internal/types.d.ts +26 -0
- package/out/internal/types.js +2 -0
- package/out/types/ComparePdfDetailedResult.d.ts +35 -0
- package/out/types/ComparePdfDetailedResult.js +2 -0
- package/out/types/ComparePdfOptions.d.ts +33 -11
- package/out/types/ComparePdfPageResult.d.ts +42 -0
- package/out/types/ComparePdfPageResult.js +2 -0
- package/out/types/ComparePdfPageStatus.d.ts +4 -0
- package/out/types/ComparePdfPageStatus.js +2 -0
- package/out/types/ExcludedPageArea.d.ts +3 -33
- package/out/types/PageArea.d.ts +13 -0
- package/out/types/PageArea.js +2 -0
- package/out/types/PageExclusion.d.ts +46 -0
- package/out/types/PageExclusion.js +2 -0
- package/out/types/PdfInput.d.ts +11 -0
- package/out/types/PdfInput.js +2 -0
- package/out/types/PdfRenderOptions.d.ts +51 -0
- package/out/types/PdfRenderOptions.js +2 -0
- package/out/types/RgbColor.d.ts +11 -0
- package/out/types/RgbColor.js +2 -0
- package/package.json +86 -74
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.listRenderedPageNumbers = listRenderedPageNumbers;
|
|
4
|
+
exports.renderPdfPages = renderPdfPages;
|
|
5
|
+
exports.renderPdfPage = renderPdfPage;
|
|
6
|
+
const node_fs_1 = require("node:fs");
|
|
7
|
+
const node_path_1 = require("node:path");
|
|
8
|
+
const pdf_to_png_converter_1 = require("pdf-to-png-converter");
|
|
9
|
+
const ComparePdfConfigurationError_js_1 = require("../errors/ComparePdfConfigurationError.js");
|
|
10
|
+
const ComparePdfRenderingError_js_1 = require("../errors/ComparePdfRenderingError.js");
|
|
11
|
+
const securePath_js_1 = require("./securePath.js");
|
|
12
|
+
async function listRenderedPageNumbers(pdfFile, pdfToPngConvertOpts, sourceLabel) {
|
|
13
|
+
const metadataPages = await runPdfToPng(pdfFile, {
|
|
14
|
+
...pdfToPngConvertOpts,
|
|
15
|
+
returnMetadataOnly: true,
|
|
16
|
+
returnPageContent: false,
|
|
17
|
+
}, sourceLabel);
|
|
18
|
+
return {
|
|
19
|
+
pageNumbers: metadataPages.map((page) => page.pageNumber),
|
|
20
|
+
prefetchedPages: new Map(metadataPages.map((page) => [page.pageNumber, page])),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
async function renderPdfPages(pdfFile, pdfToPngConvertOpts, sourceLabel) {
|
|
24
|
+
const renderedPages = await runPdfToPng(pdfFile, pdfToPngConvertOpts, sourceLabel);
|
|
25
|
+
const missingContentPage = renderedPages.find((page) => !Buffer.isBuffer(page.content));
|
|
26
|
+
if (missingContentPage) {
|
|
27
|
+
throw new ComparePdfRenderingError_js_1.ComparePdfRenderingError(`Rendered page content is missing for page: ${missingContentPage.name}.`);
|
|
28
|
+
}
|
|
29
|
+
return renderedPages;
|
|
30
|
+
}
|
|
31
|
+
async function renderPdfPage(pdfFile, pdfToPngConvertOpts, pageNumber, sourceLabel) {
|
|
32
|
+
clearStaleRenderTarget(pdfToPngConvertOpts, pageNumber, sourceLabel);
|
|
33
|
+
const renderedPages = await renderPdfPages(pdfFile, {
|
|
34
|
+
...pdfToPngConvertOpts,
|
|
35
|
+
pagesToProcess: [pageNumber],
|
|
36
|
+
}, sourceLabel);
|
|
37
|
+
return renderedPages[0];
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Removes the exact PNG file this page render is about to (re)create when an
|
|
41
|
+
* `outputFolder` is configured. The renderer opens output files with exclusive-create
|
|
42
|
+
* (`'wx'`) and refuses to overwrite, so a prior run's PNG at the same name would otherwise
|
|
43
|
+
* make re-rendering fail with `EEXIST`. This performs the "clear the output name before
|
|
44
|
+
* re-running the same conversion" step the renderer documents for callers, scoped to the
|
|
45
|
+
* single file the library owns and is regenerating.
|
|
46
|
+
*
|
|
47
|
+
* The removal is constrained to the library-owned render namespace: the resolved target must
|
|
48
|
+
* stay inside `<outputFolder>/<sourceLabel>`. A mask that escapes that directory is left
|
|
49
|
+
* untouched, so the renderer's own containment check (`savePNGfile`) remains the single
|
|
50
|
+
* source of truth for rejecting it. `force: true` ignores a missing file and unlinks a leaf
|
|
51
|
+
* symlink without following it. A caller-supplied `outputFileMaskFunc` that throws, and any
|
|
52
|
+
* other filesystem failure (for example a non-writable folder, or a directory occupying the
|
|
53
|
+
* target name), are surfaced as the library's typed `ComparePdfConfigurationError` rather than
|
|
54
|
+
* leaking a raw error out of the public surface.
|
|
55
|
+
*/
|
|
56
|
+
function clearStaleRenderTarget(pdfToPngConvertOpts, pageNumber, sourceLabel) {
|
|
57
|
+
const { outputFolder, outputFileMaskFunc } = pdfToPngConvertOpts;
|
|
58
|
+
if (!outputFolder || !outputFileMaskFunc) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
let maskedFileName;
|
|
62
|
+
try {
|
|
63
|
+
maskedFileName = outputFileMaskFunc(pageNumber);
|
|
64
|
+
}
|
|
65
|
+
catch (cause) {
|
|
66
|
+
throw new ComparePdfConfigurationError_js_1.ComparePdfConfigurationError(`pdfToPngConvertOptions.outputFileMaskFunc threw while resolving the output file name for page ${pageNumber}.`, { cause });
|
|
67
|
+
}
|
|
68
|
+
const renderNamespace = (0, node_path_1.resolve)(outputFolder, sourceLabel);
|
|
69
|
+
const targetPath = (0, node_path_1.resolve)(renderNamespace, maskedFileName);
|
|
70
|
+
if (!(0, securePath_js_1.isPathWithinRoot)(targetPath, renderNamespace)) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
(0, node_fs_1.rmSync)(targetPath, { force: true });
|
|
75
|
+
}
|
|
76
|
+
catch (cause) {
|
|
77
|
+
throw new ComparePdfConfigurationError_js_1.ComparePdfConfigurationError(`pdfToPngConvertOptions.outputFolder must point to a writable directory: ${outputFolder}`, { cause });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function toRenderablePdfInput(pdfFile) {
|
|
81
|
+
if (typeof pdfFile === 'string' || Buffer.isBuffer(pdfFile)) {
|
|
82
|
+
return pdfFile;
|
|
83
|
+
}
|
|
84
|
+
return Uint8Array.from(new Uint8Array(pdfFile)).buffer;
|
|
85
|
+
}
|
|
86
|
+
async function runPdfToPng(pdfFile, pdfToPngConvertOpts, sourceLabel) {
|
|
87
|
+
try {
|
|
88
|
+
const renderedPages = await (0, pdf_to_png_converter_1.pdfToPng)(toRenderablePdfInput(pdfFile), {
|
|
89
|
+
...pdfToPngConvertOpts,
|
|
90
|
+
outputFolder: pdfToPngConvertOpts.outputFolder
|
|
91
|
+
? (0, node_path_1.join)(pdfToPngConvertOpts.outputFolder, sourceLabel)
|
|
92
|
+
: undefined,
|
|
93
|
+
});
|
|
94
|
+
if (!Array.isArray(renderedPages)) {
|
|
95
|
+
throw new ComparePdfRenderingError_js_1.ComparePdfRenderingError(`Failed to render ${sourceLabel} PDF pages.`);
|
|
96
|
+
}
|
|
97
|
+
return renderedPages;
|
|
98
|
+
}
|
|
99
|
+
catch (cause) {
|
|
100
|
+
if (cause instanceof ComparePdfRenderingError_js_1.ComparePdfRenderingError) {
|
|
101
|
+
throw cause;
|
|
102
|
+
}
|
|
103
|
+
throw new ComparePdfRenderingError_js_1.ComparePdfRenderingError(`Failed to render ${sourceLabel} PDF pages.`, { cause });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared path-containment and symlink-rejection primitives used by every code path that
|
|
3
|
+
* lets a caller name a filesystem location. Two adapters were drifting before this seam
|
|
4
|
+
* existed (`src/internal/normalizePdfInput.ts` for `allowedInputRoot` and
|
|
5
|
+
* `src/internal/diffOutputGuards.ts` for `diffsOutputFolder`), and a third caller has
|
|
6
|
+
* since landed for `pdfToPngConvertOptions.outputFolder`. Centralizing the symlink-swap
|
|
7
|
+
* defense here keeps the CWE-59 / CWE-61 fix in one place.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Returns `true` when `pathToCheck` is `rootPath` itself or a descendant of it. The
|
|
11
|
+
* comparison is purely lexical and operates on already-resolved absolute paths — callers
|
|
12
|
+
* resolve and (when relevant) canonicalize before calling.
|
|
13
|
+
*/
|
|
14
|
+
export declare function isPathWithinRoot(pathToCheck: string, rootPath: string): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Reports whether a filesystem entry exists at `pathToCheck` without following symbolic
|
|
17
|
+
* links. Returning `true` does not imply the entry is a regular file or directory —
|
|
18
|
+
* callers must inspect the lstat result separately.
|
|
19
|
+
*/
|
|
20
|
+
export declare function pathExistsWithoutFollowingSymlinks(pathToCheck: string): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Throws `ComparePdfConfigurationError` when `pathToCheck` itself is a symbolic link.
|
|
23
|
+
* The path must already exist — callers are expected to have established existence via
|
|
24
|
+
* {@link pathExistsWithoutFollowingSymlinks}.
|
|
25
|
+
*/
|
|
26
|
+
export declare function assertPathIsNotSymbolicLink(pathToCheck: string, errorMessage: string): void;
|
|
27
|
+
/**
|
|
28
|
+
* Walks from `pathToCheck` up to the filesystem root and throws
|
|
29
|
+
* `ComparePdfConfigurationError` if any existing ancestor (or `pathToCheck` itself) is a
|
|
30
|
+
* symbolic link. Non-existent ancestors are skipped because they cannot redirect a write
|
|
31
|
+
* before they are created — and the leaf-creation paths in each caller separately defend
|
|
32
|
+
* against symlink swaps at creation time.
|
|
33
|
+
*/
|
|
34
|
+
export declare function assertPathAndAncestorsAreNotSymbolicLinks(pathToCheck: string, errorMessage: string): void;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isPathWithinRoot = isPathWithinRoot;
|
|
4
|
+
exports.pathExistsWithoutFollowingSymlinks = pathExistsWithoutFollowingSymlinks;
|
|
5
|
+
exports.assertPathIsNotSymbolicLink = assertPathIsNotSymbolicLink;
|
|
6
|
+
exports.assertPathAndAncestorsAreNotSymbolicLinks = assertPathAndAncestorsAreNotSymbolicLinks;
|
|
7
|
+
const node_fs_1 = require("node:fs");
|
|
8
|
+
const node_path_1 = require("node:path");
|
|
9
|
+
const ComparePdfConfigurationError_js_1 = require("../errors/ComparePdfConfigurationError.js");
|
|
10
|
+
/**
|
|
11
|
+
* Shared path-containment and symlink-rejection primitives used by every code path that
|
|
12
|
+
* lets a caller name a filesystem location. Two adapters were drifting before this seam
|
|
13
|
+
* existed (`src/internal/normalizePdfInput.ts` for `allowedInputRoot` and
|
|
14
|
+
* `src/internal/diffOutputGuards.ts` for `diffsOutputFolder`), and a third caller has
|
|
15
|
+
* since landed for `pdfToPngConvertOptions.outputFolder`. Centralizing the symlink-swap
|
|
16
|
+
* defense here keeps the CWE-59 / CWE-61 fix in one place.
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* Returns `true` when `pathToCheck` is `rootPath` itself or a descendant of it. The
|
|
20
|
+
* comparison is purely lexical and operates on already-resolved absolute paths — callers
|
|
21
|
+
* resolve and (when relevant) canonicalize before calling.
|
|
22
|
+
*/
|
|
23
|
+
function isPathWithinRoot(pathToCheck, rootPath) {
|
|
24
|
+
const relativePath = (0, node_path_1.relative)(rootPath, pathToCheck);
|
|
25
|
+
return relativePath === '' || (!relativePath.startsWith('..') && !(0, node_path_1.isAbsolute)(relativePath));
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Reports whether a filesystem entry exists at `pathToCheck` without following symbolic
|
|
29
|
+
* links. Returning `true` does not imply the entry is a regular file or directory —
|
|
30
|
+
* callers must inspect the lstat result separately.
|
|
31
|
+
*/
|
|
32
|
+
function pathExistsWithoutFollowingSymlinks(pathToCheck) {
|
|
33
|
+
try {
|
|
34
|
+
(0, node_fs_1.lstatSync)(pathToCheck);
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Throws `ComparePdfConfigurationError` when `pathToCheck` itself is a symbolic link.
|
|
43
|
+
* The path must already exist — callers are expected to have established existence via
|
|
44
|
+
* {@link pathExistsWithoutFollowingSymlinks}.
|
|
45
|
+
*/
|
|
46
|
+
function assertPathIsNotSymbolicLink(pathToCheck, errorMessage) {
|
|
47
|
+
if (!(0, node_fs_1.lstatSync)(pathToCheck).isSymbolicLink()) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
throw new ComparePdfConfigurationError_js_1.ComparePdfConfigurationError(errorMessage);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Walks from `pathToCheck` up to the filesystem root and throws
|
|
54
|
+
* `ComparePdfConfigurationError` if any existing ancestor (or `pathToCheck` itself) is a
|
|
55
|
+
* symbolic link. Non-existent ancestors are skipped because they cannot redirect a write
|
|
56
|
+
* before they are created — and the leaf-creation paths in each caller separately defend
|
|
57
|
+
* against symlink swaps at creation time.
|
|
58
|
+
*/
|
|
59
|
+
function assertPathAndAncestorsAreNotSymbolicLinks(pathToCheck, errorMessage) {
|
|
60
|
+
const existingAncestorPaths = [];
|
|
61
|
+
let currentPath = (0, node_path_1.resolve)(pathToCheck);
|
|
62
|
+
while (true) {
|
|
63
|
+
if (pathExistsWithoutFollowingSymlinks(currentPath)) {
|
|
64
|
+
existingAncestorPaths.push(currentPath);
|
|
65
|
+
}
|
|
66
|
+
const parentPath = (0, node_path_1.dirname)(currentPath);
|
|
67
|
+
if (parentPath === currentPath) {
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
currentPath = parentPath;
|
|
71
|
+
}
|
|
72
|
+
for (const existingPath of existingAncestorPaths.reverse()) {
|
|
73
|
+
assertPathIsNotSymbolicLink(existingPath, errorMessage);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { PdfToPngOptions, PngPageOutput } from 'pdf-to-png-converter';
|
|
2
|
+
import type { PageExclusion } from '../types/PageExclusion.js';
|
|
3
|
+
export type RenderedPngPageOutput = Extract<PngPageOutput, {
|
|
4
|
+
kind: 'content' | 'file';
|
|
5
|
+
}> & {
|
|
6
|
+
content: Buffer;
|
|
7
|
+
};
|
|
8
|
+
export type AllowedInputRoot = {
|
|
9
|
+
displayPath: string;
|
|
10
|
+
resolvedPath: string;
|
|
11
|
+
canonicalPath: string;
|
|
12
|
+
};
|
|
13
|
+
export type PageComparisonPlanEntry = {
|
|
14
|
+
pageNumber: number;
|
|
15
|
+
actualPage?: RenderedPngPageOutput;
|
|
16
|
+
expectedPage?: RenderedPngPageOutput;
|
|
17
|
+
pageExclusion?: PageExclusion;
|
|
18
|
+
};
|
|
19
|
+
export type NormalizedComparePdfOptions = {
|
|
20
|
+
allowedInputRoot?: AllowedInputRoot;
|
|
21
|
+
compareThreshold: number;
|
|
22
|
+
diffsOutputFolder: string;
|
|
23
|
+
excludedAreas: readonly PageExclusion[];
|
|
24
|
+
pdfToPngConvertOpts: PdfToPngOptions;
|
|
25
|
+
writeDiffs: boolean;
|
|
26
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { ComparePdfPageResult } from './ComparePdfPageResult.js';
|
|
2
|
+
/**
|
|
3
|
+
* Structured document-level comparison result returned by `comparePdfDetailed()`.
|
|
4
|
+
*/
|
|
5
|
+
export type ComparePdfDetailedResult = {
|
|
6
|
+
/**
|
|
7
|
+
* `true` when every planned page comparison stays within its applicable threshold.
|
|
8
|
+
*/
|
|
9
|
+
isEqual: boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Number of rendered pages produced from the actual PDF input.
|
|
12
|
+
*/
|
|
13
|
+
actualPageCount: number;
|
|
14
|
+
/**
|
|
15
|
+
* Number of rendered pages produced from the expected PDF input.
|
|
16
|
+
*/
|
|
17
|
+
expectedPageCount: number;
|
|
18
|
+
/**
|
|
19
|
+
* Document-level threshold supplied to the comparison.
|
|
20
|
+
*/
|
|
21
|
+
compareThreshold: number;
|
|
22
|
+
/**
|
|
23
|
+
* `true` when diff PNG files were written for compared pages.
|
|
24
|
+
*/
|
|
25
|
+
writeDiffs: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Base diff output folder used when a page does not provide a custom diff path.
|
|
28
|
+
* `null` when diff writing was disabled.
|
|
29
|
+
*/
|
|
30
|
+
diffsOutputFolder: string | null;
|
|
31
|
+
/**
|
|
32
|
+
* Page-level results sorted by `pageNumber`.
|
|
33
|
+
*/
|
|
34
|
+
pages: ComparePdfPageResult[];
|
|
35
|
+
};
|
|
@@ -1,30 +1,52 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type {
|
|
1
|
+
import type { PageExclusion } from './PageExclusion.js';
|
|
2
|
+
import type { PdfRenderOptions } from './PdfRenderOptions.js';
|
|
3
3
|
/**
|
|
4
4
|
* Options for configuring the PDF comparison process.
|
|
5
5
|
*/
|
|
6
6
|
export type ComparePdfOptions = {
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
8
|
+
* When `true`, writes diff PNG images to disk.
|
|
9
|
+
* When omitted or `false`, comparison stays in memory and no diff files are produced.
|
|
10
|
+
* @default false
|
|
11
|
+
*/
|
|
12
|
+
writeDiffs?: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Folder path that defines the allowed root for diff PNG images written when differences are found.
|
|
15
|
+
* Auto-generated diff paths and any per-page `diffFilePath` overrides must stay within this root.
|
|
16
|
+
* Callers should treat this as a trusted filesystem write location on a trusted local filesystem.
|
|
17
|
+
* If provided, it is validated even when `writeDiffs` is `false`; diff files are only written
|
|
18
|
+
* when `writeDiffs` is `true`.
|
|
9
19
|
* @default "./comparePdfOutput"
|
|
10
20
|
*/
|
|
11
21
|
diffsOutputFolder?: string;
|
|
12
22
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
23
|
+
* Optional workspace root that constrains string PDF inputs.
|
|
24
|
+
*
|
|
25
|
+
* When set, `actualPdf` and `expectedPdf` string paths must resolve within this directory
|
|
26
|
+
* or `comparePdf()` throws a `ComparePdfConfigurationError`.
|
|
27
|
+
*
|
|
28
|
+
* When omitted, string paths are treated as trusted caller-controlled local file paths.
|
|
29
|
+
* Binary inputs (`Buffer`, `ArrayBuffer`, `SharedArrayBuffer`) are not affected.
|
|
30
|
+
*/
|
|
31
|
+
allowedInputRoot?: string;
|
|
32
|
+
/**
|
|
33
|
+
* Options for rendering PDF pages before comparison.
|
|
15
34
|
*/
|
|
16
|
-
pdfToPngConvertOptions?:
|
|
35
|
+
pdfToPngConvertOptions?: PdfRenderOptions;
|
|
17
36
|
/**
|
|
18
|
-
* Per-page areas to exclude from pixel comparison, matched by
|
|
19
|
-
*
|
|
37
|
+
* Per-page areas to exclude from pixel comparison, matched by the rendered page's
|
|
38
|
+
* `pageNumber` field (1-based).
|
|
39
|
+
*
|
|
40
|
+
* Entries for pages that were not rendered are ignored. If multiple entries target the
|
|
41
|
+
* same `pageNumber`, the first entry wins and later duplicates are ignored.
|
|
20
42
|
* @default []
|
|
21
43
|
*/
|
|
22
|
-
excludedAreas?: readonly
|
|
44
|
+
excludedAreas?: readonly PageExclusion[];
|
|
23
45
|
/**
|
|
24
46
|
* Maximum number of differing pixels allowed before the comparison is considered a failure.
|
|
25
|
-
* A value of `0` requires a pixel-perfect match. Must be
|
|
47
|
+
* A value of `0` requires a pixel-perfect match. Must be a finite non-negative integer.
|
|
26
48
|
* @default 0
|
|
27
|
-
* @throws {
|
|
49
|
+
* @throws {ComparePdfConfigurationError} When set to a negative, fractional, infinite, or `NaN` value.
|
|
28
50
|
*/
|
|
29
51
|
compareThreshold?: number;
|
|
30
52
|
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { ComparePdfPageStatus } from './ComparePdfPageStatus.js';
|
|
2
|
+
/**
|
|
3
|
+
* Page-level comparison outcome reported by `comparePdfDetailed()`.
|
|
4
|
+
*/
|
|
5
|
+
export type ComparePdfPageResult = {
|
|
6
|
+
/**
|
|
7
|
+
* 1-based rendered page number used for planning and comparison.
|
|
8
|
+
*/
|
|
9
|
+
pageNumber: number;
|
|
10
|
+
/**
|
|
11
|
+
* Final page outcome.
|
|
12
|
+
*/
|
|
13
|
+
status: ComparePdfPageStatus;
|
|
14
|
+
/**
|
|
15
|
+
* `true` when this page is within its applicable threshold.
|
|
16
|
+
*/
|
|
17
|
+
isEqual: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Threshold applied to this page after combining the document-level threshold with any
|
|
20
|
+
* page-specific override.
|
|
21
|
+
*/
|
|
22
|
+
threshold: number;
|
|
23
|
+
/**
|
|
24
|
+
* Pixel mismatch count returned by the PNG comparator.
|
|
25
|
+
* `null` means the page was not compared because one rendered counterpart was missing.
|
|
26
|
+
*/
|
|
27
|
+
mismatchCount: number | null;
|
|
28
|
+
/**
|
|
29
|
+
* Diff PNG output path chosen for this page comparison.
|
|
30
|
+
* `null` means no page comparison ran because one rendered counterpart was missing,
|
|
31
|
+
* or diff writing was disabled.
|
|
32
|
+
*/
|
|
33
|
+
diffFilePath: string | null;
|
|
34
|
+
/**
|
|
35
|
+
* Renderer-reported actual page image name, when the actual PDF produced this page.
|
|
36
|
+
*/
|
|
37
|
+
actualPageName: string | null;
|
|
38
|
+
/**
|
|
39
|
+
* Renderer-reported expected page image name, when the expected PDF produced this page.
|
|
40
|
+
*/
|
|
41
|
+
expectedPageName: string | null;
|
|
42
|
+
};
|
|
@@ -1,36 +1,6 @@
|
|
|
1
|
-
import type { Area, Color } from 'png-visual-compare';
|
|
2
1
|
/**
|
|
3
|
-
*
|
|
2
|
+
* Backwards-compatible alias of {@link PageExclusion}.
|
|
4
3
|
*
|
|
5
|
-
*
|
|
6
|
-
* by array index (0-based): index 0 targets the first page, index 1 the second, and so on.
|
|
4
|
+
* Exclusions are matched by rendered `pageNumber` (1-based), not by array position.
|
|
7
5
|
*/
|
|
8
|
-
export type ExcludedPageArea
|
|
9
|
-
/**
|
|
10
|
-
* Informational page number for readability.
|
|
11
|
-
* Matching is performed by array index, not by this value.
|
|
12
|
-
*/
|
|
13
|
-
pageNumber: number;
|
|
14
|
-
/**
|
|
15
|
-
* Rectangular regions to exclude from pixel comparison on this page.
|
|
16
|
-
* Coordinates (`x1`, `y1`, `x2`, `y2`) are in pixels relative to the rendered PNG
|
|
17
|
-
* at the configured `viewportScale`. `(x1, y1)` is the top-left corner and
|
|
18
|
-
* `(x2, y2)` is the bottom-right corner.
|
|
19
|
-
*/
|
|
20
|
-
excludedAreas?: Area[];
|
|
21
|
-
/**
|
|
22
|
-
* Fill color used to paint excluded regions in the diff output image.
|
|
23
|
-
* Expressed as `{ r, g, b }` with channel values in the range 0–255.
|
|
24
|
-
*/
|
|
25
|
-
excludedAreaColor?: Color;
|
|
26
|
-
/**
|
|
27
|
-
* Override the diff image output file path for this specific page.
|
|
28
|
-
* When omitted, the path is derived from the document-level `diffsOutputFolder`.
|
|
29
|
-
*/
|
|
30
|
-
diffFilePath?: string;
|
|
31
|
-
/**
|
|
32
|
-
* Per-page pixel difference threshold that overrides the document-level
|
|
33
|
-
* `compareThreshold` for this page only.
|
|
34
|
-
*/
|
|
35
|
-
matchingThreshold?: number;
|
|
36
|
-
};
|
|
6
|
+
export type { PageExclusion as ExcludedPageArea } from './PageExclusion.js';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Defines a rectangular region on a rendered PDF page by its pixel coordinates.
|
|
3
|
+
*/
|
|
4
|
+
export type PageArea = {
|
|
5
|
+
/** X coordinate of the left edge (pixels from left). */
|
|
6
|
+
x1: number;
|
|
7
|
+
/** Y coordinate of the top edge (pixels from top). */
|
|
8
|
+
y1: number;
|
|
9
|
+
/** X coordinate of the right edge (pixels from left, inclusive). */
|
|
10
|
+
x2: number;
|
|
11
|
+
/** Y coordinate of the bottom edge (pixels from top, inclusive). */
|
|
12
|
+
y2: number;
|
|
13
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { PageArea } from './PageArea.js';
|
|
2
|
+
import type { RgbColor } from './RgbColor.js';
|
|
3
|
+
/**
|
|
4
|
+
* Defines a page-specific exclusion zone for PDF comparison.
|
|
5
|
+
*
|
|
6
|
+
* Each entry is matched to a PDF page by its `pageNumber` field (1-based):
|
|
7
|
+
* `pageNumber: 1` targets the first page, `pageNumber: 2` the second, and so on.
|
|
8
|
+
* Entries whose `pageNumber` does not correspond to any rendered page are silently ignored.
|
|
9
|
+
* If multiple entries target the same `pageNumber`, only the first matching entry is used.
|
|
10
|
+
*/
|
|
11
|
+
export type PageExclusion = {
|
|
12
|
+
/**
|
|
13
|
+
* 1-based page number this exclusion applies to.
|
|
14
|
+
* Must be a finite positive integer matching the page's position in the PDF
|
|
15
|
+
* (`1` = first page, `2` = second page, etc.).
|
|
16
|
+
*/
|
|
17
|
+
pageNumber: number;
|
|
18
|
+
/**
|
|
19
|
+
* Rectangular regions to exclude from pixel comparison on this page.
|
|
20
|
+
* Coordinates (`x1`, `y1`, `x2`, `y2`) are in pixels relative to the rendered PNG
|
|
21
|
+
* at the configured `viewportScale`. `(x1, y1)` is the top-left corner and
|
|
22
|
+
* `(x2, y2)` is the bottom-right corner.
|
|
23
|
+
*/
|
|
24
|
+
excludedAreas?: PageArea[];
|
|
25
|
+
/**
|
|
26
|
+
* Colour used to paint excluded regions before comparison.
|
|
27
|
+
* Expressed as `{ r, g, b }` with channel values in the range 0–255.
|
|
28
|
+
*
|
|
29
|
+
* When omitted, the downstream PNG comparator uses its default blue fill colour.
|
|
30
|
+
*/
|
|
31
|
+
excludedAreaColor?: RgbColor;
|
|
32
|
+
/**
|
|
33
|
+
* Override the diff image output file path for this specific page.
|
|
34
|
+
* When set, takes precedence over the path derived from `diffsOutputFolder`.
|
|
35
|
+
* When omitted, the path is auto-generated as `<diffsOutputFolder>/diff_<pageName>`.
|
|
36
|
+
* When diff writing is enabled, the resolved path must stay within `diffsOutputFolder`,
|
|
37
|
+
* otherwise `comparePdf` throws `ComparePdfConfigurationError`.
|
|
38
|
+
*/
|
|
39
|
+
diffFilePath?: string;
|
|
40
|
+
/**
|
|
41
|
+
* Per-page pixel difference threshold that overrides the document-level
|
|
42
|
+
* `compareThreshold` for this page only. Must be a finite non-negative integer.
|
|
43
|
+
* When omitted, the document-level `compareThreshold` applies.
|
|
44
|
+
*/
|
|
45
|
+
matchingThreshold?: number;
|
|
46
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Supported PDF input sources for `comparePdf()` and `comparePdfDetailed()`.
|
|
3
|
+
*
|
|
4
|
+
* - `string`: trusted path to a PDF file on disk, opened and read before rendering
|
|
5
|
+
* - `Buffer`: Node.js binary PDF content
|
|
6
|
+
* - `ArrayBufferLike`: binary PDF content, including `ArrayBuffer` and `SharedArrayBuffer`
|
|
7
|
+
*
|
|
8
|
+
* `SharedArrayBuffer` inputs are normalized to `ArrayBuffer` before being passed to the PDF renderer.
|
|
9
|
+
* For untrusted environments, prefer binary inputs or configure `allowedInputRoot`.
|
|
10
|
+
*/
|
|
11
|
+
export type PdfInput = string | Buffer | ArrayBufferLike;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options for rendering PDF pages before visual comparison.
|
|
3
|
+
*
|
|
4
|
+
* These options are part of this library's public contract and are adapted internally
|
|
5
|
+
* to the current PDF rendering dependency. Options that disable page content or enable
|
|
6
|
+
* parallel rendering are intentionally excluded because this library always renders
|
|
7
|
+
* page images sequentially for safe comparison.
|
|
8
|
+
*/
|
|
9
|
+
export type PdfRenderOptions = {
|
|
10
|
+
/**
|
|
11
|
+
* Scale factor applied to each page viewport before rendering.
|
|
12
|
+
* Values above `1` produce larger, higher-resolution images; values below `1` produce smaller images.
|
|
13
|
+
*/
|
|
14
|
+
viewportScale?: number;
|
|
15
|
+
/**
|
|
16
|
+
* When `true`, embedded fonts are not loaded and built-in fonts are used instead.
|
|
17
|
+
*/
|
|
18
|
+
disableFontFace?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* When `true`, system-installed fonts may be used as fallbacks for missing embedded fonts.
|
|
21
|
+
*/
|
|
22
|
+
useSystemFonts?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* When `true`, XFA (XML Forms Architecture) form data is rendered.
|
|
25
|
+
*/
|
|
26
|
+
enableXfa?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Password used to open an encrypted PDF.
|
|
29
|
+
*/
|
|
30
|
+
pdfFilePassword?: string;
|
|
31
|
+
/**
|
|
32
|
+
* Folder path where intermediate PNG files are written.
|
|
33
|
+
* This library namespaces the renderer output under `actual/` and `expected/` subfolders
|
|
34
|
+
* to avoid filename collisions between the two compared PDFs.
|
|
35
|
+
* When omitted, rendered pages stay in memory unless another option writes them to disk.
|
|
36
|
+
*/
|
|
37
|
+
outputFolder?: string;
|
|
38
|
+
/**
|
|
39
|
+
* Custom naming function for output PNG files.
|
|
40
|
+
* Receives the 1-based page number and must return a filename ending with `.png`.
|
|
41
|
+
*/
|
|
42
|
+
outputFileMaskFunc?: (pageNumber: number) => string;
|
|
43
|
+
/**
|
|
44
|
+
* 1-based page numbers to render. When omitted, all pages are rendered.
|
|
45
|
+
*/
|
|
46
|
+
pagesToProcess?: number[];
|
|
47
|
+
/**
|
|
48
|
+
* Renderer verbosity level. `0` logs errors only, `1` warnings, and `5` informational output.
|
|
49
|
+
*/
|
|
50
|
+
verbosityLevel?: number;
|
|
51
|
+
};
|