@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/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
+ }