pdf-visual-compare 3.4.0 → 3.5.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 CHANGED
@@ -76,8 +76,8 @@ const isEqual = await comparePdf('./actual.pdf', './expected.pdf', {
76
76
  // 0 = pixel-perfect match required (default). Must be >= 0.
77
77
  compareThreshold: 200,
78
78
 
79
- // Per-page exclusion zones, matched by array index (0-based).
80
- // Index 0 → first page, index 1 → second page, etc.
79
+ // Per-page exclusion zones, matched by the `pageNumber` field (1-based).
80
+ // `pageNumber: 1` → first page, `pageNumber: 2` → second page, etc.
81
81
  // Pixel coordinates are relative to the rendered PNG at the configured viewportScale.
82
82
  excludedAreas: [
83
83
  {
@@ -128,11 +128,11 @@ const isEqual = await comparePdf(actual, expected);
128
128
 
129
129
  ### `comparePdf(actualPdf, expectedPdf, options?)`
130
130
 
131
- | Parameter | Type | Description |
132
- | ------------- | --------------------------------- | ----------------------------------------- |
133
- | `actualPdf` | `string \| Buffer` | File path or buffer of the PDF under test |
134
- | `expectedPdf` | `string \| Buffer` | File path or buffer of the reference PDF |
135
- | `options` | `ComparePdfOptions` _(optional)_ | Comparison configuration |
131
+ | Parameter | Type | Description |
132
+ | ------------- | ------------------------------------------- | ----------------------------------------- |
133
+ | `actualPdf` | `string \| Buffer \| ArrayBufferLike` | File path or buffer of the PDF under test |
134
+ | `expectedPdf` | `string \| Buffer \| ArrayBufferLike` | File path or buffer of the reference PDF |
135
+ | `options` | `ComparePdfOptions` _(optional)_ | Comparison configuration |
136
136
 
137
137
  Returns `Promise<boolean>` — `true` if the PDFs are visually equivalent within the configured threshold, `false` otherwise.
138
138
 
@@ -147,14 +147,14 @@ Returns `Promise<boolean>` — `true` if the PDFs are visually equivalent within
147
147
  | ------------------------ | --------------------- | -------------------- | --------------------------------------------------------------------------- |
148
148
  | `diffsOutputFolder` | `string` | `./comparePdfOutput` | Folder where diff PNG images are written |
149
149
  | `compareThreshold` | `number` | `0` | Maximum number of differing pixels allowed before comparison fails |
150
- | `excludedAreas` | `ExcludedPageArea[]` | `[]` | Per-page exclusion zones; array index corresponds to page index (0-based) |
150
+ | `excludedAreas` | `ExcludedPageArea[]` | `[]` | Per-page exclusion zones matched by the `pageNumber` field of each entry (1-based) |
151
151
  | `pdfToPngConvertOptions` | `PdfToPngOptions` | see below | Options forwarded to [`pdf-to-png-converter`](https://github.com/dichovsky/pdf-to-png-converter) |
152
152
 
153
153
  ### `ExcludedPageArea`
154
154
 
155
155
  | Property | Type | Description |
156
156
  | ------------------- | -------- | --------------------------------------------------------------------------------------------------- |
157
- | `pageNumber` | `number` | Informational page number (matching is performed by array index, not this value) |
157
+ | `pageNumber` | `number` | 1-based page number this exclusion applies to (`1` = first page, `2` = second page, etc.) |
158
158
  | `excludedAreas` | `Area[]` | Rectangles to exclude. `Area` = `{ x1, y1, x2, y2 }` in pixels at the configured `viewportScale` |
159
159
  | `excludedAreaColor` | `Color` | Fill color for excluded regions in diff images. `Color` = `{ r, g, b }` with values 0–255 |
160
160
  | `diffFilePath` | `string` | Override the diff image output path for this page |
package/out/comparePdf.js CHANGED
@@ -27,50 +27,58 @@ async function comparePdf(actualPdf, expectedPdf, opts = {}) {
27
27
  if (!pdfToPngConvertOpts.outputFileMaskFunc) {
28
28
  pdfToPngConvertOpts.outputFileMaskFunc = (pageNumber) => `comparePdf_${pageNumber}.png`;
29
29
  }
30
- const diffsOutputFolder = opts?.diffsOutputFolder ?? const_js_1.DEFAULT_DIFFS_FOLDER;
31
- const compareThreshold = opts?.compareThreshold ?? 0;
32
- const excludedAreas = opts?.excludedAreas ?? [];
30
+ const diffsOutputFolder = opts.diffsOutputFolder ?? const_js_1.DEFAULT_DIFFS_FOLDER;
31
+ const compareThreshold = opts.compareThreshold ?? 0;
32
+ const excludedAreas = opts.excludedAreas ?? [];
33
33
  if (compareThreshold < 0) {
34
34
  throw Error('Compare Threshold cannot be less than 0.');
35
35
  }
36
- // Convert PDFs to PNGs
37
- let [actualPdfPngPages, expectedPdfPngPages] = await Promise.all([
38
- (0, pdf_to_png_converter_1.pdfToPng)(actualPdf, pdfToPngConvertOpts),
39
- (0, pdf_to_png_converter_1.pdfToPng)(expectedPdf, pdfToPngConvertOpts),
40
- ]);
41
- // Ensure actualPdfPngPages is always the longer array to avoid index out of bounds errors
42
- if (actualPdfPngPages.length < expectedPdfPngPages.length) {
43
- [actualPdfPngPages, expectedPdfPngPages] = [expectedPdfPngPages, actualPdfPngPages];
44
- }
36
+ // Convert PDFs to PNGs sequentially to avoid PDF.js worker state corruption.
37
+ // When two pdfToPng calls run concurrently via Promise.all, the pdfDocument.cleanup()
38
+ // in one call can corrupt the shared PDF.js worker state for the other call, causing
39
+ // "Invalid page request" errors — particularly when the two PDFs have different page counts.
40
+ const actualPdfPngPages = await (0, pdf_to_png_converter_1.pdfToPng)(actualPdf, { ...pdfToPngConvertOpts });
41
+ const expectedPdfPngPages = await (0, pdf_to_png_converter_1.pdfToPng)(expectedPdf, { ...pdfToPngConvertOpts });
45
42
  let documentCompareResult = true;
46
- actualPdfPngPages.forEach((pngPage, index) => {
43
+ for (const [index, pngPage] of actualPdfPngPages.entries()) {
44
+ // Look up the exclusion zone for this page by 1-based page number.
45
+ const pageExcludedArea = excludedAreas.find((area) => area.pageNumber === index + 1);
46
+ if (!pngPage.content) {
47
+ throw new Error(`Page content is undefined for page: ${pngPage.name}`);
48
+ }
49
+ // Only forward the fields that ComparePngOptions actually recognises.
50
+ // The per-page diffFilePath override takes precedence over the auto-generated path.
47
51
  const comparePngOpts = {
48
- ...opts?.pdfToPngConvertOptions,
49
- ...excludedAreas[index],
52
+ excludedAreas: pageExcludedArea?.excludedAreas,
53
+ diffFilePath: pageExcludedArea?.diffFilePath ?? (0, node_path_1.resolve)(diffsOutputFolder, `diff_${pngPage.name}`),
50
54
  throwErrorOnInvalidInputData: false,
51
55
  };
52
- comparePngOpts.diffFilePath = (0, node_path_1.resolve)(diffsOutputFolder, `diff_${pngPage.name}`);
53
56
  const pngPageOutputToCompareWith = expectedPdfPngPages.find((p) => p.name === pngPage.name);
54
57
  const pageCompareResult = (0, png_visual_compare_1.comparePng)(pngPage.content, pngPageOutputToCompareWith?.content ?? '', comparePngOpts);
55
- if (pageCompareResult > compareThreshold) {
58
+ // Per-page matchingThreshold overrides the document-level compareThreshold when set.
59
+ const pageThreshold = pageExcludedArea?.matchingThreshold ?? compareThreshold;
60
+ if (pageCompareResult > pageThreshold) {
56
61
  documentCompareResult = false;
57
62
  }
58
- });
63
+ }
64
+ // Extra pages present in expected but absent from actual are always a mismatch.
65
+ if (expectedPdfPngPages.length > actualPdfPngPages.length) {
66
+ documentCompareResult = false;
67
+ }
59
68
  return documentCompareResult;
60
69
  }
61
70
  /**
62
- * Validates the type of the input file. The input file can either be a Buffer or a string representing a file path.
63
- * If the input file is a Buffer, the function returns without any error.
64
- * If the input file is a string, the function checks if the file exists at the given path.
65
- * If the file does not exist, an error is thrown.
66
- * If the input file is neither a Buffer nor a string, an error is thrown.
71
+ * Validates the type of the input file.
72
+ *
73
+ * Accepts a `Buffer`, any `ArrayBufferLike` (`ArrayBuffer` / `SharedArrayBuffer`), or a
74
+ * string path that points to an existing file. Throws for any other input.
67
75
  *
68
- * @param inputFile - The input file to validate. It can be a Buffer or a string representing a file path.
69
- * @throws {Error} If the input file is a string and the file does not exist.
70
- * @throws {Error} If the input file is neither a Buffer nor a string.
76
+ * @param inputFile - The input to validate.
77
+ * @throws {Error} If the input is a string path that does not exist.
78
+ * @throws {Error} If the input is neither a recognised buffer type nor a string.
71
79
  */
72
80
  function validateInputFileType(inputFile) {
73
- if (Buffer.isBuffer(inputFile)) {
81
+ if (Buffer.isBuffer(inputFile) || inputFile instanceof ArrayBuffer || inputFile instanceof SharedArrayBuffer) {
74
82
  return;
75
83
  }
76
84
  if (typeof inputFile === 'string') {
@@ -2,13 +2,14 @@ import type { Area, Color } from 'png-visual-compare';
2
2
  /**
3
3
  * Defines a page-specific exclusion zone for PDF comparison.
4
4
  *
5
- * Elements in the `excludedAreas` array on {@link ComparePdfOptions} are matched to pages
6
- * by array index (0-based): index 0 targets the first page, index 1 the second, and so on.
5
+ * Each entry is matched to a PDF page by its `pageNumber` field (1-based):
6
+ * `pageNumber: 1` targets the first page, `pageNumber: 2` the second, and so on.
7
+ * Entries whose `pageNumber` does not correspond to any page are silently ignored.
7
8
  */
8
9
  export type ExcludedPageArea = {
9
10
  /**
10
- * Informational page number for readability.
11
- * Matching is performed by array index, not by this value.
11
+ * 1-based page number this exclusion applies to.
12
+ * Must match the page's position in the PDF (`1` = first page, `2` = second page, etc.).
12
13
  */
13
14
  pageNumber: number;
14
15
  /**
@@ -21,16 +22,21 @@ export type ExcludedPageArea = {
21
22
  /**
22
23
  * Fill color used to paint excluded regions in the diff output image.
23
24
  * Expressed as `{ r, g, b }` with channel values in the range 0–255.
25
+ *
26
+ * @remarks Currently reserved for future use. The underlying `png-visual-compare`
27
+ * library always paints excluded areas blue; this field has no effect at runtime.
24
28
  */
25
29
  excludedAreaColor?: Color;
26
30
  /**
27
31
  * Override the diff image output file path for this specific page.
28
- * When omitted, the path is derived from the document-level `diffsOutputFolder`.
32
+ * When set, takes precedence over the path derived from `diffsOutputFolder`.
33
+ * When omitted, the path is auto-generated as `<diffsOutputFolder>/diff_<pageName>`.
29
34
  */
30
35
  diffFilePath?: string;
31
36
  /**
32
37
  * Per-page pixel difference threshold that overrides the document-level
33
- * `compareThreshold` for this page only.
38
+ * `compareThreshold` for this page only. Must be >= 0.
39
+ * When omitted, the document-level `compareThreshold` applies.
34
40
  */
35
41
  matchingThreshold?: number;
36
42
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pdf-visual-compare",
3
- "version": "3.4.0",
3
+ "version": "3.5.0",
4
4
  "description": "Visual regression testing library for PDFs in Js/Ts without binary and OS dependencies.",
5
5
  "keywords": [
6
6
  "pdf",
@@ -9,7 +9,9 @@
9
9
  "pdf compare",
10
10
  "compare pdf",
11
11
  "pdf diff",
12
- "visual regression"
12
+ "visual regression",
13
+ "pdf testing",
14
+ "visual-diff"
13
15
  ],
14
16
  "homepage": "https://github.com/dichovsky/pdf-visual-compare#readme",
15
17
  "bugs": {
@@ -38,6 +40,7 @@
38
40
  "./out"
39
41
  ],
40
42
  "scripts": {
43
+ "prepublishOnly": "npm test && npm run build",
41
44
  "prebuild": "npm run clean",
42
45
  "build": "tsc --pretty",
43
46
  "clean": "rimraf ./out ./coverage ./test-results ./comparePdfOutput",
@@ -53,23 +56,21 @@
53
56
  "test:license": "npx --yes license-checker --production --onlyAllow \"ISC; MIT; MIT OR X11; BSD; Apache-2.0; Unlicense\""
54
57
  },
55
58
  "dependencies": {
56
- "pdf-to-png-converter": "~3.14.0",
57
- "png-visual-compare": "~4.1.0"
59
+ "pdf-to-png-converter": "~3.18.0",
60
+ "png-visual-compare": "~5.1.0"
58
61
  },
59
62
  "devDependencies": {
60
- "@types/node": "^25.3.2",
63
+ "@types/node": "^25.6.0",
61
64
  "@types/pngjs": "^6.0.5",
62
- "@vitest/coverage-v8": "^4.0.18",
63
- "eslint": "^10.0.2",
65
+ "@vitest/coverage-v8": "^4.1.4",
66
+ "eslint": "^10.2.0",
64
67
  "jiti": "^2.6.1",
65
68
  "rimraf": "^6.1.3",
66
- "ts-node": "^10.9.2",
67
- "typescript": "^5.9.3",
68
- "typescript-eslint": "^8.56.1",
69
- "vitest": "^4.0.18"
69
+ "typescript": "^6.0.2",
70
+ "typescript-eslint": "^8.58.1",
71
+ "vitest": "^4.1.4"
70
72
  },
71
73
  "engines": {
72
- "node": ">=20",
73
- "yarn": "please-use-npm"
74
+ "node": ">=20"
74
75
  }
75
76
  }