@sythos/js_barcode_universal 1.4.1 → 1.5.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.
@@ -98,6 +98,11 @@
98
98
  Sythos Barcode Suite — 100% original JavaScript, zero dependencies, MIT.
99
99
  See also <a href="create.html">create.html</a>.
100
100
  </p>
101
+ <p class="sub">
102
+ Image reading includes Code 11, MSI Plessey, GS1-128, GS1 DataBar
103
+ Omnidirectional/Truncated and EAN-2/EAN-5 supplements attached to an EAN/UPC
104
+ parent. Pharmacode remains intentionally write-only for generic autodetection.
105
+ </p>
101
106
  </header>
102
107
 
103
108
  <div class="layout">
@@ -36,8 +36,8 @@ appendix carries the consolidated inventory these files expand on.
36
36
  | EAN-5 supplement | [`ean-5.license`](ean-5.license) | 2 |
37
37
  | Micro QR Code | [`micro-qr.license`](micro-qr.license) | 2 |
38
38
  | rMQR Code | [`rmqr.license`](rmqr.license) | 2 |
39
- | FrameQR Code | [`frameqr.license`](frameqr.license) | 2 |
40
- | GS1 DataBar data-layer codecs | [`gs1-databar.license`](gs1-databar.license) | 2 |
39
+ | Frame QR / Sythos Canvas QR profile | [`frameqr.license`](frameqr.license) | 2 |
40
+ | GS1 DataBar Omnidirectional / Truncated | [`gs1-databar.license`](gs1-databar.license) | 2 |
41
41
 
42
42
  ## On the `[TO VERIFY]` markers
43
43
 
@@ -60,8 +60,9 @@ CONCLUSION
60
60
  --------------------------------------------------------------------------
61
61
 
62
62
  The original add-on implementation is distributed under the MIT License.
63
- Generic camera read capability remains intentionally separate from the
64
- direct matrix helper.
63
+ Image reading is intentionally parent-bound: the generic reader accepts
64
+ EAN-2 only when attached to a validated EAN/UPC symbol; it is not a
65
+ standalone retail-symbol detector.
65
66
 
66
67
  References relied upon:
67
68
  - ISO/IEC 15420, ISO/IEC
@@ -60,8 +60,9 @@ CONCLUSION
60
60
  --------------------------------------------------------------------------
61
61
 
62
62
  The original add-on implementation is distributed under the MIT License.
63
- Generic camera read capability remains intentionally separate from the
64
- direct matrix helper.
63
+ Image reading is intentionally parent-bound: the generic reader accepts
64
+ EAN-5 only when attached to a validated EAN/UPC symbol; it is not a
65
+ standalone retail-symbol detector.
65
66
 
66
67
  References relied upon:
67
68
  - ISO/IEC 15420, ISO/IEC
@@ -1,7 +1,7 @@
1
- FrameQR Code — format provenance and usability
1
+ Frame QR / Sythos Canvas QR profile — format provenance and usability
2
2
  ==========================================================================
3
3
 
4
- This file records where the FrameQR Code symbology comes from, what governs
4
+ This file records where the Frame QR / Sythos Canvas QR profile symbology comes from, what governs
5
5
  it, and the basis on which this project implements and redistributes it.
6
6
 
7
7
  It is an ENGINEERING INVENTORY, NOT LEGAL ADVICE. Items marked [TO VERIFY]
@@ -22,7 +22,7 @@ SPECIFICATION
22
22
 
23
23
  DENSO WAVE public FrameQR material, including the product overview
24
24
  and the public announcement. The proprietary format definition is not
25
- reproduced here. This project implements the separate FrameQR Code
25
+ reproduced here. This project implements the separate Sythos Canvas QR
26
26
  profile on an ISO/IEC 18004 QR Model 2 baseline.
27
27
 
28
28
  Specification TEXT is copyrighted by its publisher. The SYMBOLOGY it
@@ -55,7 +55,7 @@ branding — how a product names and presents itself.
55
55
  IMPLEMENTATION BASIS IN THIS PROJECT
56
56
  --------------------------------------------------------------------------
57
57
 
58
- FrameQR Code is implemented here from published descriptions of the format,
58
+ Frame QR / Sythos Canvas QR profile is implemented here from published descriptions of the format,
59
59
  which are systems and facts rather than works of authorship. No source
60
60
  code or constant table from any other barcode implementation is copied or
61
61
  shipped. Public technical material and independent implementations may be
@@ -66,8 +66,8 @@ recorded in NOTICE.md.
66
66
  CONCLUSION
67
67
  --------------------------------------------------------------------------
68
68
 
69
- The distributed implementation is the original, non-certified FrameQR Code
70
- profile `sythos-canvas-qr/1`, not DENSO FrameQR. It reserves a
69
+ The distributed implementation is the original, non-certified Sythos
70
+ Canvas QR profile `sythos-canvas-qr/1`, not DENSO FrameQR. It reserves a
71
71
  bounded artwork canvas inside a QR Model 2 ECC-H symbol and does not claim
72
72
  native FrameQR interoperability. Third-party software cited here was used
73
73
  exclusively as an independent verification and cross-checking tool; no
@@ -1,7 +1,7 @@
1
- GS1 DataBar data-layer codecs — format provenance and usability
1
+ GS1 DataBar Omnidirectional / Truncated — format provenance and usability
2
2
  ==========================================================================
3
3
 
4
- This file records where the GS1 DataBar data-layer codecs symbology comes from, what governs
4
+ This file records where the GS1 DataBar Omnidirectional / Truncated symbology comes from, what governs
5
5
  it, and the basis on which this project implements and redistributes it.
6
6
 
7
7
  It is an ENGINEERING INVENTORY, NOT LEGAL ADVICE. Items marked [TO VERIFY]
@@ -13,8 +13,9 @@ ORIGIN
13
13
 
14
14
  GS1 DataBar is a family of seven GS1 linear symbologies defined by ISO/IEC
15
15
  24724 and GS1 specifications. This release contains original GTIN and GS1
16
- element-string data-layer codecs; physical bar-pattern support is limited
17
- to the variants explicitly listed by the package.
16
+ element-string data-layer codecs plus physical Omnidirectional and
17
+ Truncated pattern readers and writers; other physical variants remain
18
+ outside the package scope.
18
19
 
19
20
 
20
21
  SPECIFICATION
@@ -54,7 +55,7 @@ branding — how a product names and presents itself.
54
55
  IMPLEMENTATION BASIS IN THIS PROJECT
55
56
  --------------------------------------------------------------------------
56
57
 
57
- GS1 DataBar data-layer codecs is implemented here from published descriptions of the format,
58
+ GS1 DataBar Omnidirectional / Truncated is implemented here from published descriptions of the format,
58
59
  which are systems and facts rather than works of authorship. No source
59
60
  code or constant table from any other barcode implementation is copied or
60
61
  shipped. Public technical material and independent implementations may be
@@ -66,8 +67,10 @@ CONCLUSION
66
67
  --------------------------------------------------------------------------
67
68
 
68
69
  Only original Sythos DataBar code is distributed under the MIT License.
69
- Unsupported physical variants and conformance questions are intentionally
70
- not presented as completed capability. [LEGAL REVIEW]
70
+ Omnidirectional and Truncated image support is exposed after
71
+ checksum-validated black-box verification; Limited, Stacked and Expanded
72
+ physical variants remain intentionally outside the completed capability.
73
+ [LEGAL REVIEW]
71
74
 
72
75
  References relied upon:
73
76
  - ISO/IEC 24724, ISO/IEC
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sythos/js_barcode_universal",
3
- "version": "1.4.1",
3
+ "version": "1.5.1",
4
4
  "description": "Read and write barcodes in JavaScript with zero runtime dependencies. QR Code, Micro QR, rMQR, FrameQR Code, Aztec Code and Rune, PDF417 variants, GS1 DataBar Omnidirectional/Truncated, EAN supplements and one-dimensional formats.",
5
5
  "author": {
6
6
  "name": "Sythos",
@@ -4,6 +4,7 @@
4
4
  * MIT License
5
5
  *
6
6
  * Copyright (c) 2026 Sythos
7
+ * SPDX-FileCopyrightText: 2026 Sythos (https://www.sythos.net)
7
8
  *
8
9
  * Permission is hereby granted, free of charge, to any person obtaining a copy
9
10
  * of this software and associated documentation files (the "Software"), to deal
@@ -23,7 +24,6 @@
23
24
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
25
  * SOFTWARE.
25
26
  *
26
- * SPDX-FileCopyrightText: 2026 Sythos (https://www.sythos.net)
27
27
  * SPDX-License-Identifier: MIT
28
28
  *
29
29
  * Original work. No code from any other barcode implementation.
@@ -32,6 +32,7 @@
32
32
  /** Clean-matrix decoder for GS1 DataBar Omnidirectional and Truncated. @module databar/decoder */
33
33
 
34
34
  import { ChecksumError, FormatError } from '../core/errors.js';
35
+ import { BitMatrix } from '../core/bit-matrix.js';
35
36
  import { decodeDataBar14GTIN } from './codec.js';
36
37
  import {
37
38
  DATABAR14_CHECKSUM_WEIGHTS,
@@ -113,3 +114,103 @@ export function decodeDataBar14(matrix) {
113
114
  symbologyIdentifier: ']e0',
114
115
  });
115
116
  }
117
+
118
+ /**
119
+ * Extract alternating runs from a binarized scanline.
120
+ * @param {Uint8Array} row
121
+ * @returns {{widths:number[], dark:boolean}[]}
122
+ */
123
+ function scanlineRuns(row) {
124
+ if (!row || row.length < 96) return [];
125
+ const runs = [];
126
+ let dark = row[0] === 1;
127
+ let start = 0;
128
+ for (let x = 1; x < row.length; x++) {
129
+ const nextDark = row[x] === 1;
130
+ if (nextDark === dark) continue;
131
+ runs.push({ width: x - start, dark });
132
+ start = x;
133
+ dark = nextDark;
134
+ }
135
+ runs.push({ width: row.length - start, dark });
136
+ return runs;
137
+ }
138
+
139
+ /**
140
+ * Normalize a candidate's pixel runs to the 96 logical DataBar modules.
141
+ * Rounding is followed by a small conservation pass so mild non-integer
142
+ * scaling does not change the total symbol width.
143
+ * @param {number[]} raw
144
+ * @returns {number[]|null}
145
+ */
146
+ function normalizeScanlineWidths(raw) {
147
+ const total = raw.reduce((sum, width) => sum + width, 0);
148
+ if (total <= 0) return null;
149
+ const scale = total / 96;
150
+ if (scale < 0.5) return null;
151
+ const widths = raw.map((width) => Math.max(1, Math.round(width / scale)));
152
+ let delta = 96 - widths.reduce((sum, width) => sum + width, 0);
153
+ while (delta !== 0) {
154
+ if (delta > 0) {
155
+ let index = 0;
156
+ for (let i = 1; i < widths.length; i++) if (raw[i] > raw[index]) index = i;
157
+ widths[index]++;
158
+ delta--;
159
+ } else {
160
+ let index = -1;
161
+ for (let i = 0; i < widths.length; i++) {
162
+ if (widths[i] <= 1) continue;
163
+ if (index < 0 || raw[i] / widths[i] > raw[index] / widths[index]) index = i;
164
+ }
165
+ if (index < 0) return null;
166
+ widths[index]--;
167
+ delta++;
168
+ }
169
+ }
170
+ if (widths[0] !== 1 || widths[widths.length - 1] !== 1) return null;
171
+ return widths;
172
+ }
173
+
174
+ /** Build a 96-module row from normalized alternating runs. */
175
+ function matrixFromRuns(widths, darkFirst) {
176
+ const matrix = new BitMatrix(96, 1);
177
+ let x = 0;
178
+ let dark = darkFirst;
179
+ for (const width of widths) {
180
+ if (dark) for (let i = 0; i < width; i++) matrix.set(x + i, 0);
181
+ x += width;
182
+ dark = !dark;
183
+ }
184
+ return matrix;
185
+ }
186
+
187
+ /**
188
+ * Decode GS1 DataBar-14 from one raster scanline. This is the image layer over
189
+ * the existing clean 96-module decoder; it recognizes both Omnidirectional
190
+ * and Truncated symbols because their horizontal pattern is identical.
191
+ *
192
+ * @param {Uint8Array} row
193
+ * @returns {{format:'gs1databar14', text:string, gtin:string, gs1:boolean, linkage:boolean, symbologyIdentifier:string, elements:Array}|null}
194
+ */
195
+ export function decodeDataBar14Scanline(row) {
196
+ const runs = scanlineRuns(row);
197
+ for (let start = 0; start + 46 <= runs.length; start++) {
198
+ if (runs[start].dark) continue;
199
+ const candidate = runs.slice(start, start + 46);
200
+ if (start + 46 < runs.length && runs[start + 46].dark) continue;
201
+ const widths = normalizeScanlineWidths(candidate.map((run) => run.width));
202
+ if (!widths) continue;
203
+ try {
204
+ const decoded = decodeDataBar14(matrixFromRuns(widths, false));
205
+ return {
206
+ ...decoded,
207
+ format: 'gs1databar14',
208
+ gs1: true,
209
+ elements: [{ ai: '01', value: decoded.gtin, fixed: true }],
210
+ };
211
+ } catch {
212
+ // Try the next light-run candidate in the scanline.
213
+ }
214
+ }
215
+ return null;
216
+ }
@@ -4,6 +4,7 @@
4
4
  * MIT License
5
5
  *
6
6
  * Copyright (c) 2026 Sythos
7
+ * SPDX-FileCopyrightText: 2026 Sythos (https://www.sythos.net)
7
8
  *
8
9
  * Permission is hereby granted, free of charge, to any person obtaining a copy
9
10
  * of this software and associated documentation files (the "Software"), to deal
@@ -23,7 +24,6 @@
23
24
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
25
  * SOFTWARE.
25
26
  *
26
- * SPDX-FileCopyrightText: 2026 Sythos (https://www.sythos.net)
27
27
  * SPDX-License-Identifier: MIT
28
28
  *
29
29
  * Original work. No code from any other barcode implementation.
@@ -59,7 +59,7 @@ export {
59
59
  } from './tables.js';
60
60
 
61
61
  export { encodeDataBar14 } from './encoder.js';
62
- export { decodeDataBar14 } from './decoder.js';
62
+ export { decodeDataBar14, decodeDataBar14Scanline } from './decoder.js';
63
63
  export {
64
64
  DATABAR14_CHECKSUM_WEIGHTS,
65
65
  DATABAR14_FINDERS,
package/src/index.js CHANGED
@@ -84,8 +84,8 @@ export { encodeAztec, decodeAztec, detectAztec, detectAndDecodeAztec } from './a
84
84
  export * from './aztecrune/index.js';
85
85
  export { encodePDF417, decodePDF417, detectPDF417, detectAndDecodePDF417 } from './pdf417/index.js';
86
86
  export * from './compactpdf417/index.js';
87
- // DataBar currently exposes verified GS1 data-layer codecs only; physical
88
- // symbol rendering and image detection remain deliberately out of scope.
87
+ // DataBar exports include both the verified GTIN/data layer and the
88
+ // Omnidirectional/Truncated physical image path.
89
89
  export * from './databar/index.js';
90
90
  export {
91
91
  encodeMicroPDF417, decodeMicroPDF417, detectMicroPDF417, detectAndDecodeMicroPDF417,
@@ -103,6 +103,7 @@ export {
103
103
  * @property {boolean} canWrite
104
104
  * @property {boolean} canRead
105
105
  * @property {'1D' | '2D'} kind
106
+ * @property {'supplement'} [role]
106
107
  */
107
108
 
108
109
  // Writing and reading a format are separate capabilities that can land at
@@ -141,6 +142,7 @@ const aztecRuneCanDecode = typeof aztecRune.detectAndDecodeAztecRune === 'functi
141
142
  const compactPdf417CanEncode = typeof compactPdf417.encodeCompactPDF417 === 'function';
142
143
  const compactPdf417CanDecode = typeof compactPdf417.detectAndDecodeCompactPDF417 === 'function';
143
144
  const dataBarCanEncode = typeof databar.encodeDataBar14 === 'function';
145
+ const dataBarCanDecode = typeof databar.decodeDataBar14Scanline === 'function';
144
146
 
145
147
  /**
146
148
  * Every format this build supports.
@@ -159,6 +161,7 @@ export function listFormats() {
159
161
  canWrite: true,
160
162
  canRead: info.readable,
161
163
  kind: /** @type {'1D'} */ ('1D'),
164
+ ...(info.role ? { role: info.role } : {}),
162
165
  }));
163
166
 
164
167
  formats.push({
@@ -235,7 +238,7 @@ export function listFormats() {
235
238
  id: 'gs1databar14',
236
239
  label: 'GS1 DataBar Omnidirectional / Truncated',
237
240
  canWrite: dataBarCanEncode,
238
- canRead: false,
241
+ canRead: dataBarCanDecode,
239
242
  kind: /** @type {'1D'} */ ('1D'),
240
243
  });
241
244
 
@@ -344,6 +347,13 @@ export function encode(text, options = {}) {
344
347
  * @property {string} [profile] FrameQR Code profile identifier.
345
348
  * @property {boolean} [certified] Whether the profile is certified by its originator.
346
349
  * @property {object} [canvas] Canvas reservation metadata for the FrameQR Code profile.
350
+ * @property {{format:'ean2'|'ean5', text:string, parity:string, checksum?:number}} [addon] Attached EAN/UPC supplement.
351
+ * @property {boolean} [gs1] Whether the physical symbol is classified as GS1.
352
+ * @property {string} [symbologyIdentifier] GS1 symbology identifier.
353
+ * @property {Array<{ai:string,value:string,fixed?:boolean}>} [elements] Parsed GS1 Application Identifier fields.
354
+ * @property {string} [gs1ParseError] Semantic GS1 parsing error after a valid physical read.
355
+ * @property {string} [gtin] GS1 DataBar GTIN-14 payload.
356
+ * @property {boolean} [linkage] GS1 DataBar linkage flag.
347
357
  */
348
358
 
349
359
  /**
@@ -375,7 +385,8 @@ export function decode(image, options = {}) {
375
385
  const wantMicroQR = !want || want.has('microqr') || want.has('micro-qr');
376
386
  const wantRMQR = !want || want.has('rmqr') || want.has('r-mqr') || want.has('rectangular-micro-qr');
377
387
  const wantFrameQR = !want || want.has('frameqr') || want.has('frame-qr') || want.has('canvas-qr');
378
- const wantOneD = !want || [...want].some((f) => f in ONED_FORMATS);
388
+ const oneDAliases = new Set(['gs1databar14', 'databar', 'gs1-databar14']);
389
+ const wantOneD = !want || [...want].some((f) => f in ONED_FORMATS || oneDAliases.has(f));
379
390
 
380
391
  const source = LuminanceSource.fromImageData(image);
381
392
  const results = [];
@@ -499,9 +510,23 @@ export function decode(image, options = {}) {
499
510
  }
500
511
 
501
512
  if (wantOneD) {
502
- const oneDFormats = want ? [...want].filter((f) => f in ONED_FORMATS) : null;
503
- for (const found of decodeOneD(bits, { formats: oneDFormats, tryHarder })) {
504
- results.push({ text: found.text, format: found.format });
513
+ const oneDFormats = want
514
+ ? [...want].filter((f) => f in ONED_FORMATS || oneDAliases.has(f))
515
+ : null;
516
+ for (const found of decodeOneD(bits, { ...options, formats: oneDFormats, tryHarder })) {
517
+ const { row, ...publicFound } = found;
518
+ void row;
519
+ if (publicFound.gs1) {
520
+ const semanticText = publicFound.format === 'gs1databar14'
521
+ ? `01${publicFound.gtin ?? publicFound.text}`
522
+ : publicFound.text;
523
+ try {
524
+ publicFound.elements = databar.decodeGS1ElementString(semanticText);
525
+ } catch (error) {
526
+ publicFound.gs1ParseError = error instanceof Error ? error.message : String(error);
527
+ }
528
+ }
529
+ results.push(publicFound);
505
530
  }
506
531
  }
507
532
 
package/src/oned/index.js CHANGED
@@ -4,6 +4,7 @@
4
4
  * MIT License
5
5
  *
6
6
  * Copyright (c) 2026 Sythos
7
+ * SPDX-FileCopyrightText: 2026 Sythos (https://www.sythos.net)
7
8
  *
8
9
  * Permission is hereby granted, free of charge, to any person obtaining a copy
9
10
  * of this software and associated documentation files (the "Software"), to deal
@@ -23,7 +24,6 @@
23
24
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
25
  * SOFTWARE.
25
26
  *
26
- * SPDX-FileCopyrightText: 2026 Sythos (https://www.sythos.net)
27
27
  * SPDX-License-Identifier: MIT
28
28
  *
29
29
  * Original work. No code from any other barcode implementation.
@@ -56,6 +56,7 @@ export {
56
56
 
57
57
  export {
58
58
  decodeOneD, decodeOneDStrict,
59
+ decodeCode11, decodeMSI,
59
60
  patternVariance, recordPattern, toNarrowWidePattern,
60
61
  } from './reader.js';
61
62
 
@@ -77,7 +78,7 @@ import { encodeEAN2, encodeEAN5 } from './addons.js';
77
78
  * so the two lists legitimately differ and the API says so rather than
78
79
  * failing at runtime.
79
80
  *
80
- * @type {Record<string, {encode: Function, readable: boolean, label: string}>}
81
+ * @type {Record<string, {encode: Function, readable: boolean, label: string, role?: string}>}
81
82
  */
82
83
  export const ONED_FORMATS = {
83
84
  ean13: { encode: encodeEAN13, readable: true, label: 'EAN-13' },
@@ -96,12 +97,11 @@ export const ONED_FORMATS = {
96
97
  itf: { encode: encodeITF, readable: true, label: 'ITF (Interleaved 2 of 5)' },
97
98
  itf14: { encode: encodeITF14, readable: true, label: 'ITF-14' },
98
99
  codabar: { encode: encodeCodabar, readable: true, label: 'Codabar' },
99
- code11: { encode: encodeCode11, readable: false, label: 'Code 11' },
100
- msi: { encode: encodeMSI, readable: false, label: 'MSI Plessey' },
100
+ code11: { encode: encodeCode11, readable: true, label: 'Code 11' },
101
+ msi: { encode: encodeMSI, readable: true, label: 'MSI Plessey' },
101
102
  pharmacode: { encode: encodePharmacode, readable: false, label: 'Pharmacode' },
102
- // Supplements are writable standalone matrices, but their reader is
103
- // intentionally exposed through the EAN/UPC add-on helpers rather than the
104
- // generic one-dimensional scan pipeline.
105
- ean2: { encode: encodeEAN2, readable: false, label: 'EAN-2 supplement' },
106
- ean5: { encode: encodeEAN5, readable: false, label: 'EAN-5 supplement' },
103
+ // Supplements are reported as readable capabilities, but the image reader
104
+ // only accepts them when attached to a validated EAN/UPC parent symbol.
105
+ ean2: { encode: encodeEAN2, readable: true, role: 'supplement', label: 'EAN-2 supplement' },
106
+ ean5: { encode: encodeEAN5, readable: true, role: 'supplement', label: 'EAN-5 supplement' },
107
107
  };