@sythos/js_barcode_universal 1.5.6 → 1.5.8

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 (180) hide show
  1. package/NOTICE.md +1 -1
  2. package/README.md +55 -35
  3. package/bundle/sythos-barcode.esm.js +618 -465
  4. package/bundle/sythos-barcode.js +618 -465
  5. package/package.json +90 -24
  6. package/src/index.d.ts +363 -0
  7. package/src/index.js +286 -133
  8. package/src/{aztec → js/aztec}/decoder.js +0 -1
  9. package/src/{aztec → js/aztec}/detector.js +0 -1
  10. package/src/{aztec → js/aztec}/encoder.js +0 -1
  11. package/src/{aztec → js/aztec}/high-level.js +0 -1
  12. package/src/{aztec → js/aztec}/index.js +0 -1
  13. package/src/{aztec → js/aztec}/tables.js +0 -1
  14. package/src/{aztecrune → js/aztecrune}/decoder.js +0 -1
  15. package/src/{aztecrune → js/aztecrune}/detector.js +0 -1
  16. package/src/{aztecrune → js/aztecrune}/encoder.js +0 -1
  17. package/src/{aztecrune → js/aztecrune}/index.js +0 -1
  18. package/src/{aztecrune → js/aztecrune}/tables.js +0 -1
  19. package/src/{compactpdf417 → js/compactpdf417}/decoder.js +0 -1
  20. package/src/{compactpdf417 → js/compactpdf417}/detector.js +0 -1
  21. package/src/{compactpdf417 → js/compactpdf417}/encoder.js +0 -1
  22. package/src/{compactpdf417 → js/compactpdf417}/index.js +0 -5
  23. package/src/{compactpdf417 → js/compactpdf417}/tables.js +0 -1
  24. package/src/{core → js/core}/bit-buffer.js +0 -1
  25. package/src/{core → js/core}/bit-matrix.js +0 -1
  26. package/src/{core → js/core}/errors.js +0 -1
  27. package/src/{core → js/core}/galois-field.js +0 -1
  28. package/src/{core → js/core}/index.js +0 -1
  29. package/src/{core → js/core}/reed-solomon.js +0 -1
  30. package/src/{databar → js/databar}/codec.js +0 -1
  31. package/src/{databar → js/databar}/decoder.js +0 -1
  32. package/src/{databar → js/databar}/encoder.js +0 -1
  33. package/src/{databar → js/databar}/gs1.js +0 -1
  34. package/src/{databar → js/databar}/index.js +0 -1
  35. package/src/{databar → js/databar}/patterns.js +0 -1
  36. package/src/{databar → js/databar}/tables.js +0 -1
  37. package/src/{datamatrix → js/datamatrix}/decoder.js +0 -1
  38. package/src/{datamatrix → js/datamatrix}/detector.js +0 -1
  39. package/src/{datamatrix → js/datamatrix}/encoder.js +0 -1
  40. package/src/{datamatrix → js/datamatrix}/index.js +0 -1
  41. package/src/{datamatrix → js/datamatrix}/tables.js +0 -1
  42. package/src/{frameqr → js/frameqr}/decoder.js +15 -16
  43. package/src/{frameqr → js/frameqr}/detector.js +2 -3
  44. package/src/{frameqr → js/frameqr}/encoder.js +7 -8
  45. package/src/{frameqr → js/frameqr}/index.js +0 -1
  46. package/src/{frameqr → js/frameqr}/tables.js +10 -11
  47. package/src/{image → js/image}/binarizer.js +0 -1
  48. package/src/{image → js/image}/grid-sampler.js +0 -1
  49. package/src/js/image/index.js +40 -0
  50. package/src/{image → js/image}/luminance.js +0 -1
  51. package/src/{image → js/image}/perspective.js +0 -1
  52. package/src/{micropdf417 → js/micropdf417}/compaction.js +0 -1
  53. package/src/{micropdf417 → js/micropdf417}/decoder.js +0 -1
  54. package/src/{micropdf417 → js/micropdf417}/detector.js +0 -1
  55. package/src/{micropdf417 → js/micropdf417}/encoder.js +0 -1
  56. package/src/{micropdf417 → js/micropdf417}/error-correction.js +0 -1
  57. package/src/{micropdf417 → js/micropdf417}/index.js +0 -1
  58. package/src/{micropdf417 → js/micropdf417}/tables.js +0 -1
  59. package/src/{microqr → js/microqr}/decoder.js +0 -1
  60. package/src/{microqr → js/microqr}/detector.js +0 -1
  61. package/src/{microqr → js/microqr}/encoder.js +0 -1
  62. package/src/js/microqr/index.js +36 -0
  63. package/src/{microqr → js/microqr}/tables.js +0 -1
  64. package/src/{oned → js/oned}/addons.js +0 -1
  65. package/src/{oned → js/oned}/index.js +0 -1
  66. package/src/{oned → js/oned}/patterns.js +0 -1
  67. package/src/{oned → js/oned}/reader.js +0 -1
  68. package/src/{oned → js/oned}/writers.js +0 -1
  69. package/src/{pdf417 → js/pdf417}/compaction.js +0 -1
  70. package/src/{pdf417 → js/pdf417}/decoder.js +0 -1
  71. package/src/{pdf417 → js/pdf417}/detector.js +0 -1
  72. package/src/{pdf417 → js/pdf417}/encoder.js +0 -1
  73. package/src/{pdf417 → js/pdf417}/error-correction.js +0 -1
  74. package/src/js/pdf417/index.js +6 -0
  75. package/src/{pdf417 → js/pdf417}/tables.js +0 -1
  76. package/src/{qr → js/qr}/decoder.js +0 -1
  77. package/src/{qr → js/qr}/detector.js +0 -1
  78. package/src/{qr → js/qr}/encoder.js +0 -1
  79. package/src/js/qr/index.js +44 -0
  80. package/src/{qr → js/qr}/tables.js +0 -1
  81. package/src/{render → js/render}/image-data.js +0 -1
  82. package/src/{render → js/render}/index.js +0 -1
  83. package/src/{render → js/render}/options.js +0 -1
  84. package/src/{render → js/render}/png.js +0 -1
  85. package/src/{render → js/render}/svg.js +0 -1
  86. package/src/{render → js/render}/webgl.js +0 -1
  87. package/src/{render → js/render}/webgpu.js +0 -1
  88. package/src/{rmqr → js/rmqr}/decoder.js +0 -1
  89. package/src/{rmqr → js/rmqr}/detector.js +0 -1
  90. package/src/{rmqr → js/rmqr}/encoder.js +0 -1
  91. package/src/{rmqr → js/rmqr}/index.js +0 -1
  92. package/src/{rmqr → js/rmqr}/tables.js +0 -1
  93. package/src/ts/aztec/decoder.d.ts +57 -0
  94. package/src/ts/aztec/detector.d.ts +62 -0
  95. package/src/ts/aztec/encoder.d.ts +94 -0
  96. package/src/ts/aztec/high-level.d.ts +89 -0
  97. package/src/ts/aztec/index.d.ts +35 -0
  98. package/src/ts/aztec/tables.d.ts +144 -0
  99. package/src/ts/aztecrune/decoder.d.ts +56 -0
  100. package/src/ts/aztecrune/detector.d.ts +53 -0
  101. package/src/ts/aztecrune/encoder.d.ts +51 -0
  102. package/src/ts/aztecrune/index.d.ts +35 -0
  103. package/src/ts/aztecrune/tables.d.ts +75 -0
  104. package/src/ts/bundle/sythos-barcode.d.ts +31 -0
  105. package/src/ts/bundle/sythos-barcode.esm.d.ts +31 -0
  106. package/src/ts/compactpdf417/decoder.d.ts +64 -0
  107. package/src/ts/compactpdf417/detector.d.ts +48 -0
  108. package/src/ts/compactpdf417/encoder.d.ts +49 -0
  109. package/src/ts/compactpdf417/index.d.ts +8 -0
  110. package/src/ts/compactpdf417/tables.d.ts +84 -0
  111. package/src/ts/core/bit-buffer.d.ts +78 -0
  112. package/src/ts/core/bit-matrix.d.ts +129 -0
  113. package/src/ts/core/errors.d.ts +57 -0
  114. package/src/ts/core/galois-field.d.ts +141 -0
  115. package/src/ts/core/index.d.ts +40 -0
  116. package/src/ts/core/reed-solomon.d.ts +65 -0
  117. package/src/ts/databar/codec.d.ts +78 -0
  118. package/src/ts/databar/decoder.d.ts +55 -0
  119. package/src/ts/databar/encoder.d.ts +34 -0
  120. package/src/ts/databar/gs1.d.ts +48 -0
  121. package/src/ts/databar/index.d.ts +37 -0
  122. package/src/ts/databar/patterns.d.ts +46 -0
  123. package/src/ts/databar/tables.d.ts +119 -0
  124. package/src/ts/datamatrix/decoder.d.ts +57 -0
  125. package/src/ts/datamatrix/detector.d.ts +59 -0
  126. package/src/ts/datamatrix/encoder.d.ts +36 -0
  127. package/src/ts/datamatrix/index.d.ts +35 -0
  128. package/src/ts/datamatrix/tables.d.ts +88 -0
  129. package/src/ts/frameqr/decoder.d.ts +64 -0
  130. package/src/ts/frameqr/detector.d.ts +96 -0
  131. package/src/ts/frameqr/encoder.d.ts +73 -0
  132. package/src/ts/frameqr/index.d.ts +34 -0
  133. package/src/ts/frameqr/tables.d.ts +123 -0
  134. package/src/ts/image/binarizer.d.ts +85 -0
  135. package/src/ts/image/grid-sampler.d.ts +82 -0
  136. package/src/{image/index.js → ts/image/index.d.ts} +0 -2
  137. package/src/ts/image/luminance.d.ts +112 -0
  138. package/src/ts/image/perspective.d.ts +111 -0
  139. package/src/ts/index.d.ts +363 -0
  140. package/src/ts/micropdf417/compaction.d.ts +38 -0
  141. package/src/ts/micropdf417/decoder.d.ts +57 -0
  142. package/src/ts/micropdf417/detector.d.ts +95 -0
  143. package/src/ts/micropdf417/encoder.d.ts +34 -0
  144. package/src/ts/micropdf417/error-correction.d.ts +38 -0
  145. package/src/ts/micropdf417/index.d.ts +36 -0
  146. package/src/ts/micropdf417/tables.d.ts +73 -0
  147. package/src/ts/microqr/decoder.d.ts +49 -0
  148. package/src/ts/microqr/detector.d.ts +96 -0
  149. package/src/ts/microqr/encoder.d.ts +33 -0
  150. package/src/{microqr/index.js → ts/microqr/index.d.ts} +0 -2
  151. package/src/ts/microqr/tables.d.ts +107 -0
  152. package/src/ts/oned/addons.d.ts +181 -0
  153. package/src/ts/oned/index.d.ts +55 -0
  154. package/src/ts/oned/patterns.d.ts +259 -0
  155. package/src/ts/oned/reader.d.ts +129 -0
  156. package/src/ts/oned/writers.d.ts +218 -0
  157. package/src/ts/pdf417/compaction.d.ts +59 -0
  158. package/src/ts/pdf417/decoder.d.ts +48 -0
  159. package/src/ts/pdf417/detector.d.ts +73 -0
  160. package/src/ts/pdf417/encoder.d.ts +32 -0
  161. package/src/ts/pdf417/error-correction.d.ts +34 -0
  162. package/src/{pdf417/index.js → ts/pdf417/index.d.ts} +0 -1
  163. package/src/ts/pdf417/tables.d.ts +53 -0
  164. package/src/ts/qr/decoder.d.ts +79 -0
  165. package/src/ts/qr/detector.d.ts +93 -0
  166. package/src/ts/qr/encoder.d.ts +166 -0
  167. package/src/{qr/index.js → ts/qr/index.d.ts} +0 -2
  168. package/src/ts/qr/tables.d.ts +291 -0
  169. package/src/ts/render/image-data.d.ts +58 -0
  170. package/src/ts/render/index.d.ts +110 -0
  171. package/src/ts/render/options.d.ts +98 -0
  172. package/src/ts/render/png.d.ts +58 -0
  173. package/src/ts/render/svg.d.ts +46 -0
  174. package/src/ts/render/webgl.d.ts +45 -0
  175. package/src/ts/render/webgpu.d.ts +51 -0
  176. package/src/ts/rmqr/decoder.d.ts +42 -0
  177. package/src/ts/rmqr/detector.d.ts +81 -0
  178. package/src/ts/rmqr/encoder.d.ts +45 -0
  179. package/src/ts/rmqr/index.d.ts +34 -0
  180. package/src/ts/rmqr/tables.d.ts +89 -0
package/src/index.js CHANGED
@@ -46,55 +46,55 @@
46
46
  * @module @sythos/js_barcode_universal
47
47
  */
48
48
 
49
- import { BitMatrix } from './core/bit-matrix.js';
50
- import { EncodeError, NotFoundError } from './core/errors.js';
51
- import { LuminanceSource } from './image/luminance.js';
52
- import { binarize } from './image/binarizer.js';
53
- import { ONED_FORMATS } from './oned/index.js';
54
- import { decodeOneD } from './oned/reader.js';
55
- import * as datamatrix from './datamatrix/index.js';
56
- import * as qr from './qr/index.js';
57
- import * as aztec from './aztec/index.js';
58
- import * as pdf417 from './pdf417/index.js';
59
- import * as micropdf417 from './micropdf417/index.js';
60
- import * as microqr from './microqr/index.js';
61
- import * as rmqr from './rmqr/index.js';
62
- import * as frameqr from './frameqr/index.js';
63
- import * as aztecRune from './aztecrune/index.js';
64
- import * as compactPdf417 from './compactpdf417/index.js';
65
- import * as databar from './databar/index.js';
49
+ import { BitMatrix } from './js/core/bit-matrix.js';
50
+ import { EncodeError, NotFoundError } from './js/core/errors.js';
51
+ import { LuminanceSource } from './js/image/luminance.js';
52
+ import { binarize } from './js/image/binarizer.js';
53
+ import { ONED_FORMATS } from './js/oned/index.js';
54
+ import { decodeOneD } from './js/oned/reader.js';
55
+ import * as datamatrix from './js/datamatrix/index.js';
56
+ import * as qr from './js/qr/index.js';
57
+ import * as aztec from './js/aztec/index.js';
58
+ import * as pdf417 from './js/pdf417/index.js';
59
+ import * as micropdf417 from './js/micropdf417/index.js';
60
+ import * as microqr from './js/microqr/index.js';
61
+ import * as rmqr from './js/rmqr/index.js';
62
+ import * as frameqr from './js/frameqr/index.js';
63
+ import * as aztecRune from './js/aztecrune/index.js';
64
+ import * as compactPdf417 from './js/compactpdf417/index.js';
65
+ import * as databar from './js/databar/index.js';
66
66
 
67
67
  export { BitMatrix };
68
68
  export {
69
69
  BarcodeError, EncodeError, NotFoundError, FormatError, ChecksumError,
70
- } from './core/errors.js';
71
- export { LuminanceSource } from './image/luminance.js';
72
- export { binarize, binarizeGlobal, binarizeHybrid } from './image/binarizer.js';
73
- export * from './oned/index.js';
74
- export { toSVG, toSVGDataURI } from './render/svg.js';
75
- export { toImageData, toCanvas } from './render/image-data.js';
76
- export { toPNG, toPNGDataURI } from './render/png.js';
77
- export { renderToCanvasAuto, isWebGL2Available } from './render/index.js';
78
- export { renderToCanvasAutoAsync, isWebGPUAvailable } from './render/index.js';
79
- export { encodeQR, decodeQR, detectQR, detectAndDecodeQR } from './qr/index.js';
70
+ } from './js/core/errors.js';
71
+ export { LuminanceSource } from './js/image/luminance.js';
72
+ export { binarize, binarizeGlobal, binarizeHybrid } from './js/image/binarizer.js';
73
+ export * from './js/oned/index.js';
74
+ export { toSVG, toSVGDataURI } from './js/render/svg.js';
75
+ export { toImageData, toCanvas } from './js/render/image-data.js';
76
+ export { toPNG, toPNGDataURI } from './js/render/png.js';
77
+ export { renderToCanvasAuto, isWebGL2Available } from './js/render/index.js';
78
+ export { renderToCanvasAutoAsync, isWebGPUAvailable } from './js/render/index.js';
79
+ export { encodeQR, decodeQR, detectQR, detectAndDecodeQR } from './js/qr/index.js';
80
80
  export {
81
81
  encodeDataMatrix, decodeDataMatrix, detectDataMatrix, detectAndDecodeDataMatrix,
82
- } from './datamatrix/index.js';
83
- export { encodeAztec, decodeAztec, detectAztec, detectAndDecodeAztec } from './aztec/index.js';
84
- export * from './aztecrune/index.js';
85
- export { encodePDF417, decodePDF417, detectPDF417, detectAndDecodePDF417 } from './pdf417/index.js';
86
- export * from './compactpdf417/index.js';
82
+ } from './js/datamatrix/index.js';
83
+ export { encodeAztec, decodeAztec, detectAztec, detectAndDecodeAztec } from './js/aztec/index.js';
84
+ export * from './js/aztecrune/index.js';
85
+ export { encodePDF417, decodePDF417, detectPDF417, detectAndDecodePDF417 } from './js/pdf417/index.js';
86
+ export * from './js/compactpdf417/index.js';
87
87
  // DataBar exports include both the verified GTIN/data layer and the
88
88
  // Omnidirectional/Truncated physical image path.
89
- export * from './databar/index.js';
89
+ export * from './js/databar/index.js';
90
90
  export {
91
91
  encodeMicroPDF417, decodeMicroPDF417, detectMicroPDF417, detectAndDecodeMicroPDF417,
92
- } from './micropdf417/index.js';
93
- export { encodeMicroQR, decodeMicroQR, detectMicroQR, detectAndDecodeMicroQR } from './microqr/index.js';
94
- export { encodeRMQR, decodeRMQR, detectRMQR, detectAndDecodeRMQR } from './rmqr/index.js';
92
+ } from './js/micropdf417/index.js';
93
+ export { encodeMicroQR, decodeMicroQR, detectMicroQR, detectAndDecodeMicroQR } from './js/microqr/index.js';
94
+ export { encodeRMQR, decodeRMQR, detectRMQR, detectAndDecodeRMQR } from './js/rmqr/index.js';
95
95
  export {
96
96
  encodeFrameQR, decodeFrameQR, detectFrameQR, detectAndDecodeFrameQR,
97
- } from './frameqr/index.js';
97
+ } from './js/frameqr/index.js';
98
98
 
99
99
  /**
100
100
  * @typedef {object} FormatInfo
@@ -229,7 +229,7 @@ export function listFormats() {
229
229
  });
230
230
  formats.push({
231
231
  id: 'frameqr',
232
- label: 'FrameQR Code',
232
+ label: 'Sythos Canvas QR profile',
233
233
  canWrite: frameQrCanEncode,
234
234
  canRead: frameQrCanDecode,
235
235
  kind: /** @type {'2D'} */ ('2D'),
@@ -271,7 +271,7 @@ export function listFormats() {
271
271
  * @param {'auto'|'text'|'byte'|'numeric'} [options.compaction] PDF417 compaction mode.
272
272
  * @param {number} [options.eci] MicroPDF417 byte-compaction ECI assignment (3 or 26).
273
273
  * @param {number} [options.aspectRatio] Preferred MicroPDF417 symbol aspect ratio.
274
- * @param {object} [options.canvas] FrameQR Code artwork reservation.
274
+ * @param {object} [options.canvas] Sythos Canvas QR artwork reservation.
275
275
  * @param {'square'|'circle'|'diamond'} [options.canvas.shape] Canvas shape.
276
276
  * @param {number} [options.canvas.size] Odd canvas size in QR modules.
277
277
  * @param {number} [options.canvas.width] Canvas width in QR modules.
@@ -344,13 +344,13 @@ export function encode(text, options = {}) {
344
344
  * @property {number} [rowHeight] PDF417 row height in modules.
345
345
  * @property {number} [variant] MicroPDF417 predefined variant number.
346
346
  * @property {number} [eccCodewords] MicroPDF417 fixed error-correction codewords.
347
- * @property {string} [profile] FrameQR Code profile identifier.
347
+ * @property {string} [profile] Sythos Canvas QR profile identifier.
348
348
  * @property {boolean} [certified] Whether the profile is certified by its originator.
349
- * @property {object} [canvas] Canvas reservation metadata for the FrameQR Code profile.
349
+ * @property {object} [canvas] Canvas reservation metadata for the Sythos profile.
350
350
  * @property {{format:'ean2'|'ean5', text:string, parity:string, checksum?:number}} [addon] Attached EAN/UPC supplement.
351
351
  * @property {number} [confidence] Camera-profile confidence from 0 to 1.
352
352
  * @property {{x:number,y:number,width:number,height:number}} [bounds] Camera-profile bounds in the scanned orientation.
353
- * @property {0|90|180|270} [rotation] Camera-profile orientation in degrees.
353
+ * @property {0|45|90|135|180|225|270|315} [rotation] Camera-profile orientation in degrees.
354
354
  * @property {{quietZone:boolean,checksum:boolean|null,rows:number|null,consistency:number|null}} [quality] Camera-profile validation evidence.
355
355
  * @property {boolean} [gs1] Whether the physical symbol is classified as GS1.
356
356
  * @property {string} [symbologyIdentifier] GS1 symbology identifier.
@@ -372,8 +372,8 @@ export function encode(text, options = {}) {
372
372
  * @param {string[]} [options.formats] Restrict to these format ids.
373
373
  * @param {boolean} [options.tryHarder] Retry inverted and rotated. Default true.
374
374
  * @param {'global'|'hybrid'|'auto'} [options.binarizer]
375
- * @param {'camera'} [options.profile] Opt-in strict camera profile for validated 1D reads.
376
- * @param {object} [options.frameqr] FrameQR Code detector options when
375
+ * @param {'camera'} [options.profile] Opt-in strict camera profile for validated reads.
376
+ * @param {object} [options.frameqr] Sythos Canvas QR detector options when
377
377
  * the profile marker is not preserved through image rendering.
378
378
  * @returns {DecodeResult[]}
379
379
  */
@@ -392,6 +392,8 @@ export function decode(image, options = {}) {
392
392
  const wantFrameQR = !want || want.has('frameqr') || want.has('frame-qr') || want.has('canvas-qr');
393
393
  const oneDAliases = new Set(['gs1databar14', 'databar', 'gs1-databar14']);
394
394
  const wantOneD = !want || [...want].some((f) => f in ONED_FORMATS || oneDAliases.has(f));
395
+ const wantTwoD = wantQR || wantDataMatrix || wantAztec || wantAztecRune || wantPDF417
396
+ || wantCompactPDF417 || wantMicroPDF417 || wantMicroQR || wantRMQR || wantFrameQR;
395
397
 
396
398
  const source = LuminanceSource.fromImageData(image);
397
399
  const results = [];
@@ -403,114 +405,151 @@ export function decode(image, options = {}) {
403
405
  for (const pass of passes) {
404
406
  const bits = binarize(pass, binarizer);
405
407
 
406
- if (wantQR && qrCanDecode) {
407
- try {
408
- for (const found of qr.detectAndDecodeQR(bits)) {
409
- results.push({ ...found, format: 'qr' });
410
- }
411
- } catch {
412
- /* no QR in this pass */
413
- }
414
- }
408
+ // Keep all two-dimensional detector calls in one helper so camera-only
409
+ // orientation retries use exactly the same validation path as the native
410
+ // orientation. Arbitrary-angle retries remain opt-in to the camera
411
+ // profile because resampling is intentionally not part of ordinary decode.
412
+ const readTwoD = (candidateBits, cameraRotation = 0, candidateSource = pass) => {
413
+ const before = results.length;
414
+ const add = (found, format) => {
415
+ if (!found) return;
416
+ const publicFound = { ...found, format };
417
+ if (profile === 'camera' && cameraRotation !== 0) publicFound.rotation = cameraRotation;
418
+ results.push(publicFound);
419
+ };
415
420
 
416
- if (wantDataMatrix && dataMatrixCanDecode) {
417
- // Hybrid thresholding can erase the interior of very large, perfectly
418
- // uniform modules. In auto mode keep the local-threshold attempt, then
419
- // retry Data Matrix once with the global threshold before giving up.
420
- const dataMatrixBits = binarizer === 'auto' ? [bits, binarize(pass, 'global')] : [bits];
421
- for (const candidateBits of dataMatrixBits) {
421
+ if (wantQR && qrCanDecode) {
422
422
  try {
423
- const found = datamatrix.detectAndDecodeDataMatrix(candidateBits);
424
- if (found) { results.push({ ...found, format: 'datamatrix' }); break; }
423
+ for (const found of qr.detectAndDecodeQR(candidateBits)) add(found, 'qr');
425
424
  } catch {
426
- /* no Data Matrix with this threshold */
425
+ /* no QR in this pass */
426
+ }
427
+ }
428
+
429
+ if (wantDataMatrix && dataMatrixCanDecode) {
430
+ // Hybrid thresholding can erase the interior of very large, perfectly
431
+ // uniform modules. In auto mode keep the local-threshold attempt, then
432
+ // retry Data Matrix once with the global threshold before giving up.
433
+ const dataMatrixBits = binarizer === 'auto'
434
+ ? [candidateBits, binarize(candidateSource, 'global')]
435
+ : [candidateBits];
436
+ for (const thresholdBits of dataMatrixBits) {
437
+ try {
438
+ const found = datamatrix.detectAndDecodeDataMatrix(thresholdBits);
439
+ if (found) { add(found, 'datamatrix'); break; }
440
+ } catch {
441
+ /* no Data Matrix with this threshold */
442
+ }
443
+ }
444
+ }
445
+
446
+ if (wantAztec && aztecCanDecode) {
447
+ // The central bull's-eye is a small, high-contrast target. Hybrid
448
+ // thresholding can flatten it on clean rendered symbols, so mirror the
449
+ // Data Matrix global fallback in auto mode.
450
+ const aztecBits = binarizer === 'auto'
451
+ ? [candidateBits, binarize(candidateSource, 'global')]
452
+ : [candidateBits];
453
+ for (const thresholdBits of aztecBits) {
454
+ try {
455
+ const found = aztec.detectAndDecodeAztec(thresholdBits);
456
+ if (found) { add(found, 'aztec'); break; }
457
+ } catch {
458
+ /* no Aztec code with this threshold */
459
+ }
427
460
  }
428
461
  }
429
- }
430
462
 
431
- if (wantAztec && aztecCanDecode) {
432
- // The central bull's-eye is a small, high-contrast target. Hybrid
433
- // thresholding can flatten it on clean rendered symbols, so mirror the
434
- // Data Matrix global fallback in auto mode.
435
- const aztecBits = binarizer === 'auto' ? [bits, binarize(pass, 'global')] : [bits];
436
- for (const candidateBits of aztecBits) {
463
+ if (wantAztecRune && aztecRuneCanDecode) {
437
464
  try {
438
- const found = aztec.detectAndDecodeAztec(candidateBits);
439
- if (found) { results.push({ ...found, format: 'aztec' }); break; }
465
+ const found = aztecRune.detectAndDecodeAztecRune(candidateBits);
466
+ if (found) add(found, 'aztecrune');
440
467
  } catch {
441
- /* no Aztec code with this threshold */
468
+ /* no Aztec Rune in this pass */
442
469
  }
443
470
  }
444
- }
445
471
 
446
- if (wantAztecRune && aztecRuneCanDecode) {
447
- try {
448
- const found = aztecRune.detectAndDecodeAztecRune(bits);
449
- if (found) results.push({ ...found, format: 'aztecrune' });
450
- } catch {
451
- /* no Aztec Rune in this pass */
472
+ if (wantPDF417 && pdf417CanDecode) {
473
+ try {
474
+ const found = pdf417.detectAndDecodePDF417(candidateBits);
475
+ if (found) add(found, 'pdf417');
476
+ } catch {
477
+ /* no PDF417 in this pass */
478
+ }
452
479
  }
453
- }
454
480
 
455
- if (wantPDF417 && pdf417CanDecode) {
456
- try {
457
- const found = pdf417.detectAndDecodePDF417(bits);
458
- if (found) results.push({ ...found, format: 'pdf417' });
459
- } catch {
460
- /* no PDF417 in this pass */
481
+ if (wantCompactPDF417 && compactPdf417CanDecode) {
482
+ try {
483
+ const found = compactPdf417.detectAndDecodeCompactPDF417(candidateBits);
484
+ if (found) add(found, 'compactpdf417');
485
+ } catch {
486
+ /* no Compact PDF417 in this pass */
487
+ }
461
488
  }
462
- }
463
489
 
464
- if (wantCompactPDF417 && compactPdf417CanDecode) {
465
- try {
466
- const found = compactPdf417.detectAndDecodeCompactPDF417(bits);
467
- if (found) results.push({ ...found, format: 'compactpdf417' });
468
- } catch {
469
- /* no Compact PDF417 in this pass */
490
+ if (wantMicroPDF417 && microPdf417CanDecode) {
491
+ // MicroPDF417 detection measures runs across the whole raster. Hybrid
492
+ // thresholding can alter uniform modules near local-window boundaries,
493
+ // so retry the global threshold in auto mode as for the other 2D codes.
494
+ const microPdf417Bits = binarizer === 'auto'
495
+ ? [candidateBits, binarize(candidateSource, 'global')]
496
+ : [candidateBits];
497
+ for (const thresholdBits of microPdf417Bits) {
498
+ try {
499
+ const found = micropdf417.detectAndDecodeMicroPDF417(thresholdBits);
500
+ if (found) { add(found, 'micropdf417'); break; }
501
+ } catch {
502
+ /* no MicroPDF417 with this threshold */
503
+ }
504
+ }
470
505
  }
471
- }
472
506
 
473
- if (wantMicroPDF417 && microPdf417CanDecode) {
474
- // MicroPDF417 detection measures runs across the whole raster. Hybrid
475
- // thresholding can alter uniform modules near local-window boundaries,
476
- // so retry the global threshold in auto mode as for the other 2D codes.
477
- const microPdf417Bits = binarizer === 'auto' ? [bits, binarize(pass, 'global')] : [bits];
478
- for (const candidateBits of microPdf417Bits) {
507
+ if (wantMicroQR && microQrCanDecode) {
479
508
  try {
480
- const found = micropdf417.detectAndDecodeMicroPDF417(candidateBits);
481
- if (found) { results.push({ ...found, format: 'micropdf417' }); break; }
509
+ for (const found of microqr.detectAndDecodeMicroQR(candidateBits)) add(found, 'microqr');
482
510
  } catch {
483
- /* no MicroPDF417 with this threshold */
511
+ /* no Micro QR in this pass */
484
512
  }
485
513
  }
486
- }
487
514
 
488
- if (wantMicroQR && microQrCanDecode) {
489
- try {
490
- for (const found of microqr.detectAndDecodeMicroQR(bits)) {
491
- results.push({ ...found, format: 'microqr' });
515
+ if (wantRMQR && rmqrCanDecode) {
516
+ try {
517
+ const found = rmqr.detectAndDecodeRMQR(candidateBits);
518
+ if (found) add(found, 'rmqr');
519
+ } catch {
520
+ /* no rMQR in this pass */
492
521
  }
493
- } catch {
494
- /* no Micro QR in this pass */
495
522
  }
496
- }
497
523
 
498
- if (wantRMQR && rmqrCanDecode) {
499
- try {
500
- const found = rmqr.detectAndDecodeRMQR(bits);
501
- if (found) results.push({ ...found, format: 'rmqr' });
502
- } catch {
503
- /* no rMQR in this pass */
524
+ if (wantFrameQR && frameQrCanDecode) {
525
+ try {
526
+ for (const found of frameqr.detectAndDecodeFrameQR(candidateBits, options.frameqr ?? {})) add(found, 'frameqr');
527
+ } catch {
528
+ /* no Sythos Canvas QR profile in this pass */
529
+ }
504
530
  }
505
- }
506
531
 
507
- if (wantFrameQR && frameQrCanDecode) {
508
- try {
509
- for (const found of frameqr.detectAndDecodeFrameQR(bits, options.frameqr ?? {})) {
510
- results.push({ ...found, format: 'frameqr' });
511
- }
512
- } catch {
513
- /* no FrameQR Code in this pass */
532
+ return results.length > before;
533
+ };
534
+
535
+ const twoDFound = readTwoD(bits);
536
+ if (profile === 'camera' && wantTwoD && !twoDFound) {
537
+ // Normalize a camera frame through the full eight-angle set only after
538
+ // the native orientation has failed. This preserves the fast path while
539
+ // limiting resampling to frames that need it.
540
+ const cameraRotations = [45, 90, 135, 180, 225, 270, 315];
541
+ for (const rotation of cameraRotations) {
542
+ const inverse = (360 - rotation) % 360;
543
+ const orientedSource = inverse % 90 === 0
544
+ ? inverse === 0
545
+ ? pass
546
+ : inverse === 90
547
+ ? pass.rotate90()
548
+ : inverse === 180
549
+ ? rotateLuminanceSourceByDegrees(pass, 180)
550
+ : pass.rotate90().rotate90().rotate90()
551
+ : rotateLuminanceSourceByDegrees(pass, inverse);
552
+ if (readTwoD(binarize(orientedSource, binarizer), rotation, orientedSource)) break;
514
553
  }
515
554
  }
516
555
 
@@ -519,18 +558,33 @@ export function decode(image, options = {}) {
519
558
  ? [...want].filter((f) => f in ONED_FORMATS || oneDAliases.has(f))
520
559
  : null;
521
560
  const oneDPasses = [{ bits, rotation: 0 }];
522
- // A linear symbol rotated by 90° has no usable horizontal scanline.
523
- // The strict camera profile adds exactly two normalized orientations,
524
- // only when the native orientation found no validated 1D result.
561
+ // A linear symbol rotated away from the horizontal has no usable
562
+ // horizontal scanline. The strict camera profile adds normalized
563
+ // orientations only when the native orientation found no validated 1D
564
+ // result. Keep the two quarter-turns first: they are the established
565
+ // path and avoid paying for diagonal resampling on the common case.
525
566
  const readOneD = (candidateBits, rotation) => decodeOneD(candidateBits, {
526
567
  ...options, formats: oneDFormats, tryHarder, profile, cameraRotation: rotation,
527
568
  });
528
569
  let oneDResults = readOneD(bits, 0);
529
570
  if (profile === 'camera' && oneDResults.length === 0) {
530
- oneDPasses.push(
531
- { bits: rotateBitMatrix90(bits, false), rotation: 90 },
532
- { bits: rotateBitMatrix90(bits, true), rotation: 270 },
533
- );
571
+ // `rotation` describes the supplied raster, clockwise from the
572
+ // canonical horizontal orientation. To normalize it, apply the
573
+ // inverse rotation. Include 180 explicitly so the full eight-angle
574
+ // set still works when tryHarder is disabled (the normal reversed-row
575
+ // pass also handles it without a second raster transform).
576
+ const cameraRotations = [90, 270, 180, 45, 135, 225, 315];
577
+ for (const rotation of cameraRotations) {
578
+ const inverse = (360 - rotation) % 360;
579
+ const oriented = inverse % 90 === 0
580
+ ? inverse === 0
581
+ ? bits.clone()
582
+ : inverse === 180
583
+ ? (() => { const copy = bits.clone(); copy.rotate180(); return copy; })()
584
+ : rotateBitMatrix90(bits, inverse === 90)
585
+ : rotateBitMatrixByDegrees(bits, inverse);
586
+ oneDPasses.push({ bits: oriented, rotation });
587
+ }
534
588
  for (let i = 1; i < oneDPasses.length && oneDResults.length === 0; i++) {
535
589
  oneDResults = readOneD(oneDPasses[i].bits, oneDPasses[i].rotation);
536
590
  }
@@ -620,6 +674,105 @@ function rotateBitMatrix90(matrix, clockwise) {
620
674
  return rotated;
621
675
  }
622
676
 
677
+ /**
678
+ * Rotate a binarized raster by an arbitrary clockwise angle without changing
679
+ * the source matrix. This is intentionally used only by the strict camera
680
+ * profile: arbitrary-angle resampling is useful for linear symbols, but is
681
+ * too permissive to become a default retry for every detector.
682
+ *
683
+ * @param {BitMatrix} matrix
684
+ * @param {number} degrees Clockwise angle, normally one of 45/135/225/315.
685
+ * @returns {BitMatrix}
686
+ */
687
+ function rotateBitMatrixByDegrees(matrix, degrees) {
688
+ const angle = ((degrees % 360) + 360) % 360;
689
+ if (angle === 0) return matrix.clone();
690
+ if (angle === 90) return rotateBitMatrix90(matrix, true);
691
+ if (angle === 180) {
692
+ const rotated = matrix.clone();
693
+ rotated.rotate180();
694
+ return rotated;
695
+ }
696
+ if (angle === 270) return rotateBitMatrix90(matrix, false);
697
+
698
+ const radians = angle * Math.PI / 180;
699
+ const sin = Math.sin(radians);
700
+ const cos = Math.cos(radians);
701
+ const width = Math.ceil(Math.abs(matrix.width * cos) + Math.abs(matrix.height * sin));
702
+ const height = Math.ceil(Math.abs(matrix.width * sin) + Math.abs(matrix.height * cos));
703
+ const rotated = new BitMatrix(width, height);
704
+ const sourceCenterX = (matrix.width - 1) / 2;
705
+ const sourceCenterY = (matrix.height - 1) / 2;
706
+ const destinationCenterX = (width - 1) / 2;
707
+ const destinationCenterY = (height - 1) / 2;
708
+
709
+ // Inverse-map destination pixels into the source. Sampling the already
710
+ // binarized raster keeps the operation deterministic and leaves the caller's
711
+ // original image and threshold result untouched.
712
+ for (let y = 0; y < height; y++) {
713
+ const dy = y - destinationCenterY;
714
+ for (let x = 0; x < width; x++) {
715
+ const dx = x - destinationCenterX;
716
+ const sourceX = Math.round(cos * dx + sin * dy + sourceCenterX);
717
+ const sourceY = Math.round(-sin * dx + cos * dy + sourceCenterY);
718
+ if (sourceX >= 0 && sourceX < matrix.width && sourceY >= 0 && sourceY < matrix.height &&
719
+ matrix.get(sourceX, sourceY)) {
720
+ rotated.set(x, y);
721
+ }
722
+ }
723
+ }
724
+ return rotated;
725
+ }
726
+
727
+ /**
728
+ * Rotate the greyscale source before thresholding. Resampling luminance rather
729
+ * than an already-binarized matrix preserves module contrast at diagonal
730
+ * orientations and gives the format-specific detectors the same input quality
731
+ * as the native camera frame.
732
+ *
733
+ * @param {LuminanceSource} source
734
+ * @param {number} degrees Clockwise angle.
735
+ * @returns {LuminanceSource}
736
+ */
737
+ function rotateLuminanceSourceByDegrees(source, degrees) {
738
+ const angle = ((degrees % 360) + 360) % 360;
739
+ if (angle === 0) return source;
740
+ if (angle === 90) return source.rotate90();
741
+ if (angle === 180) return source.rotate90().rotate90();
742
+ if (angle === 270) return source.rotate90().rotate90().rotate90();
743
+
744
+ const radians = angle * Math.PI / 180;
745
+ const sin = Math.sin(radians);
746
+ const cos = Math.cos(radians);
747
+ const width = Math.ceil(Math.abs(source.width * cos) + Math.abs(source.height * sin));
748
+ const height = Math.ceil(Math.abs(source.width * sin) + Math.abs(source.height * cos));
749
+ const rotated = new Uint8Array(width * height);
750
+ rotated.fill(255);
751
+ const sourceCenterX = (source.width - 1) / 2;
752
+ const sourceCenterY = (source.height - 1) / 2;
753
+ const destinationCenterX = (width - 1) / 2;
754
+ const destinationCenterY = (height - 1) / 2;
755
+
756
+ for (let y = 0; y < height; y++) {
757
+ const dy = y - destinationCenterY;
758
+ for (let x = 0; x < width; x++) {
759
+ const dx = x - destinationCenterX;
760
+ const sourceX = cos * dx + sin * dy + sourceCenterX;
761
+ const sourceY = -sin * dx + cos * dy + sourceCenterY;
762
+ if (sourceX >= 0 && sourceX < source.width && sourceY >= 0 && sourceY < source.height) {
763
+ const left = Math.floor(sourceX), top = Math.floor(sourceY);
764
+ const right = Math.min(source.width - 1, left + 1);
765
+ const bottom = Math.min(source.height - 1, top + 1);
766
+ const fx = sourceX - left, fy = sourceY - top;
767
+ const topValue = source.get(left, top) * (1 - fx) + source.get(right, top) * fx;
768
+ const bottomValue = source.get(left, bottom) * (1 - fx) + source.get(right, bottom) * fx;
769
+ rotated[y * width + x] = Math.round(topValue * (1 - fy) + bottomValue * fy);
770
+ }
771
+ }
772
+ }
773
+ return LuminanceSource.fromGrey(rotated, width, height);
774
+ }
775
+
623
776
  /**
624
777
  * Decode, or throw if nothing is found.
625
778
  *
@@ -634,4 +787,4 @@ export function decodeStrict(image, options) {
634
787
  }
635
788
 
636
789
  /** Library version, matching package.json. */
637
- export const VERSION = '1.5.6';
790
+ export const VERSION = '1.5.8';
@@ -23,7 +23,6 @@
23
23
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
24
  * SOFTWARE.
25
25
  *
26
- * SPDX-FileCopyrightText: 2026 Sythos (https://www.sythos.net)
27
26
  * SPDX-License-Identifier: MIT
28
27
  *
29
28
  * Original work. No code from any other barcode implementation.
@@ -23,7 +23,6 @@
23
23
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
24
  * SOFTWARE.
25
25
  *
26
- * SPDX-FileCopyrightText: 2026 Sythos (https://www.sythos.net)
27
26
  * SPDX-License-Identifier: MIT
28
27
  *
29
28
  * Original work. No code from any other barcode implementation.
@@ -23,7 +23,6 @@
23
23
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
24
  * SOFTWARE.
25
25
  *
26
- * SPDX-FileCopyrightText: 2026 Sythos (https://www.sythos.net)
27
26
  * SPDX-License-Identifier: MIT
28
27
  *
29
28
  * Original work. No code from any other barcode implementation.
@@ -23,7 +23,6 @@
23
23
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
24
  * SOFTWARE.
25
25
  *
26
- * SPDX-FileCopyrightText: 2026 Sythos (https://www.sythos.net)
27
26
  * SPDX-License-Identifier: MIT
28
27
  *
29
28
  * Original work. No code from any other barcode implementation.
@@ -23,7 +23,6 @@
23
23
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
24
  * SOFTWARE.
25
25
  *
26
- * SPDX-FileCopyrightText: 2026 Sythos (https://www.sythos.net)
27
26
  * SPDX-License-Identifier: MIT
28
27
  *
29
28
  * Original work. No code from any other barcode implementation.
@@ -23,7 +23,6 @@
23
23
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
24
  * SOFTWARE.
25
25
  *
26
- * SPDX-FileCopyrightText: 2026 Sythos (https://www.sythos.net)
27
26
  * SPDX-License-Identifier: MIT
28
27
  *
29
28
  * Original work. No code from any other barcode implementation.
@@ -23,7 +23,6 @@
23
23
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
24
  * SOFTWARE.
25
25
  *
26
- * SPDX-FileCopyrightText: 2026 Sythos (https://www.sythos.net)
27
26
  * SPDX-License-Identifier: MIT
28
27
  *
29
28
  * Original work. No code from any other barcode implementation.
@@ -23,7 +23,6 @@
23
23
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
24
  * SOFTWARE.
25
25
  *
26
- * SPDX-FileCopyrightText: 2026 Sythos (https://www.sythos.net)
27
26
  * SPDX-License-Identifier: MIT
28
27
  *
29
28
  * Original work. No code from any other barcode implementation.
@@ -23,7 +23,6 @@
23
23
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
24
  * SOFTWARE.
25
25
  *
26
- * SPDX-FileCopyrightText: 2026 Sythos (https://www.sythos.net)
27
26
  * SPDX-License-Identifier: MIT
28
27
  *
29
28
  * Original work. No code from any other barcode implementation.
@@ -23,7 +23,6 @@
23
23
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
24
  * SOFTWARE.
25
25
  *
26
- * SPDX-FileCopyrightText: 2026 Sythos (https://www.sythos.net)
27
26
  * SPDX-License-Identifier: MIT
28
27
  *
29
28
  * Original work. No code from any other barcode implementation.
@@ -23,7 +23,6 @@
23
23
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
24
  * SOFTWARE.
25
25
  *
26
- * SPDX-FileCopyrightText: 2026 Sythos (https://www.sythos.net)
27
26
  * SPDX-License-Identifier: MIT
28
27
  *
29
28
  * Original work. No code from any other barcode implementation.
@@ -23,7 +23,6 @@
23
23
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
24
  * SOFTWARE.
25
25
  *
26
- * SPDX-FileCopyrightText: 2026 Sythos (https://www.sythos.net)
27
26
  * SPDX-License-Identifier: MIT
28
27
  *
29
28
  * Original work. No code from any other barcode implementation.
@@ -23,7 +23,6 @@
23
23
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
24
  * SOFTWARE.
25
25
  *
26
- * SPDX-FileCopyrightText: 2026 Sythos (https://www.sythos.net)
27
26
  * SPDX-License-Identifier: MIT
28
27
  *
29
28
  * Original work. No code from any other barcode implementation.