@sythos/js_barcode_universal 1.5.8 → 1.5.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (179) hide show
  1. package/LICENSE +5 -4
  2. package/NOTICE.md +2 -2
  3. package/README.md +61 -13
  4. package/bundle/sythos-barcode.esm.js +11658 -11244
  5. package/bundle/sythos-barcode.js +11658 -11244
  6. package/package.json +10 -3
  7. package/src/index.d.ts +1 -1
  8. package/src/index.js +508 -516
  9. package/src/js/aztec/decoder.js +208 -199
  10. package/src/js/aztec/detector.js +153 -126
  11. package/src/js/aztec/encoder.js +172 -153
  12. package/src/js/aztec/high-level.js +106 -101
  13. package/src/js/aztec/index.js +1 -12
  14. package/src/js/aztec/tables.js +106 -120
  15. package/src/js/aztecrune/decoder.js +76 -85
  16. package/src/js/aztecrune/detector.js +107 -93
  17. package/src/js/aztecrune/encoder.js +50 -68
  18. package/src/js/aztecrune/index.js +1 -16
  19. package/src/js/aztecrune/tables.js +59 -54
  20. package/src/js/compactpdf417/decoder.js +69 -74
  21. package/src/js/compactpdf417/detector.js +74 -75
  22. package/src/js/compactpdf417/encoder.js +68 -81
  23. package/src/js/compactpdf417/index.js +1 -13
  24. package/src/js/compactpdf417/tables.js +71 -80
  25. package/src/js/core/bit-buffer.js +106 -122
  26. package/src/js/core/bit-matrix.js +196 -194
  27. package/src/js/core/errors.js +13 -15
  28. package/src/js/core/galois-field.js +119 -133
  29. package/src/js/core/index.js +2 -19
  30. package/src/js/core/reed-solomon.js +195 -204
  31. package/src/js/databar/codec.js +93 -108
  32. package/src/js/databar/decoder.js +149 -140
  33. package/src/js/databar/encoder.js +47 -55
  34. package/src/js/databar/gs1.js +122 -123
  35. package/src/js/databar/index.js +4 -36
  36. package/src/js/databar/patterns.js +81 -85
  37. package/src/js/databar/tables.js +51 -52
  38. package/src/js/datamatrix/decoder.js +216 -182
  39. package/src/js/datamatrix/detector.js +161 -132
  40. package/src/js/datamatrix/encoder.js +192 -133
  41. package/src/js/datamatrix/index.js +1 -9
  42. package/src/js/datamatrix/tables.js +68 -69
  43. package/src/js/frameqr/decoder.js +102 -125
  44. package/src/js/frameqr/detector.js +101 -94
  45. package/src/js/frameqr/encoder.js +67 -80
  46. package/src/js/frameqr/index.js +1 -10
  47. package/src/js/frameqr/tables.js +145 -163
  48. package/src/js/image/binarizer.js +146 -162
  49. package/src/js/image/grid-sampler.js +63 -79
  50. package/src/js/image/index.js +0 -2
  51. package/src/js/image/luminance.js +129 -140
  52. package/src/js/image/perspective.js +112 -149
  53. package/src/js/micropdf417/compaction.js +64 -65
  54. package/src/js/micropdf417/decoder.js +106 -109
  55. package/src/js/micropdf417/detector.js +73 -70
  56. package/src/js/micropdf417/encoder.js +129 -154
  57. package/src/js/micropdf417/error-correction.js +6 -11
  58. package/src/js/micropdf417/index.js +2 -16
  59. package/src/js/micropdf417/tables.js +112 -99
  60. package/src/js/microqr/decoder.js +177 -182
  61. package/src/js/microqr/detector.js +240 -256
  62. package/src/js/microqr/encoder.js +239 -194
  63. package/src/js/microqr/index.js +0 -2
  64. package/src/js/microqr/tables.js +208 -200
  65. package/src/js/oned/addons.js +167 -191
  66. package/src/js/oned/index.js +27 -58
  67. package/src/js/oned/patterns.js +194 -213
  68. package/src/js/oned/reader.js +1003 -984
  69. package/src/js/oned/writers.js +411 -466
  70. package/src/js/pdf417/compaction.js +315 -222
  71. package/src/js/pdf417/decoder.js +71 -36
  72. package/src/js/pdf417/detector.js +377 -361
  73. package/src/js/pdf417/encoder.js +57 -46
  74. package/src/js/pdf417/error-correction.js +5 -8
  75. package/src/js/pdf417/tables.js +248 -250
  76. package/src/js/qr/decoder.js +311 -347
  77. package/src/js/qr/detector.js +378 -395
  78. package/src/js/qr/encoder.js +514 -599
  79. package/src/js/qr/index.js +0 -2
  80. package/src/js/qr/tables.js +375 -422
  81. package/src/js/render/image-data.js +57 -62
  82. package/src/js/render/index.js +21 -23
  83. package/src/js/render/options.js +82 -90
  84. package/src/js/render/png.js +144 -169
  85. package/src/js/render/svg.js +41 -44
  86. package/src/js/render/webgl.js +103 -109
  87. package/src/js/render/webgpu.js +205 -217
  88. package/src/js/rmqr/decoder.js +161 -53
  89. package/src/js/rmqr/detector.js +73 -41
  90. package/src/js/rmqr/encoder.js +253 -104
  91. package/src/js/rmqr/index.js +1 -5
  92. package/src/js/rmqr/tables.js +108 -85
  93. package/src/ts/aztec/decoder.ts +317 -0
  94. package/src/ts/aztec/detector.ts +224 -0
  95. package/src/ts/aztec/encoder.ts +257 -0
  96. package/src/ts/aztec/high-level.ts +211 -0
  97. package/src/ts/aztec/index.ts +45 -0
  98. package/src/ts/aztec/tables.ts +210 -0
  99. package/src/ts/aztecrune/decoder.ts +155 -0
  100. package/src/ts/aztecrune/detector.ts +166 -0
  101. package/src/ts/aztecrune/encoder.ts +121 -0
  102. package/src/ts/aztecrune/index.ts +49 -0
  103. package/src/ts/aztecrune/tables.ts +137 -0
  104. package/src/ts/compactpdf417/decoder.ts +128 -0
  105. package/src/ts/compactpdf417/detector.ts +139 -0
  106. package/src/ts/compactpdf417/encoder.ts +140 -0
  107. package/src/ts/compactpdf417/index.ts +17 -0
  108. package/src/ts/compactpdf417/tables.ts +173 -0
  109. package/src/ts/core/bit-buffer.ts +174 -0
  110. package/src/ts/core/bit-matrix.ts +241 -0
  111. package/src/ts/core/errors.ts +61 -0
  112. package/src/ts/core/galois-field.ts +207 -0
  113. package/src/ts/core/index.ts +56 -0
  114. package/src/ts/core/reed-solomon.ts +327 -0
  115. package/src/ts/databar/codec.ts +181 -0
  116. package/src/ts/databar/decoder.ts +215 -0
  117. package/src/ts/databar/encoder.ts +96 -0
  118. package/src/ts/databar/gs1.ts +177 -0
  119. package/src/ts/databar/index.ts +68 -0
  120. package/src/ts/databar/patterns.ts +143 -0
  121. package/src/ts/databar/tables.ts +124 -0
  122. package/src/ts/datamatrix/decoder.ts +262 -0
  123. package/src/ts/datamatrix/detector.ts +225 -0
  124. package/src/ts/datamatrix/encoder.ts +191 -0
  125. package/src/ts/datamatrix/index.ts +42 -0
  126. package/src/ts/datamatrix/tables.ts +123 -0
  127. package/src/ts/frameqr/decoder.ts +239 -0
  128. package/src/ts/frameqr/detector.ts +192 -0
  129. package/src/ts/frameqr/encoder.ts +156 -0
  130. package/src/ts/frameqr/index.ts +42 -0
  131. package/src/ts/frameqr/tables.ts +270 -0
  132. package/src/ts/image/binarizer.ts +270 -0
  133. package/src/ts/image/grid-sampler.ts +164 -0
  134. package/src/ts/image/index.ts +40 -0
  135. package/src/ts/image/luminance.ts +196 -0
  136. package/src/ts/image/perspective.ts +195 -0
  137. package/src/ts/index.d.ts +1 -1
  138. package/src/ts/index.ts +790 -0
  139. package/src/ts/micropdf417/compaction.ts +116 -0
  140. package/src/ts/micropdf417/decoder.ts +183 -0
  141. package/src/ts/micropdf417/detector.ts +149 -0
  142. package/src/ts/micropdf417/encoder.ts +209 -0
  143. package/src/ts/micropdf417/error-correction.ts +55 -0
  144. package/src/ts/micropdf417/index.ts +49 -0
  145. package/src/ts/micropdf417/tables.ts +184 -0
  146. package/src/ts/microqr/decoder.ts +245 -0
  147. package/src/ts/microqr/detector.ts +355 -0
  148. package/src/ts/microqr/encoder.ts +269 -0
  149. package/src/ts/microqr/index.ts +36 -0
  150. package/src/ts/microqr/tables.ts +316 -0
  151. package/src/ts/oned/addons.ts +420 -0
  152. package/src/ts/oned/index.ts +106 -0
  153. package/src/ts/oned/patterns.ts +384 -0
  154. package/src/ts/oned/reader.ts +1364 -0
  155. package/src/ts/oned/writers.ts +746 -0
  156. package/src/ts/pdf417/compaction.ts +298 -0
  157. package/src/ts/pdf417/decoder.ts +75 -0
  158. package/src/ts/pdf417/detector.ts +468 -0
  159. package/src/ts/pdf417/encoder.ts +91 -0
  160. package/src/ts/pdf417/error-correction.ts +47 -0
  161. package/src/ts/pdf417/index.ts +6 -0
  162. package/src/ts/pdf417/tables.ts +317 -0
  163. package/src/ts/qr/decoder.ts +575 -0
  164. package/src/ts/qr/detector.ts +630 -0
  165. package/src/ts/qr/encoder.ts +958 -0
  166. package/src/ts/qr/index.ts +44 -0
  167. package/src/ts/qr/tables.ts +737 -0
  168. package/src/ts/render/image-data.ts +125 -0
  169. package/src/ts/render/index.ts +130 -0
  170. package/src/ts/render/options.ts +160 -0
  171. package/src/ts/render/png.ts +295 -0
  172. package/src/ts/render/svg.ts +120 -0
  173. package/src/ts/render/webgl.ts +206 -0
  174. package/src/ts/render/webgpu.ts +369 -0
  175. package/src/ts/rmqr/decoder.ts +101 -0
  176. package/src/ts/rmqr/detector.ts +90 -0
  177. package/src/ts/rmqr/encoder.ts +172 -0
  178. package/src/ts/rmqr/index.ts +37 -0
  179. package/src/ts/rmqr/tables.ts +154 -0
@@ -27,442 +27,458 @@
27
27
  *
28
28
  * Original work. No code from any other barcode implementation.
29
29
  */
30
-
31
30
  import { BitMatrix } from '../core/bit-matrix.js';
32
31
  import { sampleGrid, sampleGridVoting } from '../image/grid-sampler.js';
33
32
  import { PerspectiveTransform } from '../image/perspective.js';
34
33
  import { decodePDF417 } from './decoder.js';
35
-
36
34
  const START_PATTERN = [8, 1, 1, 1, 1, 1, 1, 3];
37
35
  const STOP_PATTERN = [7, 1, 1, 3, 1, 1, 1, 2, 1];
38
36
  const SCAN_ANGLES = [0, -4, 4, -8, 8, -14, 14, -22, 22, -32, 32]
39
- .map((degrees) => degrees * Math.PI / 180);
40
-
37
+ .map((degrees) => degrees * Math.PI / 180);
41
38
  function rotateClockwise(source) {
42
- const rotated = new BitMatrix(source.height, source.width);
43
- for (let y = 0; y < source.height; y++) for (let x = 0; x < source.width; x++) {
44
- if (source.get(x, y)) rotated.set(source.height - 1 - y, x);
45
- }
46
- return rotated;
39
+ const rotated = new BitMatrix(source.height, source.width);
40
+ for (let y = 0; y < source.height; y++)
41
+ for (let x = 0; x < source.width; x++) {
42
+ if (source.get(x, y))
43
+ rotated.set(source.height - 1 - y, x);
44
+ }
45
+ return rotated;
47
46
  }
48
-
49
47
  function scanGeometry(angle) {
50
- return {
51
- along: { x: Math.cos(angle), y: Math.sin(angle) },
52
- across: { x: -Math.sin(angle), y: Math.cos(angle) },
53
- };
48
+ return {
49
+ along: { x: Math.cos(angle), y: Math.sin(angle) },
50
+ across: { x: -Math.sin(angle), y: Math.cos(angle) },
51
+ };
54
52
  }
55
-
56
53
  function projectionRange(image, vector) {
57
- const points = [
58
- { x: 0, y: 0 }, { x: image.width - 1, y: 0 },
59
- { x: image.width - 1, y: image.height - 1 }, { x: 0, y: image.height - 1 },
60
- ];
61
- const values = points.map((point) => point.x * vector.x + point.y * vector.y);
62
- return { min: Math.min(...values), max: Math.max(...values) };
54
+ const points = [
55
+ { x: 0, y: 0 }, { x: image.width - 1, y: 0 },
56
+ { x: image.width - 1, y: image.height - 1 }, { x: 0, y: image.height - 1 },
57
+ ];
58
+ const values = points.map((point) => point.x * vector.x + point.y * vector.y);
59
+ return { min: Math.min(...values), max: Math.max(...values) };
63
60
  }
64
-
65
61
  function lineRange(image, geometry, across) {
66
- const { along, across: normal } = geometry;
67
- let min = -Infinity, max = Infinity;
68
- const constrain = (low, high, step, offset) => {
69
- if (Math.abs(step) < 1e-9) return offset >= low && offset <= high;
70
- const first = (low - offset) / step, second = (high - offset) / step;
71
- min = Math.max(min, Math.min(first, second));
72
- max = Math.min(max, Math.max(first, second));
73
- return min <= max;
74
- };
75
- if (!constrain(0, image.width - 1, along.x, normal.x * across) ||
76
- !constrain(0, image.height - 1, along.y, normal.y * across)) return null;
77
- return { min: Math.ceil(min), max: Math.floor(max) };
62
+ const { along, across: normal } = geometry;
63
+ let min = -Infinity, max = Infinity;
64
+ const constrain = (low, high, step, offset) => {
65
+ if (Math.abs(step) < 1e-9)
66
+ return offset >= low && offset <= high;
67
+ const first = (low - offset) / step, second = (high - offset) / step;
68
+ min = Math.max(min, Math.min(first, second));
69
+ max = Math.min(max, Math.max(first, second));
70
+ return min <= max;
71
+ };
72
+ if (!constrain(0, image.width - 1, along.x, normal.x * across) ||
73
+ !constrain(0, image.height - 1, along.y, normal.y * across))
74
+ return null;
75
+ return { min: Math.ceil(min), max: Math.floor(max) };
78
76
  }
79
-
80
77
  function runsInLine(image, geometry, across) {
81
- const range = lineRange(image, geometry, across);
82
- if (!range || range.max < range.min) return [];
83
- const { along, across: normal } = geometry;
84
- const runs = [];
85
- const valueAt = (position) => image.get(
86
- Math.round(along.x * position + normal.x * across),
87
- Math.round(along.y * position + normal.y * across),
88
- );
89
- let dark = valueAt(range.min), start = range.min;
90
- for (let position = range.min + 1; position <= range.max + 1; position++) {
91
- const value = position <= range.max ? valueAt(position) : !dark;
92
- if (value !== dark) {
93
- runs.push({ dark, start, end: position, length: position - start });
94
- start = position; dark = value;
78
+ const range = lineRange(image, geometry, across);
79
+ if (!range || range.max < range.min)
80
+ return [];
81
+ const { along, across: normal } = geometry;
82
+ const runs = [];
83
+ const valueAt = (position) => image.get(Math.round(along.x * position + normal.x * across), Math.round(along.y * position + normal.y * across));
84
+ let dark = valueAt(range.min), start = range.min;
85
+ for (let position = range.min + 1; position <= range.max + 1; position++) {
86
+ const value = position <= range.max ? valueAt(position) : !dark;
87
+ if (value !== dark) {
88
+ runs.push({ dark, start, end: position, length: position - start });
89
+ start = position;
90
+ dark = value;
91
+ }
95
92
  }
96
- }
97
- return runs;
93
+ return runs;
98
94
  }
99
-
100
95
  function removeSinglePixelSpecks(runs) {
101
- const clean = runs.map((run) => ({ ...run }));
102
- for (let index = 1; index < clean.length - 1;) {
103
- if (clean[index].length > 1) { index++; continue; }
104
- const merged = {
105
- dark: clean[index - 1].dark,
106
- start: clean[index - 1].start,
107
- end: clean[index + 1].end,
108
- length: clean[index - 1].length + clean[index].length + clean[index + 1].length,
109
- };
110
- clean.splice(index - 1, 3, merged);
111
- index = Math.max(1, index - 2);
112
- }
113
- return clean;
96
+ const clean = runs.map((run) => ({ ...run }));
97
+ for (let index = 1; index < clean.length - 1;) {
98
+ if (clean[index].length > 1) {
99
+ index++;
100
+ continue;
101
+ }
102
+ const merged = {
103
+ dark: clean[index - 1].dark,
104
+ start: clean[index - 1].start,
105
+ end: clean[index + 1].end,
106
+ length: clean[index - 1].length + clean[index].length + clean[index + 1].length,
107
+ };
108
+ clean.splice(index - 1, 3, merged);
109
+ index = Math.max(1, index - 2);
110
+ }
111
+ return clean;
114
112
  }
115
-
116
113
  function matchPattern(runs, at, expected) {
117
- if (at + expected.length > runs.length || !runs[at].dark) return null;
118
- let observed = 0, modules = 0;
119
- for (let i = 0; i < expected.length; i++) { observed += runs[at + i].length; modules += expected[i]; }
120
- const scale = observed / modules;
121
- if (scale < 0.65) return null;
122
- let error = 0;
123
- for (let i = 0; i < expected.length; i++) {
124
- const delta = Math.abs(runs[at + i].length - expected[i] * scale);
125
- if (delta > Math.max(1.15, scale * 0.62)) return null;
126
- error += delta / scale;
127
- }
128
- if (error / expected.length > 0.48) return null;
129
- return { start: runs[at].start, end: runs[at + expected.length - 1].end, scale, error, at };
114
+ if (at + expected.length > runs.length || !runs[at].dark)
115
+ return null;
116
+ let observed = 0, modules = 0;
117
+ for (let i = 0; i < expected.length; i++) {
118
+ observed += runs[at + i].length;
119
+ modules += expected[i];
120
+ }
121
+ const scale = observed / modules;
122
+ if (scale < 0.65)
123
+ return null;
124
+ let error = 0;
125
+ for (let i = 0; i < expected.length; i++) {
126
+ const delta = Math.abs(runs[at + i].length - expected[i] * scale);
127
+ if (delta > Math.max(1.15, scale * 0.62))
128
+ return null;
129
+ error += delta / scale;
130
+ }
131
+ if (error / expected.length > 0.48)
132
+ return null;
133
+ return { start: runs[at].start, end: runs[at + expected.length - 1].end, scale, error, at };
130
134
  }
131
-
132
135
  function quietPenalty(runs, start, stop) {
133
- const before = start.at > 0 ? runs[start.at - 1].length / start.scale : 0;
134
- const afterIndex = stop.at + STOP_PATTERN.length;
135
- const after = afterIndex < runs.length ? runs[afterIndex].length / stop.scale : 0;
136
- return Math.max(0, 2 - before) + Math.max(0, 2 - after);
136
+ const before = start.at > 0 ? runs[start.at - 1].length / start.scale : 0;
137
+ const afterIndex = stop.at + STOP_PATTERN.length;
138
+ const after = afterIndex < runs.length ? runs[afterIndex].length / stop.scale : 0;
139
+ return Math.max(0, 2 - before) + Math.max(0, 2 - after);
137
140
  }
138
-
139
141
  function patternPairs(runs) {
140
- const starts = [], stops = [];
141
- for (let at = 0; at < runs.length; at++) {
142
- const start = matchPattern(runs, at, START_PATTERN); if (start) starts.push(start);
143
- const stop = matchPattern(runs, at, STOP_PATTERN); if (stop) stops.push(stop);
144
- }
145
- const pairs = [];
146
- for (const start of starts) for (const stop of stops) {
147
- if (stop.start <= start.end) continue;
148
- const measured = stop.end - start.start;
149
- const localScale = Math.sqrt(start.scale * stop.scale);
150
- const roughColumns = Math.round((measured / localScale - 69) / 17);
151
- for (let columns = Math.max(1, roughColumns - 2); columns <= Math.min(30, roughColumns + 2); columns++) {
152
- const width = 69 + columns * 17;
153
- const globalScale = measured / width;
154
- const ratio = Math.max(start.scale, stop.scale, globalScale) /
155
- Math.min(start.scale, stop.scale, globalScale);
156
- if (ratio > 1.85) continue;
157
- const scaleError = Math.abs(Math.log(start.scale / globalScale)) +
158
- Math.abs(Math.log(stop.scale / globalScale));
159
- pairs.push({ start, stop, columns, width, scale: globalScale,
160
- startScale: start.scale, stopScale: stop.scale,
161
- error: start.error + stop.error + scaleError * 4 + quietPenalty(runs, start, stop) * 0.15 });
142
+ const starts = [], stops = [];
143
+ for (let at = 0; at < runs.length; at++) {
144
+ const start = matchPattern(runs, at, START_PATTERN);
145
+ if (start)
146
+ starts.push(start);
147
+ const stop = matchPattern(runs, at, STOP_PATTERN);
148
+ if (stop)
149
+ stops.push(stop);
162
150
  }
163
- }
164
- pairs.sort((a, b) => a.error - b.error);
165
- const used = new Set();
166
- return pairs.filter((pair) => {
167
- if (used.has(pair.columns)) return false;
168
- used.add(pair.columns);
169
- return used.size <= 3;
170
- });
151
+ const pairs = [];
152
+ for (const start of starts)
153
+ for (const stop of stops) {
154
+ if (stop.start <= start.end)
155
+ continue;
156
+ const measured = stop.end - start.start;
157
+ const localScale = Math.sqrt(start.scale * stop.scale);
158
+ const roughColumns = Math.round((measured / localScale - 69) / 17);
159
+ for (let columns = Math.max(1, roughColumns - 2); columns <= Math.min(30, roughColumns + 2); columns++) {
160
+ const width = 69 + columns * 17;
161
+ const globalScale = measured / width;
162
+ const ratio = Math.max(start.scale, stop.scale, globalScale) /
163
+ Math.min(start.scale, stop.scale, globalScale);
164
+ if (ratio > 1.85)
165
+ continue;
166
+ const scaleError = Math.abs(Math.log(start.scale / globalScale)) +
167
+ Math.abs(Math.log(stop.scale / globalScale));
168
+ pairs.push({ start, stop, columns, width, scale: globalScale,
169
+ startScale: start.scale, stopScale: stop.scale,
170
+ error: start.error + stop.error + scaleError * 4 + quietPenalty(runs, start, stop) * 0.15 });
171
+ }
172
+ }
173
+ pairs.sort((a, b) => a.error - b.error);
174
+ const used = new Set();
175
+ return pairs.filter((pair) => {
176
+ if (used.has(pair.columns))
177
+ return false;
178
+ used.add(pair.columns);
179
+ return used.size <= 3;
180
+ });
171
181
  }
172
-
173
182
  function pointAt(geometry, along, across) {
174
- return {
175
- x: geometry.along.x * along + geometry.across.x * across,
176
- y: geometry.along.y * along + geometry.across.y * across,
177
- };
183
+ return {
184
+ x: geometry.along.x * along + geometry.across.x * across,
185
+ y: geometry.along.y * along + geometry.across.y * across,
186
+ };
178
187
  }
179
-
180
188
  function scanHits(image, angle) {
181
- const geometry = scanGeometry(angle);
182
- const range = projectionRange(image, geometry.across);
183
- const hits = [];
184
- for (let across = Math.ceil(range.min); across <= Math.floor(range.max); across++) {
185
- const raw = runsInLine(image, geometry, across);
186
- let pairs = patternPairs(raw);
187
- if (!pairs.length) pairs = patternPairs(removeSinglePixelSpecks(raw));
188
- for (const pair of pairs) {
189
- hits.push({ across, left: pair.start.start, right: pair.stop.end,
190
- leftPoint: pointAt(geometry, pair.start.start, across),
191
- rightPoint: pointAt(geometry, pair.stop.end, across),
192
- scale: pair.scale, startScale: pair.startScale, stopScale: pair.stopScale,
193
- columns: pair.columns, width: pair.width, error: pair.error,
194
- geometry });
189
+ const geometry = scanGeometry(angle);
190
+ const range = projectionRange(image, geometry.across);
191
+ const hits = [];
192
+ for (let across = Math.ceil(range.min); across <= Math.floor(range.max); across++) {
193
+ const raw = runsInLine(image, geometry, across);
194
+ let pairs = patternPairs(raw);
195
+ if (!pairs.length)
196
+ pairs = patternPairs(removeSinglePixelSpecks(raw));
197
+ for (const pair of pairs) {
198
+ hits.push({ across, left: pair.start.start, right: pair.stop.end,
199
+ leftPoint: pointAt(geometry, pair.start.start, across),
200
+ rightPoint: pointAt(geometry, pair.stop.end, across),
201
+ scale: pair.scale, startScale: pair.startScale, stopScale: pair.stopScale,
202
+ columns: pair.columns, width: pair.width, error: pair.error,
203
+ geometry });
204
+ }
195
205
  }
196
- }
197
- return hits;
206
+ return hits;
198
207
  }
199
-
200
208
  function fittedLine(hits, key) {
201
- const meanX = hits.reduce((sum, hit) => sum + hit.across, 0) / hits.length;
202
- const meanY = hits.reduce((sum, hit) => sum + hit[key], 0) / hits.length;
203
- let covariance = 0, variance = 0;
204
- for (const hit of hits) {
205
- const delta = hit.across - meanX;
206
- covariance += delta * (hit[key] - meanY);
207
- variance += delta * delta;
208
- }
209
- const slope = variance ? covariance / variance : 0;
210
- return { slope, intercept: meanY - slope * meanX };
209
+ const meanX = hits.reduce((sum, hit) => sum + hit.across, 0) / hits.length;
210
+ const meanY = hits.reduce((sum, hit) => sum + hit[key], 0) / hits.length;
211
+ let covariance = 0, variance = 0;
212
+ for (const hit of hits) {
213
+ const delta = hit.across - meanX;
214
+ covariance += delta * (hit[key] - meanY);
215
+ variance += delta * delta;
216
+ }
217
+ const slope = variance ? covariance / variance : 0;
218
+ return { slope, intercept: meanY - slope * meanX };
211
219
  }
212
-
213
220
  function lineValue(line, at) {
214
- return line.intercept + line.slope * at;
221
+ return line.intercept + line.slope * at;
215
222
  }
216
-
217
223
  function finderExtent(image, hits, edgeKey, scaleKey, centreModules, fallback) {
218
- const geometry = hits[0].geometry;
219
- const edge = fittedLine(hits, edgeKey), scale = fittedLine(hits, scaleKey);
220
- const span = fallback.bottom - fallback.top;
221
- const projection = projectionRange(image, geometry.across);
222
- const margin = Math.max(6, span * 0.35, hits[0].scale * 4);
223
- const start = Math.max(Math.ceil(projection.min), Math.floor(fallback.top - margin));
224
- const end = Math.min(Math.floor(projection.max), Math.ceil(fallback.bottom + margin));
225
- const values = [];
226
- for (let across = start; across <= end; across++) {
227
- const localScale = Math.max(0.5, lineValue(scale, across));
228
- const centre = lineValue(edge, across) + localScale * centreModules;
229
- let dark = 0;
230
- for (const offset of [-0.75, 0, 0.75]) {
231
- const point = pointAt(geometry, centre + localScale * offset, across);
232
- if (image.get(Math.round(point.x), Math.round(point.y))) dark++;
224
+ const geometry = hits[0].geometry;
225
+ const edge = fittedLine(hits, edgeKey), scale = fittedLine(hits, scaleKey);
226
+ const span = fallback.bottom - fallback.top;
227
+ const projection = projectionRange(image, geometry.across);
228
+ const margin = Math.max(6, span * 0.35, hits[0].scale * 4);
229
+ const start = Math.max(Math.ceil(projection.min), Math.floor(fallback.top - margin));
230
+ const end = Math.min(Math.floor(projection.max), Math.ceil(fallback.bottom + margin));
231
+ const values = [];
232
+ for (let across = start; across <= end; across++) {
233
+ const localScale = Math.max(0.5, lineValue(scale, across));
234
+ const centre = lineValue(edge, across) + localScale * centreModules;
235
+ let dark = 0;
236
+ for (const offset of [-0.75, 0, 0.75]) {
237
+ const point = pointAt(geometry, centre + localScale * offset, across);
238
+ if (image.get(Math.round(point.x), Math.round(point.y)))
239
+ dark++;
240
+ }
241
+ values.push({ across, dark: dark >= 2 });
233
242
  }
234
- values.push({ across, dark: dark >= 2 });
235
- }
236
- const segments = [];
237
- let first = null, lastDark = null, gap = 0;
238
- for (const value of values) {
239
- if (value.dark) {
240
- if (first === null) first = value.across;
241
- lastDark = value.across; gap = 0;
242
- } else if (first !== null && ++gap > 2) {
243
- segments.push({ top: first, bottom: lastDark + 1 });
244
- first = null; lastDark = null; gap = 0;
243
+ const segments = [];
244
+ let first = null, lastDark = null, gap = 0;
245
+ for (const value of values) {
246
+ if (value.dark) {
247
+ if (first === null)
248
+ first = value.across;
249
+ lastDark = value.across;
250
+ gap = 0;
251
+ }
252
+ else if (first !== null && ++gap > 2) {
253
+ segments.push({ top: first, bottom: lastDark + 1 });
254
+ first = null;
255
+ lastDark = null;
256
+ gap = 0;
257
+ }
258
+ }
259
+ if (first !== null)
260
+ segments.push({ top: first, bottom: lastDark + 1 });
261
+ let best = null, bestScore = -Infinity;
262
+ for (const segment of segments) {
263
+ const overlap = Math.max(0, Math.min(segment.bottom, fallback.bottom) - Math.max(segment.top, fallback.top));
264
+ const score = overlap * 4 + (segment.bottom - segment.top) -
265
+ Math.abs((segment.top + segment.bottom) / 2 - (fallback.top + fallback.bottom) / 2);
266
+ if (overlap >= span * 0.55 && score > bestScore) {
267
+ best = segment;
268
+ bestScore = score;
269
+ }
245
270
  }
246
- }
247
- if (first !== null) segments.push({ top: first, bottom: lastDark + 1 });
248
- let best = null, bestScore = -Infinity;
249
- for (const segment of segments) {
250
- const overlap = Math.max(0, Math.min(segment.bottom, fallback.bottom) - Math.max(segment.top, fallback.top));
251
- const score = overlap * 4 + (segment.bottom - segment.top) -
252
- Math.abs((segment.top + segment.bottom) / 2 - (fallback.top + fallback.bottom) / 2);
253
- if (overlap >= span * 0.55 && score > bestScore) { best = segment; bestScore = score; }
254
- }
255
- return best ?? fallback;
271
+ return best ?? fallback;
256
272
  }
257
-
258
273
  function intersectBoundary(leftAcross, rightAcross, leftCentre, rightCentre, leftEdge, rightEdge) {
259
- const delta = rightCentre - leftCentre;
260
- if (Math.abs(delta) < 1e-6) return { left: leftAcross, right: rightAcross };
261
- const slope = (rightAcross - leftAcross) / delta;
262
- const intercept = leftAcross - slope * leftCentre;
263
- const atEdge = (edge, fallback) => {
264
- const denominator = 1 - slope * edge.slope;
265
- return Math.abs(denominator) < 1e-6 ? fallback :
266
- (slope * edge.intercept + intercept) / denominator;
267
- };
268
- return { left: atEdge(leftEdge, leftAcross), right: atEdge(rightEdge, rightAcross) };
274
+ const delta = rightCentre - leftCentre;
275
+ if (Math.abs(delta) < 1e-6)
276
+ return { left: leftAcross, right: rightAcross };
277
+ const slope = (rightAcross - leftAcross) / delta;
278
+ const intercept = leftAcross - slope * leftCentre;
279
+ const atEdge = (edge, fallback) => {
280
+ const denominator = 1 - slope * edge.slope;
281
+ return Math.abs(denominator) < 1e-6 ? fallback :
282
+ (slope * edge.intercept + intercept) / denominator;
283
+ };
284
+ return { left: atEdge(leftEdge, leftAcross), right: atEdge(rightEdge, rightAcross) };
269
285
  }
270
-
271
286
  function groupHits(hits, image) {
272
- const groups = [];
273
- for (const hit of hits.sort((a, b) => a.across - b.across || a.error - b.error)) {
274
- let best = null, bestDistance = Infinity;
287
+ const groups = [];
288
+ for (const hit of hits.sort((a, b) => a.across - b.across || a.error - b.error)) {
289
+ let best = null, bestDistance = Infinity;
290
+ for (const group of groups) {
291
+ const last = group.hits[group.hits.length - 1];
292
+ const gap = hit.across - last.across;
293
+ if (hit.columns !== last.columns || gap <= 0 || gap > Math.max(5, hit.scale * 3))
294
+ continue;
295
+ const scaleRatio = Math.max(hit.scale, last.scale) / Math.min(hit.scale, last.scale);
296
+ if (scaleRatio > 1.45)
297
+ continue;
298
+ const tolerance = Math.max(5, hit.scale * 4 + gap);
299
+ const distance = Math.abs(hit.left - last.left) + Math.abs(hit.right - last.right);
300
+ if (distance > tolerance * 2 || distance >= bestDistance)
301
+ continue;
302
+ best = group;
303
+ bestDistance = distance;
304
+ }
305
+ if (best)
306
+ best.hits.push(hit);
307
+ else
308
+ groups.push({ hits: [hit] });
309
+ }
310
+ const candidates = [];
275
311
  for (const group of groups) {
276
- const last = group.hits[group.hits.length - 1];
277
- const gap = hit.across - last.across;
278
- if (hit.columns !== last.columns || gap <= 0 || gap > Math.max(5, hit.scale * 3)) continue;
279
- const scaleRatio = Math.max(hit.scale, last.scale) / Math.min(hit.scale, last.scale);
280
- if (scaleRatio > 1.45) continue;
281
- const tolerance = Math.max(5, hit.scale * 4 + gap);
282
- const distance = Math.abs(hit.left - last.left) + Math.abs(hit.right - last.right);
283
- if (distance > tolerance * 2 || distance >= bestDistance) continue;
284
- best = group; bestDistance = distance;
312
+ const rows = group.hits;
313
+ const scale = rows.reduce((sum, hit) => sum + hit.scale, 0) / rows.length;
314
+ const top = rows[0].across, bottom = rows[rows.length - 1].across + 1;
315
+ const span = bottom - top;
316
+ if (rows.length < 4 || span < Math.max(6, scale * 4) || rows.length / span < 0.28)
317
+ continue;
318
+ const geometry = rows[0].geometry;
319
+ const left = fittedLine(rows, 'left'), right = fittedLine(rows, 'right');
320
+ const startScale = fittedLine(rows, 'startScale'), stopScale = fittedLine(rows, 'stopScale');
321
+ const fallback = { top, bottom };
322
+ const leftExtent = image ? finderExtent(image, rows, 'left', 'startScale', 4, fallback) : fallback;
323
+ const rightExtent = image ? finderExtent(image, rows, 'right', 'stopScale', -14.5, fallback) : fallback;
324
+ const boundary = (leftAcross, rightAcross) => intersectBoundary(leftAcross, rightAcross, lineValue(left, leftAcross) + lineValue(startScale, leftAcross) * 4, lineValue(right, rightAcross) - lineValue(stopScale, rightAcross) * 14.5, left, right);
325
+ const topBoundary = boundary(leftExtent.top, rightExtent.top);
326
+ const bottomBoundary = boundary(leftExtent.bottom, rightExtent.bottom);
327
+ candidates.push({
328
+ width: rows[0].width, scale,
329
+ corners: [
330
+ pointAt(geometry, lineValue(left, topBoundary.left), topBoundary.left),
331
+ pointAt(geometry, lineValue(right, topBoundary.right), topBoundary.right),
332
+ pointAt(geometry, lineValue(right, bottomBoundary.right), bottomBoundary.right),
333
+ pointAt(geometry, lineValue(left, bottomBoundary.left), bottomBoundary.left),
334
+ ],
335
+ score: rows.length / span * 8 + Math.log2(rows.length + 1) * 2 -
336
+ rows.reduce((sum, hit) => sum + hit.error, 0) / rows.length,
337
+ });
285
338
  }
286
- if (best) best.hits.push(hit); else groups.push({ hits: [hit] });
287
- }
288
- const candidates = [];
289
- for (const group of groups) {
290
- const rows = group.hits;
291
- const scale = rows.reduce((sum, hit) => sum + hit.scale, 0) / rows.length;
292
- const top = rows[0].across, bottom = rows[rows.length - 1].across + 1;
293
- const span = bottom - top;
294
- if (rows.length < 4 || span < Math.max(6, scale * 4) || rows.length / span < 0.28) continue;
295
- const geometry = rows[0].geometry;
296
- const left = fittedLine(rows, 'left'), right = fittedLine(rows, 'right');
297
- const startScale = fittedLine(rows, 'startScale'), stopScale = fittedLine(rows, 'stopScale');
298
- const fallback = { top, bottom };
299
- const leftExtent = image ? finderExtent(image, rows, 'left', 'startScale', 4, fallback) : fallback;
300
- const rightExtent = image ? finderExtent(image, rows, 'right', 'stopScale', -14.5, fallback) : fallback;
301
- const boundary = (leftAcross, rightAcross) => intersectBoundary(
302
- leftAcross,
303
- rightAcross,
304
- lineValue(left, leftAcross) + lineValue(startScale, leftAcross) * 4,
305
- lineValue(right, rightAcross) - lineValue(stopScale, rightAcross) * 14.5,
306
- left,
307
- right,
308
- );
309
- const topBoundary = boundary(leftExtent.top, rightExtent.top);
310
- const bottomBoundary = boundary(leftExtent.bottom, rightExtent.bottom);
311
- candidates.push({
312
- width: rows[0].width, scale,
313
- corners: [
314
- pointAt(geometry, lineValue(left, topBoundary.left), topBoundary.left),
315
- pointAt(geometry, lineValue(right, topBoundary.right), topBoundary.right),
316
- pointAt(geometry, lineValue(right, bottomBoundary.right), bottomBoundary.right),
317
- pointAt(geometry, lineValue(left, bottomBoundary.left), bottomBoundary.left),
318
- ],
319
- score: rows.length / span * 8 + Math.log2(rows.length + 1) * 2 -
320
- rows.reduce((sum, hit) => sum + hit.error, 0) / rows.length,
321
- });
322
- }
323
- return candidates.sort((a, b) => b.score - a.score);
339
+ return candidates.sort((a, b) => b.score - a.score);
324
340
  }
325
-
326
341
  function validQuadrilateral(value) {
327
- return Array.isArray(value) && value.length === 4 &&
328
- value.every((point) => Number.isFinite(point?.x) && Number.isFinite(point?.y));
342
+ return Array.isArray(value) && value.length === 4 &&
343
+ value.every((point) => Number.isFinite(point?.x) && Number.isFinite(point?.y));
329
344
  }
330
-
331
345
  function manualCandidate(corners) {
332
- const horizontal = (Math.hypot(corners[1].x - corners[0].x, corners[1].y - corners[0].y) +
333
- Math.hypot(corners[2].x - corners[3].x, corners[2].y - corners[3].y)) / 2;
334
- const vertical = (Math.hypot(corners[3].x - corners[0].x, corners[3].y - corners[0].y) +
335
- Math.hypot(corners[2].x - corners[1].x, corners[2].y - corners[1].y)) / 2;
336
- const out = [];
337
- for (let columns = 1; columns <= 30; columns++) {
338
- const width = 69 + columns * 17, scale = horizontal / width;
339
- if (scale < 0.65) continue;
340
- const moduleHeight = vertical / scale;
341
- let plausibility = Infinity;
342
- for (let rowHeight = 3; rowHeight <= 12; rowHeight++) {
343
- const rows = Math.round(moduleHeight / rowHeight);
344
- if (rows >= 3 && rows <= 90) plausibility = Math.min(plausibility,
345
- Math.abs(moduleHeight - rows * rowHeight) / rowHeight);
346
+ const horizontal = (Math.hypot(corners[1].x - corners[0].x, corners[1].y - corners[0].y) +
347
+ Math.hypot(corners[2].x - corners[3].x, corners[2].y - corners[3].y)) / 2;
348
+ const vertical = (Math.hypot(corners[3].x - corners[0].x, corners[3].y - corners[0].y) +
349
+ Math.hypot(corners[2].x - corners[1].x, corners[2].y - corners[1].y)) / 2;
350
+ const out = [];
351
+ for (let columns = 1; columns <= 30; columns++) {
352
+ const width = 69 + columns * 17, scale = horizontal / width;
353
+ if (scale < 0.65)
354
+ continue;
355
+ const moduleHeight = vertical / scale;
356
+ let plausibility = Infinity;
357
+ for (let rowHeight = 3; rowHeight <= 12; rowHeight++) {
358
+ const rows = Math.round(moduleHeight / rowHeight);
359
+ if (rows >= 3 && rows <= 90)
360
+ plausibility = Math.min(plausibility, Math.abs(moduleHeight - rows * rowHeight) / rowHeight);
361
+ }
362
+ out.push({ width, scale, corners, score: Number.isFinite(plausibility) ? 2 - plausibility : 0 });
346
363
  }
347
- out.push({ width, scale, corners, score: Number.isFinite(plausibility) ? 2 - plausibility : 0 });
348
- }
349
- return out.sort((a, b) => b.score - a.score);
364
+ return out.sort((a, b) => b.score - a.score);
350
365
  }
351
-
352
366
  function edgeLength(first, second) {
353
- return Math.hypot(second.x - first.x, second.y - first.y);
367
+ return Math.hypot(second.x - first.x, second.y - first.y);
354
368
  }
355
-
356
369
  function rowCandidates(candidate, options) {
357
- const vertical = (edgeLength(candidate.corners[0], candidate.corners[3]) +
358
- edgeLength(candidate.corners[1], candidate.corners[2])) / 2;
359
- const allowedHeights = Number.isInteger(options.rowHeight) ? [options.rowHeight] :
360
- Array.from({ length: 10 }, (_, index) => index + 3);
361
- const rows = [];
362
- for (let value = 3; value <= 90; value++) {
363
- let score = Infinity;
364
- for (const rowHeight of allowedHeights) {
365
- score = Math.min(score, Math.abs(Math.log(vertical / (candidate.scale * value * rowHeight))));
370
+ const vertical = (edgeLength(candidate.corners[0], candidate.corners[3]) +
371
+ edgeLength(candidate.corners[1], candidate.corners[2])) / 2;
372
+ const allowedHeights = Number.isInteger(options.rowHeight) ? [options.rowHeight] :
373
+ Array.from({ length: 10 }, (_, index) => index + 3);
374
+ const rows = [];
375
+ for (let value = 3; value <= 90; value++) {
376
+ let score = Infinity;
377
+ for (const rowHeight of allowedHeights) {
378
+ score = Math.min(score, Math.abs(Math.log(vertical / (candidate.scale * value * rowHeight))));
379
+ }
380
+ rows.push({ value, score });
366
381
  }
367
- rows.push({ value, score });
368
- }
369
- return rows.sort((a, b) => a.score - b.score).map((entry) => entry.value);
382
+ return rows.sort((a, b) => a.score - b.score).map((entry) => entry.value);
370
383
  }
371
-
372
384
  function shiftedCandidate(candidate, amount) {
373
- if (!amount) return candidate;
374
- const topDx = candidate.corners[3].x - candidate.corners[0].x;
375
- const topDy = candidate.corners[3].y - candidate.corners[0].y;
376
- const bottomDx = candidate.corners[2].x - candidate.corners[1].x;
377
- const bottomDy = candidate.corners[2].y - candidate.corners[1].y;
378
- const topLength = Math.hypot(topDx, topDy) || 1;
379
- const bottomLength = Math.hypot(bottomDx, bottomDy) || 1;
380
- return { ...candidate, corners: [
381
- { x: candidate.corners[0].x - topDx / topLength * amount, y: candidate.corners[0].y - topDy / topLength * amount },
382
- { x: candidate.corners[1].x - bottomDx / bottomLength * amount, y: candidate.corners[1].y - bottomDy / bottomLength * amount },
383
- { x: candidate.corners[2].x + bottomDx / bottomLength * amount, y: candidate.corners[2].y + bottomDy / bottomLength * amount },
384
- { x: candidate.corners[3].x + topDx / topLength * amount, y: candidate.corners[3].y + topDy / topLength * amount },
385
- ] };
385
+ if (!amount)
386
+ return candidate;
387
+ const topDx = candidate.corners[3].x - candidate.corners[0].x;
388
+ const topDy = candidate.corners[3].y - candidate.corners[0].y;
389
+ const bottomDx = candidate.corners[2].x - candidate.corners[1].x;
390
+ const bottomDy = candidate.corners[2].y - candidate.corners[1].y;
391
+ const topLength = Math.hypot(topDx, topDy) || 1;
392
+ const bottomLength = Math.hypot(bottomDx, bottomDy) || 1;
393
+ return { ...candidate, corners: [
394
+ { x: candidate.corners[0].x - topDx / topLength * amount, y: candidate.corners[0].y - topDy / topLength * amount },
395
+ { x: candidate.corners[1].x - bottomDx / bottomLength * amount, y: candidate.corners[1].y - bottomDy / bottomLength * amount },
396
+ { x: candidate.corners[2].x + bottomDx / bottomLength * amount, y: candidate.corners[2].y + bottomDy / bottomLength * amount },
397
+ { x: candidate.corners[3].x + topDx / topLength * amount, y: candidate.corners[3].y + topDy / topLength * amount },
398
+ ] };
386
399
  }
387
-
388
400
  function canonicalRows(matrix) {
389
- const rowHeight = 3;
390
- const out = new BitMatrix(matrix.width, matrix.height * rowHeight);
391
- for (let y = 0; y < matrix.height; y++) for (let x = 0; x < matrix.width; x++) {
392
- if (matrix.get(x, y)) out.setRegion(x, y * rowHeight, 1, rowHeight);
393
- }
394
- return out;
401
+ const rowHeight = 3;
402
+ const out = new BitMatrix(matrix.width, matrix.height * rowHeight);
403
+ for (let y = 0; y < matrix.height; y++)
404
+ for (let x = 0; x < matrix.width; x++) {
405
+ if (matrix.get(x, y))
406
+ out.setRegion(x, y * rowHeight, 1, rowHeight);
407
+ }
408
+ return out;
395
409
  }
396
-
397
410
  function sampleCandidate(image, candidate, options) {
398
- for (const edgeShift of [0, candidate.scale * 0.35, -candidate.scale * 0.25]) {
399
- const geometry = shiftedCandidate(candidate, edgeShift);
400
- for (const rows of rowCandidates(geometry, options)) {
401
- const transform = PerspectiveTransform.quadToQuad(0, 0, geometry.width, 0, geometry.width, rows, 0, rows,
402
- geometry.corners[0].x, geometry.corners[0].y, geometry.corners[1].x, geometry.corners[1].y,
403
- geometry.corners[2].x, geometry.corners[2].y, geometry.corners[3].x, geometry.corners[3].y);
404
- for (const voting of [false, true]) {
405
- let matrix;
406
- try { matrix = voting ? sampleGridVoting(image, geometry.width, rows, transform) : sampleGrid(image, geometry.width, rows, transform); }
407
- catch { continue; }
408
- try {
409
- const result = decodePDF417(matrix, { ...options, rowHeight: 1 });
410
- return { matrix: canonicalRows(matrix), result, corners: geometry.corners };
411
+ for (const edgeShift of [0, candidate.scale * 0.35, -candidate.scale * 0.25]) {
412
+ const geometry = shiftedCandidate(candidate, edgeShift);
413
+ for (const rows of rowCandidates(geometry, options)) {
414
+ const transform = PerspectiveTransform.quadToQuad(0, 0, geometry.width, 0, geometry.width, rows, 0, rows, geometry.corners[0].x, geometry.corners[0].y, geometry.corners[1].x, geometry.corners[1].y, geometry.corners[2].x, geometry.corners[2].y, geometry.corners[3].x, geometry.corners[3].y);
415
+ for (const voting of [false, true]) {
416
+ let matrix;
417
+ try {
418
+ matrix = voting ? sampleGridVoting(image, geometry.width, rows, transform) : sampleGrid(image, geometry.width, rows, transform);
419
+ }
420
+ catch {
421
+ continue;
422
+ }
423
+ try {
424
+ const result = decodePDF417(matrix, { ...options, rowHeight: 1 });
425
+ return { matrix: canonicalRows(matrix), result, corners: geometry.corners };
426
+ }
427
+ catch { /* Try the next geometry. */ }
428
+ }
411
429
  }
412
- catch { /* Try the next geometry. */ }
413
- }
414
430
  }
415
- }
416
- return null;
431
+ return null;
417
432
  }
418
-
419
433
  function automaticCandidates(image) {
420
- const out = [];
421
- for (const angle of SCAN_ANGLES) {
422
- out.push(...groupHits(scanHits(image, angle), image));
423
- if (out.length) break;
424
- }
425
- return out;
434
+ const out = [];
435
+ for (const angle of SCAN_ANGLES) {
436
+ out.push(...groupHits(scanHits(image, angle), image));
437
+ if (out.length)
438
+ break;
439
+ }
440
+ return out;
426
441
  }
427
-
428
442
  function detectInOrientation(image, options, supplied) {
429
- for (const candidate of [...supplied, ...automaticCandidates(image)]) {
430
- const decoded = sampleCandidate(image, candidate, options);
431
- if (decoded) return { candidate, decoded };
432
- }
433
- if (supplied.length) return null;
434
- for (const angle of SCAN_ANGLES.slice(1)) {
435
- const candidates = groupHits(scanHits(image, angle), image);
436
- for (const candidate of candidates) {
437
- const decoded = sampleCandidate(image, candidate, options);
438
- if (decoded) return { candidate, decoded };
443
+ for (const candidate of [...supplied, ...automaticCandidates(image)]) {
444
+ const decoded = sampleCandidate(image, candidate, options);
445
+ if (decoded)
446
+ return { candidate, decoded };
439
447
  }
440
- }
441
- return null;
448
+ if (supplied.length)
449
+ return null;
450
+ for (const angle of SCAN_ANGLES.slice(1)) {
451
+ const candidates = groupHits(scanHits(image, angle), image);
452
+ for (const candidate of candidates) {
453
+ const decoded = sampleCandidate(image, candidate, options);
454
+ if (decoded)
455
+ return { candidate, decoded };
456
+ }
457
+ }
458
+ return null;
442
459
  }
443
-
444
460
  /*
445
461
  * Locate a binarized raster symbol from its repeated start and stop patterns.
446
462
  * The detector estimates a projective quadrilateral; grayscale binarization
447
463
  * remains the caller's responsibility.
448
464
  */
449
465
  export function detectPDF417(binaryImage, options = {}) {
450
- if (!binaryImage?.width || !binaryImage?.height || typeof binaryImage.get !== 'function') return null;
451
- let oriented = binaryImage;
452
- let toOriginal = (point) => ({ x: point.x, y: point.y });
453
- for (let turns = 0; turns < 4; turns++) {
454
- const supplied = turns === 0 && validQuadrilateral(options.quadrilateral)
455
- ? manualCandidate(options.quadrilateral.map(({ x, y }) => ({ x, y }))) : [];
456
- const found = detectInOrientation(oriented, options, supplied);
457
- if (found) {
458
- return { matrix: found.decoded.matrix, rotation: turns * 90,
459
- corners: found.decoded.corners.map(toOriginal), ...found.decoded.result };
466
+ if (!binaryImage?.width || !binaryImage?.height || typeof binaryImage.get !== 'function')
467
+ return null;
468
+ let oriented = binaryImage;
469
+ let toOriginal = (point) => ({ x: point.x, y: point.y });
470
+ for (let turns = 0; turns < 4; turns++) {
471
+ const supplied = turns === 0 && validQuadrilateral(options.quadrilateral)
472
+ ? manualCandidate(options.quadrilateral.map(({ x, y }) => ({ x, y }))) : [];
473
+ const found = detectInOrientation(oriented, options, supplied);
474
+ if (found) {
475
+ return { matrix: found.decoded.matrix, rotation: turns * 90,
476
+ corners: found.decoded.corners.map(toOriginal), ...found.decoded.result };
477
+ }
478
+ const previous = oriented, previousToOriginal = toOriginal;
479
+ oriented = rotateClockwise(previous);
480
+ toOriginal = (point) => previousToOriginal({ x: point.y, y: previous.height - point.x });
460
481
  }
461
- const previous = oriented, previousToOriginal = toOriginal;
462
- oriented = rotateClockwise(previous);
463
- toOriginal = (point) => previousToOriginal({ x: point.y, y: previous.height - point.x });
464
- }
465
- return null;
482
+ return null;
466
483
  }
467
-
468
484
  export function detectAndDecodePDF417(binaryImage, options = {}) { return detectPDF417(binaryImage, options); }