@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,117 @@
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
+ * SVG output.
32
+ *
33
+ * Dark modules are emitted as a single `<path>` with horizontal runs merged,
34
+ * not as one `<rect>` per module. A version 40 QR symbol has 31329 modules; the
35
+ * naive rendering is a megabyte of XML that browsers choke on, while the merged
36
+ * path is a few kilobytes and draws identically.
37
+ *
38
+ * @module render/svg
39
+ */
40
+ import { normalizeOptions } from './options.js';
41
+ /**
42
+ * @param {string} value
43
+ * @returns {string}
44
+ */
45
+ function escapeAttr(value) {
46
+ return String(value)
47
+ .replace(/&/g, '&amp;')
48
+ .replace(/</g, '&lt;')
49
+ .replace(/>/g, '&gt;')
50
+ .replace(/"/g, '&quot;');
51
+ }
52
+ /**
53
+ * Render to an SVG document.
54
+ *
55
+ * @param {import('../core/bit-matrix.js').BitMatrix} matrix
56
+ * @param {import('./options.js').RenderOptions} [options]
57
+ * @returns {string}
58
+ */
59
+ export function toSVG(matrix, options = {}) {
60
+ const opts = normalizeOptions(matrix, options);
61
+ const { scale, source, pixelWidth, pixelHeight, rowHeight } = opts;
62
+ let path = '';
63
+ for (let y = 0; y < source.height; y++) {
64
+ let x = 0;
65
+ while (x < source.width) {
66
+ if (!source.get(x, y)) {
67
+ x++;
68
+ continue;
69
+ }
70
+ let run = 1;
71
+ while (x + run < source.width && source.get(x + run, y))
72
+ run++;
73
+ // Relative horizontal-vertical path commands: shorter than rects and
74
+ // free of the seams that appear between adjacent rects at some zooms.
75
+ path += `M${x * scale} ${y * rowHeight}h${run * scale}v${rowHeight}h${-run * scale}z`;
76
+ x += run;
77
+ }
78
+ }
79
+ const bg = opts.light === 'none'
80
+ ? ''
81
+ : `<rect width="${pixelWidth}" height="${pixelHeight}" fill="${escapeAttr(opts.light)}"/>`;
82
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${pixelWidth}" height="${pixelHeight}" ` +
83
+ `viewBox="0 0 ${pixelWidth} ${pixelHeight}" shape-rendering="crispEdges">` +
84
+ bg +
85
+ `<path d="${path}" fill="${escapeAttr(opts.dark)}"/>` +
86
+ '</svg>';
87
+ }
88
+ /**
89
+ * Base64 that works identically in Node and the browser.
90
+ *
91
+ * `btoa` is byte-oriented, so the UTF-8 encoding has to happen first — passing
92
+ * it a string with any character above U+00FF throws.
93
+ *
94
+ * @param {string} text
95
+ * @returns {string}
96
+ */
97
+ function toBase64(text) {
98
+ const bytes = new TextEncoder().encode(text);
99
+ let binary = '';
100
+ for (let i = 0; i < bytes.length; i++)
101
+ binary += String.fromCharCode(bytes[i]);
102
+ if (typeof btoa === 'function')
103
+ return btoa(binary);
104
+ // Node before btoa was global, and non-browser embedders.
105
+ /* eslint-disable-next-line no-undef */
106
+ return Buffer.from(bytes).toString('base64');
107
+ }
108
+ /**
109
+ * Render to a data URI usable directly as an `<img>` src.
110
+ *
111
+ * @param {import('../core/bit-matrix.js').BitMatrix} matrix
112
+ * @param {import('./options.js').RenderOptions} [options]
113
+ * @returns {string}
114
+ */
115
+ export function toSVGDataURI(matrix, options = {}) {
116
+ return 'data:image/svg+xml;base64,' + toBase64(toSVG(matrix, options));
117
+ }
@@ -0,0 +1,200 @@
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
+ * WebGL2 drawing.
32
+ *
33
+ * The matrix is uploaded as a one-byte-per-module R8 texture and drawn with a
34
+ * fragment shader sampling it at NEAREST. That keeps module edges perfectly
35
+ * sharp at any zoom, which is the property that matters — a barcode resampled
36
+ * with interpolation stops being a barcode.
37
+ *
38
+ * Every entry point is failure-tolerant: no context, a lost context, a driver
39
+ * that rejects the shader — all return false so the caller falls back to the
40
+ * 2D path rather than showing the user nothing.
41
+ *
42
+ * @module render/webgl
43
+ */
44
+ import { normalizeOptions, parseColor } from './options.js';
45
+ const VERTEX_SHADER = `#version 300 es
46
+ // A single oversized triangle covers the viewport with no vertex buffer:
47
+ // three gl_VertexID lookups, no attribute state to set up or leak.
48
+ out vec2 vUV;
49
+ void main() {
50
+ vec2 pos = vec2((gl_VertexID << 1) & 2, gl_VertexID & 2);
51
+ vUV = pos;
52
+ gl_Position = vec4(pos * 2.0 - 1.0, 0.0, 1.0);
53
+ }`;
54
+ const FRAGMENT_SHADER = `#version 300 es
55
+ precision highp float;
56
+ in vec2 vUV;
57
+ out vec4 fragColor;
58
+ uniform sampler2D uMatrix;
59
+ uniform vec4 uDark;
60
+ uniform vec4 uLight;
61
+ uniform vec2 uSize;
62
+ void main() {
63
+ // Flip Y: texture row 0 is the top of the symbol, but GL's origin is bottom.
64
+ vec2 uv = vec2(vUV.x, 1.0 - vUV.y);
65
+ // Sample at the centre of the module, never on a boundary, so rounding
66
+ // cannot pull a neighbouring module's value in at fractional scales.
67
+ vec2 texel = (floor(uv * uSize) + 0.5) / uSize;
68
+ float v = texture(uMatrix, texel).r;
69
+ fragColor = v > 0.5 ? uDark : uLight;
70
+ }`;
71
+ /**
72
+ * Is WebGL2 usable here?
73
+ *
74
+ * @returns {boolean}
75
+ */
76
+ export function isWebGL2Available() {
77
+ try {
78
+ if (typeof document === 'undefined')
79
+ return false;
80
+ if (typeof WebGL2RenderingContext === 'undefined')
81
+ return false;
82
+ const probe = document.createElement('canvas');
83
+ const gl = probe.getContext('webgl2');
84
+ if (!gl)
85
+ return false;
86
+ const lose = gl.getExtension('WEBGL_lose_context');
87
+ if (lose)
88
+ lose.loseContext();
89
+ return true;
90
+ }
91
+ catch {
92
+ return false;
93
+ }
94
+ }
95
+ /**
96
+ * @param {WebGL2RenderingContext} gl
97
+ * @param {number} type
98
+ * @param {string} source
99
+ * @returns {WebGLShader | null}
100
+ */
101
+ function compile(gl, type, source) {
102
+ const shader = gl.createShader(type);
103
+ if (!shader)
104
+ return null;
105
+ gl.shaderSource(shader, source);
106
+ gl.compileShader(shader);
107
+ if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
108
+ gl.deleteShader(shader);
109
+ return null;
110
+ }
111
+ return shader;
112
+ }
113
+ /**
114
+ * Draw a matrix into a canvas with WebGL2.
115
+ *
116
+ * @param {import('../core/bit-matrix.js').BitMatrix} matrix
117
+ * @param {HTMLCanvasElement | OffscreenCanvas} canvas
118
+ * @param {import('./options.js').RenderOptions} [options]
119
+ * @returns {boolean} True if it drew; false means the caller should fall back.
120
+ */
121
+ export function renderToCanvasWebGL(matrix, canvas, options = {}) {
122
+ let gl = null;
123
+ let program = null;
124
+ let vs = null;
125
+ let fs = null;
126
+ let texture = null;
127
+ let vao = null;
128
+ try {
129
+ const opts = normalizeOptions(matrix, options);
130
+ const { source, pixelWidth, pixelHeight } = opts;
131
+ gl = canvas.getContext('webgl2', { antialias: false, premultipliedAlpha: false });
132
+ if (!gl)
133
+ return false;
134
+ canvas.width = pixelWidth;
135
+ canvas.height = pixelHeight;
136
+ vs = compile(gl, gl.VERTEX_SHADER, VERTEX_SHADER);
137
+ fs = compile(gl, gl.FRAGMENT_SHADER, FRAGMENT_SHADER);
138
+ if (!vs || !fs)
139
+ return false;
140
+ program = gl.createProgram();
141
+ gl.attachShader(program, vs);
142
+ gl.attachShader(program, fs);
143
+ gl.linkProgram(program);
144
+ if (!gl.getProgramParameter(program, gl.LINK_STATUS))
145
+ return false;
146
+ gl.useProgram(program);
147
+ // One byte per module. R8 is the narrowest single-channel format WebGL2
148
+ // guarantees as colour-renderable and filterable.
149
+ const pixels = new Uint8Array(source.width * source.height);
150
+ for (let y = 0; y < source.height; y++) {
151
+ for (let x = 0; x < source.width; x++) {
152
+ pixels[y * source.width + x] = source.get(x, y) ? 255 : 0;
153
+ }
154
+ }
155
+ texture = gl.createTexture();
156
+ gl.bindTexture(gl.TEXTURE_2D, texture);
157
+ gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
158
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.R8, source.width, source.height, 0, gl.RED, gl.UNSIGNED_BYTE, pixels);
159
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
160
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
161
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
162
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
163
+ const dark = parseColor(opts.dark).map((c) => c / 255);
164
+ const light = parseColor(opts.light).map((c) => c / 255);
165
+ gl.uniform1i(gl.getUniformLocation(program, 'uMatrix'), 0);
166
+ gl.uniform4f(gl.getUniformLocation(program, 'uDark'), dark[0], dark[1], dark[2], dark[3]);
167
+ gl.uniform4f(gl.getUniformLocation(program, 'uLight'), light[0], light[1], light[2], light[3]);
168
+ gl.uniform2f(gl.getUniformLocation(program, 'uSize'), source.width, source.height);
169
+ // WebGL2 still requires a bound VAO even when drawing without attributes.
170
+ vao = gl.createVertexArray();
171
+ gl.bindVertexArray(vao);
172
+ gl.viewport(0, 0, pixelWidth, pixelHeight);
173
+ gl.drawArrays(gl.TRIANGLES, 0, 3);
174
+ return true;
175
+ }
176
+ catch {
177
+ return false;
178
+ }
179
+ finally {
180
+ // Release eagerly: a page generating many barcodes would otherwise hold
181
+ // every texture until GC caught up, and GL memory is not GC's priority.
182
+ if (gl) {
183
+ try {
184
+ if (vao)
185
+ gl.deleteVertexArray(vao);
186
+ if (texture)
187
+ gl.deleteTexture(texture);
188
+ if (program)
189
+ gl.deleteProgram(program);
190
+ if (vs)
191
+ gl.deleteShader(vs);
192
+ if (fs)
193
+ gl.deleteShader(fs);
194
+ }
195
+ catch {
196
+ /* context already gone */
197
+ }
198
+ }
199
+ }
200
+ }
@@ -0,0 +1,357 @@
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
+ * WebGPU drawing.
32
+ *
33
+ * The same idea as the WebGL2 backend, expressed in WGSL: the matrix is
34
+ * uploaded as a one-byte-per-module `r8unorm` texture and sampled with NEAREST
35
+ * filtering, so module edges stay perfectly sharp at any size. A barcode
36
+ * resampled with interpolation stops being a barcode, which is why the filter
37
+ * choice is not a detail.
38
+ *
39
+ * Every entry point is failure-tolerant: no `navigator.gpu`, no adapter, a
40
+ * device that refuses the shader, a lost device — all return false (or a
41
+ * resolved false) so the caller falls back rather than showing nothing. The
42
+ * functions here are async only because WebGPU's own setup is; nothing about
43
+ * the drawing needs to be.
44
+ *
45
+ * @module render/webgpu
46
+ */
47
+ import { normalizeOptions, parseColor } from './options.js';
48
+ /**
49
+ * Vertex and fragment stages in one module, mirroring the WebGL2 pair.
50
+ *
51
+ * The vertex stage builds one oversized triangle from `vertex_index` alone:
52
+ * three positions, no vertex buffer to allocate, bind or release.
53
+ */
54
+ const SHADER = `
55
+ struct VertexOutput {
56
+ @builtin(position) position : vec4<f32>,
57
+ @location(0) uv : vec2<f32>,
58
+ };
59
+
60
+ @vertex
61
+ fn vertexMain(@builtin(vertex_index) vertexIndex : u32) -> VertexOutput {
62
+ let x = f32((vertexIndex << 1u) & 2u);
63
+ let y = f32(vertexIndex & 2u);
64
+ var result : VertexOutput;
65
+ result.uv = vec2<f32>(x, y);
66
+ result.position = vec4<f32>(x * 2.0 - 1.0, y * 2.0 - 1.0, 0.0, 1.0);
67
+ return result;
68
+ }
69
+
70
+ struct Style {
71
+ dark : vec4<f32>,
72
+ light : vec4<f32>,
73
+ size : vec2<f32>,
74
+ };
75
+
76
+ @group(0) @binding(0) var moduleTexture : texture_2d<f32>;
77
+ @group(0) @binding(1) var moduleSampler : sampler;
78
+ @group(0) @binding(2) var<uniform> style : Style;
79
+
80
+ @fragment
81
+ fn fragmentMain(@location(0) quadUV : vec2<f32>) -> @location(0) vec4<f32> {
82
+ // Flip Y: texture row 0 is the top of the symbol, but clip space puts +Y at
83
+ // the top, so the interpolated quad coordinate runs the other way.
84
+ let uv = vec2<f32>(quadUV.x, 1.0 - quadUV.y);
85
+ // Sample at the centre of the module, never on a boundary, so rounding
86
+ // cannot pull a neighbouring module's value in at fractional scales.
87
+ let texel = (floor(uv * style.size) + vec2<f32>(0.5, 0.5)) / style.size;
88
+ let v = textureSample(moduleTexture, moduleSampler, texel).r;
89
+ return select(style.light, style.dark, v > 0.5);
90
+ }
91
+ `;
92
+ /**
93
+ * The adapter and device are per-page, not per-barcode.
94
+ *
95
+ * Unlike a WebGL context — which the canvas owns, and which dies with it —
96
+ * a `GPUDevice` is independent of any canvas, and requesting one is slow. A
97
+ * page drawing a table of barcodes would otherwise pay for a full adapter
98
+ * negotiation per symbol, and leak a device per symbol on top, because the
99
+ * device cannot be destroyed while the canvas it configured is still on screen.
100
+ *
101
+ * @type {Promise<any> | null}
102
+ */
103
+ let sharedDevice = null;
104
+ /**
105
+ * Get the shared device, requesting one on first use.
106
+ *
107
+ * Never rejects: an unusable platform resolves to null.
108
+ *
109
+ * @returns {Promise<any>} The device, or null.
110
+ */
111
+ function acquireDevice() {
112
+ if (sharedDevice)
113
+ return sharedDevice;
114
+ // Hoisted so the `lost` handler below can compare against this exact
115
+ // promise, and never clear a newer one that has replaced it.
116
+ function forget() {
117
+ if (sharedDevice === pending)
118
+ sharedDevice = null;
119
+ }
120
+ const pending = (async () => {
121
+ try {
122
+ if (typeof navigator === 'undefined' || !navigator.gpu)
123
+ return null;
124
+ const adapter = await navigator.gpu.requestAdapter();
125
+ if (!adapter) {
126
+ forget();
127
+ return null;
128
+ }
129
+ const device = await adapter.requestDevice();
130
+ if (!device) {
131
+ forget();
132
+ return null;
133
+ }
134
+ // A lost device can never be revived, so drop it and let the next
135
+ // render ask for a fresh one instead of failing forever.
136
+ if (device.lost && typeof device.lost.then === 'function') {
137
+ device.lost.then(forget, forget);
138
+ }
139
+ return device;
140
+ }
141
+ catch {
142
+ forget();
143
+ return null;
144
+ }
145
+ })();
146
+ sharedDevice = pending;
147
+ return pending;
148
+ }
149
+ /**
150
+ * Is WebGPU usable here?
151
+ *
152
+ * Resolves false rather than throwing on every unsupported path, including
153
+ * Node, where there is no `navigator.gpu` at all.
154
+ *
155
+ * @returns {Promise<boolean>}
156
+ */
157
+ export async function isWebGPUAvailable() {
158
+ try {
159
+ if (typeof navigator === 'undefined')
160
+ return false;
161
+ if (!navigator.gpu || typeof navigator.gpu.requestAdapter !== 'function')
162
+ return false;
163
+ // An adapter is the real test: `navigator.gpu` exists on machines whose
164
+ // GPU is blocklisted, where every request still comes back null.
165
+ const adapter = await navigator.gpu.requestAdapter();
166
+ return Boolean(adapter);
167
+ }
168
+ catch {
169
+ return false;
170
+ }
171
+ }
172
+ /**
173
+ * Did the shader compile?
174
+ *
175
+ * The WGSL analogue of checking `COMPILE_STATUS` in WebGL. Diagnostics are
176
+ * optional in practice, so an implementation that cannot report them is given
177
+ * the benefit of the doubt and the pipeline decides instead.
178
+ *
179
+ * @param {any} module
180
+ * @returns {Promise<boolean>}
181
+ */
182
+ async function compiles(module) {
183
+ try {
184
+ const query = module.getCompilationInfo ?? module.compilationInfo;
185
+ if (typeof query !== 'function')
186
+ return true;
187
+ const info = await query.call(module);
188
+ if (!info || !info.messages)
189
+ return true;
190
+ for (let i = 0; i < info.messages.length; i++) {
191
+ if (info.messages[i].type === 'error')
192
+ return false;
193
+ }
194
+ return true;
195
+ }
196
+ catch {
197
+ return true;
198
+ }
199
+ }
200
+ /**
201
+ * Draw a matrix into a canvas with WebGPU.
202
+ *
203
+ * @param {import('../core/bit-matrix.js').BitMatrix} matrix
204
+ * @param {HTMLCanvasElement | OffscreenCanvas} canvas
205
+ * @param {import('./options.js').RenderOptions} [options]
206
+ * @returns {Promise<boolean>} True if it drew; false means the caller should
207
+ * fall back. Note that a canvas whose context has already been taken for
208
+ * WebGPU cannot then be handed to WebGL2 or 2D, so callers should probe
209
+ * availability before committing a canvas to this path.
210
+ */
211
+ export async function renderToCanvasWebGPU(matrix, canvas, options = {}) {
212
+ let device = null;
213
+ let texture = null;
214
+ let uniforms = null;
215
+ try {
216
+ if (typeof navigator === 'undefined' || !navigator.gpu)
217
+ return false;
218
+ const opts = normalizeOptions(matrix, options);
219
+ const { source, pixelWidth, pixelHeight } = opts;
220
+ device = await acquireDevice();
221
+ if (!device)
222
+ return false;
223
+ // Oversized symbols are a legitimate failure, not a crash: say so and let
224
+ // the caller fall back to a path with no texture ceiling.
225
+ // 8192 is the floor the specification guarantees, so it is the right
226
+ // assumption when an implementation does not report its limits.
227
+ const maxDimension = (device.limits && device.limits.maxTextureDimension2D) || 8192;
228
+ if (source.width > maxDimension || source.height > maxDimension)
229
+ return false;
230
+ const context = canvas.getContext('webgpu');
231
+ if (!context)
232
+ return false;
233
+ canvas.width = pixelWidth;
234
+ canvas.height = pixelHeight;
235
+ const format = navigator.gpu.getPreferredCanvasFormat();
236
+ context.configure({ device, format, alphaMode: 'premultiplied' });
237
+ // One byte per module. r8unorm is the narrowest format every WebGPU
238
+ // implementation is required to support as a sampled texture.
239
+ const pixels = new Uint8Array(source.width * source.height);
240
+ for (let y = 0; y < source.height; y++) {
241
+ for (let x = 0; x < source.width; x++) {
242
+ pixels[y * source.width + x] = source.get(x, y) ? 255 : 0;
243
+ }
244
+ }
245
+ texture = device.createTexture({
246
+ size: { width: source.width, height: source.height, depthOrArrayLayers: 1 },
247
+ format: 'r8unorm',
248
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST,
249
+ });
250
+ // `bytesPerRow` needs no 256-byte alignment here — that rule belongs to
251
+ // buffer-to-texture copies, not to writeTexture's linear source data.
252
+ device.queue.writeTexture({ texture }, pixels, { offset: 0, bytesPerRow: source.width, rowsPerImage: source.height }, { width: source.width, height: source.height, depthOrArrayLayers: 1 });
253
+ const sampler = device.createSampler({
254
+ magFilter: 'nearest',
255
+ minFilter: 'nearest',
256
+ addressModeU: 'clamp-to-edge',
257
+ addressModeV: 'clamp-to-edge',
258
+ });
259
+ // The canvas is configured as premultiplied, so the colours have to be
260
+ // too — otherwise a translucent `light` would come out too bright and a
261
+ // fully transparent one would tint the page behind it.
262
+ const dark = parseColor(opts.dark);
263
+ const light = parseColor(opts.light);
264
+ const premultiplied = (c) => {
265
+ const a = c[3] / 255;
266
+ return [(c[0] / 255) * a, (c[1] / 255) * a, (c[2] / 255) * a, a];
267
+ };
268
+ const darkF = premultiplied(dark);
269
+ const lightF = premultiplied(light);
270
+ // std140-style layout: two vec4 then a vec2, rounded up to the struct's
271
+ // 16-byte alignment. 48 bytes, of which the last 8 are padding.
272
+ const style = new Float32Array(12);
273
+ style.set(darkF, 0);
274
+ style.set(lightF, 4);
275
+ style[8] = source.width;
276
+ style[9] = source.height;
277
+ uniforms = device.createBuffer({
278
+ size: style.byteLength,
279
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
280
+ });
281
+ device.queue.writeBuffer(uniforms, 0, style);
282
+ const shader = device.createShaderModule({ code: SHADER });
283
+ if (!(await compiles(shader)))
284
+ return false;
285
+ // An error scope is the WGSL analogue of checking LINK_STATUS: a rejected
286
+ // pipeline is reported here instead of surfacing later as a lost device.
287
+ let pipeline = null;
288
+ if (typeof device.pushErrorScope === 'function') {
289
+ device.pushErrorScope('validation');
290
+ pipeline = device.createRenderPipeline({
291
+ layout: 'auto',
292
+ vertex: { module: shader, entryPoint: 'vertexMain' },
293
+ fragment: { module: shader, entryPoint: 'fragmentMain', targets: [{ format }] },
294
+ primitive: { topology: 'triangle-list' },
295
+ });
296
+ const failure = await device.popErrorScope();
297
+ if (failure)
298
+ return false;
299
+ }
300
+ else {
301
+ pipeline = device.createRenderPipeline({
302
+ layout: 'auto',
303
+ vertex: { module: shader, entryPoint: 'vertexMain' },
304
+ fragment: { module: shader, entryPoint: 'fragmentMain', targets: [{ format }] },
305
+ primitive: { topology: 'triangle-list' },
306
+ });
307
+ }
308
+ if (!pipeline)
309
+ return false;
310
+ const bindGroup = device.createBindGroup({
311
+ layout: pipeline.getBindGroupLayout(0),
312
+ entries: [
313
+ { binding: 0, resource: texture.createView() },
314
+ { binding: 1, resource: sampler },
315
+ { binding: 2, resource: { buffer: uniforms } },
316
+ ],
317
+ });
318
+ const encoder = device.createCommandEncoder();
319
+ const pass = encoder.beginRenderPass({
320
+ colorAttachments: [{
321
+ view: context.getCurrentTexture().createView(),
322
+ clearValue: { r: 0, g: 0, b: 0, a: 0 },
323
+ loadOp: 'clear',
324
+ storeOp: 'store',
325
+ }],
326
+ });
327
+ pass.setPipeline(pipeline);
328
+ pass.setBindGroup(0, bindGroup);
329
+ pass.draw(3);
330
+ pass.end();
331
+ device.queue.submit([encoder.finish()]);
332
+ // Wait for the draw before the `finally` below frees its inputs, so
333
+ // returning true genuinely means the pixels are there.
334
+ if (device.queue && typeof device.queue.onSubmittedWorkDone === 'function') {
335
+ await device.queue.onSubmittedWorkDone();
336
+ }
337
+ return true;
338
+ }
339
+ catch {
340
+ return false;
341
+ }
342
+ finally {
343
+ // Release eagerly: a page generating many barcodes would otherwise hold
344
+ // every texture until GC caught up, and GPU memory is not GC's priority.
345
+ // The device itself is deliberately kept — it is shared, and destroying it
346
+ // would blank every canvas already configured with it.
347
+ try {
348
+ if (texture && typeof texture.destroy === 'function')
349
+ texture.destroy();
350
+ if (uniforms && typeof uniforms.destroy === 'function')
351
+ uniforms.destroy();
352
+ }
353
+ catch {
354
+ /* device already gone */
355
+ }
356
+ }
357
+ }