@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
package/src/index.js CHANGED
@@ -28,7 +28,6 @@
28
28
  *
29
29
  * Original work. No code from any other barcode implementation.
30
30
  */
31
-
32
31
  /**
33
32
  * Sythos Barcode Suite — public API.
34
33
  *
@@ -45,7 +44,6 @@
45
44
  *
46
45
  * @module @sythos/js_barcode_universal
47
46
  */
48
-
49
47
  import { BitMatrix } from './js/core/bit-matrix.js';
50
48
  import { EncodeError, NotFoundError } from './js/core/errors.js';
51
49
  import { LuminanceSource } from './js/image/luminance.js';
@@ -63,11 +61,8 @@ import * as frameqr from './js/frameqr/index.js';
63
61
  import * as aztecRune from './js/aztecrune/index.js';
64
62
  import * as compactPdf417 from './js/compactpdf417/index.js';
65
63
  import * as databar from './js/databar/index.js';
66
-
67
64
  export { BitMatrix };
68
- export {
69
- BarcodeError, EncodeError, NotFoundError, FormatError, ChecksumError,
70
- } from './js/core/errors.js';
65
+ export { BarcodeError, EncodeError, NotFoundError, FormatError, ChecksumError, } from './js/core/errors.js';
71
66
  export { LuminanceSource } from './js/image/luminance.js';
72
67
  export { binarize, binarizeGlobal, binarizeHybrid } from './js/image/binarizer.js';
73
68
  export * from './js/oned/index.js';
@@ -77,25 +72,18 @@ export { toPNG, toPNGDataURI } from './js/render/png.js';
77
72
  export { renderToCanvasAuto, isWebGL2Available } from './js/render/index.js';
78
73
  export { renderToCanvasAutoAsync, isWebGPUAvailable } from './js/render/index.js';
79
74
  export { encodeQR, decodeQR, detectQR, detectAndDecodeQR } from './js/qr/index.js';
80
- export {
81
- encodeDataMatrix, decodeDataMatrix, detectDataMatrix, detectAndDecodeDataMatrix,
82
- } from './js/datamatrix/index.js';
75
+ export { encodeDataMatrix, decodeDataMatrix, detectDataMatrix, detectAndDecodeDataMatrix, } from './js/datamatrix/index.js';
83
76
  export { encodeAztec, decodeAztec, detectAztec, detectAndDecodeAztec } from './js/aztec/index.js';
84
77
  export * from './js/aztecrune/index.js';
85
78
  export { encodePDF417, decodePDF417, detectPDF417, detectAndDecodePDF417 } from './js/pdf417/index.js';
86
79
  export * from './js/compactpdf417/index.js';
87
- // DataBar exports include both the verified GTIN/data layer and the
88
- // Omnidirectional/Truncated physical image path.
80
+ // DataBar exports include both the verified GTIN/data layer and the
81
+ // Omnidirectional/Truncated physical image path.
89
82
  export * from './js/databar/index.js';
90
- export {
91
- encodeMicroPDF417, decodeMicroPDF417, detectMicroPDF417, detectAndDecodeMicroPDF417,
92
- } from './js/micropdf417/index.js';
83
+ export { encodeMicroPDF417, decodeMicroPDF417, detectMicroPDF417, detectAndDecodeMicroPDF417, } from './js/micropdf417/index.js';
93
84
  export { encodeMicroQR, decodeMicroQR, detectMicroQR, detectAndDecodeMicroQR } from './js/microqr/index.js';
94
85
  export { encodeRMQR, decodeRMQR, detectRMQR, detectAndDecodeRMQR } from './js/rmqr/index.js';
95
- export {
96
- encodeFrameQR, decodeFrameQR, detectFrameQR, detectAndDecodeFrameQR,
97
- } from './js/frameqr/index.js';
98
-
86
+ export { encodeFrameQR, decodeFrameQR, detectFrameQR, detectAndDecodeFrameQR, } from './js/frameqr/index.js';
99
87
  /**
100
88
  * @typedef {object} FormatInfo
101
89
  * @property {string} id
@@ -105,7 +93,6 @@ export {
105
93
  * @property {'1D' | '2D'} kind
106
94
  * @property {'supplement'} [role]
107
95
  */
108
-
109
96
  // Writing and reading a format are separate capabilities that can land at
110
97
  // different times, so they are reported separately rather than collapsed into
111
98
  // one "supported" flag that would be wrong in one direction or the other.
@@ -116,9 +103,9 @@ export {
116
103
  // value.
117
104
  const qrPresent = qr.QR_PLACEHOLDER !== true;
118
105
  const qrCanEncode = qrPresent &&
119
- typeof qr.encodeQR === 'function' && qr.QR_CAN_ENCODE !== false;
106
+ typeof qr.encodeQR === 'function' && qr.QR_CAN_ENCODE !== false;
120
107
  const qrCanDecode = qrPresent &&
121
- typeof qr.detectAndDecodeQR === 'function' && qr.QR_CAN_DECODE !== false;
108
+ typeof qr.detectAndDecodeQR === 'function' && qr.QR_CAN_DECODE !== false;
122
109
  const dataMatrixCanEncode = typeof datamatrix.encodeDataMatrix === 'function';
123
110
  const dataMatrixCanDecode = typeof datamatrix.detectAndDecodeDataMatrix === 'function';
124
111
  const aztecCanEncode = typeof aztec.encodeAztec === 'function';
@@ -143,7 +130,6 @@ const compactPdf417CanEncode = typeof compactPdf417.encodeCompactPDF417 === 'fun
143
130
  const compactPdf417CanDecode = typeof compactPdf417.detectAndDecodeCompactPDF417 === 'function';
144
131
  const dataBarCanEncode = typeof databar.encodeDataBar14 === 'function';
145
132
  const dataBarCanDecode = typeof databar.decodeDataBar14Scanline === 'function';
146
-
147
133
  /**
148
134
  * Every format this build supports.
149
135
  *
@@ -155,96 +141,93 @@ const dataBarCanDecode = typeof databar.decodeDataBar14Scanline === 'function';
155
141
  * @returns {FormatInfo[]}
156
142
  */
157
143
  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;
144
+ const formats = Object.entries(ONED_FORMATS).map(([id, info]) => ({
145
+ id,
146
+ label: info.label,
147
+ canWrite: true,
148
+ canRead: info.readable,
149
+ kind: /** @type {'1D'} */ ('1D'),
150
+ ...(info.role ? { role: info.role } : {}),
151
+ }));
152
+ formats.push({
153
+ id: 'qr',
154
+ label: 'QR Code',
155
+ canWrite: qrCanEncode,
156
+ canRead: qrCanDecode,
157
+ kind: /** @type {'2D'} */ ('2D'),
158
+ });
159
+ formats.push({
160
+ id: 'datamatrix',
161
+ label: 'Data Matrix ECC 200',
162
+ canWrite: dataMatrixCanEncode,
163
+ canRead: dataMatrixCanDecode,
164
+ kind: /** @type {'2D'} */ ('2D'),
165
+ });
166
+ formats.push({
167
+ id: 'aztec',
168
+ label: 'Aztec Code',
169
+ canWrite: aztecCanEncode,
170
+ canRead: aztecCanDecode,
171
+ kind: /** @type {'2D'} */ ('2D'),
172
+ });
173
+ formats.push({
174
+ id: 'aztecrune',
175
+ label: 'Aztec Rune',
176
+ canWrite: aztecRuneCanEncode,
177
+ canRead: aztecRuneCanDecode,
178
+ kind: /** @type {'2D'} */ ('2D'),
179
+ });
180
+ formats.push({
181
+ id: 'pdf417',
182
+ label: 'PDF417',
183
+ canWrite: pdf417CanEncode,
184
+ canRead: pdf417CanDecode,
185
+ kind: /** @type {'2D'} */ ('2D'),
186
+ });
187
+ formats.push({
188
+ id: 'compactpdf417',
189
+ label: 'Compact PDF417',
190
+ canWrite: compactPdf417CanEncode,
191
+ canRead: compactPdf417CanDecode,
192
+ kind: /** @type {'2D'} */ ('2D'),
193
+ });
194
+ formats.push({
195
+ id: 'micropdf417',
196
+ label: 'MicroPDF417',
197
+ canWrite: microPdf417CanEncode,
198
+ canRead: microPdf417CanDecode,
199
+ kind: /** @type {'2D'} */ ('2D'),
200
+ });
201
+ formats.push({
202
+ id: 'microqr',
203
+ label: 'Micro QR Code',
204
+ canWrite: microQrCanEncode,
205
+ canRead: microQrCanDecode,
206
+ kind: /** @type {'2D'} */ ('2D'),
207
+ });
208
+ formats.push({
209
+ id: 'rmqr',
210
+ label: 'rMQR Code',
211
+ canWrite: rmqrCanEncode,
212
+ canRead: rmqrCanDecode,
213
+ kind: /** @type {'2D'} */ ('2D'),
214
+ });
215
+ formats.push({
216
+ id: 'frameqr',
217
+ label: 'Sythos Canvas QR profile',
218
+ canWrite: frameQrCanEncode,
219
+ canRead: frameQrCanDecode,
220
+ kind: /** @type {'2D'} */ ('2D'),
221
+ });
222
+ formats.push({
223
+ id: 'gs1databar14',
224
+ label: 'GS1 DataBar Omnidirectional / Truncated',
225
+ canWrite: dataBarCanEncode,
226
+ canRead: dataBarCanDecode,
227
+ kind: /** @type {'1D'} */ ('1D'),
228
+ });
229
+ return formats;
246
230
  }
247
-
248
231
  /**
249
232
  * Encode a payload into a barcode matrix.
250
233
  *
@@ -282,51 +265,48 @@ export function listFormats() {
282
265
  * @returns {BitMatrix}
283
266
  */
284
267
  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);
268
+ const format = String(options.format ?? 'qr').toLowerCase();
269
+ const value = typeof text === 'number' ? String(text) : text;
270
+ if (format === 'qr' || format === 'qrcode') {
271
+ return qr.encodeQR(value, options);
272
+ }
273
+ if (format === 'datamatrix' || format === 'data-matrix') {
274
+ return datamatrix.encodeDataMatrix(value, options);
275
+ }
276
+ if (format === 'aztec' || format === 'aztec-code') {
277
+ return aztec.encodeAztec(value, options);
278
+ }
279
+ if (format === 'aztecrune' || format === 'aztec-rune' || format === 'rune') {
280
+ return aztecRune.encodeAztecRune(value, options);
281
+ }
282
+ if (format === 'pdf417' || format === 'pdf-417') {
283
+ return pdf417.encodePDF417(value, options);
284
+ }
285
+ if (format === 'compactpdf417' || format === 'compact-pdf417' || format === 'compact-pdf-417') {
286
+ return compactPdf417.encodeCompactPDF417(value, options);
287
+ }
288
+ if (format === 'micropdf417' || format === 'micro-pdf417' || format === 'micro-pdf-417') {
289
+ return micropdf417.encodeMicroPDF417(value, options);
290
+ }
291
+ if (format === 'microqr' || format === 'micro-qr') {
292
+ return microqr.encodeMicroQR(value, options);
293
+ }
294
+ if (format === 'rmqr' || format === 'r-mqr' || format === 'rectangular-micro-qr') {
295
+ return rmqr.encodeRMQR(value, options);
296
+ }
297
+ if (format === 'frameqr' || format === 'frame-qr' || format === 'canvas-qr') {
298
+ return frameqr.encodeFrameQR(value, options);
299
+ }
300
+ if (format === 'gs1databar14' || format === 'gs1-databar14' || format === 'databar') {
301
+ return databar.encodeDataBar14(value, options);
302
+ }
303
+ const entry = ONED_FORMATS[format];
304
+ if (!entry) {
305
+ const known = [...Object.keys(ONED_FORMATS), 'qr', 'datamatrix', 'aztec', 'aztecrune', 'pdf417', 'compactpdf417', 'micropdf417', 'microqr', 'rmqr', 'frameqr', 'gs1databar14'].join(', ');
306
+ throw new EncodeError(`Unknown format "${format}". Known formats: ${known}`);
307
+ }
308
+ return entry.encode(value, options);
328
309
  }
329
-
330
310
  /**
331
311
  * @typedef {object} DecodeResult
332
312
  * @property {string} text
@@ -359,7 +339,6 @@ export function encode(text, options = {}) {
359
339
  * @property {string} [gtin] GS1 DataBar GTIN-14 payload.
360
340
  * @property {boolean} [linkage] GS1 DataBar linkage flag.
361
341
  */
362
-
363
342
  /**
364
343
  * Find and decode every barcode in an image.
365
344
  *
@@ -378,302 +357,314 @@ export function encode(text, options = {}) {
378
357
  * @returns {DecodeResult[]}
379
358
  */
380
359
  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 */
360
+ const { formats = null, tryHarder = true, binarizer = 'auto', profile = null } = options;
361
+ const want = formats ? new Set(formats.map((f) => f.toLowerCase())) : null;
362
+ const wantQR = !want || want.has('qr') || want.has('qrcode');
363
+ const wantDataMatrix = !want || want.has('datamatrix') || want.has('data-matrix');
364
+ const wantAztec = !want || want.has('aztec') || want.has('aztec-code');
365
+ const wantAztecRune = !want || want.has('aztecrune') || want.has('aztec-rune') || want.has('rune');
366
+ const wantPDF417 = !want || want.has('pdf417') || want.has('pdf-417');
367
+ const wantCompactPDF417 = !want || want.has('compactpdf417') || want.has('compact-pdf417') || want.has('compact-pdf-417');
368
+ const wantMicroPDF417 = !want || want.has('micropdf417') || want.has('micro-pdf417') || want.has('micro-pdf-417');
369
+ const wantMicroQR = !want || want.has('microqr') || want.has('micro-qr');
370
+ const wantRMQR = !want || want.has('rmqr') || want.has('r-mqr') || want.has('rectangular-micro-qr');
371
+ const wantFrameQR = !want || want.has('frameqr') || want.has('frame-qr') || want.has('canvas-qr');
372
+ const oneDAliases = new Set(['gs1databar14', 'databar', 'gs1-databar14']);
373
+ const wantOneD = !want || [...want].some((f) => f in ONED_FORMATS || oneDAliases.has(f));
374
+ const wantTwoD = wantQR || wantDataMatrix || wantAztec || wantAztecRune || wantPDF417
375
+ || wantCompactPDF417 || wantMicroPDF417 || wantMicroQR || wantRMQR || wantFrameQR;
376
+ const source = LuminanceSource.fromImageData(image);
377
+ const results = [];
378
+ // Light-on-dark symbols are common on screens and packaging, so a second
379
+ // inverted pass is worth the cost when the first finds nothing.
380
+ const passes = tryHarder ? [source, source.invert()] : [source];
381
+ for (const pass of passes) {
382
+ const bits = binarize(pass, binarizer);
383
+ // Keep all two-dimensional detector calls in one helper so camera-only
384
+ // orientation retries use exactly the same validation path as the native
385
+ // orientation. Arbitrary-angle retries remain opt-in to the camera
386
+ // profile because resampling is intentionally not part of ordinary decode.
387
+ const readTwoD = (candidateBits, cameraRotation = 0, candidateSource = pass) => {
388
+ const before = results.length;
389
+ const add = (found, format) => {
390
+ if (!found)
391
+ return;
392
+ const publicFound = { ...found, format };
393
+ if (profile === 'camera' && cameraRotation !== 0)
394
+ publicFound.rotation = cameraRotation;
395
+ results.push(publicFound);
396
+ };
397
+ if (wantQR && qrCanDecode) {
398
+ try {
399
+ for (const found of qr.detectAndDecodeQR(candidateBits))
400
+ add(found, 'qr');
401
+ }
402
+ catch {
403
+ /* no QR in this pass */
404
+ }
405
+ }
406
+ if (wantDataMatrix && dataMatrixCanDecode) {
407
+ // Hybrid thresholding can erase the interior of very large, perfectly
408
+ // uniform modules. In auto mode keep the local-threshold attempt, then
409
+ // retry Data Matrix once with the global threshold before giving up.
410
+ const dataMatrixBits = binarizer === 'auto'
411
+ ? [candidateBits, binarize(candidateSource, 'global')]
412
+ : [candidateBits];
413
+ for (const thresholdBits of dataMatrixBits) {
414
+ try {
415
+ const found = datamatrix.detectAndDecodeDataMatrix(thresholdBits);
416
+ if (found) {
417
+ add(found, 'datamatrix');
418
+ break;
419
+ }
420
+ }
421
+ catch {
422
+ /* no Data Matrix with this threshold */
423
+ }
424
+ }
425
+ }
426
+ if (wantAztec && aztecCanDecode) {
427
+ // The central bull's-eye is a small, high-contrast target. Hybrid
428
+ // thresholding can flatten it on clean rendered symbols, so mirror the
429
+ // Data Matrix global fallback in auto mode.
430
+ const aztecBits = binarizer === 'auto'
431
+ ? [candidateBits, binarize(candidateSource, 'global')]
432
+ : [candidateBits];
433
+ for (const thresholdBits of aztecBits) {
434
+ try {
435
+ const found = aztec.detectAndDecodeAztec(thresholdBits);
436
+ if (found) {
437
+ add(found, 'aztec');
438
+ break;
439
+ }
440
+ }
441
+ catch {
442
+ /* no Aztec code with this threshold */
443
+ }
444
+ }
445
+ }
446
+ if (wantAztecRune && aztecRuneCanDecode) {
447
+ try {
448
+ const found = aztecRune.detectAndDecodeAztecRune(candidateBits);
449
+ if (found)
450
+ add(found, 'aztecrune');
451
+ }
452
+ catch {
453
+ /* no Aztec Rune in this pass */
454
+ }
455
+ }
456
+ if (wantPDF417 && pdf417CanDecode) {
457
+ try {
458
+ const found = pdf417.detectAndDecodePDF417(candidateBits);
459
+ if (found)
460
+ add(found, 'pdf417');
461
+ }
462
+ catch {
463
+ /* no PDF417 in this pass */
464
+ }
465
+ }
466
+ if (wantCompactPDF417 && compactPdf417CanDecode) {
467
+ try {
468
+ const found = compactPdf417.detectAndDecodeCompactPDF417(candidateBits);
469
+ if (found)
470
+ add(found, 'compactpdf417');
471
+ }
472
+ catch {
473
+ /* no Compact PDF417 in this pass */
474
+ }
475
+ }
476
+ if (wantMicroPDF417 && microPdf417CanDecode) {
477
+ // MicroPDF417 detection measures runs across the whole raster. Hybrid
478
+ // thresholding can alter uniform modules near local-window boundaries,
479
+ // so retry the global threshold in auto mode as for the other 2D codes.
480
+ const microPdf417Bits = binarizer === 'auto'
481
+ ? [candidateBits, binarize(candidateSource, 'global')]
482
+ : [candidateBits];
483
+ for (const thresholdBits of microPdf417Bits) {
484
+ try {
485
+ const found = micropdf417.detectAndDecodeMicroPDF417(thresholdBits);
486
+ if (found) {
487
+ add(found, 'micropdf417');
488
+ break;
489
+ }
490
+ }
491
+ catch {
492
+ /* no MicroPDF417 with this threshold */
493
+ }
494
+ }
495
+ }
496
+ if (wantMicroQR && microQrCanDecode) {
497
+ try {
498
+ for (const found of microqr.detectAndDecodeMicroQR(candidateBits))
499
+ add(found, 'microqr');
500
+ }
501
+ catch {
502
+ /* no Micro QR in this pass */
503
+ }
504
+ }
505
+ if (wantRMQR && rmqrCanDecode) {
506
+ try {
507
+ const found = rmqr.detectAndDecodeRMQR(candidateBits);
508
+ if (found)
509
+ add(found, 'rmqr');
510
+ }
511
+ catch {
512
+ /* no rMQR in this pass */
513
+ }
514
+ }
515
+ if (wantFrameQR && frameQrCanDecode) {
516
+ try {
517
+ for (const found of frameqr.detectAndDecodeFrameQR(candidateBits, options.frameqr ?? {}))
518
+ add(found, 'frameqr');
519
+ }
520
+ catch {
521
+ /* no Sythos Canvas QR profile in this pass */
522
+ }
523
+ }
524
+ return results.length > before;
525
+ };
526
+ const twoDFound = readTwoD(bits);
527
+ if (profile === 'camera' && wantTwoD && !twoDFound) {
528
+ // Normalize a camera frame through the full eight-angle set only after
529
+ // the native orientation has failed. This preserves the fast path while
530
+ // limiting resampling to frames that need it.
531
+ const cameraRotations = [45, 90, 135, 180, 225, 270, 315];
532
+ for (const rotation of cameraRotations) {
533
+ const inverse = (360 - rotation) % 360;
534
+ const orientedSource = inverse % 90 === 0
535
+ ? inverse === 0
536
+ ? pass
537
+ : inverse === 90
538
+ ? pass.rotate90()
539
+ : inverse === 180
540
+ ? rotateLuminanceSourceByDegrees(pass, 180)
541
+ : pass.rotate90().rotate90().rotate90()
542
+ : rotateLuminanceSourceByDegrees(pass, inverse);
543
+ if (readTwoD(binarize(orientedSource, binarizer), rotation, orientedSource))
544
+ break;
545
+ }
469
546
  }
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 */
547
+ if (wantOneD) {
548
+ const oneDFormats = want
549
+ ? [...want].filter((f) => f in ONED_FORMATS || oneDAliases.has(f))
550
+ : null;
551
+ const oneDPasses = [{ bits, rotation: 0 }];
552
+ // A linear symbol rotated away from the horizontal has no usable
553
+ // horizontal scanline. The strict camera profile adds normalized
554
+ // orientations only when the native orientation found no validated 1D
555
+ // result. Keep the two quarter-turns first: they are the established
556
+ // path and avoid paying for diagonal resampling on the common case.
557
+ const readOneD = (candidateBits, rotation) => decodeOneD(candidateBits, {
558
+ ...options, formats: oneDFormats, tryHarder, profile, cameraRotation: rotation,
559
+ });
560
+ let oneDResults = readOneD(bits, 0);
561
+ if (profile === 'camera' && oneDResults.length === 0) {
562
+ // `rotation` describes the supplied raster, clockwise from the
563
+ // canonical horizontal orientation. To normalize it, apply the
564
+ // inverse rotation. Include 180 explicitly so the full eight-angle
565
+ // set still works when tryHarder is disabled (the normal reversed-row
566
+ // pass also handles it without a second raster transform).
567
+ const cameraRotations = [90, 270, 180, 45, 135, 225, 315];
568
+ for (const rotation of cameraRotations) {
569
+ const inverse = (360 - rotation) % 360;
570
+ const oriented = inverse % 90 === 0
571
+ ? inverse === 0
572
+ ? bits.clone()
573
+ : inverse === 180
574
+ ? (() => { const copy = bits.clone(); copy.rotate180(); return copy; })()
575
+ : rotateBitMatrix90(bits, inverse === 90)
576
+ : rotateBitMatrixByDegrees(bits, inverse);
577
+ oneDPasses.push({ bits: oriented, rotation });
578
+ }
579
+ for (let i = 1; i < oneDPasses.length && oneDResults.length === 0; i++) {
580
+ oneDResults = readOneD(oneDPasses[i].bits, oneDPasses[i].rotation);
581
+ }
582
+ }
583
+ for (const found of oneDResults) {
584
+ const { row, ...publicFound } = found;
585
+ void row;
586
+ if (publicFound.gs1) {
587
+ const semanticText = publicFound.format === 'gs1databar14'
588
+ ? `01${publicFound.gtin ?? publicFound.text}`
589
+ : publicFound.text;
590
+ try {
591
+ publicFound.elements = databar.decodeGS1ElementString(semanticText);
592
+ }
593
+ catch (error) {
594
+ publicFound.gs1ParseError = error instanceof Error ? error.message : String(error);
595
+ }
596
+ }
597
+ results.push(publicFound);
598
+ }
478
599
  }
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
- }
600
+ if (results.length > 0)
601
+ break;
554
602
  }
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
- }
603
+ // De-duplicate: the same symbol is often read on several scan rows.
604
+ const seen = new Set();
605
+ const unique = results.filter((r) => {
606
+ const key = `${r.format}:${r.text}`;
607
+ if (seen.has(key))
608
+ return false;
609
+ seen.add(key);
610
+ return true;
611
+ });
612
+ // On large clean rasters, hybrid thresholding can erase otherwise uniform
613
+ // QR/PDF417 modules. Keep auto/hybrid as the primary strategy, then make one
614
+ // focused global retry only when the complete primary pass found nothing.
615
+ // This deliberately leaves an explicit global request single-pass.
616
+ const retryFormats = formats
617
+ ? formats.filter((format) => {
618
+ const id = String(format).toLowerCase();
619
+ return id === 'qr' || id === 'qrcode'
620
+ || id === 'pdf417' || id === 'pdf-417'
621
+ || id === 'compactpdf417' || id === 'compact-pdf417' || id === 'compact-pdf-417';
622
+ })
623
+ : ['qr', 'pdf417', 'compactpdf417'];
624
+ const shouldRetryGlobal = unique.length === 0
625
+ && (binarizer === 'auto' || binarizer === 'hybrid')
626
+ && retryFormats.length > 0;
627
+ if (!shouldRetryGlobal) {
628
+ return profile === 'camera'
629
+ ? unique.sort((a, b) => (b.confidence ?? 0) - (a.confidence ?? 0))
630
+ : unique;
607
631
  }
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) {
632
+ const fallback = decode(image, {
633
+ ...options,
634
+ formats: retryFormats,
635
+ binarizer: 'global',
636
+ });
637
+ const fallbackSeen = new Set();
638
+ const merged = [...unique, ...fallback].filter((r) => {
639
+ const key = `${r.format}:${r.text}`;
640
+ if (fallbackSeen.has(key))
641
+ return false;
642
+ fallbackSeen.add(key);
643
+ return true;
644
+ });
638
645
  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;
646
+ ? merged.sort((a, b) => (b.confidence ?? 0) - (a.confidence ?? 0))
647
+ : merged;
658
648
  }
659
-
660
649
  /**
661
650
  * @param {BitMatrix} matrix
662
651
  * @param {boolean} clockwise
663
652
  * @returns {BitMatrix}
664
653
  */
665
654
  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);
655
+ const rotated = new BitMatrix(matrix.height, matrix.width);
656
+ for (let y = 0; y < matrix.height; y++) {
657
+ for (let x = 0; x < matrix.width; x++) {
658
+ if (!matrix.get(x, y))
659
+ continue;
660
+ if (clockwise)
661
+ rotated.set(matrix.height - 1 - y, x);
662
+ else
663
+ rotated.set(y, matrix.width - 1 - x);
664
+ }
672
665
  }
673
- }
674
- return rotated;
666
+ return rotated;
675
667
  }
676
-
677
668
  /**
678
669
  * Rotate a binarized raster by an arbitrary clockwise angle without changing
679
670
  * the source matrix. This is intentionally used only by the strict camera
@@ -685,45 +676,45 @@ function rotateBitMatrix90(matrix, clockwise) {
685
676
  * @returns {BitMatrix}
686
677
  */
687
678
  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
- }
679
+ const angle = ((degrees % 360) + 360) % 360;
680
+ if (angle === 0)
681
+ return matrix.clone();
682
+ if (angle === 90)
683
+ return rotateBitMatrix90(matrix, true);
684
+ if (angle === 180) {
685
+ const rotated = matrix.clone();
686
+ rotated.rotate180();
687
+ return rotated;
722
688
  }
723
- }
724
- return rotated;
689
+ if (angle === 270)
690
+ return rotateBitMatrix90(matrix, false);
691
+ const radians = angle * Math.PI / 180;
692
+ const sin = Math.sin(radians);
693
+ const cos = Math.cos(radians);
694
+ const width = Math.ceil(Math.abs(matrix.width * cos) + Math.abs(matrix.height * sin));
695
+ const height = Math.ceil(Math.abs(matrix.width * sin) + Math.abs(matrix.height * cos));
696
+ const rotated = new BitMatrix(width, height);
697
+ const sourceCenterX = (matrix.width - 1) / 2;
698
+ const sourceCenterY = (matrix.height - 1) / 2;
699
+ const destinationCenterX = (width - 1) / 2;
700
+ const destinationCenterY = (height - 1) / 2;
701
+ // Inverse-map destination pixels into the source. Sampling the already
702
+ // binarized raster keeps the operation deterministic and leaves the caller's
703
+ // original image and threshold result untouched.
704
+ for (let y = 0; y < height; y++) {
705
+ const dy = y - destinationCenterY;
706
+ for (let x = 0; x < width; x++) {
707
+ const dx = x - destinationCenterX;
708
+ const sourceX = Math.round(cos * dx + sin * dy + sourceCenterX);
709
+ const sourceY = Math.round(-sin * dx + cos * dy + sourceCenterY);
710
+ if (sourceX >= 0 && sourceX < matrix.width && sourceY >= 0 && sourceY < matrix.height &&
711
+ matrix.get(sourceX, sourceY)) {
712
+ rotated.set(x, y);
713
+ }
714
+ }
715
+ }
716
+ return rotated;
725
717
  }
726
-
727
718
  /**
728
719
  * Rotate the greyscale source before thresholding. Resampling luminance rather
729
720
  * than an already-binarized matrix preserves module contrast at diagonal
@@ -735,44 +726,45 @@ function rotateBitMatrixByDegrees(matrix, degrees) {
735
726
  * @returns {LuminanceSource}
736
727
  */
737
728
  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
- }
729
+ const angle = ((degrees % 360) + 360) % 360;
730
+ if (angle === 0)
731
+ return source;
732
+ if (angle === 90)
733
+ return source.rotate90();
734
+ if (angle === 180)
735
+ return source.rotate90().rotate90();
736
+ if (angle === 270)
737
+ return source.rotate90().rotate90().rotate90();
738
+ const radians = angle * Math.PI / 180;
739
+ const sin = Math.sin(radians);
740
+ const cos = Math.cos(radians);
741
+ const width = Math.ceil(Math.abs(source.width * cos) + Math.abs(source.height * sin));
742
+ const height = Math.ceil(Math.abs(source.width * sin) + Math.abs(source.height * cos));
743
+ const rotated = new Uint8Array(width * height);
744
+ rotated.fill(255);
745
+ const sourceCenterX = (source.width - 1) / 2;
746
+ const sourceCenterY = (source.height - 1) / 2;
747
+ const destinationCenterX = (width - 1) / 2;
748
+ const destinationCenterY = (height - 1) / 2;
749
+ for (let y = 0; y < height; y++) {
750
+ const dy = y - destinationCenterY;
751
+ for (let x = 0; x < width; x++) {
752
+ const dx = x - destinationCenterX;
753
+ const sourceX = cos * dx + sin * dy + sourceCenterX;
754
+ const sourceY = -sin * dx + cos * dy + sourceCenterY;
755
+ if (sourceX >= 0 && sourceX < source.width && sourceY >= 0 && sourceY < source.height) {
756
+ const left = Math.floor(sourceX), top = Math.floor(sourceY);
757
+ const right = Math.min(source.width - 1, left + 1);
758
+ const bottom = Math.min(source.height - 1, top + 1);
759
+ const fx = sourceX - left, fy = sourceY - top;
760
+ const topValue = source.get(left, top) * (1 - fx) + source.get(right, top) * fx;
761
+ const bottomValue = source.get(left, bottom) * (1 - fx) + source.get(right, bottom) * fx;
762
+ rotated[y * width + x] = Math.round(topValue * (1 - fy) + bottomValue * fy);
763
+ }
764
+ }
771
765
  }
772
- }
773
- return LuminanceSource.fromGrey(rotated, width, height);
766
+ return LuminanceSource.fromGrey(rotated, width, height);
774
767
  }
775
-
776
768
  /**
777
769
  * Decode, or throw if nothing is found.
778
770
  *
@@ -781,10 +773,10 @@ function rotateLuminanceSourceByDegrees(source, degrees) {
781
773
  * @returns {DecodeResult}
782
774
  */
783
775
  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];
776
+ const results = decode(image, options);
777
+ if (results.length === 0)
778
+ throw new NotFoundError('No barcode found in image');
779
+ return results[0];
787
780
  }
788
-
789
781
  /** Library version, matching package.json. */
790
- export const VERSION = '1.5.8';
782
+ export const VERSION = '1.5.10';