tapirscan 1.2.0 → 1.2.1
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 +2 -7
- package/dist/detail-runtime/host.mjs +2 -0
- package/dist/detail.d.ts +3 -1
- package/dist/detail.js +1 -1
- package/dist/index.js +9 -7
- package/dist/multiformat/formats.js +1 -1
- package/dist/multiformat/scanner.d.ts +1 -1
- package/dist/multiformat/scanner.js +27 -6
- package/package.json +6 -6
- package/wasm/low-shared-retail-portable-20260919.wasm +0 -0
- package/wasm/medium-shared-retail-portable-20260919.wasm +0 -0
- package/wasm/multiformat.json +2 -2
- package/wasm/multiformat.wasm +0 -0
- package/wasm/low-complete-release-20260916.wasm +0 -0
- package/wasm/medium-complete-release-20260916.wasm +0 -0
- /package/wasm/{high-complete-release-20260916.wasm → high-shared-retail-portable-20260919.wasm} +0 -0
- /package/wasm/{very-high-complete-release-20260916.wasm → very-high-shared-retail-portable-20260919.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.
|
|
@@ -141,7 +136,7 @@ call and does not change the default formats. Previously returned results surviv
|
|
|
141
136
|
| Option | Where | Default | Meaning |
|
|
142
137
|
| ---------------- | ------------------- | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
143
138
|
| `mode` | Creation | `"medium"` | `"low"`, `"medium"`, `"high"`, `"very-high"`. |
|
|
144
|
-
| `formats` | Creation / scan | `
|
|
139
|
+
| `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
140
|
| `wasmBaseUrl` | Creation | Module-relative assets | Directory URL for packaged WASMs. Use this for normal browser hosting. |
|
|
146
141
|
| `loadWasm` | Creation | Module-relative loader | `(url: URL) => Promise<ArrayBuffer>`. Uses HTTP fetch in browsers and filesystem reads for Node file URLs. |
|
|
147
142
|
| `eanAddOnPolicy` | Creation / one-shot | `"Ignore"` | `"Ignore"`, `"Read"`, `"Require"`; optional EAN/UPC supplement policy. |
|
|
@@ -252,6 +252,8 @@ export class IndependentScanner {
|
|
|
252
252
|
coordinates[i * 8 + j * 2] = p[0];
|
|
253
253
|
coordinates[i * 8 + j * 2 + 1] = p[1];
|
|
254
254
|
}));
|
|
255
|
+
if (e.regions_retail)
|
|
256
|
+
status(e.regions_retail(id, policy.retailMask ?? 1, policy.retailMask ? 3 : 0));
|
|
255
257
|
const mask = policy.retryMask ?? [4294967295, 4294967295];
|
|
256
258
|
if (!Array.isArray(mask) || mask.length !== 2)
|
|
257
259
|
throw new ScannerError("invalid_input", "Invalid retry mask");
|
package/dist/detail.d.ts
CHANGED
|
@@ -13,6 +13,8 @@ export declare class ReleaseDetailScanner {
|
|
|
13
13
|
private disposed;
|
|
14
14
|
private constructor();
|
|
15
15
|
static create(primary: ArrayBuffer, low: ArrayBuffer, mode: Exclude<Mode, "low">): Promise<ReleaseDetailScanner>;
|
|
16
|
-
scanLocalized(image: Image, policy: Parameters<IndependentScanner["scanLocalized"]>[1]
|
|
16
|
+
scanLocalized(image: Image, policy: Parameters<IndependentScanner["scanLocalized"]>[1] & {
|
|
17
|
+
retailMask?: number;
|
|
18
|
+
}, fit: number, full: boolean, coverage?: readonly Quad[]): DetailResult;
|
|
17
19
|
dispose(): void;
|
|
18
20
|
}
|
package/dist/detail.js
CHANGED
|
@@ -52,7 +52,7 @@ export class ReleaseDetailScanner {
|
|
|
52
52
|
scanLocalized(image, policy, fit, full, coverage = []) {
|
|
53
53
|
if (this.disposed)
|
|
54
54
|
throw Error("Scanner is disposed");
|
|
55
|
-
const result = this.scanner.scanLocalized(packed(image), policy
|
|
55
|
+
const result = this.scanner.scanLocalized(packed(image), policy, fit, full, coverage);
|
|
56
56
|
const primaryCount = result.scan.barcodes.length;
|
|
57
57
|
const barcodes = result.recovery.barcodes.map((b, i) => i < primaryCount ? b : { ...b, candidate_indices: [] });
|
|
58
58
|
const localization = result.localization;
|
package/dist/index.js
CHANGED
|
@@ -125,10 +125,10 @@ function publicResult(raw, image, debug) {
|
|
|
125
125
|
});
|
|
126
126
|
}
|
|
127
127
|
const modes = {
|
|
128
|
-
low: "low-
|
|
129
|
-
medium: "medium-
|
|
130
|
-
high: "high-
|
|
131
|
-
"very-high": "very-high-
|
|
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
132
|
};
|
|
133
133
|
/** Mode selects a compiled implementation. Create another instance to switch. */
|
|
134
134
|
export class Scanner {
|
|
@@ -136,7 +136,7 @@ export class Scanner {
|
|
|
136
136
|
mode;
|
|
137
137
|
configuredFormats;
|
|
138
138
|
addOnPolicy;
|
|
139
|
-
constructor(host, mode, configuredFormats =
|
|
139
|
+
constructor(host, mode, configuredFormats = resolveFormats(), addOnPolicy = "Ignore") {
|
|
140
140
|
this.host = host;
|
|
141
141
|
this.mode = mode;
|
|
142
142
|
this.configuredFormats = configuredFormats;
|
|
@@ -176,11 +176,13 @@ export class Scanner {
|
|
|
176
176
|
if (!base.pathname.endsWith("/"))
|
|
177
177
|
base.pathname += "/";
|
|
178
178
|
const load = options.loadWasm ?? loadDefault;
|
|
179
|
-
const
|
|
179
|
+
const sharedRetail = mode === "medium" && addonPolicy === "Ignore";
|
|
180
|
+
const needsPrimary = formats.some((f) => f === "EAN13" || f === "UPCA" || (sharedRetail && (f === "EAN8" || f === "UPCE")));
|
|
180
181
|
const bytes = needsPrimary ? await load(new URL(modes[mode], base)) : undefined;
|
|
181
182
|
const recovery = !needsPrimary || mode === "low" ? undefined : await load(new URL(modes.low, base));
|
|
182
183
|
const multiformat = addonPolicy !== "Ignore" || formats.length !== 1 || formats[0] !== "EAN13";
|
|
183
|
-
const extra = addonPolicy !== "Ignore" ||
|
|
184
|
+
const extra = addonPolicy !== "Ignore" ||
|
|
185
|
+
formats.some((f) => f !== "EAN13" && f !== "UPCA" && (!sharedRetail || (f !== "EAN8" && f !== "UPCE")))
|
|
184
186
|
? await load(new URL("multiformat.wasm", base))
|
|
185
187
|
: undefined;
|
|
186
188
|
if (multiformat)
|
|
@@ -53,7 +53,7 @@ export function resolveFormats(input) {
|
|
|
53
53
|
if (input === "all")
|
|
54
54
|
return [...linearFormats, ...matrixFormats];
|
|
55
55
|
if (input === undefined)
|
|
56
|
-
return [
|
|
56
|
+
return [...retailFormats];
|
|
57
57
|
if (typeof input === "string" && Object.hasOwn(formatBits, input))
|
|
58
58
|
return [input];
|
|
59
59
|
if (!Array.isArray(input) || input.length === 0)
|
|
@@ -36,7 +36,7 @@ export interface Frame {
|
|
|
36
36
|
localizationMs: number;
|
|
37
37
|
linearStrategy: "scanlines" | "medium-localized";
|
|
38
38
|
}
|
|
39
|
-
/**
|
|
39
|
+
/** Shared retail evidence over primary profiles, plus independent additional readers. */
|
|
40
40
|
export declare class MediumMultiformatScanner {
|
|
41
41
|
private medium?;
|
|
42
42
|
private mode;
|
|
@@ -10,7 +10,7 @@ function scanGray(image) {
|
|
|
10
10
|
? image.data.subarray(0, image.width * image.height)
|
|
11
11
|
: toGray(image);
|
|
12
12
|
}
|
|
13
|
-
/**
|
|
13
|
+
/** Shared retail evidence over primary profiles, plus independent additional readers. */
|
|
14
14
|
export class MediumMultiformatScanner {
|
|
15
15
|
medium;
|
|
16
16
|
mode = "medium";
|
|
@@ -104,7 +104,12 @@ export class MediumMultiformatScanner {
|
|
|
104
104
|
const barcodes = [], regions = [];
|
|
105
105
|
let primary;
|
|
106
106
|
let unfinished = false, mediumMs = 0, additionalMs = 0, preparationMs = 0, localizationMs = 0;
|
|
107
|
-
const
|
|
107
|
+
const sharedRetail = this.mode === "medium" &&
|
|
108
|
+
eanAddOnSymbol === "Ignore" &&
|
|
109
|
+
formats.some((f) => f === "EAN8" || f === "UPCE");
|
|
110
|
+
const retailPolicy = { ...scanPolicy, ...(sharedRetail ? { retailMask: 15 } : {}) };
|
|
111
|
+
const extraFormats = formats.filter((f) => (!sharedRetail || (f !== "EAN8" && f !== "UPCE")) &&
|
|
112
|
+
(eanAddOnSymbol !== "Ignore" || (f !== "EAN13" && f !== "UPCA")));
|
|
108
113
|
const conservative = medium instanceof ReleaseDetailScanner &&
|
|
109
114
|
!localized &&
|
|
110
115
|
eanAddOnSymbol === "Ignore" &&
|
|
@@ -131,12 +136,12 @@ export class MediumMultiformatScanner {
|
|
|
131
136
|
coverage.push(read.polygon);
|
|
132
137
|
}
|
|
133
138
|
}
|
|
134
|
-
if (formats.includes("EAN13") || formats.includes("UPCA")) {
|
|
139
|
+
if (sharedRetail || formats.includes("EAN13") || formats.includes("UPCA")) {
|
|
135
140
|
if (!medium)
|
|
136
141
|
throw Error("EAN13 engine has not been initialized.");
|
|
137
142
|
const begin = performance.now();
|
|
138
143
|
const found = medium instanceof ReleaseDetailScanner
|
|
139
|
-
? medium.scanLocalized(image,
|
|
144
|
+
? medium.scanLocalized(image, retailPolicy, fitLimits[this.mode], true, coverage)
|
|
140
145
|
: medium.scanLocalized(image, scanPolicy, fitLimits[this.mode], true);
|
|
141
146
|
primary = found;
|
|
142
147
|
const localization = found.localization;
|
|
@@ -153,6 +158,22 @@ export class MediumMultiformatScanner {
|
|
|
153
158
|
else if (formats.includes("EAN13"))
|
|
154
159
|
barcodes.push({ format: "EAN13", text: b.text, polygon: b.polygon, support: b.support });
|
|
155
160
|
}
|
|
161
|
+
if (sharedRetail) {
|
|
162
|
+
const evidence = found;
|
|
163
|
+
const shared = [...(evidence.scan.retail?.barcodes ?? [])];
|
|
164
|
+
unfinished ||= evidence.scan.retail?.unfinished ?? false;
|
|
165
|
+
for (const attempt of evidence.recovery?.attempts ?? []) {
|
|
166
|
+
unfinished ||= attempt.frame.retail?.unfinished ?? false;
|
|
167
|
+
for (const b of attempt.frame.retail?.barcodes ?? []) {
|
|
168
|
+
const polygon = b.polygon.map(([x, y]) => [
|
|
169
|
+
attempt.x + x / attempt.factor,
|
|
170
|
+
attempt.y + y / attempt.factor,
|
|
171
|
+
]);
|
|
172
|
+
shared.push({ ...b, polygon });
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
barcodes.push(...shared.filter((b) => formats.includes(b.format)));
|
|
176
|
+
}
|
|
156
177
|
const decoded = new Set(found.scan.barcodes.flatMap((b) => b.candidate_indices));
|
|
157
178
|
localization.proposals.forEach((p, i) => {
|
|
158
179
|
if (!decoded.has(i))
|
|
@@ -168,13 +189,13 @@ export class MediumMultiformatScanner {
|
|
|
168
189
|
});
|
|
169
190
|
}
|
|
170
191
|
}
|
|
171
|
-
unfinished
|
|
192
|
+
unfinished ||=
|
|
172
193
|
found.scan.unfinished ||
|
|
173
194
|
Boolean(localization.workLimited) ||
|
|
174
195
|
(localization.omitted ?? 0) > 0;
|
|
175
196
|
mediumMs = performance.now() - begin;
|
|
176
197
|
}
|
|
177
|
-
if (localized && !formats.includes("EAN13") && !formats.includes("UPCA")) {
|
|
198
|
+
if (!sharedRetail && localized && !formats.includes("EAN13") && !formats.includes("UPCA")) {
|
|
178
199
|
if (!medium)
|
|
179
200
|
throw Error("EAN13 localization engine has not been initialized.");
|
|
180
201
|
const begin = performance.now();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tapirscan",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Orientation-aware barcode scanning with a Rust/WASM core and four effort modes",
|
|
6
6
|
"exports": {
|
|
@@ -14,15 +14,15 @@
|
|
|
14
14
|
"examples",
|
|
15
15
|
"wasm/multiformat.json",
|
|
16
16
|
"THIRD_PARTY_NOTICES.md",
|
|
17
|
-
"wasm/low-
|
|
18
|
-
"wasm/medium-
|
|
19
|
-
"wasm/high-
|
|
20
|
-
"wasm/very-high-
|
|
17
|
+
"wasm/low-shared-retail-portable-20260919.wasm",
|
|
18
|
+
"wasm/medium-shared-retail-portable-20260919.wasm",
|
|
19
|
+
"wasm/high-shared-retail-portable-20260919.wasm",
|
|
20
|
+
"wasm/very-high-shared-retail-portable-20260919.wasm",
|
|
21
21
|
"wasm/multiformat.wasm"
|
|
22
22
|
],
|
|
23
23
|
"scripts": {
|
|
24
24
|
"build": "tsc -p tsconfig.json",
|
|
25
|
-
"test": "node --test test/scanner.mjs test/linear-duplicates.mjs && tsc --noEmit --strict --skipLibCheck --target ES2022 --module NodeNext test/api-types.ts",
|
|
25
|
+
"test": "node --test test/scanner.mjs test/linear-duplicates.mjs test/shared-retail.mjs && tsc --noEmit --strict --skipLibCheck --target ES2022 --module NodeNext test/api-types.ts",
|
|
26
26
|
"prepack": "npm run build && node scripts/verify-package.mjs"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
Binary file
|
|
Binary file
|
package/wasm/multiformat.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
|
-
"sha256": "
|
|
2
|
+
"sha256": "fb020f1c9ca597c1c4dd8ae8d45a70eae78e425eb08ca9225f85d8efc8652b29",
|
|
3
3
|
"sourceManifest": "provenance/import.json",
|
|
4
|
-
"sourceDigest": "
|
|
4
|
+
"sourceDigest": "e9b4ee4dacf9df2477b86dff44e2c74768c78f21d0e743158b09fff7386f6898",
|
|
5
5
|
"rustToolchain": "1.91.1",
|
|
6
6
|
"rustflags": "-C target-feature=+simd128 --remap-path-prefix=/home/runner/.rustup/toolchains/1.91.1-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library=/rustc/ed61e7d7e242494fb7057f2657300d9e77bb4fcb/library"
|
|
7
7
|
}
|
package/wasm/multiformat.wasm
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
/package/wasm/{high-complete-release-20260916.wasm → high-shared-retail-portable-20260919.wasm}
RENAMED
|
File without changes
|
|
File without changes
|