@rosepetal/barcode-engine-client 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +209 -0
- package/lib/engine.js +684 -0
- package/lib/format.js +22 -0
- package/lib/framing.js +174 -0
- package/lib/index.js +18 -0
- package/package.json +32 -0
- package/test/fake-engine.js +292 -0
- package/test/fixtures/analyze-pass.json +1 -0
- package/test/fixtures/verify-fail.json +1 -0
- package/test/fixtures/verify-not-detected.json +1 -0
- package/test/fixtures/verify-pass.json +1 -0
package/lib/format.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* The `format` name of a schema-1.0 symbol, as node-red-contrib-barcode-reader has always printed it
|
|
4
|
+
* (compat/barcode-reader.md §3 of rosepetal-barcode-sdk): ZXing's spelling for the symbologies; the add-ons, which
|
|
5
|
+
* ZXing does not report on their own, take ZBar's (EAN-2 / EAN-5). Shared here so that the reader and the verifier
|
|
6
|
+
* print the same `format` for the same symbol.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// symbology (schema 1.0 identifier) → the name the nodes print
|
|
10
|
+
const FORMAT_NAMES = {
|
|
11
|
+
EAN13: 'EAN-13', EAN8: 'EAN-8', UPCA: 'UPC-A', UPCE: 'UPC-E',
|
|
12
|
+
Code128: 'Code128', Code39: 'Code39', Code93: 'Code93', Codabar: 'Codabar', ITF: 'ITF'
|
|
13
|
+
};
|
|
14
|
+
// The identifier is read before the symbology: an add-on carries its main's symbology
|
|
15
|
+
const ADDON_NAMES = { ']E1': 'EAN-2', ']E2': 'EAN-5' };
|
|
16
|
+
|
|
17
|
+
/** The `format` the nodes print for a schema-1.0 symbol; an unknown symbology keeps the SDK's name. */
|
|
18
|
+
function formatName(symbol) {
|
|
19
|
+
return ADDON_NAMES[symbol.identifier] || FORMAT_NAMES[symbol.symbology] || String(symbol.symbology);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
module.exports = { FORMAT_NAMES, ADDON_NAMES, formatName };
|
package/lib/framing.js
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* Frame codec of the `rp-barcode serve` protocol, version 1 (docs/service.md §3.1 of rosepetal-barcode-sdk, the
|
|
4
|
+
* repository this package lives in; the plan: docs/plans/fase2/00-overview.md §3.1). Shared by every client of
|
|
5
|
+
* the engine (@rosepetal/barcode-engine-client) and by the fake engine of test/fake-engine.js:
|
|
6
|
+
*
|
|
7
|
+
* u32 big-endian headerLen | u32 big-endian payloadLen | header (headerLen bytes, UTF-8 JSON object) | payload
|
|
8
|
+
*
|
|
9
|
+
* headerLen is >= 2 and <= maxHeader (1 MiB by default), payloadLen <= maxPayload (256 MiB by default) and the
|
|
10
|
+
* header must be a JSON object. Anything else is a protocol violation: the parser emits one FramingError and
|
|
11
|
+
* ignores every later chunk, because the byte stream can no longer be delimited (the caller restarts the process).
|
|
12
|
+
*
|
|
13
|
+
* The parser copies every byte exactly once, into a buffer sized from the prefix, so a payload never lives twice
|
|
14
|
+
* in memory and the emitted Buffer is independent of the chunks pushed in.
|
|
15
|
+
*/
|
|
16
|
+
const { EventEmitter } = require('node:events');
|
|
17
|
+
|
|
18
|
+
const PREFIX_BYTES = 8;
|
|
19
|
+
const MIN_HEADER = 2; // "{}"
|
|
20
|
+
const MAX_U32 = 0xffffffff;
|
|
21
|
+
const DEFAULT_MAX_HEADER = 1 << 20; // 1 MiB
|
|
22
|
+
const DEFAULT_MAX_PAYLOAD = 256 << 20; // 256 MiB
|
|
23
|
+
const EMPTY = Buffer.alloc(0);
|
|
24
|
+
|
|
25
|
+
class FramingError extends Error {
|
|
26
|
+
/**
|
|
27
|
+
* @param {'bad_frame'|'bad_header'|'bad_payload'} code
|
|
28
|
+
* @param {string} message
|
|
29
|
+
*/
|
|
30
|
+
constructor(code, message) {
|
|
31
|
+
super(message);
|
|
32
|
+
this.name = 'FramingError';
|
|
33
|
+
this.code = code;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function isJsonObject(value) {
|
|
38
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Encodes one frame as [head, payload]: head is the 8-byte prefix followed by the UTF-8 JSON header and payload
|
|
43
|
+
* is the caller's buffer untouched, so the two go out in two writes and the pixels are never copied.
|
|
44
|
+
*
|
|
45
|
+
* @param {object} header a JSON object (control characters such as GS 0x1D survive as \u001d escapes)
|
|
46
|
+
* @param {Buffer|Uint8Array} [payload] optional (payloadLen 0)
|
|
47
|
+
* @returns {[Buffer, Buffer|Uint8Array]}
|
|
48
|
+
*/
|
|
49
|
+
function encodeFrame(header, payload = EMPTY) {
|
|
50
|
+
if (!isJsonObject(header)) {
|
|
51
|
+
throw new FramingError('bad_header', 'header must be a JSON object');
|
|
52
|
+
}
|
|
53
|
+
if (!(payload instanceof Uint8Array)) { // a Buffer is a Uint8Array
|
|
54
|
+
throw new FramingError('bad_payload', 'payload must be a Buffer or Uint8Array');
|
|
55
|
+
}
|
|
56
|
+
if (payload.length > MAX_U32) {
|
|
57
|
+
throw new FramingError('bad_payload', `payload length ${payload.length} does not fit in u32`);
|
|
58
|
+
}
|
|
59
|
+
const json = Buffer.from(JSON.stringify(header), 'utf8');
|
|
60
|
+
if (json.length > MAX_U32) {
|
|
61
|
+
throw new FramingError('bad_header', `header length ${json.length} does not fit in u32`);
|
|
62
|
+
}
|
|
63
|
+
const head = Buffer.allocUnsafe(PREFIX_BYTES + json.length);
|
|
64
|
+
head.writeUInt32BE(json.length, 0);
|
|
65
|
+
head.writeUInt32BE(payload.length, 4);
|
|
66
|
+
json.copy(head, PREFIX_BYTES);
|
|
67
|
+
return [head, payload];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Incremental parser over arbitrary chunks (any split, one byte at a time or several frames at once).
|
|
72
|
+
*
|
|
73
|
+
* Events:
|
|
74
|
+
* 'frame' (header: object, payload: Buffer) payload is an empty Buffer when payloadLen is 0
|
|
75
|
+
* 'error' (FramingError) emitted once; afterwards push() is a no-op and `failed` holds the error
|
|
76
|
+
*/
|
|
77
|
+
class FrameParser extends EventEmitter {
|
|
78
|
+
constructor({ maxHeader = DEFAULT_MAX_HEADER, maxPayload = DEFAULT_MAX_PAYLOAD } = {}) {
|
|
79
|
+
super();
|
|
80
|
+
this.maxHeader = maxHeader;
|
|
81
|
+
this.maxPayload = maxPayload;
|
|
82
|
+
this.failed = null;
|
|
83
|
+
this.headerLen = 0;
|
|
84
|
+
this.payloadLen = 0;
|
|
85
|
+
this.header = null;
|
|
86
|
+
this.prefix = Buffer.alloc(PREFIX_BYTES);
|
|
87
|
+
this._expect('prefix', this.prefix);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** @param {Buffer|Uint8Array} chunk */
|
|
91
|
+
push(chunk) {
|
|
92
|
+
if (this.failed) return;
|
|
93
|
+
if (!(chunk instanceof Uint8Array)) {
|
|
94
|
+
throw new TypeError('FrameParser.push expects a Buffer or Uint8Array');
|
|
95
|
+
}
|
|
96
|
+
let offset = 0;
|
|
97
|
+
while (offset < chunk.length) {
|
|
98
|
+
const n = Math.min(this.target.length - this.filled, chunk.length - offset);
|
|
99
|
+
this.target.set(chunk.subarray(offset, offset + n), this.filled);
|
|
100
|
+
this.filled += n;
|
|
101
|
+
offset += n;
|
|
102
|
+
if (this.filled === this.target.length && !this._complete()) return;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
_expect(state, target) {
|
|
107
|
+
this.state = state;
|
|
108
|
+
this.target = target;
|
|
109
|
+
this.filled = 0;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** The current target is full: validate it and move to the next state. Returns false once the parser is dead. */
|
|
113
|
+
_complete() {
|
|
114
|
+
switch (this.state) {
|
|
115
|
+
case 'prefix': {
|
|
116
|
+
this.headerLen = this.prefix.readUInt32BE(0);
|
|
117
|
+
this.payloadLen = this.prefix.readUInt32BE(4);
|
|
118
|
+
if (this.headerLen < MIN_HEADER || this.headerLen > this.maxHeader) {
|
|
119
|
+
return this._fail('bad_frame', `header length ${this.headerLen} outside ${MIN_HEADER}..${this.maxHeader}`);
|
|
120
|
+
}
|
|
121
|
+
if (this.payloadLen > this.maxPayload) {
|
|
122
|
+
return this._fail('bad_frame', `payload length ${this.payloadLen} exceeds ${this.maxPayload}`);
|
|
123
|
+
}
|
|
124
|
+
this._expect('header', Buffer.allocUnsafe(this.headerLen));
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
case 'header': {
|
|
128
|
+
let header;
|
|
129
|
+
try {
|
|
130
|
+
header = JSON.parse(this.target.toString('utf8'));
|
|
131
|
+
} catch (err) {
|
|
132
|
+
return this._fail('bad_header', `header is not JSON: ${err.message}`);
|
|
133
|
+
}
|
|
134
|
+
if (!isJsonObject(header)) {
|
|
135
|
+
return this._fail('bad_header', 'header is not a JSON object');
|
|
136
|
+
}
|
|
137
|
+
this.header = header;
|
|
138
|
+
if (this.payloadLen === 0) return this._emitFrame(EMPTY);
|
|
139
|
+
this._expect('payload', Buffer.allocUnsafe(this.payloadLen));
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
case 'payload':
|
|
143
|
+
return this._emitFrame(this.target);
|
|
144
|
+
default:
|
|
145
|
+
return this._fail('bad_frame', `unexpected parser state ${this.state}`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
_emitFrame(payload) {
|
|
150
|
+
const header = this.header;
|
|
151
|
+
this.header = null;
|
|
152
|
+
this._expect('prefix', this.prefix); // consistent state before listeners run
|
|
153
|
+
this.emit('frame', header, payload);
|
|
154
|
+
return !this.failed;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
_fail(code, message) {
|
|
158
|
+
this.failed = new FramingError(code, message);
|
|
159
|
+
this.header = null;
|
|
160
|
+
this._expect('dead', EMPTY);
|
|
161
|
+
this.emit('error', this.failed);
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
module.exports = {
|
|
167
|
+
encodeFrame,
|
|
168
|
+
FrameParser,
|
|
169
|
+
FramingError,
|
|
170
|
+
PREFIX_BYTES,
|
|
171
|
+
MIN_HEADER,
|
|
172
|
+
DEFAULT_MAX_HEADER,
|
|
173
|
+
DEFAULT_MAX_PAYLOAD
|
|
174
|
+
};
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* @rosepetal/barcode-engine-client: the Node client of `rp-barcode serve`, the persistent decode service of
|
|
4
|
+
* rosepetal-barcode-sdk. One module, one shared engine: every package that requires this one and calls acquire()
|
|
5
|
+
* shares the same rp-barcode process of the Node-RED runtime (node-red-contrib-barcode-reader and
|
|
6
|
+
* node-red-contrib-barcode-verifier). See README.md.
|
|
7
|
+
*/
|
|
8
|
+
const engine = require('./engine');
|
|
9
|
+
const framing = require('./framing');
|
|
10
|
+
const format = require('./format');
|
|
11
|
+
|
|
12
|
+
// The fake `rp-barcode serve` of this package's tests (protocol 1, one fixed EAN-13 per decode, the canned
|
|
13
|
+
// VerifyResults of test/fixtures/*.json for verify/analyze, knobs through FAKE_* environment variables), shipped so
|
|
14
|
+
// that the tests of the nodes can point RP_BARCODE_ENGINE, or an Engine's `command`/`args`, at it instead of a
|
|
15
|
+
// real binary
|
|
16
|
+
const FAKE_ENGINE = require.resolve('../test/fake-engine.js');
|
|
17
|
+
|
|
18
|
+
module.exports = { ...engine, ...framing, ...format, FAKE_ENGINE };
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rosepetal/barcode-engine-client",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Node client of rp-barcode serve, the persistent decode and verify service of rosepetal-barcode-sdk: one shared engine process per Node-RED runtime, framed requests over stdio, timeouts, bounded queue, restart with backoff",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/rosepetal-ai/rosepetal-barcode-sdk.git",
|
|
9
|
+
"directory": "clients/node"
|
|
10
|
+
},
|
|
11
|
+
"main": "lib/index.js",
|
|
12
|
+
"files": [
|
|
13
|
+
"lib/",
|
|
14
|
+
"test/fake-engine.js",
|
|
15
|
+
"test/fixtures/",
|
|
16
|
+
"README.md",
|
|
17
|
+
"LICENSE"
|
|
18
|
+
],
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=18"
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"test": "node --test test/*.test.js"
|
|
24
|
+
},
|
|
25
|
+
"optionalDependencies": {
|
|
26
|
+
"@rosepetal/barcode-engine-linux-x64": "0.2.0",
|
|
27
|
+
"@rosepetal/barcode-engine-linux-arm64": "0.2.0"
|
|
28
|
+
},
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
/**
|
|
4
|
+
* Fake `rp-barcode serve` for the tests: protocol 1 over stdio (rosepetal-barcode-sdk docs/service.md; the plan:
|
|
5
|
+
* docs/plans/fase2/00-overview.md §3, 04-subfase-2D.md §3.5), one fixed EAN-13 per decode, canned VerifyResults
|
|
6
|
+
* for verify/analyze (test/fixtures/*.json: what the CLI printed for the synthetic renders of
|
|
7
|
+
* test/tools/make-verify-fixtures.sh), knobs through environment variables (tasks 2B-T2 and 2D-T2). Executable with
|
|
8
|
+
* a shebang so it also works as RP_BARCODE_ENGINE (the client spawns `<path> serve`). Never matched by `npm test`
|
|
9
|
+
* (`test/*.test.js`).
|
|
10
|
+
*
|
|
11
|
+
* Knobs (environment of the child):
|
|
12
|
+
* FAKE_DELAY_MS=n every decode/verify/analyze answers after n ms
|
|
13
|
+
* FAKE_CRASH_AFTER=n exits with status 7 on receiving decode/verify/analyze n+1, without answering it
|
|
14
|
+
* FAKE_PROTOCOL=n the `protocol` number of the hello (default 1)
|
|
15
|
+
* FAKE_GARBAGE=1 writes text outside any frame right after the hello
|
|
16
|
+
* FAKE_OVERLOAD=1 answers `overloaded` to every decode/verify/analyze
|
|
17
|
+
* FAKE_NO_HELLO=1 never greets, never answers, ignores SIGTERM (the client has to SIGKILL)
|
|
18
|
+
* FAKE_IGNORE_STOP=1 works but ignores `shutdown` and SIGTERM
|
|
19
|
+
* FAKE_MAX_PAYLOAD=n the `limits.maxPayload` advertised in the hello (default 256 MiB, like `--max-payload`)
|
|
20
|
+
* FAKE_REPLY_PAYLOAD=1 attaches a payload to every decode/verify/analyze reply (a protocol violation seen from
|
|
21
|
+
* the client)
|
|
22
|
+
* FAKE_STALL=1 greets, then never reads stdin (the client's writes pile up); with FAKE_IGNORE_STOP the
|
|
23
|
+
* client has to SIGKILL it
|
|
24
|
+
* FAKE_VERIFY_STATUS=s the canned result of verify/analyze: pass (default), fail or not_detected
|
|
25
|
+
* FAKE_DECODE_SYMBOLS=n copies of the fixed EAN-13 a decode answers (default 1; 0 = no symbol)
|
|
26
|
+
* FAKE_OPS=a,b,c the ops the hello announces (default decode,ping,shutdown,verify,analyze); an op left
|
|
27
|
+
* out is answered `unsupported`, as by a 2A engine (an SDK 0.2.0 built before 2D)
|
|
28
|
+
*
|
|
29
|
+
* Like the real server it takes `image.encoding` `raw` (payload.length === width·height·channels) or `encoded` (a
|
|
30
|
+
* non-empty PNG, JPEG, BMP or TIFF file, told by its magic bytes; the size answered is the PNG's IHDR, else the
|
|
31
|
+
* header's width/height, where the real server reports the decoded size; with no size at all, 0×0, the bounds
|
|
32
|
+
* check is skipped, where the real server checks against the decoded size), rejects unknown keys inside `image`,
|
|
33
|
+
* `region`, `options` (`spec` included) and `acquisition` (`calibration` included) with `invalid_input` and the
|
|
34
|
+
* server's own wording (`json: unknown field "x"`; `plain.x` for a region value that is not an integer, as its
|
|
35
|
+
* encoding/json names the field), a region under 2 px, an orientation that is not a multiple of 90, a region
|
|
36
|
+
* outside the image (`barcode: region {…} outside image WxH`), and a `deadlineMs` that is not an integer.
|
|
37
|
+
* verify/analyze answer the canned result of FAKE_VERIFY_STATUS with the request's region echoed in
|
|
38
|
+
* `result.symbol.region` (orientation normalised to 0/90/180/270); `analyze` answers analyze-pass.json under
|
|
39
|
+
* `pass` and the verify fixture plus `scans: []` otherwise (the real Analyze returns the scans it measured even
|
|
40
|
+
* when nothing decodes).
|
|
41
|
+
*
|
|
42
|
+
* Known simplifications: `shutdown` and EOF on stdin exit at once, without answering the requests still delayed
|
|
43
|
+
* by FAKE_DELAY_MS (the real server finishes what is in flight first, §3.1/§3.2); keys are matched
|
|
44
|
+
* case-sensitively (`TryInvert` is an unknown key here, the real server's encoding/json takes it for `tryInvert`);
|
|
45
|
+
* the raw length message reads `imgio: data length N does not match W×H×C` (the server's: `imgio: GRAY WxH needs
|
|
46
|
+
* N bytes, got M`); option values are not checked against their vocabularies (`edition: "2020"` or a GS1 table
|
|
47
|
+
* `42` pass here and are `invalid_input` on the server); `options`, `spec`, `acquisition` and `calibration` that
|
|
48
|
+
* are not objects pass here (the server: `cannot unmarshal string into Go value of type barcode.VerifyOptions`).
|
|
49
|
+
* Logs go to stderr, one line per event.
|
|
50
|
+
*/
|
|
51
|
+
const fs = require('node:fs');
|
|
52
|
+
const path = require('node:path');
|
|
53
|
+
const { FrameParser, encodeFrame } = require('../lib/framing');
|
|
54
|
+
|
|
55
|
+
const env = process.env;
|
|
56
|
+
const delayMs = Number(env.FAKE_DELAY_MS || 0);
|
|
57
|
+
const crashAfter = env.FAKE_CRASH_AFTER === undefined ? -1 : Number(env.FAKE_CRASH_AFTER);
|
|
58
|
+
const protocol = env.FAKE_PROTOCOL === undefined ? 1 : Number(env.FAKE_PROTOCOL);
|
|
59
|
+
const garbage = env.FAKE_GARBAGE === '1';
|
|
60
|
+
const overload = env.FAKE_OVERLOAD === '1';
|
|
61
|
+
const noHello = env.FAKE_NO_HELLO === '1';
|
|
62
|
+
const ignoreStop = env.FAKE_IGNORE_STOP === '1';
|
|
63
|
+
const maxPayload = env.FAKE_MAX_PAYLOAD === undefined ? 268435456 : Number(env.FAKE_MAX_PAYLOAD);
|
|
64
|
+
const replyPayload = env.FAKE_REPLY_PAYLOAD === '1';
|
|
65
|
+
const stall = env.FAKE_STALL === '1';
|
|
66
|
+
const verifyStatus = env.FAKE_VERIFY_STATUS || 'pass';
|
|
67
|
+
const decodeSymbols = env.FAKE_DECODE_SYMBOLS === undefined ? 1 : Number(env.FAKE_DECODE_SYMBOLS);
|
|
68
|
+
const ops = env.FAKE_OPS ? env.FAKE_OPS.split(',') : ['decode', 'ping', 'shutdown', 'verify', 'analyze'];
|
|
69
|
+
|
|
70
|
+
// The key sets the server accepts (DisallowUnknownFields on the SDK's structs): anything else is invalid_input.
|
|
71
|
+
// image: docs/service.md §3.2; decodeOptions: docs/schema/result-1.0.md §12.1; verifyOptions §12.2 (its spec
|
|
72
|
+
// {kind, table}); acquisition §12.3 and its calibration §12.4; region: docs/service.md §3.5
|
|
73
|
+
const KNOWN_IMAGE = new Set(['width', 'height', 'channels', 'colorSpace', 'encoding']);
|
|
74
|
+
const KNOWN_DECODE_OPTIONS = new Set(['symbologies', 'region', 'scanStep', 'horizontal', 'vertical', 'quietZone', 'optionalChecksum',
|
|
75
|
+
'minLines', 'tryInvert', 'maxSymbols', 'addOn', 'effort', 'upcaAsEan13', 'code39FullAscii']);
|
|
76
|
+
const KNOWN_VERIFY_OPTIONS = new Set(['symbology', 'edition', 'scanCount', 'topMargin', 'bottomMargin', 'spec', 'failUnder', 'warnUnder',
|
|
77
|
+
'readOnly', 'heightErrors', 'xDimErrors', 'structureErrors', 'enforceAssociations', 'quietZoneRule', 'parameterSummary', 'apertureMils',
|
|
78
|
+
'noDenseScans']);
|
|
79
|
+
const KNOWN_SPEC = new Set(['kind', 'table']);
|
|
80
|
+
const KNOWN_ACQUISITION = new Set(['milsPerPixel', 'wavelengthNm', 'calibration']);
|
|
81
|
+
const KNOWN_CALIBRATION = new Set(['mode', 'black', 'white', 'cardRmax', 'cardContrast']);
|
|
82
|
+
const KNOWN_REGION = new Set(['x', 'y', 'w', 'h', 'orientation']);
|
|
83
|
+
|
|
84
|
+
// The canned results: what rp-barcode verify/analyze print for the synthetic renders of test/tools/make-verify-fixtures.sh
|
|
85
|
+
// (verify-pass, verify-fail, verify-not-detected, analyze-pass), read afresh per request so a reply never shares state
|
|
86
|
+
const FIXTURES = path.join(__dirname, 'fixtures');
|
|
87
|
+
const canned = (name) => JSON.parse(fs.readFileSync(path.join(FIXTURES, `${name}.json`), 'utf8'));
|
|
88
|
+
|
|
89
|
+
// The symbol rp-barcode decode reads from test/fixtures/ean13.png of the reader (347×68, X = 3 px, bars 60 px high);
|
|
90
|
+
// region is what `rp-barcode decode --robust` reports for it
|
|
91
|
+
const SYMBOL = {
|
|
92
|
+
symbology: 'EAN13', identifier: ']E0', value: '4006381333931', bytes: 'NDAwNjM4MTMzMzkzMQ==', gs1: true,
|
|
93
|
+
corners: [{ x: 37, y: 4 }, { x: 322, y: 4 }, { x: 322, y: 64 }, { x: 37, y: 64 }],
|
|
94
|
+
orientation: 0, confidence: 1, lines: 40, checksum: 'valid', region: { x: 1, y: 0, w: 346, h: 68, orientation: 0 }
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
function log(line) {
|
|
98
|
+
process.stderr.write(`fake-engine: ${line}\n`);
|
|
99
|
+
}
|
|
100
|
+
function write(header, payload, onDone) {
|
|
101
|
+
const [head, body] = encodeFrame(header, payload);
|
|
102
|
+
if (body.length === 0) return process.stdout.write(head, onDone);
|
|
103
|
+
process.stdout.write(head);
|
|
104
|
+
process.stdout.write(body, onDone);
|
|
105
|
+
}
|
|
106
|
+
function fail(id, code, message) {
|
|
107
|
+
write({ id, ok: false, error: { code, message } });
|
|
108
|
+
}
|
|
109
|
+
// A successful reply, after FAKE_DELAY_MS, with a payload under FAKE_REPLY_PAYLOAD
|
|
110
|
+
function reply(id, body) {
|
|
111
|
+
setTimeout(() => write({ id, ok: true, ...body }, replyPayload ? Buffer.from([1, 2, 3, 4]) : undefined), delayMs);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// What a JSON value is called in encoding/json's "cannot unmarshal … into" messages
|
|
115
|
+
function jsonKind(value) {
|
|
116
|
+
if (typeof value === 'number') return `number ${value}`;
|
|
117
|
+
if (typeof value === 'boolean') return 'bool';
|
|
118
|
+
if (Array.isArray(value)) return 'array';
|
|
119
|
+
return typeof value;
|
|
120
|
+
}
|
|
121
|
+
// The first key of obj outside known, as the server's decoder (DisallowUnknownFields) names it; null when all are
|
|
122
|
+
// known or obj is not an object
|
|
123
|
+
function unknownField(obj, known) {
|
|
124
|
+
if (!obj || typeof obj !== 'object' || Array.isArray(obj)) return null;
|
|
125
|
+
for (const key of Object.keys(obj)) {
|
|
126
|
+
if (!known.has(key)) return `json: unknown field ${JSON.stringify(key)}`;
|
|
127
|
+
}
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// The format of an encoded payload by its magic bytes (the ones imgio decodes), or null
|
|
132
|
+
function imageFormat(buf) {
|
|
133
|
+
if (buf.length >= 8 && buf.readUInt32BE(0) === 0x89504e47) return 'png';
|
|
134
|
+
if (buf.length >= 3 && buf[0] === 0xff && buf[1] === 0xd8 && buf[2] === 0xff) return 'jpeg';
|
|
135
|
+
if (buf.length >= 2 && buf[0] === 0x42 && buf[1] === 0x4d) return 'bmp';
|
|
136
|
+
if (buf.length >= 4 && (buf.readUInt32BE(0) === 0x49492a00 || buf.readUInt32BE(0) === 0x4d4d002a)) return 'tiff';
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
// What the server's loadImage makes of the header and the payload: {width, height}, or {error} with its wording.
|
|
140
|
+
// raw: the packed rows, checked by length; encoded: a file of one of the formats above (the PNG's IHDR gives the
|
|
141
|
+
// size; for the others the header's width/height stand in for the decoded size the real server reports)
|
|
142
|
+
function loadImage(img, payload) {
|
|
143
|
+
img = img || {};
|
|
144
|
+
const unknown = unknownField(img, KNOWN_IMAGE);
|
|
145
|
+
if (unknown) return { error: `image: ${unknown}` };
|
|
146
|
+
const encoding = String(img.encoding === undefined ? '' : img.encoding).toLowerCase();
|
|
147
|
+
if (encoding === '' || encoding === 'raw') {
|
|
148
|
+
if (payload.length === 0) return { error: 'image: decode needs a raw payload of width*height*channels bytes (encoding raw)' };
|
|
149
|
+
const expected = (img.width | 0) * (img.height | 0) * (img.channels | 0);
|
|
150
|
+
if (payload.length !== expected) {
|
|
151
|
+
return { error: `imgio: data length ${payload.length} does not match ${img.width}×${img.height}×${img.channels}` };
|
|
152
|
+
}
|
|
153
|
+
return { width: img.width, height: img.height };
|
|
154
|
+
}
|
|
155
|
+
if (encoding === 'encoded') {
|
|
156
|
+
if (payload.length === 0) return { error: 'image: decode needs an encoded payload, a PNG, JPEG, BMP or TIFF file (encoding encoded)' };
|
|
157
|
+
const format = imageFormat(payload);
|
|
158
|
+
if (!format) return { error: 'imgio: image: unknown format' };
|
|
159
|
+
if (format === 'png' && payload.length >= 24) return { width: payload.readUInt32BE(16), height: payload.readUInt32BE(20) };
|
|
160
|
+
return { width: img.width | 0, height: img.height | 0 };
|
|
161
|
+
}
|
|
162
|
+
return { error: `image: unknown encoding ${JSON.stringify(img.encoding)} (raw or encoded)` };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// The server's region checks (RegionSpec.UnmarshalJSON, regionFrom; docs/service.md §3.5) with their wording:
|
|
166
|
+
// {error}, or the region with its orientation normalised to 0/90/180/270. The bounds are checked against the image
|
|
167
|
+
// once it is loaded, as Verify does
|
|
168
|
+
function checkRegion(region) {
|
|
169
|
+
if (region === undefined || region === null) {
|
|
170
|
+
return { error: 'region: required for verify and analyze ({x, y, w, h, orientation} in image pixels)' };
|
|
171
|
+
}
|
|
172
|
+
if (typeof region !== 'object' || Array.isArray(region)) {
|
|
173
|
+
// the type without the value, as encoding/json names a whole value of the wrong kind
|
|
174
|
+
return { error: `region: json: cannot unmarshal ${typeof region === 'number' ? 'number' : jsonKind(region)} into Go value of type serve.plain` };
|
|
175
|
+
}
|
|
176
|
+
const unknown = unknownField(region, KNOWN_REGION);
|
|
177
|
+
if (unknown) return { error: `region: ${unknown}` };
|
|
178
|
+
for (const key of KNOWN_REGION) {
|
|
179
|
+
const value = region[key];
|
|
180
|
+
if (value !== undefined && value !== null && !Number.isInteger(value)) {
|
|
181
|
+
return { error: `region: json: cannot unmarshal ${jsonKind(value)} into Go struct field plain.${key} of type int` };
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
const [x, y, w, h, o] = ['x', 'y', 'w', 'h', 'orientation'].map((key) => region[key] || 0);
|
|
185
|
+
if (w < 2 || h < 2) return { error: `region: width ${w} and height ${h} must be positive and at least 2 px` };
|
|
186
|
+
if (o % 90 !== 0) return { error: `region: orientation ${o} is not a multiple of 90` };
|
|
187
|
+
return { x, y, w, h, orientation: ((o % 360) + 360) % 360 };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
process.stdout.on('error', () => process.exit(1)); // broken pipe: the parent is gone (§3.1: status 1)
|
|
191
|
+
|
|
192
|
+
if (!['pass', 'fail', 'not_detected'].includes(verifyStatus)) {
|
|
193
|
+
log(`FAKE_VERIFY_STATUS ${JSON.stringify(verifyStatus)} is not pass, fail or not_detected`);
|
|
194
|
+
process.exit(2);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (noHello) {
|
|
198
|
+
process.on('SIGTERM', () => { /* ignored: the client has to SIGKILL */ });
|
|
199
|
+
process.stdin.resume(); // reads and never answers
|
|
200
|
+
} else {
|
|
201
|
+
if (ignoreStop) process.on('SIGTERM', () => { /* ignored */ });
|
|
202
|
+
write({
|
|
203
|
+
id: 0, op: 'hello', protocol, sdk: '0.2.0-fake', engineVersion: '0.2.0+fake', schema: '1.0',
|
|
204
|
+
ops, workers: 1, queue: 64,
|
|
205
|
+
limits: { maxHeader: 1048576, maxPayload, maxSide: 32768, maxPixels: 268435456 }
|
|
206
|
+
});
|
|
207
|
+
log(`hello sent (pid ${process.pid})`);
|
|
208
|
+
if (garbage) process.stdout.write('this is not a frame\n');
|
|
209
|
+
|
|
210
|
+
// FAKE_CRASH_AFTER counts decode, verify and analyze alike: request n+1 dies mid-flight, without an answer
|
|
211
|
+
let requests = 0;
|
|
212
|
+
function countOrCrash() {
|
|
213
|
+
if (crashAfter >= 0 && requests >= crashAfter) process.exit(7);
|
|
214
|
+
requests += 1;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const parser = new FrameParser({ maxPayload });
|
|
218
|
+
parser.on('error', (err) => {
|
|
219
|
+
fail(0, 'bad_frame', err.message);
|
|
220
|
+
log(`bad frame: ${err.message}`);
|
|
221
|
+
process.exit(3);
|
|
222
|
+
});
|
|
223
|
+
parser.on('frame', (req, payload) => {
|
|
224
|
+
// As the binary's parseRequest: a non-integer deadlineMs fails the whole request before its op is looked at
|
|
225
|
+
if (req.deadlineMs !== undefined && req.deadlineMs !== null && !Number.isInteger(req.deadlineMs)) {
|
|
226
|
+
const what = typeof req.deadlineMs === 'number' ? `number ${req.deadlineMs}` : typeof req.deadlineMs;
|
|
227
|
+
return fail(req.id, 'invalid_input', `json: cannot unmarshal ${what} into Go struct field Request.deadlineMs of type int`);
|
|
228
|
+
}
|
|
229
|
+
switch (req.op) {
|
|
230
|
+
case 'ping':
|
|
231
|
+
write({ id: req.id, ok: true });
|
|
232
|
+
break;
|
|
233
|
+
case 'shutdown':
|
|
234
|
+
if (ignoreStop) break;
|
|
235
|
+
write({ id: req.id, ok: true }, undefined, () => process.exit(0));
|
|
236
|
+
break;
|
|
237
|
+
case 'decode': {
|
|
238
|
+
countOrCrash();
|
|
239
|
+
if (overload) return fail(req.id, 'overloaded', 'queue full');
|
|
240
|
+
const img = loadImage(req.image, payload);
|
|
241
|
+
if (img.error) return fail(req.id, 'invalid_input', img.error);
|
|
242
|
+
const unknown = unknownField(req.options, KNOWN_DECODE_OPTIONS);
|
|
243
|
+
if (unknown) return fail(req.id, 'invalid_input', `options: ${unknown}`);
|
|
244
|
+
reply(req.id, {
|
|
245
|
+
image: img,
|
|
246
|
+
symbols: Array.from({ length: decodeSymbols }, () => ({ ...SYMBOL, region: { ...SYMBOL.region } })),
|
|
247
|
+
timing: { totalMs: delayMs, decodeMs: delayMs, queuedMs: 0 }
|
|
248
|
+
});
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
case 'verify':
|
|
252
|
+
case 'analyze': {
|
|
253
|
+
// The server's order (handleVerify): region, options, acquisition, image, then Verify's bounds check
|
|
254
|
+
if (!ops.includes(req.op)) return fail(req.id, 'unsupported', `op ${JSON.stringify(req.op)} is not supported (ops: ${ops.join(', ')})`);
|
|
255
|
+
countOrCrash();
|
|
256
|
+
if (overload) return fail(req.id, 'overloaded', 'queue full');
|
|
257
|
+
const region = checkRegion(req.region);
|
|
258
|
+
if (region.error) return fail(req.id, 'invalid_input', region.error);
|
|
259
|
+
const unknownOpt = unknownField(req.options, KNOWN_VERIFY_OPTIONS) || unknownField(req.options && req.options.spec, KNOWN_SPEC);
|
|
260
|
+
if (unknownOpt) return fail(req.id, 'invalid_input', `options: ${unknownOpt}`);
|
|
261
|
+
const acq = req.acquisition || {};
|
|
262
|
+
const unknownAcq = unknownField(acq, KNOWN_ACQUISITION) || unknownField(acq.calibration, KNOWN_CALIBRATION);
|
|
263
|
+
if (unknownAcq) return fail(req.id, 'invalid_input', `acquisition: ${unknownAcq}`);
|
|
264
|
+
const img = loadImage(req.image, payload);
|
|
265
|
+
if (img.error) return fail(req.id, 'invalid_input', img.error);
|
|
266
|
+
// Verify's bounds check, when the size is known (an encoded payload that is not a PNG and declares no size is 0×0 here)
|
|
267
|
+
if (img.width > 0 && img.height > 0 && (region.x < 0 || region.y < 0 || region.x + region.w > img.width || region.y + region.h > img.height)) {
|
|
268
|
+
return fail(req.id, 'invalid_input', `barcode: region {X:${region.x} Y:${region.y} W:${region.w} H:${region.h} ` +
|
|
269
|
+
`Orientation:${region.orientation}} outside image ${img.width}x${img.height}`);
|
|
270
|
+
}
|
|
271
|
+
let result;
|
|
272
|
+
if (verifyStatus === 'pass') {
|
|
273
|
+
result = canned(req.op === 'analyze' ? 'analyze-pass' : 'verify-pass');
|
|
274
|
+
} else {
|
|
275
|
+
result = canned(verifyStatus === 'fail' ? 'verify-fail' : 'verify-not-detected');
|
|
276
|
+
if (req.op === 'analyze') result.scans = [];
|
|
277
|
+
}
|
|
278
|
+
if (result.symbol) result.symbol.region = region;
|
|
279
|
+
reply(req.id, { image: img, result, timing: { totalMs: delayMs, verifyMs: delayMs, queuedMs: 0 } });
|
|
280
|
+
break;
|
|
281
|
+
}
|
|
282
|
+
default:
|
|
283
|
+
fail(req.id, 'unsupported', `op ${JSON.stringify(req.op)} is not supported (ops: ${ops.join(', ')})`);
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
if (stall) {
|
|
287
|
+
setInterval(() => { /* alive, deaf: stdin is never read */ }, 1 << 30);
|
|
288
|
+
} else {
|
|
289
|
+
process.stdin.on('data', (chunk) => parser.push(chunk));
|
|
290
|
+
process.stdin.on('end', () => process.exit(0)); // EOF: the parent closed the pipe (§3.1: status 0)
|
|
291
|
+
}
|
|
292
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"status":"pass","reasons":[],"warnings":[],"symbol":{"symbology":"EAN13","identifier":"]E0","value":"4006381333931","bytes":"NDAwNjM4MTMzMzkzMQ==","gs1":true,"corners":[{"x":37,"y":4},{"x":322,"y":4},{"x":322,"y":64},{"x":37,"y":64}],"orientation":0,"confidence":1,"lines":60,"checksum":"valid","region":{"x":4,"y":4,"w":339,"h":60,"orientation":0}},"quality":{"overall":{"value":0,"grade":4,"letter":"A","worst":0,"mean":0,"hasValue":true},"text":"4.0/00/660","decode":{"pass":true,"value":60,"detail":""},"decodedLines":60,"edgeDetermination":{"pass":true,"value":0,"detail":""},"minReflectance":{"pass":true,"value":5.098,"detail":""},"minEdgeContrast":{"pass":true,"value":80,"detail":""},"symbolContrast":{"value":80,"grade":4,"letter":"A","worst":4,"mean":80,"hasValue":true},"modulation":{"value":1,"grade":4,"letter":"A","worst":4,"mean":1,"hasValue":true},"defects":{"value":0,"grade":4,"letter":"A","worst":4,"mean":0,"hasValue":true},"decodability":{"value":1,"grade":4,"letter":"A","worst":4,"mean":1,"hasValue":true},"rmin":5.098,"rmax":85.098,"gt":45.098,"quietZone":{"leftX":11,"rightX":7,"leftPct":110,"rightPct":112.9032,"requiredLeftX":10,"requiredRightX":6.2,"pass":true},"additional":{"pass":true,"value":0,"detail":""},"decodabilityProvisional":false,"inconsistent":false,"limiting":"","scanGrades":[4,4,4]},"dimensions":{"evaluated":false,"xDimPx":3,"xDimMils":0,"xDimMm":0,"nominalXMm":0,"magnification":0,"barHeightPx":60,"barHeightMm":0,"barGrowthPct":0,"narrowBarPx":3,"narrowSpacePx":3,"symbolWidthPx":285,"quietZoneLeftX":11,"quietZoneRightX":7,"ratio":0},"gs1":{"identifier":"]E0","hri":"(01)04006381333931","elements":[{"ai":"01","value":"04006381333931","title":"GTIN"}],"structure":"pass","dataStructure":"pass","errors":[],"table":"","limits":[],"dictionary":"2026-01-27"},"application":{"spec":{"kind":"iso","table":""},"tableTitle":"","limitAction":"","apertureRef":0,"apertureMils":0,"wavelengthNm":660,"edition":"2016","calibration":"none"},"version":{"sdk":"0.2.0","schema":"1.0","dictionary":"2026-01-27"},"timing":{"totalMs":2.411,"measureMs":1.258,"decodeMs":0.4,"gradeMs":0.012,"gs1Ms":0.714},"report":[{"category":0,"name":"Overall grade","value":"4.0/00/660"},{"category":2,"name":"Symbology","value":"EAN-13"},{"category":2,"name":"Decoded text","value":"4006381333931"},{"category":2,"name":"Xdim","value":"3.00 px"},{"category":2,"name":"Edge determ","value":"PASS"},{"category":2,"name":"Min Reflect","value":"PASS"},{"category":2,"name":"Minimum EC","value":"PASS"},{"category":2,"name":"Decode","value":"PASS 60"},{"category":2,"name":"Quiet zone","value":"PASS"},{"category":2,"name":"Contrast","value":"4.0 80%"},{"category":2,"name":"Modulation","value":"4.0 100%"},{"category":2,"name":"Decodability","value":"4.0 100%"},{"category":2,"name":"Defects","value":"4.0 0%"},{"category":2,"name":"Rmin","value":"5.1%"},{"category":2,"name":"Rmax","value":"85.1%"},{"category":2,"name":"GT","value":"45.1%"},{"category":2,"name":"Bar height","value":"60.0 px"},{"category":2,"name":"Bar growth","value":"0%"},{"category":2,"name":"Structure","value":"PASS"},{"category":6,"name":"GS1 Data","value":"(01)04006381333931"},{"category":6,"name":"GS1 Data Structure Pass/Fail","value":"PASS"},{"category":6,"name":"Letter Grade","value":"A"},{"category":6,"name":"Application standard","value":"ISO/IEC 15416"},{"category":6,"name":"Effective aperture","value":"n/a"},{"category":6,"name":"Lighting","value":"660"},{"category":6,"name":"Software product and version","value":"rosepetal-barcode-sdk 0.2.0 (schema 1.0)"}],"scans":[{"index":0,"y":14,"grade":4,"decoded":true,"value":"4006381333931","reason":"","rmax":85.098,"rmin":5.098,"gt":45.098,"sc":80,"ecmin":80,"mod":1,"ernmax":0,"defects":0,"decodability":1,"quietZoneLeftX":11,"quietZoneRightX":7,"profile":[85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098],"edges":[33,36,39,42,51,57,60,63,66,69,75,84,87,90,93,105,108,120,123,126,135,138,144,147,150,156,162,168,171,174,177,180,183,186,198,201,204,207,219,222,225,228,240,243,246,255,258,261,267,270,282,285,288,294,300,306,309,312,315,318]},{"index":1,"y":30,"grade":4,"decoded":true,"value":"4006381333931","reason":"","rmax":85.098,"rmin":5.098,"gt":45.098,"sc":80,"ecmin":80,"mod":1,"ernmax":0,"defects":0,"decodability":1,"quietZoneLeftX":11,"quietZoneRightX":7,"profile":[85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098],"edges":[33,36,39,42,51,57,60,63,66,69,75,84,87,90,93,105,108,120,123,126,135,138,144,147,150,156,162,168,171,174,177,180,183,186,198,201,204,207,219,222,225,228,240,243,246,255,258,261,267,270,282,285,288,294,300,306,309,312,315,318]},{"index":2,"y":46,"grade":4,"decoded":true,"value":"4006381333931","reason":"","rmax":85.098,"rmin":5.098,"gt":45.098,"sc":80,"ecmin":80,"mod":1,"ernmax":0,"defects":0,"decodability":1,"quietZoneLeftX":11,"quietZoneRightX":7,"profile":[85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,77.098,45.098,13.098,5.098,5.098,5.098,13.098,45.098,77.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098,85.098],"edges":[33,36,39,42,51,57,60,63,66,69,75,84,87,90,93,105,108,120,123,126,135,138,144,147,150,156,162,168,171,174,177,180,183,186,198,201,204,207,219,222,225,228,240,243,246,255,258,261,267,270,282,285,288,294,300,306,309,312,315,318]}]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"status":"fail","reasons":["overall grade 0.0 below fail threshold 1.5 (limiting: Min Reflect)"],"warnings":[],"symbol":{"symbology":"Code128","identifier":"]C0","value":"ROSEPETAL-ISO","bytes":"Uk9TRVBFVEFMLUlTTw==","gs1":false,"corners":[{"x":44,"y":4},{"x":756,"y":4},{"x":756,"y":64},{"x":44,"y":64}],"orientation":0,"confidence":1,"lines":60,"checksum":"valid","region":{"x":4,"y":4,"w":792,"h":60,"orientation":0}},"quality":{"overall":{"value":0,"grade":0,"letter":"F","worst":0,"mean":0,"hasValue":true},"text":"0.0/00/660","decode":{"pass":true,"value":60,"detail":""},"decodedLines":60,"edgeDetermination":{"pass":true,"value":0,"detail":""},"minReflectance":{"pass":false,"value":70.1961,"detail":""},"minEdgeContrast":{"pass":false,"value":9.8039,"detail":""},"symbolContrast":{"value":9.8039,"grade":0,"letter":"F","worst":0,"mean":9.8039,"hasValue":true},"modulation":{"value":1,"grade":4,"letter":"A","worst":4,"mean":1,"hasValue":true},"defects":{"value":0,"grade":4,"letter":"A","worst":4,"mean":0,"hasValue":true},"decodability":{"value":1,"grade":4,"letter":"A","worst":4,"mean":1,"hasValue":true},"rmin":70.1961,"rmax":80,"gt":75.098,"quietZone":{"leftX":10,"rightX":10,"leftPct":100,"rightPct":100,"requiredLeftX":10,"requiredRightX":10,"pass":true},"additional":{"pass":true,"value":0,"detail":""},"decodabilityProvisional":false,"inconsistent":false,"limiting":"MinReflectance","scanGrades":[0,0,0,0,0,0,0,0,0,0]},"dimensions":{"evaluated":false,"xDimPx":4,"xDimMils":0,"xDimMm":0,"nominalXMm":0,"magnification":0,"barHeightPx":60,"barHeightMm":0,"barGrowthPct":0,"narrowBarPx":4,"narrowSpacePx":4,"symbolWidthPx":712,"quietZoneLeftX":10,"quietZoneRightX":10,"ratio":0},"gs1":null,"application":{"spec":{"kind":"iso","table":""},"tableTitle":"","limitAction":"","apertureRef":0,"apertureMils":0,"wavelengthNm":660,"edition":"2016","calibration":"none"},"version":{"sdk":"0.2.0","schema":"1.0","dictionary":"2026-01-27"},"timing":{"totalMs":7.238,"measureMs":5.092,"decodeMs":0.971,"gradeMs":0.024,"gs1Ms":1.126},"report":[{"category":0,"name":"Overall grade","value":"0.0/00/660"},{"category":2,"name":"Symbology","value":"Code 128"},{"category":2,"name":"Decoded text","value":"ROSEPETAL-ISO"},{"category":2,"name":"Xdim","value":"4.00 px"},{"category":2,"name":"Edge determ","value":"PASS"},{"category":2,"name":"Min Reflect","value":"FAIL"},{"category":2,"name":"Minimum EC","value":"FAIL"},{"category":2,"name":"Decode","value":"PASS 60"},{"category":2,"name":"Quiet zone","value":"PASS"},{"category":2,"name":"Contrast","value":"0.0 9.8%"},{"category":2,"name":"Modulation","value":"4.0 100%"},{"category":2,"name":"Decodability","value":"4.0 100%"},{"category":2,"name":"Defects","value":"4.0 0%"},{"category":2,"name":"Rmin","value":"70.2%"},{"category":2,"name":"Rmax","value":"80%"},{"category":2,"name":"GT","value":"75.1%"},{"category":2,"name":"Bar height","value":"60.0 px"},{"category":2,"name":"Bar growth","value":"0%"},{"category":2,"name":"Structure","value":"PASS"},{"category":6,"name":"Letter Grade","value":"F"},{"category":6,"name":"Application standard","value":"ISO/IEC 15416"},{"category":6,"name":"Effective aperture","value":"n/a"},{"category":6,"name":"Lighting","value":"660"},{"category":6,"name":"Software product and version","value":"rosepetal-barcode-sdk 0.2.0 (schema 1.0)"}]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"status":"not_detected","reasons":["no decode"],"warnings":[],"symbol":null,"quality":{"overall":{"value":0,"grade":0,"letter":"F","worst":0,"mean":0,"hasValue":true},"text":"0.0/00/660","decode":{"pass":false,"value":0,"detail":""},"decodedLines":0,"edgeDetermination":{"pass":false,"value":0,"detail":""},"minReflectance":{"pass":false,"value":59.473,"detail":""},"minEdgeContrast":{"pass":false,"value":12.8125,"detail":""},"symbolContrast":{"value":12.8125,"grade":0,"letter":"F","worst":0,"mean":12.8125,"hasValue":true},"modulation":{"value":0.5,"grade":0,"letter":"F","worst":0,"mean":0.5,"hasValue":true},"defects":{"value":0,"grade":0,"letter":"F","worst":0,"mean":0,"hasValue":true},"decodability":{"value":0,"grade":0,"letter":"","worst":0,"mean":0,"hasValue":false},"rmin":59.473,"rmax":72.2855,"gt":65.8793,"quietZone":{"leftX":0,"rightX":0,"leftPct":0,"rightPct":0,"requiredLeftX":0,"requiredRightX":0,"pass":false},"additional":{"pass":false,"value":0,"detail":""},"decodabilityProvisional":false,"inconsistent":false,"limiting":"Decode","scanGrades":[0,0,0,0,0,0,0,0,0,0]},"dimensions":{"evaluated":false,"xDimPx":0,"xDimMils":0,"xDimMm":0,"nominalXMm":0,"magnification":0,"barHeightPx":0,"barHeightMm":0,"barGrowthPct":0,"narrowBarPx":0,"narrowSpacePx":0,"symbolWidthPx":0,"quietZoneLeftX":0,"quietZoneRightX":0,"ratio":0},"gs1":null,"application":{"spec":{"kind":"iso","table":""},"tableTitle":"","limitAction":"","apertureRef":0,"apertureMils":0,"wavelengthNm":660,"edition":"2016","calibration":"none"},"version":{"sdk":"0.2.0","schema":"1.0","dictionary":"2026-01-27"},"timing":{"totalMs":23.855,"measureMs":23.767,"decodeMs":0.061,"gradeMs":0.022,"gs1Ms":0},"report":[{"category":0,"name":"Overall grade","value":"Bar Code Not Detected"},{"category":2,"name":"Xdim","value":"n/a"},{"category":2,"name":"Edge determ","value":"n/a"},{"category":2,"name":"Min Reflect","value":"FAIL"},{"category":2,"name":"Minimum EC","value":"FAIL"},{"category":2,"name":"Decode","value":"FAIL"},{"category":2,"name":"Quiet zone","value":"n/a"},{"category":2,"name":"Contrast","value":"0.0 12.8%"},{"category":2,"name":"Modulation","value":"0.0 50%"},{"category":2,"name":"Decodability","value":"n/a"},{"category":2,"name":"Defects","value":"0.0 0%"},{"category":2,"name":"Rmin","value":"59.5%"},{"category":2,"name":"Rmax","value":"72.3%"},{"category":2,"name":"GT","value":"65.9%"},{"category":2,"name":"Bar height","value":"n/a"},{"category":2,"name":"Bar growth","value":"n/a"},{"category":2,"name":"Structure","value":"n/a"},{"category":6,"name":"Letter Grade","value":"F"},{"category":6,"name":"Application standard","value":"ISO/IEC 15416"},{"category":6,"name":"Effective aperture","value":"n/a"},{"category":6,"name":"Lighting","value":"660"},{"category":6,"name":"Software product and version","value":"rosepetal-barcode-sdk 0.2.0 (schema 1.0)"}]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"status":"pass","reasons":[],"warnings":[],"symbol":{"symbology":"EAN13","identifier":"]E0","value":"4006381333931","bytes":"NDAwNjM4MTMzMzkzMQ==","gs1":true,"corners":[{"x":37,"y":4},{"x":322,"y":4},{"x":322,"y":64},{"x":37,"y":64}],"orientation":0,"confidence":1,"lines":60,"checksum":"valid","region":{"x":4,"y":4,"w":339,"h":60,"orientation":0}},"quality":{"overall":{"value":0,"grade":4,"letter":"A","worst":0,"mean":0,"hasValue":true},"text":"4.0/00/660","decode":{"pass":true,"value":60,"detail":""},"decodedLines":60,"edgeDetermination":{"pass":true,"value":0,"detail":""},"minReflectance":{"pass":true,"value":5.098,"detail":""},"minEdgeContrast":{"pass":true,"value":80,"detail":""},"symbolContrast":{"value":80,"grade":4,"letter":"A","worst":4,"mean":80,"hasValue":true},"modulation":{"value":1,"grade":4,"letter":"A","worst":4,"mean":1,"hasValue":true},"defects":{"value":0,"grade":4,"letter":"A","worst":4,"mean":0,"hasValue":true},"decodability":{"value":1,"grade":4,"letter":"A","worst":4,"mean":1,"hasValue":true},"rmin":5.098,"rmax":85.098,"gt":45.098,"quietZone":{"leftX":11,"rightX":7,"leftPct":110,"rightPct":112.9032,"requiredLeftX":10,"requiredRightX":6.2,"pass":true},"additional":{"pass":true,"value":0,"detail":""},"decodabilityProvisional":false,"inconsistent":false,"limiting":"","scanGrades":[4,4,4,4,4,4,4,4,4,4]},"dimensions":{"evaluated":false,"xDimPx":3,"xDimMils":0,"xDimMm":0,"nominalXMm":0,"magnification":0,"barHeightPx":60,"barHeightMm":0,"barGrowthPct":0,"narrowBarPx":3,"narrowSpacePx":3,"symbolWidthPx":285,"quietZoneLeftX":11,"quietZoneRightX":7,"ratio":0},"gs1":{"identifier":"]E0","hri":"(01)04006381333931","elements":[{"ai":"01","value":"04006381333931","title":"GTIN"}],"structure":"pass","dataStructure":"pass","errors":[],"table":"","limits":[],"dictionary":"2026-01-27"},"application":{"spec":{"kind":"iso","table":""},"tableTitle":"","limitAction":"","apertureRef":0,"apertureMils":0,"wavelengthNm":660,"edition":"2016","calibration":"none"},"version":{"sdk":"0.2.0","schema":"1.0","dictionary":"2026-01-27"},"timing":{"totalMs":3.515,"measureMs":2.238,"decodeMs":0.526,"gradeMs":0.026,"gs1Ms":0.706},"report":[{"category":0,"name":"Overall grade","value":"4.0/00/660"},{"category":2,"name":"Symbology","value":"EAN-13"},{"category":2,"name":"Decoded text","value":"4006381333931"},{"category":2,"name":"Xdim","value":"3.00 px"},{"category":2,"name":"Edge determ","value":"PASS"},{"category":2,"name":"Min Reflect","value":"PASS"},{"category":2,"name":"Minimum EC","value":"PASS"},{"category":2,"name":"Decode","value":"PASS 60"},{"category":2,"name":"Quiet zone","value":"PASS"},{"category":2,"name":"Contrast","value":"4.0 80%"},{"category":2,"name":"Modulation","value":"4.0 100%"},{"category":2,"name":"Decodability","value":"4.0 100%"},{"category":2,"name":"Defects","value":"4.0 0%"},{"category":2,"name":"Rmin","value":"5.1%"},{"category":2,"name":"Rmax","value":"85.1%"},{"category":2,"name":"GT","value":"45.1%"},{"category":2,"name":"Bar height","value":"60.0 px"},{"category":2,"name":"Bar growth","value":"0%"},{"category":2,"name":"Structure","value":"PASS"},{"category":6,"name":"GS1 Data","value":"(01)04006381333931"},{"category":6,"name":"GS1 Data Structure Pass/Fail","value":"PASS"},{"category":6,"name":"Letter Grade","value":"A"},{"category":6,"name":"Application standard","value":"ISO/IEC 15416"},{"category":6,"name":"Effective aperture","value":"n/a"},{"category":6,"name":"Lighting","value":"660"},{"category":6,"name":"Software product and version","value":"rosepetal-barcode-sdk 0.2.0 (schema 1.0)"}]}
|