tapirscan 1.2.2 → 1.3.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/LICENSE +220 -0
- package/README.md +384 -230
- package/dist/browser-worker.d.ts +33 -0
- package/dist/browser-worker.js +83 -0
- package/dist/browser.d.ts +44 -0
- package/dist/browser.js +211 -0
- package/dist/freeze.d.ts +6 -0
- package/dist/freeze.js +7 -0
- package/dist/index.d.ts +30 -18
- package/dist/index.js +138 -87
- package/dist/layout.d.ts +19 -0
- package/dist/layout.js +27 -0
- package/dist/multiformat/format-registry.d.ts +2 -2
- package/dist/multiformat/format-registry.js +12 -11
- package/dist/multiformat/formats.d.ts +2 -0
- package/dist/multiformat/formats.js +13 -6
- package/dist/rust-session.d.ts +2 -2
- package/dist/rust-session.js +16 -6
- package/examples/camera.html +28 -29
- package/examples/vite/README.md +31 -0
- package/examples/vite/index.html +14 -0
- package/examples/vite/main.js +33 -0
- package/examples/vite/package.json +16 -0
- package/examples/vite/vite.config.js +6 -0
- package/package.json +27 -8
- package/wasm/build.json +295 -0
- package/wasm/experimental-turbo16.wasm +0 -0
- package/wasm/experimental-turbo2.wasm +0 -0
- package/wasm/experimental-turbo4.wasm +0 -0
- package/wasm/experimental-turbo8.wasm +0 -0
- package/wasm/high.wasm +0 -0
- package/wasm/low.wasm +0 -0
- package/wasm/medium.wasm +0 -0
- package/wasm/very-high.wasm +0 -0
- package/examples/scan-worker.mjs +0 -20
- package/examples/worker-client.mjs +0 -63
- package/wasm/high-release-1.2.2-public-low-r2.wasm +0 -0
- package/wasm/low-release-1.2.2-public-low-r2.wasm +0 -0
- package/wasm/medium-release-1.2.2-public-low-r2.wasm +0 -0
- package/wasm/very-high-release-1.2.2-public-low-r2.wasm +0 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type EanAddOnPolicy, type ExperimentalTurbo, type FormatSelection, type Mode, type PixelImage, type ScanOptions } from "./index.js";
|
|
2
|
+
/** Scanner options that can cross the worker boundary. */
|
|
3
|
+
export interface WorkerScannerOptions {
|
|
4
|
+
mode?: Mode;
|
|
5
|
+
formats?: FormatSelection;
|
|
6
|
+
eanAddOnPolicy?: EanAddOnPolicy;
|
|
7
|
+
experimentalTurbo?: ExperimentalTurbo;
|
|
8
|
+
/** Absolute URL of a directory serving the packaged WASM files. */
|
|
9
|
+
wasmBaseUrl?: string;
|
|
10
|
+
}
|
|
11
|
+
/** Inputs that are cheap to send: everything else becomes an ImageBitmap first. */
|
|
12
|
+
export type WorkerSource = Blob | ImageBitmap | PixelImage;
|
|
13
|
+
export type WorkerRequest = {
|
|
14
|
+
id: number;
|
|
15
|
+
type: "create";
|
|
16
|
+
options: WorkerScannerOptions;
|
|
17
|
+
} | {
|
|
18
|
+
id: number;
|
|
19
|
+
type: "scan" | "inspect";
|
|
20
|
+
source: WorkerSource;
|
|
21
|
+
options: ScanOptions;
|
|
22
|
+
};
|
|
23
|
+
export type WorkerResponse = {
|
|
24
|
+
id: number;
|
|
25
|
+
value: unknown;
|
|
26
|
+
} | {
|
|
27
|
+
id: number;
|
|
28
|
+
error: {
|
|
29
|
+
name: string;
|
|
30
|
+
message: string;
|
|
31
|
+
code?: string;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// Worker side of tapirscan/browser: one core Scanner, fed with decoded pixels.
|
|
2
|
+
import { Scanner, } from "./index.js";
|
|
3
|
+
// Literal URLs let bundlers such as Vite and webpack emit these assets without
|
|
4
|
+
// configuration. Keep the file names in sync with index.ts (verify-package checks).
|
|
5
|
+
const wasm = {
|
|
6
|
+
low: new URL("../wasm/low.wasm", import.meta.url),
|
|
7
|
+
medium: new URL("../wasm/medium.wasm", import.meta.url),
|
|
8
|
+
high: new URL("../wasm/high.wasm", import.meta.url),
|
|
9
|
+
"very-high": new URL("../wasm/very-high.wasm", import.meta.url),
|
|
10
|
+
};
|
|
11
|
+
const turboWasm = {
|
|
12
|
+
2: new URL("../wasm/experimental-turbo2.wasm", import.meta.url),
|
|
13
|
+
4: new URL("../wasm/experimental-turbo4.wasm", import.meta.url),
|
|
14
|
+
8: new URL("../wasm/experimental-turbo8.wasm", import.meta.url),
|
|
15
|
+
16: new URL("../wasm/experimental-turbo16.wasm", import.meta.url),
|
|
16
|
+
};
|
|
17
|
+
// Typed locally: the DOM and WebWorker libraries cannot share one compilation.
|
|
18
|
+
const scope = globalThis;
|
|
19
|
+
let scanner;
|
|
20
|
+
async function load(url) {
|
|
21
|
+
const response = await fetch(url);
|
|
22
|
+
if (!response.ok)
|
|
23
|
+
throw new Error(`WASM load failed: ${String(response.status)} ${url.href}`);
|
|
24
|
+
return response.arrayBuffer();
|
|
25
|
+
}
|
|
26
|
+
/** Decode a Blob or ImageBitmap onto white RGBA pixels; pixel inputs pass through. */
|
|
27
|
+
async function pixels(source) {
|
|
28
|
+
if (!(source instanceof Blob) && !(source instanceof ImageBitmap))
|
|
29
|
+
return source;
|
|
30
|
+
const bitmap = source instanceof Blob ? await createImageBitmap(source) : source;
|
|
31
|
+
try {
|
|
32
|
+
const { width, height } = bitmap;
|
|
33
|
+
// Reject before allocating a canvas; the scanner enforces the same limits.
|
|
34
|
+
if (width < 3 || height < 3 || width * height > 32 * 1024 * 1024)
|
|
35
|
+
throw new TypeError(`Images must be 3×3 to 32 megapixels, got ${String(width)}×${String(height)}`);
|
|
36
|
+
const context = new OffscreenCanvas(width, height).getContext("2d", {
|
|
37
|
+
willReadFrequently: true,
|
|
38
|
+
});
|
|
39
|
+
if (!context)
|
|
40
|
+
throw new Error("2D canvas is unavailable in this browser's workers");
|
|
41
|
+
// Composite onto white, as images are displayed: the scanner ignores alpha, so
|
|
42
|
+
// transparent areas would otherwise read as black.
|
|
43
|
+
context.fillStyle = "#fff";
|
|
44
|
+
context.fillRect(0, 0, width, height);
|
|
45
|
+
context.drawImage(bitmap, 0, 0);
|
|
46
|
+
return context.getImageData(0, 0, width, height);
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
bitmap.close();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async function handle(request) {
|
|
53
|
+
if (request.type === "create") {
|
|
54
|
+
const { wasmBaseUrl, ...options } = request.options;
|
|
55
|
+
const { experimentalTurbo: turbo, mode = "medium" } = options;
|
|
56
|
+
// The core validates options before loading; a custom base URL uses its loader.
|
|
57
|
+
// Invalid modes or presets fail validation, so the lookup below is never used for them.
|
|
58
|
+
const asset = turbo === undefined ? wasm[mode] : turboWasm[turbo];
|
|
59
|
+
scanner = await Scanner.create(wasmBaseUrl === undefined
|
|
60
|
+
? { ...options, loadWasm: () => load(asset) }
|
|
61
|
+
: { ...options, wasmBaseUrl });
|
|
62
|
+
return { mode: scanner.mode, formats: scanner.formats, eanAddOnPolicy: scanner.eanAddOnPolicy };
|
|
63
|
+
}
|
|
64
|
+
if (!scanner)
|
|
65
|
+
throw new Error("Scanner is not created");
|
|
66
|
+
const image = await pixels(request.source);
|
|
67
|
+
return request.type === "scan"
|
|
68
|
+
? scanner.scan(image, request.options)
|
|
69
|
+
: scanner.inspect(image, request.options);
|
|
70
|
+
}
|
|
71
|
+
scope.onmessage = ({ data: request }) => {
|
|
72
|
+
handle(request).then((value) => {
|
|
73
|
+
scope.postMessage({ id: request.id, value });
|
|
74
|
+
}, (error) => {
|
|
75
|
+
const { name, message, code } = error instanceof Error
|
|
76
|
+
? error
|
|
77
|
+
: { name: "Error", message: String(error), code: undefined };
|
|
78
|
+
scope.postMessage({
|
|
79
|
+
id: request.id,
|
|
80
|
+
error: { name, message, ...(typeof code === "string" ? { code } : {}) },
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { EanAddOnPolicy, ExperimentalTurbo, FormatSelection, Mode, PixelImage, ScanOptions, InspectionResult, ScanResult } from "./index.js";
|
|
2
|
+
export { ScannerError } from "./rust-session.js";
|
|
3
|
+
export type { Barcode, DiagnosticBarcode, Diagnostics, EanAddOnPolicy, ExperimentalTurbo, Format, FormatSelection, Image, Mode, PixelImage, Quad, RegionEvidence, ScanOptions, InspectionResult, ScanResult, StructuredAppend, UndecodedRegion, } from "./index.js";
|
|
4
|
+
/**
|
|
5
|
+
* A file or blob (JPEG, PNG and other browser-decodable images), an `<img>`,
|
|
6
|
+
* `<video>` (its current frame), canvas, `ImageBitmap`, `VideoFrame`, `ImageData`
|
|
7
|
+
* or decoded pixels.
|
|
8
|
+
*/
|
|
9
|
+
export type ImageSource = ImageBitmapSource | PixelImage;
|
|
10
|
+
export interface ScannerOptions {
|
|
11
|
+
/** Effort; defaults to "medium". */
|
|
12
|
+
mode?: Mode;
|
|
13
|
+
/** Formats to read; defaults to retail EAN/UPC. */
|
|
14
|
+
formats?: FormatSelection;
|
|
15
|
+
/** EAN/UPC supplement policy; defaults to "ignore". */
|
|
16
|
+
eanAddOnPolicy?: EanAddOnPolicy;
|
|
17
|
+
/** @experimental Faster 1D preset instead of a mode; may change in minor releases. */
|
|
18
|
+
experimentalTurbo?: ExperimentalTurbo;
|
|
19
|
+
/** Serve the packaged WASM files from another directory, such as a CDN. */
|
|
20
|
+
wasmBaseUrl?: string | URL;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* A reusable scanner running in a dedicated worker, so scanning never blocks the
|
|
24
|
+
* page. Construction is synchronous and starts loading in the background; during
|
|
25
|
+
* server rendering it does nothing. Inputs and options are captured when a call
|
|
26
|
+
* starts, and calls reach the worker in call order. Results are deeply frozen and
|
|
27
|
+
* survive disposal. Call dispose() when done to stop the worker.
|
|
28
|
+
*/
|
|
29
|
+
export declare class Scanner {
|
|
30
|
+
#private;
|
|
31
|
+
/** Resolves once the scanner is loaded; rejects with the loading error. Optional. */
|
|
32
|
+
readonly ready: Promise<void>;
|
|
33
|
+
constructor(options?: ScannerOptions);
|
|
34
|
+
/** Decode barcodes with source-image positions. No detection returns empty values and barcodes. */
|
|
35
|
+
scan(source: ImageSource, options?: ScanOptions): Promise<ScanResult>;
|
|
36
|
+
/** Scan with unread regions, timing and engine diagnostics. */
|
|
37
|
+
inspect(source: ImageSource, options?: ScanOptions): Promise<InspectionResult>;
|
|
38
|
+
/** Stop the worker and reject queued scans. Repeated disposal is safe. */
|
|
39
|
+
dispose(): void;
|
|
40
|
+
}
|
|
41
|
+
/** Scan one image with a temporary scanner. Reuse a Scanner for several images. */
|
|
42
|
+
export declare function scan(source: ImageSource, options?: ScannerOptions): Promise<ScanResult>;
|
|
43
|
+
/** Inspect one image with a temporary scanner. */
|
|
44
|
+
export declare function inspect(source: ImageSource, options?: ScannerOptions): Promise<InspectionResult>;
|
package/dist/browser.js
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { freeze } from "./freeze.js";
|
|
2
|
+
import { pixelLayout } from "./layout.js";
|
|
3
|
+
import { ScannerError } from "./rust-session.js";
|
|
4
|
+
export { ScannerError } from "./rust-session.js";
|
|
5
|
+
const scannerOptions = ["mode", "formats", "eanAddOnPolicy", "experimentalTurbo", "wasmBaseUrl"];
|
|
6
|
+
function revive({ name, message, code }) {
|
|
7
|
+
if (code !== undefined)
|
|
8
|
+
return new ScannerError(code, message);
|
|
9
|
+
return name === "TypeError" ? new TypeError(message) : new Error(message);
|
|
10
|
+
}
|
|
11
|
+
/** Snapshot mutable inputs before the first await; Blobs are immutable. */
|
|
12
|
+
async function prepare(source) {
|
|
13
|
+
const input = source;
|
|
14
|
+
if (input === null || typeof input !== "object")
|
|
15
|
+
throw new TypeError("Expected an image source");
|
|
16
|
+
if (source instanceof Blob)
|
|
17
|
+
return [source, []];
|
|
18
|
+
if ("data" in source) {
|
|
19
|
+
// Validate first, then copy only the addressed rows of a larger backing buffer.
|
|
20
|
+
const { data, width, height, channels, stride, addressed } = pixelLayout(source);
|
|
21
|
+
const copy = new Uint8Array(data.subarray(0, addressed));
|
|
22
|
+
return [{ data: copy, width, height, channels, stride }, [copy.buffer]];
|
|
23
|
+
}
|
|
24
|
+
// A copy, so a caller's ImageBitmap stays usable after it is transferred.
|
|
25
|
+
const bitmap = await createImageBitmap(source);
|
|
26
|
+
return [bitmap, [bitmap]];
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* A reusable scanner running in a dedicated worker, so scanning never blocks the
|
|
30
|
+
* page. Construction is synchronous and starts loading in the background; during
|
|
31
|
+
* server rendering it does nothing. Inputs and options are captured when a call
|
|
32
|
+
* starts, and calls reach the worker in call order. Results are deeply frozen and
|
|
33
|
+
* survive disposal. Call dispose() when done to stop the worker.
|
|
34
|
+
*/
|
|
35
|
+
export class Scanner {
|
|
36
|
+
/** Resolves once the scanner is loaded; rejects with the loading error. Optional. */
|
|
37
|
+
ready;
|
|
38
|
+
#worker;
|
|
39
|
+
#pending = new Map();
|
|
40
|
+
#nextId = 0;
|
|
41
|
+
#closed;
|
|
42
|
+
/** Rejecters of calls still waiting; each removes itself when its wait ends. */
|
|
43
|
+
#waiting = new Set();
|
|
44
|
+
/** Settles once the previous call has been sent, keeping submissions in call order. */
|
|
45
|
+
#submitted = Promise.resolve();
|
|
46
|
+
constructor(options = {}) {
|
|
47
|
+
const input = options;
|
|
48
|
+
if (input === null || typeof input !== "object" || Array.isArray(input))
|
|
49
|
+
throw new TypeError("Invalid scanner options");
|
|
50
|
+
for (const key of Object.keys(options))
|
|
51
|
+
if (!scannerOptions.includes(key))
|
|
52
|
+
throw new TypeError(`Unknown scanner option: ${key}. Use the core "tapirscan" entry for loadWasm.`);
|
|
53
|
+
if (typeof Worker === "undefined") {
|
|
54
|
+
this.#closed = new Error("tapirscan/browser scans in browsers; this environment has no Worker");
|
|
55
|
+
this.ready = Promise.reject(this.#closed);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
const { wasmBaseUrl, ...rest } = options;
|
|
59
|
+
if (wasmBaseUrl !== undefined &&
|
|
60
|
+
typeof wasmBaseUrl !== "string" &&
|
|
61
|
+
!(wasmBaseUrl instanceof URL))
|
|
62
|
+
throw new TypeError("wasmBaseUrl must be a string or URL");
|
|
63
|
+
const base = typeof document === "undefined" ? location.href : document.baseURI;
|
|
64
|
+
const workerOptions = wasmBaseUrl === undefined
|
|
65
|
+
? rest
|
|
66
|
+
: { ...rest, wasmBaseUrl: new URL(wasmBaseUrl, base).href };
|
|
67
|
+
const worker = new Worker(new URL("./browser-worker.js", import.meta.url), {
|
|
68
|
+
type: "module",
|
|
69
|
+
});
|
|
70
|
+
this.#worker = worker;
|
|
71
|
+
worker.onmessage = ({ data }) => {
|
|
72
|
+
const pending = this.#pending.get(data.id);
|
|
73
|
+
if (!pending)
|
|
74
|
+
return;
|
|
75
|
+
this.#pending.delete(data.id);
|
|
76
|
+
if ("error" in data)
|
|
77
|
+
pending.reject(revive(data.error));
|
|
78
|
+
else
|
|
79
|
+
pending.resolve(data.value);
|
|
80
|
+
};
|
|
81
|
+
worker.onerror = (event) => {
|
|
82
|
+
event.preventDefault();
|
|
83
|
+
// A worker script that cannot load gives no message; name the usual cause.
|
|
84
|
+
this.#close(new Error(event.message ||
|
|
85
|
+
'Scanner worker failed to start. With the Vite dev server, add optimizeDeps: { exclude: ["tapirscan"] } to vite.config.'));
|
|
86
|
+
};
|
|
87
|
+
worker.onmessageerror = () => {
|
|
88
|
+
this.#close(new Error("Could not read a scanner worker message"));
|
|
89
|
+
};
|
|
90
|
+
this.ready = this.#request({ type: "create", options: workerOptions }, []).then(() => undefined, (error) => {
|
|
91
|
+
this.#close(error instanceof Error ? error : new Error(String(error)));
|
|
92
|
+
throw error;
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
// Loading errors also reject every scan; awaiting ready is optional.
|
|
96
|
+
this.ready.catch(() => undefined);
|
|
97
|
+
}
|
|
98
|
+
/** Decode barcodes with source-image positions. No detection returns empty values and barcodes. */
|
|
99
|
+
async scan(source, options = {}) {
|
|
100
|
+
return freeze((await this.#scan("scan", source, options)));
|
|
101
|
+
}
|
|
102
|
+
/** Scan with unread regions, timing and engine diagnostics. */
|
|
103
|
+
async inspect(source, options = {}) {
|
|
104
|
+
return freeze((await this.#scan("inspect", source, options)));
|
|
105
|
+
}
|
|
106
|
+
/** Stop the worker and reject queued scans. Repeated disposal is safe. */
|
|
107
|
+
dispose() {
|
|
108
|
+
// Same error as the core scanner, so one handler covers both entries.
|
|
109
|
+
this.#close(new ScannerError("disposed", "Scanner was disposed"));
|
|
110
|
+
}
|
|
111
|
+
async #scan(type, source, options) {
|
|
112
|
+
if (this.#closed)
|
|
113
|
+
throw this.#closed;
|
|
114
|
+
// Capture the options and the frame before any await, so later changes by the
|
|
115
|
+
// caller (a reused options object or buffer, the next video frame) cannot leak in.
|
|
116
|
+
// Plain copies (scan options hold at most a format selection); invalid values
|
|
117
|
+
// pass through unchanged for the core to reject.
|
|
118
|
+
const input = options;
|
|
119
|
+
const scanOptions = input !== null && typeof input === "object" && !Array.isArray(input)
|
|
120
|
+
? {
|
|
121
|
+
...options,
|
|
122
|
+
...(typeof options.formats === "object" ? { formats: [...options.formats] } : {}),
|
|
123
|
+
}
|
|
124
|
+
: options;
|
|
125
|
+
const preparing = prepare(source);
|
|
126
|
+
const previous = this.#submitted;
|
|
127
|
+
let submitted = () => undefined;
|
|
128
|
+
this.#submitted = new Promise((resolve) => {
|
|
129
|
+
submitted = resolve;
|
|
130
|
+
});
|
|
131
|
+
try {
|
|
132
|
+
const [prepared, transfer] = await this.#untilClosed(preparing);
|
|
133
|
+
await this.#untilClosed(previous);
|
|
134
|
+
await this.#untilClosed(this.ready);
|
|
135
|
+
const result = this.#request({ type, source: prepared, options: scanOptions }, transfer);
|
|
136
|
+
submitted();
|
|
137
|
+
return await result;
|
|
138
|
+
}
|
|
139
|
+
finally {
|
|
140
|
+
// A failed call releases its successor only once its own predecessor was
|
|
141
|
+
// sent, so later calls never overtake earlier ones.
|
|
142
|
+
void previous.then(submitted);
|
|
143
|
+
// Release a bitmap snapshot that was not sent, including one still being prepared.
|
|
144
|
+
void preparing.then(([prepared]) => {
|
|
145
|
+
if (prepared instanceof ImageBitmap)
|
|
146
|
+
prepared.close();
|
|
147
|
+
}, () => undefined);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/** Wait for `promise`, but reject at once on dispose. Leaves nothing behind. */
|
|
151
|
+
#untilClosed(promise) {
|
|
152
|
+
if (this.#closed)
|
|
153
|
+
return Promise.reject(this.#closed);
|
|
154
|
+
return new Promise((resolve, reject) => {
|
|
155
|
+
this.#waiting.add(reject);
|
|
156
|
+
promise
|
|
157
|
+
.then(resolve, reject)
|
|
158
|
+
.finally(() => this.#waiting.delete(reject))
|
|
159
|
+
.catch(() => undefined);
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
#request(message, transfer) {
|
|
163
|
+
const worker = this.#worker;
|
|
164
|
+
// Without a worker, the constructor has already recorded why.
|
|
165
|
+
if (this.#closed || !worker)
|
|
166
|
+
return Promise.reject(this.#closed ?? new Error("No scanner worker"));
|
|
167
|
+
const id = this.#nextId++;
|
|
168
|
+
return new Promise((resolve, reject) => {
|
|
169
|
+
this.#pending.set(id, { resolve, reject });
|
|
170
|
+
try {
|
|
171
|
+
worker.postMessage({ ...message, id }, transfer);
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
this.#pending.delete(id);
|
|
175
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
#close(reason) {
|
|
180
|
+
if (this.#closed)
|
|
181
|
+
return;
|
|
182
|
+
this.#closed = reason;
|
|
183
|
+
for (const reject of this.#waiting)
|
|
184
|
+
reject(reason);
|
|
185
|
+
this.#waiting.clear();
|
|
186
|
+
this.#worker?.terminate();
|
|
187
|
+
for (const pending of this.#pending.values())
|
|
188
|
+
pending.reject(reason);
|
|
189
|
+
this.#pending.clear();
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/** Scan one image with a temporary scanner. Reuse a Scanner for several images. */
|
|
193
|
+
export async function scan(source, options = {}) {
|
|
194
|
+
const scanner = new Scanner(options);
|
|
195
|
+
try {
|
|
196
|
+
return await scanner.scan(source);
|
|
197
|
+
}
|
|
198
|
+
finally {
|
|
199
|
+
scanner.dispose();
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
/** Inspect one image with a temporary scanner. */
|
|
203
|
+
export async function inspect(source, options = {}) {
|
|
204
|
+
const scanner = new Scanner(options);
|
|
205
|
+
try {
|
|
206
|
+
return await scanner.inspect(source);
|
|
207
|
+
}
|
|
208
|
+
finally {
|
|
209
|
+
scanner.dispose();
|
|
210
|
+
}
|
|
211
|
+
}
|
package/dist/freeze.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Deeply readonly view of plain result data. */
|
|
2
|
+
export type ReadonlyDeep<T> = T extends object ? {
|
|
3
|
+
readonly [K in keyof T]: ReadonlyDeep<T[K]>;
|
|
4
|
+
} : T;
|
|
5
|
+
/** Freeze plain result data in place, so results stay immutable at runtime. */
|
|
6
|
+
export declare function freeze<T extends object>(value: T): ReadonlyDeep<T>;
|
package/dist/freeze.js
ADDED
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type Format, type FormatSelection } from "./multiformat/formats.js";
|
|
2
|
-
|
|
2
|
+
import { type ReadonlyDeep } from "./freeze.js";
|
|
3
|
+
export { commonFormats, commonLinearFormats, linearFormats, matrixFormats, retailFormats, } from "./multiformat/formats.js";
|
|
3
4
|
export type { Format, FormatSelection } from "./multiformat/formats.js";
|
|
4
5
|
export { ScannerError } from "./rust-session.js";
|
|
5
6
|
export type Quad = readonly [
|
|
@@ -17,12 +18,8 @@ export interface Image {
|
|
|
17
18
|
}
|
|
18
19
|
export type Mode = "low" | "medium" | "high" | "very-high";
|
|
19
20
|
export interface ScanOptions {
|
|
20
|
-
/**
|
|
21
|
-
extendedBudget?: boolean;
|
|
22
|
-
/** Per-call subset of the formats configured at creation. */
|
|
21
|
+
/** Formats for this call only; defaults to the scanner's formats. */
|
|
23
22
|
formats?: FormatSelection;
|
|
24
|
-
/** Include raw engine diagnostics. Public barcodes and unread geometry are always included. */
|
|
25
|
-
debug?: boolean;
|
|
26
23
|
}
|
|
27
24
|
interface RawDiagnostics {
|
|
28
25
|
scan: {
|
|
@@ -44,9 +41,6 @@ interface RawDiagnostics {
|
|
|
44
41
|
}[];
|
|
45
42
|
[key: string]: unknown;
|
|
46
43
|
}
|
|
47
|
-
type ReadonlyDeep<T> = T extends object ? {
|
|
48
|
-
readonly [K in keyof T]: ReadonlyDeep<T[K]>;
|
|
49
|
-
} : T;
|
|
50
44
|
interface RawDiagnosticBarcode {
|
|
51
45
|
text: string;
|
|
52
46
|
format: Format | "Unknown";
|
|
@@ -83,7 +77,8 @@ export interface Barcode {
|
|
|
83
77
|
/** Payload bytes before character-set interpretation; absent when unavailable. */
|
|
84
78
|
readonly payloadBytes?: readonly number[];
|
|
85
79
|
readonly text: string;
|
|
86
|
-
|
|
80
|
+
/** Decoded barcodes always have a known format; only undecoded regions report "Unknown". */
|
|
81
|
+
readonly format: Format;
|
|
87
82
|
/** Reader-specific ranking evidence, not a probability or cross-reader confidence. */
|
|
88
83
|
readonly support: number;
|
|
89
84
|
readonly gs1?: boolean;
|
|
@@ -98,27 +93,35 @@ export interface Barcode {
|
|
|
98
93
|
readonly height: number;
|
|
99
94
|
};
|
|
100
95
|
}
|
|
96
|
+
/** Decoded values and their source-image locations. */
|
|
101
97
|
export interface ScanResult {
|
|
102
98
|
readonly barcodes: readonly Barcode[];
|
|
103
99
|
readonly values: readonly string[];
|
|
104
100
|
/** Largest reader-specific support; not a cross-format confidence comparison. */
|
|
105
101
|
readonly best: Barcode | undefined;
|
|
102
|
+
}
|
|
103
|
+
export interface InspectionResult extends ScanResult {
|
|
106
104
|
readonly image: {
|
|
107
105
|
readonly width: number;
|
|
108
106
|
readonly height: number;
|
|
109
107
|
};
|
|
110
|
-
|
|
108
|
+
/** Effort mode used; absent for Turbo presets, which report `experimentalTurbo`. */
|
|
109
|
+
readonly mode?: Mode;
|
|
110
|
+
/** @experimental Selected Turbo preset, when requested. */
|
|
111
|
+
readonly experimentalTurbo?: ExperimentalTurbo;
|
|
111
112
|
/** Whole synchronous WASM call time measured by the JavaScript host. */
|
|
112
113
|
readonly elapsedMs: number;
|
|
113
|
-
readonly unfinished: boolean;
|
|
114
114
|
readonly undecoded: readonly UndecodedRegion[];
|
|
115
|
-
readonly
|
|
115
|
+
readonly diagnostics: Diagnostics;
|
|
116
116
|
}
|
|
117
|
-
export type EanAddOnPolicy = "
|
|
117
|
+
export type EanAddOnPolicy = "ignore" | "read" | "require";
|
|
118
|
+
export type ExperimentalTurbo = 2 | 4 | 8 | 16;
|
|
118
119
|
export interface ScannerOptions {
|
|
120
|
+
mode?: Mode;
|
|
121
|
+
/** @experimental Targets faster 1D scanning, not 2D speedups. May change in minor releases. */
|
|
122
|
+
experimentalTurbo?: ExperimentalTurbo;
|
|
119
123
|
/** Optional EAN/UPC supplement policy, fixed at creation. */
|
|
120
124
|
eanAddOnPolicy?: EanAddOnPolicy;
|
|
121
|
-
mode?: Mode;
|
|
122
125
|
formats?: FormatSelection;
|
|
123
126
|
/** Directory containing the packaged WASMs; relative to the page in browsers. */
|
|
124
127
|
wasmBaseUrl?: string | URL;
|
|
@@ -129,17 +132,26 @@ export type PixelImage = Image | Pick<ImageData, "data" | "width" | "height">;
|
|
|
129
132
|
/** A reusable scanner. Results own their data and remain valid after later scans or disposal. */
|
|
130
133
|
export declare class Scanner {
|
|
131
134
|
private readonly host;
|
|
132
|
-
|
|
135
|
+
/** Effort mode, fixed at creation; undefined for Turbo presets. */
|
|
136
|
+
readonly mode: Mode | undefined;
|
|
133
137
|
private readonly configuredFormats;
|
|
134
138
|
private readonly addOnPolicy;
|
|
139
|
+
/** @experimental Selected Turbo preset, fixed at creation. */
|
|
140
|
+
readonly experimentalTurbo?: ExperimentalTurbo | undefined;
|
|
135
141
|
private constructor();
|
|
136
|
-
/**
|
|
142
|
+
/** Default formats, fixed at creation; individual scans may override them. */
|
|
137
143
|
get formats(): readonly Format[];
|
|
138
144
|
get eanAddOnPolicy(): EanAddOnPolicy;
|
|
139
145
|
static create(options?: ScannerOptions): Promise<Scanner>;
|
|
146
|
+
/** Decode barcodes with positions. Use inspect() for diagnostic evidence. */
|
|
140
147
|
scan(inputImage: PixelImage, options?: ScanOptions): ScanResult;
|
|
148
|
+
/** Inspect barcodes, unread regions, timing and engine diagnostics. */
|
|
149
|
+
inspect(inputImage: PixelImage, options?: ScanOptions): InspectionResult;
|
|
150
|
+
private run;
|
|
141
151
|
/** Release the WASM session. Repeated disposal is safe; scanning afterward fails. */
|
|
142
152
|
dispose(): void;
|
|
143
153
|
}
|
|
144
154
|
/** Scan one image with automatic cleanup. Reuse Scanner for a stream of images. */
|
|
145
|
-
export declare function scan(image: PixelImage, options?: ScannerOptions
|
|
155
|
+
export declare function scan(image: PixelImage, options?: ScannerOptions): Promise<ScanResult>;
|
|
156
|
+
/** Inspect one image with automatic cleanup. */
|
|
157
|
+
export declare function inspect(image: PixelImage, options?: ScannerOptions): Promise<InspectionResult>;
|