@u1f992/pdfdiff 0.2.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 +37 -13
- package/dist/browser.js.map +1 -1
- package/dist/cli.js +35 -22
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.html +1 -1
- package/dist/index.js +34 -13
- package/dist/index.js.map +1 -1
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/worker.d.ts +7 -0
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +35 -24
- package/dist/worker.js.map +1 -1
- package/package.json +3 -2
- package/scripts/version.ts +35 -0
- package/src/browser.ts +4 -0
- package/src/cli.ts +2 -10
- package/src/index.html +1 -1
- package/src/index.ts +40 -25
- package/src/worker.ts +43 -22
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@u1f992/pdfdiff",
|
|
3
|
-
"version": "0.2.
|
|
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": {
|
|
@@ -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
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) => {
|
package/src/cli.ts
CHANGED
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
formatHex,
|
|
30
30
|
visualizeDifferences,
|
|
31
31
|
} from "./index.ts";
|
|
32
|
+
import { VERSION } from "./version.ts";
|
|
32
33
|
|
|
33
34
|
const {
|
|
34
35
|
positionals,
|
|
@@ -89,16 +90,7 @@ NOTES:
|
|
|
89
90
|
process.exit(0);
|
|
90
91
|
}
|
|
91
92
|
if (version) {
|
|
92
|
-
|
|
93
|
-
const versionStr = JSON.parse(
|
|
94
|
-
fs.readFileSync(new URL("../package.json", import.meta.url), {
|
|
95
|
-
encoding: "utf-8",
|
|
96
|
-
}),
|
|
97
|
-
).version;
|
|
98
|
-
console.log(versionStr);
|
|
99
|
-
} catch {
|
|
100
|
-
console.log("unknown");
|
|
101
|
-
}
|
|
93
|
+
console.log(VERSION);
|
|
102
94
|
process.exit(0);
|
|
103
95
|
}
|
|
104
96
|
|
package/src/index.html
CHANGED
package/src/index.ts
CHANGED
|
@@ -23,9 +23,12 @@ import { type Pallet } from "./diff.ts";
|
|
|
23
23
|
import { isValidAlignStrategy, type AlignStrategy } from "./image.ts";
|
|
24
24
|
import { withIndex } from "./iterable.ts";
|
|
25
25
|
import { parseHex, formatHex } from "./rgba-color.ts";
|
|
26
|
+
import { VERSION } from "./version.ts";
|
|
26
27
|
import type { JimpInstance } from "./jimp.ts";
|
|
27
28
|
import type {
|
|
29
|
+
ErrorMessage,
|
|
28
30
|
InitMessage,
|
|
31
|
+
LoadedMessage,
|
|
29
32
|
PageMessage,
|
|
30
33
|
PageResultMessage,
|
|
31
34
|
ReadyMessage,
|
|
@@ -80,37 +83,53 @@ function asSharedBytes(bytes: Uint8Array): Uint8Array {
|
|
|
80
83
|
return new Uint8Array(bytes);
|
|
81
84
|
}
|
|
82
85
|
|
|
86
|
+
type WorkerResponse =
|
|
87
|
+
| LoadedMessage
|
|
88
|
+
| ReadyMessage
|
|
89
|
+
| PageResultMessage
|
|
90
|
+
| ErrorMessage;
|
|
91
|
+
|
|
83
92
|
class WorkerHandle {
|
|
84
93
|
worker: InstanceType<typeof Worker>;
|
|
85
|
-
private
|
|
86
|
-
|
|
87
|
-
| null = null;
|
|
94
|
+
private loaded: Promise<void>;
|
|
95
|
+
private pendingResolve: ((data: WorkerResponse) => void) | null = null;
|
|
88
96
|
private pendingReject: ((reason: unknown) => void) | null = null;
|
|
89
97
|
|
|
90
98
|
constructor(url: URL) {
|
|
91
99
|
this.worker = new Worker(url, { type: "module" });
|
|
92
|
-
this.
|
|
93
|
-
|
|
94
|
-
|
|
100
|
+
this.loaded = new Promise<void>((resolveLoaded, rejectLoaded) => {
|
|
101
|
+
const onMessage = (e: MessageEvent<WorkerResponse>) => {
|
|
102
|
+
const data = e.data;
|
|
103
|
+
if (data.type === "loaded") {
|
|
104
|
+
resolveLoaded();
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
95
107
|
const resolve = this.pendingResolve;
|
|
108
|
+
const reject = this.pendingReject;
|
|
96
109
|
this.pendingResolve = null;
|
|
97
110
|
this.pendingReject = null;
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
this.
|
|
105
|
-
|
|
111
|
+
if (data.type === "error") {
|
|
112
|
+
reject?.(new Error(`worker: ${data.message}`));
|
|
113
|
+
} else {
|
|
114
|
+
resolve?.(data);
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
this.worker.addEventListener("message", onMessage);
|
|
118
|
+
this.worker.addEventListener("error", (e: ErrorEvent) => {
|
|
119
|
+
const err = e.error ?? new Error(e.message);
|
|
120
|
+
rejectLoaded(err);
|
|
121
|
+
const reject = this.pendingReject;
|
|
122
|
+
this.pendingResolve = null;
|
|
123
|
+
this.pendingReject = null;
|
|
124
|
+
reject?.(err);
|
|
125
|
+
});
|
|
106
126
|
});
|
|
107
127
|
}
|
|
108
128
|
|
|
109
|
-
init(msg: InitMessage): Promise<ReadyMessage> {
|
|
129
|
+
async init(msg: InitMessage): Promise<ReadyMessage> {
|
|
130
|
+
await this.loaded;
|
|
110
131
|
return new Promise<ReadyMessage>((resolve, reject) => {
|
|
111
|
-
this.pendingResolve = resolve as (
|
|
112
|
-
data: ReadyMessage | PageResultMessage,
|
|
113
|
-
) => void;
|
|
132
|
+
this.pendingResolve = resolve as (data: WorkerResponse) => void;
|
|
114
133
|
this.pendingReject = reject;
|
|
115
134
|
this.worker.postMessage(msg);
|
|
116
135
|
});
|
|
@@ -118,9 +137,7 @@ class WorkerHandle {
|
|
|
118
137
|
|
|
119
138
|
processPage(index: number): Promise<PageResultMessage> {
|
|
120
139
|
return new Promise<PageResultMessage>((resolve, reject) => {
|
|
121
|
-
this.pendingResolve = resolve as (
|
|
122
|
-
data: ReadyMessage | PageResultMessage,
|
|
123
|
-
) => void;
|
|
140
|
+
this.pendingResolve = resolve as (data: WorkerResponse) => void;
|
|
124
141
|
this.pendingReject = reject;
|
|
125
142
|
const msg: PageMessage = { type: "page", index };
|
|
126
143
|
this.worker.postMessage(msg);
|
|
@@ -133,10 +150,8 @@ class WorkerHandle {
|
|
|
133
150
|
}
|
|
134
151
|
|
|
135
152
|
function workerUrl(): URL {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
import.meta.url,
|
|
139
|
-
);
|
|
153
|
+
const file = import.meta.url.endsWith(".ts") ? "./worker.ts" : "./worker.js";
|
|
154
|
+
return new URL(`${file}?v=${encodeURIComponent(VERSION)}`, import.meta.url);
|
|
140
155
|
}
|
|
141
156
|
|
|
142
157
|
function pageResultToResult(msg: PageResultMessage): Result {
|
package/src/worker.ts
CHANGED
|
@@ -42,6 +42,10 @@ export type PageMessage = {
|
|
|
42
42
|
index: number;
|
|
43
43
|
};
|
|
44
44
|
|
|
45
|
+
export type LoadedMessage = {
|
|
46
|
+
type: "loaded";
|
|
47
|
+
};
|
|
48
|
+
|
|
45
49
|
export type ReadyMessage = {
|
|
46
50
|
type: "ready";
|
|
47
51
|
};
|
|
@@ -57,6 +61,11 @@ export type PageResultMessage = {
|
|
|
57
61
|
modification: [number, number][];
|
|
58
62
|
};
|
|
59
63
|
|
|
64
|
+
export type ErrorMessage = {
|
|
65
|
+
type: "error";
|
|
66
|
+
message: string;
|
|
67
|
+
};
|
|
68
|
+
|
|
60
69
|
let pdfA: mupdf.Document;
|
|
61
70
|
let pdfB: mupdf.Document;
|
|
62
71
|
let pdfMask: mupdf.Document;
|
|
@@ -131,29 +140,41 @@ async function processPage(index: number): Promise<PageResultMessage> {
|
|
|
131
140
|
self.addEventListener(
|
|
132
141
|
"message",
|
|
133
142
|
async (e: MessageEvent<InitMessage | PageMessage>) => {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
143
|
+
try {
|
|
144
|
+
const msg = e.data;
|
|
145
|
+
if (msg.type === "init") {
|
|
146
|
+
pdfA = mupdf.PDFDocument.openDocument(msg.aBytes, "application/pdf");
|
|
147
|
+
pdfB = mupdf.PDFDocument.openDocument(msg.bBytes, "application/pdf");
|
|
148
|
+
pdfMask = msg.maskBytes
|
|
149
|
+
? mupdf.PDFDocument.openDocument(msg.maskBytes, "application/pdf")
|
|
150
|
+
: new mupdf.PDFDocument();
|
|
151
|
+
opts = {
|
|
152
|
+
dpi: msg.dpi,
|
|
153
|
+
alpha: msg.alpha,
|
|
154
|
+
pallet: msg.pallet,
|
|
155
|
+
align: msg.align,
|
|
156
|
+
};
|
|
157
|
+
if (pdfA.countPages() > 0) pdfA.loadPage(0).destroy();
|
|
158
|
+
const ready: ReadyMessage = { type: "ready" };
|
|
159
|
+
self.postMessage(ready);
|
|
160
|
+
} else if (msg.type === "page") {
|
|
161
|
+
const result = await processPage(msg.index);
|
|
162
|
+
self.postMessage(result, [
|
|
163
|
+
result.a.data,
|
|
164
|
+
result.b.data,
|
|
165
|
+
result.diff.data,
|
|
166
|
+
]);
|
|
167
|
+
}
|
|
168
|
+
} catch (err) {
|
|
169
|
+
const errorMsg: ErrorMessage = {
|
|
170
|
+
type: "error",
|
|
171
|
+
message:
|
|
172
|
+
err instanceof Error ? `${err.message}\n${err.stack}` : String(err),
|
|
146
173
|
};
|
|
147
|
-
|
|
148
|
-
const ready: ReadyMessage = { type: "ready" };
|
|
149
|
-
self.postMessage(ready);
|
|
150
|
-
} else if (msg.type === "page") {
|
|
151
|
-
const result = await processPage(msg.index);
|
|
152
|
-
self.postMessage(result, [
|
|
153
|
-
result.a.data,
|
|
154
|
-
result.b.data,
|
|
155
|
-
result.diff.data,
|
|
156
|
-
]);
|
|
174
|
+
self.postMessage(errorMsg);
|
|
157
175
|
}
|
|
158
176
|
},
|
|
159
177
|
);
|
|
178
|
+
|
|
179
|
+
const loaded: LoadedMessage = { type: "loaded" };
|
|
180
|
+
self.postMessage(loaded);
|