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.
Files changed (62) hide show
  1. package/README.md +7 -3
  2. package/dist/index.d.ts +24 -29
  3. package/dist/index.js +64 -156
  4. package/dist/multiformat/format-registry.d.ts +26 -0
  5. package/dist/multiformat/format-registry.js +62 -0
  6. package/dist/multiformat/formats.d.ts +2 -24
  7. package/dist/multiformat/formats.js +2 -40
  8. package/dist/rust-session.d.ts +19 -0
  9. package/dist/rust-session.js +92 -0
  10. package/package.json +7 -9
  11. package/wasm/high-release-1.2.2-public-low-r2.wasm +0 -0
  12. package/wasm/low-release-1.2.2-public-low-r2.wasm +0 -0
  13. package/wasm/medium-release-1.2.2-public-low-r2.wasm +0 -0
  14. package/wasm/very-high-release-1.2.2-public-low-r2.wasm +0 -0
  15. package/dist/completion-host.d.mts +0 -40
  16. package/dist/completion-host.mjs +0 -188
  17. package/dist/detail-20260914/continuity.d.mts +0 -2
  18. package/dist/detail-20260914/continuity.mjs +0 -62
  19. package/dist/detail-20260914/detail-proposals-rich.d.mts +0 -13
  20. package/dist/detail-20260914/detail-proposals-rich.mjs +0 -53
  21. package/dist/detail-20260914/direct-recovery.d.mts +0 -28
  22. package/dist/detail-20260914/direct-recovery.mjs +0 -139
  23. package/dist/detail-20260914/host.d.mts +0 -40
  24. package/dist/detail-20260914/host.mjs +0 -280
  25. package/dist/detail-20260914/scanner.d.mts +0 -12
  26. package/dist/detail-20260914/scanner.mjs +0 -64
  27. package/dist/detail-20260914/source-evidence.d.mts +0 -10
  28. package/dist/detail-20260914/source-evidence.mjs +0 -94
  29. package/dist/detail-canvas.d.ts +0 -35
  30. package/dist/detail-canvas.js +0 -73
  31. package/dist/detail-runtime/direct-recovery.d.mts +0 -28
  32. package/dist/detail-runtime/direct-recovery.mjs +0 -142
  33. package/dist/detail-runtime/host.d.mts +0 -40
  34. package/dist/detail-runtime/host.mjs +0 -284
  35. package/dist/detail-runtime/scanner.d.mts +0 -12
  36. package/dist/detail-runtime/scanner.mjs +0 -70
  37. package/dist/detail.d.ts +0 -20
  38. package/dist/detail.js +0 -84
  39. package/dist/host.d.ts +0 -112
  40. package/dist/host.js +0 -184
  41. package/dist/host64.d.ts +0 -112
  42. package/dist/host64.js +0 -184
  43. package/dist/multiformat/coverage.d.ts +0 -7
  44. package/dist/multiformat/coverage.js +0 -26
  45. package/dist/multiformat/geometry.d.ts +0 -37
  46. package/dist/multiformat/geometry.js +0 -132
  47. package/dist/multiformat/linear-duplicates.d.ts +0 -16
  48. package/dist/multiformat/linear-duplicates.js +0 -163
  49. package/dist/multiformat/pixels.d.ts +0 -3
  50. package/dist/multiformat/pixels.js +0 -36
  51. package/dist/multiformat/scanner.d.ts +0 -60
  52. package/dist/multiformat/scanner.js +0 -382
  53. package/dist/multiformat-host.d.ts +0 -121
  54. package/dist/multiformat-host.js +0 -207
  55. package/dist/policy.d.ts +0 -12
  56. package/dist/policy.js +0 -12
  57. package/wasm/high-shared-retail-portable-20260919.wasm +0 -0
  58. package/wasm/low-shared-retail-portable-20260919.wasm +0 -0
  59. package/wasm/medium-shared-retail-portable-20260919.wasm +0 -0
  60. package/wasm/multiformat.json +0 -7
  61. package/wasm/multiformat.wasm +0 -0
  62. 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. Only engines needed by the selected formats are loaded.
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: EAN13/UPCA scanning in Medium/High/Very high also loads the Low recovery decoder. The demo and its
98
- comparison engines are not needed in your app.
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
- import { type ScanFrame, type Quad as HostQuad } from "./completion-host.mjs";
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
- regions?: FormatBarcode[];
44
- } & Partial<Omit<ScanFrame, "barcodes" | "unfinished">>;
31
+ candidates?: unknown[];
32
+ };
45
33
  localization?: {
46
34
  proposals: {
47
- polygon: HostQuad;
48
- score: number;
49
- text: string;
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
- /** Mode selects a compiled implementation. Create another instance to switch. */
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 { mergeLinearDuplicates } from "./multiformat/linear-duplicates.js";
2
- import { ReleaseDetailScanner, fitLimits } from "./detail.js";
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
- import { IndependentScanner, } from "./completion-host.mjs";
8
- export { ScannerError } from "./completion-host.mjs";
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 regionEvidence(raw) {
58
- const undecoded = [];
59
- if (raw.scan.regions) {
60
- for (const region of raw.scan.regions) {
61
- if (!raw.scan.barcodes.includes(region))
62
- undecoded.push({ format: region.format, polygon: region.polygon });
63
- }
64
- }
65
- else {
66
- const decoded = new Set(raw.scan.barcodes.flatMap((b) => ("candidate_indices" in b ? b.candidate_indices : [])));
67
- raw.localization?.proposals.forEach((p, i) => {
68
- if (!decoded.has(i))
69
- undecoded.push({ format: "Unknown", polygon: p.polygon });
70
- });
71
- for (const attempt of raw.recovery?.attempts ?? []) {
72
- const decoded = new Set(attempt.reads.flatMap((b) => b.candidate_indices));
73
- attempt.proposals.forEach((p, i) => {
74
- if (!decoded.has(i))
75
- undecoded.push({ format: "Unknown", polygon: p.polygon });
76
- });
77
- }
78
- }
79
- return {
80
- proposals: raw.localization?.proposals ?? null,
81
- searchWindows: raw.searchWindows ?? null,
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
- undecoded: regions.undecoded,
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: raw.elapsedMs,
123
- unfinished: raw.scan.unfinished || raw.localizationLimited || (raw.localization?.omitted ?? 0) > 0,
124
- ...(debug ? { debug: { ...raw, regions } } : {}),
98
+ elapsedMs,
99
+ unfinished: raw.unfinished,
100
+ undecoded,
101
+ ...(debug && raw.debug ? { debug: { ...raw.debug, regions } } : {}),
125
102
  });
126
103
  }
127
- const modes = {
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 = resolveFormats(), addOnPolicy = "Ignore") {
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 addonPolicy = options.eanAddOnPolicy === undefined ? "Ignore" : options.eanAddOnPolicy;
165
- if (!["Ignore", "Read", "Require"].includes(addonPolicy))
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 load = options.loadWasm ?? loadDefault;
179
- const sharedRetail = mode === "medium" && addonPolicy === "Ignore";
180
- const needsPrimary = formats.some((f) => f === "EAN13" || f === "UPCA" || (sharedRetail && (f === "EAN8" || f === "UPCE")));
181
- const bytes = needsPrimary ? await load(new URL(modes[mode], base)) : undefined;
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 (key !== "debug" && key !== "formats" && key !== "extendedBudget")
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
- if (this.host instanceof MediumMultiformatScanner) {
216
- const frame = this.host.scan(image, formats, {
217
- eanAddOnSymbol: this.eanAddOnPolicy,
218
- finishCandidates,
219
- });
220
- const barcodes = frame.barcodes;
221
- const primary = frame.primary;
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
- export declare const formatBits: {
3
- readonly EAN13: 1;
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
- 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
- ...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
+ }