@juit/librebarcode 1.0.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-ASL.md +211 -0
- package/LICENSE-OFL.md +97 -0
- package/README.md +126 -0
- package/assets/LibreBarcode128-Regular.ttf +0 -0
- package/assets/LibreBarcode128Text-Regular.ttf +0 -0
- package/assets/LibreBarcodeEAN13Text-Regular.ttf +0 -0
- package/dist/checksum.cjs +2 -0
- package/dist/checksum.cjs.map +6 -0
- package/dist/checksum.d.ts +0 -0
- package/dist/checksum.mjs +1 -0
- package/dist/checksum.mjs.map +6 -0
- package/dist/code128.cjs +379 -0
- package/dist/code128.cjs.map +6 -0
- package/dist/code128.d.ts +66 -0
- package/dist/code128.mjs +336 -0
- package/dist/code128.mjs.map +6 -0
- package/dist/ean.cjs +194 -0
- package/dist/ean.cjs.map +6 -0
- package/dist/ean.d.ts +5 -0
- package/dist/ean.mjs +168 -0
- package/dist/ean.mjs.map +6 -0
- package/dist/index.cjs +52 -0
- package/dist/index.cjs.map +6 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.mjs +20 -0
- package/dist/index.mjs.map +6 -0
- package/package.json +59 -0
- package/src/checksum.ts +0 -0
- package/src/code128.ts +374 -0
- package/src/ean.ts +235 -0
- package/src/index.ts +10 -0
package/src/code128.ts
ADDED
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
/* eslint-disable no-sparse-arrays */
|
|
2
|
+
/* eslint-disable @stylistic/no-multi-spaces */
|
|
3
|
+
|
|
4
|
+
/* ========================================================================== *
|
|
5
|
+
* INTERNAL TYPES *
|
|
6
|
+
* ========================================================================== */
|
|
7
|
+
|
|
8
|
+
/** The three code-sets of Code128 */
|
|
9
|
+
export type CodeSet = 'A' | 'B' | 'C'
|
|
10
|
+
/** Any possible value in our sequence */
|
|
11
|
+
export type Code = typeof DATA[number][0]
|
|
12
|
+
|
|
13
|
+
/* ========================================================================== *
|
|
14
|
+
* CONSTANTS AND INITIALIZERS *
|
|
15
|
+
* ========================================================================== */
|
|
16
|
+
|
|
17
|
+
/** The unicode character representing Code128's `FNC1` */
|
|
18
|
+
export const FNC1 = '\uf001'
|
|
19
|
+
/** The unicode character representing Code128's `FNC2` */
|
|
20
|
+
export const FNC2 = '\uf002'
|
|
21
|
+
/** The unicode character representing Code128's `FNC3` */
|
|
22
|
+
export const FNC3 = '\uf003'
|
|
23
|
+
/** The unicode character representing Code128's `FNC4` */
|
|
24
|
+
export const FNC4 = '\uf004'
|
|
25
|
+
|
|
26
|
+
const DATA = [
|
|
27
|
+
// CODESET A CODESET B CODESET C FONT CHARACTER
|
|
28
|
+
[ 0, ' ', ' ', '00', '\u00c2' ], // "Â"
|
|
29
|
+
[ 1, '!', '!', '01', '!' ],
|
|
30
|
+
[ 2, '"', '"', '02', '"' ],
|
|
31
|
+
[ 3, '#', '#', '03', '#' ],
|
|
32
|
+
[ 4, '$', '$', '04', '$' ],
|
|
33
|
+
[ 5, '%', '%', '05', '%' ],
|
|
34
|
+
[ 6, '&', '&', '06', '&' ],
|
|
35
|
+
[ 7, '\u0027', '\u0027', '07', '\u0027' ], // single quote "'"
|
|
36
|
+
[ 8, '(', '(', '08', '(' ],
|
|
37
|
+
[ 9, ')', ')', '09', ')' ],
|
|
38
|
+
[ 10, '*', '*', '10', '*' ],
|
|
39
|
+
[ 11, '+', '+', '11', '+' ],
|
|
40
|
+
[ 12, ',', ',', '12', ',' ],
|
|
41
|
+
[ 13, '-', '-', '13', '-' ],
|
|
42
|
+
[ 14, '.', '.', '14', '.' ],
|
|
43
|
+
[ 15, '/', '/', '15', '/' ],
|
|
44
|
+
[ 16, '0', '0', '16', '0' ],
|
|
45
|
+
[ 17, '1', '1', '17', '1' ],
|
|
46
|
+
[ 18, '2', '2', '18', '2' ],
|
|
47
|
+
[ 19, '3', '3', '19', '3' ],
|
|
48
|
+
[ 20, '4', '4', '20', '4' ],
|
|
49
|
+
[ 21, '5', '5', '21', '5' ],
|
|
50
|
+
[ 22, '6', '6', '22', '6' ],
|
|
51
|
+
[ 23, '7', '7', '23', '7' ],
|
|
52
|
+
[ 24, '8', '8', '24', '8' ],
|
|
53
|
+
[ 25, '9', '9', '25', '9' ],
|
|
54
|
+
[ 26, ':', ':', '26', ':' ],
|
|
55
|
+
[ 27, ';', ';', '27', ';' ],
|
|
56
|
+
[ 28, '<', '<', '28', '<' ],
|
|
57
|
+
[ 29, '=', '=', '29', '=' ],
|
|
58
|
+
[ 30, '>', '>', '30', '>' ],
|
|
59
|
+
[ 31, '?', '?', '31', '?' ],
|
|
60
|
+
[ 32, '@', '@', '32', '@' ],
|
|
61
|
+
[ 33, 'A', 'A', '33', 'A' ],
|
|
62
|
+
[ 34, 'B', 'B', '34', 'B' ],
|
|
63
|
+
[ 35, 'C', 'C', '35', 'C' ],
|
|
64
|
+
[ 36, 'D', 'D', '36', 'D' ],
|
|
65
|
+
[ 37, 'E', 'E', '37', 'E' ],
|
|
66
|
+
[ 38, 'F', 'F', '38', 'F' ],
|
|
67
|
+
[ 39, 'G', 'G', '39', 'G' ],
|
|
68
|
+
[ 40, 'H', 'H', '40', 'H' ],
|
|
69
|
+
[ 41, 'I', 'I', '41', 'I' ],
|
|
70
|
+
[ 42, 'J', 'J', '42', 'J' ],
|
|
71
|
+
[ 43, 'K', 'K', '43', 'K' ],
|
|
72
|
+
[ 44, 'L', 'L', '44', 'L' ],
|
|
73
|
+
[ 45, 'M', 'M', '45', 'M' ],
|
|
74
|
+
[ 46, 'N', 'N', '46', 'N' ],
|
|
75
|
+
[ 47, 'O', 'O', '47', 'O' ],
|
|
76
|
+
[ 48, 'P', 'P', '48', 'P' ],
|
|
77
|
+
[ 49, 'Q', 'Q', '49', 'Q' ],
|
|
78
|
+
[ 50, 'R', 'R', '50', 'R' ],
|
|
79
|
+
[ 51, 'S', 'S', '51', 'S' ],
|
|
80
|
+
[ 52, 'T', 'T', '52', 'T' ],
|
|
81
|
+
[ 53, 'U', 'U', '53', 'U' ],
|
|
82
|
+
[ 54, 'V', 'V', '54', 'V' ],
|
|
83
|
+
[ 55, 'W', 'W', '55', 'W' ],
|
|
84
|
+
[ 56, 'X', 'X', '56', 'X' ],
|
|
85
|
+
[ 57, 'Y', 'Y', '57', 'Y' ],
|
|
86
|
+
[ 58, 'Z', 'Z', '58', 'Z' ],
|
|
87
|
+
[ 59, '[', '[', '59', '[' ],
|
|
88
|
+
[ 60, '\u005c', '\u005c', '60', '\u005c' ], // backslash "\"
|
|
89
|
+
[ 61, ']', ']', '61', ']' ],
|
|
90
|
+
[ 62, '^', '^', '62', '^' ],
|
|
91
|
+
[ 63, '_', '_', '63', '_' ],
|
|
92
|
+
[ 64, '\u0000', '`', '64', '`' ], // NUL
|
|
93
|
+
[ 65, '\u0001', 'a', '65', 'a' ], // SOH
|
|
94
|
+
[ 66, '\u0002', 'b', '66', 'b' ], // STX
|
|
95
|
+
[ 67, '\u0003', 'c', '67', 'c' ], // ETX
|
|
96
|
+
[ 68, '\u0004', 'd', '68', 'd' ], // EOT
|
|
97
|
+
[ 69, '\u0005', 'e', '69', 'e' ], // ENQ
|
|
98
|
+
[ 70, '\u0006', 'f', '70', 'f' ], // ACK
|
|
99
|
+
[ 71, '\u0007', 'g', '71', 'g' ], // BEL
|
|
100
|
+
[ 72, '\u0008', 'h', '72', 'h' ], // BS
|
|
101
|
+
[ 73, '\u0009', 'i', '73', 'i' ], // HT
|
|
102
|
+
[ 74, '\u000a', 'j', '74', 'j' ], // LF
|
|
103
|
+
[ 75, '\u000b', 'k', '75', 'k' ], // VT
|
|
104
|
+
[ 76, '\u000c', 'l', '76', 'l' ], // FF
|
|
105
|
+
[ 77, '\u000d', 'm', '77', 'm' ], // CR
|
|
106
|
+
[ 78, '\u000e', 'n', '78', 'n' ], // SO
|
|
107
|
+
[ 79, '\u000f', 'o', '79', 'o' ], // SI
|
|
108
|
+
[ 80, '\u0010', 'p', '80', 'p' ], // DLE
|
|
109
|
+
[ 81, '\u0011', 'q', '81', 'q' ], // DC1
|
|
110
|
+
[ 82, '\u0012', 'r', '82', 'r' ], // DC2
|
|
111
|
+
[ 83, '\u0013', 's', '83', 's' ], // DC3
|
|
112
|
+
[ 84, '\u0014', 't', '84', 't' ], // DC4
|
|
113
|
+
[ 85, '\u0015', 'u', '85', 'u' ], // NAK
|
|
114
|
+
[ 86, '\u0016', 'v', '86', 'v' ], // SYN
|
|
115
|
+
[ 87, '\u0017', 'w', '87', 'w' ], // ETB
|
|
116
|
+
[ 88, '\u0018', 'x', '88', 'x' ], // CAN
|
|
117
|
+
[ 89, '\u0019', 'y', '89', 'y' ], // EM
|
|
118
|
+
[ 90, '\u001a', 'z', '90', 'z' ], // SUB
|
|
119
|
+
[ 91, '\u001b', '{', '91', '{' ], // ESC
|
|
120
|
+
[ 92, '\u001c', '|', '92', '|' ], // FS
|
|
121
|
+
[ 93, '\u001d', '}', '93', '}' ], // GS
|
|
122
|
+
[ 94, '\u001e', '~', '94', '~' ], // RS
|
|
123
|
+
[ 95, '\u001f', '\u007f', '95', '\u00c3' ], // US // DEL // Ã
|
|
124
|
+
[ 96, FNC3, FNC3, '96', '\u00c4' ], // FNC 3 // Ä
|
|
125
|
+
[ 97, FNC2, FNC2, '97', '\u00c5' ], // FNC 2 // Å
|
|
126
|
+
[ 98, /* SH B */, /* SH A */, '98', '\u00c6' ], // Æ
|
|
127
|
+
[ 99, /* CD C */, /* CD C */, '99', '\u00c7' ], // Ç
|
|
128
|
+
[ 100, /* CD B */, FNC4, /* CD B */, '\u00c8' ], // FNC 4 // È
|
|
129
|
+
[ 101, FNC4, /* CD A */, /* CD A */, '\u00c9' ], // FNC 4 // É
|
|
130
|
+
[ 102, FNC1, FNC1, FNC1, '\u00ca' ], // FNC 1 // Ê
|
|
131
|
+
[ 103, /* ST A */, /* ST A */, /* ST A */, '\u00cb' ], // Ë
|
|
132
|
+
[ 104, /* ST B */, /* ST B */, /* ST B */, '\u00cc' ], // Ì
|
|
133
|
+
[ 105, /* ST C */, /* ST C */, /* ST C */, '\u00cd' ], // Í
|
|
134
|
+
] as const
|
|
135
|
+
|
|
136
|
+
/** Sequences encodable with code-set `A` and related values */
|
|
137
|
+
export const CODESET_A = DATA.reduce((data, [ val, csa, _csb, _csc, _chr ]) => {
|
|
138
|
+
if (csa !== undefined) data[csa] = val
|
|
139
|
+
return data
|
|
140
|
+
}, {} as Record<string, Code>)
|
|
141
|
+
|
|
142
|
+
/** Sequences encodable with code-set `B` and related values */
|
|
143
|
+
export const CODESET_B = DATA.reduce((data, [ val, _csa, csb, _csc, _chr ]) => {
|
|
144
|
+
if (csb !== undefined) data[csb] = val
|
|
145
|
+
return data
|
|
146
|
+
}, {} as Record<string, Code>)
|
|
147
|
+
|
|
148
|
+
/** Sequences encodable with code-set `C` and related values */
|
|
149
|
+
export const CODESET_C = DATA.reduce((data, [ val, _csa, _csb, csc, _chr ]) => {
|
|
150
|
+
if (csc !== undefined) data[csc] = val
|
|
151
|
+
return data
|
|
152
|
+
}, {} as Record<string, Code>)
|
|
153
|
+
|
|
154
|
+
/** Encodable characters with the various code-sets */
|
|
155
|
+
export const CODESETS = {
|
|
156
|
+
A: CODESET_A,
|
|
157
|
+
B: CODESET_B,
|
|
158
|
+
C: CODESET_C,
|
|
159
|
+
} as const
|
|
160
|
+
|
|
161
|
+
/** Array of all characters present in our Code128 font */
|
|
162
|
+
export const CHARACTERS: Record<Code, string> & Array<string> = [] as any
|
|
163
|
+
DATA.forEach(([ val, _csa, _csb, _csc, chr ]) => CHARACTERS[val] = chr)
|
|
164
|
+
|
|
165
|
+
/** Shift to code-set `B` (one character) while in code-set `A`, or vice-versa */
|
|
166
|
+
export const SHIFT = 98
|
|
167
|
+
|
|
168
|
+
/** Switch to code-set `C` (following characters) while in code-set `A` or `B` */
|
|
169
|
+
export const CODE_C = 99
|
|
170
|
+
/** Switch to code-set `B` (following characters) while in code-set `A` or `C` */
|
|
171
|
+
export const CODE_B = 100
|
|
172
|
+
/** Switch to code-set `A` (following characters) while in code-set `B` or `C` */
|
|
173
|
+
export const CODE_A = 101
|
|
174
|
+
|
|
175
|
+
/** Start with code-set `A` */
|
|
176
|
+
export const START_A = 103
|
|
177
|
+
/** Start with code-set `B` */
|
|
178
|
+
export const START_B = 104
|
|
179
|
+
/** Start with code-set `C` */
|
|
180
|
+
export const START_C = 105
|
|
181
|
+
|
|
182
|
+
/** The stop character */
|
|
183
|
+
export const STOP = '\u00ce' // Î
|
|
184
|
+
|
|
185
|
+
/* ========================================================================== *
|
|
186
|
+
* ENCODER CLASS *
|
|
187
|
+
* ========================================================================== */
|
|
188
|
+
|
|
189
|
+
/** Match the beginning of the string with a sequence */
|
|
190
|
+
function match(string: string, codeset: CodeSet): [ Code | null, string ] {
|
|
191
|
+
for (const [ sequence, value ] of Object.entries(CODESETS[codeset])) {
|
|
192
|
+
if (string.startsWith(sequence)) return [ value, string.substring(sequence.length) ]
|
|
193
|
+
}
|
|
194
|
+
return [ null, string ]
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** Represent the first character of a string as a nice \uXXXX */
|
|
198
|
+
function unicode(string: string): string {
|
|
199
|
+
const hex = string.charCodeAt(0).toString(16).padStart(4, '0')
|
|
200
|
+
return `\\u${hex}`
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** Our encoder class */
|
|
204
|
+
export class Encoder {
|
|
205
|
+
/** The _current_ code-set of this encoder */
|
|
206
|
+
private _codeset: CodeSet
|
|
207
|
+
/** The array of values encoded so far */
|
|
208
|
+
private _values: Code[]
|
|
209
|
+
/** The remainder of the string to encode */
|
|
210
|
+
private _string: string
|
|
211
|
+
/** An array of encoders to add forks of this encoder to */
|
|
212
|
+
private _encoders: Encoder[]
|
|
213
|
+
|
|
214
|
+
constructor(codeset: CodeSet, string: string, encoders: Encoder[], values?: Code[]) {
|
|
215
|
+
this._codeset = codeset
|
|
216
|
+
this._string = string
|
|
217
|
+
this._encoders = encoders
|
|
218
|
+
|
|
219
|
+
if (values) {
|
|
220
|
+
this._values = values
|
|
221
|
+
} else {
|
|
222
|
+
switch (codeset) {
|
|
223
|
+
case 'A': this._values = [ START_A ]; break
|
|
224
|
+
case 'B': this._values = [ START_B ]; break
|
|
225
|
+
case 'C': this._values = [ START_C ]; break
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
this._encoders.push(this)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/** Whether this encoder is done or not */
|
|
233
|
+
public get done(): boolean {
|
|
234
|
+
return this._string.length === 0
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Do the next round of encoding, returning `true` if _this_ instance has
|
|
239
|
+
* finished encoding.
|
|
240
|
+
*/
|
|
241
|
+
public encode(length: number): boolean {
|
|
242
|
+
// When we get to the empty string, we're done!
|
|
243
|
+
if (this._string.length == 0) return true
|
|
244
|
+
// When we have already encoded this character, pass!
|
|
245
|
+
if (this._values.length >= length) return false
|
|
246
|
+
|
|
247
|
+
// Match in the _current_ code set, if we have a value, just add it
|
|
248
|
+
const [ value, remaining ] = match(this._string, this._codeset)
|
|
249
|
+
if (value !== null) {
|
|
250
|
+
this._values.push(value)
|
|
251
|
+
this._string = remaining
|
|
252
|
+
|
|
253
|
+
// Optimization: in code-set "A" or "B" we _might_ find a double-digit
|
|
254
|
+
// sequence (or FNC1) which may be advantageous to encode in "C"
|
|
255
|
+
if ((this._codeset === 'A') || (this._codeset === 'B')) {
|
|
256
|
+
const [ valuec, remainingc ] = match(this._string, 'C')
|
|
257
|
+
if (valuec !== null) {
|
|
258
|
+
// "Fork" with the switched "C" code-set
|
|
259
|
+
new Encoder('C', remainingc, this._encoders, [ ...this._values, CODE_C, valuec ])
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
//
|
|
264
|
+
return this.done
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// In code-set "A" we want to try shifting to "B" or switching to "B" or "C"
|
|
268
|
+
if (this._codeset === 'A') {
|
|
269
|
+
// We already handled a switch to "C" above
|
|
270
|
+
const [ valueb, remainingb ] = match(this._string, 'B')
|
|
271
|
+
if (valueb !== null) {
|
|
272
|
+
// "Fork" with the switched "B" code-set
|
|
273
|
+
new Encoder('B', remainingb, this._encoders, [ ...this._values, CODE_B, valueb ])
|
|
274
|
+
// This instance contains with only a shift to "B"
|
|
275
|
+
this._values.push(SHIFT, valueb)
|
|
276
|
+
this._string = remainingb
|
|
277
|
+
return this.done
|
|
278
|
+
|
|
279
|
+
// Couldn't encode in "B"? Then this is not encodable...
|
|
280
|
+
} else {
|
|
281
|
+
throw new Error(`Unable to encode character "${unicode(remaining)}"`)
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// In code-set "B" we want to try shifting to "A" or switching to "A" or "C"
|
|
285
|
+
} else if (this._codeset === 'B') {
|
|
286
|
+
// We already handled a switch to "C" above
|
|
287
|
+
const [ valuea, remaininga ] = match(this._string, 'A')
|
|
288
|
+
if (valuea !== null) {
|
|
289
|
+
// "Fork" with the switched "A" code-set
|
|
290
|
+
new Encoder('A', remaininga, this._encoders, [ ...this._values, CODE_A, valuea ])
|
|
291
|
+
// This instance contains with only a shift to "A"
|
|
292
|
+
this._values.push(SHIFT, valuea)
|
|
293
|
+
this._string = remaininga
|
|
294
|
+
return this.done
|
|
295
|
+
|
|
296
|
+
// Couldn't encode in "A"? Then this is not encodable...
|
|
297
|
+
} else {
|
|
298
|
+
throw new Error(`Unable to encode character "${unicode(remaining)}"`)
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Only other option is for us to be in code-set "C" and we can only switch
|
|
302
|
+
} else {
|
|
303
|
+
const [ valuea, remaininga ] = match(this._string, 'A')
|
|
304
|
+
const [ valueb, remainingb ] = match(this._string, 'B')
|
|
305
|
+
|
|
306
|
+
// If both code sets match, fork on "A" and continue on "B"
|
|
307
|
+
if ((valuea !== null) && (valueb !== null)) {
|
|
308
|
+
new Encoder('A', remaininga, this._encoders, [ ...this._values, CODE_A, valuea ])
|
|
309
|
+
this._values.push(CODE_B, valueb)
|
|
310
|
+
this._string = remainingb
|
|
311
|
+
this._codeset = 'B'
|
|
312
|
+
return this.done
|
|
313
|
+
|
|
314
|
+
// If we match only on "A" we continue this after switching
|
|
315
|
+
} else if ((valuea !== null) && (valueb === null)) {
|
|
316
|
+
this._values.push(CODE_A, valuea)
|
|
317
|
+
this._string = remaininga
|
|
318
|
+
this._codeset = 'A'
|
|
319
|
+
return this.done
|
|
320
|
+
|
|
321
|
+
// If we match only on "B" we continue this after switching
|
|
322
|
+
} else if ((valuea === null) && (valueb !== null)) {
|
|
323
|
+
this._values.push(CODE_B, valueb)
|
|
324
|
+
this._string = remainingb
|
|
325
|
+
this._codeset = 'B'
|
|
326
|
+
return this.done
|
|
327
|
+
|
|
328
|
+
// If no code-sets match, we failed...
|
|
329
|
+
} else {
|
|
330
|
+
throw new Error(`Unable to encode character "${unicode(remaining)}"`)
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/** Finish the encoding, and return the string for our barcode */
|
|
336
|
+
public finish(): string {
|
|
337
|
+
// The checksum, as the weighted sum of all values modulo 103
|
|
338
|
+
let sum = 0
|
|
339
|
+
for (let i = 0; i < this._values.length; i ++) {
|
|
340
|
+
sum += this._values[i]! * (i || 1)
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Encode all our values, convert into characters, and add a "stop"
|
|
344
|
+
return [ ...this._values, (sum % 103) as Code ]
|
|
345
|
+
.map((value) => CHARACTERS[value])
|
|
346
|
+
.join('') + STOP
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/* ========================================================================== *
|
|
351
|
+
* ENCODE *
|
|
352
|
+
* ========================================================================== */
|
|
353
|
+
|
|
354
|
+
export function code128(string: string): string {
|
|
355
|
+
// All our encoders, they'll "procreate" while encoding
|
|
356
|
+
const encoders: Encoder[] = []
|
|
357
|
+
|
|
358
|
+
// Initial encoders, starting at each code-set (we start with "C" as it
|
|
359
|
+
// has the biggest chance of encoding as much data in a shorter barcode)
|
|
360
|
+
new Encoder('C', string, encoders)
|
|
361
|
+
new Encoder('B', string, encoders)
|
|
362
|
+
new Encoder('A', string, encoders)
|
|
363
|
+
|
|
364
|
+
// Repeat ad nauseam
|
|
365
|
+
for (let length = 1; ; length ++) {
|
|
366
|
+
// Process all encoders, and go up to the specified barcode length
|
|
367
|
+
for (const encoder of encoders) encoder.encode(length)
|
|
368
|
+
|
|
369
|
+
// After encoding ad this length, we check if any of our encoders is done
|
|
370
|
+
for (const encoder of encoders) {
|
|
371
|
+
if (encoder.done) return encoder.finish()
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
package/src/ean.ts
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/* ========================================================================== *
|
|
2
|
+
* INTERNAL TYPES *
|
|
3
|
+
* ========================================================================== */
|
|
4
|
+
|
|
5
|
+
/** A digit in a EAN8/EAN13 string */
|
|
6
|
+
export type Digit = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
|
|
7
|
+
/** Keys for code-set in patterns (`L` odd, `G` even) */
|
|
8
|
+
type Pattern = 'L' | 'G'
|
|
9
|
+
|
|
10
|
+
/* ========================================================================== *
|
|
11
|
+
* CONSTANTS AND INITIALIZERS *
|
|
12
|
+
* ========================================================================== */
|
|
13
|
+
|
|
14
|
+
/** The characters used in the font for the `L` cod-eset (odd parity) */
|
|
15
|
+
const CODESET_L = [ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J' ] as const
|
|
16
|
+
/** The characters used in the font for the `G` code-set (even parity) */
|
|
17
|
+
const CODESET_G = [ 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T' ] as const
|
|
18
|
+
/** The characters used in the font for the `R` code-set */
|
|
19
|
+
const CODESET_R = [ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j' ] as const
|
|
20
|
+
|
|
21
|
+
/** The codeset for the `L` and `G` patterns (`R` is used directly) */
|
|
22
|
+
const CODESETS = {
|
|
23
|
+
L: CODESET_L,
|
|
24
|
+
G: CODESET_G,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Pattern used to encode the first half (digits 2...7) of an EAN13, depending
|
|
29
|
+
* on the EAN13's own first digit.
|
|
30
|
+
*/
|
|
31
|
+
const PATTERNS_EAN13: Record<Digit, [ Pattern, Pattern, Pattern, Pattern, Pattern, Pattern ]> = [
|
|
32
|
+
[ 'L', 'L', 'L', 'L', 'L', 'L' ],
|
|
33
|
+
[ 'L', 'L', 'G', 'L', 'G', 'G' ],
|
|
34
|
+
[ 'L', 'L', 'G', 'G', 'L', 'G' ],
|
|
35
|
+
[ 'L', 'L', 'G', 'G', 'G', 'L' ],
|
|
36
|
+
[ 'L', 'G', 'L', 'L', 'G', 'G' ],
|
|
37
|
+
[ 'L', 'G', 'G', 'L', 'L', 'G' ],
|
|
38
|
+
[ 'L', 'G', 'G', 'G', 'L', 'L' ],
|
|
39
|
+
[ 'L', 'G', 'L', 'G', 'L', 'G' ],
|
|
40
|
+
[ 'L', 'G', 'L', 'G', 'G', 'L' ],
|
|
41
|
+
[ 'L', 'G', 'G', 'L', 'G', 'L' ],
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Pattern used to encode the 5-characters addition to an EAN13 depending on
|
|
46
|
+
* the addition's own checksum (modulo 10).
|
|
47
|
+
*/
|
|
48
|
+
const PATTERNS_ADDON5: Record<Digit, [ Pattern, Pattern, Pattern, Pattern, Pattern ]> = [
|
|
49
|
+
[ 'G', 'G', 'L', 'L', 'L' ],
|
|
50
|
+
[ 'G', 'L', 'G', 'L', 'L' ],
|
|
51
|
+
[ 'G', 'L', 'L', 'G', 'L' ],
|
|
52
|
+
[ 'G', 'L', 'L', 'L', 'G' ],
|
|
53
|
+
[ 'L', 'G', 'G', 'L', 'L' ],
|
|
54
|
+
[ 'L', 'L', 'G', 'G', 'L' ],
|
|
55
|
+
[ 'L', 'L', 'L', 'G', 'G' ],
|
|
56
|
+
[ 'L', 'G', 'L', 'G', 'L' ],
|
|
57
|
+
[ 'L', 'G', 'L', 'L', 'G' ],
|
|
58
|
+
[ 'L', 'L', 'G', 'L', 'G' ],
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Pattern used to encode the 2-digits addition to an EAN13 depending on its
|
|
63
|
+
* digits checksum modulo 4.
|
|
64
|
+
*/
|
|
65
|
+
const PATTERNS_ADDON2: Record<number, [ Pattern, Pattern]> = [
|
|
66
|
+
[ 'L', 'L' ],
|
|
67
|
+
[ 'L', 'G' ],
|
|
68
|
+
[ 'G', 'L' ],
|
|
69
|
+
[ 'G', 'G' ],
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
/** The separator character in our font (middle of the barcode) */
|
|
73
|
+
const SEPARATOR = '*'
|
|
74
|
+
/** The barcode start character in our font */
|
|
75
|
+
const START = ':'
|
|
76
|
+
/** The barcode end character in our font */
|
|
77
|
+
const END = '+'
|
|
78
|
+
/** The character indicating the start of the addon part */
|
|
79
|
+
const ADDON_START = '['
|
|
80
|
+
/** The character separating each character in the addon part */
|
|
81
|
+
const ADDON_SEPARATOR = '\\'
|
|
82
|
+
|
|
83
|
+
/* ========================================================================== *
|
|
84
|
+
* ENCODING *
|
|
85
|
+
* ========================================================================== */
|
|
86
|
+
|
|
87
|
+
/** Encode an EAN13 (trusting the array is 13-digits long) */
|
|
88
|
+
function encodeEan13(digits: Digit[]): string[] {
|
|
89
|
+
const pattern = PATTERNS_EAN13[digits[0]!]
|
|
90
|
+
|
|
91
|
+
return [
|
|
92
|
+
digits[0]!.toString(), // this is to encode the first digit as text
|
|
93
|
+
CODESETS[pattern[0]][digits[1]!],
|
|
94
|
+
CODESETS[pattern[1]][digits[2]!],
|
|
95
|
+
CODESETS[pattern[2]][digits[3]!],
|
|
96
|
+
CODESETS[pattern[3]][digits[4]!],
|
|
97
|
+
CODESETS[pattern[4]][digits[5]!],
|
|
98
|
+
CODESETS[pattern[5]][digits[6]!],
|
|
99
|
+
SEPARATOR,
|
|
100
|
+
CODESET_R[digits[7]!],
|
|
101
|
+
CODESET_R[digits[8]!],
|
|
102
|
+
CODESET_R[digits[9]!],
|
|
103
|
+
CODESET_R[digits[10]!],
|
|
104
|
+
CODESET_R[digits[11]!],
|
|
105
|
+
CODESET_R[digits[12]!],
|
|
106
|
+
END,
|
|
107
|
+
]
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Encode an EAN8 (trusting the array is 8-digits long) */
|
|
111
|
+
function encodeEan8(digits: Digit[]): string[] {
|
|
112
|
+
return [
|
|
113
|
+
START,
|
|
114
|
+
CODESET_L[digits[0]!],
|
|
115
|
+
CODESET_L[digits[1]!],
|
|
116
|
+
CODESET_L[digits[2]!],
|
|
117
|
+
CODESET_L[digits[3]!],
|
|
118
|
+
SEPARATOR,
|
|
119
|
+
CODESET_R[digits[4]!],
|
|
120
|
+
CODESET_R[digits[5]!],
|
|
121
|
+
CODESET_R[digits[6]!],
|
|
122
|
+
CODESET_R[digits[7]!],
|
|
123
|
+
END,
|
|
124
|
+
]
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Encode the 5-digits additional component of an EAN */
|
|
128
|
+
function encodeAdd5(digits: Digit[]): string[] {
|
|
129
|
+
const checksum = (
|
|
130
|
+
((digits[0]! + digits[2]! + digits[4]!) * 3) +
|
|
131
|
+
((digits[1]! + digits[3]!) * 9)
|
|
132
|
+
) % 10 as Digit
|
|
133
|
+
const pattern = PATTERNS_ADDON5[checksum]
|
|
134
|
+
|
|
135
|
+
return [
|
|
136
|
+
ADDON_START,
|
|
137
|
+
CODESETS[pattern[0]][digits[0]!], ADDON_SEPARATOR,
|
|
138
|
+
CODESETS[pattern[1]][digits[1]!], ADDON_SEPARATOR,
|
|
139
|
+
CODESETS[pattern[2]][digits[2]!], ADDON_SEPARATOR,
|
|
140
|
+
CODESETS[pattern[3]][digits[3]!], ADDON_SEPARATOR,
|
|
141
|
+
CODESETS[pattern[4]][digits[4]!],
|
|
142
|
+
]
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Encode the 5-digits additional component of an EAN */
|
|
146
|
+
function encodeAdd2(digits: Digit[]): string[] {
|
|
147
|
+
const checksum = ((digits[0]! * 10) + digits[1]!) % 4
|
|
148
|
+
const pattern = PATTERNS_ADDON2[checksum]!
|
|
149
|
+
|
|
150
|
+
return [
|
|
151
|
+
ADDON_START,
|
|
152
|
+
CODESETS[pattern[0]][digits[0]!],
|
|
153
|
+
ADDON_SEPARATOR,
|
|
154
|
+
CODESETS[pattern[1]][digits[1]!],
|
|
155
|
+
]
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Convert a string to an array of digits */
|
|
159
|
+
function toDigits(string: string): Digit[] {
|
|
160
|
+
const digits: Digit[] = []
|
|
161
|
+
for (let i = 0; i < string.length; i ++) {
|
|
162
|
+
const digit = string.charCodeAt(i) - 48
|
|
163
|
+
if ((digit < 0) || (digit > 9)) {
|
|
164
|
+
throw new Error(`Invalid character to encode "${string[i]}"`)
|
|
165
|
+
} else {
|
|
166
|
+
digits.push(digit as Digit)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return digits
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** Encode an EAN8/EAN13 with potential 2-digits or 5-digits extensions */
|
|
173
|
+
export function ean(string: string): string {
|
|
174
|
+
const digits = toDigits(string)
|
|
175
|
+
|
|
176
|
+
switch (digits.length) {
|
|
177
|
+
case 18: // EAN13 + 5
|
|
178
|
+
return [
|
|
179
|
+
...encodeEan13(digits.slice(0, 13)),
|
|
180
|
+
...encodeAdd5(digits.slice(13)),
|
|
181
|
+
].join('')
|
|
182
|
+
|
|
183
|
+
case 17: // UPC-A + 5
|
|
184
|
+
return [
|
|
185
|
+
...encodeEan13([ 0, ...digits.slice(0, 12) ]),
|
|
186
|
+
...encodeAdd5(digits.slice(12)),
|
|
187
|
+
].join('')
|
|
188
|
+
|
|
189
|
+
case 15: // EAN13 + 2
|
|
190
|
+
return [
|
|
191
|
+
...encodeEan13(digits.slice(0, 13)),
|
|
192
|
+
...encodeAdd2(digits.slice(13)),
|
|
193
|
+
].join('')
|
|
194
|
+
|
|
195
|
+
case 14: // UPC-A + 2
|
|
196
|
+
return [
|
|
197
|
+
...encodeEan13([ 0, ...digits.slice(0, 12) ]),
|
|
198
|
+
...encodeAdd2(digits.slice(12)),
|
|
199
|
+
].join('')
|
|
200
|
+
|
|
201
|
+
case 13: // EAN13
|
|
202
|
+
return encodeEan13(digits).join('')
|
|
203
|
+
|
|
204
|
+
case 12: // UPC-A
|
|
205
|
+
return encodeEan13([ 0, ...digits ]).join('')
|
|
206
|
+
|
|
207
|
+
case 8: // EAN8
|
|
208
|
+
return encodeEan8(digits).join('')
|
|
209
|
+
|
|
210
|
+
case 5: // 5-digits addition
|
|
211
|
+
return encodeAdd5(digits).join('')
|
|
212
|
+
|
|
213
|
+
case 2: // 2-digits addition
|
|
214
|
+
return encodeAdd2(digits).join('')
|
|
215
|
+
|
|
216
|
+
default:
|
|
217
|
+
throw new Error(`Unable to encode string ${digits.length} characters long`)
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
export function checksum(string: string): Digit {
|
|
223
|
+
const digits = toDigits(string)
|
|
224
|
+
|
|
225
|
+
// insert a zero if the number of digits is odd to properly calculate sums
|
|
226
|
+
if (digits.length % 2) digits.unshift(0)
|
|
227
|
+
|
|
228
|
+
// calculate the sum of the digits (multiply by 3 the even ones)
|
|
229
|
+
let sum = 0
|
|
230
|
+
for (let i = 0; i < digits.length; i ++) {
|
|
231
|
+
sum += digits[i]! * ((i % 2) ? 3 : 1)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return (10 - (sum % 10)) % 10 as Digit
|
|
235
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { FNC1, FNC2, FNC3, FNC4, code128 } from './code128'
|
|
2
|
+
export { checksum, ean } from './ean'
|
|
3
|
+
|
|
4
|
+
const here = new URL(__fileurl, 'file:///')
|
|
5
|
+
|
|
6
|
+
export const fonts = {
|
|
7
|
+
'LibreBarcode128-Regular.ttf': new URL('../assets/LibreBarcode128-Regular.ttf', here).pathname,
|
|
8
|
+
'LibreBarcode128Text-Regular.ttf': new URL('../assets/LibreBarcode128Text-Regular.ttf', here).pathname,
|
|
9
|
+
'LibreBarcodeEAN13Text-Regular.ttf': new URL('../assets/LibreBarcodeEAN13Text-Regular.ttf', here).pathname,
|
|
10
|
+
}
|