@sythos/js_barcode_universal 1.5.7 → 1.5.9

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 (267) hide show
  1. package/LICENSE +5 -4
  2. package/NOTICE.md +2 -2
  3. package/README.md +50 -37
  4. package/bundle/sythos-barcode.esm.js +12051 -11484
  5. package/bundle/sythos-barcode.js +12051 -11484
  6. package/package.json +52 -45
  7. package/src/index.d.ts +363 -0
  8. package/src/index.js +573 -428
  9. package/src/js/aztec/decoder.js +326 -0
  10. package/src/js/aztec/detector.js +251 -0
  11. package/src/js/aztec/encoder.js +276 -0
  12. package/src/js/aztec/high-level.js +216 -0
  13. package/src/js/aztec/index.js +34 -0
  14. package/src/js/aztec/tables.js +196 -0
  15. package/src/js/aztecrune/decoder.js +146 -0
  16. package/src/js/aztecrune/detector.js +180 -0
  17. package/src/js/aztecrune/encoder.js +103 -0
  18. package/src/js/aztecrune/index.js +34 -0
  19. package/src/js/aztecrune/tables.js +142 -0
  20. package/src/js/compactpdf417/decoder.js +123 -0
  21. package/src/js/compactpdf417/detector.js +138 -0
  22. package/src/js/compactpdf417/encoder.js +127 -0
  23. package/src/js/compactpdf417/index.js +5 -0
  24. package/src/js/compactpdf417/tables.js +164 -0
  25. package/src/js/core/bit-buffer.js +158 -0
  26. package/src/js/core/bit-matrix.js +243 -0
  27. package/src/js/core/errors.js +59 -0
  28. package/src/js/core/galois-field.js +193 -0
  29. package/src/js/core/index.js +39 -0
  30. package/src/js/core/reed-solomon.js +318 -0
  31. package/src/js/databar/codec.js +166 -0
  32. package/src/js/databar/decoder.js +224 -0
  33. package/src/js/databar/encoder.js +88 -0
  34. package/src/js/databar/gs1.js +176 -0
  35. package/src/js/databar/index.js +36 -0
  36. package/src/js/databar/patterns.js +139 -0
  37. package/src/js/databar/tables.js +123 -0
  38. package/src/js/datamatrix/decoder.js +296 -0
  39. package/src/js/datamatrix/detector.js +254 -0
  40. package/src/js/datamatrix/encoder.js +250 -0
  41. package/src/js/datamatrix/index.js +34 -0
  42. package/src/js/datamatrix/tables.js +122 -0
  43. package/src/js/frameqr/decoder.js +216 -0
  44. package/src/js/frameqr/detector.js +199 -0
  45. package/src/js/frameqr/encoder.js +143 -0
  46. package/src/js/frameqr/index.js +33 -0
  47. package/src/js/frameqr/tables.js +252 -0
  48. package/src/js/image/binarizer.js +254 -0
  49. package/src/js/image/grid-sampler.js +148 -0
  50. package/src/js/image/index.js +38 -0
  51. package/src/js/image/luminance.js +185 -0
  52. package/src/js/image/perspective.js +158 -0
  53. package/src/js/micropdf417/compaction.js +115 -0
  54. package/src/js/micropdf417/decoder.js +180 -0
  55. package/src/js/micropdf417/detector.js +152 -0
  56. package/src/js/micropdf417/encoder.js +184 -0
  57. package/src/js/micropdf417/error-correction.js +50 -0
  58. package/src/js/micropdf417/index.js +35 -0
  59. package/src/js/micropdf417/tables.js +197 -0
  60. package/src/js/microqr/decoder.js +240 -0
  61. package/src/js/microqr/detector.js +339 -0
  62. package/src/js/microqr/encoder.js +314 -0
  63. package/src/js/microqr/index.js +34 -0
  64. package/src/js/microqr/tables.js +324 -0
  65. package/src/js/oned/addons.js +396 -0
  66. package/src/js/oned/index.js +75 -0
  67. package/src/js/oned/patterns.js +365 -0
  68. package/src/js/oned/reader.js +1383 -0
  69. package/src/js/oned/writers.js +691 -0
  70. package/src/js/pdf417/compaction.js +391 -0
  71. package/src/js/pdf417/decoder.js +110 -0
  72. package/src/js/pdf417/detector.js +484 -0
  73. package/src/js/pdf417/encoder.js +102 -0
  74. package/src/js/pdf417/error-correction.js +44 -0
  75. package/src/{pdf417 → js/pdf417}/index.js +0 -5
  76. package/src/js/pdf417/tables.js +315 -0
  77. package/src/js/qr/decoder.js +539 -0
  78. package/src/js/qr/detector.js +613 -0
  79. package/src/js/qr/encoder.js +873 -0
  80. package/src/js/qr/index.js +42 -0
  81. package/src/js/qr/tables.js +690 -0
  82. package/src/js/render/image-data.js +120 -0
  83. package/src/js/render/index.js +128 -0
  84. package/src/js/render/options.js +152 -0
  85. package/src/js/render/png.js +270 -0
  86. package/src/js/render/svg.js +117 -0
  87. package/src/js/render/webgl.js +200 -0
  88. package/src/js/render/webgpu.js +357 -0
  89. package/src/js/rmqr/decoder.js +209 -0
  90. package/src/js/rmqr/detector.js +122 -0
  91. package/src/js/rmqr/encoder.js +321 -0
  92. package/src/js/rmqr/index.js +33 -0
  93. package/src/js/rmqr/tables.js +177 -0
  94. package/src/{aztec/decoder.js → ts/aztec/decoder.ts} +0 -1
  95. package/{typings → src/ts}/aztec/detector.d.ts +2 -2
  96. package/src/{aztec/detector.js → ts/aztec/detector.ts} +0 -1
  97. package/src/{aztec/encoder.js → ts/aztec/encoder.ts} +0 -1
  98. package/src/{aztec/high-level.js → ts/aztec/high-level.ts} +0 -1
  99. package/src/{aztec/index.js → ts/aztec/index.ts} +0 -1
  100. package/src/{aztec/tables.js → ts/aztec/tables.ts} +0 -1
  101. package/src/{aztecrune/decoder.js → ts/aztecrune/decoder.ts} +0 -1
  102. package/src/{aztecrune/detector.js → ts/aztecrune/detector.ts} +0 -1
  103. package/src/{aztecrune/encoder.js → ts/aztecrune/encoder.ts} +0 -1
  104. package/src/{aztecrune/index.js → ts/aztecrune/index.ts} +0 -1
  105. package/src/{aztecrune/tables.js → ts/aztecrune/tables.ts} +0 -1
  106. package/src/{compactpdf417/decoder.js → ts/compactpdf417/decoder.ts} +0 -1
  107. package/src/{compactpdf417/detector.js → ts/compactpdf417/detector.ts} +0 -1
  108. package/src/{compactpdf417/encoder.js → ts/compactpdf417/encoder.ts} +0 -1
  109. package/src/{compactpdf417/index.js → ts/compactpdf417/index.ts} +0 -5
  110. package/src/{compactpdf417/tables.js → ts/compactpdf417/tables.ts} +0 -1
  111. package/src/{core/bit-buffer.js → ts/core/bit-buffer.ts} +0 -1
  112. package/src/{core/bit-matrix.js → ts/core/bit-matrix.ts} +0 -1
  113. package/src/{core/errors.js → ts/core/errors.ts} +0 -1
  114. package/src/{core/galois-field.js → ts/core/galois-field.ts} +0 -1
  115. package/src/{core/index.js → ts/core/index.ts} +0 -1
  116. package/src/{core/reed-solomon.js → ts/core/reed-solomon.ts} +0 -1
  117. package/src/{databar/codec.js → ts/databar/codec.ts} +0 -1
  118. package/src/{databar/decoder.js → ts/databar/decoder.ts} +0 -1
  119. package/src/{databar/encoder.js → ts/databar/encoder.ts} +0 -1
  120. package/src/{databar/gs1.js → ts/databar/gs1.ts} +0 -1
  121. package/src/{databar/index.js → ts/databar/index.ts} +0 -1
  122. package/src/{databar/patterns.js → ts/databar/patterns.ts} +0 -1
  123. package/src/{databar/tables.js → ts/databar/tables.ts} +0 -1
  124. package/src/{datamatrix/decoder.js → ts/datamatrix/decoder.ts} +0 -1
  125. package/{typings → src/ts}/datamatrix/detector.d.ts +2 -2
  126. package/src/{datamatrix/detector.js → ts/datamatrix/detector.ts} +0 -1
  127. package/src/{datamatrix/encoder.js → ts/datamatrix/encoder.ts} +0 -1
  128. package/src/{datamatrix/index.js → ts/datamatrix/index.ts} +0 -1
  129. package/src/{datamatrix/tables.js → ts/datamatrix/tables.ts} +0 -1
  130. package/src/{frameqr/decoder.js → ts/frameqr/decoder.ts} +15 -16
  131. package/src/{frameqr/detector.js → ts/frameqr/detector.ts} +2 -3
  132. package/src/{frameqr/encoder.js → ts/frameqr/encoder.ts} +7 -8
  133. package/src/{frameqr/index.js → ts/frameqr/index.ts} +0 -1
  134. package/src/{frameqr/tables.js → ts/frameqr/tables.ts} +10 -11
  135. package/src/{image/binarizer.js → ts/image/binarizer.ts} +0 -1
  136. package/src/{image/grid-sampler.js → ts/image/grid-sampler.ts} +0 -1
  137. package/src/{image/index.js → ts/image/index.ts} +0 -1
  138. package/src/{image/luminance.js → ts/image/luminance.ts} +0 -1
  139. package/src/{image/perspective.js → ts/image/perspective.ts} +0 -1
  140. package/{typings → src/ts}/index.d.ts +3 -3
  141. package/src/ts/index.ts +790 -0
  142. package/src/{micropdf417/compaction.js → ts/micropdf417/compaction.ts} +0 -1
  143. package/src/{micropdf417/decoder.js → ts/micropdf417/decoder.ts} +0 -1
  144. package/src/{micropdf417/detector.js → ts/micropdf417/detector.ts} +0 -1
  145. package/src/{micropdf417/encoder.js → ts/micropdf417/encoder.ts} +0 -1
  146. package/src/{micropdf417/error-correction.js → ts/micropdf417/error-correction.ts} +0 -1
  147. package/src/{micropdf417/index.js → ts/micropdf417/index.ts} +0 -1
  148. package/src/{micropdf417/tables.js → ts/micropdf417/tables.ts} +0 -1
  149. package/src/{microqr/decoder.js → ts/microqr/decoder.ts} +0 -1
  150. package/src/{microqr/detector.js → ts/microqr/detector.ts} +0 -1
  151. package/src/{microqr/encoder.js → ts/microqr/encoder.ts} +0 -1
  152. package/src/{microqr/index.js → ts/microqr/index.ts} +0 -1
  153. package/src/{microqr/tables.js → ts/microqr/tables.ts} +0 -1
  154. package/src/{oned/addons.js → ts/oned/addons.ts} +0 -1
  155. package/src/{oned/index.js → ts/oned/index.ts} +0 -1
  156. package/src/{oned/patterns.js → ts/oned/patterns.ts} +0 -1
  157. package/src/{oned/reader.js → ts/oned/reader.ts} +0 -1
  158. package/src/{oned/writers.js → ts/oned/writers.ts} +0 -1
  159. package/src/{pdf417/compaction.js → ts/pdf417/compaction.ts} +0 -1
  160. package/src/{pdf417/decoder.js → ts/pdf417/decoder.ts} +0 -1
  161. package/src/{pdf417/detector.js → ts/pdf417/detector.ts} +0 -1
  162. package/src/{pdf417/encoder.js → ts/pdf417/encoder.ts} +0 -1
  163. package/src/{pdf417/error-correction.js → ts/pdf417/error-correction.ts} +0 -1
  164. package/src/ts/pdf417/index.ts +6 -0
  165. package/src/{pdf417/tables.js → ts/pdf417/tables.ts} +0 -1
  166. package/src/{qr/decoder.js → ts/qr/decoder.ts} +0 -1
  167. package/src/{qr/detector.js → ts/qr/detector.ts} +0 -1
  168. package/src/{qr/encoder.js → ts/qr/encoder.ts} +0 -1
  169. package/src/{qr/index.js → ts/qr/index.ts} +0 -1
  170. package/src/{qr/tables.js → ts/qr/tables.ts} +0 -1
  171. package/src/{render/image-data.js → ts/render/image-data.ts} +0 -1
  172. package/src/{render/index.js → ts/render/index.ts} +0 -1
  173. package/src/{render/options.js → ts/render/options.ts} +0 -1
  174. package/src/{render/png.js → ts/render/png.ts} +0 -1
  175. package/src/{render/svg.js → ts/render/svg.ts} +0 -1
  176. package/src/{render/webgl.js → ts/render/webgl.ts} +0 -1
  177. package/src/{render/webgpu.js → ts/render/webgpu.ts} +0 -1
  178. package/src/{rmqr/decoder.js → ts/rmqr/decoder.ts} +0 -1
  179. package/src/{rmqr/detector.js → ts/rmqr/detector.ts} +0 -1
  180. package/src/{rmqr/encoder.js → ts/rmqr/encoder.ts} +0 -1
  181. package/src/{rmqr/index.js → ts/rmqr/index.ts} +0 -1
  182. package/src/{rmqr/tables.js → ts/rmqr/tables.ts} +0 -1
  183. /package/{typings → src/ts}/aztec/decoder.d.ts +0 -0
  184. /package/{typings → src/ts}/aztec/encoder.d.ts +0 -0
  185. /package/{typings → src/ts}/aztec/high-level.d.ts +0 -0
  186. /package/{typings → src/ts}/aztec/index.d.ts +0 -0
  187. /package/{typings → src/ts}/aztec/tables.d.ts +0 -0
  188. /package/{typings → src/ts}/aztecrune/decoder.d.ts +0 -0
  189. /package/{typings → src/ts}/aztecrune/detector.d.ts +0 -0
  190. /package/{typings → src/ts}/aztecrune/encoder.d.ts +0 -0
  191. /package/{typings → src/ts}/aztecrune/index.d.ts +0 -0
  192. /package/{typings → src/ts}/aztecrune/tables.d.ts +0 -0
  193. /package/{typings → src/ts}/bundle/sythos-barcode.d.ts +0 -0
  194. /package/{typings → src/ts}/bundle/sythos-barcode.esm.d.ts +0 -0
  195. /package/{typings → src/ts}/compactpdf417/decoder.d.ts +0 -0
  196. /package/{typings → src/ts}/compactpdf417/detector.d.ts +0 -0
  197. /package/{typings → src/ts}/compactpdf417/encoder.d.ts +0 -0
  198. /package/{typings → src/ts}/compactpdf417/index.d.ts +0 -0
  199. /package/{typings → src/ts}/compactpdf417/tables.d.ts +0 -0
  200. /package/{typings → src/ts}/core/bit-buffer.d.ts +0 -0
  201. /package/{typings → src/ts}/core/bit-matrix.d.ts +0 -0
  202. /package/{typings → src/ts}/core/errors.d.ts +0 -0
  203. /package/{typings → src/ts}/core/galois-field.d.ts +0 -0
  204. /package/{typings → src/ts}/core/index.d.ts +0 -0
  205. /package/{typings → src/ts}/core/reed-solomon.d.ts +0 -0
  206. /package/{typings → src/ts}/databar/codec.d.ts +0 -0
  207. /package/{typings → src/ts}/databar/decoder.d.ts +0 -0
  208. /package/{typings → src/ts}/databar/encoder.d.ts +0 -0
  209. /package/{typings → src/ts}/databar/gs1.d.ts +0 -0
  210. /package/{typings → src/ts}/databar/index.d.ts +0 -0
  211. /package/{typings → src/ts}/databar/patterns.d.ts +0 -0
  212. /package/{typings → src/ts}/databar/tables.d.ts +0 -0
  213. /package/{typings → src/ts}/datamatrix/decoder.d.ts +0 -0
  214. /package/{typings → src/ts}/datamatrix/encoder.d.ts +0 -0
  215. /package/{typings → src/ts}/datamatrix/index.d.ts +0 -0
  216. /package/{typings → src/ts}/datamatrix/tables.d.ts +0 -0
  217. /package/{typings → src/ts}/frameqr/decoder.d.ts +0 -0
  218. /package/{typings → src/ts}/frameqr/detector.d.ts +0 -0
  219. /package/{typings → src/ts}/frameqr/encoder.d.ts +0 -0
  220. /package/{typings → src/ts}/frameqr/index.d.ts +0 -0
  221. /package/{typings → src/ts}/frameqr/tables.d.ts +0 -0
  222. /package/{typings → src/ts}/image/binarizer.d.ts +0 -0
  223. /package/{typings → src/ts}/image/grid-sampler.d.ts +0 -0
  224. /package/{typings → src/ts}/image/index.d.ts +0 -0
  225. /package/{typings → src/ts}/image/luminance.d.ts +0 -0
  226. /package/{typings → src/ts}/image/perspective.d.ts +0 -0
  227. /package/{typings → src/ts}/micropdf417/compaction.d.ts +0 -0
  228. /package/{typings → src/ts}/micropdf417/decoder.d.ts +0 -0
  229. /package/{typings → src/ts}/micropdf417/detector.d.ts +0 -0
  230. /package/{typings → src/ts}/micropdf417/encoder.d.ts +0 -0
  231. /package/{typings → src/ts}/micropdf417/error-correction.d.ts +0 -0
  232. /package/{typings → src/ts}/micropdf417/index.d.ts +0 -0
  233. /package/{typings → src/ts}/micropdf417/tables.d.ts +0 -0
  234. /package/{typings → src/ts}/microqr/decoder.d.ts +0 -0
  235. /package/{typings → src/ts}/microqr/detector.d.ts +0 -0
  236. /package/{typings → src/ts}/microqr/encoder.d.ts +0 -0
  237. /package/{typings → src/ts}/microqr/index.d.ts +0 -0
  238. /package/{typings → src/ts}/microqr/tables.d.ts +0 -0
  239. /package/{typings → src/ts}/oned/addons.d.ts +0 -0
  240. /package/{typings → src/ts}/oned/index.d.ts +0 -0
  241. /package/{typings → src/ts}/oned/patterns.d.ts +0 -0
  242. /package/{typings → src/ts}/oned/reader.d.ts +0 -0
  243. /package/{typings → src/ts}/oned/writers.d.ts +0 -0
  244. /package/{typings → src/ts}/pdf417/compaction.d.ts +0 -0
  245. /package/{typings → src/ts}/pdf417/decoder.d.ts +0 -0
  246. /package/{typings → src/ts}/pdf417/detector.d.ts +0 -0
  247. /package/{typings → src/ts}/pdf417/encoder.d.ts +0 -0
  248. /package/{typings → src/ts}/pdf417/error-correction.d.ts +0 -0
  249. /package/{typings → src/ts}/pdf417/index.d.ts +0 -0
  250. /package/{typings → src/ts}/pdf417/tables.d.ts +0 -0
  251. /package/{typings → src/ts}/qr/decoder.d.ts +0 -0
  252. /package/{typings → src/ts}/qr/detector.d.ts +0 -0
  253. /package/{typings → src/ts}/qr/encoder.d.ts +0 -0
  254. /package/{typings → src/ts}/qr/index.d.ts +0 -0
  255. /package/{typings → src/ts}/qr/tables.d.ts +0 -0
  256. /package/{typings → src/ts}/render/image-data.d.ts +0 -0
  257. /package/{typings → src/ts}/render/index.d.ts +0 -0
  258. /package/{typings → src/ts}/render/options.d.ts +0 -0
  259. /package/{typings → src/ts}/render/png.d.ts +0 -0
  260. /package/{typings → src/ts}/render/svg.d.ts +0 -0
  261. /package/{typings → src/ts}/render/webgl.d.ts +0 -0
  262. /package/{typings → src/ts}/render/webgpu.d.ts +0 -0
  263. /package/{typings → src/ts}/rmqr/decoder.d.ts +0 -0
  264. /package/{typings → src/ts}/rmqr/detector.d.ts +0 -0
  265. /package/{typings → src/ts}/rmqr/encoder.d.ts +0 -0
  266. /package/{typings → src/ts}/rmqr/index.d.ts +0 -0
  267. /package/{typings → src/ts}/rmqr/tables.d.ts +0 -0
@@ -0,0 +1,1383 @@
1
+ /*!
2
+ * Sythos Barcode Suite
3
+ *
4
+ * MIT License
5
+ *
6
+ * Copyright (c) 2026 Sythos
7
+ *
8
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
9
+ * of this software and associated documentation files (the "Software"), to deal
10
+ * in the Software without restriction, including without limitation the rights
11
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
+ * copies of the Software, and to permit persons to whom the Software is
13
+ * furnished to do so, subject to the following conditions:
14
+ *
15
+ * The above copyright notice and this permission notice shall be included in all
16
+ * copies or substantial portions of the Software.
17
+ *
18
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
+ * SOFTWARE.
25
+ *
26
+ * SPDX-License-Identifier: MIT
27
+ *
28
+ * Original work. No code from any other barcode implementation.
29
+ */
30
+ /**
31
+ * Linear barcode reading.
32
+ *
33
+ * A linear symbol carries no vertical information, so reading one is a 1D
34
+ * signal problem: take a horizontal slice, measure the run lengths of dark and
35
+ * light, and match those against the symbology's patterns.
36
+ *
37
+ * Two consequences shape everything here:
38
+ *
39
+ * - **Scan many rows, not one.** Any single row can be crossed by a fold, a
40
+ * glare highlight or a printing void. Rows are sampled across the height and
41
+ * the first that decodes cleanly wins.
42
+ * - **Match ratios, not absolute widths.** The scale is unknown and varies
43
+ * across the image under perspective, so patterns are compared after
44
+ * normalising by total width. This is what makes a symbol readable at any
45
+ * size without being told the module width.
46
+ *
47
+ * @module oned/reader
48
+ */
49
+ import { NotFoundError } from '../core/errors.js';
50
+ import { EAN_L, EAN_G, EAN_R, EAN13_PARITY, UPCE_PARITY, CODE39, CODE39_CHECK_SET, CODE93, CODE93_VALUES, CODE128, CODE128_START_A, CODE128_START_B, CODE128_START_C, CODE128_STOP, CODE128_FNC1, CODE128_CODE_A, CODE128_CODE_B, CODE128_CODE_C, CODE128_SHIFT, ITF, CODABAR, CODABAR_START_STOP, CODE11, CODE11_START_STOP, } from './patterns.js';
51
+ import { ean13CheckDigit, upceToUpcaBody } from './writers.js';
52
+ import { EAN2_PARITY, EAN5_PARITY, ean5Checksum, } from './addons.js';
53
+ import { decodeDataBar14Scanline } from '../databar/decoder.js';
54
+ /* ------------------------------------------------------------------ *
55
+ * Pattern matching primitives
56
+ * ------------------------------------------------------------------ */
57
+ /**
58
+ * Compare measured run lengths against an ideal pattern, scale-independently.
59
+ *
60
+ * Returns a normalised mismatch score, or Infinity when any single element is
61
+ * further out of proportion than `maxIndividual` allows. Rejecting on the
62
+ * worst element as well as the total is what stops a run of noise whose widths
63
+ * happen to average out from being accepted as a character.
64
+ *
65
+ * @param {number[]} counters Measured widths, in pixels.
66
+ * @param {number[]} pattern Ideal widths, in modules.
67
+ * @param {number} maxIndividual Tolerance per element, as a fraction of a module.
68
+ * @returns {number}
69
+ */
70
+ export function patternVariance(counters, pattern, maxIndividual) {
71
+ const n = counters.length;
72
+ if (n !== pattern.length)
73
+ return Infinity;
74
+ let total = 0;
75
+ let patternTotal = 0;
76
+ for (let i = 0; i < n; i++) {
77
+ total += counters[i];
78
+ patternTotal += pattern[i];
79
+ }
80
+ if (total < patternTotal)
81
+ return Infinity; // fewer pixels than modules
82
+ const unit = total / patternTotal;
83
+ const maxVariance = unit * maxIndividual;
84
+ let variance = 0;
85
+ for (let i = 0; i < n; i++) {
86
+ const expected = pattern[i] * unit;
87
+ const delta = Math.abs(counters[i] - expected);
88
+ if (delta > maxVariance)
89
+ return Infinity;
90
+ variance += delta;
91
+ }
92
+ return variance / total;
93
+ }
94
+ /**
95
+ * Measure alternating run lengths starting at `start`.
96
+ *
97
+ * @param {Uint8Array} row One byte per pixel, 1 = dark.
98
+ * @param {number} start
99
+ * @param {number[]} counters Filled in place; its length sets how many runs to read.
100
+ * @returns {boolean} False if the row ended before the runs were filled.
101
+ */
102
+ export function recordPattern(row, start, counters) {
103
+ counters.fill(0);
104
+ const end = row.length;
105
+ if (start >= end)
106
+ return false;
107
+ let isDark = row[start] === 1;
108
+ let index = 0;
109
+ let i = start;
110
+ while (i < end) {
111
+ if ((row[i] === 1) === isDark) {
112
+ counters[index]++;
113
+ }
114
+ else {
115
+ index++;
116
+ if (index === counters.length)
117
+ break;
118
+ counters[index] = 1;
119
+ isDark = !isDark;
120
+ }
121
+ i++;
122
+ }
123
+ // The final run may legitimately reach the edge of the image.
124
+ return index === counters.length || (index === counters.length - 1 && i === end);
125
+ }
126
+ /**
127
+ * Classify run lengths into narrow and wide, for the n/w symbologies.
128
+ *
129
+ * The wide:narrow ratio is not fixed by these formats — it is anywhere from
130
+ * 2:1 to 3:1 and varies with the printer — so the split has to be discovered
131
+ * from the data. Candidate thresholds are tried from the smallest counter
132
+ * upward until exactly the expected number of wide elements falls out.
133
+ *
134
+ * @param {number[]} counters
135
+ * @param {number} expectedWide How many elements must be wide.
136
+ * @returns {number} Bit pattern, MSB = first element wide; -1 if undecidable.
137
+ */
138
+ export function toNarrowWidePattern(counters, expectedWide) {
139
+ const n = counters.length;
140
+ let maxNarrow = 0;
141
+ for (;;) {
142
+ let nextNarrow = Infinity;
143
+ for (let i = 0; i < n; i++) {
144
+ if (counters[i] > maxNarrow && counters[i] < nextNarrow)
145
+ nextNarrow = counters[i];
146
+ }
147
+ if (nextNarrow === Infinity)
148
+ return -1;
149
+ maxNarrow = nextNarrow;
150
+ let wideCount = 0;
151
+ let pattern = 0;
152
+ let wideTotal = 0;
153
+ let narrowTotal = 0;
154
+ for (let i = 0; i < n; i++) {
155
+ if (counters[i] > maxNarrow) {
156
+ pattern |= 1 << (n - 1 - i);
157
+ wideCount++;
158
+ wideTotal += counters[i];
159
+ }
160
+ else {
161
+ narrowTotal += counters[i];
162
+ }
163
+ }
164
+ if (wideCount === expectedWide) {
165
+ // Sanity: a wide element should be clearly wider than a narrow one.
166
+ const narrowCount = n - wideCount;
167
+ if (narrowCount === 0)
168
+ return -1;
169
+ const avgWide = wideTotal / wideCount;
170
+ const avgNarrow = narrowTotal / narrowCount;
171
+ if (avgWide < avgNarrow * 1.4)
172
+ return -1;
173
+ return pattern;
174
+ }
175
+ if (wideCount < expectedWide)
176
+ return -1;
177
+ }
178
+ }
179
+ /** Convert an 'n'/'w' pattern string to the same bit encoding. */
180
+ function nwToBits(pattern) {
181
+ let bits = 0;
182
+ for (let i = 0; i < pattern.length; i++) {
183
+ if (pattern[i] === 'w')
184
+ bits |= 1 << (pattern.length - 1 - i);
185
+ }
186
+ return bits;
187
+ }
188
+ /** Convert a digit-width pattern string to a numeric array. */
189
+ function widthsToArray(pattern) {
190
+ return [...pattern].map(Number);
191
+ }
192
+ /** Convert a module string ('0'/'1') to run lengths. */
193
+ function modulesToRuns(modules) {
194
+ const runs = [];
195
+ let current = modules[0];
196
+ let count = 0;
197
+ for (const ch of modules) {
198
+ if (ch === current)
199
+ count++;
200
+ else {
201
+ runs.push(count);
202
+ current = ch;
203
+ count = 1;
204
+ }
205
+ }
206
+ runs.push(count);
207
+ return runs;
208
+ }
209
+ /* ------------------------------------------------------------------ *
210
+ * Precomputed lookup structures
211
+ * ------------------------------------------------------------------ */
212
+ const EAN_L_RUNS = EAN_L.map(modulesToRuns);
213
+ const EAN_G_RUNS = EAN_G.map(modulesToRuns);
214
+ const EAN_R_RUNS = EAN_R.map(modulesToRuns);
215
+ const CODE128_RUNS = CODE128.map(widthsToArray);
216
+ const CODE93_RUNS = Object.fromEntries(Object.entries(CODE93).map(([k, v]) => [k, widthsToArray(v)]));
217
+ const CODE39_BITS = Object.fromEntries(Object.entries(CODE39).map(([k, v]) => [nwToBits(v), k]));
218
+ const CODABAR_BITS = Object.fromEntries(Object.entries(CODABAR).map(([k, v]) => [nwToBits(v), k]));
219
+ const ITF_BITS = Object.fromEntries(ITF.map((v, i) => [nwToBits(v), i]));
220
+ const CODE11_BITS = Object.fromEntries(Object.entries(CODE11).flatMap(([ch, value]) => {
221
+ const bits = nwToBits(value);
222
+ return [[`${bits}:1`, ch], [`${bits}:2`, ch]];
223
+ }));
224
+ const CODE11_START_BITS = nwToBits(CODE11_START_STOP);
225
+ const CODE11_CHARSET = '0123456789-';
226
+ /**
227
+ * Shortest ITF payload treated as a real read.
228
+ *
229
+ * ITF is always an even number of digits and real-world payloads are at least
230
+ * six (ITF-6, ITF-14 and the GS1 variants). Accepting two digits meant any
231
+ * pair of matching runs inside an unrelated symbol read as a valid ITF.
232
+ */
233
+ const MIN_ITF_DIGITS = 6;
234
+ const START_END_PATTERN = [1, 1, 1];
235
+ const MIDDLE_PATTERN = [1, 1, 1, 1, 1];
236
+ const UPCE_END_PATTERN = [1, 1, 1, 1, 1, 1];
237
+ /* ------------------------------------------------------------------ *
238
+ * EAN / UPC
239
+ * ------------------------------------------------------------------ */
240
+ /**
241
+ * Decode one EAN/UPC digit, reporting which parity set matched.
242
+ *
243
+ * @param {Uint8Array} row
244
+ * @param {number} start
245
+ * @param {boolean} rightHand True to match only the R set.
246
+ * @returns {{digit: number, even: boolean, end: number} | null}
247
+ */
248
+ function decodeEANDigit(row, start, rightHand) {
249
+ const counters = [0, 0, 0, 0];
250
+ if (!recordPattern(row, start, counters))
251
+ return null;
252
+ const width = counters[0] + counters[1] + counters[2] + counters[3];
253
+ let best = null;
254
+ let bestVariance = 0.48; // reject anything worse than this
255
+ const consider = (runs, digit, even) => {
256
+ const v = patternVariance(counters, runs, 0.7);
257
+ if (v < bestVariance) {
258
+ bestVariance = v;
259
+ best = { digit, even, end: start + width };
260
+ }
261
+ };
262
+ for (let d = 0; d < 10; d++) {
263
+ if (rightHand) {
264
+ consider(EAN_R_RUNS[d], d, false);
265
+ }
266
+ else {
267
+ consider(EAN_L_RUNS[d], d, false);
268
+ consider(EAN_G_RUNS[d], d, true);
269
+ }
270
+ }
271
+ return best;
272
+ }
273
+ /**
274
+ * @param {Uint8Array} row
275
+ * @returns {{format: string, text: string} | null}
276
+ */
277
+ function decodeEANFamily(row) {
278
+ const guard = findGuard(row, 0, START_END_PATTERN, false);
279
+ if (!guard)
280
+ return null;
281
+ let offset = guard.end;
282
+ const digits = [];
283
+ let parityBits = 0;
284
+ // Six left-hand digits, recording parity as we go.
285
+ for (let i = 0; i < 6; i++) {
286
+ const d = decodeEANDigit(row, offset, false);
287
+ if (!d)
288
+ return null;
289
+ digits.push(d.digit);
290
+ if (d.even)
291
+ parityBits |= 1 << (5 - i);
292
+ offset = d.end;
293
+ }
294
+ // EAN-8 has no even-parity digits and a middle guard at a different offset;
295
+ // try the 13-digit reading first, then fall back.
296
+ const middle = matchAt(row, offset, MIDDLE_PATTERN);
297
+ if (middle) {
298
+ offset = middle.end;
299
+ for (let i = 0; i < 6; i++) {
300
+ const d = decodeEANDigit(row, offset, true);
301
+ if (!d)
302
+ return null;
303
+ digits.push(d.digit);
304
+ offset = d.end;
305
+ }
306
+ const trailing = matchAt(row, offset, START_END_PATTERN);
307
+ if (!trailing)
308
+ return null;
309
+ const parityStr = [];
310
+ for (let i = 0; i < 6; i++)
311
+ parityStr.push((parityBits >> (5 - i)) & 1 ? 'G' : 'L');
312
+ const first = EAN13_PARITY.indexOf(parityStr.join(''));
313
+ if (first < 0)
314
+ return null;
315
+ const text = String(first) + digits.join('');
316
+ if (Number(text[12]) !== ean13CheckDigit(text.slice(0, 12)))
317
+ return null;
318
+ // A leading zero means this was printed as UPC-A.
319
+ const result = first === 0
320
+ ? { format: 'upca', text: text.slice(1) }
321
+ : { format: 'ean13', text };
322
+ return attachEANAddon(result, row, trailing.end);
323
+ }
324
+ return null;
325
+ }
326
+ /**
327
+ * EAN-8: four left digits, middle guard, four right digits.
328
+ *
329
+ * @param {Uint8Array} row
330
+ * @returns {{format: string, text: string} | null}
331
+ */
332
+ function decodeEAN8(row) {
333
+ const guard = findGuard(row, 0, START_END_PATTERN, false);
334
+ if (!guard)
335
+ return null;
336
+ let offset = guard.end;
337
+ const digits = [];
338
+ for (let i = 0; i < 4; i++) {
339
+ const d = decodeEANDigit(row, offset, false);
340
+ if (!d || d.even)
341
+ return null; // EAN-8 left digits are all odd parity
342
+ digits.push(d.digit);
343
+ offset = d.end;
344
+ }
345
+ const middle = matchAt(row, offset, MIDDLE_PATTERN);
346
+ if (!middle)
347
+ return null;
348
+ offset = middle.end;
349
+ for (let i = 0; i < 4; i++) {
350
+ const d = decodeEANDigit(row, offset, true);
351
+ if (!d)
352
+ return null;
353
+ digits.push(d.digit);
354
+ offset = d.end;
355
+ }
356
+ const trailing = matchAt(row, offset, START_END_PATTERN);
357
+ if (!trailing)
358
+ return null;
359
+ const text = digits.join('');
360
+ if (Number(text[7]) !== ean13CheckDigit(text.slice(0, 7)))
361
+ return null;
362
+ return attachEANAddon({ format: 'ean8', text }, row, trailing.end);
363
+ }
364
+ /**
365
+ * UPC-E: six digits, parity-encoded, terminated by a six-element guard.
366
+ *
367
+ * @param {Uint8Array} row
368
+ * @returns {{format: string, text: string} | null}
369
+ */
370
+ function decodeUPCE(row) {
371
+ const guard = findGuard(row, 0, START_END_PATTERN, false);
372
+ if (!guard)
373
+ return null;
374
+ let offset = guard.end;
375
+ const digits = [];
376
+ let parityBits = 0;
377
+ for (let i = 0; i < 6; i++) {
378
+ const d = decodeEANDigit(row, offset, false);
379
+ if (!d)
380
+ return null;
381
+ digits.push(d.digit);
382
+ if (d.even)
383
+ parityBits |= 1 << (5 - i);
384
+ offset = d.end;
385
+ }
386
+ // Six digits and then the end guard, in that order and nothing between. The
387
+ // EAN readers above match their trailing guard; this one used to stop at the
388
+ // last digit, which let it report a symbol it had never seen the end of.
389
+ const trailing = matchAt(row, offset, UPCE_END_PATTERN);
390
+ if (!trailing)
391
+ return null;
392
+ const parityStr = [];
393
+ for (let i = 0; i < 6; i++)
394
+ parityStr.push((parityBits >> (5 - i)) & 1 ? 'E' : 'O');
395
+ const check = UPCE_PARITY.indexOf(parityStr.join(''));
396
+ if (check < 0)
397
+ return null;
398
+ // The parity pattern carries the check digit and nothing else, so on its own
399
+ // it says nothing about the six digits it was read alongside: any run whose
400
+ // parities happen to spell one of the ten patterns would be accepted. What
401
+ // ties the two together is the check digit itself — expand the body to the
402
+ // UPC-A it stands for and confirm the digits produce the check digit the
403
+ // parity claimed. Six digits scraped out of a neighbouring symbol pass the
404
+ // parity test one time in ten and this one almost never.
405
+ const body = digits.join('');
406
+ if (ean13CheckDigit(upceToUpcaBody(0, body)) !== check)
407
+ return null;
408
+ return attachEANAddon({ format: 'upce', text: '0' + body + String(check) }, row, trailing.end);
409
+ }
410
+ /* ------------------------------------------------------------------ *
411
+ * Guard finding
412
+ * ------------------------------------------------------------------ */
413
+ /**
414
+ * Scan forward for the first place a pattern matches, starting on a dark run.
415
+ *
416
+ * @param {Uint8Array} row
417
+ * @param {number} from
418
+ * @param {number[]} pattern
419
+ * @param {boolean} startsLight
420
+ * @returns {{start: number, end: number} | null}
421
+ */
422
+ function findGuard(row, from, pattern, startsLight) {
423
+ const counters = new Array(pattern.length).fill(0);
424
+ const width = row.length;
425
+ let index = 0;
426
+ let isDark = !startsLight;
427
+ let i = from;
428
+ // Skip any leading run of the wrong colour.
429
+ while (i < width && (row[i] === 1) !== isDark)
430
+ i++;
431
+ let patternStart = i;
432
+ counters.fill(0);
433
+ while (i < width) {
434
+ if ((row[i] === 1) === isDark) {
435
+ counters[index]++;
436
+ }
437
+ else {
438
+ if (index === pattern.length - 1) {
439
+ if (patternVariance(counters, pattern, 0.7) < 0.5) {
440
+ return { start: patternStart, end: i };
441
+ }
442
+ // Slide the window forward by two runs and keep looking.
443
+ patternStart += counters[0] + counters[1];
444
+ for (let k = 2; k < pattern.length; k++)
445
+ counters[k - 2] = counters[k];
446
+ counters[pattern.length - 2] = 0;
447
+ counters[pattern.length - 1] = 0;
448
+ index--;
449
+ }
450
+ else {
451
+ index++;
452
+ }
453
+ counters[index] = 1;
454
+ isDark = !isDark;
455
+ }
456
+ i++;
457
+ }
458
+ return null;
459
+ }
460
+ /**
461
+ * Match a pattern at an exact position.
462
+ *
463
+ * @param {Uint8Array} row
464
+ * @param {number} start
465
+ * @param {number[]} pattern
466
+ * @returns {{end: number} | null}
467
+ */
468
+ function matchAt(row, start, pattern) {
469
+ const counters = new Array(pattern.length).fill(0);
470
+ if (!recordPattern(row, start, counters))
471
+ return null;
472
+ if (patternVariance(counters, pattern, 0.7) >= 0.5)
473
+ return null;
474
+ let width = 0;
475
+ for (const c of counters)
476
+ width += c;
477
+ return { end: start + width };
478
+ }
479
+ /* ------------------------------------------------------------------ *
480
+ * EAN supplements
481
+ * ------------------------------------------------------------------ */
482
+ /**
483
+ * Decode a supplement immediately following a validated EAN/UPC symbol.
484
+ * The caller supplies the end of the parent trailing guard, so a supplement
485
+ * can never be accepted as an unrelated standalone linear symbol.
486
+ *
487
+ * @param {Uint8Array} row
488
+ * @param {number} baseEnd
489
+ * @param {2|5} digitCount
490
+ * @returns {{format:'ean2'|'ean5', text:string, parity:string, checksum?:number, end:number}|null}
491
+ */
492
+ function decodeEANSupplementRow(row, baseEnd, digitCount) {
493
+ const guard = findGuard(row, baseEnd, [1, 1, 2], false);
494
+ if (!guard || guard.start - baseEnd < 4)
495
+ return null;
496
+ let offset = guard.end;
497
+ let text = '';
498
+ let parity = '';
499
+ for (let i = 0; i < digitCount; i++) {
500
+ const digit = decodeEANDigit(row, offset, false);
501
+ if (!digit)
502
+ return null;
503
+ text += String(digit.digit);
504
+ parity += digit.even ? 'B' : 'A';
505
+ offset = digit.end;
506
+ if (i + 1 < digitCount) {
507
+ const separator = matchAt(row, offset, [1, 1]);
508
+ if (!separator)
509
+ return null;
510
+ offset = separator.end;
511
+ }
512
+ }
513
+ if (digitCount === 2) {
514
+ if (EAN2_PARITY[Number(text) % 4] !== parity)
515
+ return null;
516
+ return { format: 'ean2', text, parity, end: offset };
517
+ }
518
+ const checksum = ean5Checksum(text);
519
+ if (EAN5_PARITY[checksum] !== parity)
520
+ return null;
521
+ return { format: 'ean5', text, parity, checksum, end: offset };
522
+ }
523
+ /**
524
+ * Attach the longest valid supplement to a base result. EAN-5 is attempted
525
+ * first so its prefix cannot be reported as a shorter EAN-2 symbol.
526
+ *
527
+ * @param {object} base
528
+ * @param {Uint8Array} row
529
+ * @param {number} baseEnd
530
+ * @returns {object}
531
+ */
532
+ function attachEANAddon(base, row, baseEnd) {
533
+ const addon = decodeEANSupplementRow(row, baseEnd, 5)
534
+ ?? decodeEANSupplementRow(row, baseEnd, 2);
535
+ if (!addon)
536
+ return base;
537
+ const { end, ...publicAddon } = addon;
538
+ void end;
539
+ return { ...base, addon: publicAddon };
540
+ }
541
+ /* ------------------------------------------------------------------ *
542
+ * Code 11 and MSI/Plessey
543
+ * ------------------------------------------------------------------ */
544
+ /** @param {number[]} counters @returns {number} */
545
+ function counterTotal(counters) {
546
+ return counters.reduce((sum, value) => sum + value, 0);
547
+ }
548
+ /**
549
+ * Apply the Code 11 C/K checksum grammar used by the writer.
550
+ *
551
+ * @param {string} encoded
552
+ * @param {boolean|undefined} requested
553
+ * @returns {string|null}
554
+ */
555
+ function finalizeCode11(encoded, requested) {
556
+ const weighted = (text, maxWeight) => {
557
+ let sum = 0;
558
+ for (let i = 0; i < text.length; i++) {
559
+ const weight = ((text.length - 1 - i) % maxWeight) + 1;
560
+ sum += weight * CODE11_CHARSET.indexOf(text[i]);
561
+ }
562
+ return sum;
563
+ };
564
+ const validC = (text) => {
565
+ if (text.length < 2)
566
+ return null;
567
+ const body = text.slice(0, -1);
568
+ const expected = CODE11_CHARSET[weighted(body, 10) % 11];
569
+ return text[text.length - 1] === expected ? body : null;
570
+ };
571
+ const validCK = (text) => {
572
+ if (text.length < 12)
573
+ return null;
574
+ const body = text.slice(0, -2);
575
+ const c = text[text.length - 2];
576
+ const k = text[text.length - 1];
577
+ const expectedC = CODE11_CHARSET[weighted(body, 10) % 11];
578
+ if (c !== expectedC)
579
+ return null;
580
+ const expectedK = CODE11_CHARSET[weighted(body + c, 9) % 11];
581
+ return k === expectedK ? body : null;
582
+ };
583
+ if (requested === false)
584
+ return encoded;
585
+ if (requested === true) {
586
+ const checked = encoded.length >= 12 ? validCK(encoded) : validC(encoded);
587
+ return checked;
588
+ }
589
+ // With no explicit option, strip checks only when the complete grammar is
590
+ // unambiguous; otherwise preserve the literal payload.
591
+ return validCK(encoded) ?? validC(encoded) ?? encoded;
592
+ }
593
+ /**
594
+ * Decode Code 11 from one binarized scanline.
595
+ *
596
+ * @param {Uint8Array} row
597
+ * @param {object} [options]
598
+ * @param {boolean} [options.checkDigit]
599
+ * @returns {{format:'code11', text:string}|null}
600
+ */
601
+ export function decodeCode11(row, options = {}) {
602
+ const counters = new Array(5).fill(0);
603
+ let start = null;
604
+ for (let i = 0; i < row.length; i++) {
605
+ if (row[i] !== 1 || (i > 0 && row[i - 1] === 1))
606
+ continue;
607
+ if (!recordPattern(row, i, counters))
608
+ continue;
609
+ if (toNarrowWidePattern(counters, 2) === CODE11_START_BITS) {
610
+ start = { position: i, end: i + counterTotal(counters), scale: counterTotal(counters) / 9 };
611
+ break;
612
+ }
613
+ }
614
+ if (!start)
615
+ return null;
616
+ let offset = start.end;
617
+ while (offset < row.length && row[offset] === 0)
618
+ offset++;
619
+ let encoded = '';
620
+ for (let count = 0; count < 160 && offset < row.length; count++) {
621
+ if (!recordPattern(row, offset, counters))
622
+ return null;
623
+ const width = counterTotal(counters);
624
+ const stop = toNarrowWidePattern(counters, 2);
625
+ if (stop === CODE11_START_BITS) {
626
+ if (encoded.length === 0)
627
+ return null;
628
+ const stopEnd = offset + width;
629
+ let nextDark = stopEnd;
630
+ while (nextDark < row.length && row[nextDark] === 0)
631
+ nextDark++;
632
+ if (nextDark !== row.length && nextDark - stopEnd < Math.max(3, Math.ceil(start.scale * 3))) {
633
+ return null;
634
+ }
635
+ const text = finalizeCode11(encoded, options.checkDigit);
636
+ return text == null ? null : { format: 'code11', text };
637
+ }
638
+ let character = null;
639
+ for (const expectedWide of [1, 2]) {
640
+ const bits = toNarrowWidePattern(counters, expectedWide);
641
+ if (bits < 0)
642
+ continue;
643
+ const candidate = CODE11_BITS[`${bits}:${expectedWide}`];
644
+ if (candidate !== undefined) {
645
+ character = candidate;
646
+ break;
647
+ }
648
+ }
649
+ if (character == null)
650
+ return null;
651
+ encoded += character;
652
+ if (encoded.length > 128)
653
+ return null;
654
+ offset += width;
655
+ while (offset < row.length && row[offset] === 0)
656
+ offset++;
657
+ }
658
+ return null;
659
+ }
660
+ /** @param {string} encoded @param {boolean|undefined} requested */
661
+ function finalizeMSI(encoded, requested) {
662
+ if (requested !== true)
663
+ return encoded;
664
+ if (encoded.length < 2)
665
+ return null;
666
+ const body = encoded.slice(0, -1);
667
+ let odd = '';
668
+ for (let i = body.length - 1; i >= 0; i -= 2)
669
+ odd = body[i] + odd;
670
+ const doubled = String(Number(odd) * 2);
671
+ let sum = 0;
672
+ for (const ch of doubled)
673
+ sum += Number(ch);
674
+ for (let i = body.length - 2; i >= 0; i -= 2)
675
+ sum += Number(body[i]);
676
+ const expected = String((10 - (sum % 10)) % 10);
677
+ return encoded.endsWith(expected) ? body : null;
678
+ }
679
+ /**
680
+ * Decode MSI/Plessey from one binarized scanline.
681
+ *
682
+ * @param {Uint8Array} row
683
+ * @param {object} [options]
684
+ * @param {boolean} [options.checkDigit]
685
+ * @returns {{format:'msi', text:string}|null}
686
+ */
687
+ export function decodeMSI(row, options = {}) {
688
+ const start = findGuard(row, 0, [2, 1], false);
689
+ if (!start)
690
+ return null;
691
+ const scale = (start.end - start.start) / 3;
692
+ if (!(scale >= 1))
693
+ return null;
694
+ let offset = start.end;
695
+ let encoded = '';
696
+ const bitPatterns = [
697
+ { pattern: [1, 2], bit: '0' },
698
+ { pattern: [2, 1], bit: '1' },
699
+ ];
700
+ for (let digitIndex = 0; digitIndex < 80; digitIndex++) {
701
+ const stop = matchAt(row, offset, [1, 2, 1]);
702
+ if (stop && encoded.length > 0) {
703
+ let nextDark = stop.end;
704
+ while (nextDark < row.length && row[nextDark] === 0)
705
+ nextDark++;
706
+ if (nextDark === row.length ||
707
+ nextDark - stop.end >= Math.max(3, Math.ceil(scale * 3))) {
708
+ if (encoded.length < 6 && !(options.formats && options.formats.includes('msi'))) {
709
+ return null;
710
+ }
711
+ const text = finalizeMSI(encoded, options.checkDigit);
712
+ return text == null ? null : { format: 'msi', text };
713
+ }
714
+ }
715
+ let nibble = '';
716
+ for (let bitIndex = 0; bitIndex < 4; bitIndex++) {
717
+ let matched = null;
718
+ for (const candidate of bitPatterns) {
719
+ const found = matchAt(row, offset, candidate.pattern);
720
+ if (found) {
721
+ matched = { ...candidate, end: found.end };
722
+ break;
723
+ }
724
+ }
725
+ if (!matched)
726
+ return null;
727
+ nibble += matched.bit;
728
+ offset = matched.end;
729
+ }
730
+ const digit = Number.parseInt(nibble, 2);
731
+ if (digit > 9)
732
+ return null;
733
+ encoded += String(digit);
734
+ }
735
+ return null;
736
+ }
737
+ /* ------------------------------------------------------------------ *
738
+ * Code 128
739
+ * ------------------------------------------------------------------ */
740
+ /**
741
+ * @param {Uint8Array} row
742
+ * @returns {{format: string, text: string} | null}
743
+ */
744
+ function decodeCode128(row) {
745
+ // Locate whichever start symbol appears first.
746
+ let start = null;
747
+ for (const startCode of [CODE128_START_A, CODE128_START_B, CODE128_START_C]) {
748
+ const found = findGuard(row, 0, CODE128_RUNS[startCode], false);
749
+ if (found && (!start || found.start < start.found.start)) {
750
+ start = { code: startCode, found };
751
+ }
752
+ }
753
+ if (!start)
754
+ return null;
755
+ // Read every symbol up to the stop pattern first, and only then interpret
756
+ // them. The symbol immediately before the stop is the checksum, and it is
757
+ // indistinguishable from data while scanning — interpreting as we go would
758
+ // append it to the text (a Code C checksum of 70 arrives as the digits
759
+ // "70"). Collecting first makes dropping it exact rather than a guess.
760
+ const values = [];
761
+ let offset = start.found.end;
762
+ const counters = new Array(6).fill(0);
763
+ const stopCounters = new Array(7).fill(0);
764
+ for (;;) {
765
+ // The stop pattern has seven elements, so it must be tried before the
766
+ // six-element symbol set or its first six would match something.
767
+ if (recordPattern(row, offset, stopCounters) &&
768
+ patternVariance(stopCounters, CODE128_RUNS[CODE128_STOP], 0.7) < 0.38) {
769
+ break;
770
+ }
771
+ if (!recordPattern(row, offset, counters))
772
+ return null;
773
+ let best = -1;
774
+ let bestVariance = 0.4;
775
+ for (let c = 0; c < CODE128_RUNS.length - 1; c++) {
776
+ const v = patternVariance(counters, CODE128_RUNS[c], 0.7);
777
+ if (v < bestVariance) {
778
+ bestVariance = v;
779
+ best = c;
780
+ }
781
+ }
782
+ if (best < 0)
783
+ return null;
784
+ let width = 0;
785
+ for (const c of counters)
786
+ width += c;
787
+ offset += width;
788
+ values.push(best);
789
+ if (values.length > 256)
790
+ return null; // runaway scan
791
+ }
792
+ // Start symbol, data, checksum, stop. Anything shorter is not a symbol.
793
+ if (values.length < 2)
794
+ return null;
795
+ const checksum = values[values.length - 1];
796
+ const dataValues = values.slice(0, -1);
797
+ // Verify the checksum rather than trusting the scan. Without this a run of
798
+ // noise that happens to match valid patterns decodes to plausible garbage,
799
+ // which is far worse than reporting nothing.
800
+ let sum = start.code;
801
+ for (let i = 0; i < dataValues.length; i++)
802
+ sum += dataValues[i] * (i + 1);
803
+ if (sum % 103 !== checksum)
804
+ return null;
805
+ let mode = start.code === CODE128_START_A ? 'A'
806
+ : start.code === CODE128_START_B ? 'B' : 'C';
807
+ let shifted = null;
808
+ let text = '';
809
+ const fnc1AtStart = dataValues[0] === CODE128_FNC1;
810
+ const fnc1Positions = [];
811
+ for (let dataIndex = 0; dataIndex < dataValues.length; dataIndex++) {
812
+ const value = dataValues[dataIndex];
813
+ const active = shifted || mode;
814
+ shifted = null;
815
+ if (value === CODE128_CODE_A && mode !== 'A') {
816
+ mode = 'A';
817
+ continue;
818
+ }
819
+ if (value === CODE128_CODE_B && mode !== 'B') {
820
+ mode = 'B';
821
+ continue;
822
+ }
823
+ if (value === CODE128_CODE_C) {
824
+ mode = 'C';
825
+ continue;
826
+ }
827
+ if (value === CODE128_SHIFT) {
828
+ shifted = mode === 'A' ? 'B' : 'A';
829
+ continue;
830
+ }
831
+ if (value === CODE128_FNC1) {
832
+ if (dataIndex > 0) {
833
+ fnc1Positions.push(text.length);
834
+ text += '\x1d';
835
+ }
836
+ continue;
837
+ }
838
+ if (value >= 96 && value <= 102) {
839
+ continue;
840
+ } // other function characters
841
+ if (active === 'C') {
842
+ text += String(value).padStart(2, '0');
843
+ }
844
+ else if (active === 'A') {
845
+ text += value < 64 ? String.fromCharCode(value + 32) : String.fromCharCode(value - 64);
846
+ }
847
+ else {
848
+ text += String.fromCharCode(value + 32);
849
+ }
850
+ }
851
+ if (text.length === 0)
852
+ return null;
853
+ if (fnc1AtStart) {
854
+ return {
855
+ format: 'gs1128',
856
+ text,
857
+ gs1: true,
858
+ symbologyIdentifier: ']C1',
859
+ fnc1AtStart: true,
860
+ fnc1Positions,
861
+ };
862
+ }
863
+ return { format: 'code128', text };
864
+ }
865
+ /* ------------------------------------------------------------------ *
866
+ * Code 39
867
+ * ------------------------------------------------------------------ */
868
+ /**
869
+ * @param {Uint8Array} row
870
+ * @param {object} options
871
+ * @returns {{format: string, text: string} | null}
872
+ */
873
+ function decodeCode39(row, options = {}) {
874
+ const counters = new Array(9).fill(0);
875
+ let offset = 0;
876
+ // Find the '*' start character.
877
+ const startBits = nwToBits(CODE39['*']);
878
+ let found = false;
879
+ while (offset < row.length) {
880
+ while (offset < row.length && row[offset] !== 1)
881
+ offset++;
882
+ if (offset >= row.length)
883
+ break;
884
+ if (recordPattern(row, offset, counters)) {
885
+ if (toNarrowWidePattern(counters, 3) === startBits) {
886
+ found = true;
887
+ break;
888
+ }
889
+ }
890
+ // Advance past this dark run and the following light run.
891
+ while (offset < row.length && row[offset] === 1)
892
+ offset++;
893
+ while (offset < row.length && row[offset] === 0)
894
+ offset++;
895
+ }
896
+ if (!found)
897
+ return null;
898
+ let width = 0;
899
+ for (const c of counters)
900
+ width += c;
901
+ offset += width;
902
+ let text = '';
903
+ for (;;) {
904
+ // Skip the inter-character gap.
905
+ while (offset < row.length && row[offset] === 0)
906
+ offset++;
907
+ if (offset >= row.length)
908
+ return null;
909
+ if (!recordPattern(row, offset, counters))
910
+ return null;
911
+ const bits = toNarrowWidePattern(counters, 3);
912
+ const ch = CODE39_BITS[bits];
913
+ if (ch === undefined)
914
+ return null;
915
+ let w = 0;
916
+ for (const c of counters)
917
+ w += c;
918
+ offset += w;
919
+ if (ch === '*')
920
+ break;
921
+ text += ch;
922
+ if (text.length > 80)
923
+ return null;
924
+ }
925
+ if (text.length === 0)
926
+ return null;
927
+ if (options.checkDigit) {
928
+ const expected = text[text.length - 1];
929
+ const body = text.slice(0, -1);
930
+ let sum = 0;
931
+ for (const ch of body)
932
+ sum += CODE39_CHECK_SET.indexOf(ch);
933
+ if (CODE39_CHECK_SET[sum % 43] !== expected)
934
+ return null;
935
+ text = body;
936
+ }
937
+ return { format: 'code39', text };
938
+ }
939
+ /* ------------------------------------------------------------------ *
940
+ * Code 93
941
+ * ------------------------------------------------------------------ */
942
+ /**
943
+ * @param {Uint8Array} row
944
+ * @returns {{format: string, text: string} | null}
945
+ */
946
+ function decodeCode93(row) {
947
+ const startRuns = widthsToArray('111141');
948
+ const start = findGuard(row, 0, startRuns, false);
949
+ if (!start)
950
+ return null;
951
+ let offset = start.end;
952
+ const counters = new Array(6).fill(0);
953
+ const values = [];
954
+ for (;;) {
955
+ if (!recordPattern(row, offset, counters))
956
+ return null;
957
+ let best = -1;
958
+ let bestVariance = 0.38;
959
+ for (let v = 0; v < CODE93_VALUES.length; v++) {
960
+ const runs = CODE93_RUNS[CODE93_VALUES[v]];
961
+ const variance = patternVariance(counters, runs, 0.7);
962
+ if (variance < bestVariance) {
963
+ bestVariance = variance;
964
+ best = v;
965
+ }
966
+ }
967
+ const stopVariance = patternVariance(counters, startRuns, 0.7);
968
+ if (stopVariance < bestVariance)
969
+ break;
970
+ if (best < 0)
971
+ return null;
972
+ values.push(best);
973
+ let w = 0;
974
+ for (const c of counters)
975
+ w += c;
976
+ offset += w;
977
+ if (values.length > 90)
978
+ return null;
979
+ }
980
+ if (values.length < 3)
981
+ return null;
982
+ // Verify both check characters before trusting anything.
983
+ const weighted = (data, maxWeight) => {
984
+ let sum = 0;
985
+ for (let i = 0; i < data.length; i++) {
986
+ const weight = ((data.length - 1 - i) % maxWeight) + 1;
987
+ sum += weight * data[i];
988
+ }
989
+ return sum % 47;
990
+ };
991
+ const k = values.pop();
992
+ const c = values.pop();
993
+ if (weighted(values, 20) !== c)
994
+ return null;
995
+ if (weighted([...values, c], 15) !== k)
996
+ return null;
997
+ let text = '';
998
+ for (const v of values) {
999
+ const key = CODE93_VALUES[v];
1000
+ if (key.length > 1)
1001
+ return null; // shift characters not expanded here
1002
+ text += key;
1003
+ }
1004
+ return { format: 'code93', text };
1005
+ }
1006
+ /* ------------------------------------------------------------------ *
1007
+ * ITF
1008
+ * ------------------------------------------------------------------ */
1009
+ /**
1010
+ * @param {Uint8Array} row
1011
+ * @returns {{format: string, text: string} | null}
1012
+ */
1013
+ function decodeITF(row) {
1014
+ const start = findGuard(row, 0, [1, 1, 1, 1], false);
1015
+ if (!start)
1016
+ return null;
1017
+ const startScale = (start.end - start.start) / 4;
1018
+ let offset = start.end;
1019
+ const digits = [];
1020
+ const barCounters = new Array(5).fill(0);
1021
+ const spaceCounters = new Array(5).fill(0);
1022
+ const pair = new Array(10).fill(0);
1023
+ for (;;) {
1024
+ if (!recordPattern(row, offset, pair))
1025
+ break;
1026
+ // De-interleave: even indices are bars, odd are spaces.
1027
+ for (let k = 0; k < 5; k++) {
1028
+ barCounters[k] = pair[k * 2];
1029
+ spaceCounters[k] = pair[k * 2 + 1];
1030
+ }
1031
+ const barBits = toNarrowWidePattern(barCounters, 2);
1032
+ const spaceBits = toNarrowWidePattern(spaceCounters, 2);
1033
+ const a = ITF_BITS[barBits];
1034
+ const b = ITF_BITS[spaceBits];
1035
+ if (a === undefined || b === undefined)
1036
+ break;
1037
+ digits.push(a, b);
1038
+ let w = 0;
1039
+ for (const c of pair)
1040
+ w += c;
1041
+ offset += w;
1042
+ if (digits.length > 40)
1043
+ break;
1044
+ }
1045
+ // ITF carries no mandatory checksum, so structure is the only defence
1046
+ // against a false positive — and without these two checks there is none.
1047
+ //
1048
+ // The loop above stops as soon as a pair fails to match, which happens both
1049
+ // at the genuine end of a symbol and in the middle of unrelated bars. Two
1050
+ // digits scraped out of a Code 39 or UPC-A symbol matched the digit patterns
1051
+ // often enough to be reported as a real ITF read, so `decode()` returned a
1052
+ // phantom result alongside the true one.
1053
+ if (digits.length < MIN_ITF_DIGITS)
1054
+ return null;
1055
+ // Require the run to end on the actual stop pattern: wide bar, narrow space,
1056
+ // narrow bar. A fragment that merely ran out of matching pairs has no stop
1057
+ // pattern after it and is rejected here.
1058
+ const stop = new Array(3).fill(0);
1059
+ if (!recordPattern(row, offset, stop))
1060
+ return null;
1061
+ if (toNarrowWidePattern(stop, 1) !== 0b100)
1062
+ return null;
1063
+ const stopEnd = offset + counterTotal(stop);
1064
+ let nextDark = stopEnd;
1065
+ while (nextDark < row.length && row[nextDark] === 0)
1066
+ nextDark++;
1067
+ if (nextDark !== row.length && nextDark - stopEnd < Math.max(3, Math.ceil(startScale * 3)))
1068
+ return null;
1069
+ return { format: 'itf', text: digits.join('') };
1070
+ }
1071
+ /* ------------------------------------------------------------------ *
1072
+ * Codabar
1073
+ * ------------------------------------------------------------------ */
1074
+ /**
1075
+ * @param {Uint8Array} row
1076
+ * @returns {{format: string, text: string} | null}
1077
+ */
1078
+ function decodeCodabar(row) {
1079
+ const counters = new Array(7).fill(0);
1080
+ let offset = 0;
1081
+ let startChar = null;
1082
+ while (offset < row.length) {
1083
+ while (offset < row.length && row[offset] !== 1)
1084
+ offset++;
1085
+ if (offset >= row.length)
1086
+ break;
1087
+ if (recordPattern(row, offset, counters)) {
1088
+ const bits = toNarrowWidePattern(counters, 3) >= 0
1089
+ ? toNarrowWidePattern(counters, 3)
1090
+ : toNarrowWidePattern(counters, 2);
1091
+ const ch = CODABAR_BITS[bits];
1092
+ if (ch && CODABAR_START_STOP.includes(ch)) {
1093
+ startChar = ch;
1094
+ break;
1095
+ }
1096
+ }
1097
+ while (offset < row.length && row[offset] === 1)
1098
+ offset++;
1099
+ while (offset < row.length && row[offset] === 0)
1100
+ offset++;
1101
+ }
1102
+ if (!startChar)
1103
+ return null;
1104
+ let w = 0;
1105
+ for (const c of counters)
1106
+ w += c;
1107
+ offset += w;
1108
+ let text = '';
1109
+ for (;;) {
1110
+ while (offset < row.length && row[offset] === 0)
1111
+ offset++;
1112
+ if (offset >= row.length)
1113
+ return null;
1114
+ if (!recordPattern(row, offset, counters))
1115
+ return null;
1116
+ let bits = toNarrowWidePattern(counters, 3);
1117
+ let ch = CODABAR_BITS[bits];
1118
+ if (ch === undefined) {
1119
+ bits = toNarrowWidePattern(counters, 2);
1120
+ ch = CODABAR_BITS[bits];
1121
+ }
1122
+ if (ch === undefined)
1123
+ return null;
1124
+ let width = 0;
1125
+ for (const c of counters)
1126
+ width += c;
1127
+ offset += width;
1128
+ if (CODABAR_START_STOP.includes(ch))
1129
+ break;
1130
+ text += ch;
1131
+ if (text.length > 60)
1132
+ return null;
1133
+ }
1134
+ if (text.length === 0)
1135
+ return null;
1136
+ return { format: 'codabar', text };
1137
+ }
1138
+ /* ------------------------------------------------------------------ *
1139
+ * Public entry point
1140
+ * ------------------------------------------------------------------ */
1141
+ /** Decoders in the order they are tried. */
1142
+ const DECODERS = [
1143
+ ['ean13', decodeEANFamily],
1144
+ ['upca', decodeEANFamily],
1145
+ ['ean8', decodeEAN8],
1146
+ ['upce', decodeUPCE],
1147
+ ['code128', decodeCode128],
1148
+ ['code11', decodeCode11],
1149
+ ['msi', decodeMSI],
1150
+ ['gs1databar14', decodeDataBar14Scanline],
1151
+ ['code39', decodeCode39],
1152
+ ['code93', decodeCode93],
1153
+ ['itf', decodeITF],
1154
+ ['codabar', decodeCodabar],
1155
+ ];
1156
+ const EAN_PARENT_FORMATS = new Set(['ean13', 'ean8', 'upca', 'upce', 'isbn']);
1157
+ const EAN_SUPPLEMENT_FORMATS = new Set(['ean2', 'ean5']);
1158
+ /** @param {string} format @returns {boolean} */
1159
+ function isEANParentFormat(format) {
1160
+ return format === 'ean13' || format === 'ean8' || format === 'upca' || format === 'upce';
1161
+ }
1162
+ /**
1163
+ * An ISBN is printed as a Bookland EAN-13, so its decoded parent remains
1164
+ * `ean13` while an ISBN filter accepts only the Bookland prefixes.
1165
+ *
1166
+ * @param {{format:string, text:string}} result
1167
+ * @param {Set<string>} enabled
1168
+ * @returns {boolean}
1169
+ */
1170
+ function isRequestedEANParent(result, enabled) {
1171
+ if (enabled.has(result.format))
1172
+ return true;
1173
+ return result.format === 'ean13' && enabled.has('isbn') && /^97[89]/.test(result.text);
1174
+ }
1175
+ /** @param {object} result @returns {object} */
1176
+ function withoutEANAddon(result) {
1177
+ const { addon, ...parent } = result;
1178
+ void addon;
1179
+ return parent;
1180
+ }
1181
+ /** @param {Uint8Array} row @returns {{x:number, width:number, quietZone:boolean}|null} */
1182
+ function cameraRowGeometry(row) {
1183
+ let first = 0;
1184
+ while (first < row.length && row[first] === 0)
1185
+ first++;
1186
+ if (first === row.length)
1187
+ return null;
1188
+ let last = row.length - 1;
1189
+ while (last >= 0 && row[last] === 0)
1190
+ last--;
1191
+ return {
1192
+ x: first,
1193
+ width: last - first + 1,
1194
+ quietZone: first >= 2 && row.length - 1 - last >= 2,
1195
+ };
1196
+ }
1197
+ /** @param {string} format @param {object} options @returns {boolean|null} */
1198
+ function checksumStatus(format, options) {
1199
+ if (format === 'ean13' || format === 'ean8' || format === 'upca' || format === 'upce' ||
1200
+ format === 'code93' || format === 'code128' || format === 'gs1128' ||
1201
+ format === 'gs1databar14')
1202
+ return true;
1203
+ if (format === 'code11' || format === 'msi' || format === 'code39') {
1204
+ return options.profile === 'camera' || options.checkDigit === true ? true : null;
1205
+ }
1206
+ return null;
1207
+ }
1208
+ /** @param {object} result @param {object} geometry @param {Set<number>} rows @param {object} options @returns {object} */
1209
+ function cameraMetadata(result, geometry, rows, options) {
1210
+ const checksum = checksumStatus(result.format, options);
1211
+ const consistency = Math.min(1, rows.size / 3);
1212
+ const confidence = Math.min(1, 0.4 + (geometry.quietZone ? 0.2 : 0) +
1213
+ (checksum === true ? 0.2 : 0) + consistency * 0.2);
1214
+ return {
1215
+ ...result,
1216
+ confidence,
1217
+ bounds: {
1218
+ x: geometry.x,
1219
+ y: Math.min(...rows),
1220
+ width: geometry.width,
1221
+ height: Math.max(...rows) - Math.min(...rows) + 1,
1222
+ },
1223
+ rotation: options.cameraRotation ?? 0,
1224
+ quality: { quietZone: geometry.quietZone, checksum, rows: rows.size, consistency },
1225
+ };
1226
+ }
1227
+ /**
1228
+ * Read every linear symbol found in a binarized image.
1229
+ *
1230
+ * @param {import('../core/bit-matrix.js').BitMatrix} image Binarized; set bit = dark.
1231
+ * @param {object} [options]
1232
+ * @param {string[]} [options.formats] Restrict to these format ids.
1233
+ * @param {number} [options.rows] How many horizontal slices to try.
1234
+ * @param {boolean} [options.tryHarder] Also scan reversed rows, for mirrored symbols.
1235
+ * @param {'camera'} [options.profile] Require stable, quiet-zone-qualified reads.
1236
+ * @param {0|90|180|270} [options.cameraRotation] Orientation already normalized by the caller.
1237
+ * @returns {Array<{format: string, text: string, row: number}>}
1238
+ */
1239
+ export function decodeOneD(image, options = {}) {
1240
+ const { formats = null, rows = 15, tryHarder = true, profile = null } = options;
1241
+ const cameraProfile = profile === 'camera';
1242
+ const enabled = formats ? new Set(formats) : null;
1243
+ const active = DECODERS.filter(([id]) => {
1244
+ if (!enabled)
1245
+ return true;
1246
+ if (enabled.has(id))
1247
+ return true;
1248
+ if (id === 'ean13' || id === 'ean8' || id === 'upca' || id === 'upce') {
1249
+ return enabled.has('ean2') || enabled.has('ean5') ||
1250
+ (id === 'ean13' && enabled.has('isbn'));
1251
+ }
1252
+ if (id === 'code128')
1253
+ return enabled.has('gs1128');
1254
+ if (id === 'gs1databar14')
1255
+ return enabled.has('databar') || enabled.has('gs1-databar14');
1256
+ return false;
1257
+ });
1258
+ if (active.length === 0)
1259
+ return [];
1260
+ const results = [];
1261
+ const seen = new Set();
1262
+ const height = image.height;
1263
+ const buffer = new Uint8Array(image.width);
1264
+ const cameraCandidates = new Map();
1265
+ // Sample rows from the middle outward: symbols are usually centred, and the
1266
+ // middle of a linear barcode is the part least likely to be clipped.
1267
+ const middle = height >> 1;
1268
+ const sampleRows = cameraProfile ? Math.min(height, Math.max(rows, 48)) : rows;
1269
+ const step = Math.max(1, Math.round(height / sampleRows));
1270
+ for (let attempt = 0; attempt < sampleRows; attempt++) {
1271
+ const delta = Math.ceil(attempt / 2) * step * (attempt % 2 === 0 ? 1 : -1);
1272
+ const y = middle + delta;
1273
+ if (y < 0 || y >= height)
1274
+ continue;
1275
+ const row = image.getRow(y, buffer);
1276
+ for (const pass of tryHarder ? [false, true] : [false]) {
1277
+ const scan = pass ? Uint8Array.from(row).reverse() : row;
1278
+ for (const [id, decoder] of active) {
1279
+ let result = null;
1280
+ try {
1281
+ // Code 11 and MSI checks are optional in their base standards, but
1282
+ // a camera frame cannot safely promote their short unchecked forms.
1283
+ const decoderOptions = cameraProfile && (id === 'code11' || id === 'msi')
1284
+ ? { ...options, checkDigit: true }
1285
+ : options;
1286
+ result = decoder(scan, decoderOptions);
1287
+ }
1288
+ catch {
1289
+ result = null; // a malformed candidate is not an error
1290
+ }
1291
+ if (!result)
1292
+ continue;
1293
+ if (enabled) {
1294
+ if (isEANParentFormat(result.format)) {
1295
+ const baseRequested = [...EAN_PARENT_FORMATS].some((format) => enabled.has(format));
1296
+ const parentRequested = isRequestedEANParent(result, enabled);
1297
+ if (baseRequested && !parentRequested) {
1298
+ continue;
1299
+ }
1300
+ if (!baseRequested) {
1301
+ // A supplement is never an independent barcode. When it is the
1302
+ // only requested format, return its validated EAN/UPC parent.
1303
+ if (!result.addon || !EAN_SUPPLEMENT_FORMATS.has(result.addon.format) ||
1304
+ !enabled.has(result.addon.format))
1305
+ continue;
1306
+ }
1307
+ else if (result.addon && !enabled.has(result.addon.format)) {
1308
+ // Supplements are optional whenever a requested parent exists.
1309
+ result = withoutEANAddon(result);
1310
+ }
1311
+ }
1312
+ else if (result.format === 'gs1128') {
1313
+ if (!enabled.has('gs1128') && !enabled.has('code128'))
1314
+ continue;
1315
+ }
1316
+ else if (result.format === 'gs1databar14') {
1317
+ if (!enabled.has('gs1databar14') && !enabled.has('databar') && !enabled.has('gs1-databar14'))
1318
+ continue;
1319
+ }
1320
+ else if (!enabled.has(result.format)) {
1321
+ continue;
1322
+ }
1323
+ }
1324
+ const addonKey = result.addon ? `:${result.addon.format}:${result.addon.text}` : '';
1325
+ const key = `${result.format}:${result.text}${addonKey}`;
1326
+ if (cameraProfile) {
1327
+ const geometry = cameraRowGeometry(row);
1328
+ // Do not promote partial row fragments from a camera frame.
1329
+ if (!geometry || !geometry.quietZone)
1330
+ continue;
1331
+ const candidate = cameraCandidates.get(key) ?? {
1332
+ result,
1333
+ geometry,
1334
+ rows: new Set(),
1335
+ rotation: ((options.cameraRotation ?? 0) + (pass ? 180 : 0)) % 360,
1336
+ };
1337
+ candidate.rows.add(y);
1338
+ cameraCandidates.set(key, candidate);
1339
+ continue;
1340
+ }
1341
+ if (seen.has(key))
1342
+ continue;
1343
+ seen.add(key);
1344
+ results.push({ ...result, row: y });
1345
+ void id;
1346
+ }
1347
+ }
1348
+ }
1349
+ if (cameraProfile) {
1350
+ for (const candidate of cameraCandidates.values()) {
1351
+ // A complete symbol must survive at least two nearby scan samples. This
1352
+ // rejects isolated run coincidences without imposing a payload length.
1353
+ if (candidate.rows.size < 2)
1354
+ continue;
1355
+ results.push(cameraMetadata(candidate.result, candidate.geometry, candidate.rows, {
1356
+ ...options,
1357
+ cameraRotation: candidate.rotation,
1358
+ }));
1359
+ }
1360
+ }
1361
+ // A valid EAN/UPC parent is substantially more constrained than a generic
1362
+ // narrow/wide candidate. Suppress competing interpretations of the same
1363
+ // scanline, while retaining symbols detected on other rows.
1364
+ const eanRows = new Set(results
1365
+ .filter((result) => isEANParentFormat(result.format))
1366
+ .map((result) => result.row));
1367
+ return eanRows.size === 0
1368
+ ? results
1369
+ : results.filter((result) => !eanRows.has(result.row) || isEANParentFormat(result.format));
1370
+ }
1371
+ /**
1372
+ * Convenience wrapper that throws when nothing is found.
1373
+ *
1374
+ * @param {import('../core/bit-matrix.js').BitMatrix} image
1375
+ * @param {object} [options]
1376
+ * @returns {{format: string, text: string, row: number}}
1377
+ */
1378
+ export function decodeOneDStrict(image, options) {
1379
+ const results = decodeOneD(image, options);
1380
+ if (results.length === 0)
1381
+ throw new NotFoundError('No linear barcode found');
1382
+ return results[0];
1383
+ }