@u1f992/pdfdiff 0.2.0 → 0.2.2
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 -1
- package/dist/browser.js +1219 -19
- package/dist/browser.js.map +1 -1
- package/dist/cli-png-worker.d.ts +13 -0
- package/dist/cli-png-worker.d.ts.map +1 -0
- package/dist/cli-png-worker.js +303 -0
- package/dist/cli-png-worker.js.map +1 -0
- package/dist/cli.js +240 -26
- package/dist/cli.js.map +1 -1
- package/dist/diff.d.ts +2 -1
- package/dist/diff.d.ts.map +1 -1
- package/dist/image.d.ts.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.html +6 -1
- package/dist/index.js +123 -15
- package/dist/index.js.map +1 -1
- package/dist/pdf.d.ts.map +1 -1
- package/dist/perf.d.ts +16 -0
- package/dist/perf.d.ts.map +1 -0
- package/dist/squoosh_png_bg.wasm +0 -0
- package/dist/style.css +12 -0
- package/dist/transferable.d.ts +7 -0
- package/dist/transferable.d.ts.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/worker.d.ts +9 -0
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +274 -88
- package/dist/worker.js.map +1 -1
- package/package.json +5 -2
- package/rollup.config.js +20 -0
- package/scripts/version.ts +35 -0
- package/src/browser.ts +119 -5
- package/src/cli-png-worker.ts +59 -0
- package/src/cli.ts +106 -21
- package/src/diff.ts +99 -34
- package/src/image.ts +3 -0
- package/src/index.html +6 -1
- package/src/index.ts +53 -27
- package/src/pdf.ts +9 -1
- package/src/perf.ts +94 -0
- package/src/style.css +12 -0
- package/src/transferable.ts +30 -0
- package/src/worker.ts +77 -54
package/src/index.html
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
<script type="module" src="./browser.js"></script>
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
12
|
-
<h1>pdfdiff
|
|
12
|
+
<h1>pdfdiff <small id="version"></small></h1>
|
|
13
13
|
<div class="form-container">
|
|
14
14
|
<form id="pdf-diff-form">
|
|
15
15
|
<div>
|
|
@@ -64,6 +64,11 @@
|
|
|
64
64
|
<input type="color" id="modification-color" value="#ffc105" />
|
|
65
65
|
</div>
|
|
66
66
|
<button type="submit">Submit</button>
|
|
67
|
+
<label>
|
|
68
|
+
<input type="checkbox" id="hide-no-diff" />
|
|
69
|
+
Hide pages with no diff
|
|
70
|
+
</label>
|
|
71
|
+
<button type="button" id="download-zip" disabled>Download zip</button>
|
|
67
72
|
</form>
|
|
68
73
|
</div>
|
|
69
74
|
<div class="error" id="error-message"></div>
|
package/src/index.ts
CHANGED
|
@@ -22,16 +22,20 @@ import Worker from "web-worker";
|
|
|
22
22
|
import { type Pallet } from "./diff.ts";
|
|
23
23
|
import { isValidAlignStrategy, type AlignStrategy } from "./image.ts";
|
|
24
24
|
import { withIndex } from "./iterable.ts";
|
|
25
|
+
import { perf } from "./perf.ts";
|
|
25
26
|
import { parseHex, formatHex } from "./rgba-color.ts";
|
|
27
|
+
import { VERSION } from "./version.ts";
|
|
26
28
|
import type { JimpInstance } from "./jimp.ts";
|
|
27
29
|
import type {
|
|
30
|
+
ErrorMessage,
|
|
28
31
|
InitMessage,
|
|
32
|
+
LoadedMessage,
|
|
29
33
|
PageMessage,
|
|
30
34
|
PageResultMessage,
|
|
31
35
|
ReadyMessage,
|
|
32
36
|
} from "./worker.ts";
|
|
33
37
|
|
|
34
|
-
export { withIndex, isValidAlignStrategy, parseHex, formatHex };
|
|
38
|
+
export { withIndex, isValidAlignStrategy, parseHex, formatHex, perf };
|
|
35
39
|
|
|
36
40
|
type Options = {
|
|
37
41
|
dpi: number;
|
|
@@ -80,37 +84,53 @@ function asSharedBytes(bytes: Uint8Array): Uint8Array {
|
|
|
80
84
|
return new Uint8Array(bytes);
|
|
81
85
|
}
|
|
82
86
|
|
|
87
|
+
type WorkerResponse =
|
|
88
|
+
| LoadedMessage
|
|
89
|
+
| ReadyMessage
|
|
90
|
+
| PageResultMessage
|
|
91
|
+
| ErrorMessage;
|
|
92
|
+
|
|
83
93
|
class WorkerHandle {
|
|
84
94
|
worker: InstanceType<typeof Worker>;
|
|
85
|
-
private
|
|
86
|
-
|
|
87
|
-
| null = null;
|
|
95
|
+
private loaded: Promise<void>;
|
|
96
|
+
private pendingResolve: ((data: WorkerResponse) => void) | null = null;
|
|
88
97
|
private pendingReject: ((reason: unknown) => void) | null = null;
|
|
89
98
|
|
|
90
99
|
constructor(url: URL) {
|
|
91
100
|
this.worker = new Worker(url, { type: "module" });
|
|
92
|
-
this.
|
|
93
|
-
|
|
94
|
-
|
|
101
|
+
this.loaded = new Promise<void>((resolveLoaded, rejectLoaded) => {
|
|
102
|
+
const onMessage = (e: MessageEvent<WorkerResponse>) => {
|
|
103
|
+
const data = e.data;
|
|
104
|
+
if (data.type === "loaded") {
|
|
105
|
+
resolveLoaded();
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
95
108
|
const resolve = this.pendingResolve;
|
|
109
|
+
const reject = this.pendingReject;
|
|
96
110
|
this.pendingResolve = null;
|
|
97
111
|
this.pendingReject = null;
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
this.
|
|
105
|
-
|
|
112
|
+
if (data.type === "error") {
|
|
113
|
+
reject?.(new Error(`worker: ${data.message}`));
|
|
114
|
+
} else {
|
|
115
|
+
resolve?.(data);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
this.worker.addEventListener("message", onMessage);
|
|
119
|
+
this.worker.addEventListener("error", (e: ErrorEvent) => {
|
|
120
|
+
const err = e.error ?? new Error(e.message);
|
|
121
|
+
rejectLoaded(err);
|
|
122
|
+
const reject = this.pendingReject;
|
|
123
|
+
this.pendingResolve = null;
|
|
124
|
+
this.pendingReject = null;
|
|
125
|
+
reject?.(err);
|
|
126
|
+
});
|
|
106
127
|
});
|
|
107
128
|
}
|
|
108
129
|
|
|
109
|
-
init(msg: InitMessage): Promise<ReadyMessage> {
|
|
130
|
+
async init(msg: InitMessage): Promise<ReadyMessage> {
|
|
131
|
+
await this.loaded;
|
|
110
132
|
return new Promise<ReadyMessage>((resolve, reject) => {
|
|
111
|
-
this.pendingResolve = resolve as (
|
|
112
|
-
data: ReadyMessage | PageResultMessage,
|
|
113
|
-
) => void;
|
|
133
|
+
this.pendingResolve = resolve as (data: WorkerResponse) => void;
|
|
114
134
|
this.pendingReject = reject;
|
|
115
135
|
this.worker.postMessage(msg);
|
|
116
136
|
});
|
|
@@ -118,9 +138,7 @@ class WorkerHandle {
|
|
|
118
138
|
|
|
119
139
|
processPage(index: number): Promise<PageResultMessage> {
|
|
120
140
|
return new Promise<PageResultMessage>((resolve, reject) => {
|
|
121
|
-
this.pendingResolve = resolve as (
|
|
122
|
-
data: ReadyMessage | PageResultMessage,
|
|
123
|
-
) => void;
|
|
141
|
+
this.pendingResolve = resolve as (data: WorkerResponse) => void;
|
|
124
142
|
this.pendingReject = reject;
|
|
125
143
|
const msg: PageMessage = { type: "page", index };
|
|
126
144
|
this.worker.postMessage(msg);
|
|
@@ -133,14 +151,13 @@ class WorkerHandle {
|
|
|
133
151
|
}
|
|
134
152
|
|
|
135
153
|
function workerUrl(): URL {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
import.meta.url,
|
|
139
|
-
);
|
|
154
|
+
const file = import.meta.url.endsWith(".ts") ? "./worker.ts" : "./worker.js";
|
|
155
|
+
return new URL(`${file}?v=${encodeURIComponent(VERSION)}`, import.meta.url);
|
|
140
156
|
}
|
|
141
157
|
|
|
142
158
|
function pageResultToResult(msg: PageResultMessage): Result {
|
|
143
|
-
|
|
159
|
+
const sP = perf.span("main.pageResultToResult_ms");
|
|
160
|
+
const r = {
|
|
144
161
|
a: jimp.Jimp.fromBitmap({
|
|
145
162
|
width: msg.a.width,
|
|
146
163
|
height: msg.a.height,
|
|
@@ -160,6 +177,10 @@ function pageResultToResult(msg: PageResultMessage): Result {
|
|
|
160
177
|
deletion: msg.deletion,
|
|
161
178
|
modification: msg.modification,
|
|
162
179
|
};
|
|
180
|
+
sP.stop();
|
|
181
|
+
perf.incr("main.resultsReceived");
|
|
182
|
+
if (msg.perf) perf.merge(msg.perf);
|
|
183
|
+
return r;
|
|
163
184
|
}
|
|
164
185
|
|
|
165
186
|
export async function* visualizeDifferences(
|
|
@@ -244,6 +265,7 @@ export async function* visualizeDifferences(
|
|
|
244
265
|
resolve(result);
|
|
245
266
|
} else {
|
|
246
267
|
buffered.set(idx, result);
|
|
268
|
+
perf.setMax("main.bufferedPeak", buffered.size);
|
|
247
269
|
}
|
|
248
270
|
} catch (e) {
|
|
249
271
|
workerError = e;
|
|
@@ -263,10 +285,14 @@ export async function* visualizeDifferences(
|
|
|
263
285
|
buffered.delete(i);
|
|
264
286
|
r = buf;
|
|
265
287
|
} else {
|
|
288
|
+
const sWait = perf.span("main.yieldWaitMain_ms");
|
|
266
289
|
r = await new Promise<Result>((resolve) => resolvers.set(i, resolve));
|
|
290
|
+
sWait.stop();
|
|
267
291
|
if (workerError !== null) throw workerError;
|
|
268
292
|
}
|
|
293
|
+
const sYield = perf.span("main.consumerTime_ms");
|
|
269
294
|
yield r;
|
|
295
|
+
sYield.stop();
|
|
270
296
|
}
|
|
271
297
|
await Promise.all(loops);
|
|
272
298
|
} finally {
|
package/src/pdf.ts
CHANGED
|
@@ -19,6 +19,7 @@ import * as jimp from "jimp";
|
|
|
19
19
|
import * as mupdf from "mupdf";
|
|
20
20
|
|
|
21
21
|
import type { JimpInstance } from "./jimp.ts";
|
|
22
|
+
import { perf } from "./perf.ts";
|
|
22
23
|
|
|
23
24
|
export function* loadPages(pdf: mupdf.Document) {
|
|
24
25
|
for (let i = 0; i < pdf.countPages(); i++) {
|
|
@@ -60,6 +61,7 @@ export async function pageToImage(
|
|
|
60
61
|
alpha: boolean,
|
|
61
62
|
) {
|
|
62
63
|
const zoom = dpi / 72;
|
|
64
|
+
const sToPixmap = perf.span("pdf.toPixmap_ms");
|
|
63
65
|
const pixmap = page.toPixmap(
|
|
64
66
|
[zoom, 0, 0, zoom, 0, 0],
|
|
65
67
|
mupdf.ColorSpace.DeviceRGB,
|
|
@@ -67,8 +69,14 @@ export async function pageToImage(
|
|
|
67
69
|
);
|
|
68
70
|
const width = pixmap.getWidth();
|
|
69
71
|
const height = pixmap.getHeight();
|
|
72
|
+
sToPixmap.stop();
|
|
73
|
+
const sRgba = perf.span("pdf.pixmapToRGBA_ms");
|
|
70
74
|
const data = pixmapToRGBA(pixmap);
|
|
71
75
|
pixmap.destroy();
|
|
72
76
|
page.destroy();
|
|
73
|
-
|
|
77
|
+
sRgba.stop();
|
|
78
|
+
const sFromBitmap = perf.span("pdf.fromBitmap_ms");
|
|
79
|
+
const result = jimp.Jimp.fromBitmap({ width, height, data }) as JimpInstance;
|
|
80
|
+
sFromBitmap.stop();
|
|
81
|
+
return result;
|
|
74
82
|
}
|
package/src/perf.ts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
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
|
+
const _enabled = (() => {
|
|
19
|
+
try {
|
|
20
|
+
if (
|
|
21
|
+
typeof process !== "undefined" &&
|
|
22
|
+
process.env &&
|
|
23
|
+
process.env.PDFDIFF_PROFILE === "1"
|
|
24
|
+
) {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
} catch {
|
|
28
|
+
// process not available (e.g. in some browser worker environments)
|
|
29
|
+
}
|
|
30
|
+
const g = globalThis as { __PDFDIFF_PROFILE__?: boolean };
|
|
31
|
+
return g.__PDFDIFF_PROFILE__ === true;
|
|
32
|
+
})();
|
|
33
|
+
|
|
34
|
+
export type Counters = Readonly<Record<string, number>>;
|
|
35
|
+
|
|
36
|
+
export type Span = { stop(): void };
|
|
37
|
+
|
|
38
|
+
type Perf = {
|
|
39
|
+
readonly enabled: boolean;
|
|
40
|
+
span(key: string): Span;
|
|
41
|
+
incr(key: string, delta?: number): void;
|
|
42
|
+
setMax(key: string, value: number): void;
|
|
43
|
+
merge(other: Counters): void;
|
|
44
|
+
dump(): Counters;
|
|
45
|
+
reset(): void;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const _counters: Record<string, number> = Object.create(null);
|
|
49
|
+
|
|
50
|
+
const _NOOP_SPAN: Span = Object.freeze({ stop() {} });
|
|
51
|
+
const _noop = () => {};
|
|
52
|
+
const _emptyDump = (): Counters => Object.freeze({});
|
|
53
|
+
|
|
54
|
+
const _realPerf: Perf = {
|
|
55
|
+
enabled: true,
|
|
56
|
+
span(key) {
|
|
57
|
+
const t0 = performance.now();
|
|
58
|
+
return {
|
|
59
|
+
stop() {
|
|
60
|
+
_counters[key] = (_counters[key] ?? 0) + (performance.now() - t0);
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
},
|
|
64
|
+
incr(key, delta = 1) {
|
|
65
|
+
_counters[key] = (_counters[key] ?? 0) + delta;
|
|
66
|
+
},
|
|
67
|
+
setMax(key, value) {
|
|
68
|
+
const cur = _counters[key];
|
|
69
|
+
if (cur === undefined || value > cur) _counters[key] = value;
|
|
70
|
+
},
|
|
71
|
+
merge(other) {
|
|
72
|
+
for (const k of Object.keys(other)) {
|
|
73
|
+
_counters[k] = (_counters[k] ?? 0) + other[k]!;
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
dump() {
|
|
77
|
+
return { ..._counters };
|
|
78
|
+
},
|
|
79
|
+
reset() {
|
|
80
|
+
for (const k of Object.keys(_counters)) delete _counters[k];
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const _noopPerf: Perf = {
|
|
85
|
+
enabled: false,
|
|
86
|
+
span: () => _NOOP_SPAN,
|
|
87
|
+
incr: _noop,
|
|
88
|
+
setMax: _noop,
|
|
89
|
+
merge: _noop,
|
|
90
|
+
dump: _emptyDump,
|
|
91
|
+
reset: _noop,
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export const perf: Perf = _enabled ? _realPerf : _noopPerf;
|
package/src/style.css
CHANGED
|
@@ -11,9 +11,21 @@
|
|
|
11
11
|
.diff-table {
|
|
12
12
|
border: 1px solid black;
|
|
13
13
|
border-collapse: collapse;
|
|
14
|
+
width: 100%;
|
|
15
|
+
table-layout: fixed;
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
.diff-table th,
|
|
17
19
|
.diff-table td {
|
|
18
20
|
border: 1px solid black;
|
|
19
21
|
}
|
|
22
|
+
|
|
23
|
+
.diff-table img {
|
|
24
|
+
display: block;
|
|
25
|
+
max-width: 100%;
|
|
26
|
+
height: auto;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
body.hide-no-diff .diff-details.no-diff {
|
|
30
|
+
display: none;
|
|
31
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
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
|
+
/**
|
|
19
|
+
* Slice a typed-array view into a standalone backing buffer of the same kind
|
|
20
|
+
* (ArrayBuffer in, ArrayBuffer out; SharedArrayBuffer in, SharedArrayBuffer
|
|
21
|
+
* out). The buffer kind is preserved through the generic parameter.
|
|
22
|
+
*/
|
|
23
|
+
export function sliceBackingBuffer<TArrayBuffer extends ArrayBufferLike>(
|
|
24
|
+
src: Uint8Array<TArrayBuffer> | Uint8ClampedArray<TArrayBuffer>,
|
|
25
|
+
): TArrayBuffer {
|
|
26
|
+
return src.buffer.slice(
|
|
27
|
+
src.byteOffset,
|
|
28
|
+
src.byteOffset + src.byteLength,
|
|
29
|
+
) as TArrayBuffer;
|
|
30
|
+
}
|
package/src/worker.ts
CHANGED
|
@@ -25,6 +25,8 @@ import {
|
|
|
25
25
|
} from "./image.ts";
|
|
26
26
|
import type { JimpInstance } from "./jimp.ts";
|
|
27
27
|
import { pageToImage } from "./pdf.ts";
|
|
28
|
+
import { perf, type Counters } from "./perf.ts";
|
|
29
|
+
import { sliceBackingBuffer } from "./transferable.ts";
|
|
28
30
|
|
|
29
31
|
export type InitMessage = {
|
|
30
32
|
type: "init";
|
|
@@ -42,6 +44,10 @@ export type PageMessage = {
|
|
|
42
44
|
index: number;
|
|
43
45
|
};
|
|
44
46
|
|
|
47
|
+
export type LoadedMessage = {
|
|
48
|
+
type: "loaded";
|
|
49
|
+
};
|
|
50
|
+
|
|
45
51
|
export type ReadyMessage = {
|
|
46
52
|
type: "ready";
|
|
47
53
|
};
|
|
@@ -55,6 +61,12 @@ export type PageResultMessage = {
|
|
|
55
61
|
addition: [number, number][];
|
|
56
62
|
deletion: [number, number][];
|
|
57
63
|
modification: [number, number][];
|
|
64
|
+
perf?: Counters | undefined;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export type ErrorMessage = {
|
|
68
|
+
type: "error";
|
|
69
|
+
message: string;
|
|
58
70
|
};
|
|
59
71
|
|
|
60
72
|
let pdfA: mupdf.Document;
|
|
@@ -67,19 +79,8 @@ let opts: {
|
|
|
67
79
|
align: AlignStrategy;
|
|
68
80
|
};
|
|
69
81
|
|
|
70
|
-
function toTransferable(
|
|
71
|
-
src: Buffer | Uint8Array | Uint8ClampedArray | number[],
|
|
72
|
-
): ArrayBuffer {
|
|
73
|
-
const view =
|
|
74
|
-
src instanceof Uint8Array || src instanceof Uint8ClampedArray
|
|
75
|
-
? src
|
|
76
|
-
: Uint8Array.from(src as ArrayLike<number>);
|
|
77
|
-
const out = new ArrayBuffer(view.byteLength);
|
|
78
|
-
new Uint8Array(out).set(view);
|
|
79
|
-
return out;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
82
|
async function processPage(index: number): Promise<PageResultMessage> {
|
|
83
|
+
const sLoad = perf.span("worker.pageToImageAll_ms");
|
|
83
84
|
const [pageA, pageB, pageMask] = (await Promise.all([
|
|
84
85
|
index < pdfA.countPages()
|
|
85
86
|
? pageToImage(pdfA.loadPage(index), opts.dpi, opts.alpha)
|
|
@@ -89,71 +90,93 @@ async function processPage(index: number): Promise<PageResultMessage> {
|
|
|
89
90
|
: createEmptyImage(1, 1),
|
|
90
91
|
index < pdfMask.countPages()
|
|
91
92
|
? pageToImage(pdfMask.loadPage(index), opts.dpi, opts.alpha)
|
|
92
|
-
:
|
|
93
|
-
])) as [JimpInstance, JimpInstance, JimpInstance];
|
|
93
|
+
: Promise.resolve(null),
|
|
94
|
+
])) as [JimpInstance, JimpInstance, JimpInstance | null];
|
|
95
|
+
sLoad.stop();
|
|
94
96
|
|
|
97
|
+
const sDiff = perf.span("worker.drawDifference_ms");
|
|
95
98
|
const {
|
|
96
99
|
diff: diffLayer,
|
|
97
100
|
addition,
|
|
98
101
|
deletion,
|
|
99
102
|
modification,
|
|
103
|
+
hasDiff,
|
|
100
104
|
} = drawDifference(pageA, pageB, pageMask, opts.pallet, opts.align);
|
|
101
|
-
|
|
105
|
+
sDiff.stop();
|
|
106
|
+
|
|
107
|
+
const sCompose = perf.span("worker.composeLayers_ms");
|
|
108
|
+
const layers: [JimpInstance, number][] = [
|
|
102
109
|
[pageA, 0.2],
|
|
103
110
|
[pageB, 0.2],
|
|
104
|
-
|
|
105
|
-
]);
|
|
111
|
+
];
|
|
112
|
+
if (hasDiff) layers.push([diffLayer, 1]);
|
|
113
|
+
const diff = composeLayers(pageA.width, pageA.height, layers);
|
|
114
|
+
sCompose.stop();
|
|
115
|
+
|
|
116
|
+
const sXfer = perf.span("worker.toTransferable_ms");
|
|
117
|
+
const aBuf = sliceBackingBuffer(pageA.bitmap.data);
|
|
118
|
+
const bBuf = sliceBackingBuffer(pageB.bitmap.data);
|
|
119
|
+
const dBuf = sliceBackingBuffer(diff.bitmap.data);
|
|
120
|
+
sXfer.stop();
|
|
121
|
+
perf.incr("worker.pages");
|
|
122
|
+
|
|
123
|
+
let pagePerf: Counters | undefined;
|
|
124
|
+
if (perf.enabled) {
|
|
125
|
+
pagePerf = perf.dump();
|
|
126
|
+
perf.reset();
|
|
127
|
+
}
|
|
106
128
|
|
|
107
129
|
return {
|
|
108
130
|
type: "pageResult",
|
|
109
131
|
index,
|
|
110
|
-
a: {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
data: toTransferable(pageA.bitmap.data),
|
|
114
|
-
},
|
|
115
|
-
b: {
|
|
116
|
-
width: pageB.width,
|
|
117
|
-
height: pageB.height,
|
|
118
|
-
data: toTransferable(pageB.bitmap.data),
|
|
119
|
-
},
|
|
120
|
-
diff: {
|
|
121
|
-
width: diff.width,
|
|
122
|
-
height: diff.height,
|
|
123
|
-
data: toTransferable(diff.bitmap.data),
|
|
124
|
-
},
|
|
132
|
+
a: { width: pageA.width, height: pageA.height, data: aBuf },
|
|
133
|
+
b: { width: pageB.width, height: pageB.height, data: bBuf },
|
|
134
|
+
diff: { width: diff.width, height: diff.height, data: dBuf },
|
|
125
135
|
addition,
|
|
126
136
|
deletion,
|
|
127
137
|
modification,
|
|
138
|
+
perf: pagePerf,
|
|
128
139
|
};
|
|
129
140
|
}
|
|
130
141
|
|
|
131
142
|
self.addEventListener(
|
|
132
143
|
"message",
|
|
133
144
|
async (e: MessageEvent<InitMessage | PageMessage>) => {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
145
|
+
try {
|
|
146
|
+
const msg = e.data;
|
|
147
|
+
if (msg.type === "init") {
|
|
148
|
+
pdfA = mupdf.PDFDocument.openDocument(msg.aBytes, "application/pdf");
|
|
149
|
+
pdfB = mupdf.PDFDocument.openDocument(msg.bBytes, "application/pdf");
|
|
150
|
+
pdfMask = msg.maskBytes
|
|
151
|
+
? mupdf.PDFDocument.openDocument(msg.maskBytes, "application/pdf")
|
|
152
|
+
: new mupdf.PDFDocument();
|
|
153
|
+
opts = {
|
|
154
|
+
dpi: msg.dpi,
|
|
155
|
+
alpha: msg.alpha,
|
|
156
|
+
pallet: msg.pallet,
|
|
157
|
+
align: msg.align,
|
|
158
|
+
};
|
|
159
|
+
if (pdfA.countPages() > 0) pdfA.loadPage(0).destroy();
|
|
160
|
+
const ready: ReadyMessage = { type: "ready" };
|
|
161
|
+
self.postMessage(ready);
|
|
162
|
+
} else if (msg.type === "page") {
|
|
163
|
+
const result = await processPage(msg.index);
|
|
164
|
+
self.postMessage(result, [
|
|
165
|
+
result.a.data,
|
|
166
|
+
result.b.data,
|
|
167
|
+
result.diff.data,
|
|
168
|
+
]);
|
|
169
|
+
}
|
|
170
|
+
} catch (err) {
|
|
171
|
+
const errorMsg: ErrorMessage = {
|
|
172
|
+
type: "error",
|
|
173
|
+
message:
|
|
174
|
+
err instanceof Error ? `${err.message}\n${err.stack}` : String(err),
|
|
146
175
|
};
|
|
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
|
-
]);
|
|
176
|
+
self.postMessage(errorMsg);
|
|
157
177
|
}
|
|
158
178
|
},
|
|
159
179
|
);
|
|
180
|
+
|
|
181
|
+
const loaded: LoadedMessage = { type: "loaded" };
|
|
182
|
+
self.postMessage(loaded);
|