@u1f992/pdfdiff 0.3.0 → 0.3.1
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/.github/workflows/gh-pages.yml +6 -6
- package/.vscode/extensions.json +1 -1
- package/.vscode/settings.json +1 -1
- package/README.md +7 -0
- package/dist/browser.js +6 -2
- package/dist/browser.js.map +1 -1
- package/dist/cli.js +32 -3
- package/dist/cli.js.map +1 -1
- package/dist/decode.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/pdf.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/worker.js.map +1 -1
- package/package.json +2 -2
- package/prettier.config.js +1 -1
- package/src/cli.ts +29 -0
- package/src/decode.ts +1 -3
- package/src/index.ts +9 -2
- package/src/pdf.ts +3 -1
- package/tsconfig.json +53 -50
- package/wasm/core.c +6 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@u1f992/pdfdiff",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Visualize and quantify differences between two PDF files.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"test": "node --test",
|
|
11
|
-
"test:cli": "node
|
|
11
|
+
"test:cli": "node dist/cli.js test/a.pdf test/b.pdf out --mask test/mask.pdf --dpi 300 && echo \"expected: Page 1, Addition: 7500, Deletion: 7500, Modification: 7500\"",
|
|
12
12
|
"build:version": "node scripts/version.ts",
|
|
13
13
|
"build:wasm": "docker run --rm --volume .:/workdir --workdir /workdir --user root --entrypoint bash emscripten/emsdk:5.0.7 scripts/build-wasm.sh",
|
|
14
14
|
"format:wasm": "docker run --rm --volume .:/workdir --workdir /workdir --user root --entrypoint bash emscripten/emsdk:5.0.7 scripts/build-wasm.sh format",
|
package/prettier.config.js
CHANGED
package/src/cli.ts
CHANGED
|
@@ -70,6 +70,17 @@ class PngWriterPool {
|
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
// Errors always exit 2, following diff(1)'s 0/1/2 convention, so that with
|
|
74
|
+
// --exit-code a caller can tell "differences found" (1) from a failed run.
|
|
75
|
+
process.on("uncaughtException", (err) => {
|
|
76
|
+
console.error(err);
|
|
77
|
+
process.exit(2);
|
|
78
|
+
});
|
|
79
|
+
process.on("unhandledRejection", (err) => {
|
|
80
|
+
console.error(err);
|
|
81
|
+
process.exit(2);
|
|
82
|
+
});
|
|
83
|
+
|
|
73
84
|
const _wallSpan = perf.span("cli.wallTotal_ms");
|
|
74
85
|
|
|
75
86
|
const {
|
|
@@ -83,6 +94,7 @@ const {
|
|
|
83
94
|
"deletion-color": deletionColorHex,
|
|
84
95
|
"modification-color": modificationColorHex,
|
|
85
96
|
workers: workers_,
|
|
97
|
+
"exit-code": exitCode_,
|
|
86
98
|
version,
|
|
87
99
|
help,
|
|
88
100
|
},
|
|
@@ -97,6 +109,7 @@ const {
|
|
|
97
109
|
"deletion-color": { type: "string" },
|
|
98
110
|
"modification-color": { type: "string" },
|
|
99
111
|
workers: { type: "string" },
|
|
112
|
+
"exit-code": { type: "boolean" },
|
|
100
113
|
version: { type: "boolean", short: "v" },
|
|
101
114
|
help: { type: "boolean", short: "h" },
|
|
102
115
|
},
|
|
@@ -117,9 +130,15 @@ OPTIONS:
|
|
|
117
130
|
--deletion-color <#HEX> default: ${formatHex(defaultOptions.pallet.deletion)}
|
|
118
131
|
--modification-color <#HEX> default: ${formatHex(defaultOptions.pallet.modification)}
|
|
119
132
|
--workers <N> default: ${defaultOptions.workers}
|
|
133
|
+
--exit-code exit 1 if differences are found
|
|
120
134
|
-v, --version
|
|
121
135
|
-h, --help
|
|
122
136
|
|
|
137
|
+
EXIT STATUS:
|
|
138
|
+
0 success (with --exit-code: no differences found)
|
|
139
|
+
1 differences found (only with --exit-code)
|
|
140
|
+
2 error
|
|
141
|
+
|
|
123
142
|
NOTES:
|
|
124
143
|
Pages are rendered with Ghostscript (gs-wasm). Each page render spins up a
|
|
125
144
|
transient Ghostscript instance (~26 MB WASM binary plus a rasterization
|
|
@@ -192,12 +211,15 @@ if (Number.isNaN(workers) || workers < 1) {
|
|
|
192
211
|
throw new Error("Invalid workers value");
|
|
193
212
|
}
|
|
194
213
|
|
|
214
|
+
const exitCodeOnDiff = exitCode_ ?? false;
|
|
215
|
+
|
|
195
216
|
fs.mkdirSync(outDir, { recursive: true });
|
|
196
217
|
const writerPool = new PngWriterPool(
|
|
197
218
|
workers,
|
|
198
219
|
new URL("./cli-png-worker.js", import.meta.url),
|
|
199
220
|
);
|
|
200
221
|
const pendingWrites: Promise<void>[] = [];
|
|
222
|
+
let hasDiff = false;
|
|
201
223
|
|
|
202
224
|
const _loopSpan = perf.span("cli.loopWall_ms");
|
|
203
225
|
for await (const [
|
|
@@ -221,6 +243,9 @@ for await (const [
|
|
|
221
243
|
console.log(
|
|
222
244
|
`Page ${i}, Addition: ${addition.length}, Deletion: ${deletion.length}, Modification: ${modification.length}`,
|
|
223
245
|
);
|
|
246
|
+
if (addition.length > 0 || deletion.length > 0 || modification.length > 0) {
|
|
247
|
+
hasDiff = true;
|
|
248
|
+
}
|
|
224
249
|
const dir = path.join(outDir, i.toString(10));
|
|
225
250
|
fs.mkdirSync(dir, { recursive: true });
|
|
226
251
|
const sSubmit = perf.span("cli.poolSubmit_ms");
|
|
@@ -256,6 +281,10 @@ await writerPool.terminate();
|
|
|
256
281
|
_loopSpan.stop();
|
|
257
282
|
_wallSpan.stop();
|
|
258
283
|
|
|
284
|
+
if (exitCodeOnDiff && hasDiff) {
|
|
285
|
+
process.exitCode = 1;
|
|
286
|
+
}
|
|
287
|
+
|
|
259
288
|
if (perf.enabled) {
|
|
260
289
|
const counters = perf.dump();
|
|
261
290
|
process.stderr.write("\n=== PERF ===\n");
|
package/src/decode.ts
CHANGED
|
@@ -8,8 +8,6 @@ import type { JimpInstance } from "./jimp.ts";
|
|
|
8
8
|
* `jimp.fromBuffer` accepts an ArrayBuffer directly, which keeps this usable in
|
|
9
9
|
* the browser without relying on Node's `Buffer`.
|
|
10
10
|
*/
|
|
11
|
-
export async function decodePng(
|
|
12
|
-
png: ArrayBuffer,
|
|
13
|
-
): Promise<JimpInstance> {
|
|
11
|
+
export async function decodePng(png: ArrayBuffer): Promise<JimpInstance> {
|
|
14
12
|
return (await jimp.Jimp.fromBuffer(png)) as JimpInstance;
|
|
15
13
|
}
|
package/src/index.ts
CHANGED
|
@@ -249,7 +249,11 @@ export async function* visualizeDifferences(
|
|
|
249
249
|
const makeSlots = (count: number): Slot[] =>
|
|
250
250
|
Array.from({ length: maxPages }, (_, i) => {
|
|
251
251
|
if (i >= count) {
|
|
252
|
-
return {
|
|
252
|
+
return {
|
|
253
|
+
p: Promise.resolve(null),
|
|
254
|
+
resolve: () => {},
|
|
255
|
+
reject: () => {},
|
|
256
|
+
};
|
|
253
257
|
}
|
|
254
258
|
let resolve!: (v: Uint8Array<ArrayBuffer> | null) => void;
|
|
255
259
|
let reject!: (e: unknown) => void;
|
|
@@ -279,7 +283,10 @@ export async function* visualizeDifferences(
|
|
|
279
283
|
const totalRenderPages = aPages + bPages + (hasMask ? maskPages : 0);
|
|
280
284
|
const chunkSize = Math.max(
|
|
281
285
|
1,
|
|
282
|
-
Math.min(
|
|
286
|
+
Math.min(
|
|
287
|
+
maxPages,
|
|
288
|
+
Math.max(MIN_CHUNK, Math.ceil(totalRenderPages / (2 * R))),
|
|
289
|
+
),
|
|
283
290
|
);
|
|
284
291
|
type Task = { bytes: Uint8Array; start: number; end: number; slots: Slot[] };
|
|
285
292
|
const tasks: Task[] = [];
|
package/src/pdf.ts
CHANGED
|
@@ -95,7 +95,9 @@ export async function renderPageRangePng(
|
|
|
95
95
|
});
|
|
96
96
|
sRender.stop();
|
|
97
97
|
if (exitCode !== 0) {
|
|
98
|
-
throw new Error(
|
|
98
|
+
throw new Error(
|
|
99
|
+
`gs render failed (pages ${first}-${last}, exit ${exitCode})`,
|
|
100
|
+
);
|
|
99
101
|
}
|
|
100
102
|
const result = new Map<number, Uint8Array<ArrayBuffer>>();
|
|
101
103
|
for (let k = 1; k <= pageCount; k++) {
|
package/tsconfig.json
CHANGED
|
@@ -1,50 +1,53 @@
|
|
|
1
|
-
{
|
|
2
|
-
// Visit https://aka.ms/tsconfig to read more about this file
|
|
3
|
-
"compilerOptions": {
|
|
4
|
-
// File Layout
|
|
5
|
-
"rootDir": "./src",
|
|
6
|
-
"outDir": "./dist",
|
|
7
|
-
|
|
8
|
-
// Environment Settings
|
|
9
|
-
// See also https://aka.ms/tsconfig/module
|
|
10
|
-
"module": "esnext",
|
|
11
|
-
"target": "esnext",
|
|
12
|
-
// "types": [],
|
|
13
|
-
// For nodejs:
|
|
14
|
-
"lib": ["esnext", "WebWorker", "DOM"],
|
|
15
|
-
"types": ["node"],
|
|
16
|
-
// and npm install -D @types/node
|
|
17
|
-
|
|
18
|
-
// Other Outputs
|
|
19
|
-
"sourceMap": true,
|
|
20
|
-
"declaration": true,
|
|
21
|
-
"declarationMap": true,
|
|
22
|
-
|
|
23
|
-
// Stricter Typechecking Options
|
|
24
|
-
"noUncheckedIndexedAccess": true,
|
|
25
|
-
"exactOptionalPropertyTypes": true,
|
|
26
|
-
|
|
27
|
-
// Style Options
|
|
28
|
-
// "noImplicitReturns": true,
|
|
29
|
-
// "noImplicitOverride": true,
|
|
30
|
-
// "noUnusedLocals": true,
|
|
31
|
-
// "noUnusedParameters": true,
|
|
32
|
-
// "noFallthroughCasesInSwitch": true,
|
|
33
|
-
// "noPropertyAccessFromIndexSignature": true,
|
|
34
|
-
|
|
35
|
-
// Recommended Options
|
|
36
|
-
"strict": true,
|
|
37
|
-
"jsx": "react-jsx",
|
|
38
|
-
"verbatimModuleSyntax": true,
|
|
39
|
-
"isolatedModules": true,
|
|
40
|
-
"noUncheckedSideEffectImports": true,
|
|
41
|
-
"moduleDetection": "force",
|
|
42
|
-
"skipLibCheck": true,
|
|
43
|
-
|
|
44
|
-
// With Rollup
|
|
45
|
-
"moduleResolution": "bundler",
|
|
46
|
-
"allowImportingTsExtensions": true,
|
|
47
|
-
"rewriteRelativeImportExtensions": true,
|
|
48
|
-
"noEmit": true
|
|
49
|
-
}
|
|
50
|
-
|
|
1
|
+
{
|
|
2
|
+
// Visit https://aka.ms/tsconfig to read more about this file
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
// File Layout
|
|
5
|
+
"rootDir": "./src",
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
|
|
8
|
+
// Environment Settings
|
|
9
|
+
// See also https://aka.ms/tsconfig/module
|
|
10
|
+
"module": "esnext",
|
|
11
|
+
"target": "esnext",
|
|
12
|
+
// "types": [],
|
|
13
|
+
// For nodejs:
|
|
14
|
+
"lib": ["esnext", "WebWorker", "DOM"],
|
|
15
|
+
"types": ["node"],
|
|
16
|
+
// and npm install -D @types/node
|
|
17
|
+
|
|
18
|
+
// Other Outputs
|
|
19
|
+
"sourceMap": true,
|
|
20
|
+
"declaration": true,
|
|
21
|
+
"declarationMap": true,
|
|
22
|
+
|
|
23
|
+
// Stricter Typechecking Options
|
|
24
|
+
"noUncheckedIndexedAccess": true,
|
|
25
|
+
"exactOptionalPropertyTypes": true,
|
|
26
|
+
|
|
27
|
+
// Style Options
|
|
28
|
+
// "noImplicitReturns": true,
|
|
29
|
+
// "noImplicitOverride": true,
|
|
30
|
+
// "noUnusedLocals": true,
|
|
31
|
+
// "noUnusedParameters": true,
|
|
32
|
+
// "noFallthroughCasesInSwitch": true,
|
|
33
|
+
// "noPropertyAccessFromIndexSignature": true,
|
|
34
|
+
|
|
35
|
+
// Recommended Options
|
|
36
|
+
"strict": true,
|
|
37
|
+
"jsx": "react-jsx",
|
|
38
|
+
"verbatimModuleSyntax": true,
|
|
39
|
+
"isolatedModules": true,
|
|
40
|
+
"noUncheckedSideEffectImports": true,
|
|
41
|
+
"moduleDetection": "force",
|
|
42
|
+
"skipLibCheck": true,
|
|
43
|
+
|
|
44
|
+
// With Rollup
|
|
45
|
+
"moduleResolution": "bundler",
|
|
46
|
+
"allowImportingTsExtensions": true,
|
|
47
|
+
"rewriteRelativeImportExtensions": true,
|
|
48
|
+
"noEmit": true
|
|
49
|
+
},
|
|
50
|
+
// Keep `tsc --noEmit` runnable as a type check: scripts/version.ts is run
|
|
51
|
+
// directly by node (type stripping) and lives outside rootDir.
|
|
52
|
+
"include": ["src"]
|
|
53
|
+
}
|
package/wasm/core.c
CHANGED
|
@@ -2,10 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
#include <math.h>
|
|
4
4
|
#include <stdlib.h>
|
|
5
|
-
#include <string.h>
|
|
6
5
|
|
|
7
6
|
#define COORD_INITIAL_CAPACITY 256
|
|
8
7
|
|
|
8
|
+
/* Zero constant for CoreResult; assigning it replaces struct-zeroing memset,
|
|
9
|
+
which clang-tidy's insecureAPI check rejects. */
|
|
10
|
+
static const CoreResult CORE_RESULT_ZERO = {0};
|
|
11
|
+
|
|
9
12
|
typedef struct {
|
|
10
13
|
int32_t *data;
|
|
11
14
|
int32_t count;
|
|
@@ -76,7 +79,7 @@ void core_result_free(CoreResult *r) {
|
|
|
76
79
|
free(r->addition_xy);
|
|
77
80
|
free(r->deletion_xy);
|
|
78
81
|
free(r->modification_xy);
|
|
79
|
-
|
|
82
|
+
*r = CORE_RESULT_ZERO;
|
|
80
83
|
}
|
|
81
84
|
|
|
82
85
|
int32_t process_page(const uint8_t *a_pixels, const uint8_t *b_pixels,
|
|
@@ -89,7 +92,7 @@ int32_t process_page(const uint8_t *a_pixels, const uint8_t *b_pixels,
|
|
|
89
92
|
return CORE_ERROR_INVALID;
|
|
90
93
|
}
|
|
91
94
|
|
|
92
|
-
|
|
95
|
+
*out = CORE_RESULT_ZERO;
|
|
93
96
|
|
|
94
97
|
size_t pixel_count = (size_t)width * (size_t)height;
|
|
95
98
|
size_t byte_count = pixel_count * 4;
|