@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
|
@@ -0,0 +1,741 @@
|
|
|
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 barcode writers.
|
|
33
|
+
*
|
|
34
|
+
* Every writer returns a `BitMatrix` one module tall. Height is a rendering
|
|
35
|
+
* decision, not an encoding one — a linear symbol carries no information
|
|
36
|
+
* vertically, which is exactly why it survives a laser line that crosses it
|
|
37
|
+
* anywhere. The renderers stretch it via the `barHeight` option.
|
|
38
|
+
*
|
|
39
|
+
* @module oned/writers
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
import { BitMatrix } from '../core/bit-matrix.js';
|
|
43
|
+
import { EncodeError } from '../core/errors.js';
|
|
44
|
+
import {
|
|
45
|
+
EAN_L, EAN_G, EAN_R, EAN13_PARITY, UPCE_PARITY,
|
|
46
|
+
EAN_START_END, EAN_MIDDLE, UPCE_END,
|
|
47
|
+
CODE39, CODE39_CHECK_SET, CODE39_EXTENDED,
|
|
48
|
+
CODE93, CODE93_VALUES, CODE93_START_STOP,
|
|
49
|
+
CODE128, CODE128_START_B, CODE128_START_C,
|
|
50
|
+
CODE128_STOP, CODE128_FNC1, CODE128_CODE_A, CODE128_CODE_B, CODE128_CODE_C,
|
|
51
|
+
ITF, CODABAR, CODABAR_START_STOP, CODE11, CODE11_START_STOP,
|
|
52
|
+
MSI_BIT, MSI_START, MSI_STOP,
|
|
53
|
+
} from './patterns.js';
|
|
54
|
+
|
|
55
|
+
/* ------------------------------------------------------------------ *
|
|
56
|
+
* Shared helpers
|
|
57
|
+
* ------------------------------------------------------------------ */
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Expand an n/w width pattern into a module string, starting with a bar.
|
|
61
|
+
*
|
|
62
|
+
* @param {string} pattern Characters 'n' and 'w'.
|
|
63
|
+
* @param {number} [wide] Modules per wide element.
|
|
64
|
+
* @returns {string} Module string, '1' = dark.
|
|
65
|
+
*/
|
|
66
|
+
function expandNarrowWide(pattern, wide = 3) {
|
|
67
|
+
let out = '';
|
|
68
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
69
|
+
const width = pattern[i] === 'w' ? wide : 1;
|
|
70
|
+
out += (i % 2 === 0 ? '1' : '0').repeat(width);
|
|
71
|
+
}
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Expand a digit-width pattern into a module string, starting with a bar.
|
|
77
|
+
*
|
|
78
|
+
* @param {string} pattern Characters '1'..'4'.
|
|
79
|
+
* @returns {string}
|
|
80
|
+
*/
|
|
81
|
+
function expandWidths(pattern) {
|
|
82
|
+
let out = '';
|
|
83
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
84
|
+
out += (i % 2 === 0 ? '1' : '0').repeat(Number(pattern[i]));
|
|
85
|
+
}
|
|
86
|
+
return out;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* @param {string} modules
|
|
91
|
+
* @returns {BitMatrix} One row tall.
|
|
92
|
+
*/
|
|
93
|
+
function toMatrix(modules) {
|
|
94
|
+
const m = new BitMatrix(modules.length, 1);
|
|
95
|
+
for (let x = 0; x < modules.length; x++) {
|
|
96
|
+
if (modules[x] === '1') m.set(x, 0);
|
|
97
|
+
}
|
|
98
|
+
return m;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* @param {string} value
|
|
103
|
+
* @param {string} format
|
|
104
|
+
*/
|
|
105
|
+
function requireDigits(value, format) {
|
|
106
|
+
if (!/^[0-9]+$/.test(value)) {
|
|
107
|
+
throw new EncodeError(`${format}: payload must be digits only, got "${value}"`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Modulo-10 check digit for the EAN/UPC family.
|
|
113
|
+
*
|
|
114
|
+
* Weights alternate 3 and 1, with the digit immediately left of the check
|
|
115
|
+
* position weighted 3. Anchoring from the right rather than the left makes one
|
|
116
|
+
* routine correct for EAN-8, EAN-13 and UPC-A alike, despite their different
|
|
117
|
+
* payload lengths.
|
|
118
|
+
*
|
|
119
|
+
* @param {string} payload Digits, excluding the check digit.
|
|
120
|
+
* @returns {number}
|
|
121
|
+
*/
|
|
122
|
+
export function ean13CheckDigit(payload) {
|
|
123
|
+
let sum = 0;
|
|
124
|
+
for (let i = 0; i < payload.length; i++) {
|
|
125
|
+
const fromRight = payload.length - 1 - i;
|
|
126
|
+
sum += Number(payload[i]) * (fromRight % 2 === 0 ? 3 : 1);
|
|
127
|
+
}
|
|
128
|
+
return (10 - (sum % 10)) % 10;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/* ------------------------------------------------------------------ *
|
|
132
|
+
* EAN / UPC
|
|
133
|
+
* ------------------------------------------------------------------ */
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* EAN-13. Accepts 12 digits (check digit appended) or 13 (verified).
|
|
137
|
+
*
|
|
138
|
+
* @param {string} value
|
|
139
|
+
* @returns {BitMatrix}
|
|
140
|
+
*/
|
|
141
|
+
export function encodeEAN13(value) {
|
|
142
|
+
requireDigits(value, 'EAN-13');
|
|
143
|
+
let digits = value;
|
|
144
|
+
if (digits.length === 12) {
|
|
145
|
+
digits += String(ean13CheckDigit(digits));
|
|
146
|
+
} else if (digits.length === 13) {
|
|
147
|
+
const expected = ean13CheckDigit(digits.slice(0, 12));
|
|
148
|
+
if (Number(digits[12]) !== expected) {
|
|
149
|
+
throw new EncodeError(`EAN-13: check digit is ${digits[12]}, expected ${expected}`);
|
|
150
|
+
}
|
|
151
|
+
} else {
|
|
152
|
+
throw new EncodeError(`EAN-13: needs 12 or 13 digits, got ${digits.length}`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const parity = EAN13_PARITY[Number(digits[0])];
|
|
156
|
+
let modules = EAN_START_END;
|
|
157
|
+
for (let i = 0; i < 6; i++) {
|
|
158
|
+
const d = Number(digits[i + 1]);
|
|
159
|
+
modules += parity[i] === 'L' ? EAN_L[d] : EAN_G[d];
|
|
160
|
+
}
|
|
161
|
+
modules += EAN_MIDDLE;
|
|
162
|
+
for (let i = 7; i < 13; i++) modules += EAN_R[Number(digits[i])];
|
|
163
|
+
modules += EAN_START_END;
|
|
164
|
+
|
|
165
|
+
return toMatrix(modules);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* EAN-8. Accepts 7 digits (check digit appended) or 8 (verified).
|
|
170
|
+
*
|
|
171
|
+
* @param {string} value
|
|
172
|
+
* @returns {BitMatrix}
|
|
173
|
+
*/
|
|
174
|
+
export function encodeEAN8(value) {
|
|
175
|
+
requireDigits(value, 'EAN-8');
|
|
176
|
+
let digits = value;
|
|
177
|
+
if (digits.length === 7) {
|
|
178
|
+
digits += String(ean13CheckDigit(digits));
|
|
179
|
+
} else if (digits.length === 8) {
|
|
180
|
+
const expected = ean13CheckDigit(digits.slice(0, 7));
|
|
181
|
+
if (Number(digits[7]) !== expected) {
|
|
182
|
+
throw new EncodeError(`EAN-8: check digit is ${digits[7]}, expected ${expected}`);
|
|
183
|
+
}
|
|
184
|
+
} else {
|
|
185
|
+
throw new EncodeError(`EAN-8: needs 7 or 8 digits, got ${digits.length}`);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
let modules = EAN_START_END;
|
|
189
|
+
for (let i = 0; i < 4; i++) modules += EAN_L[Number(digits[i])];
|
|
190
|
+
modules += EAN_MIDDLE;
|
|
191
|
+
for (let i = 4; i < 8; i++) modules += EAN_R[Number(digits[i])];
|
|
192
|
+
modules += EAN_START_END;
|
|
193
|
+
|
|
194
|
+
return toMatrix(modules);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* ISBN, as its printed EAN-13 ("Bookland") symbol.
|
|
199
|
+
*
|
|
200
|
+
* ISBN is not a separate symbology — an ISBN barcode *is* an EAN-13 carrying a
|
|
201
|
+
* 978 or 979 prefix. What ISBN adds is its own numbering rules, and those are
|
|
202
|
+
* worth enforcing here: an ISBN-10 uses a modulo-**11** check digit, in which
|
|
203
|
+
* the value ten is written `X`. That is a different calculation from the
|
|
204
|
+
* modulo-10 check the EAN symbol will carry. Passing an ISBN-10 straight
|
|
205
|
+
* through would produce a perfectly scannable symbol encoding the wrong
|
|
206
|
+
* number, so the two checks are kept distinct and the digit is recomputed.
|
|
207
|
+
*
|
|
208
|
+
* Accepts ISBN-10 or ISBN-13, with or without hyphens and spaces.
|
|
209
|
+
*
|
|
210
|
+
* @param {string} value
|
|
211
|
+
* @returns {BitMatrix}
|
|
212
|
+
*/
|
|
213
|
+
export function encodeISBN(value) {
|
|
214
|
+
const cleaned = String(value).replace(/[\s-]/g, '').toUpperCase();
|
|
215
|
+
|
|
216
|
+
if (/^[0-9]{9}[0-9X]$/.test(cleaned)) {
|
|
217
|
+
// ISBN-10: verify its modulo-11 check, then convert to the 978 form.
|
|
218
|
+
let sum = 0;
|
|
219
|
+
for (let i = 0; i < 9; i++) sum += Number(cleaned[i]) * (10 - i);
|
|
220
|
+
sum += cleaned[9] === 'X' ? 10 : Number(cleaned[9]);
|
|
221
|
+
if (sum % 11 !== 0) {
|
|
222
|
+
throw new EncodeError(`ISBN-10: check digit "${cleaned[9]}" fails the modulo-11 test`);
|
|
223
|
+
}
|
|
224
|
+
// The check digit does not carry over — encodeEAN13 computes the new one.
|
|
225
|
+
return encodeEAN13('978' + cleaned.slice(0, 9));
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (/^[0-9]{12,13}$/.test(cleaned)) {
|
|
229
|
+
const prefix = cleaned.slice(0, 3);
|
|
230
|
+
if (prefix !== '978' && prefix !== '979') {
|
|
231
|
+
throw new EncodeError(`ISBN-13 must begin with 978 or 979, got ${prefix}`);
|
|
232
|
+
}
|
|
233
|
+
// encodeEAN13 appends the check digit at 12, or verifies it at 13.
|
|
234
|
+
return encodeEAN13(cleaned);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
throw new EncodeError(
|
|
238
|
+
`ISBN: expected 10 or 13 digits, hyphens optional — got "${value}"`
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* UPC-A. Structurally an EAN-13 whose first digit is zero.
|
|
244
|
+
*
|
|
245
|
+
* @param {string} value 11 or 12 digits.
|
|
246
|
+
* @returns {BitMatrix}
|
|
247
|
+
*/
|
|
248
|
+
export function encodeUPCA(value) {
|
|
249
|
+
requireDigits(value, 'UPC-A');
|
|
250
|
+
if (value.length !== 11 && value.length !== 12) {
|
|
251
|
+
throw new EncodeError(`UPC-A: needs 11 or 12 digits, got ${value.length}`);
|
|
252
|
+
}
|
|
253
|
+
return encodeEAN13('0' + value);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Expand a UPC-E body to the 11 digits preceding the check digit.
|
|
258
|
+
*
|
|
259
|
+
* @param {number} system Number system, 0 or 1.
|
|
260
|
+
* @param {string} body 6 digits.
|
|
261
|
+
* @returns {string} 11 digits.
|
|
262
|
+
*/
|
|
263
|
+
export function upceToUpcaBody(system, body) {
|
|
264
|
+
const d = body;
|
|
265
|
+
const last = Number(d[5]);
|
|
266
|
+
let middle;
|
|
267
|
+
if (last <= 2) {
|
|
268
|
+
middle = d.slice(0, 2) + String(last) + '0000' + d.slice(2, 5);
|
|
269
|
+
} else if (last === 3) {
|
|
270
|
+
middle = d.slice(0, 3) + '00000' + d.slice(3, 5);
|
|
271
|
+
} else if (last === 4) {
|
|
272
|
+
middle = d.slice(0, 4) + '00000' + d[4];
|
|
273
|
+
} else {
|
|
274
|
+
middle = d.slice(0, 5) + '0000' + String(last);
|
|
275
|
+
}
|
|
276
|
+
return String(system) + middle;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* UPC-E, the zero-suppressed form of UPC-A.
|
|
281
|
+
*
|
|
282
|
+
* @param {string} value 6 digits (system 0 assumed), 7 (system + body), or 8 (with check).
|
|
283
|
+
* @returns {BitMatrix}
|
|
284
|
+
*/
|
|
285
|
+
export function encodeUPCE(value) {
|
|
286
|
+
requireDigits(value, 'UPC-E');
|
|
287
|
+
|
|
288
|
+
let system, body, check;
|
|
289
|
+
if (value.length === 6) {
|
|
290
|
+
system = 0;
|
|
291
|
+
body = value;
|
|
292
|
+
check = ean13CheckDigit(upceToUpcaBody(0, body));
|
|
293
|
+
} else if (value.length === 7) {
|
|
294
|
+
system = Number(value[0]);
|
|
295
|
+
body = value.slice(1);
|
|
296
|
+
check = ean13CheckDigit(upceToUpcaBody(system, body));
|
|
297
|
+
} else if (value.length === 8) {
|
|
298
|
+
system = Number(value[0]);
|
|
299
|
+
body = value.slice(1, 7);
|
|
300
|
+
check = Number(value[7]);
|
|
301
|
+
} else {
|
|
302
|
+
throw new EncodeError(`UPC-E: needs 6, 7 or 8 digits, got ${value.length}`);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (system !== 0 && system !== 1) {
|
|
306
|
+
throw new EncodeError(`UPC-E: number system must be 0 or 1, got ${system}`);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const parity = UPCE_PARITY[check];
|
|
310
|
+
let modules = EAN_START_END;
|
|
311
|
+
for (let i = 0; i < 6; i++) {
|
|
312
|
+
const d = Number(body[i]);
|
|
313
|
+
// Number system 1 inverts the entire parity pattern relative to system 0.
|
|
314
|
+
const even = system === 0 ? parity[i] === 'E' : parity[i] === 'O';
|
|
315
|
+
modules += even ? EAN_G[d] : EAN_L[d];
|
|
316
|
+
}
|
|
317
|
+
modules += UPCE_END;
|
|
318
|
+
|
|
319
|
+
return toMatrix(modules);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/* ------------------------------------------------------------------ *
|
|
323
|
+
* Code 39
|
|
324
|
+
* ------------------------------------------------------------------ */
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Code 39.
|
|
328
|
+
*
|
|
329
|
+
* @param {string} value
|
|
330
|
+
* @param {object} [options]
|
|
331
|
+
* @param {boolean} [options.checkDigit] Append the modulo-43 check character.
|
|
332
|
+
* @param {boolean} [options.fullAscii] Escape characters outside the native set.
|
|
333
|
+
* @param {number} [options.wideRatio] Wide-to-narrow ratio, 2 or 3.
|
|
334
|
+
* @returns {BitMatrix}
|
|
335
|
+
*/
|
|
336
|
+
export function encodeCode39(value, options = {}) {
|
|
337
|
+
const { checkDigit = false, fullAscii = false, wideRatio = 3 } = options;
|
|
338
|
+
if (wideRatio < 2 || wideRatio > 3) {
|
|
339
|
+
throw new EncodeError(`Code 39: wide ratio must be 2 or 3, got ${wideRatio}`);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
let text = value;
|
|
343
|
+
if (fullAscii) {
|
|
344
|
+
text = '';
|
|
345
|
+
for (const ch of value) {
|
|
346
|
+
const code = ch.charCodeAt(0);
|
|
347
|
+
if (code > 127) throw new EncodeError(`Code 39: '${ch}' is outside ASCII`);
|
|
348
|
+
text += CODE39_EXTENDED[code];
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
for (const ch of text) {
|
|
353
|
+
if (ch === '*') {
|
|
354
|
+
throw new EncodeError("Code 39: '*' is reserved as the start/stop character");
|
|
355
|
+
}
|
|
356
|
+
if (!CODE39[ch]) {
|
|
357
|
+
throw new EncodeError(
|
|
358
|
+
`Code 39: character '${ch}' is not encodable` +
|
|
359
|
+
(fullAscii ? '' : ' — try the fullAscii option')
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
let payload = text;
|
|
365
|
+
if (checkDigit) {
|
|
366
|
+
let sum = 0;
|
|
367
|
+
for (const ch of text) sum += CODE39_CHECK_SET.indexOf(ch);
|
|
368
|
+
payload += CODE39_CHECK_SET[sum % 43];
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Characters are separated by a narrow inter-character gap.
|
|
372
|
+
const parts = ['*', ...payload, '*'].map((ch) => expandNarrowWide(CODE39[ch], wideRatio));
|
|
373
|
+
return toMatrix(parts.join('0'));
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/* ------------------------------------------------------------------ *
|
|
377
|
+
* Code 93
|
|
378
|
+
* ------------------------------------------------------------------ */
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Code 93, always with its two mandatory check characters.
|
|
382
|
+
*
|
|
383
|
+
* @param {string} value
|
|
384
|
+
* @returns {BitMatrix}
|
|
385
|
+
*/
|
|
386
|
+
export function encodeCode93(value) {
|
|
387
|
+
const values = [];
|
|
388
|
+
for (const ch of value) {
|
|
389
|
+
const idx = CODE93_VALUES.indexOf(ch);
|
|
390
|
+
if (idx < 0) throw new EncodeError(`Code 93: character '${ch}' is not encodable`);
|
|
391
|
+
values.push(idx);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Check character C weights the payload 1..20 from the right; K then repeats
|
|
395
|
+
// the exercise over the payload plus C, weighted 1..15.
|
|
396
|
+
const weighted = (data, maxWeight) => {
|
|
397
|
+
let sum = 0;
|
|
398
|
+
for (let i = 0; i < data.length; i++) {
|
|
399
|
+
const weight = ((data.length - 1 - i) % maxWeight) + 1;
|
|
400
|
+
sum += weight * data[i];
|
|
401
|
+
}
|
|
402
|
+
return sum % 47;
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
values.push(weighted(values, 20));
|
|
406
|
+
values.push(weighted(values, 15));
|
|
407
|
+
|
|
408
|
+
let modules = expandWidths(CODE93_START_STOP);
|
|
409
|
+
for (const v of values) modules += expandWidths(CODE93[CODE93_VALUES[v]]);
|
|
410
|
+
modules += expandWidths(CODE93_START_STOP);
|
|
411
|
+
modules += '1'; // termination bar
|
|
412
|
+
|
|
413
|
+
return toMatrix(modules);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/* ------------------------------------------------------------------ *
|
|
417
|
+
* Code 128
|
|
418
|
+
* ------------------------------------------------------------------ */
|
|
419
|
+
|
|
420
|
+
/** Set B maps printable ASCII starting at space to symbol value 0. */
|
|
421
|
+
const CODE128_B_OFFSET = 32;
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Code 128, with automatic code-set selection.
|
|
425
|
+
*
|
|
426
|
+
* The heuristic: switch into set C when enough consecutive digits are present
|
|
427
|
+
* to repay the switch symbol — four at the start or end of the payload, six in
|
|
428
|
+
* the middle, since C packs two digits per symbol. An encoder that never
|
|
429
|
+
* switches produces a valid but needlessly wide symbol.
|
|
430
|
+
*
|
|
431
|
+
* @param {string} value
|
|
432
|
+
* @param {object} [options]
|
|
433
|
+
* @param {boolean} [options.gs1] Emit a leading FNC1, making this GS1-128.
|
|
434
|
+
* @returns {BitMatrix}
|
|
435
|
+
*/
|
|
436
|
+
export function encodeCode128(value, options = {}) {
|
|
437
|
+
const { gs1 = false } = options;
|
|
438
|
+
for (const ch of value) {
|
|
439
|
+
if (ch.charCodeAt(0) > 127) {
|
|
440
|
+
throw new EncodeError(`Code 128: '${ch}' is outside ASCII`);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/** Length of the digit run starting at i. */
|
|
445
|
+
const digitRun = (i) => {
|
|
446
|
+
let n = 0;
|
|
447
|
+
while (i + n < value.length && value[i + n] >= '0' && value[i + n] <= '9') n++;
|
|
448
|
+
return n;
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
const codes = [];
|
|
452
|
+
let mode;
|
|
453
|
+
let i = 0;
|
|
454
|
+
|
|
455
|
+
const startRun = digitRun(0);
|
|
456
|
+
if (startRun >= 4 && startRun % 2 === 0) {
|
|
457
|
+
codes.push(CODE128_START_C);
|
|
458
|
+
mode = 'C';
|
|
459
|
+
} else {
|
|
460
|
+
codes.push(CODE128_START_B);
|
|
461
|
+
mode = 'B';
|
|
462
|
+
}
|
|
463
|
+
if (gs1) codes.push(CODE128_FNC1);
|
|
464
|
+
|
|
465
|
+
while (i < value.length) {
|
|
466
|
+
const run = digitRun(i);
|
|
467
|
+
const atEnd = i + run === value.length;
|
|
468
|
+
const worthC = run >= 6 || (i === 0 && run >= 4) || (atEnd && run >= 4);
|
|
469
|
+
|
|
470
|
+
if (mode !== 'C' && worthC && run >= 2) {
|
|
471
|
+
codes.push(CODE128_CODE_C);
|
|
472
|
+
mode = 'C';
|
|
473
|
+
continue;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
if (mode === 'C') {
|
|
477
|
+
if (run >= 2) {
|
|
478
|
+
codes.push(Number(value.substr(i, 2)));
|
|
479
|
+
i += 2;
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
482
|
+
codes.push(CODE128_CODE_B);
|
|
483
|
+
mode = 'B';
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
const code = value.charCodeAt(i);
|
|
488
|
+
if (code < 32) {
|
|
489
|
+
// Control characters live in set A only.
|
|
490
|
+
if (mode !== 'A') {
|
|
491
|
+
codes.push(CODE128_CODE_A);
|
|
492
|
+
mode = 'A';
|
|
493
|
+
continue;
|
|
494
|
+
}
|
|
495
|
+
codes.push(code + 64);
|
|
496
|
+
} else {
|
|
497
|
+
if (mode === 'A' && code >= 96) {
|
|
498
|
+
codes.push(CODE128_CODE_B);
|
|
499
|
+
mode = 'B';
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
502
|
+
codes.push(code - CODE128_B_OFFSET);
|
|
503
|
+
}
|
|
504
|
+
i++;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// Checksum: the start value plus each symbol weighted by its position.
|
|
508
|
+
let sum = codes[0];
|
|
509
|
+
for (let k = 1; k < codes.length; k++) sum += codes[k] * k;
|
|
510
|
+
codes.push(sum % 103);
|
|
511
|
+
codes.push(CODE128_STOP);
|
|
512
|
+
|
|
513
|
+
let modules = '';
|
|
514
|
+
for (const c of codes) modules += expandWidths(CODE128[c]);
|
|
515
|
+
|
|
516
|
+
return toMatrix(modules);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/* ------------------------------------------------------------------ *
|
|
520
|
+
* Interleaved 2 of 5
|
|
521
|
+
* ------------------------------------------------------------------ */
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* Interleaved 2 of 5.
|
|
525
|
+
*
|
|
526
|
+
* Digits are encoded in pairs: the first supplies the bars, the second the
|
|
527
|
+
* spaces between them. That interleaving is where the density comes from, and
|
|
528
|
+
* why the payload length must be even.
|
|
529
|
+
*
|
|
530
|
+
* @param {string} value
|
|
531
|
+
* @param {object} [options]
|
|
532
|
+
* @param {boolean} [options.checkDigit] Append a modulo-10 check digit.
|
|
533
|
+
* @param {number} [options.wideRatio]
|
|
534
|
+
* @returns {BitMatrix}
|
|
535
|
+
*/
|
|
536
|
+
export function encodeITF(value, options = {}) {
|
|
537
|
+
const { checkDigit = false, wideRatio = 3 } = options;
|
|
538
|
+
requireDigits(value, 'ITF');
|
|
539
|
+
|
|
540
|
+
let digits = value;
|
|
541
|
+
if (checkDigit) digits += String(ean13CheckDigit(digits));
|
|
542
|
+
// A leading zero pads to an even length without changing the value.
|
|
543
|
+
if (digits.length % 2 !== 0) digits = '0' + digits;
|
|
544
|
+
|
|
545
|
+
let modules = '1010'; // start: four narrow elements
|
|
546
|
+
|
|
547
|
+
for (let i = 0; i < digits.length; i += 2) {
|
|
548
|
+
const bars = ITF[Number(digits[i])];
|
|
549
|
+
const spaces = ITF[Number(digits[i + 1])];
|
|
550
|
+
for (let k = 0; k < 5; k++) {
|
|
551
|
+
modules += '1'.repeat(bars[k] === 'w' ? wideRatio : 1);
|
|
552
|
+
modules += '0'.repeat(spaces[k] === 'w' ? wideRatio : 1);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// Stop: wide bar, narrow space, narrow bar.
|
|
557
|
+
modules += '1'.repeat(wideRatio) + '0' + '1';
|
|
558
|
+
return toMatrix(modules);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* ITF-14, the shipping-container form: exactly 14 digits.
|
|
563
|
+
*
|
|
564
|
+
* @param {string} value 13 or 14 digits.
|
|
565
|
+
* @returns {BitMatrix}
|
|
566
|
+
*/
|
|
567
|
+
export function encodeITF14(value) {
|
|
568
|
+
requireDigits(value, 'ITF-14');
|
|
569
|
+
let digits = value;
|
|
570
|
+
if (digits.length === 13) {
|
|
571
|
+
digits += String(ean13CheckDigit(digits));
|
|
572
|
+
} else if (digits.length !== 14) {
|
|
573
|
+
throw new EncodeError(`ITF-14: needs 13 or 14 digits, got ${digits.length}`);
|
|
574
|
+
}
|
|
575
|
+
return encodeITF(digits);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/* ------------------------------------------------------------------ *
|
|
579
|
+
* Codabar
|
|
580
|
+
* ------------------------------------------------------------------ */
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* Codabar.
|
|
584
|
+
*
|
|
585
|
+
* @param {string} value Optionally already wrapped in start/stop characters A-D.
|
|
586
|
+
* @param {object} [options]
|
|
587
|
+
* @param {string} [options.start] One of A, B, C, D.
|
|
588
|
+
* @param {string} [options.stop]
|
|
589
|
+
* @param {number} [options.wideRatio]
|
|
590
|
+
* @returns {BitMatrix}
|
|
591
|
+
*/
|
|
592
|
+
export function encodeCodabar(value, options = {}) {
|
|
593
|
+
const { wideRatio = 3 } = options;
|
|
594
|
+
let text = value.toUpperCase();
|
|
595
|
+
let start = (options.start || '').toUpperCase();
|
|
596
|
+
let stop = (options.stop || '').toUpperCase();
|
|
597
|
+
|
|
598
|
+
// Accept the common convention of embedding the guards in the payload.
|
|
599
|
+
if (!start && text.length >= 2 &&
|
|
600
|
+
CODABAR_START_STOP.includes(text[0]) &&
|
|
601
|
+
CODABAR_START_STOP.includes(text[text.length - 1])) {
|
|
602
|
+
start = text[0];
|
|
603
|
+
stop = text[text.length - 1];
|
|
604
|
+
text = text.slice(1, -1);
|
|
605
|
+
}
|
|
606
|
+
if (!start) start = 'A';
|
|
607
|
+
if (!stop) stop = 'A';
|
|
608
|
+
|
|
609
|
+
if (!CODABAR_START_STOP.includes(start) || !CODABAR_START_STOP.includes(stop)) {
|
|
610
|
+
throw new EncodeError('Codabar: start and stop characters must be A, B, C or D');
|
|
611
|
+
}
|
|
612
|
+
for (const ch of text) {
|
|
613
|
+
if (!CODABAR[ch] || CODABAR_START_STOP.includes(ch)) {
|
|
614
|
+
throw new EncodeError(`Codabar: character '${ch}' is not encodable in the payload`);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
const parts = [start, ...text, stop].map((ch) => expandNarrowWide(CODABAR[ch], wideRatio));
|
|
619
|
+
return toMatrix(parts.join('0'));
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/* ------------------------------------------------------------------ *
|
|
623
|
+
* Code 11
|
|
624
|
+
* ------------------------------------------------------------------ */
|
|
625
|
+
|
|
626
|
+
/** Code 11 character values, in order. */
|
|
627
|
+
const CODE11_CHARSET = '0123456789-';
|
|
628
|
+
|
|
629
|
+
/**
|
|
630
|
+
* Code 11, digits and hyphen.
|
|
631
|
+
*
|
|
632
|
+
* @param {string} value
|
|
633
|
+
* @param {object} [options]
|
|
634
|
+
* @param {boolean} [options.checkDigit] Append check character C, plus K when long.
|
|
635
|
+
* @param {number} [options.wideRatio]
|
|
636
|
+
* @returns {BitMatrix}
|
|
637
|
+
*/
|
|
638
|
+
export function encodeCode11(value, options = {}) {
|
|
639
|
+
const { checkDigit = true, wideRatio = 3 } = options;
|
|
640
|
+
|
|
641
|
+
for (const ch of value) {
|
|
642
|
+
if (!CODE11_CHARSET.includes(ch)) {
|
|
643
|
+
throw new EncodeError(`Code 11: character '${ch}' is not encodable`);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
let payload = value;
|
|
648
|
+
if (checkDigit) {
|
|
649
|
+
const weighted = (text, maxWeight) => {
|
|
650
|
+
let sum = 0;
|
|
651
|
+
for (let i = 0; i < text.length; i++) {
|
|
652
|
+
const weight = ((text.length - 1 - i) % maxWeight) + 1;
|
|
653
|
+
sum += weight * CODE11_CHARSET.indexOf(text[i]);
|
|
654
|
+
}
|
|
655
|
+
return sum;
|
|
656
|
+
};
|
|
657
|
+
payload += CODE11_CHARSET[weighted(payload, 10) % 11];
|
|
658
|
+
// The second check character is conventionally added only to longer payloads.
|
|
659
|
+
if (value.length >= 10) payload += CODE11_CHARSET[weighted(payload, 9) % 11];
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
const parts = [
|
|
663
|
+
expandNarrowWide(CODE11_START_STOP, wideRatio),
|
|
664
|
+
...[...payload].map((ch) => expandNarrowWide(CODE11[ch], wideRatio)),
|
|
665
|
+
expandNarrowWide(CODE11_START_STOP, wideRatio),
|
|
666
|
+
];
|
|
667
|
+
return toMatrix(parts.join('0'));
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/* ------------------------------------------------------------------ *
|
|
671
|
+
* MSI / Plessey
|
|
672
|
+
* ------------------------------------------------------------------ */
|
|
673
|
+
|
|
674
|
+
/**
|
|
675
|
+
* MSI Plessey.
|
|
676
|
+
*
|
|
677
|
+
* @param {string} value Digits.
|
|
678
|
+
* @param {object} [options]
|
|
679
|
+
* @param {boolean} [options.checkDigit] Append the Luhn modulo-10 check digit.
|
|
680
|
+
* @returns {BitMatrix}
|
|
681
|
+
*/
|
|
682
|
+
export function encodeMSI(value, options = {}) {
|
|
683
|
+
const { checkDigit = false } = options;
|
|
684
|
+
requireDigits(value, 'MSI');
|
|
685
|
+
|
|
686
|
+
let digits = value;
|
|
687
|
+
if (checkDigit) {
|
|
688
|
+
// Luhn: the odd-positioned digits, read as one number, are doubled.
|
|
689
|
+
let odd = '';
|
|
690
|
+
for (let i = digits.length - 1; i >= 0; i -= 2) odd = digits[i] + odd;
|
|
691
|
+
const doubled = String(Number(odd) * 2);
|
|
692
|
+
let sum = 0;
|
|
693
|
+
for (const ch of doubled) sum += Number(ch);
|
|
694
|
+
for (let i = digits.length - 2; i >= 0; i -= 2) sum += Number(digits[i]);
|
|
695
|
+
digits += String((10 - (sum % 10)) % 10);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
let modules = MSI_START;
|
|
699
|
+
for (const ch of digits) {
|
|
700
|
+
const bits = Number(ch).toString(2).padStart(4, '0');
|
|
701
|
+
for (const b of bits) modules += MSI_BIT[b];
|
|
702
|
+
}
|
|
703
|
+
modules += MSI_STOP;
|
|
704
|
+
|
|
705
|
+
return toMatrix(modules);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
/* ------------------------------------------------------------------ *
|
|
709
|
+
* Pharmacode
|
|
710
|
+
* ------------------------------------------------------------------ */
|
|
711
|
+
|
|
712
|
+
/**
|
|
713
|
+
* Pharmacode, one-track.
|
|
714
|
+
*
|
|
715
|
+
* Unusual among linear symbologies: it encodes an integer directly in a
|
|
716
|
+
* bijective base-2 representation rather than digit by digit, and carries no
|
|
717
|
+
* check digit at all — its only redundancy is the narrow legal value range.
|
|
718
|
+
*
|
|
719
|
+
* @param {number | string} value 3 to 131070.
|
|
720
|
+
* @returns {BitMatrix}
|
|
721
|
+
*/
|
|
722
|
+
export function encodePharmacode(value) {
|
|
723
|
+
let n = typeof value === 'string' ? Number(value) : value;
|
|
724
|
+
if (!Number.isInteger(n) || n < 3 || n > 131070) {
|
|
725
|
+
throw new EncodeError(`Pharmacode: value must be an integer in 3..131070, got ${value}`);
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
const bars = [];
|
|
729
|
+
while (n > 0) {
|
|
730
|
+
if (n % 2 === 0) {
|
|
731
|
+
bars.push('111'); // wide
|
|
732
|
+
n = n / 2 - 1;
|
|
733
|
+
} else {
|
|
734
|
+
bars.push('1'); // narrow
|
|
735
|
+
n = (n - 1) / 2;
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
bars.reverse();
|
|
739
|
+
|
|
740
|
+
return toMatrix(bars.join('0'));
|
|
741
|
+
}
|