@u1f992/pdfdiff 0.1.0 → 0.2.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/dist/browser.js +218 -125
- package/dist/browser.js.map +1 -1
- package/dist/cli.js +235 -170
- package/dist/cli.js.map +1 -1
- package/dist/coi-serviceworker.min.js +2 -0
- package/dist/diff.d.ts +3 -3
- package/dist/diff.d.ts.map +1 -1
- package/dist/image.d.ts +1 -1
- package/dist/image.d.ts.map +1 -1
- package/dist/index.d.ts +6 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.html +6 -1
- package/dist/index.js +207 -125
- package/dist/index.js.map +1 -1
- package/dist/index.test.d.ts +2 -0
- package/dist/index.test.d.ts.map +1 -0
- package/dist/jimp.d.ts +1 -1
- package/dist/jimp.d.ts.map +1 -1
- package/dist/pdf.d.ts +2 -374
- package/dist/pdf.d.ts.map +1 -1
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/worker.d.ts +48 -1
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +3184 -38
- package/dist/worker.js.map +1 -1
- package/package.json +4 -2
- package/rollup.config.js +29 -42
- package/scripts/version.ts +35 -0
- package/src/browser.ts +17 -1
- package/src/cli.ts +23 -11
- package/src/diff.ts +42 -27
- package/src/image.ts +31 -9
- package/src/index.html +6 -1
- package/src/index.test.ts +97 -0
- package/src/index.ts +210 -106
- package/src/jimp.ts +1 -0
- package/src/pdf.ts +34 -2
- package/src/worker.ts +157 -39
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@u1f992/pdfdiff",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Visualize and quantify differences between two PDF files.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
"scripts": {
|
|
10
10
|
"test": "node --test",
|
|
11
11
|
"test:cli": "node src/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
|
-
"build": "
|
|
12
|
+
"build:version": "node scripts/version.ts",
|
|
13
|
+
"build": "npm run build:version && rollup -c",
|
|
13
14
|
"serve": "npm run build && http-server dist"
|
|
14
15
|
},
|
|
15
16
|
"repository": {
|
|
@@ -30,6 +31,7 @@
|
|
|
30
31
|
"@rollup/plugin-node-resolve": "^16.0.1",
|
|
31
32
|
"@rollup/plugin-typescript": "^12.1.4",
|
|
32
33
|
"@types/node": "^22.18.7",
|
|
34
|
+
"coi-serviceworker": "^0.1.7",
|
|
33
35
|
"http-server": "^14.1.1",
|
|
34
36
|
"nodehog": "^0.1.2",
|
|
35
37
|
"prettier": "^3.5.3",
|
package/rollup.config.js
CHANGED
|
@@ -15,6 +15,10 @@ const plugins = [
|
|
|
15
15
|
src: "node_modules/mupdf/dist/mupdf-wasm.wasm",
|
|
16
16
|
dest: "dist",
|
|
17
17
|
},
|
|
18
|
+
{
|
|
19
|
+
src: "node_modules/coi-serviceworker/coi-serviceworker.min.js",
|
|
20
|
+
dest: "dist",
|
|
21
|
+
},
|
|
18
22
|
{
|
|
19
23
|
src: "src/index.html",
|
|
20
24
|
dest: "dist",
|
|
@@ -27,6 +31,26 @@ const plugins = [
|
|
|
27
31
|
}),
|
|
28
32
|
];
|
|
29
33
|
|
|
34
|
+
const jimpAlias = alias({
|
|
35
|
+
entries: [
|
|
36
|
+
{
|
|
37
|
+
find: "jimp",
|
|
38
|
+
replacement: path.resolve("node_modules/jimp/dist/browser/index.js"),
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const webWorkerAlias = alias({
|
|
44
|
+
entries: [
|
|
45
|
+
{
|
|
46
|
+
find: "web-worker",
|
|
47
|
+
replacement: path.resolve(
|
|
48
|
+
"node_modules/web-worker/dist/browser/index.cjs",
|
|
49
|
+
),
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
});
|
|
53
|
+
|
|
30
54
|
const rollupConfig = defineConfig([
|
|
31
55
|
{
|
|
32
56
|
input: "src/index.ts",
|
|
@@ -35,22 +59,8 @@ const rollupConfig = defineConfig([
|
|
|
35
59
|
sourcemap: true,
|
|
36
60
|
},
|
|
37
61
|
plugins: [
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
{
|
|
41
|
-
find: "jimp",
|
|
42
|
-
replacement: path.resolve(
|
|
43
|
-
"node_modules/jimp/dist/browser/index.js",
|
|
44
|
-
),
|
|
45
|
-
},
|
|
46
|
-
{
|
|
47
|
-
find: "web-worker",
|
|
48
|
-
replacement: path.resolve(
|
|
49
|
-
"node_modules/web-worker/dist/browser/index.cjs",
|
|
50
|
-
),
|
|
51
|
-
},
|
|
52
|
-
],
|
|
53
|
-
}),
|
|
62
|
+
jimpAlias,
|
|
63
|
+
webWorkerAlias,
|
|
54
64
|
typescript({ tsconfig: "./tsconfig.json" }),
|
|
55
65
|
...plugins,
|
|
56
66
|
],
|
|
@@ -62,16 +72,7 @@ const rollupConfig = defineConfig([
|
|
|
62
72
|
sourcemap: true,
|
|
63
73
|
},
|
|
64
74
|
plugins: [
|
|
65
|
-
|
|
66
|
-
entries: [
|
|
67
|
-
{
|
|
68
|
-
find: "jimp",
|
|
69
|
-
replacement: path.resolve(
|
|
70
|
-
"node_modules/jimp/dist/browser/index.js",
|
|
71
|
-
),
|
|
72
|
-
},
|
|
73
|
-
],
|
|
74
|
-
}),
|
|
75
|
+
jimpAlias,
|
|
75
76
|
typescript({ tsconfig: "./tsconfig.json" }),
|
|
76
77
|
...plugins,
|
|
77
78
|
],
|
|
@@ -96,22 +97,8 @@ const rollupConfig = defineConfig([
|
|
|
96
97
|
sourcemap: true,
|
|
97
98
|
},
|
|
98
99
|
plugins: [
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
{
|
|
102
|
-
find: "jimp",
|
|
103
|
-
replacement: path.resolve(
|
|
104
|
-
"node_modules/jimp/dist/browser/index.js",
|
|
105
|
-
),
|
|
106
|
-
},
|
|
107
|
-
{
|
|
108
|
-
find: "web-worker",
|
|
109
|
-
replacement: path.resolve(
|
|
110
|
-
"node_modules/web-worker/dist/browser/index.cjs",
|
|
111
|
-
),
|
|
112
|
-
},
|
|
113
|
-
],
|
|
114
|
-
}),
|
|
100
|
+
jimpAlias,
|
|
101
|
+
webWorkerAlias,
|
|
115
102
|
typescript({ tsconfig: "./tsconfig.json" }),
|
|
116
103
|
...plugins,
|
|
117
104
|
],
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { execSync } from "node:child_process";
|
|
3
|
+
|
|
4
|
+
const version = JSON.parse(readFileSync("package.json", "utf-8")).version;
|
|
5
|
+
|
|
6
|
+
let suffix = "";
|
|
7
|
+
try {
|
|
8
|
+
execSync(`git describe --tags --match "v${version}" --exact-match`, {
|
|
9
|
+
stdio: "ignore",
|
|
10
|
+
});
|
|
11
|
+
} catch {
|
|
12
|
+
try {
|
|
13
|
+
const hash = execSync("git rev-parse --short HEAD", {
|
|
14
|
+
encoding: "utf-8",
|
|
15
|
+
}).trim();
|
|
16
|
+
suffix = `+${hash}`;
|
|
17
|
+
} catch {
|
|
18
|
+
// not in a git repo
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const dirty = execSync("git status --porcelain", {
|
|
24
|
+
encoding: "utf-8",
|
|
25
|
+
}).trim();
|
|
26
|
+
if (dirty) {
|
|
27
|
+
suffix += ".dirty";
|
|
28
|
+
}
|
|
29
|
+
} catch {
|
|
30
|
+
// not in a git repo
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const full = version + suffix;
|
|
34
|
+
writeFileSync("src/version.ts", `export const VERSION = "${full}";\n`, "utf-8");
|
|
35
|
+
console.log(full);
|
package/src/browser.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
/// <reference lib="dom" />
|
|
2
2
|
|
|
3
|
-
import * as pdfdiff from "./index.
|
|
3
|
+
import * as pdfdiff from "./index.ts";
|
|
4
|
+
import { VERSION } from "./version.ts";
|
|
5
|
+
|
|
6
|
+
const versionEl = document.getElementById("version");
|
|
7
|
+
if (versionEl) versionEl.textContent = "v" + VERSION;
|
|
4
8
|
|
|
5
9
|
async function readFileAsUint8Array(file: File): Promise<Uint8Array> {
|
|
6
10
|
return new Promise((resolve, reject) => {
|
|
@@ -62,6 +66,17 @@ document
|
|
|
62
66
|
throw new Error();
|
|
63
67
|
}
|
|
64
68
|
|
|
69
|
+
const workers = ((
|
|
70
|
+
val = (document.getElementById("workers") as HTMLInputElement | null)
|
|
71
|
+
?.value,
|
|
72
|
+
) => (typeof val !== "undefined" ? parseInt(val, 10) : undefined))();
|
|
73
|
+
if (
|
|
74
|
+
typeof workers !== "undefined" &&
|
|
75
|
+
(Number.isNaN(workers) || workers < 1)
|
|
76
|
+
) {
|
|
77
|
+
throw new Error();
|
|
78
|
+
}
|
|
79
|
+
|
|
65
80
|
const additionColorHex = (
|
|
66
81
|
document.getElementById("addition-color") as HTMLInputElement | null
|
|
67
82
|
)?.value;
|
|
@@ -93,6 +108,7 @@ document
|
|
|
93
108
|
if (alpha !== undefined) options.alpha = alpha;
|
|
94
109
|
if (pdfMask !== undefined) options.mask = pdfMask;
|
|
95
110
|
if (align !== undefined) options.align = align;
|
|
111
|
+
if (workers !== undefined) options.workers = workers;
|
|
96
112
|
if (additionColor || deletionColor || modificationColor) {
|
|
97
113
|
options.pallet = {};
|
|
98
114
|
if (additionColor) options.pallet.addition = additionColor;
|
package/src/cli.ts
CHANGED
|
@@ -28,7 +28,8 @@ import {
|
|
|
28
28
|
parseHex,
|
|
29
29
|
formatHex,
|
|
30
30
|
visualizeDifferences,
|
|
31
|
-
} from "./index.
|
|
31
|
+
} from "./index.ts";
|
|
32
|
+
import { VERSION } from "./version.ts";
|
|
32
33
|
|
|
33
34
|
const {
|
|
34
35
|
positionals,
|
|
@@ -40,6 +41,7 @@ const {
|
|
|
40
41
|
"addition-color": additionColorHex,
|
|
41
42
|
"deletion-color": deletionColorHex,
|
|
42
43
|
"modification-color": modificationColorHex,
|
|
44
|
+
workers: workers_,
|
|
43
45
|
version,
|
|
44
46
|
help,
|
|
45
47
|
},
|
|
@@ -53,6 +55,7 @@ const {
|
|
|
53
55
|
"addition-color": { type: "string" },
|
|
54
56
|
"deletion-color": { type: "string" },
|
|
55
57
|
"modification-color": { type: "string" },
|
|
58
|
+
workers: { type: "string" },
|
|
56
59
|
version: { type: "boolean", short: "v" },
|
|
57
60
|
help: { type: "boolean", short: "h" },
|
|
58
61
|
},
|
|
@@ -72,22 +75,22 @@ OPTIONS:
|
|
|
72
75
|
--addition-color <#HEX> default: ${formatHex(defaultOptions.pallet.addition)}
|
|
73
76
|
--deletion-color <#HEX> default: ${formatHex(defaultOptions.pallet.deletion)}
|
|
74
77
|
--modification-color <#HEX> default: ${formatHex(defaultOptions.pallet.modification)}
|
|
78
|
+
--workers <N> default: ${defaultOptions.workers}
|
|
75
79
|
-v, --version
|
|
76
80
|
-h, --help
|
|
81
|
+
|
|
82
|
+
NOTES:
|
|
83
|
+
Approximate per-worker memory:
|
|
84
|
+
a_size_MB + b_size_MB [+ mask_size_MB] (PDF buffers in wasm)
|
|
85
|
+
+ 300 MB (mupdf + V8 base)
|
|
86
|
+
+ (dpi / 150)^2 * 50 MB (pixmap working set)
|
|
87
|
+
The main process adds ~500 MB - 1 GB (varies with --workers).
|
|
88
|
+
Choose --workers so the total stays under ~80% of available memory.
|
|
77
89
|
`);
|
|
78
90
|
process.exit(0);
|
|
79
91
|
}
|
|
80
92
|
if (version) {
|
|
81
|
-
|
|
82
|
-
const versionStr = JSON.parse(
|
|
83
|
-
fs.readFileSync(new URL("../package.json", import.meta.url), {
|
|
84
|
-
encoding: "utf-8",
|
|
85
|
-
}),
|
|
86
|
-
).version;
|
|
87
|
-
console.log(versionStr);
|
|
88
|
-
} catch {
|
|
89
|
-
console.log("unknown");
|
|
90
|
-
}
|
|
93
|
+
console.log(VERSION);
|
|
91
94
|
process.exit(0);
|
|
92
95
|
}
|
|
93
96
|
|
|
@@ -137,6 +140,14 @@ if (
|
|
|
137
140
|
throw new Error("Invalid color format");
|
|
138
141
|
}
|
|
139
142
|
|
|
143
|
+
const workers =
|
|
144
|
+
typeof workers_ !== "undefined"
|
|
145
|
+
? parseInt(workers_, 10)
|
|
146
|
+
: defaultOptions.workers;
|
|
147
|
+
if (Number.isNaN(workers) || workers < 1) {
|
|
148
|
+
throw new Error("Invalid workers value");
|
|
149
|
+
}
|
|
150
|
+
|
|
140
151
|
fs.mkdirSync(outDir, { recursive: true });
|
|
141
152
|
for await (const [
|
|
142
153
|
i,
|
|
@@ -152,6 +163,7 @@ for await (const [
|
|
|
152
163
|
deletion: deletionColor,
|
|
153
164
|
modification: modificationColor,
|
|
154
165
|
},
|
|
166
|
+
workers,
|
|
155
167
|
}),
|
|
156
168
|
1,
|
|
157
169
|
)) {
|
package/src/diff.ts
CHANGED
|
@@ -15,11 +15,9 @@
|
|
|
15
15
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import
|
|
19
|
-
|
|
20
|
-
import { type
|
|
21
|
-
import { alignSize, createEmptyImage, type AlignStrategy } from "./image.js";
|
|
22
|
-
import { type RGBAColor } from "./rgba-color.js";
|
|
18
|
+
import { type JimpInstance } from "./jimp.ts";
|
|
19
|
+
import { alignSize, createEmptyImage, type AlignStrategy } from "./image.ts";
|
|
20
|
+
import { type RGBAColor } from "./rgba-color.ts";
|
|
23
21
|
|
|
24
22
|
export type Pallet = {
|
|
25
23
|
addition: RGBAColor;
|
|
@@ -35,34 +33,51 @@ export function drawDifference(
|
|
|
35
33
|
align: AlignStrategy,
|
|
36
34
|
) {
|
|
37
35
|
const [aNew, bNew, maskNew] = alignSize([a, b, mask], align);
|
|
36
|
+
const width = aNew.width;
|
|
37
|
+
const height = aNew.height;
|
|
38
|
+
const aData = aNew.bitmap.data;
|
|
39
|
+
const bData = bNew.bitmap.data;
|
|
40
|
+
const mData = maskNew.bitmap.data;
|
|
38
41
|
|
|
39
|
-
const
|
|
40
|
-
const
|
|
41
|
-
const modColor = jimp.rgbaToInt(...pallet.modification);
|
|
42
|
+
const diffImage = createEmptyImage(width, height);
|
|
43
|
+
const dData = diffImage.bitmap.data;
|
|
42
44
|
|
|
43
|
-
const
|
|
44
|
-
const
|
|
45
|
-
const
|
|
46
|
-
const modification = [] as [number, number][];
|
|
45
|
+
const addition: [number, number][] = [];
|
|
46
|
+
const deletion: [number, number][] = [];
|
|
47
|
+
const modification: [number, number][] = [];
|
|
47
48
|
|
|
48
|
-
for (let x = 0; x <
|
|
49
|
-
for (let y = 0; y <
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
49
|
+
for (let x = 0; x < width; x++) {
|
|
50
|
+
for (let y = 0; y < height; y++) {
|
|
51
|
+
const idx = (y * width + x) * 4;
|
|
52
|
+
if (mData[idx + 3]! !== 0) continue;
|
|
53
|
+
const aAlpha = aData[idx + 3]!;
|
|
54
|
+
const bAlpha = bData[idx + 3]!;
|
|
55
|
+
if (
|
|
56
|
+
aAlpha === bAlpha &&
|
|
57
|
+
aData[idx] === bData[idx] &&
|
|
58
|
+
aData[idx + 1] === bData[idx + 1] &&
|
|
59
|
+
aData[idx + 2] === bData[idx + 2]
|
|
60
|
+
) {
|
|
56
61
|
continue;
|
|
57
62
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
63
|
+
if (aAlpha === 0 && bAlpha === 0) continue;
|
|
64
|
+
let target: [number, number][];
|
|
65
|
+
let color: Readonly<RGBAColor>;
|
|
66
|
+
if (aAlpha === 0) {
|
|
67
|
+
target = addition;
|
|
68
|
+
color = pallet.addition;
|
|
69
|
+
} else if (bAlpha === 0) {
|
|
70
|
+
target = deletion;
|
|
71
|
+
color = pallet.deletion;
|
|
72
|
+
} else {
|
|
73
|
+
target = modification;
|
|
74
|
+
color = pallet.modification;
|
|
75
|
+
}
|
|
64
76
|
target.push([x, y]);
|
|
65
|
-
|
|
77
|
+
dData[idx] = color[0];
|
|
78
|
+
dData[idx + 1] = color[1];
|
|
79
|
+
dData[idx + 2] = color[2];
|
|
80
|
+
dData[idx + 3] = color[3];
|
|
66
81
|
}
|
|
67
82
|
}
|
|
68
83
|
|
package/src/image.ts
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import * as jimp from "jimp";
|
|
19
|
-
import { type JimpInstance } from "./jimp.
|
|
19
|
+
import { type JimpInstance } from "./jimp.ts";
|
|
20
20
|
|
|
21
21
|
export function createEmptyImage(width: number, height: number) {
|
|
22
22
|
return new jimp.Jimp({
|
|
@@ -117,12 +117,34 @@ export function composeLayers(
|
|
|
117
117
|
canvasHeight: number,
|
|
118
118
|
layers: [JimpInstance, number][],
|
|
119
119
|
) {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
120
|
+
const canvas = createEmptyImage(canvasWidth, canvasHeight);
|
|
121
|
+
const dData = canvas.bitmap.data;
|
|
122
|
+
for (const [image, opacity] of layers) {
|
|
123
|
+
const sData = image.bitmap.data;
|
|
124
|
+
const srcWidth = image.width;
|
|
125
|
+
const w = Math.min(canvasWidth, srcWidth);
|
|
126
|
+
const h = Math.min(canvasHeight, image.height);
|
|
127
|
+
for (let y = 0; y < h; y++) {
|
|
128
|
+
for (let x = 0; x < w; x++) {
|
|
129
|
+
const dIdx = (y * canvasWidth + x) * 4;
|
|
130
|
+
const sIdx = (y * srcWidth + x) * 4;
|
|
131
|
+
const sa = (sData[sIdx + 3]! / 255) * opacity;
|
|
132
|
+
if (sa === 0) continue;
|
|
133
|
+
const da = dData[dIdx + 3]! / 255;
|
|
134
|
+
const oa = sa + da * (1 - sa);
|
|
135
|
+
if (oa === 0) continue;
|
|
136
|
+
const sw = sa / oa;
|
|
137
|
+
const dw = (da * (1 - sa)) / oa;
|
|
138
|
+
dData[dIdx] = Math.round(sData[sIdx]! * sw + dData[dIdx]! * dw);
|
|
139
|
+
dData[dIdx + 1] = Math.round(
|
|
140
|
+
sData[sIdx + 1]! * sw + dData[dIdx + 1]! * dw,
|
|
141
|
+
);
|
|
142
|
+
dData[dIdx + 2] = Math.round(
|
|
143
|
+
sData[sIdx + 2]! * sw + dData[dIdx + 2]! * dw,
|
|
144
|
+
);
|
|
145
|
+
dData[dIdx + 3] = Math.round(oa * 255);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return canvas;
|
|
128
150
|
}
|
package/src/index.html
CHANGED
|
@@ -5,10 +5,11 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>pdfdiff</title>
|
|
7
7
|
<link rel="stylesheet" href="./style.css" />
|
|
8
|
+
<script src="./coi-serviceworker.min.js"></script>
|
|
8
9
|
<script type="module" src="./browser.js"></script>
|
|
9
10
|
</head>
|
|
10
11
|
<body>
|
|
11
|
-
<h1>pdfdiff
|
|
12
|
+
<h1>pdfdiff <small id="version"></small></h1>
|
|
12
13
|
<div class="form-container">
|
|
13
14
|
<form id="pdf-diff-form">
|
|
14
15
|
<div>
|
|
@@ -46,6 +47,10 @@
|
|
|
46
47
|
<option value="bottom-right">bottom-right</option>
|
|
47
48
|
</select>
|
|
48
49
|
</div>
|
|
50
|
+
<div>
|
|
51
|
+
<label for="workers">Workers:</label>
|
|
52
|
+
<input type="number" id="workers" value="1" min="1" />
|
|
53
|
+
</div>
|
|
49
54
|
<div>
|
|
50
55
|
<label for="addition-color">Addition:</label>
|
|
51
56
|
<input type="color" id="addition-color" value="#4cae4f" />
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (C) 2025 Koutaro Mukai
|
|
3
|
+
*
|
|
4
|
+
* This program is free software: you can redistribute it and/or modify
|
|
5
|
+
* it under the terms of the GNU General Public License as published by
|
|
6
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
* (at your option) any later version.
|
|
8
|
+
*
|
|
9
|
+
* This program is distributed in the hope that it will be useful,
|
|
10
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
* GNU General Public License for more details.
|
|
13
|
+
*
|
|
14
|
+
* You should have received a copy of the GNU General Public License
|
|
15
|
+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import assert from "node:assert/strict";
|
|
19
|
+
import fs from "node:fs";
|
|
20
|
+
import test from "node:test";
|
|
21
|
+
|
|
22
|
+
import {
|
|
23
|
+
defaultOptions,
|
|
24
|
+
formatHex,
|
|
25
|
+
isValidAlignStrategy,
|
|
26
|
+
parseHex,
|
|
27
|
+
visualizeDifferences,
|
|
28
|
+
withIndex,
|
|
29
|
+
} from "./index.ts";
|
|
30
|
+
|
|
31
|
+
const fixtures = new URL("../test/", import.meta.url);
|
|
32
|
+
const readFixture = (name: string) =>
|
|
33
|
+
new Uint8Array(fs.readFileSync(new URL(name, fixtures)));
|
|
34
|
+
|
|
35
|
+
test("re-exports are exposed as runtime values", () => {
|
|
36
|
+
assert.equal(typeof withIndex, "function");
|
|
37
|
+
assert.equal(typeof isValidAlignStrategy, "function");
|
|
38
|
+
assert.equal(typeof parseHex, "function");
|
|
39
|
+
assert.equal(typeof formatHex, "function");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("defaultOptions", () => {
|
|
43
|
+
assert.deepEqual(defaultOptions, {
|
|
44
|
+
dpi: 150,
|
|
45
|
+
alpha: true,
|
|
46
|
+
mask: undefined,
|
|
47
|
+
align: "resize",
|
|
48
|
+
pallet: {
|
|
49
|
+
addition: [0x4c, 0xae, 0x4f, 0xff],
|
|
50
|
+
deletion: [0xff, 0x57, 0x24, 0xff],
|
|
51
|
+
modification: [0xff, 0xc1, 0x05, 0xff],
|
|
52
|
+
},
|
|
53
|
+
workers: 1,
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("isValidAlignStrategy", async (ctx) => {
|
|
58
|
+
for (const s of [
|
|
59
|
+
"resize",
|
|
60
|
+
"top-left",
|
|
61
|
+
"top-center",
|
|
62
|
+
"top-right",
|
|
63
|
+
"middle-left",
|
|
64
|
+
"middle-center",
|
|
65
|
+
"middle-right",
|
|
66
|
+
"bottom-left",
|
|
67
|
+
"bottom-center",
|
|
68
|
+
"bottom-right",
|
|
69
|
+
]) {
|
|
70
|
+
await ctx.test(s, () => {
|
|
71
|
+
assert.equal(isValidAlignStrategy(s), true);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
await ctx.test("invalid", () => {
|
|
75
|
+
assert.equal(isValidAlignStrategy("invalid"), false);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("visualizeDifferences pins counts for fixtures at dpi 300", async () => {
|
|
80
|
+
const a = readFixture("a.pdf");
|
|
81
|
+
const b = readFixture("b.pdf");
|
|
82
|
+
const mask = readFixture("mask.pdf");
|
|
83
|
+
|
|
84
|
+
const pages: { addition: number; deletion: number; modification: number }[] =
|
|
85
|
+
[];
|
|
86
|
+
for await (const page of visualizeDifferences(a, b, { dpi: 300, mask })) {
|
|
87
|
+
pages.push({
|
|
88
|
+
addition: page.addition.length,
|
|
89
|
+
deletion: page.deletion.length,
|
|
90
|
+
modification: page.modification.length,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
assert.deepEqual(pages, [
|
|
95
|
+
{ addition: 7500, deletion: 7500, modification: 7500 },
|
|
96
|
+
]);
|
|
97
|
+
});
|