tapirscan 1.2.0 → 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 +9 -10
- package/dist/index.d.ts +24 -29
- package/dist/index.js +64 -154
- 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 +3 -41
- 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 -282
- package/dist/detail-runtime/scanner.d.mts +0 -12
- package/dist/detail-runtime/scanner.mjs +0 -70
- package/dist/detail.d.ts +0 -18
- 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 -361
- 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-complete-release-20260916.wasm +0 -0
- package/wasm/low-complete-release-20260916.wasm +0 -0
- package/wasm/medium-complete-release-20260916.wasm +0 -0
- package/wasm/multiformat.json +0 -7
- package/wasm/multiformat.wasm +0 -0
- package/wasm/very-high-complete-release-20260916.wasm +0 -0
package/README.md
CHANGED
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
# Tapirscan for JavaScript and TypeScript
|
|
2
2
|
|
|
3
|
-
This guide describes the 1.2.0 API revision. See [migration](../../docs/API_MIGRATION.md).
|
|
4
|
-
Build/install this checkout using [the development guide](../../docs/DEVELOPMENT.md)
|
|
5
|
-
to use these changes before publication; older registry packages use their own
|
|
6
|
-
versioned API.
|
|
7
|
-
|
|
8
3
|
Scan image pixels in a browser or Node with the same Rust/WASM core.
|
|
9
4
|
[Try the live demo](https://tapirscan.netlify.app) · [Quick start](#quick-start) · [WASM loading](#wasm-loading) · [Functions](#functions) · [All options](#all-options) · [Results](#results)
|
|
10
5
|
|
|
@@ -29,7 +24,7 @@ const result = await scan(image);
|
|
|
29
24
|
console.log(result.values); // e.g. ["4006381333931"]
|
|
30
25
|
```
|
|
31
26
|
|
|
32
|
-
Defaults are Medium effort,
|
|
27
|
+
Defaults are Medium effort, retail formats, multiple results, and debug disabled.
|
|
33
28
|
`result.barcodes` also gives each read's text, format, polygon and rectangle.
|
|
34
29
|
The helper creates and disposes a scanner automatically. Browser apps need to
|
|
35
30
|
serve its [WASM assets](#wasm-loading); Node loads the packaged files automatically.
|
|
@@ -96,11 +91,12 @@ The one-shot helper accepts the same option:
|
|
|
96
91
|
URLs resolve against the page/worker URL in browsers and the package module in
|
|
97
92
|
Node; use an absolute URL for an unambiguous location. For authenticated requests
|
|
98
93
|
or custom storage, use `loadWasm: async (url) => arrayBuffer`. The callback receives
|
|
99
|
-
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.
|
|
100
96
|
|
|
101
97
|
Use the deployed base path if your app is hosted below a subpath. Copy all current
|
|
102
|
-
WASMs
|
|
103
|
-
|
|
98
|
+
WASMs so every effort mode remains available. The demo and its comparison engines
|
|
99
|
+
are not needed in your app.
|
|
104
100
|
|
|
105
101
|
## Camera and worker use
|
|
106
102
|
|
|
@@ -141,7 +137,7 @@ call and does not change the default formats. Previously returned results surviv
|
|
|
141
137
|
| Option | Where | Default | Meaning |
|
|
142
138
|
| ---------------- | ------------------- | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
143
139
|
| `mode` | Creation | `"medium"` | `"low"`, `"medium"`, `"high"`, `"very-high"`. |
|
|
144
|
-
| `formats` | Creation / scan | `
|
|
140
|
+
| `formats` | Creation / scan | `"retail"` | A single identifier, `"retail"`, `"common1D"`, `"common"`, `"1D"`, `"2D"`, `"all"`, or a nonempty array. Per-call selections must be subsets of creation formats. |
|
|
145
141
|
| `wasmBaseUrl` | Creation | Module-relative assets | Directory URL for packaged WASMs. Use this for normal browser hosting. |
|
|
146
142
|
| `loadWasm` | Creation | Module-relative loader | `(url: URL) => Promise<ArrayBuffer>`. Uses HTTP fetch in browsers and filesystem reads for Node file URLs. |
|
|
147
143
|
| `eanAddOnPolicy` | Creation / one-shot | `"Ignore"` | `"Ignore"`, `"Read"`, `"Require"`; optional EAN/UPC supplement policy. |
|
|
@@ -205,6 +201,9 @@ including separate copies of the same value. Coordinates start at the
|
|
|
205
201
|
top left, x rightward and y downward. Geometry is returned, not a cropped bitmap.
|
|
206
202
|
Map coordinates back yourself if you resize/rotate before scanning. Support is a
|
|
207
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.
|
|
208
207
|
|
|
209
208
|
The package exports `EanAddOnPolicy`, `ScannerOptions`, `ScanOptions`, `ScanResult`, `Barcode`,
|
|
210
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-complete-release-20260916.wasm",
|
|
129
|
-
medium: "medium-complete-release-20260916.wasm",
|
|
130
|
-
high: "high-complete-release-20260916.wasm",
|
|
131
|
-
"very-high": "very-high-complete-release-20260916.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,22 +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 multiformat = addonPolicy !== "Ignore" || formats.length !== 1 || formats[0] !== "EAN13";
|
|
183
|
-
const extra = addonPolicy !== "Ignore" || formats.some((f) => f !== "EAN13" && f !== "UPCA")
|
|
184
|
-
? await load(new URL("multiformat.wasm", base))
|
|
185
|
-
: undefined;
|
|
186
|
-
if (multiformat)
|
|
187
|
-
return new Scanner(await MediumMultiformatScanner.create(bytes, extra, mode, recovery), mode, formats, addonPolicy);
|
|
188
|
-
if (!bytes)
|
|
189
|
-
throw new Error("EAN13 engine was not loaded");
|
|
190
|
-
const host = recovery
|
|
191
|
-
? await ReleaseDetailScanner.create(bytes, recovery, mode)
|
|
192
|
-
: await IndependentScanner.create(bytes);
|
|
193
|
-
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);
|
|
194
153
|
}
|
|
195
154
|
scan(inputImage, options = {}) {
|
|
196
155
|
const image = pixels(inputImage);
|
|
@@ -198,73 +157,24 @@ export class Scanner {
|
|
|
198
157
|
if (input === null || typeof input !== "object" || Array.isArray(input))
|
|
199
158
|
throw new TypeError("Invalid scan options");
|
|
200
159
|
for (const key of Object.keys(options))
|
|
201
|
-
if (
|
|
160
|
+
if (!["debug", "formats", "extendedBudget"].includes(key))
|
|
202
161
|
throw new TypeError(`Unknown scan option: ${key}`);
|
|
203
162
|
if (options.debug !== undefined && typeof options.debug !== "boolean")
|
|
204
163
|
throw new TypeError("debug must be a boolean");
|
|
205
164
|
if (options.extendedBudget !== undefined && typeof options.extendedBudget !== "boolean")
|
|
206
165
|
throw new TypeError("extendedBudget must be a boolean");
|
|
207
|
-
const finishCandidates = options.extendedBudget ?? false;
|
|
208
|
-
const debug = options.debug ?? false;
|
|
209
|
-
const scanPolicy = { ...policy, finishCandidates };
|
|
210
166
|
const formats = options.formats === undefined ? this.formats : resolveFormats(options.formats);
|
|
211
167
|
if (formats.some((format) => !this.formats.includes(format)))
|
|
212
168
|
throw new TypeError(`Scan formats must be a subset of configured formats. Requested: ${formats.join(", ")}; configured: ${this.formats.join(", ")}`);
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
const localization = primary?.localization;
|
|
221
|
-
return publicResult({
|
|
222
|
-
schemaVersion: 2,
|
|
223
|
-
mode: this.mode,
|
|
224
|
-
multiple: true,
|
|
225
|
-
elapsedMs: frame.scanMs,
|
|
226
|
-
// Preserve the aggregate limit flag when additional readers cannot separate causes.
|
|
227
|
-
localizationLimited: frame.unfinished,
|
|
228
|
-
scan: {
|
|
229
|
-
...primary?.scan,
|
|
230
|
-
barcodes,
|
|
231
|
-
unfinished: frame.unfinished,
|
|
232
|
-
regions: frame.regions,
|
|
233
|
-
},
|
|
234
|
-
...(primary
|
|
235
|
-
? {
|
|
236
|
-
localization,
|
|
237
|
-
searchWindows: primary.searchWindows,
|
|
238
|
-
...("recovery" in primary ? { recovery: primary.recovery } : {}),
|
|
239
|
-
...("detailRegions" in primary ? { detailRegions: primary.detailRegions } : {}),
|
|
240
|
-
}
|
|
241
|
-
: {}),
|
|
242
|
-
}, image, debug);
|
|
243
|
-
}
|
|
244
|
-
const full = this.host.scanLocalized(image, scanPolicy, fitLimits[this.mode], true);
|
|
245
|
-
// Isolate the imported host's JSON result at the public ABI type boundary.
|
|
246
|
-
const localization = full.localization;
|
|
247
|
-
const barcodes = mergeLinearDuplicates(full.scan.barcodes.map((b) => ({
|
|
248
|
-
...b,
|
|
249
|
-
format: "EAN13",
|
|
250
|
-
})), image);
|
|
251
|
-
return publicResult({
|
|
252
|
-
schemaVersion: 2,
|
|
253
|
-
mode: this.mode,
|
|
254
|
-
multiple: true,
|
|
255
|
-
elapsedMs: full.scanMs,
|
|
256
|
-
localizationLimited: localization.workLimited || localization.omitted > 0,
|
|
257
|
-
scan: { ...full.scan, barcodes },
|
|
258
|
-
localization,
|
|
259
|
-
searchWindows: full.searchWindows,
|
|
260
|
-
...("recovery" in full && "detailRegions" in full
|
|
261
|
-
? {
|
|
262
|
-
recovery: full.recovery,
|
|
263
|
-
detailRegions: full.detailRegions,
|
|
264
|
-
}
|
|
265
|
-
: {}),
|
|
266
|
-
}, 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);
|
|
267
176
|
}
|
|
177
|
+
/** Release the WASM session. Repeated disposal is safe; scanning afterward fails. */
|
|
268
178
|
dispose() {
|
|
269
179
|
this.host.dispose();
|
|
270
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];
|
|
@@ -53,7 +15,7 @@ export function resolveFormats(input) {
|
|
|
53
15
|
if (input === "all")
|
|
54
16
|
return [...linearFormats, ...matrixFormats];
|
|
55
17
|
if (input === undefined)
|
|
56
|
-
return [
|
|
18
|
+
return [...retailFormats];
|
|
57
19
|
if (typeof input === "string" && Object.hasOwn(formatBits, input))
|
|
58
20
|
return [input];
|
|
59
21
|
if (!Array.isArray(input) || input.length === 0)
|
|
@@ -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
|
+
}
|