@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.
Files changed (53) hide show
  1. package/LICENSE +215 -0
  2. package/NOTICE.md +106 -0
  3. package/README.md +433 -0
  4. package/bundle/sythos-barcode.esm.js +7998 -0
  5. package/bundle/sythos-barcode.js +7948 -0
  6. package/examples/create.html +731 -0
  7. package/examples/read.html +341 -0
  8. package/licenses/README.md +42 -0
  9. package/licenses/codabar.license +74 -0
  10. package/licenses/code-11.license +69 -0
  11. package/licenses/code-128.license +69 -0
  12. package/licenses/code-39.license +70 -0
  13. package/licenses/code-93.license +71 -0
  14. package/licenses/ean-13.license +70 -0
  15. package/licenses/ean-8.license +70 -0
  16. package/licenses/gs1-128.license +71 -0
  17. package/licenses/isbn.license +76 -0
  18. package/licenses/itf-14.license +69 -0
  19. package/licenses/itf.license +70 -0
  20. package/licenses/msi-plessey.license +72 -0
  21. package/licenses/pharmacode.license +71 -0
  22. package/licenses/qr-code.license +75 -0
  23. package/licenses/upc-a.license +72 -0
  24. package/licenses/upc-e.license +69 -0
  25. package/package.json +89 -0
  26. package/src/core/bit-buffer.js +174 -0
  27. package/src/core/bit-matrix.js +241 -0
  28. package/src/core/errors.js +61 -0
  29. package/src/core/galois-field.js +204 -0
  30. package/src/core/index.js +56 -0
  31. package/src/core/reed-solomon.js +313 -0
  32. package/src/image/binarizer.js +270 -0
  33. package/src/image/grid-sampler.js +164 -0
  34. package/src/image/index.js +40 -0
  35. package/src/image/luminance.js +196 -0
  36. package/src/image/perspective.js +195 -0
  37. package/src/index.js +240 -0
  38. package/src/oned/index.js +89 -0
  39. package/src/oned/patterns.js +384 -0
  40. package/src/oned/reader.js +918 -0
  41. package/src/oned/writers.js +741 -0
  42. package/src/qr/decoder.js +575 -0
  43. package/src/qr/detector.js +630 -0
  44. package/src/qr/encoder.js +958 -0
  45. package/src/qr/index.js +44 -0
  46. package/src/qr/tables.js +737 -0
  47. package/src/render/image-data.js +125 -0
  48. package/src/render/index.js +130 -0
  49. package/src/render/options.js +160 -0
  50. package/src/render/png.js +295 -0
  51. package/src/render/svg.js +120 -0
  52. package/src/render/webgl.js +206 -0
  53. package/src/render/webgpu.js +369 -0
@@ -0,0 +1,575 @@
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
+ * QR Code decoder.
33
+ *
34
+ * Input is a square {@link BitMatrix} that is exactly the symbol — one bit per
35
+ * module, no quiet zone. Locating and resampling a symbol out of a photograph
36
+ * is the detector's job; this module assumes that has already happened, which
37
+ * keeps the two independently testable.
38
+ *
39
+ * Both BCH-protected fields — format information and version information — are
40
+ * recovered by nearest-neighbour lookup over the full set of legal codewords
41
+ * rather than by running a syndrome decoder. There are only 32 and 34 of them
42
+ * respectively, the minimum distance is known, and a Hamming search is both
43
+ * shorter and easier to be sure of than a second BCH implementation.
44
+ *
45
+ * @module qr/decoder
46
+ */
47
+
48
+ import { BitReader } from '../core/bit-buffer.js';
49
+ import { ChecksumError, FormatError } from '../core/errors.js';
50
+ import { GF256_QR } from '../core/galois-field.js';
51
+ import { rsDecode } from '../core/reed-solomon.js';
52
+ import {
53
+ ECC_LEVELS,
54
+ MAX_VERSION,
55
+ MIN_VERSION,
56
+ MODE,
57
+ VERSION_INFO_MIN,
58
+ blockLayout,
59
+ countBits,
60
+ dataModuleOrder,
61
+ formatInfoPositions,
62
+ maskBit,
63
+ versionSize,
64
+ } from './tables.js';
65
+ import { ALPHANUMERIC_CHARS, formatInfoBits, versionInfoBits } from './encoder.js';
66
+
67
+ /**
68
+ * Maximum bit errors tolerated when matching a BCH field.
69
+ *
70
+ * Both codes have minimum distance 7 in principle, but the format information
71
+ * is only guaranteed distance 7 across the whole set once masking is applied;
72
+ * accepting three errors is the conventional, safe limit, and a wrong match
73
+ * would be caught downstream by Reed-Solomon anyway.
74
+ */
75
+ const BCH_MAX_DISTANCE = 3;
76
+
77
+ /** Every legal masked format value, with what it means. */
78
+ const FORMAT_CODES = (() => {
79
+ const codes = [];
80
+ for (let l = 0; l < ECC_LEVELS.length; l++) {
81
+ for (let mask = 0; mask < 8; mask++) {
82
+ codes.push({ bits: formatInfoBits(ECC_LEVELS[l], mask), ecc: ECC_LEVELS[l], mask });
83
+ }
84
+ }
85
+ return codes;
86
+ })();
87
+
88
+ /** Every legal version information value. */
89
+ const VERSION_CODES = (() => {
90
+ const codes = [];
91
+ for (let v = VERSION_INFO_MIN; v <= MAX_VERSION; v++) {
92
+ codes.push({ bits: versionInfoBits(v), version: v });
93
+ }
94
+ return codes;
95
+ })();
96
+
97
+ /**
98
+ * @param {number} a @param {number} b
99
+ * @returns {number} Number of differing bits.
100
+ */
101
+ function hammingDistance(a, b) {
102
+ let v = a ^ b;
103
+ let n = 0;
104
+ while (v !== 0) {
105
+ v &= v - 1;
106
+ n++;
107
+ }
108
+ return n;
109
+ }
110
+
111
+ /**
112
+ * Nearest legal codeword, or null when nothing is close enough.
113
+ *
114
+ * @template T
115
+ * @param {number} value
116
+ * @param {Array<T & {bits: number}>} codes
117
+ * @returns {(T & {distance: number}) | null}
118
+ */
119
+ function nearestCode(value, codes) {
120
+ let best = null;
121
+ let bestDistance = BCH_MAX_DISTANCE + 1;
122
+ let ambiguous = false;
123
+
124
+ for (let i = 0; i < codes.length; i++) {
125
+ const d = hammingDistance(value, codes[i].bits);
126
+ if (d < bestDistance) {
127
+ bestDistance = d;
128
+ best = codes[i];
129
+ ambiguous = false;
130
+ } else if (d === bestDistance) {
131
+ ambiguous = true;
132
+ }
133
+ }
134
+
135
+ if (!best || bestDistance > BCH_MAX_DISTANCE || ambiguous) return null;
136
+ return Object.assign({ distance: bestDistance }, best);
137
+ }
138
+
139
+ /**
140
+ * Read a run of module positions as a little-endian bit value.
141
+ *
142
+ * @param {import('../core/bit-matrix.js').BitMatrix} m
143
+ * @param {Array<[number, number]>} positions Index i holds bit i.
144
+ * @returns {number}
145
+ */
146
+ function readBits(m, positions) {
147
+ let value = 0;
148
+ for (let i = 0; i < positions.length; i++) {
149
+ if (m.get(positions[i][0], positions[i][1])) value |= 1 << i;
150
+ }
151
+ return value;
152
+ }
153
+
154
+ /**
155
+ * Recover the error correction level and mask.
156
+ *
157
+ * Both copies are tried and the cleaner one wins, so a symbol with one corner
158
+ * damaged still reads.
159
+ *
160
+ * @param {import('../core/bit-matrix.js').BitMatrix} m
161
+ * @param {number} size
162
+ * @returns {{ecc: string, mask: number}}
163
+ */
164
+ function readFormatInfo(m, size) {
165
+ const [a, b] = formatInfoPositions(size);
166
+ const candidates = [nearestCode(readBits(m, a), FORMAT_CODES), nearestCode(readBits(m, b), FORMAT_CODES)];
167
+
168
+ let best = null;
169
+ for (let i = 0; i < candidates.length; i++) {
170
+ const c = candidates[i];
171
+ if (c && (!best || c.distance < best.distance)) best = c;
172
+ }
173
+ if (!best) {
174
+ throw new FormatError('QR: format information is unreadable in both copies');
175
+ }
176
+ return { ecc: best.ecc, mask: best.mask };
177
+ }
178
+
179
+ /**
180
+ * Cross-check the version information against the symbol's dimension.
181
+ *
182
+ * The dimension already determines the version, so this is redundancy rather
183
+ * than information — which is exactly why it is worth reading: a disagreement
184
+ * means the matrix handed to us is not the symbol we think it is.
185
+ *
186
+ * @param {import('../core/bit-matrix.js').BitMatrix} m
187
+ * @param {number} size @param {number} fromDimension
188
+ * @returns {number}
189
+ */
190
+ function readVersionInfo(m, size, fromDimension) {
191
+ if (fromDimension < VERSION_INFO_MIN) return fromDimension;
192
+
193
+ /** @type {Array<[number, number]>} */
194
+ const bottomLeft = [];
195
+ /** @type {Array<[number, number]>} */
196
+ const topRight = [];
197
+ for (let i = 0; i < 18; i++) {
198
+ const major = Math.floor(i / 3);
199
+ const minor = i % 3;
200
+ bottomLeft.push([major, size - 11 + minor]);
201
+ topRight.push([size - 11 + minor, major]);
202
+ }
203
+
204
+ const candidates = [
205
+ nearestCode(readBits(m, bottomLeft), VERSION_CODES),
206
+ nearestCode(readBits(m, topRight), VERSION_CODES),
207
+ ];
208
+
209
+ let best = null;
210
+ for (let i = 0; i < candidates.length; i++) {
211
+ const c = candidates[i];
212
+ if (c && (!best || c.distance < best.distance)) best = c;
213
+ }
214
+
215
+ // Unreadable version information is survivable; contradictory version
216
+ // information is not.
217
+ if (!best) return fromDimension;
218
+ if (best.version !== fromDimension) {
219
+ throw new FormatError(
220
+ `QR: version information says ${best.version} but the symbol is ` +
221
+ `${size}x${size} modules (version ${fromDimension})`
222
+ );
223
+ }
224
+ return best.version;
225
+ }
226
+
227
+ /**
228
+ * Unmask and read the interleaved codewords out of the module grid.
229
+ *
230
+ * @param {import('../core/bit-matrix.js').BitMatrix} m
231
+ * @param {number} version @param {number} mask @param {number} totalCodewords
232
+ * @returns {Uint8Array}
233
+ */
234
+ function readCodewords(m, version, mask, totalCodewords) {
235
+ const order = dataModuleOrder(version);
236
+ const out = new Uint8Array(totalCodewords);
237
+ const available = totalCodewords * 8;
238
+
239
+ for (let p = 0, bit = 0; p < order.length && bit < available; p += 2, bit++) {
240
+ const x = order[p];
241
+ const y = order[p + 1];
242
+ let dark = m.get(x, y);
243
+ if (maskBit(mask, x, y)) dark = !dark;
244
+ if (dark) out[bit >> 3] |= 0x80 >> (bit & 7);
245
+ }
246
+
247
+ return out;
248
+ }
249
+
250
+ /**
251
+ * Undo the block interleaving and repair each block.
252
+ *
253
+ * @param {Uint8Array} codewords
254
+ * @param {import('./tables.js').BlockLayout} layout
255
+ * @returns {{data: Uint8Array, corrections: number}}
256
+ */
257
+ function deinterleaveAndCorrect(codewords, layout) {
258
+ const counts = new Array(layout.blockCount);
259
+ for (let b = 0; b < layout.blockCount; b++) {
260
+ counts[b] = b < layout.group1Blocks ? layout.group1DataCount : layout.group2DataCount;
261
+ }
262
+
263
+ const blocks = [];
264
+ for (let b = 0; b < layout.blockCount; b++) {
265
+ blocks.push(new Array(counts[b] + layout.eccPerBlock).fill(0));
266
+ }
267
+
268
+ let n = 0;
269
+ const maxData = layout.group2Blocks > 0 ? layout.group2DataCount : layout.group1DataCount;
270
+ for (let i = 0; i < maxData; i++) {
271
+ for (let b = 0; b < layout.blockCount; b++) {
272
+ if (i < counts[b]) blocks[b][i] = codewords[n++];
273
+ }
274
+ }
275
+ for (let i = 0; i < layout.eccPerBlock; i++) {
276
+ for (let b = 0; b < layout.blockCount; b++) {
277
+ blocks[b][counts[b] + i] = codewords[n++];
278
+ }
279
+ }
280
+
281
+ const data = new Uint8Array(layout.totalDataCodewords);
282
+ let offset = 0;
283
+ let corrections = 0;
284
+
285
+ for (let b = 0; b < layout.blockCount; b++) {
286
+ corrections += rsDecode(blocks[b], layout.eccPerBlock, GF256_QR, 0);
287
+ for (let i = 0; i < counts[b]; i++) data[offset + i] = blocks[b][i];
288
+ offset += counts[b];
289
+ }
290
+
291
+ return { data, corrections };
292
+ }
293
+
294
+ /* ------------------------------------------------------------------ *
295
+ * Bitstream interpretation
296
+ * ------------------------------------------------------------------ */
297
+
298
+ /**
299
+ * Unpack a 13-bit kanji value back to Shift_JIS. Inverse of the encoder's
300
+ * `sjisToThirteenBits`; the round-trip is asserted by the test suite.
301
+ *
302
+ * @param {number} value
303
+ * @returns {number} 16-bit Shift_JIS value.
304
+ */
305
+ function thirteenBitsToSjis(value) {
306
+ const combined = (Math.floor(value / 0xc0) << 8) | (value % 0xc0);
307
+ return combined + (combined + 0x8140 <= 0x9ffc ? 0x8140 : 0xc140);
308
+ }
309
+
310
+ /** ECI assignment numbers this decoder maps to a concrete codec label. */
311
+ const ECI_LABELS = {
312
+ 0: 'iso-8859-1',
313
+ 1: 'iso-8859-1',
314
+ 2: 'iso-8859-1',
315
+ 3: 'iso-8859-1',
316
+ 4: 'iso-8859-2',
317
+ 5: 'iso-8859-3',
318
+ 6: 'iso-8859-4',
319
+ 7: 'iso-8859-5',
320
+ 8: 'iso-8859-6',
321
+ 9: 'iso-8859-7',
322
+ 10: 'iso-8859-8',
323
+ 11: 'iso-8859-9',
324
+ 12: 'iso-8859-10',
325
+ 13: 'iso-8859-11',
326
+ 15: 'iso-8859-13',
327
+ 16: 'iso-8859-14',
328
+ 17: 'iso-8859-15',
329
+ 18: 'iso-8859-16',
330
+ 20: 'shift_jis',
331
+ 21: 'windows-1250',
332
+ 22: 'windows-1251',
333
+ 23: 'windows-1252',
334
+ 24: 'windows-1256',
335
+ 25: 'utf-16be',
336
+ 26: 'utf-8',
337
+ 27: 'us-ascii',
338
+ 28: 'big5',
339
+ 29: 'gb18030',
340
+ 30: 'euc-kr',
341
+ 170: 'us-ascii',
342
+ };
343
+
344
+ /**
345
+ * Turn a byte segment into text.
346
+ *
347
+ * With no ECI in force the interpretation is genuinely ambiguous — the default
348
+ * is ISO-8859-1, but the overwhelming majority of real symbols carry UTF-8
349
+ * without announcing it. So: accept UTF-8 when the bytes are valid UTF-8, and
350
+ * fall back to ISO-8859-1 when they are not. Bytes that are valid under both
351
+ * readings cannot be told apart by anyone, encoder included.
352
+ *
353
+ * @param {Uint8Array} bytes @param {number | null} eci
354
+ * @returns {string}
355
+ */
356
+ function decodeBytes(bytes, eci) {
357
+ if (eci !== null && eci !== undefined) {
358
+ const label = ECI_LABELS[eci];
359
+ if (label) {
360
+ try {
361
+ return new TextDecoder(label).decode(bytes);
362
+ } catch (e) {
363
+ /* Unsupported label on this platform; fall through. */
364
+ }
365
+ }
366
+ } else {
367
+ try {
368
+ return new TextDecoder('utf-8', { fatal: true }).decode(bytes);
369
+ } catch (e) {
370
+ /* Not valid UTF-8; it is Latin-1. */
371
+ }
372
+ }
373
+ return latin1(bytes);
374
+ }
375
+
376
+ /**
377
+ * @param {Uint8Array} bytes
378
+ * @returns {string}
379
+ */
380
+ function latin1(bytes) {
381
+ let s = '';
382
+ for (let i = 0; i < bytes.length; i++) s += String.fromCharCode(bytes[i]);
383
+ return s;
384
+ }
385
+
386
+ /**
387
+ * @param {Uint8Array} bytes Shift_JIS double bytes.
388
+ * @returns {string}
389
+ */
390
+ function decodeKanji(bytes) {
391
+ try {
392
+ const decoder = new TextDecoder('shift_jis');
393
+ const text = decoder.decode(bytes);
394
+ if (text.indexOf('�') === -1) return text;
395
+ } catch (e) {
396
+ /* No Shift_JIS codec on this platform. */
397
+ }
398
+ // Graceful degradation: the payload is structurally intact but we cannot
399
+ // name the characters, so mark them rather than failing the whole symbol.
400
+ let s = '';
401
+ for (let i = 0; i < bytes.length; i += 2) s += '�';
402
+ return s;
403
+ }
404
+
405
+ /**
406
+ * Read the ECI designator, which is 1, 2 or 3 bytes depending on its leading
407
+ * bits.
408
+ *
409
+ * @param {BitReader} reader
410
+ * @returns {number}
411
+ */
412
+ function readEciDesignator(reader) {
413
+ const first = reader.read(8);
414
+ if ((first & 0x80) === 0) return first;
415
+ if ((first & 0xc0) === 0x80) return ((first & 0x3f) << 8) | reader.read(8);
416
+ if ((first & 0xe0) === 0xc0) return ((first & 0x1f) << 16) | reader.read(16);
417
+ throw new FormatError(`QR: malformed ECI designator (first byte 0x${first.toString(16)})`);
418
+ }
419
+
420
+ /**
421
+ * Walk the mode segments and rebuild the payload.
422
+ *
423
+ * @param {Uint8Array} data Corrected data codewords.
424
+ * @param {number} version
425
+ * @returns {{text: string, bytes: Uint8Array}}
426
+ */
427
+ function parseSegments(data, version) {
428
+ const reader = new BitReader(data);
429
+ let text = '';
430
+ /** @type {number[]} */
431
+ const rawBytes = [];
432
+ /** @type {number | null} */
433
+ let eci = null;
434
+
435
+ // A symbol whose payload ends exactly on a codeword boundary has no room for
436
+ // a terminator, so running out of bits is a normal end, not an error.
437
+ while (reader.available() >= 4) {
438
+ const mode = reader.read(4);
439
+ if (mode === MODE.TERMINATOR) break;
440
+
441
+ if (mode === MODE.ECI) {
442
+ eci = readEciDesignator(reader);
443
+ continue;
444
+ }
445
+
446
+ if (mode === MODE.FNC1_FIRST) continue;
447
+ if (mode === MODE.FNC1_SECOND) {
448
+ reader.read(8); // application indicator
449
+ continue;
450
+ }
451
+ if (mode === MODE.STRUCTURED_APPEND) {
452
+ reader.read(16); // sequence position, total, parity
453
+ continue;
454
+ }
455
+
456
+ const width = countBits(mode, version);
457
+ if (width === 0) {
458
+ throw new FormatError(`QR: unsupported mode indicator 0x${mode.toString(16)}`);
459
+ }
460
+ const count = reader.read(width);
461
+
462
+ switch (mode) {
463
+ case MODE.NUMERIC: {
464
+ let i = 0;
465
+ while (i + 3 <= count) {
466
+ const triple = reader.read(10);
467
+ if (triple > 999) throw new FormatError(`QR: numeric triple ${triple} out of range`);
468
+ text += String(triple).padStart(3, '0');
469
+ i += 3;
470
+ }
471
+ if (count - i === 2) {
472
+ const pair = reader.read(7);
473
+ if (pair > 99) throw new FormatError(`QR: numeric pair ${pair} out of range`);
474
+ text += String(pair).padStart(2, '0');
475
+ } else if (count - i === 1) {
476
+ const single = reader.read(4);
477
+ if (single > 9) throw new FormatError(`QR: numeric digit ${single} out of range`);
478
+ text += String(single);
479
+ }
480
+ break;
481
+ }
482
+
483
+ case MODE.ALPHANUMERIC: {
484
+ let i = 0;
485
+ while (i + 2 <= count) {
486
+ const pair = reader.read(11);
487
+ if (pair >= 45 * 45) throw new FormatError(`QR: alphanumeric pair ${pair} out of range`);
488
+ text += ALPHANUMERIC_CHARS[Math.floor(pair / 45)] + ALPHANUMERIC_CHARS[pair % 45];
489
+ i += 2;
490
+ }
491
+ if (i < count) {
492
+ const single = reader.read(6);
493
+ if (single >= 45) throw new FormatError(`QR: alphanumeric value ${single} out of range`);
494
+ text += ALPHANUMERIC_CHARS[single];
495
+ }
496
+ break;
497
+ }
498
+
499
+ case MODE.BYTE: {
500
+ const bytes = new Uint8Array(count);
501
+ for (let i = 0; i < count; i++) {
502
+ bytes[i] = reader.read(8);
503
+ rawBytes.push(bytes[i]);
504
+ }
505
+ text += decodeBytes(bytes, eci);
506
+ break;
507
+ }
508
+
509
+ case MODE.KANJI: {
510
+ const bytes = new Uint8Array(count * 2);
511
+ for (let i = 0; i < count; i++) {
512
+ const sjis = thirteenBitsToSjis(reader.read(13));
513
+ bytes[i * 2] = sjis >> 8;
514
+ bytes[i * 2 + 1] = sjis & 0xff;
515
+ }
516
+ text += decodeKanji(bytes);
517
+ break;
518
+ }
519
+
520
+ default:
521
+ throw new FormatError(`QR: unsupported mode indicator 0x${mode.toString(16)}`);
522
+ }
523
+ }
524
+
525
+ return { text, bytes: Uint8Array.from(rawBytes) };
526
+ }
527
+
528
+ /**
529
+ * @typedef {object} DecodeResult
530
+ * @property {string} text Decoded payload.
531
+ * @property {Uint8Array} bytes Raw bytes of the byte-mode segments; empty when
532
+ * the payload used no byte segments.
533
+ * @property {number} version 1-40.
534
+ * @property {string} ecc 'L' | 'M' | 'Q' | 'H'.
535
+ * @property {number} mask 0-7.
536
+ * @property {number} corrections Symbols repaired by Reed-Solomon.
537
+ */
538
+
539
+ /**
540
+ * Decode a sampled QR Code symbol.
541
+ *
542
+ * @param {import('../core/bit-matrix.js').BitMatrix} matrix Square, exactly the
543
+ * symbol, no quiet zone. Set bit = dark module.
544
+ * @returns {DecodeResult}
545
+ * @throws {FormatError} If the geometry or content is malformed.
546
+ * @throws {ChecksumError} If error correction cannot repair the symbol.
547
+ */
548
+ export function decodeQR(matrix) {
549
+ if (!matrix || !matrix.width) throw new FormatError('QR: no matrix supplied');
550
+
551
+ const size = matrix.width;
552
+ if (matrix.height !== size) {
553
+ throw new FormatError(`QR: symbol must be square, got ${size}x${matrix.height}`);
554
+ }
555
+ if ((size - 17) % 4 !== 0) {
556
+ throw new FormatError(`QR: ${size} modules is not a valid symbol size`);
557
+ }
558
+
559
+ const dimensionVersion = (size - 17) / 4;
560
+ if (dimensionVersion < MIN_VERSION || dimensionVersion > MAX_VERSION) {
561
+ throw new FormatError(`QR: ${size} modules implies version ${dimensionVersion}`);
562
+ }
563
+
564
+ const { ecc, mask } = readFormatInfo(matrix, size);
565
+ const version = readVersionInfo(matrix, size, dimensionVersion);
566
+
567
+ const layout = blockLayout(version, ecc);
568
+ const codewords = readCodewords(matrix, version, mask, layout.totalCodewords);
569
+ const { data, corrections } = deinterleaveAndCorrect(codewords, layout);
570
+ const { text, bytes } = parseSegments(data, version);
571
+
572
+ return { text, bytes, version, ecc, mask, corrections };
573
+ }
574
+
575
+ export { ChecksumError };