@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,7 +27,6 @@
27
27
  *
28
28
  * Original work. No code from any other barcode implementation.
29
29
  */
30
-
31
30
  /**
32
31
  * Sythos Canvas QR profile encoder.
33
32
  *
@@ -38,16 +37,9 @@
38
37
  *
39
38
  * @module frameqr/encoder
40
39
  */
41
-
42
40
  import { EncodeError } from '../core/errors.js';
43
41
  import { encodeQR } from '../qr/encoder.js';
44
- import {
45
- FRAMEQR_PROFILE,
46
- canvasModules,
47
- normalizeCanvasSpec,
48
- validateCanvasSpec,
49
- } from './tables.js';
50
-
42
+ import { FRAMEQR_PROFILE, canvasModules, normalizeCanvasSpec, validateCanvasSpec, } from './tables.js';
51
43
  /**
52
44
  * @typedef {object} FrameQrEncodeOptions
53
45
  * @property {'H'} [ecc] The profile always uses QR error correction H.
@@ -57,15 +49,13 @@ import {
57
49
  * @property {boolean} [kanji] Allow QR kanji mode.
58
50
  * @property {object} [canvas] Profile artwork reservation.
59
51
  */
60
-
61
52
  function versionFor(matrix) {
62
- return (matrix.width - 17) / 4;
53
+ return (matrix.width - 17) / 4;
63
54
  }
64
-
65
55
  function clearCanvas(matrix, modules) {
66
- for (const [x, y] of modules) matrix.unset(x, y);
56
+ for (const [x, y] of modules)
57
+ matrix.unset(x, y);
67
58
  }
68
-
69
59
  /**
70
60
  * Encode a QR Code with a conservative artwork canvas according to the
71
61
  * non-certified Sythos Canvas QR profile.
@@ -83,74 +73,71 @@ function clearCanvas(matrix, modules) {
83
73
  * cannot safely fit the selected QR version.
84
74
  */
85
75
  export function encodeFrameQR(text, options = {}) {
86
- if (typeof text !== 'string') {
87
- throw new EncodeError('Sythos Canvas QR: text must be a string');
88
- }
89
- if (options.ecc !== undefined && options.ecc !== 'H') {
90
- throw new EncodeError('Sythos Canvas QR: ecc is fixed to H for this profile');
91
- }
92
-
93
- const qrOptions = {
94
- mask: options.mask,
95
- charset: options.charset,
96
- kanji: options.kanji,
97
- ecc: 'H',
98
- };
99
- for (const key of Object.keys(qrOptions)) {
100
- if (qrOptions[key] === undefined) delete qrOptions[key];
101
- }
102
-
103
- const versions = options.version === undefined
104
- ? Array.from({ length: 40 }, (_, index) => index + 1)
105
- : [options.version];
106
- let capacityError = null;
107
- let unsafeAnalysis = null;
108
- let selected = null;
109
-
110
- for (const version of versions) {
111
- let matrix;
112
- try {
113
- matrix = encodeQR(text, { ...qrOptions, version });
114
- } catch (error) {
115
- capacityError = error;
116
- continue;
76
+ if (typeof text !== 'string') {
77
+ throw new EncodeError('Sythos Canvas QR: text must be a string');
117
78
  }
118
-
119
- let canvas;
120
- let analysis;
121
- try {
122
- canvas = normalizeCanvasSpec(matrix.width, options.canvas);
123
- analysis = validateCanvasSpec(versionFor(matrix), canvas);
124
- } catch (error) {
125
- if (error instanceof EncodeError) throw error;
126
- throw new EncodeError(`Sythos Canvas QR: invalid canvas: ${error.message}`);
79
+ if (options.ecc !== undefined && options.ecc !== 'H') {
80
+ throw new EncodeError('Sythos Canvas QR: ecc is fixed to H for this profile');
127
81
  }
128
- if (!analysis.safe) {
129
- unsafeAnalysis = analysis;
130
- continue;
82
+ const qrOptions = {
83
+ mask: options.mask,
84
+ charset: options.charset,
85
+ kanji: options.kanji,
86
+ ecc: 'H',
87
+ };
88
+ for (const key of Object.keys(qrOptions)) {
89
+ if (qrOptions[key] === undefined)
90
+ delete qrOptions[key];
131
91
  }
132
- selected = { matrix, canvas };
133
- break;
134
- }
135
-
136
- if (!selected) {
137
- if (unsafeAnalysis) {
138
- throw new EncodeError(
139
- 'Sythos Canvas QR: canvas is not safe for the selected QR version; ' +
140
- `it touches ${unsafeAnalysis.touchedCodewordCount} codewords and has ` +
141
- `a per-block correction budget of ${unsafeAnalysis.correctionBudgetPerBlock}`
142
- );
92
+ const versions = options.version === undefined
93
+ ? Array.from({ length: 40 }, (_, index) => index + 1)
94
+ : [options.version];
95
+ let capacityError = null;
96
+ let unsafeAnalysis = null;
97
+ let selected = null;
98
+ for (const version of versions) {
99
+ let matrix;
100
+ try {
101
+ matrix = encodeQR(text, { ...qrOptions, version });
102
+ }
103
+ catch (error) {
104
+ capacityError = error;
105
+ continue;
106
+ }
107
+ let canvas;
108
+ let analysis;
109
+ try {
110
+ canvas = normalizeCanvasSpec(matrix.width, options.canvas);
111
+ analysis = validateCanvasSpec(versionFor(matrix), canvas);
112
+ }
113
+ catch (error) {
114
+ if (error instanceof EncodeError)
115
+ throw error;
116
+ throw new EncodeError(`Sythos Canvas QR: invalid canvas: ${error.message}`);
117
+ }
118
+ if (!analysis.safe) {
119
+ unsafeAnalysis = analysis;
120
+ continue;
121
+ }
122
+ selected = { matrix, canvas };
123
+ break;
143
124
  }
144
- if (capacityError) throw capacityError;
145
- throw new EncodeError('Sythos Canvas QR: unable to select a QR version');
146
- }
147
-
148
- const { matrix, canvas } = selected;
149
- clearCanvas(matrix, canvasModules(matrix.width, canvas));
150
- matrix.frameqr = {
151
- profile: FRAMEQR_PROFILE.id,
152
- certified: false,
153
- canvas,
154
- };
155
- return matrix;
125
+ if (!selected) {
126
+ if (unsafeAnalysis) {
127
+ throw new EncodeError('Sythos Canvas QR: canvas is not safe for the selected QR version; ' +
128
+ `it touches ${unsafeAnalysis.touchedCodewordCount} codewords and has ` +
129
+ `a per-block correction budget of ${unsafeAnalysis.correctionBudgetPerBlock}`);
130
+ }
131
+ if (capacityError)
132
+ throw capacityError;
133
+ throw new EncodeError('Sythos Canvas QR: unable to select a QR version');
134
+ }
135
+ const { matrix, canvas } = selected;
136
+ clearCanvas(matrix, canvasModules(matrix.width, canvas));
137
+ matrix.frameqr = {
138
+ profile: FRAMEQR_PROFILE.id,
139
+ certified: false,
140
+ canvas,
141
+ };
142
+ return matrix;
156
143
  }
@@ -27,16 +27,7 @@
27
27
  *
28
28
  * Original work. No code from any other barcode implementation.
29
29
  */
30
-
31
30
  export { encodeFrameQR } from './encoder.js';
32
31
  export { decodeFrameQR } from './decoder.js';
33
32
  export { detectFrameQR, detectAndDecodeFrameQR } from './detector.js';
34
- export {
35
- FRAMEQR_PROFILE,
36
- FRAMEQR_CANVAS_SHAPES,
37
- canvasModules,
38
- normalizeCanvasSpec,
39
- analyzeCanvasDamage,
40
- validateCanvasSpec,
41
- validateFrameQrTables,
42
- } from './tables.js';
33
+ export { FRAMEQR_PROFILE, FRAMEQR_CANVAS_SHAPES, canvasModules, normalizeCanvasSpec, analyzeCanvasDamage, validateCanvasSpec, validateFrameQrTables, } from './tables.js';
@@ -27,7 +27,6 @@
27
27
  *
28
28
  * Original work. No code from any other barcode implementation.
29
29
  */
30
-
31
30
  /**
32
31
  * Structural contract for the Sythos Canvas QR profile.
33
32
  *
@@ -47,40 +46,30 @@
47
46
  *
48
47
  * @module frameqr/tables
49
48
  */
50
-
51
- import {
52
- blockLayout,
53
- dataModuleOrder,
54
- reservedModules,
55
- versionSize,
56
- } from '../qr/tables.js';
57
-
49
+ import { blockLayout, dataModuleOrder, reservedModules, versionSize, } from '../qr/tables.js';
58
50
  /** Public identity and compatibility boundary of the implementable profile. */
59
51
  export const FRAMEQR_PROFILE = Object.freeze({
60
- id: 'sythos-canvas-qr/1',
61
- name: 'Sythos Canvas QR profile',
62
- certified: false,
63
- densoFrameQrCompatible: false,
64
- baseSymbology: 'QR Code Model 2',
65
- requiredEcc: 'H',
66
- standard: 'ISO/IEC 18004 QR Code baseline',
52
+ id: 'sythos-canvas-qr/1',
53
+ name: 'Sythos Canvas QR profile',
54
+ certified: false,
55
+ densoFrameQrCompatible: false,
56
+ baseSymbology: 'QR Code Model 2',
57
+ requiredEcc: 'H',
58
+ standard: 'ISO/IEC 18004 QR Code baseline',
67
59
  });
68
-
69
60
  /** Canvas shapes whose module membership is fully deterministic. */
70
61
  export const FRAMEQR_CANVAS_SHAPES = Object.freeze(['square', 'circle', 'diamond']);
71
-
72
62
  function oddAtMost(value, maximum) {
73
- let n = Math.max(1, Math.min(maximum, Math.floor(value)));
74
- if ((n & 1) === 0) n--;
75
- return Math.max(1, n);
63
+ let n = Math.max(1, Math.min(maximum, Math.floor(value)));
64
+ if ((n & 1) === 0)
65
+ n--;
66
+ return Math.max(1, n);
76
67
  }
77
-
78
68
  function assertSymbolSize(symbolSize) {
79
- if (!Number.isInteger(symbolSize) || symbolSize < 21 || symbolSize > 177 || (symbolSize - 17) % 4 !== 0) {
80
- throw new RangeError(`Frame QR profile: ${symbolSize} is not a QR Model 2 symbol size`);
81
- }
69
+ if (!Number.isInteger(symbolSize) || symbolSize < 21 || symbolSize > 177 || (symbolSize - 17) % 4 !== 0) {
70
+ throw new RangeError(`Frame QR profile: ${symbolSize} is not a QR Model 2 symbol size`);
71
+ }
82
72
  }
83
-
84
73
  /**
85
74
  * Canonicalise a canvas request.
86
75
  *
@@ -93,44 +82,37 @@ function assertSymbolSize(symbolSize) {
93
82
  * @returns {{shape:string,centerX:number,centerY:number,width:number,height:number,angle:number}}
94
83
  */
95
84
  export function normalizeCanvasSpec(symbolSize, canvas = {}) {
96
- assertSymbolSize(symbolSize);
97
- if (canvas === null || typeof canvas !== 'object' || Array.isArray(canvas)) {
98
- throw new TypeError('Frame QR profile: canvas must be an object');
99
- }
100
-
101
- const shape = String(canvas.shape ?? 'square').toLowerCase();
102
- if (!FRAMEQR_CANVAS_SHAPES.includes(shape)) {
103
- throw new RangeError(`Frame QR profile: unsupported canvas shape "${shape}"`);
104
- }
105
-
106
- const defaultSize = oddAtMost(Math.max(3, Math.round(symbolSize * 0.17)), symbolSize - 16);
107
- const requestedSize = canvas.size;
108
- const requestedWidth = canvas.width ?? requestedSize ?? defaultSize;
109
- const requestedHeight = canvas.height ?? requestedSize ?? defaultSize;
110
- const maximumDimension = symbolSize - 16;
111
- for (const [name, value] of [['width', requestedWidth], ['height', requestedHeight]]) {
112
- if (!Number.isFinite(Number(value)) || Number(value) < 1 || Number(value) > maximumDimension) {
113
- throw new RangeError(
114
- `Frame QR profile: canvas ${name} must be between 1 and ${maximumDimension} modules`
115
- );
85
+ assertSymbolSize(symbolSize);
86
+ if (canvas === null || typeof canvas !== 'object' || Array.isArray(canvas)) {
87
+ throw new TypeError('Frame QR profile: canvas must be an object');
88
+ }
89
+ const shape = String(canvas.shape ?? 'square').toLowerCase();
90
+ if (!FRAMEQR_CANVAS_SHAPES.includes(shape)) {
91
+ throw new RangeError(`Frame QR profile: unsupported canvas shape "${shape}"`);
92
+ }
93
+ const defaultSize = oddAtMost(Math.max(3, Math.round(symbolSize * 0.17)), symbolSize - 16);
94
+ const requestedSize = canvas.size;
95
+ const requestedWidth = canvas.width ?? requestedSize ?? defaultSize;
96
+ const requestedHeight = canvas.height ?? requestedSize ?? defaultSize;
97
+ const maximumDimension = symbolSize - 16;
98
+ for (const [name, value] of [['width', requestedWidth], ['height', requestedHeight]]) {
99
+ if (!Number.isFinite(Number(value)) || Number(value) < 1 || Number(value) > maximumDimension) {
100
+ throw new RangeError(`Frame QR profile: canvas ${name} must be between 1 and ${maximumDimension} modules`);
101
+ }
116
102
  }
117
- }
118
- const width = oddAtMost(Number(requestedWidth), maximumDimension);
119
- const height = oddAtMost(Number(requestedHeight), maximumDimension);
120
- const centerX = Math.round(canvas.centerX ?? (symbolSize - 1) / 2);
121
- const centerY = Math.round(canvas.centerY ?? (symbolSize - 1) / 2);
122
- const angle = ((Number(canvas.angle ?? 0) % 360) + 360) % 360;
123
-
124
- if (![0, 90, 180, 270].includes(angle)) {
125
- throw new RangeError('Frame QR profile: angle must be 0, 90, 180 or 270 degrees');
126
- }
127
- if (centerX < 8 || centerY < 8 || centerX >= symbolSize - 8 || centerY >= symbolSize - 8) {
128
- throw new RangeError('Frame QR profile: canvas centre must remain inside the finder-pattern boundary');
129
- }
130
-
131
- return { shape, centerX, centerY, width, height, angle };
103
+ const width = oddAtMost(Number(requestedWidth), maximumDimension);
104
+ const height = oddAtMost(Number(requestedHeight), maximumDimension);
105
+ const centerX = Math.round(canvas.centerX ?? (symbolSize - 1) / 2);
106
+ const centerY = Math.round(canvas.centerY ?? (symbolSize - 1) / 2);
107
+ const angle = ((Number(canvas.angle ?? 0) % 360) + 360) % 360;
108
+ if (![0, 90, 180, 270].includes(angle)) {
109
+ throw new RangeError('Frame QR profile: angle must be 0, 90, 180 or 270 degrees');
110
+ }
111
+ if (centerX < 8 || centerY < 8 || centerX >= symbolSize - 8 || centerY >= symbolSize - 8) {
112
+ throw new RangeError('Frame QR profile: canvas centre must remain inside the finder-pattern boundary');
113
+ }
114
+ return { shape, centerX, centerY, width, height, angle };
132
115
  }
133
-
134
116
  /**
135
117
  * Enumerate canvas modules, including any overlaps with QR function modules.
136
118
  * A conforming encoder rejects such overlaps rather than damaging function
@@ -141,50 +123,52 @@ export function normalizeCanvasSpec(symbolSize, canvas = {}) {
141
123
  * @returns {Array<[number, number]>}
142
124
  */
143
125
  export function canvasModules(symbolSize, canvas = {}) {
144
- const spec = normalizeCanvasSpec(symbolSize, canvas);
145
- const rotate = spec.angle === 90 || spec.angle === 270;
146
- const width = rotate ? spec.height : spec.width;
147
- const height = rotate ? spec.width : spec.height;
148
- const halfWidth = (width - 1) / 2;
149
- const halfHeight = (height - 1) / 2;
150
- const modules = [];
151
-
152
- for (let dy = -halfHeight; dy <= halfHeight; dy++) {
153
- for (let dx = -halfWidth; dx <= halfWidth; dx++) {
154
- const nx = halfWidth === 0 ? 0 : dx / halfWidth;
155
- const ny = halfHeight === 0 ? 0 : dy / halfHeight;
156
- let inside;
157
- if (spec.shape === 'circle') inside = nx * nx + ny * ny <= 1 + Number.EPSILON;
158
- else if (spec.shape === 'diamond') inside = Math.abs(nx) + Math.abs(ny) <= 1 + Number.EPSILON;
159
- else inside = true;
160
- if (inside) modules.push([spec.centerX + dx, spec.centerY + dy]);
126
+ const spec = normalizeCanvasSpec(symbolSize, canvas);
127
+ const rotate = spec.angle === 90 || spec.angle === 270;
128
+ const width = rotate ? spec.height : spec.width;
129
+ const height = rotate ? spec.width : spec.height;
130
+ const halfWidth = (width - 1) / 2;
131
+ const halfHeight = (height - 1) / 2;
132
+ const modules = [];
133
+ for (let dy = -halfHeight; dy <= halfHeight; dy++) {
134
+ for (let dx = -halfWidth; dx <= halfWidth; dx++) {
135
+ const nx = halfWidth === 0 ? 0 : dx / halfWidth;
136
+ const ny = halfHeight === 0 ? 0 : dy / halfHeight;
137
+ let inside;
138
+ if (spec.shape === 'circle')
139
+ inside = nx * nx + ny * ny <= 1 + Number.EPSILON;
140
+ else if (spec.shape === 'diamond')
141
+ inside = Math.abs(nx) + Math.abs(ny) <= 1 + Number.EPSILON;
142
+ else
143
+ inside = true;
144
+ if (inside)
145
+ modules.push([spec.centerX + dx, spec.centerY + dy]);
146
+ }
161
147
  }
162
- }
163
- return modules;
148
+ return modules;
164
149
  }
165
-
166
150
  /** Build the interleaved-codeword to RS-block map used by QR Model 2. */
167
151
  function codewordBlockMap(layout) {
168
- const dataCounts = new Array(layout.blockCount);
169
- for (let block = 0; block < layout.blockCount; block++) {
170
- dataCounts[block] = block < layout.group1Blocks
171
- ? layout.group1DataCount
172
- : layout.group2DataCount;
173
- }
174
-
175
- const map = [];
176
- const maxData = Math.max(...dataCounts);
177
- for (let i = 0; i < maxData; i++) {
152
+ const dataCounts = new Array(layout.blockCount);
178
153
  for (let block = 0; block < layout.blockCount; block++) {
179
- if (i < dataCounts[block]) map.push(block);
154
+ dataCounts[block] = block < layout.group1Blocks
155
+ ? layout.group1DataCount
156
+ : layout.group2DataCount;
157
+ }
158
+ const map = [];
159
+ const maxData = Math.max(...dataCounts);
160
+ for (let i = 0; i < maxData; i++) {
161
+ for (let block = 0; block < layout.blockCount; block++) {
162
+ if (i < dataCounts[block])
163
+ map.push(block);
164
+ }
180
165
  }
181
- }
182
- for (let i = 0; i < layout.eccPerBlock; i++) {
183
- for (let block = 0; block < layout.blockCount; block++) map.push(block);
184
- }
185
- return map;
166
+ for (let i = 0; i < layout.eccPerBlock; i++) {
167
+ for (let block = 0; block < layout.blockCount; block++)
168
+ map.push(block);
169
+ }
170
+ return map;
186
171
  }
187
-
188
172
  /**
189
173
  * Calculate worst-case QR codeword damage caused by a canvas.
190
174
  * A codeword is counted if any of its modules is touched. This is conservative:
@@ -195,76 +179,74 @@ function codewordBlockMap(layout) {
195
179
  * @param {object} [canvas]
196
180
  */
197
181
  export function analyzeCanvasDamage(version, canvas = {}) {
198
- if (!Number.isInteger(version) || version < 1 || version > 40) {
199
- throw new RangeError(`Frame QR profile: version must be an integer 1-40, got ${version}`);
200
- }
201
- const symbolSize = versionSize(version);
202
- const spec = normalizeCanvasSpec(symbolSize, canvas);
203
- const modules = canvasModules(symbolSize, spec);
204
- const reserved = reservedModules(version);
205
- const reservedOverlaps = modules.filter(([x, y]) => reserved.get(x, y));
206
-
207
- const moduleKeys = new Set(modules.map(([x, y]) => `${x},${y}`));
208
- const order = dataModuleOrder(version);
209
- const touchedCodewords = new Set();
210
- for (let bit = 0; bit < order.length / 2; bit++) {
211
- if (moduleKeys.has(`${order[bit * 2]},${order[bit * 2 + 1]}`)) touchedCodewords.add(bit >> 3);
212
- }
213
-
214
- const layout = blockLayout(version, 'H');
215
- const blockMap = codewordBlockMap(layout);
216
- const touchedByBlockSets = Array.from({ length: layout.blockCount }, () => new Set());
217
- for (const codeword of touchedCodewords) {
218
- const block = blockMap[codeword];
219
- if (block !== undefined) touchedByBlockSets[block].add(codeword);
220
- }
221
- const touchedCodewordsByBlock = touchedByBlockSets.map((set) => set.size);
222
- const correctionBudgetPerBlock = Math.floor(layout.eccPerBlock / 2);
223
- const safe = reservedOverlaps.length === 0 &&
224
- touchedCodewordsByBlock.every((count) => count <= correctionBudgetPerBlock);
225
-
226
- return {
227
- profile: FRAMEQR_PROFILE.id,
228
- certified: false,
229
- version,
230
- symbolSize,
231
- canvas: spec,
232
- canvasModuleCount: modules.length,
233
- reservedOverlaps,
234
- touchedCodewordCount: touchedCodewords.size,
235
- touchedCodewordsByBlock,
236
- correctionBudgetPerBlock,
237
- safe,
238
- };
182
+ if (!Number.isInteger(version) || version < 1 || version > 40) {
183
+ throw new RangeError(`Frame QR profile: version must be an integer 1-40, got ${version}`);
184
+ }
185
+ const symbolSize = versionSize(version);
186
+ const spec = normalizeCanvasSpec(symbolSize, canvas);
187
+ const modules = canvasModules(symbolSize, spec);
188
+ const reserved = reservedModules(version);
189
+ const reservedOverlaps = modules.filter(([x, y]) => reserved.get(x, y));
190
+ const moduleKeys = new Set(modules.map(([x, y]) => `${x},${y}`));
191
+ const order = dataModuleOrder(version);
192
+ const touchedCodewords = new Set();
193
+ for (let bit = 0; bit < order.length / 2; bit++) {
194
+ if (moduleKeys.has(`${order[bit * 2]},${order[bit * 2 + 1]}`))
195
+ touchedCodewords.add(bit >> 3);
196
+ }
197
+ const layout = blockLayout(version, 'H');
198
+ const blockMap = codewordBlockMap(layout);
199
+ const touchedByBlockSets = Array.from({ length: layout.blockCount }, () => new Set());
200
+ for (const codeword of touchedCodewords) {
201
+ const block = blockMap[codeword];
202
+ if (block !== undefined)
203
+ touchedByBlockSets[block].add(codeword);
204
+ }
205
+ const touchedCodewordsByBlock = touchedByBlockSets.map((set) => set.size);
206
+ const correctionBudgetPerBlock = Math.floor(layout.eccPerBlock / 2);
207
+ const safe = reservedOverlaps.length === 0 &&
208
+ touchedCodewordsByBlock.every((count) => count <= correctionBudgetPerBlock);
209
+ return {
210
+ profile: FRAMEQR_PROFILE.id,
211
+ certified: false,
212
+ version,
213
+ symbolSize,
214
+ canvas: spec,
215
+ canvasModuleCount: modules.length,
216
+ reservedOverlaps,
217
+ touchedCodewordCount: touchedCodewords.size,
218
+ touchedCodewordsByBlock,
219
+ correctionBudgetPerBlock,
220
+ safe,
221
+ };
239
222
  }
240
-
241
223
  /** Validate a canvas and return the non-certifying structural analysis. */
242
224
  export function validateCanvasSpec(version, canvas = {}) {
243
- return analyzeCanvasDamage(version, canvas);
225
+ return analyzeCanvasDamage(version, canvas);
244
226
  }
245
-
246
227
  /** Self-check the fixed profile contract and representative QR geometries. */
247
228
  export function validateFrameQrTables() {
248
- const problems = [];
249
- if (FRAMEQR_PROFILE.certified !== false || FRAMEQR_PROFILE.densoFrameQrCompatible !== false) {
250
- problems.push('profile compatibility boundary must remain explicitly non-certified');
251
- }
252
- if (new Set(FRAMEQR_CANVAS_SHAPES).size !== FRAMEQR_CANVAS_SHAPES.length) {
253
- problems.push('canvas shape identifiers must be unique');
254
- }
255
- for (const version of [1, 2, 3, 4, 7, 10, 20, 30, 40]) {
256
- const size = versionSize(version);
257
- const spec = normalizeCanvasSpec(size);
258
- const modules = canvasModules(size, spec);
259
- const unique = new Set(modules.map(([x, y]) => `${x},${y}`));
260
- if (unique.size !== modules.length) problems.push(`v${version}: duplicate canvas modules`);
261
- if (modules.some(([x, y]) => x < 0 || y < 0 || x >= size || y >= size)) {
262
- problems.push(`v${version}: canvas escapes the symbol`);
229
+ const problems = [];
230
+ if (FRAMEQR_PROFILE.certified !== false || FRAMEQR_PROFILE.densoFrameQrCompatible !== false) {
231
+ problems.push('profile compatibility boundary must remain explicitly non-certified');
232
+ }
233
+ if (new Set(FRAMEQR_CANVAS_SHAPES).size !== FRAMEQR_CANVAS_SHAPES.length) {
234
+ problems.push('canvas shape identifiers must be unique');
263
235
  }
264
- const analysis = analyzeCanvasDamage(version, spec);
265
- if (analysis.touchedCodewordsByBlock.length !== blockLayout(version, 'H').blockCount) {
266
- problems.push(`v${version}: damage analysis block count mismatch`);
236
+ for (const version of [1, 2, 3, 4, 7, 10, 20, 30, 40]) {
237
+ const size = versionSize(version);
238
+ const spec = normalizeCanvasSpec(size);
239
+ const modules = canvasModules(size, spec);
240
+ const unique = new Set(modules.map(([x, y]) => `${x},${y}`));
241
+ if (unique.size !== modules.length)
242
+ problems.push(`v${version}: duplicate canvas modules`);
243
+ if (modules.some(([x, y]) => x < 0 || y < 0 || x >= size || y >= size)) {
244
+ problems.push(`v${version}: canvas escapes the symbol`);
245
+ }
246
+ const analysis = analyzeCanvasDamage(version, spec);
247
+ if (analysis.touchedCodewordsByBlock.length !== blockLayout(version, 'H').blockCount) {
248
+ problems.push(`v${version}: damage analysis block count mismatch`);
249
+ }
267
250
  }
268
- }
269
- return problems;
251
+ return problems;
270
252
  }