@sythos/js_barcode_universal 0.1.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 +215 -0
- package/NOTICE.md +106 -0
- package/README.md +433 -0
- package/bundle/sythos-barcode.esm.js +7998 -0
- package/bundle/sythos-barcode.js +7948 -0
- package/examples/create.html +731 -0
- package/examples/read.html +341 -0
- package/licenses/README.md +42 -0
- package/licenses/codabar.license +74 -0
- package/licenses/code-11.license +69 -0
- package/licenses/code-128.license +69 -0
- package/licenses/code-39.license +70 -0
- package/licenses/code-93.license +71 -0
- package/licenses/ean-13.license +70 -0
- package/licenses/ean-8.license +70 -0
- package/licenses/gs1-128.license +71 -0
- package/licenses/isbn.license +76 -0
- package/licenses/itf-14.license +69 -0
- package/licenses/itf.license +70 -0
- package/licenses/msi-plessey.license +72 -0
- package/licenses/pharmacode.license +71 -0
- package/licenses/qr-code.license +75 -0
- package/licenses/upc-a.license +72 -0
- package/licenses/upc-e.license +69 -0
- package/package.json +89 -0
- package/src/core/bit-buffer.js +174 -0
- package/src/core/bit-matrix.js +241 -0
- package/src/core/errors.js +61 -0
- package/src/core/galois-field.js +204 -0
- package/src/core/index.js +56 -0
- package/src/core/reed-solomon.js +313 -0
- package/src/image/binarizer.js +270 -0
- package/src/image/grid-sampler.js +164 -0
- package/src/image/index.js +40 -0
- package/src/image/luminance.js +196 -0
- package/src/image/perspective.js +195 -0
- package/src/index.js +240 -0
- package/src/oned/index.js +89 -0
- package/src/oned/patterns.js +384 -0
- package/src/oned/reader.js +918 -0
- package/src/oned/writers.js +741 -0
- package/src/qr/decoder.js +575 -0
- package/src/qr/detector.js +630 -0
- package/src/qr/encoder.js +958 -0
- package/src/qr/index.js +44 -0
- package/src/qr/tables.js +737 -0
- package/src/render/image-data.js +125 -0
- package/src/render/index.js +130 -0
- package/src/render/options.js +160 -0
- package/src/render/png.js +295 -0
- package/src/render/svg.js +120 -0
- package/src/render/webgl.js +206 -0
- package/src/render/webgpu.js +369 -0
package/src/index.js
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Sythos Barcode Suite
|
|
3
|
+
*
|
|
4
|
+
* MIT License
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) 2026 Sythos
|
|
7
|
+
*
|
|
8
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
9
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
10
|
+
* in the Software without restriction, including without limitation the rights
|
|
11
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
12
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
13
|
+
* furnished to do so, subject to the following conditions:
|
|
14
|
+
*
|
|
15
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
16
|
+
* copies or substantial portions of the Software.
|
|
17
|
+
*
|
|
18
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
19
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
20
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
21
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
22
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
23
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
24
|
+
* SOFTWARE.
|
|
25
|
+
*
|
|
26
|
+
* SPDX-License-Identifier: MIT
|
|
27
|
+
*
|
|
28
|
+
* Original work. No code from any other barcode implementation.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Sythos Barcode Suite — public API.
|
|
33
|
+
*
|
|
34
|
+
* Two functions carry the whole surface:
|
|
35
|
+
*
|
|
36
|
+
* encode(text, { format }) -> BitMatrix
|
|
37
|
+
* decode(image, { formats }) -> Result[]
|
|
38
|
+
*
|
|
39
|
+
* Everything else is a renderer or a format-specific escape hatch. The core is
|
|
40
|
+
* free of I/O and of any platform assumption: images go in as
|
|
41
|
+
* `{ data, width, height }` with RGBA bytes, which is exactly what `ImageData`
|
|
42
|
+
* is, so a canvas, an `OffscreenCanvas`, sharp, jimp and node-canvas all work
|
|
43
|
+
* without an adapter.
|
|
44
|
+
*
|
|
45
|
+
* @module @sythos/js_barcode_universal
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
import { BitMatrix } from './core/bit-matrix.js';
|
|
49
|
+
import { EncodeError, NotFoundError } from './core/errors.js';
|
|
50
|
+
import { LuminanceSource } from './image/luminance.js';
|
|
51
|
+
import { binarize } from './image/binarizer.js';
|
|
52
|
+
import { ONED_FORMATS } from './oned/index.js';
|
|
53
|
+
import { decodeOneD } from './oned/reader.js';
|
|
54
|
+
import * as qr from './qr/index.js';
|
|
55
|
+
|
|
56
|
+
export { BitMatrix };
|
|
57
|
+
export {
|
|
58
|
+
BarcodeError, EncodeError, NotFoundError, FormatError, ChecksumError,
|
|
59
|
+
} from './core/errors.js';
|
|
60
|
+
export { LuminanceSource } from './image/luminance.js';
|
|
61
|
+
export { binarize, binarizeGlobal, binarizeHybrid } from './image/binarizer.js';
|
|
62
|
+
export * from './oned/index.js';
|
|
63
|
+
export { toSVG, toSVGDataURI } from './render/svg.js';
|
|
64
|
+
export { toImageData, toCanvas } from './render/image-data.js';
|
|
65
|
+
export { toPNG, toPNGDataURI } from './render/png.js';
|
|
66
|
+
export { renderToCanvasAuto, isWebGL2Available } from './render/index.js';
|
|
67
|
+
export { renderToCanvasAutoAsync, isWebGPUAvailable } from './render/index.js';
|
|
68
|
+
export { encodeQR, decodeQR, detectQR, detectAndDecodeQR } from './qr/index.js';
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @typedef {object} FormatInfo
|
|
72
|
+
* @property {string} id
|
|
73
|
+
* @property {string} label
|
|
74
|
+
* @property {boolean} canWrite
|
|
75
|
+
* @property {boolean} canRead
|
|
76
|
+
* @property {'1D' | '2D'} kind
|
|
77
|
+
*/
|
|
78
|
+
|
|
79
|
+
// Writing and reading a format are separate capabilities that can land at
|
|
80
|
+
// different times, so they are reported separately rather than collapsed into
|
|
81
|
+
// one "supported" flag that would be wrong in one direction or the other.
|
|
82
|
+
//
|
|
83
|
+
// Capability is probed rather than declared, so this stays correct whether the
|
|
84
|
+
// QR module is the full implementation or a stand-in: a module may opt out
|
|
85
|
+
// explicitly with QR_CAN_ENCODE/QR_CAN_DECODE, and is otherwise taken at face
|
|
86
|
+
// value.
|
|
87
|
+
const qrPresent = qr.QR_PLACEHOLDER !== true;
|
|
88
|
+
const qrCanEncode = qrPresent &&
|
|
89
|
+
typeof qr.encodeQR === 'function' && qr.QR_CAN_ENCODE !== false;
|
|
90
|
+
const qrCanDecode = qrPresent &&
|
|
91
|
+
typeof qr.detectAndDecodeQR === 'function' && qr.QR_CAN_DECODE !== false;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Every format this build supports.
|
|
95
|
+
*
|
|
96
|
+
* Writing and reading are listed separately on purpose. Writing a symbology is
|
|
97
|
+
* a table lookup; reading one needs a detector that finds it in a photograph,
|
|
98
|
+
* which is far more work. The two lists legitimately differ, and saying so
|
|
99
|
+
* here is better than failing at call time.
|
|
100
|
+
*
|
|
101
|
+
* @returns {FormatInfo[]}
|
|
102
|
+
*/
|
|
103
|
+
export function listFormats() {
|
|
104
|
+
const formats = Object.entries(ONED_FORMATS).map(([id, info]) => ({
|
|
105
|
+
id,
|
|
106
|
+
label: info.label,
|
|
107
|
+
canWrite: true,
|
|
108
|
+
canRead: info.readable,
|
|
109
|
+
kind: /** @type {'1D'} */ ('1D'),
|
|
110
|
+
}));
|
|
111
|
+
|
|
112
|
+
formats.push({
|
|
113
|
+
id: 'qr',
|
|
114
|
+
label: 'QR Code',
|
|
115
|
+
canWrite: qrCanEncode,
|
|
116
|
+
canRead: qrCanDecode,
|
|
117
|
+
kind: /** @type {'2D'} */ ('2D'),
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
return formats;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Encode a payload into a barcode matrix.
|
|
125
|
+
*
|
|
126
|
+
* The result is a `BitMatrix` where a set bit is a dark module, with no quiet
|
|
127
|
+
* zone — the renderers add that, because the right margin depends on the
|
|
128
|
+
* output medium. Linear symbols come back one module tall; height is a
|
|
129
|
+
* rendering decision, not an encoding one.
|
|
130
|
+
*
|
|
131
|
+
* @param {string | number} text
|
|
132
|
+
* @param {object} [options]
|
|
133
|
+
* @param {string} [options.format] Format id. Default 'qr'.
|
|
134
|
+
* @param {'L'|'M'|'Q'|'H'} [options.ecc] QR error-correction level.
|
|
135
|
+
* @param {number} [options.version] QR version, 1-40. Auto if omitted.
|
|
136
|
+
* @param {boolean} [options.checkDigit] Append a check digit, where optional.
|
|
137
|
+
* @param {boolean} [options.fullAscii] Code 39 extended encoding.
|
|
138
|
+
* @param {boolean} [options.gs1] Emit a leading FNC1.
|
|
139
|
+
* @returns {BitMatrix}
|
|
140
|
+
*/
|
|
141
|
+
export function encode(text, options = {}) {
|
|
142
|
+
const format = String(options.format ?? 'qr').toLowerCase();
|
|
143
|
+
const value = typeof text === 'number' ? String(text) : text;
|
|
144
|
+
|
|
145
|
+
if (format === 'qr' || format === 'qrcode') {
|
|
146
|
+
return qr.encodeQR(value, options);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const entry = ONED_FORMATS[format];
|
|
150
|
+
if (!entry) {
|
|
151
|
+
const known = [...Object.keys(ONED_FORMATS), 'qr'].join(', ');
|
|
152
|
+
throw new EncodeError(`Unknown format "${format}". Known formats: ${known}`);
|
|
153
|
+
}
|
|
154
|
+
return entry.encode(value, options);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* @typedef {object} DecodeResult
|
|
159
|
+
* @property {string} text
|
|
160
|
+
* @property {string} format
|
|
161
|
+
* @property {Uint8Array} [bytes] Raw payload, before text decoding.
|
|
162
|
+
* @property {number} [version] QR version.
|
|
163
|
+
* @property {string} [ecc] QR error-correction level.
|
|
164
|
+
*/
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Find and decode every barcode in an image.
|
|
168
|
+
*
|
|
169
|
+
* Returns an array, empty when nothing is found — an image with no barcode is
|
|
170
|
+
* an ordinary outcome for a camera frame, not an error, and throwing would
|
|
171
|
+
* make the common scanning loop a try/catch.
|
|
172
|
+
*
|
|
173
|
+
* @param {{data: Uint8ClampedArray|Uint8Array|number[], width: number, height: number}} image
|
|
174
|
+
* @param {object} [options]
|
|
175
|
+
* @param {string[]} [options.formats] Restrict to these format ids.
|
|
176
|
+
* @param {boolean} [options.tryHarder] Retry inverted and rotated. Default true.
|
|
177
|
+
* @param {'global'|'hybrid'|'auto'} [options.binarizer]
|
|
178
|
+
* @returns {DecodeResult[]}
|
|
179
|
+
*/
|
|
180
|
+
export function decode(image, options = {}) {
|
|
181
|
+
const { formats = null, tryHarder = true, binarizer = 'auto' } = options;
|
|
182
|
+
const want = formats ? new Set(formats.map((f) => f.toLowerCase())) : null;
|
|
183
|
+
const wantQR = !want || want.has('qr') || want.has('qrcode');
|
|
184
|
+
const wantOneD = !want || [...want].some((f) => f in ONED_FORMATS);
|
|
185
|
+
|
|
186
|
+
const source = LuminanceSource.fromImageData(image);
|
|
187
|
+
const results = [];
|
|
188
|
+
|
|
189
|
+
// Light-on-dark symbols are common on screens and packaging, so a second
|
|
190
|
+
// inverted pass is worth the cost when the first finds nothing.
|
|
191
|
+
const passes = tryHarder ? [source, source.invert()] : [source];
|
|
192
|
+
|
|
193
|
+
for (const pass of passes) {
|
|
194
|
+
const bits = binarize(pass, binarizer);
|
|
195
|
+
|
|
196
|
+
if (wantQR && qrCanDecode) {
|
|
197
|
+
try {
|
|
198
|
+
for (const found of qr.detectAndDecodeQR(bits)) {
|
|
199
|
+
results.push({ ...found, format: 'qr' });
|
|
200
|
+
}
|
|
201
|
+
} catch {
|
|
202
|
+
/* no QR in this pass */
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (wantOneD) {
|
|
207
|
+
const oneDFormats = want ? [...want].filter((f) => f in ONED_FORMATS) : null;
|
|
208
|
+
for (const found of decodeOneD(bits, { formats: oneDFormats, tryHarder })) {
|
|
209
|
+
results.push({ text: found.text, format: found.format });
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (results.length > 0) break;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// De-duplicate: the same symbol is often read on several scan rows.
|
|
217
|
+
const seen = new Set();
|
|
218
|
+
return results.filter((r) => {
|
|
219
|
+
const key = `${r.format}:${r.text}`;
|
|
220
|
+
if (seen.has(key)) return false;
|
|
221
|
+
seen.add(key);
|
|
222
|
+
return true;
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Decode, or throw if nothing is found.
|
|
228
|
+
*
|
|
229
|
+
* @param {{data: Uint8ClampedArray|Uint8Array|number[], width: number, height: number}} image
|
|
230
|
+
* @param {object} [options]
|
|
231
|
+
* @returns {DecodeResult}
|
|
232
|
+
*/
|
|
233
|
+
export function decodeStrict(image, options) {
|
|
234
|
+
const results = decode(image, options);
|
|
235
|
+
if (results.length === 0) throw new NotFoundError('No barcode found in image');
|
|
236
|
+
return results[0];
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** Library version, matching package.json. */
|
|
240
|
+
export const VERSION = '0.1.0';
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Sythos Barcode Suite
|
|
3
|
+
*
|
|
4
|
+
* MIT License
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) 2026 Sythos
|
|
7
|
+
*
|
|
8
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
9
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
10
|
+
* in the Software without restriction, including without limitation the rights
|
|
11
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
12
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
13
|
+
* furnished to do so, subject to the following conditions:
|
|
14
|
+
*
|
|
15
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
16
|
+
* copies or substantial portions of the Software.
|
|
17
|
+
*
|
|
18
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
19
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
20
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
21
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
22
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
23
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
24
|
+
* SOFTWARE.
|
|
25
|
+
*
|
|
26
|
+
* SPDX-License-Identifier: MIT
|
|
27
|
+
*
|
|
28
|
+
* Original work. No code from any other barcode implementation.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Linear symbologies.
|
|
33
|
+
*
|
|
34
|
+
* @module oned
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
export {
|
|
38
|
+
encodeEAN13, encodeEAN8, encodeUPCA, encodeUPCE, encodeISBN,
|
|
39
|
+
encodeCode39, encodeCode93, encodeCode128,
|
|
40
|
+
encodeITF, encodeITF14, encodeCodabar, encodeCode11,
|
|
41
|
+
encodeMSI, encodePharmacode,
|
|
42
|
+
ean13CheckDigit,
|
|
43
|
+
} from './writers.js';
|
|
44
|
+
|
|
45
|
+
export {
|
|
46
|
+
decodeOneD, decodeOneDStrict,
|
|
47
|
+
patternVariance, recordPattern, toNarrowWidePattern,
|
|
48
|
+
} from './reader.js';
|
|
49
|
+
|
|
50
|
+
export { validateTables } from './patterns.js';
|
|
51
|
+
|
|
52
|
+
import {
|
|
53
|
+
encodeEAN13, encodeEAN8, encodeUPCA, encodeUPCE, encodeISBN,
|
|
54
|
+
encodeCode39, encodeCode93, encodeCode128,
|
|
55
|
+
encodeITF, encodeITF14, encodeCodabar, encodeCode11,
|
|
56
|
+
encodeMSI, encodePharmacode,
|
|
57
|
+
} from './writers.js';
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Writers by format id, for the top-level `encode()` dispatcher.
|
|
61
|
+
*
|
|
62
|
+
* `readable` marks the formats this suite can also decode. Writing is a table
|
|
63
|
+
* lookup and easy to support broadly; reading needs a detector per symbology,
|
|
64
|
+
* so the two lists legitimately differ and the API says so rather than
|
|
65
|
+
* failing at runtime.
|
|
66
|
+
*
|
|
67
|
+
* @type {Record<string, {encode: Function, readable: boolean, label: string}>}
|
|
68
|
+
*/
|
|
69
|
+
export const ONED_FORMATS = {
|
|
70
|
+
ean13: { encode: encodeEAN13, readable: true, label: 'EAN-13' },
|
|
71
|
+
ean8: { encode: encodeEAN8, readable: true, label: 'EAN-8' },
|
|
72
|
+
upca: { encode: encodeUPCA, readable: true, label: 'UPC-A' },
|
|
73
|
+
isbn: { encode: encodeISBN, readable: true, label: 'ISBN (Bookland EAN-13)' },
|
|
74
|
+
upce: { encode: encodeUPCE, readable: true, label: 'UPC-E' },
|
|
75
|
+
code128: { encode: encodeCode128, readable: true, label: 'Code 128' },
|
|
76
|
+
gs1128: {
|
|
77
|
+
encode: (v, o) => encodeCode128(v, { ...o, gs1: true }),
|
|
78
|
+
readable: true,
|
|
79
|
+
label: 'GS1-128',
|
|
80
|
+
},
|
|
81
|
+
code39: { encode: encodeCode39, readable: true, label: 'Code 39' },
|
|
82
|
+
code93: { encode: encodeCode93, readable: true, label: 'Code 93' },
|
|
83
|
+
itf: { encode: encodeITF, readable: true, label: 'ITF (Interleaved 2 of 5)' },
|
|
84
|
+
itf14: { encode: encodeITF14, readable: true, label: 'ITF-14' },
|
|
85
|
+
codabar: { encode: encodeCodabar, readable: true, label: 'Codabar' },
|
|
86
|
+
code11: { encode: encodeCode11, readable: false, label: 'Code 11' },
|
|
87
|
+
msi: { encode: encodeMSI, readable: false, label: 'MSI Plessey' },
|
|
88
|
+
pharmacode: { encode: encodePharmacode, readable: false, label: 'Pharmacode' },
|
|
89
|
+
};
|