tapirscan 1.2.1 → 1.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/README.md +7 -3
- package/dist/index.d.ts +24 -29
- package/dist/index.js +64 -156
- package/dist/multiformat/format-registry.d.ts +26 -0
- package/dist/multiformat/format-registry.js +62 -0
- package/dist/multiformat/formats.d.ts +2 -24
- package/dist/multiformat/formats.js +2 -40
- package/dist/rust-session.d.ts +19 -0
- package/dist/rust-session.js +92 -0
- package/package.json +7 -9
- 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
- package/dist/completion-host.d.mts +0 -40
- package/dist/completion-host.mjs +0 -188
- package/dist/detail-20260914/continuity.d.mts +0 -2
- package/dist/detail-20260914/continuity.mjs +0 -62
- package/dist/detail-20260914/detail-proposals-rich.d.mts +0 -13
- package/dist/detail-20260914/detail-proposals-rich.mjs +0 -53
- package/dist/detail-20260914/direct-recovery.d.mts +0 -28
- package/dist/detail-20260914/direct-recovery.mjs +0 -139
- package/dist/detail-20260914/host.d.mts +0 -40
- package/dist/detail-20260914/host.mjs +0 -280
- package/dist/detail-20260914/scanner.d.mts +0 -12
- package/dist/detail-20260914/scanner.mjs +0 -64
- package/dist/detail-20260914/source-evidence.d.mts +0 -10
- package/dist/detail-20260914/source-evidence.mjs +0 -94
- package/dist/detail-canvas.d.ts +0 -35
- package/dist/detail-canvas.js +0 -73
- package/dist/detail-runtime/direct-recovery.d.mts +0 -28
- package/dist/detail-runtime/direct-recovery.mjs +0 -142
- package/dist/detail-runtime/host.d.mts +0 -40
- package/dist/detail-runtime/host.mjs +0 -284
- package/dist/detail-runtime/scanner.d.mts +0 -12
- package/dist/detail-runtime/scanner.mjs +0 -70
- package/dist/detail.d.ts +0 -20
- package/dist/detail.js +0 -84
- package/dist/host.d.ts +0 -112
- package/dist/host.js +0 -184
- package/dist/host64.d.ts +0 -112
- package/dist/host64.js +0 -184
- package/dist/multiformat/coverage.d.ts +0 -7
- package/dist/multiformat/coverage.js +0 -26
- package/dist/multiformat/geometry.d.ts +0 -37
- package/dist/multiformat/geometry.js +0 -132
- package/dist/multiformat/linear-duplicates.d.ts +0 -16
- package/dist/multiformat/linear-duplicates.js +0 -163
- package/dist/multiformat/pixels.d.ts +0 -3
- package/dist/multiformat/pixels.js +0 -36
- package/dist/multiformat/scanner.d.ts +0 -60
- package/dist/multiformat/scanner.js +0 -382
- package/dist/multiformat-host.d.ts +0 -121
- package/dist/multiformat-host.js +0 -207
- package/dist/policy.d.ts +0 -12
- package/dist/policy.js +0 -12
- package/wasm/high-shared-retail-portable-20260919.wasm +0 -0
- package/wasm/low-shared-retail-portable-20260919.wasm +0 -0
- package/wasm/medium-shared-retail-portable-20260919.wasm +0 -0
- package/wasm/multiformat.json +0 -7
- package/wasm/multiformat.wasm +0 -0
- package/wasm/very-high-shared-retail-portable-20260919.wasm +0 -0
package/README.md
CHANGED
|
@@ -91,11 +91,12 @@ The one-shot helper accepts the same option:
|
|
|
91
91
|
URLs resolve against the page/worker URL in browsers and the package module in
|
|
92
92
|
Node; use an absolute URL for an unambiguous location. For authenticated requests
|
|
93
93
|
or custom storage, use `loadWasm: async (url) => arrayBuffer`. The callback receives
|
|
94
|
-
URLs resolved against `wasmBaseUrl` when both options are supplied.
|
|
94
|
+
URLs resolved against `wasmBaseUrl` when both options are supplied. Creation loads
|
|
95
|
+
one complete Rust scanner for the selected effort mode.
|
|
95
96
|
|
|
96
97
|
Use the deployed base path if your app is hosted below a subpath. Copy all current
|
|
97
|
-
WASMs
|
|
98
|
-
|
|
98
|
+
WASMs so every effort mode remains available. The demo and its comparison engines
|
|
99
|
+
are not needed in your app.
|
|
99
100
|
|
|
100
101
|
## Camera and worker use
|
|
101
102
|
|
|
@@ -200,6 +201,9 @@ including separate copies of the same value. Coordinates start at the
|
|
|
200
201
|
top left, x rightward and y downward. Geometry is returned, not a cropped bitmap.
|
|
201
202
|
Map coordinates back yourself if you resize/rotate before scanning. Support is a
|
|
202
203
|
ranking heuristic, not a probability.
|
|
204
|
+
Polygon coordinates are Rust `f32` values exposed as JavaScript numbers. Their
|
|
205
|
+
decimal string form may show the exact binary value instead of the shorter
|
|
206
|
+
decimal spelling used by older package builds.
|
|
203
207
|
|
|
204
208
|
The package exports `EanAddOnPolicy`, `ScannerOptions`, `ScanOptions`, `ScanResult`, `Barcode`,
|
|
205
209
|
`PixelImage`, `Image`, `Quad`, `Mode`, `Format`, `FormatSelection`, `Diagnostics`,
|
package/dist/index.d.ts
CHANGED
|
@@ -1,17 +1,13 @@
|
|
|
1
|
-
import type { Recovery, DetailRegion } from "./detail-20260914/scanner.mjs";
|
|
2
|
-
import { type Barcode as FormatBarcode } from "./multiformat/scanner.js";
|
|
3
1
|
import { type Format, type FormatSelection } from "./multiformat/formats.js";
|
|
4
2
|
export { commonFormats, commonLinearFormats, formatBits, linearFormats, matrixFormats, retailFormats, } from "./multiformat/formats.js";
|
|
5
3
|
export type { Format, FormatSelection } from "./multiformat/formats.js";
|
|
6
|
-
|
|
7
|
-
/** Source-image corners, in pixels. */
|
|
4
|
+
export { ScannerError } from "./rust-session.js";
|
|
8
5
|
export type Quad = readonly [
|
|
9
6
|
readonly [number, number],
|
|
10
7
|
readonly [number, number],
|
|
11
8
|
readonly [number, number],
|
|
12
9
|
readonly [number, number]
|
|
13
10
|
];
|
|
14
|
-
/** Decoded pixels; stride defaults to width * channels. Alpha is ignored. */
|
|
15
11
|
export interface Image {
|
|
16
12
|
readonly data: Uint8Array;
|
|
17
13
|
readonly width: number;
|
|
@@ -19,58 +15,56 @@ export interface Image {
|
|
|
19
15
|
readonly channels: 1 | 3 | 4;
|
|
20
16
|
readonly stride?: number;
|
|
21
17
|
}
|
|
22
|
-
export { ScannerError } from "./completion-host.mjs";
|
|
23
|
-
type RawDiagnosticBarcode = FormatBarcode | (ScanFrame["barcodes"][number] & {
|
|
24
|
-
format: "EAN13";
|
|
25
|
-
});
|
|
26
18
|
export type Mode = "low" | "medium" | "high" | "very-high";
|
|
27
19
|
export interface ScanOptions {
|
|
28
20
|
/** Allow reader-specific extra work. Supported for every format; exact budgets may evolve. */
|
|
29
21
|
extendedBudget?: boolean;
|
|
30
22
|
/** Per-call subset of the formats configured at creation. */
|
|
31
23
|
formats?: FormatSelection;
|
|
24
|
+
/** Include raw engine diagnostics. Public barcodes and unread geometry are always included. */
|
|
32
25
|
debug?: boolean;
|
|
33
26
|
}
|
|
34
27
|
interface RawDiagnostics {
|
|
35
|
-
schemaVersion: 2;
|
|
36
|
-
mode: Mode;
|
|
37
|
-
multiple: boolean;
|
|
38
|
-
elapsedMs: number;
|
|
39
|
-
localizationLimited: boolean;
|
|
40
28
|
scan: {
|
|
41
29
|
barcodes: RawDiagnosticBarcode[];
|
|
42
30
|
unfinished: boolean;
|
|
43
|
-
|
|
44
|
-
}
|
|
31
|
+
candidates?: unknown[];
|
|
32
|
+
};
|
|
45
33
|
localization?: {
|
|
46
34
|
proposals: {
|
|
47
|
-
polygon:
|
|
48
|
-
score
|
|
49
|
-
text
|
|
35
|
+
polygon: Quad;
|
|
36
|
+
score?: number;
|
|
37
|
+
text?: string;
|
|
50
38
|
}[];
|
|
51
|
-
omitted: number;
|
|
52
|
-
workLimited: boolean;
|
|
53
|
-
trace?: Record<string, number>;
|
|
54
39
|
};
|
|
55
|
-
recovery?: Recovery;
|
|
56
|
-
detailRegions?: DetailRegion[];
|
|
57
40
|
searchWindows?: {
|
|
58
41
|
kind: string;
|
|
59
42
|
polygon: number[][];
|
|
60
43
|
candidateIndex: number;
|
|
61
44
|
}[];
|
|
45
|
+
[key: string]: unknown;
|
|
62
46
|
}
|
|
63
|
-
/** Deeply immutable scan evidence, independent of the scanner lifetime. */
|
|
64
47
|
type ReadonlyDeep<T> = T extends object ? {
|
|
65
48
|
readonly [K in keyof T]: ReadonlyDeep<T[K]>;
|
|
66
49
|
} : T;
|
|
50
|
+
interface RawDiagnosticBarcode {
|
|
51
|
+
text: string;
|
|
52
|
+
format: Format | "Unknown";
|
|
53
|
+
polygon: Quad;
|
|
54
|
+
support: number;
|
|
55
|
+
bytes?: number[];
|
|
56
|
+
candidate_indices?: number[];
|
|
57
|
+
gs1?: boolean;
|
|
58
|
+
readerInitialization?: boolean;
|
|
59
|
+
structuredAppend?: StructuredAppend;
|
|
60
|
+
eanAddOn?: string;
|
|
61
|
+
[key: string]: unknown;
|
|
62
|
+
}
|
|
67
63
|
export type DiagnosticBarcode = ReadonlyDeep<RawDiagnosticBarcode>;
|
|
68
|
-
/** Source geometry with no accepted decode; format is a reader hint. */
|
|
69
64
|
export interface UndecodedRegion {
|
|
70
65
|
readonly format: Format | "Unknown";
|
|
71
66
|
readonly polygon: Quad;
|
|
72
67
|
}
|
|
73
|
-
/** Stable region evidence. null means this reader did not expose that evidence. */
|
|
74
68
|
export interface RegionEvidence {
|
|
75
69
|
readonly proposals: ReadonlyDeep<NonNullable<RawDiagnostics["localization"]>["proposals"]> | null;
|
|
76
70
|
readonly searchWindows: ReadonlyDeep<NonNullable<RawDiagnostics["searchWindows"]>> | null;
|
|
@@ -80,7 +74,6 @@ export type Diagnostics = ReadonlyDeep<RawDiagnostics> & {
|
|
|
80
74
|
readonly regions: RegionEvidence;
|
|
81
75
|
};
|
|
82
76
|
export interface StructuredAppend {
|
|
83
|
-
/** One-based symbol index; symbols are not automatically assembled. */
|
|
84
77
|
readonly index: number;
|
|
85
78
|
readonly count: number;
|
|
86
79
|
readonly id?: string;
|
|
@@ -115,6 +108,7 @@ export interface ScanResult {
|
|
|
115
108
|
readonly height: number;
|
|
116
109
|
};
|
|
117
110
|
readonly mode: Mode;
|
|
111
|
+
/** Whole synchronous WASM call time measured by the JavaScript host. */
|
|
118
112
|
readonly elapsedMs: number;
|
|
119
113
|
readonly unfinished: boolean;
|
|
120
114
|
readonly undecoded: readonly UndecodedRegion[];
|
|
@@ -132,7 +126,7 @@ export interface ScannerOptions {
|
|
|
132
126
|
loadWasm?: (url: URL) => Promise<ArrayBuffer>;
|
|
133
127
|
}
|
|
134
128
|
export type PixelImage = Image | Pick<ImageData, "data" | "width" | "height">;
|
|
135
|
-
/**
|
|
129
|
+
/** A reusable scanner. Results own their data and remain valid after later scans or disposal. */
|
|
136
130
|
export declare class Scanner {
|
|
137
131
|
private readonly host;
|
|
138
132
|
readonly mode: Mode;
|
|
@@ -144,6 +138,7 @@ export declare class Scanner {
|
|
|
144
138
|
get eanAddOnPolicy(): EanAddOnPolicy;
|
|
145
139
|
static create(options?: ScannerOptions): Promise<Scanner>;
|
|
146
140
|
scan(inputImage: PixelImage, options?: ScanOptions): ScanResult;
|
|
141
|
+
/** Release the WASM session. Repeated disposal is safe; scanning afterward fails. */
|
|
147
142
|
dispose(): void;
|
|
148
143
|
}
|
|
149
144
|
/** Scan one image with automatic cleanup. Reuse Scanner for a stream of images. */
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { policy } from "./policy.js";
|
|
4
|
-
import { MediumMultiformatScanner } from "./multiformat/scanner.js";
|
|
5
|
-
import { resolveFormats } from "./multiformat/formats.js";
|
|
1
|
+
import { maskFor, resolveFormats, } from "./multiformat/formats.js";
|
|
2
|
+
import { RustScannerSession, ScannerError } from "./rust-session.js";
|
|
6
3
|
export { commonFormats, commonLinearFormats, formatBits, linearFormats, matrixFormats, retailFormats, } from "./multiformat/formats.js";
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
export { ScannerError } from "./rust-session.js";
|
|
5
|
+
const modes = {
|
|
6
|
+
low: { id: 0, file: "low-release-1.2.2-public-low-r2.wasm" },
|
|
7
|
+
medium: { id: 1, file: "medium-release-1.2.2-public-low-r2.wasm" },
|
|
8
|
+
high: { id: 2, file: "high-release-1.2.2-public-low-r2.wasm" },
|
|
9
|
+
"very-high": { id: 3, file: "very-high-release-1.2.2-public-low-r2.wasm" },
|
|
10
|
+
};
|
|
11
|
+
const addOnPolicies = { Ignore: 0, Read: 1, Require: 2 };
|
|
9
12
|
function pixels(image) {
|
|
10
13
|
const input = image;
|
|
11
14
|
if (input === null || typeof input !== "object")
|
|
@@ -36,7 +39,6 @@ function pixels(image) {
|
|
|
36
39
|
stride,
|
|
37
40
|
};
|
|
38
41
|
}
|
|
39
|
-
/** Freeze only plain result data, never caller-owned input buffers. */
|
|
40
42
|
function freeze(value) {
|
|
41
43
|
for (const child of Object.values(value))
|
|
42
44
|
if (child !== null && typeof child === "object")
|
|
@@ -54,89 +56,58 @@ async function loadDefault(url) {
|
|
|
54
56
|
throw new Error(`WASM load failed: ${String(response.status)}`);
|
|
55
57
|
return response.arrayBuffer();
|
|
56
58
|
}
|
|
57
|
-
function
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
59
|
+
function wireResult(value) {
|
|
60
|
+
if (value === null ||
|
|
61
|
+
typeof value !== "object" ||
|
|
62
|
+
!("barcodes" in value) ||
|
|
63
|
+
!Array.isArray(value.barcodes) ||
|
|
64
|
+
!("bestIndex" in value) ||
|
|
65
|
+
(value.bestIndex !== null &&
|
|
66
|
+
(!Number.isInteger(value.bestIndex) ||
|
|
67
|
+
value.bestIndex < 0 ||
|
|
68
|
+
value.bestIndex >= value.barcodes.length)) ||
|
|
69
|
+
!("undecoded" in value) ||
|
|
70
|
+
!Array.isArray(value.undecoded) ||
|
|
71
|
+
!("image" in value) ||
|
|
72
|
+
value.image === null ||
|
|
73
|
+
typeof value.image !== "object" ||
|
|
74
|
+
!("mode" in value) ||
|
|
75
|
+
!Object.hasOwn(modes, String(value.mode)) ||
|
|
76
|
+
!("unfinished" in value) ||
|
|
77
|
+
typeof value.unfinished !== "boolean")
|
|
78
|
+
throw new ScannerError("invalid_output", "Scanner returned an invalid result");
|
|
79
|
+
return value;
|
|
80
|
+
}
|
|
81
|
+
function publicResult(raw, elapsedMs, debug) {
|
|
82
|
+
const barcodes = raw.barcodes;
|
|
83
|
+
const undecoded = raw.undecoded.map(({ format, polygon }) => ({
|
|
84
|
+
format: format ?? "Unknown",
|
|
85
|
+
polygon,
|
|
86
|
+
}));
|
|
87
|
+
const regions = {
|
|
88
|
+
proposals: raw.debug?.localization?.proposals ?? null,
|
|
89
|
+
searchWindows: raw.debug?.searchWindows ?? null,
|
|
82
90
|
undecoded,
|
|
83
91
|
};
|
|
84
|
-
}
|
|
85
|
-
function publicResult(raw, image, debug) {
|
|
86
|
-
const barcodes = raw.scan.barcodes.map((read) => {
|
|
87
|
-
const { text, format, polygon, support } = read;
|
|
88
|
-
const left = Math.floor(Math.min(...polygon.map((p) => p[0])));
|
|
89
|
-
const top = Math.floor(Math.min(...polygon.map((p) => p[1])));
|
|
90
|
-
return {
|
|
91
|
-
text,
|
|
92
|
-
format,
|
|
93
|
-
polygon,
|
|
94
|
-
support,
|
|
95
|
-
...("bytes" in read ? { payloadBytes: read.bytes } : {}),
|
|
96
|
-
...("gs1" in read ? { gs1: read.gs1 } : {}),
|
|
97
|
-
...("readerInitialization" in read
|
|
98
|
-
? { readerInitialization: read.readerInitialization }
|
|
99
|
-
: {}),
|
|
100
|
-
...("structuredAppend" in read ? { structuredAppend: read.structuredAppend } : {}),
|
|
101
|
-
...("eanAddOn" in read ? { eanAddOn: read.eanAddOn } : {}),
|
|
102
|
-
rect: {
|
|
103
|
-
left,
|
|
104
|
-
top,
|
|
105
|
-
width: Math.ceil(Math.max(...polygon.map((p) => p[0]))) - left,
|
|
106
|
-
height: Math.ceil(Math.max(...polygon.map((p) => p[1]))) - top,
|
|
107
|
-
},
|
|
108
|
-
};
|
|
109
|
-
});
|
|
110
|
-
let bestIndex = -1;
|
|
111
|
-
for (let i = 0; i < barcodes.length; i++)
|
|
112
|
-
if (bestIndex < 0 || raw.scan.barcodes[i].support > raw.scan.barcodes[bestIndex].support)
|
|
113
|
-
bestIndex = i;
|
|
114
|
-
const regions = regionEvidence(raw);
|
|
115
92
|
return freeze({
|
|
116
93
|
barcodes,
|
|
117
94
|
values: barcodes.map((b) => b.text),
|
|
118
|
-
best: barcodes[bestIndex],
|
|
119
|
-
|
|
120
|
-
image: { width: image.width, height: image.height },
|
|
95
|
+
best: raw.bestIndex === null ? undefined : barcodes[raw.bestIndex],
|
|
96
|
+
image: raw.image,
|
|
121
97
|
mode: raw.mode,
|
|
122
|
-
elapsedMs
|
|
123
|
-
unfinished: raw.
|
|
124
|
-
|
|
98
|
+
elapsedMs,
|
|
99
|
+
unfinished: raw.unfinished,
|
|
100
|
+
undecoded,
|
|
101
|
+
...(debug && raw.debug ? { debug: { ...raw.debug, regions } } : {}),
|
|
125
102
|
});
|
|
126
103
|
}
|
|
127
|
-
|
|
128
|
-
low: "low-shared-retail-portable-20260919.wasm",
|
|
129
|
-
medium: "medium-shared-retail-portable-20260919.wasm",
|
|
130
|
-
high: "high-shared-retail-portable-20260919.wasm",
|
|
131
|
-
"very-high": "very-high-shared-retail-portable-20260919.wasm",
|
|
132
|
-
};
|
|
133
|
-
/** Mode selects a compiled implementation. Create another instance to switch. */
|
|
104
|
+
/** A reusable scanner. Results own their data and remain valid after later scans or disposal. */
|
|
134
105
|
export class Scanner {
|
|
135
106
|
host;
|
|
136
107
|
mode;
|
|
137
108
|
configuredFormats;
|
|
138
109
|
addOnPolicy;
|
|
139
|
-
constructor(host, mode, configuredFormats
|
|
110
|
+
constructor(host, mode, configuredFormats, addOnPolicy) {
|
|
140
111
|
this.host = host;
|
|
141
112
|
this.mode = mode;
|
|
142
113
|
this.configuredFormats = configuredFormats;
|
|
@@ -161,8 +132,8 @@ export class Scanner {
|
|
|
161
132
|
if (!Object.hasOwn(modes, mode))
|
|
162
133
|
throw new TypeError("Unknown scanner mode");
|
|
163
134
|
const formats = resolveFormats(options.formats);
|
|
164
|
-
const
|
|
165
|
-
if (!
|
|
135
|
+
const addOnPolicy = options.eanAddOnPolicy === undefined ? "Ignore" : options.eanAddOnPolicy;
|
|
136
|
+
if (!Object.hasOwn(addOnPolicies, addOnPolicy))
|
|
166
137
|
throw new TypeError("eanAddOnPolicy must be Ignore, Read or Require");
|
|
167
138
|
if (options.loadWasm !== undefined && typeof options.loadWasm !== "function")
|
|
168
139
|
throw new TypeError("loadWasm must be a function");
|
|
@@ -175,24 +146,10 @@ export class Scanner {
|
|
|
175
146
|
: new URL(options.wasmBaseUrl, typeof location === "undefined" ? import.meta.url : location.href);
|
|
176
147
|
if (!base.pathname.endsWith("/"))
|
|
177
148
|
base.pathname += "/";
|
|
178
|
-
const
|
|
179
|
-
const
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
const recovery = !needsPrimary || mode === "low" ? undefined : await load(new URL(modes.low, base));
|
|
183
|
-
const multiformat = addonPolicy !== "Ignore" || formats.length !== 1 || formats[0] !== "EAN13";
|
|
184
|
-
const extra = addonPolicy !== "Ignore" ||
|
|
185
|
-
formats.some((f) => f !== "EAN13" && f !== "UPCA" && (!sharedRetail || (f !== "EAN8" && f !== "UPCE")))
|
|
186
|
-
? await load(new URL("multiformat.wasm", base))
|
|
187
|
-
: undefined;
|
|
188
|
-
if (multiformat)
|
|
189
|
-
return new Scanner(await MediumMultiformatScanner.create(bytes, extra, mode, recovery), mode, formats, addonPolicy);
|
|
190
|
-
if (!bytes)
|
|
191
|
-
throw new Error("EAN13 engine was not loaded");
|
|
192
|
-
const host = recovery
|
|
193
|
-
? await ReleaseDetailScanner.create(bytes, recovery, mode)
|
|
194
|
-
: await IndependentScanner.create(bytes);
|
|
195
|
-
return new Scanner(host, mode, formats);
|
|
149
|
+
const selected = modes[mode];
|
|
150
|
+
const bytes = await (options.loadWasm ?? loadDefault)(new URL(selected.file, base));
|
|
151
|
+
const host = await RustScannerSession.create(bytes, selected.id, maskFor(formats), addOnPolicies[addOnPolicy]);
|
|
152
|
+
return new Scanner(host, mode, formats, addOnPolicy);
|
|
196
153
|
}
|
|
197
154
|
scan(inputImage, options = {}) {
|
|
198
155
|
const image = pixels(inputImage);
|
|
@@ -200,73 +157,24 @@ export class Scanner {
|
|
|
200
157
|
if (input === null || typeof input !== "object" || Array.isArray(input))
|
|
201
158
|
throw new TypeError("Invalid scan options");
|
|
202
159
|
for (const key of Object.keys(options))
|
|
203
|
-
if (
|
|
160
|
+
if (!["debug", "formats", "extendedBudget"].includes(key))
|
|
204
161
|
throw new TypeError(`Unknown scan option: ${key}`);
|
|
205
162
|
if (options.debug !== undefined && typeof options.debug !== "boolean")
|
|
206
163
|
throw new TypeError("debug must be a boolean");
|
|
207
164
|
if (options.extendedBudget !== undefined && typeof options.extendedBudget !== "boolean")
|
|
208
165
|
throw new TypeError("extendedBudget must be a boolean");
|
|
209
|
-
const finishCandidates = options.extendedBudget ?? false;
|
|
210
|
-
const debug = options.debug ?? false;
|
|
211
|
-
const scanPolicy = { ...policy, finishCandidates };
|
|
212
166
|
const formats = options.formats === undefined ? this.formats : resolveFormats(options.formats);
|
|
213
167
|
if (formats.some((format) => !this.formats.includes(format)))
|
|
214
168
|
throw new TypeError(`Scan formats must be a subset of configured formats. Requested: ${formats.join(", ")}; configured: ${this.formats.join(", ")}`);
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
const localization = primary?.localization;
|
|
223
|
-
return publicResult({
|
|
224
|
-
schemaVersion: 2,
|
|
225
|
-
mode: this.mode,
|
|
226
|
-
multiple: true,
|
|
227
|
-
elapsedMs: frame.scanMs,
|
|
228
|
-
// Preserve the aggregate limit flag when additional readers cannot separate causes.
|
|
229
|
-
localizationLimited: frame.unfinished,
|
|
230
|
-
scan: {
|
|
231
|
-
...primary?.scan,
|
|
232
|
-
barcodes,
|
|
233
|
-
unfinished: frame.unfinished,
|
|
234
|
-
regions: frame.regions,
|
|
235
|
-
},
|
|
236
|
-
...(primary
|
|
237
|
-
? {
|
|
238
|
-
localization,
|
|
239
|
-
searchWindows: primary.searchWindows,
|
|
240
|
-
...("recovery" in primary ? { recovery: primary.recovery } : {}),
|
|
241
|
-
...("detailRegions" in primary ? { detailRegions: primary.detailRegions } : {}),
|
|
242
|
-
}
|
|
243
|
-
: {}),
|
|
244
|
-
}, image, debug);
|
|
245
|
-
}
|
|
246
|
-
const full = this.host.scanLocalized(image, scanPolicy, fitLimits[this.mode], true);
|
|
247
|
-
// Isolate the imported host's JSON result at the public ABI type boundary.
|
|
248
|
-
const localization = full.localization;
|
|
249
|
-
const barcodes = mergeLinearDuplicates(full.scan.barcodes.map((b) => ({
|
|
250
|
-
...b,
|
|
251
|
-
format: "EAN13",
|
|
252
|
-
})), image);
|
|
253
|
-
return publicResult({
|
|
254
|
-
schemaVersion: 2,
|
|
255
|
-
mode: this.mode,
|
|
256
|
-
multiple: true,
|
|
257
|
-
elapsedMs: full.scanMs,
|
|
258
|
-
localizationLimited: localization.workLimited || localization.omitted > 0,
|
|
259
|
-
scan: { ...full.scan, barcodes },
|
|
260
|
-
localization,
|
|
261
|
-
searchWindows: full.searchWindows,
|
|
262
|
-
...("recovery" in full && "detailRegions" in full
|
|
263
|
-
? {
|
|
264
|
-
recovery: full.recovery,
|
|
265
|
-
detailRegions: full.detailRegions,
|
|
266
|
-
}
|
|
267
|
-
: {}),
|
|
268
|
-
}, image, debug);
|
|
169
|
+
const debug = options.debug ?? false;
|
|
170
|
+
const flags = (options.extendedBudget ? 1 : 0) | (debug ? 2 : 0);
|
|
171
|
+
const start = performance.now();
|
|
172
|
+
const raw = wireResult(this.host.scan(image, flags, options.formats === undefined ? 0 : maskFor(formats)));
|
|
173
|
+
if (debug && !raw.debug)
|
|
174
|
+
throw new ScannerError("invalid_output", "Scanner omitted requested diagnostics");
|
|
175
|
+
return publicResult(raw, performance.now() - start, debug);
|
|
269
176
|
}
|
|
177
|
+
/** Release the WASM session. Repeated disposal is safe; scanning afterward fails. */
|
|
270
178
|
dispose() {
|
|
271
179
|
this.host.dispose();
|
|
272
180
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export declare const formatBits: {
|
|
2
|
+
readonly EAN13: 1;
|
|
3
|
+
readonly UPCA: 2;
|
|
4
|
+
readonly EAN8: 4;
|
|
5
|
+
readonly UPCE: 8;
|
|
6
|
+
readonly Code128: 16;
|
|
7
|
+
readonly Code39: 32;
|
|
8
|
+
readonly ITF: 64;
|
|
9
|
+
readonly Codabar: 128;
|
|
10
|
+
readonly Code93: 256;
|
|
11
|
+
readonly QRCode: 512;
|
|
12
|
+
readonly DataMatrix: 1024;
|
|
13
|
+
readonly PDF417: 2048;
|
|
14
|
+
readonly Aztec: 4096;
|
|
15
|
+
readonly DataBar: 8192;
|
|
16
|
+
readonly DataBarExpanded: 16384;
|
|
17
|
+
readonly MaxiCode: 131072;
|
|
18
|
+
};
|
|
19
|
+
export type Format = keyof typeof formatBits;
|
|
20
|
+
export declare const retailFormats: readonly Format[];
|
|
21
|
+
export declare const commonLinearFormats: readonly Format[];
|
|
22
|
+
export declare const commonFormats: readonly Format[];
|
|
23
|
+
export declare const linearFormats: readonly Format[];
|
|
24
|
+
export declare const matrixFormats: readonly Format[];
|
|
25
|
+
export declare const eanAddOnReadFlag = 32768;
|
|
26
|
+
export declare const eanAddOnRequireFlag = 65536;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// Generated by scripts/generate_formats.py from config/formats.json. Do not edit.
|
|
2
|
+
export const formatBits = {
|
|
3
|
+
EAN13: 1,
|
|
4
|
+
UPCA: 2,
|
|
5
|
+
EAN8: 4,
|
|
6
|
+
UPCE: 8,
|
|
7
|
+
Code128: 16,
|
|
8
|
+
Code39: 32,
|
|
9
|
+
ITF: 64,
|
|
10
|
+
Codabar: 128,
|
|
11
|
+
Code93: 256,
|
|
12
|
+
QRCode: 512,
|
|
13
|
+
DataMatrix: 1024,
|
|
14
|
+
PDF417: 2048,
|
|
15
|
+
Aztec: 4096,
|
|
16
|
+
DataBar: 8192,
|
|
17
|
+
DataBarExpanded: 16384,
|
|
18
|
+
MaxiCode: 131072,
|
|
19
|
+
};
|
|
20
|
+
export const retailFormats = ["EAN13", "UPCA", "EAN8", "UPCE"];
|
|
21
|
+
export const commonLinearFormats = [
|
|
22
|
+
"EAN13",
|
|
23
|
+
"UPCA",
|
|
24
|
+
"EAN8",
|
|
25
|
+
"UPCE",
|
|
26
|
+
"Code128",
|
|
27
|
+
"Code39",
|
|
28
|
+
"ITF",
|
|
29
|
+
];
|
|
30
|
+
export const commonFormats = [
|
|
31
|
+
"EAN13",
|
|
32
|
+
"UPCA",
|
|
33
|
+
"EAN8",
|
|
34
|
+
"UPCE",
|
|
35
|
+
"Code128",
|
|
36
|
+
"Code39",
|
|
37
|
+
"ITF",
|
|
38
|
+
"QRCode",
|
|
39
|
+
"DataMatrix",
|
|
40
|
+
];
|
|
41
|
+
export const linearFormats = [
|
|
42
|
+
"EAN13",
|
|
43
|
+
"UPCA",
|
|
44
|
+
"EAN8",
|
|
45
|
+
"UPCE",
|
|
46
|
+
"Code128",
|
|
47
|
+
"Code39",
|
|
48
|
+
"ITF",
|
|
49
|
+
"Codabar",
|
|
50
|
+
"Code93",
|
|
51
|
+
"DataBar",
|
|
52
|
+
"DataBarExpanded",
|
|
53
|
+
];
|
|
54
|
+
export const matrixFormats = [
|
|
55
|
+
"QRCode",
|
|
56
|
+
"DataMatrix",
|
|
57
|
+
"PDF417",
|
|
58
|
+
"Aztec",
|
|
59
|
+
"MaxiCode",
|
|
60
|
+
];
|
|
61
|
+
export const eanAddOnReadFlag = 32768;
|
|
62
|
+
export const eanAddOnRequireFlag = 65536;
|
|
@@ -1,28 +1,6 @@
|
|
|
1
1
|
/** Explicit opt-in formats for the independent Medium extension. */
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
readonly UPCA: 2;
|
|
5
|
-
readonly EAN8: 4;
|
|
6
|
-
readonly UPCE: 8;
|
|
7
|
-
readonly Code128: 16;
|
|
8
|
-
readonly Code39: 32;
|
|
9
|
-
readonly ITF: 64;
|
|
10
|
-
readonly Codabar: 128;
|
|
11
|
-
readonly Code93: 256;
|
|
12
|
-
readonly QRCode: 512;
|
|
13
|
-
readonly DataMatrix: 1024;
|
|
14
|
-
readonly PDF417: 2048;
|
|
15
|
-
readonly Aztec: 4096;
|
|
16
|
-
readonly DataBar: 8192;
|
|
17
|
-
readonly DataBarExpanded: 16384;
|
|
18
|
-
readonly MaxiCode: 131072;
|
|
19
|
-
};
|
|
20
|
-
export type Format = keyof typeof formatBits;
|
|
21
|
-
export declare const retailFormats: readonly Format[];
|
|
22
|
-
export declare const commonLinearFormats: readonly Format[];
|
|
23
|
-
export declare const commonFormats: readonly Format[];
|
|
24
|
-
export declare const linearFormats: readonly Format[];
|
|
25
|
-
export declare const matrixFormats: readonly Format[];
|
|
2
|
+
import { commonFormats, commonLinearFormats, formatBits, linearFormats, matrixFormats, retailFormats, type Format } from "./format-registry.js";
|
|
3
|
+
export { commonFormats, commonLinearFormats, formatBits, linearFormats, matrixFormats, retailFormats, type Format, };
|
|
26
4
|
export type FormatSelection = Format | readonly Format[] | "retail" | "common1D" | "common" | "1D" | "2D" | "all";
|
|
27
5
|
export declare function resolveFormats(input?: readonly string[] | string): Format[];
|
|
28
6
|
export declare function maskFor(formats: readonly Format[]): number;
|
|
@@ -1,44 +1,6 @@
|
|
|
1
1
|
/** Explicit opt-in formats for the independent Medium extension. */
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
UPCA: 2,
|
|
5
|
-
EAN8: 4,
|
|
6
|
-
UPCE: 8,
|
|
7
|
-
Code128: 16,
|
|
8
|
-
Code39: 32,
|
|
9
|
-
ITF: 64,
|
|
10
|
-
Codabar: 128,
|
|
11
|
-
Code93: 256,
|
|
12
|
-
QRCode: 512,
|
|
13
|
-
DataMatrix: 1024,
|
|
14
|
-
PDF417: 2048,
|
|
15
|
-
Aztec: 4096,
|
|
16
|
-
DataBar: 8192,
|
|
17
|
-
DataBarExpanded: 16384,
|
|
18
|
-
MaxiCode: 131072,
|
|
19
|
-
};
|
|
20
|
-
export const retailFormats = ["EAN13", "UPCA", "EAN8", "UPCE"];
|
|
21
|
-
export const commonLinearFormats = [
|
|
22
|
-
...retailFormats,
|
|
23
|
-
"Code128",
|
|
24
|
-
"Code39",
|
|
25
|
-
"ITF",
|
|
26
|
-
];
|
|
27
|
-
export const commonFormats = [...commonLinearFormats, "QRCode", "DataMatrix"];
|
|
28
|
-
export const linearFormats = [
|
|
29
|
-
...commonLinearFormats,
|
|
30
|
-
"Codabar",
|
|
31
|
-
"Code93",
|
|
32
|
-
"DataBar",
|
|
33
|
-
"DataBarExpanded",
|
|
34
|
-
];
|
|
35
|
-
export const matrixFormats = [
|
|
36
|
-
"QRCode",
|
|
37
|
-
"DataMatrix",
|
|
38
|
-
"PDF417",
|
|
39
|
-
"Aztec",
|
|
40
|
-
"MaxiCode",
|
|
41
|
-
];
|
|
2
|
+
import { commonFormats, commonLinearFormats, formatBits, linearFormats, matrixFormats, retailFormats, } from "./format-registry.js";
|
|
3
|
+
export { commonFormats, commonLinearFormats, formatBits, linearFormats, matrixFormats, retailFormats, };
|
|
42
4
|
export function resolveFormats(input) {
|
|
43
5
|
if (input === "retail")
|
|
44
6
|
return [...retailFormats];
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export declare class ScannerError extends Error {
|
|
2
|
+
readonly code: string;
|
|
3
|
+
constructor(code: string, message: string);
|
|
4
|
+
}
|
|
5
|
+
export declare class RustScannerSession {
|
|
6
|
+
private readonly exports;
|
|
7
|
+
private handle;
|
|
8
|
+
private constructor();
|
|
9
|
+
static create(bytes: ArrayBuffer, mode: number, formats: number, addOnPolicy: number): Promise<RustScannerSession>;
|
|
10
|
+
scan(image: {
|
|
11
|
+
data: Uint8Array;
|
|
12
|
+
width: number;
|
|
13
|
+
height: number;
|
|
14
|
+
channels: number;
|
|
15
|
+
stride: number;
|
|
16
|
+
}, flags: number, formats: number): unknown;
|
|
17
|
+
dispose(): void;
|
|
18
|
+
private activeExports;
|
|
19
|
+
}
|