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.
Files changed (40) hide show
  1. package/LICENSE +220 -0
  2. package/README.md +384 -230
  3. package/dist/browser-worker.d.ts +33 -0
  4. package/dist/browser-worker.js +83 -0
  5. package/dist/browser.d.ts +44 -0
  6. package/dist/browser.js +211 -0
  7. package/dist/freeze.d.ts +6 -0
  8. package/dist/freeze.js +7 -0
  9. package/dist/index.d.ts +30 -18
  10. package/dist/index.js +138 -87
  11. package/dist/layout.d.ts +19 -0
  12. package/dist/layout.js +27 -0
  13. package/dist/multiformat/format-registry.d.ts +2 -2
  14. package/dist/multiformat/format-registry.js +12 -11
  15. package/dist/multiformat/formats.d.ts +2 -0
  16. package/dist/multiformat/formats.js +13 -6
  17. package/dist/rust-session.d.ts +2 -2
  18. package/dist/rust-session.js +16 -6
  19. package/examples/camera.html +28 -29
  20. package/examples/vite/README.md +31 -0
  21. package/examples/vite/index.html +14 -0
  22. package/examples/vite/main.js +33 -0
  23. package/examples/vite/package.json +16 -0
  24. package/examples/vite/vite.config.js +6 -0
  25. package/package.json +27 -8
  26. package/wasm/build.json +295 -0
  27. package/wasm/experimental-turbo16.wasm +0 -0
  28. package/wasm/experimental-turbo2.wasm +0 -0
  29. package/wasm/experimental-turbo4.wasm +0 -0
  30. package/wasm/experimental-turbo8.wasm +0 -0
  31. package/wasm/high.wasm +0 -0
  32. package/wasm/low.wasm +0 -0
  33. package/wasm/medium.wasm +0 -0
  34. package/wasm/very-high.wasm +0 -0
  35. package/examples/scan-worker.mjs +0 -20
  36. package/examples/worker-client.mjs +0 -63
  37. package/wasm/high-release-1.2.2-public-low-r2.wasm +0 -0
  38. package/wasm/low-release-1.2.2-public-low-r2.wasm +0 -0
  39. package/wasm/medium-release-1.2.2-public-low-r2.wasm +0 -0
  40. 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>;
@@ -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
+ }
@@ -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
@@ -0,0 +1,7 @@
1
+ /** Freeze plain result data in place, so results stay immutable at runtime. */
2
+ export function freeze(value) {
3
+ for (const child of Object.values(value))
4
+ if (child !== null && typeof child === "object")
5
+ freeze(child);
6
+ return Object.freeze(value);
7
+ }
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { type Format, type FormatSelection } from "./multiformat/formats.js";
2
- export { commonFormats, commonLinearFormats, formatBits, linearFormats, matrixFormats, retailFormats, } from "./multiformat/formats.js";
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
- /** Allow reader-specific extra work. Supported for every format; exact budgets may evolve. */
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
- readonly format: Format | "Unknown";
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
- readonly mode: Mode;
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 debug?: Diagnostics;
115
+ readonly diagnostics: Diagnostics;
116
116
  }
117
- export type EanAddOnPolicy = "Ignore" | "Read" | "Require";
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
- readonly mode: Mode;
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
- /** Formats available for scanning, fixed at creation. */
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 & ScanOptions): Promise<ScanResult>;
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>;