@sythos/js_barcode_universal 1.5.8 → 1.5.10

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 (179) hide show
  1. package/LICENSE +5 -4
  2. package/NOTICE.md +2 -2
  3. package/README.md +61 -13
  4. package/bundle/sythos-barcode.esm.js +11658 -11244
  5. package/bundle/sythos-barcode.js +11658 -11244
  6. package/package.json +10 -3
  7. package/src/index.d.ts +1 -1
  8. package/src/index.js +508 -516
  9. package/src/js/aztec/decoder.js +208 -199
  10. package/src/js/aztec/detector.js +153 -126
  11. package/src/js/aztec/encoder.js +172 -153
  12. package/src/js/aztec/high-level.js +106 -101
  13. package/src/js/aztec/index.js +1 -12
  14. package/src/js/aztec/tables.js +106 -120
  15. package/src/js/aztecrune/decoder.js +76 -85
  16. package/src/js/aztecrune/detector.js +107 -93
  17. package/src/js/aztecrune/encoder.js +50 -68
  18. package/src/js/aztecrune/index.js +1 -16
  19. package/src/js/aztecrune/tables.js +59 -54
  20. package/src/js/compactpdf417/decoder.js +69 -74
  21. package/src/js/compactpdf417/detector.js +74 -75
  22. package/src/js/compactpdf417/encoder.js +68 -81
  23. package/src/js/compactpdf417/index.js +1 -13
  24. package/src/js/compactpdf417/tables.js +71 -80
  25. package/src/js/core/bit-buffer.js +106 -122
  26. package/src/js/core/bit-matrix.js +196 -194
  27. package/src/js/core/errors.js +13 -15
  28. package/src/js/core/galois-field.js +119 -133
  29. package/src/js/core/index.js +2 -19
  30. package/src/js/core/reed-solomon.js +195 -204
  31. package/src/js/databar/codec.js +93 -108
  32. package/src/js/databar/decoder.js +149 -140
  33. package/src/js/databar/encoder.js +47 -55
  34. package/src/js/databar/gs1.js +122 -123
  35. package/src/js/databar/index.js +4 -36
  36. package/src/js/databar/patterns.js +81 -85
  37. package/src/js/databar/tables.js +51 -52
  38. package/src/js/datamatrix/decoder.js +216 -182
  39. package/src/js/datamatrix/detector.js +161 -132
  40. package/src/js/datamatrix/encoder.js +192 -133
  41. package/src/js/datamatrix/index.js +1 -9
  42. package/src/js/datamatrix/tables.js +68 -69
  43. package/src/js/frameqr/decoder.js +102 -125
  44. package/src/js/frameqr/detector.js +101 -94
  45. package/src/js/frameqr/encoder.js +67 -80
  46. package/src/js/frameqr/index.js +1 -10
  47. package/src/js/frameqr/tables.js +145 -163
  48. package/src/js/image/binarizer.js +146 -162
  49. package/src/js/image/grid-sampler.js +63 -79
  50. package/src/js/image/index.js +0 -2
  51. package/src/js/image/luminance.js +129 -140
  52. package/src/js/image/perspective.js +112 -149
  53. package/src/js/micropdf417/compaction.js +64 -65
  54. package/src/js/micropdf417/decoder.js +106 -109
  55. package/src/js/micropdf417/detector.js +73 -70
  56. package/src/js/micropdf417/encoder.js +129 -154
  57. package/src/js/micropdf417/error-correction.js +6 -11
  58. package/src/js/micropdf417/index.js +2 -16
  59. package/src/js/micropdf417/tables.js +112 -99
  60. package/src/js/microqr/decoder.js +177 -182
  61. package/src/js/microqr/detector.js +240 -256
  62. package/src/js/microqr/encoder.js +239 -194
  63. package/src/js/microqr/index.js +0 -2
  64. package/src/js/microqr/tables.js +208 -200
  65. package/src/js/oned/addons.js +167 -191
  66. package/src/js/oned/index.js +27 -58
  67. package/src/js/oned/patterns.js +194 -213
  68. package/src/js/oned/reader.js +1003 -984
  69. package/src/js/oned/writers.js +411 -466
  70. package/src/js/pdf417/compaction.js +315 -222
  71. package/src/js/pdf417/decoder.js +71 -36
  72. package/src/js/pdf417/detector.js +377 -361
  73. package/src/js/pdf417/encoder.js +57 -46
  74. package/src/js/pdf417/error-correction.js +5 -8
  75. package/src/js/pdf417/tables.js +248 -250
  76. package/src/js/qr/decoder.js +311 -347
  77. package/src/js/qr/detector.js +378 -395
  78. package/src/js/qr/encoder.js +514 -599
  79. package/src/js/qr/index.js +0 -2
  80. package/src/js/qr/tables.js +375 -422
  81. package/src/js/render/image-data.js +57 -62
  82. package/src/js/render/index.js +21 -23
  83. package/src/js/render/options.js +82 -90
  84. package/src/js/render/png.js +144 -169
  85. package/src/js/render/svg.js +41 -44
  86. package/src/js/render/webgl.js +103 -109
  87. package/src/js/render/webgpu.js +205 -217
  88. package/src/js/rmqr/decoder.js +161 -53
  89. package/src/js/rmqr/detector.js +73 -41
  90. package/src/js/rmqr/encoder.js +253 -104
  91. package/src/js/rmqr/index.js +1 -5
  92. package/src/js/rmqr/tables.js +108 -85
  93. package/src/ts/aztec/decoder.ts +317 -0
  94. package/src/ts/aztec/detector.ts +224 -0
  95. package/src/ts/aztec/encoder.ts +257 -0
  96. package/src/ts/aztec/high-level.ts +211 -0
  97. package/src/ts/aztec/index.ts +45 -0
  98. package/src/ts/aztec/tables.ts +210 -0
  99. package/src/ts/aztecrune/decoder.ts +155 -0
  100. package/src/ts/aztecrune/detector.ts +166 -0
  101. package/src/ts/aztecrune/encoder.ts +121 -0
  102. package/src/ts/aztecrune/index.ts +49 -0
  103. package/src/ts/aztecrune/tables.ts +137 -0
  104. package/src/ts/compactpdf417/decoder.ts +128 -0
  105. package/src/ts/compactpdf417/detector.ts +139 -0
  106. package/src/ts/compactpdf417/encoder.ts +140 -0
  107. package/src/ts/compactpdf417/index.ts +17 -0
  108. package/src/ts/compactpdf417/tables.ts +173 -0
  109. package/src/ts/core/bit-buffer.ts +174 -0
  110. package/src/ts/core/bit-matrix.ts +241 -0
  111. package/src/ts/core/errors.ts +61 -0
  112. package/src/ts/core/galois-field.ts +207 -0
  113. package/src/ts/core/index.ts +56 -0
  114. package/src/ts/core/reed-solomon.ts +327 -0
  115. package/src/ts/databar/codec.ts +181 -0
  116. package/src/ts/databar/decoder.ts +215 -0
  117. package/src/ts/databar/encoder.ts +96 -0
  118. package/src/ts/databar/gs1.ts +177 -0
  119. package/src/ts/databar/index.ts +68 -0
  120. package/src/ts/databar/patterns.ts +143 -0
  121. package/src/ts/databar/tables.ts +124 -0
  122. package/src/ts/datamatrix/decoder.ts +262 -0
  123. package/src/ts/datamatrix/detector.ts +225 -0
  124. package/src/ts/datamatrix/encoder.ts +191 -0
  125. package/src/ts/datamatrix/index.ts +42 -0
  126. package/src/ts/datamatrix/tables.ts +123 -0
  127. package/src/ts/frameqr/decoder.ts +239 -0
  128. package/src/ts/frameqr/detector.ts +192 -0
  129. package/src/ts/frameqr/encoder.ts +156 -0
  130. package/src/ts/frameqr/index.ts +42 -0
  131. package/src/ts/frameqr/tables.ts +270 -0
  132. package/src/ts/image/binarizer.ts +270 -0
  133. package/src/ts/image/grid-sampler.ts +164 -0
  134. package/src/ts/image/index.ts +40 -0
  135. package/src/ts/image/luminance.ts +196 -0
  136. package/src/ts/image/perspective.ts +195 -0
  137. package/src/ts/index.d.ts +1 -1
  138. package/src/ts/index.ts +790 -0
  139. package/src/ts/micropdf417/compaction.ts +116 -0
  140. package/src/ts/micropdf417/decoder.ts +183 -0
  141. package/src/ts/micropdf417/detector.ts +149 -0
  142. package/src/ts/micropdf417/encoder.ts +209 -0
  143. package/src/ts/micropdf417/error-correction.ts +55 -0
  144. package/src/ts/micropdf417/index.ts +49 -0
  145. package/src/ts/micropdf417/tables.ts +184 -0
  146. package/src/ts/microqr/decoder.ts +245 -0
  147. package/src/ts/microqr/detector.ts +355 -0
  148. package/src/ts/microqr/encoder.ts +269 -0
  149. package/src/ts/microqr/index.ts +36 -0
  150. package/src/ts/microqr/tables.ts +316 -0
  151. package/src/ts/oned/addons.ts +420 -0
  152. package/src/ts/oned/index.ts +106 -0
  153. package/src/ts/oned/patterns.ts +384 -0
  154. package/src/ts/oned/reader.ts +1364 -0
  155. package/src/ts/oned/writers.ts +746 -0
  156. package/src/ts/pdf417/compaction.ts +298 -0
  157. package/src/ts/pdf417/decoder.ts +75 -0
  158. package/src/ts/pdf417/detector.ts +468 -0
  159. package/src/ts/pdf417/encoder.ts +91 -0
  160. package/src/ts/pdf417/error-correction.ts +47 -0
  161. package/src/ts/pdf417/index.ts +6 -0
  162. package/src/ts/pdf417/tables.ts +317 -0
  163. package/src/ts/qr/decoder.ts +575 -0
  164. package/src/ts/qr/detector.ts +630 -0
  165. package/src/ts/qr/encoder.ts +958 -0
  166. package/src/ts/qr/index.ts +44 -0
  167. package/src/ts/qr/tables.ts +737 -0
  168. package/src/ts/render/image-data.ts +125 -0
  169. package/src/ts/render/index.ts +130 -0
  170. package/src/ts/render/options.ts +160 -0
  171. package/src/ts/render/png.ts +295 -0
  172. package/src/ts/render/svg.ts +120 -0
  173. package/src/ts/render/webgl.ts +206 -0
  174. package/src/ts/render/webgpu.ts +369 -0
  175. package/src/ts/rmqr/decoder.ts +101 -0
  176. package/src/ts/rmqr/detector.ts +90 -0
  177. package/src/ts/rmqr/encoder.ts +172 -0
  178. package/src/ts/rmqr/index.ts +37 -0
  179. package/src/ts/rmqr/tables.ts +154 -0
@@ -0,0 +1,790 @@
1
+ /*!
2
+ * Sythos Barcode Suite
3
+ *
4
+ * MIT License
5
+ *
6
+ * Copyright (c) 2026 Sythos
7
+ * SPDX-FileCopyrightText: 2026 Sythos (https://www.sythos.net)
8
+ *
9
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
10
+ * of this software and associated documentation files (the "Software"), to deal
11
+ * in the Software without restriction, including without limitation the rights
12
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ * copies of the Software, and to permit persons to whom the Software is
14
+ * furnished to do so, subject to the following conditions:
15
+ *
16
+ * The above copyright notice and this permission notice shall be included in all
17
+ * copies or substantial portions of the Software.
18
+ *
19
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
+ * SOFTWARE.
26
+ *
27
+ * SPDX-License-Identifier: MIT
28
+ *
29
+ * Original work. No code from any other barcode implementation.
30
+ */
31
+
32
+ /**
33
+ * Sythos Barcode Suite — public API.
34
+ *
35
+ * Two functions carry the whole surface:
36
+ *
37
+ * encode(text, { format }) -> BitMatrix
38
+ * decode(image, { formats }) -> Result[]
39
+ *
40
+ * Everything else is a renderer or a format-specific escape hatch. The core is
41
+ * free of I/O and of any platform assumption: images go in as
42
+ * `{ data, width, height }` with RGBA bytes, which is exactly what `ImageData`
43
+ * is, so a canvas, an `OffscreenCanvas`, sharp, jimp and node-canvas all work
44
+ * without an adapter.
45
+ *
46
+ * @module @sythos/js_barcode_universal
47
+ */
48
+
49
+ import { BitMatrix } from './core/bit-matrix.js';
50
+ import { EncodeError, NotFoundError } from './core/errors.js';
51
+ import { LuminanceSource } from './image/luminance.js';
52
+ import { binarize } from './image/binarizer.js';
53
+ import { ONED_FORMATS } from './oned/index.js';
54
+ import { decodeOneD } from './oned/reader.js';
55
+ import * as datamatrix from './datamatrix/index.js';
56
+ import * as qr from './qr/index.js';
57
+ import * as aztec from './aztec/index.js';
58
+ import * as pdf417 from './pdf417/index.js';
59
+ import * as micropdf417 from './micropdf417/index.js';
60
+ import * as microqr from './microqr/index.js';
61
+ import * as rmqr from './rmqr/index.js';
62
+ import * as frameqr from './frameqr/index.js';
63
+ import * as aztecRune from './aztecrune/index.js';
64
+ import * as compactPdf417 from './compactpdf417/index.js';
65
+ import * as databar from './databar/index.js';
66
+
67
+ export { BitMatrix };
68
+ export {
69
+ BarcodeError, EncodeError, NotFoundError, FormatError, ChecksumError,
70
+ } from './core/errors.js';
71
+ export { LuminanceSource } from './image/luminance.js';
72
+ export { binarize, binarizeGlobal, binarizeHybrid } from './image/binarizer.js';
73
+ export * from './oned/index.js';
74
+ export { toSVG, toSVGDataURI } from './render/svg.js';
75
+ export { toImageData, toCanvas } from './render/image-data.js';
76
+ export { toPNG, toPNGDataURI } from './render/png.js';
77
+ export { renderToCanvasAuto, isWebGL2Available } from './render/index.js';
78
+ export { renderToCanvasAutoAsync, isWebGPUAvailable } from './render/index.js';
79
+ export { encodeQR, decodeQR, detectQR, detectAndDecodeQR } from './qr/index.js';
80
+ export {
81
+ encodeDataMatrix, decodeDataMatrix, detectDataMatrix, detectAndDecodeDataMatrix,
82
+ } from './datamatrix/index.js';
83
+ export { encodeAztec, decodeAztec, detectAztec, detectAndDecodeAztec } from './aztec/index.js';
84
+ export * from './aztecrune/index.js';
85
+ export { encodePDF417, decodePDF417, detectPDF417, detectAndDecodePDF417 } from './pdf417/index.js';
86
+ export * from './compactpdf417/index.js';
87
+ // DataBar exports include both the verified GTIN/data layer and the
88
+ // Omnidirectional/Truncated physical image path.
89
+ export * from './databar/index.js';
90
+ export {
91
+ encodeMicroPDF417, decodeMicroPDF417, detectMicroPDF417, detectAndDecodeMicroPDF417,
92
+ } from './micropdf417/index.js';
93
+ export { encodeMicroQR, decodeMicroQR, detectMicroQR, detectAndDecodeMicroQR } from './microqr/index.js';
94
+ export { encodeRMQR, decodeRMQR, detectRMQR, detectAndDecodeRMQR } from './rmqr/index.js';
95
+ export {
96
+ encodeFrameQR, decodeFrameQR, detectFrameQR, detectAndDecodeFrameQR,
97
+ } from './frameqr/index.js';
98
+
99
+ /**
100
+ * @typedef {object} FormatInfo
101
+ * @property {string} id
102
+ * @property {string} label
103
+ * @property {boolean} canWrite
104
+ * @property {boolean} canRead
105
+ * @property {'1D' | '2D'} kind
106
+ * @property {'supplement'} [role]
107
+ */
108
+
109
+ // Writing and reading a format are separate capabilities that can land at
110
+ // different times, so they are reported separately rather than collapsed into
111
+ // one "supported" flag that would be wrong in one direction or the other.
112
+ //
113
+ // Capability is probed rather than declared, so this stays correct whether the
114
+ // QR module is the full implementation or a stand-in: a module may opt out
115
+ // explicitly with QR_CAN_ENCODE/QR_CAN_DECODE, and is otherwise taken at face
116
+ // value.
117
+ const qrPresent = qr.QR_PLACEHOLDER !== true;
118
+ const qrCanEncode = qrPresent &&
119
+ typeof qr.encodeQR === 'function' && qr.QR_CAN_ENCODE !== false;
120
+ const qrCanDecode = qrPresent &&
121
+ typeof qr.detectAndDecodeQR === 'function' && qr.QR_CAN_DECODE !== false;
122
+ const dataMatrixCanEncode = typeof datamatrix.encodeDataMatrix === 'function';
123
+ const dataMatrixCanDecode = typeof datamatrix.detectAndDecodeDataMatrix === 'function';
124
+ const aztecCanEncode = typeof aztec.encodeAztec === 'function';
125
+ const aztecCanDecode = typeof aztec.detectAndDecodeAztec === 'function';
126
+ const pdf417CanEncode = typeof pdf417.encodePDF417 === 'function';
127
+ // The matrix decoder is complete, but automatic image localization is still
128
+ // limited to clean module-aligned symbols or an application-supplied
129
+ // quadrilateral. Keep the generic scanner capability opt-in until a
130
+ // perspective/noise corpus is passed.
131
+ const pdf417CanDecode = typeof pdf417.detectAndDecodePDF417 === 'function';
132
+ const microPdf417CanEncode = typeof micropdf417.encodeMicroPDF417 === 'function';
133
+ const microPdf417CanDecode = typeof micropdf417.detectAndDecodeMicroPDF417 === 'function';
134
+ const microQrCanEncode = typeof microqr.encodeMicroQR === 'function';
135
+ const microQrCanDecode = typeof microqr.detectAndDecodeMicroQR === 'function';
136
+ const rmqrCanEncode = typeof rmqr.encodeRMQR === 'function';
137
+ const rmqrCanDecode = typeof rmqr.detectAndDecodeRMQR === 'function';
138
+ const frameQrCanEncode = typeof frameqr.encodeFrameQR === 'function';
139
+ const frameQrCanDecode = typeof frameqr.detectAndDecodeFrameQR === 'function';
140
+ const aztecRuneCanEncode = typeof aztecRune.encodeAztecRune === 'function';
141
+ const aztecRuneCanDecode = typeof aztecRune.detectAndDecodeAztecRune === 'function';
142
+ const compactPdf417CanEncode = typeof compactPdf417.encodeCompactPDF417 === 'function';
143
+ const compactPdf417CanDecode = typeof compactPdf417.detectAndDecodeCompactPDF417 === 'function';
144
+ const dataBarCanEncode = typeof databar.encodeDataBar14 === 'function';
145
+ const dataBarCanDecode = typeof databar.decodeDataBar14Scanline === 'function';
146
+
147
+ /**
148
+ * Every format this build supports.
149
+ *
150
+ * Writing and reading are listed separately on purpose. Writing a symbology is
151
+ * a table lookup; reading one needs a detector that finds it in a photograph,
152
+ * which is far more work. The two lists legitimately differ, and saying so
153
+ * here is better than failing at call time.
154
+ *
155
+ * @returns {FormatInfo[]}
156
+ */
157
+ export function listFormats() {
158
+ const formats = Object.entries(ONED_FORMATS).map(([id, info]) => ({
159
+ id,
160
+ label: info.label,
161
+ canWrite: true,
162
+ canRead: info.readable,
163
+ kind: /** @type {'1D'} */ ('1D'),
164
+ ...(info.role ? { role: info.role } : {}),
165
+ }));
166
+
167
+ formats.push({
168
+ id: 'qr',
169
+ label: 'QR Code',
170
+ canWrite: qrCanEncode,
171
+ canRead: qrCanDecode,
172
+ kind: /** @type {'2D'} */ ('2D'),
173
+ });
174
+ formats.push({
175
+ id: 'datamatrix',
176
+ label: 'Data Matrix ECC 200',
177
+ canWrite: dataMatrixCanEncode,
178
+ canRead: dataMatrixCanDecode,
179
+ kind: /** @type {'2D'} */ ('2D'),
180
+ });
181
+ formats.push({
182
+ id: 'aztec',
183
+ label: 'Aztec Code',
184
+ canWrite: aztecCanEncode,
185
+ canRead: aztecCanDecode,
186
+ kind: /** @type {'2D'} */ ('2D'),
187
+ });
188
+ formats.push({
189
+ id: 'aztecrune',
190
+ label: 'Aztec Rune',
191
+ canWrite: aztecRuneCanEncode,
192
+ canRead: aztecRuneCanDecode,
193
+ kind: /** @type {'2D'} */ ('2D'),
194
+ });
195
+ formats.push({
196
+ id: 'pdf417',
197
+ label: 'PDF417',
198
+ canWrite: pdf417CanEncode,
199
+ canRead: pdf417CanDecode,
200
+ kind: /** @type {'2D'} */ ('2D'),
201
+ });
202
+ formats.push({
203
+ id: 'compactpdf417',
204
+ label: 'Compact PDF417',
205
+ canWrite: compactPdf417CanEncode,
206
+ canRead: compactPdf417CanDecode,
207
+ kind: /** @type {'2D'} */ ('2D'),
208
+ });
209
+ formats.push({
210
+ id: 'micropdf417',
211
+ label: 'MicroPDF417',
212
+ canWrite: microPdf417CanEncode,
213
+ canRead: microPdf417CanDecode,
214
+ kind: /** @type {'2D'} */ ('2D'),
215
+ });
216
+ formats.push({
217
+ id: 'microqr',
218
+ label: 'Micro QR Code',
219
+ canWrite: microQrCanEncode,
220
+ canRead: microQrCanDecode,
221
+ kind: /** @type {'2D'} */ ('2D'),
222
+ });
223
+ formats.push({
224
+ id: 'rmqr',
225
+ label: 'rMQR Code',
226
+ canWrite: rmqrCanEncode,
227
+ canRead: rmqrCanDecode,
228
+ kind: /** @type {'2D'} */ ('2D'),
229
+ });
230
+ formats.push({
231
+ id: 'frameqr',
232
+ label: 'Sythos Canvas QR profile',
233
+ canWrite: frameQrCanEncode,
234
+ canRead: frameQrCanDecode,
235
+ kind: /** @type {'2D'} */ ('2D'),
236
+ });
237
+ formats.push({
238
+ id: 'gs1databar14',
239
+ label: 'GS1 DataBar Omnidirectional / Truncated',
240
+ canWrite: dataBarCanEncode,
241
+ canRead: dataBarCanDecode,
242
+ kind: /** @type {'1D'} */ ('1D'),
243
+ });
244
+
245
+ return formats;
246
+ }
247
+
248
+ /**
249
+ * Encode a payload into a barcode matrix.
250
+ *
251
+ * The result is a `BitMatrix` where a set bit is a dark module, with no quiet
252
+ * zone — the renderers add that, because the right margin depends on the
253
+ * output medium. Linear symbols come back one module tall; height is a
254
+ * rendering decision, not an encoding one.
255
+ *
256
+ * @param {string | number} text
257
+ * @param {object} [options]
258
+ * @param {string} [options.format] Format id. Default 'qr'.
259
+ * @param {'L'|'M'|'Q'|'H'} [options.ecc] QR error-correction level.
260
+ * @param {number} [options.version] QR version, 1-40. Auto if omitted.
261
+ * @param {boolean} [options.checkDigit] Append a check digit, where optional.
262
+ * @param {boolean} [options.fullAscii] Code 39 extended encoding.
263
+ * @param {boolean} [options.gs1] Emit a leading FNC1.
264
+ * @param {number} [options.layers] Aztec layer count; automatic if omitted.
265
+ * @param {boolean} [options.compact] Force an Aztec Compact or Full symbol.
266
+ * @param {number} [options.eccPercent] Requested Aztec error-correction percentage.
267
+ * @param {number} [options.eccLevel] PDF417 error-correction level, 0-8.
268
+ * @param {number} [options.columns] PDF417 columns, 1-30.
269
+ * @param {number} [options.rows] PDF417 rows, 3-90.
270
+ * @param {number} [options.rowHeight] PDF417 row height in modules.
271
+ * @param {'auto'|'text'|'byte'|'numeric'} [options.compaction] PDF417 compaction mode.
272
+ * @param {number} [options.eci] MicroPDF417 byte-compaction ECI assignment (3 or 26).
273
+ * @param {number} [options.aspectRatio] Preferred MicroPDF417 symbol aspect ratio.
274
+ * @param {object} [options.canvas] Sythos Canvas QR artwork reservation.
275
+ * @param {'square'|'circle'|'diamond'} [options.canvas.shape] Canvas shape.
276
+ * @param {number} [options.canvas.size] Odd canvas size in QR modules.
277
+ * @param {number} [options.canvas.width] Canvas width in QR modules.
278
+ * @param {number} [options.canvas.height] Canvas height in QR modules.
279
+ * @param {number} [options.canvas.centerX] Canvas centre X in QR modules.
280
+ * @param {number} [options.canvas.centerY] Canvas centre Y in QR modules.
281
+ * @param {0|90|180|270} [options.canvas.angle] Canvas quarter-turn.
282
+ * @returns {BitMatrix}
283
+ */
284
+ export function encode(text, options = {}) {
285
+ const format = String(options.format ?? 'qr').toLowerCase();
286
+ const value = typeof text === 'number' ? String(text) : text;
287
+
288
+ if (format === 'qr' || format === 'qrcode') {
289
+ return qr.encodeQR(value, options);
290
+ }
291
+ if (format === 'datamatrix' || format === 'data-matrix') {
292
+ return datamatrix.encodeDataMatrix(value, options);
293
+ }
294
+ if (format === 'aztec' || format === 'aztec-code') {
295
+ return aztec.encodeAztec(value, options);
296
+ }
297
+ if (format === 'aztecrune' || format === 'aztec-rune' || format === 'rune') {
298
+ return aztecRune.encodeAztecRune(value, options);
299
+ }
300
+ if (format === 'pdf417' || format === 'pdf-417') {
301
+ return pdf417.encodePDF417(value, options);
302
+ }
303
+ if (format === 'compactpdf417' || format === 'compact-pdf417' || format === 'compact-pdf-417') {
304
+ return compactPdf417.encodeCompactPDF417(value, options);
305
+ }
306
+ if (format === 'micropdf417' || format === 'micro-pdf417' || format === 'micro-pdf-417') {
307
+ return micropdf417.encodeMicroPDF417(value, options);
308
+ }
309
+ if (format === 'microqr' || format === 'micro-qr') {
310
+ return microqr.encodeMicroQR(value, options);
311
+ }
312
+ if (format === 'rmqr' || format === 'r-mqr' || format === 'rectangular-micro-qr') {
313
+ return rmqr.encodeRMQR(value, options);
314
+ }
315
+ if (format === 'frameqr' || format === 'frame-qr' || format === 'canvas-qr') {
316
+ return frameqr.encodeFrameQR(value, options);
317
+ }
318
+ if (format === 'gs1databar14' || format === 'gs1-databar14' || format === 'databar') {
319
+ return databar.encodeDataBar14(value, options);
320
+ }
321
+
322
+ const entry = ONED_FORMATS[format];
323
+ if (!entry) {
324
+ const known = [...Object.keys(ONED_FORMATS), 'qr', 'datamatrix', 'aztec', 'aztecrune', 'pdf417', 'compactpdf417', 'micropdf417', 'microqr', 'rmqr', 'frameqr', 'gs1databar14'].join(', ');
325
+ throw new EncodeError(`Unknown format "${format}". Known formats: ${known}`);
326
+ }
327
+ return entry.encode(value, options);
328
+ }
329
+
330
+ /**
331
+ * @typedef {object} DecodeResult
332
+ * @property {string} text
333
+ * @property {string} format
334
+ * @property {Uint8Array} [bytes] Raw octets exposed by byte-oriented payload modes, before text decoding.
335
+ * @property {{mode: 'text'|'byte'|'numeric', text: string, bytes: Uint8Array, eci: number, latch: number|null, codewordStart: number, codewordEnd: number}[]} [segments] PDF417 compaction segments in source order.
336
+ * @property {number} [version] QR version.
337
+ * @property {string} [ecc] QR error-correction level.
338
+ * @property {number} [layers] Aztec layer count.
339
+ * @property {boolean} [compact] Whether an Aztec symbol is Compact.
340
+ * @property {number} [corrections] Reed–Solomon corrections applied by an Aztec decode.
341
+ * @property {number} [rows] PDF417 row count.
342
+ * @property {number} [columns] PDF417 column count.
343
+ * @property {number} [eccLevel] PDF417 error-correction level.
344
+ * @property {number} [rowHeight] PDF417 row height in modules.
345
+ * @property {number} [variant] MicroPDF417 predefined variant number.
346
+ * @property {number} [eccCodewords] MicroPDF417 fixed error-correction codewords.
347
+ * @property {string} [profile] Sythos Canvas QR profile identifier.
348
+ * @property {boolean} [certified] Whether the profile is certified by its originator.
349
+ * @property {object} [canvas] Canvas reservation metadata for the Sythos profile.
350
+ * @property {{format:'ean2'|'ean5', text:string, parity:string, checksum?:number}} [addon] Attached EAN/UPC supplement.
351
+ * @property {number} [confidence] Camera-profile confidence from 0 to 1.
352
+ * @property {{x:number,y:number,width:number,height:number}} [bounds] Camera-profile bounds in the scanned orientation.
353
+ * @property {0|45|90|135|180|225|270|315} [rotation] Camera-profile orientation in degrees.
354
+ * @property {{quietZone:boolean,checksum:boolean|null,rows:number|null,consistency:number|null}} [quality] Camera-profile validation evidence.
355
+ * @property {boolean} [gs1] Whether the physical symbol is classified as GS1.
356
+ * @property {string} [symbologyIdentifier] GS1 symbology identifier.
357
+ * @property {Array<{ai:string,value:string,fixed?:boolean}>} [elements] Parsed GS1 Application Identifier fields.
358
+ * @property {string} [gs1ParseError] Semantic GS1 parsing error after a valid physical read.
359
+ * @property {string} [gtin] GS1 DataBar GTIN-14 payload.
360
+ * @property {boolean} [linkage] GS1 DataBar linkage flag.
361
+ */
362
+
363
+ /**
364
+ * Find and decode every barcode in an image.
365
+ *
366
+ * Returns an array, empty when nothing is found — an image with no barcode is
367
+ * an ordinary outcome for a camera frame, not an error, and throwing would
368
+ * make the common scanning loop a try/catch.
369
+ *
370
+ * @param {{data: Uint8ClampedArray|Uint8Array|number[], width: number, height: number}} image
371
+ * @param {object} [options]
372
+ * @param {string[]} [options.formats] Restrict to these format ids.
373
+ * @param {boolean} [options.tryHarder] Retry inverted and rotated. Default true.
374
+ * @param {'global'|'hybrid'|'auto'} [options.binarizer]
375
+ * @param {'camera'} [options.profile] Opt-in strict camera profile for validated reads.
376
+ * @param {object} [options.frameqr] Sythos Canvas QR detector options when
377
+ * the profile marker is not preserved through image rendering.
378
+ * @returns {DecodeResult[]}
379
+ */
380
+ export function decode(image, options = {}) {
381
+ const { formats = null, tryHarder = true, binarizer = 'auto', profile = null } = options;
382
+ const want = formats ? new Set(formats.map((f) => f.toLowerCase())) : null;
383
+ const wantQR = !want || want.has('qr') || want.has('qrcode');
384
+ const wantDataMatrix = !want || want.has('datamatrix') || want.has('data-matrix');
385
+ const wantAztec = !want || want.has('aztec') || want.has('aztec-code');
386
+ const wantAztecRune = !want || want.has('aztecrune') || want.has('aztec-rune') || want.has('rune');
387
+ const wantPDF417 = !want || want.has('pdf417') || want.has('pdf-417');
388
+ const wantCompactPDF417 = !want || want.has('compactpdf417') || want.has('compact-pdf417') || want.has('compact-pdf-417');
389
+ const wantMicroPDF417 = !want || want.has('micropdf417') || want.has('micro-pdf417') || want.has('micro-pdf-417');
390
+ const wantMicroQR = !want || want.has('microqr') || want.has('micro-qr');
391
+ const wantRMQR = !want || want.has('rmqr') || want.has('r-mqr') || want.has('rectangular-micro-qr');
392
+ const wantFrameQR = !want || want.has('frameqr') || want.has('frame-qr') || want.has('canvas-qr');
393
+ const oneDAliases = new Set(['gs1databar14', 'databar', 'gs1-databar14']);
394
+ const wantOneD = !want || [...want].some((f) => f in ONED_FORMATS || oneDAliases.has(f));
395
+ const wantTwoD = wantQR || wantDataMatrix || wantAztec || wantAztecRune || wantPDF417
396
+ || wantCompactPDF417 || wantMicroPDF417 || wantMicroQR || wantRMQR || wantFrameQR;
397
+
398
+ const source = LuminanceSource.fromImageData(image);
399
+ const results = [];
400
+
401
+ // Light-on-dark symbols are common on screens and packaging, so a second
402
+ // inverted pass is worth the cost when the first finds nothing.
403
+ const passes = tryHarder ? [source, source.invert()] : [source];
404
+
405
+ for (const pass of passes) {
406
+ const bits = binarize(pass, binarizer);
407
+
408
+ // Keep all two-dimensional detector calls in one helper so camera-only
409
+ // orientation retries use exactly the same validation path as the native
410
+ // orientation. Arbitrary-angle retries remain opt-in to the camera
411
+ // profile because resampling is intentionally not part of ordinary decode.
412
+ const readTwoD = (candidateBits, cameraRotation = 0, candidateSource = pass) => {
413
+ const before = results.length;
414
+ const add = (found, format) => {
415
+ if (!found) return;
416
+ const publicFound = { ...found, format };
417
+ if (profile === 'camera' && cameraRotation !== 0) publicFound.rotation = cameraRotation;
418
+ results.push(publicFound);
419
+ };
420
+
421
+ if (wantQR && qrCanDecode) {
422
+ try {
423
+ for (const found of qr.detectAndDecodeQR(candidateBits)) add(found, 'qr');
424
+ } catch {
425
+ /* no QR in this pass */
426
+ }
427
+ }
428
+
429
+ if (wantDataMatrix && dataMatrixCanDecode) {
430
+ // Hybrid thresholding can erase the interior of very large, perfectly
431
+ // uniform modules. In auto mode keep the local-threshold attempt, then
432
+ // retry Data Matrix once with the global threshold before giving up.
433
+ const dataMatrixBits = binarizer === 'auto'
434
+ ? [candidateBits, binarize(candidateSource, 'global')]
435
+ : [candidateBits];
436
+ for (const thresholdBits of dataMatrixBits) {
437
+ try {
438
+ const found = datamatrix.detectAndDecodeDataMatrix(thresholdBits);
439
+ if (found) { add(found, 'datamatrix'); break; }
440
+ } catch {
441
+ /* no Data Matrix with this threshold */
442
+ }
443
+ }
444
+ }
445
+
446
+ if (wantAztec && aztecCanDecode) {
447
+ // The central bull's-eye is a small, high-contrast target. Hybrid
448
+ // thresholding can flatten it on clean rendered symbols, so mirror the
449
+ // Data Matrix global fallback in auto mode.
450
+ const aztecBits = binarizer === 'auto'
451
+ ? [candidateBits, binarize(candidateSource, 'global')]
452
+ : [candidateBits];
453
+ for (const thresholdBits of aztecBits) {
454
+ try {
455
+ const found = aztec.detectAndDecodeAztec(thresholdBits);
456
+ if (found) { add(found, 'aztec'); break; }
457
+ } catch {
458
+ /* no Aztec code with this threshold */
459
+ }
460
+ }
461
+ }
462
+
463
+ if (wantAztecRune && aztecRuneCanDecode) {
464
+ try {
465
+ const found = aztecRune.detectAndDecodeAztecRune(candidateBits);
466
+ if (found) add(found, 'aztecrune');
467
+ } catch {
468
+ /* no Aztec Rune in this pass */
469
+ }
470
+ }
471
+
472
+ if (wantPDF417 && pdf417CanDecode) {
473
+ try {
474
+ const found = pdf417.detectAndDecodePDF417(candidateBits);
475
+ if (found) add(found, 'pdf417');
476
+ } catch {
477
+ /* no PDF417 in this pass */
478
+ }
479
+ }
480
+
481
+ if (wantCompactPDF417 && compactPdf417CanDecode) {
482
+ try {
483
+ const found = compactPdf417.detectAndDecodeCompactPDF417(candidateBits);
484
+ if (found) add(found, 'compactpdf417');
485
+ } catch {
486
+ /* no Compact PDF417 in this pass */
487
+ }
488
+ }
489
+
490
+ if (wantMicroPDF417 && microPdf417CanDecode) {
491
+ // MicroPDF417 detection measures runs across the whole raster. Hybrid
492
+ // thresholding can alter uniform modules near local-window boundaries,
493
+ // so retry the global threshold in auto mode as for the other 2D codes.
494
+ const microPdf417Bits = binarizer === 'auto'
495
+ ? [candidateBits, binarize(candidateSource, 'global')]
496
+ : [candidateBits];
497
+ for (const thresholdBits of microPdf417Bits) {
498
+ try {
499
+ const found = micropdf417.detectAndDecodeMicroPDF417(thresholdBits);
500
+ if (found) { add(found, 'micropdf417'); break; }
501
+ } catch {
502
+ /* no MicroPDF417 with this threshold */
503
+ }
504
+ }
505
+ }
506
+
507
+ if (wantMicroQR && microQrCanDecode) {
508
+ try {
509
+ for (const found of microqr.detectAndDecodeMicroQR(candidateBits)) add(found, 'microqr');
510
+ } catch {
511
+ /* no Micro QR in this pass */
512
+ }
513
+ }
514
+
515
+ if (wantRMQR && rmqrCanDecode) {
516
+ try {
517
+ const found = rmqr.detectAndDecodeRMQR(candidateBits);
518
+ if (found) add(found, 'rmqr');
519
+ } catch {
520
+ /* no rMQR in this pass */
521
+ }
522
+ }
523
+
524
+ if (wantFrameQR && frameQrCanDecode) {
525
+ try {
526
+ for (const found of frameqr.detectAndDecodeFrameQR(candidateBits, options.frameqr ?? {})) add(found, 'frameqr');
527
+ } catch {
528
+ /* no Sythos Canvas QR profile in this pass */
529
+ }
530
+ }
531
+
532
+ return results.length > before;
533
+ };
534
+
535
+ const twoDFound = readTwoD(bits);
536
+ if (profile === 'camera' && wantTwoD && !twoDFound) {
537
+ // Normalize a camera frame through the full eight-angle set only after
538
+ // the native orientation has failed. This preserves the fast path while
539
+ // limiting resampling to frames that need it.
540
+ const cameraRotations = [45, 90, 135, 180, 225, 270, 315];
541
+ for (const rotation of cameraRotations) {
542
+ const inverse = (360 - rotation) % 360;
543
+ const orientedSource = inverse % 90 === 0
544
+ ? inverse === 0
545
+ ? pass
546
+ : inverse === 90
547
+ ? pass.rotate90()
548
+ : inverse === 180
549
+ ? rotateLuminanceSourceByDegrees(pass, 180)
550
+ : pass.rotate90().rotate90().rotate90()
551
+ : rotateLuminanceSourceByDegrees(pass, inverse);
552
+ if (readTwoD(binarize(orientedSource, binarizer), rotation, orientedSource)) break;
553
+ }
554
+ }
555
+
556
+ if (wantOneD) {
557
+ const oneDFormats = want
558
+ ? [...want].filter((f) => f in ONED_FORMATS || oneDAliases.has(f))
559
+ : null;
560
+ const oneDPasses = [{ bits, rotation: 0 }];
561
+ // A linear symbol rotated away from the horizontal has no usable
562
+ // horizontal scanline. The strict camera profile adds normalized
563
+ // orientations only when the native orientation found no validated 1D
564
+ // result. Keep the two quarter-turns first: they are the established
565
+ // path and avoid paying for diagonal resampling on the common case.
566
+ const readOneD = (candidateBits, rotation) => decodeOneD(candidateBits, {
567
+ ...options, formats: oneDFormats, tryHarder, profile, cameraRotation: rotation,
568
+ });
569
+ let oneDResults = readOneD(bits, 0);
570
+ if (profile === 'camera' && oneDResults.length === 0) {
571
+ // `rotation` describes the supplied raster, clockwise from the
572
+ // canonical horizontal orientation. To normalize it, apply the
573
+ // inverse rotation. Include 180 explicitly so the full eight-angle
574
+ // set still works when tryHarder is disabled (the normal reversed-row
575
+ // pass also handles it without a second raster transform).
576
+ const cameraRotations = [90, 270, 180, 45, 135, 225, 315];
577
+ for (const rotation of cameraRotations) {
578
+ const inverse = (360 - rotation) % 360;
579
+ const oriented = inverse % 90 === 0
580
+ ? inverse === 0
581
+ ? bits.clone()
582
+ : inverse === 180
583
+ ? (() => { const copy = bits.clone(); copy.rotate180(); return copy; })()
584
+ : rotateBitMatrix90(bits, inverse === 90)
585
+ : rotateBitMatrixByDegrees(bits, inverse);
586
+ oneDPasses.push({ bits: oriented, rotation });
587
+ }
588
+ for (let i = 1; i < oneDPasses.length && oneDResults.length === 0; i++) {
589
+ oneDResults = readOneD(oneDPasses[i].bits, oneDPasses[i].rotation);
590
+ }
591
+ }
592
+ for (const found of oneDResults) {
593
+ const { row, ...publicFound } = found;
594
+ void row;
595
+ if (publicFound.gs1) {
596
+ const semanticText = publicFound.format === 'gs1databar14'
597
+ ? `01${publicFound.gtin ?? publicFound.text}`
598
+ : publicFound.text;
599
+ try {
600
+ publicFound.elements = databar.decodeGS1ElementString(semanticText);
601
+ } catch (error) {
602
+ publicFound.gs1ParseError = error instanceof Error ? error.message : String(error);
603
+ }
604
+ }
605
+ results.push(publicFound);
606
+ }
607
+ }
608
+
609
+ if (results.length > 0) break;
610
+ }
611
+
612
+ // De-duplicate: the same symbol is often read on several scan rows.
613
+ const seen = new Set();
614
+ const unique = results.filter((r) => {
615
+ const key = `${r.format}:${r.text}`;
616
+ if (seen.has(key)) return false;
617
+ seen.add(key);
618
+ return true;
619
+ });
620
+
621
+ // On large clean rasters, hybrid thresholding can erase otherwise uniform
622
+ // QR/PDF417 modules. Keep auto/hybrid as the primary strategy, then make one
623
+ // focused global retry only when the complete primary pass found nothing.
624
+ // This deliberately leaves an explicit global request single-pass.
625
+ const retryFormats = formats
626
+ ? formats.filter((format) => {
627
+ const id = String(format).toLowerCase();
628
+ return id === 'qr' || id === 'qrcode'
629
+ || id === 'pdf417' || id === 'pdf-417'
630
+ || id === 'compactpdf417' || id === 'compact-pdf417' || id === 'compact-pdf-417';
631
+ })
632
+ : ['qr', 'pdf417', 'compactpdf417'];
633
+ const shouldRetryGlobal = unique.length === 0
634
+ && (binarizer === 'auto' || binarizer === 'hybrid')
635
+ && retryFormats.length > 0;
636
+
637
+ if (!shouldRetryGlobal) {
638
+ return profile === 'camera'
639
+ ? unique.sort((a, b) => (b.confidence ?? 0) - (a.confidence ?? 0))
640
+ : unique;
641
+ }
642
+
643
+ const fallback = decode(image, {
644
+ ...options,
645
+ formats: retryFormats,
646
+ binarizer: 'global',
647
+ });
648
+ const fallbackSeen = new Set();
649
+ const merged = [...unique, ...fallback].filter((r) => {
650
+ const key = `${r.format}:${r.text}`;
651
+ if (fallbackSeen.has(key)) return false;
652
+ fallbackSeen.add(key);
653
+ return true;
654
+ });
655
+ return profile === 'camera'
656
+ ? merged.sort((a, b) => (b.confidence ?? 0) - (a.confidence ?? 0))
657
+ : merged;
658
+ }
659
+
660
+ /**
661
+ * @param {BitMatrix} matrix
662
+ * @param {boolean} clockwise
663
+ * @returns {BitMatrix}
664
+ */
665
+ function rotateBitMatrix90(matrix, clockwise) {
666
+ const rotated = new BitMatrix(matrix.height, matrix.width);
667
+ for (let y = 0; y < matrix.height; y++) {
668
+ for (let x = 0; x < matrix.width; x++) {
669
+ if (!matrix.get(x, y)) continue;
670
+ if (clockwise) rotated.set(matrix.height - 1 - y, x);
671
+ else rotated.set(y, matrix.width - 1 - x);
672
+ }
673
+ }
674
+ return rotated;
675
+ }
676
+
677
+ /**
678
+ * Rotate a binarized raster by an arbitrary clockwise angle without changing
679
+ * the source matrix. This is intentionally used only by the strict camera
680
+ * profile: arbitrary-angle resampling is useful for linear symbols, but is
681
+ * too permissive to become a default retry for every detector.
682
+ *
683
+ * @param {BitMatrix} matrix
684
+ * @param {number} degrees Clockwise angle, normally one of 45/135/225/315.
685
+ * @returns {BitMatrix}
686
+ */
687
+ function rotateBitMatrixByDegrees(matrix, degrees) {
688
+ const angle = ((degrees % 360) + 360) % 360;
689
+ if (angle === 0) return matrix.clone();
690
+ if (angle === 90) return rotateBitMatrix90(matrix, true);
691
+ if (angle === 180) {
692
+ const rotated = matrix.clone();
693
+ rotated.rotate180();
694
+ return rotated;
695
+ }
696
+ if (angle === 270) return rotateBitMatrix90(matrix, false);
697
+
698
+ const radians = angle * Math.PI / 180;
699
+ const sin = Math.sin(radians);
700
+ const cos = Math.cos(radians);
701
+ const width = Math.ceil(Math.abs(matrix.width * cos) + Math.abs(matrix.height * sin));
702
+ const height = Math.ceil(Math.abs(matrix.width * sin) + Math.abs(matrix.height * cos));
703
+ const rotated = new BitMatrix(width, height);
704
+ const sourceCenterX = (matrix.width - 1) / 2;
705
+ const sourceCenterY = (matrix.height - 1) / 2;
706
+ const destinationCenterX = (width - 1) / 2;
707
+ const destinationCenterY = (height - 1) / 2;
708
+
709
+ // Inverse-map destination pixels into the source. Sampling the already
710
+ // binarized raster keeps the operation deterministic and leaves the caller's
711
+ // original image and threshold result untouched.
712
+ for (let y = 0; y < height; y++) {
713
+ const dy = y - destinationCenterY;
714
+ for (let x = 0; x < width; x++) {
715
+ const dx = x - destinationCenterX;
716
+ const sourceX = Math.round(cos * dx + sin * dy + sourceCenterX);
717
+ const sourceY = Math.round(-sin * dx + cos * dy + sourceCenterY);
718
+ if (sourceX >= 0 && sourceX < matrix.width && sourceY >= 0 && sourceY < matrix.height &&
719
+ matrix.get(sourceX, sourceY)) {
720
+ rotated.set(x, y);
721
+ }
722
+ }
723
+ }
724
+ return rotated;
725
+ }
726
+
727
+ /**
728
+ * Rotate the greyscale source before thresholding. Resampling luminance rather
729
+ * than an already-binarized matrix preserves module contrast at diagonal
730
+ * orientations and gives the format-specific detectors the same input quality
731
+ * as the native camera frame.
732
+ *
733
+ * @param {LuminanceSource} source
734
+ * @param {number} degrees Clockwise angle.
735
+ * @returns {LuminanceSource}
736
+ */
737
+ function rotateLuminanceSourceByDegrees(source, degrees) {
738
+ const angle = ((degrees % 360) + 360) % 360;
739
+ if (angle === 0) return source;
740
+ if (angle === 90) return source.rotate90();
741
+ if (angle === 180) return source.rotate90().rotate90();
742
+ if (angle === 270) return source.rotate90().rotate90().rotate90();
743
+
744
+ const radians = angle * Math.PI / 180;
745
+ const sin = Math.sin(radians);
746
+ const cos = Math.cos(radians);
747
+ const width = Math.ceil(Math.abs(source.width * cos) + Math.abs(source.height * sin));
748
+ const height = Math.ceil(Math.abs(source.width * sin) + Math.abs(source.height * cos));
749
+ const rotated = new Uint8Array(width * height);
750
+ rotated.fill(255);
751
+ const sourceCenterX = (source.width - 1) / 2;
752
+ const sourceCenterY = (source.height - 1) / 2;
753
+ const destinationCenterX = (width - 1) / 2;
754
+ const destinationCenterY = (height - 1) / 2;
755
+
756
+ for (let y = 0; y < height; y++) {
757
+ const dy = y - destinationCenterY;
758
+ for (let x = 0; x < width; x++) {
759
+ const dx = x - destinationCenterX;
760
+ const sourceX = cos * dx + sin * dy + sourceCenterX;
761
+ const sourceY = -sin * dx + cos * dy + sourceCenterY;
762
+ if (sourceX >= 0 && sourceX < source.width && sourceY >= 0 && sourceY < source.height) {
763
+ const left = Math.floor(sourceX), top = Math.floor(sourceY);
764
+ const right = Math.min(source.width - 1, left + 1);
765
+ const bottom = Math.min(source.height - 1, top + 1);
766
+ const fx = sourceX - left, fy = sourceY - top;
767
+ const topValue = source.get(left, top) * (1 - fx) + source.get(right, top) * fx;
768
+ const bottomValue = source.get(left, bottom) * (1 - fx) + source.get(right, bottom) * fx;
769
+ rotated[y * width + x] = Math.round(topValue * (1 - fy) + bottomValue * fy);
770
+ }
771
+ }
772
+ }
773
+ return LuminanceSource.fromGrey(rotated, width, height);
774
+ }
775
+
776
+ /**
777
+ * Decode, or throw if nothing is found.
778
+ *
779
+ * @param {{data: Uint8ClampedArray|Uint8Array|number[], width: number, height: number}} image
780
+ * @param {object} [options]
781
+ * @returns {DecodeResult}
782
+ */
783
+ export function decodeStrict(image, options) {
784
+ const results = decode(image, options);
785
+ if (results.length === 0) throw new NotFoundError('No barcode found in image');
786
+ return results[0];
787
+ }
788
+
789
+ /** Library version, matching package.json. */
790
+ export const VERSION = '1.5.10';