@vkontakte/vk-qr 2.1.1 → 3.1.0

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.
package/src/qr.ts CHANGED
@@ -21,946 +21,820 @@
21
21
  * Software.
22
22
  */
23
23
 
24
- 'use strict';
25
-
26
24
  type bit = number;
27
25
  type byte = number;
28
26
  type int = number;
29
27
 
30
- /*---- QR Code symbol class ----*/
28
+ /*---- Public helper constants ----*/
31
29
 
32
30
  /*
33
- * A QR Code symbol, which is a type of two-dimension barcode.
34
- * Invented by Denso Wave and described in the ISO/IEC 18004 standard.
35
- * Instances of this class represent an immutable square grid of black and white cells.
36
- * The class provides static factory functions to create a QR Code from text or binary data.
37
- * The class covers the QR Code Model 2 specification, supporting all versions (sizes)
38
- * from 1 to 40, all 4 error correction levels, and 4 character encoding modes.
39
- *
40
- * Ways to create a QR Code object:
41
- * - High level: Take the payload data and call QrCode.encodeText() or QrCode.encodeBinary().
42
- * - Mid level: Custom-make the list of segments and call QrCode.encodeSegments().
43
- * - Low level: Custom-make the array of data codeword bytes (including
44
- * segment headers and final padding, excluding error correction codewords),
45
- * supply the appropriate version number, and call the QrCode() constructor.
46
- * (Note that all ways require supplying the desired error correction level.)
31
+ * The error correction level in a QR Code symbol.
47
32
  */
48
- export class QrCode {
49
- /*-- Static factory functions (high level) --*/
50
-
51
- // Returns a QR Code representing the given Unicode text string at the given error correction level.
52
- // As a conservative upper bound, this function is guaranteed to succeed for strings that have 738 or fewer
53
- // Unicode code points (not UTF-16 code units) if the low error correction level is used. The smallest possible
54
- // QR Code version is automatically chosen for the output. The ECC level of the result may be higher than the
55
- // ecl argument if it can be done without increasing the version.
56
- public static encodeText(text: string, ecl: QrCode.Ecc): QrCode {
57
- const segs: Array<QrSegment> = QrSegment.makeSegments(text);
58
- return QrCode.encodeSegments(segs, ecl);
59
- }
60
-
61
- // Returns a QR Code representing the given binary data at the given error correction level.
62
- // This function always encodes using the binary segment mode, not any text mode. The maximum number of
63
- // bytes allowed is 2953. The smallest possible QR Code version is automatically chosen for the output.
64
- // The ECC level of the result may be higher than the ecl argument if it can be done without increasing the version.
65
- public static encodeBinary(data: Array<byte>, ecl: QrCode.Ecc): QrCode {
66
- const seg: QrSegment = QrSegment.makeBytes(data);
67
- return QrCode.encodeSegments([seg], ecl);
68
- }
69
-
70
- /*-- Static factory functions (mid level) --*/
71
-
72
- // Returns a QR Code representing the given segments with the given encoding parameters.
73
- // The smallest possible QR Code version within the given range is automatically
74
- // chosen for the output. Iff boostEcl is true, then the ECC level of the result
75
- // may be higher than the ecl argument if it can be done without increasing the
76
- // version. The mask number is either between 0 to 7 (inclusive) to force that
77
- // mask, or -1 to automatically choose an appropriate mask (which may be slow).
78
- // This function allows the user to create a custom sequence of segments that switches
79
- // between modes (such as alphanumeric and byte) to encode text in less space.
80
- // This is a mid-level API; the high-level API is encodeText() and encodeBinary().
81
- public static encodeSegments(
82
- segs: Array<QrSegment>,
83
- ecl: QrCode.Ecc,
84
- minVersion: int = 1,
85
- maxVersion: int = 40,
86
- mask: int = -1,
87
- boostEcl: boolean = true
88
- ): QrCode {
89
- if (
90
- !(QrCode.MIN_VERSION <= minVersion && minVersion <= maxVersion && maxVersion <= QrCode.MAX_VERSION) ||
91
- mask < -1 ||
92
- mask > 7
93
- )
94
- throw 'Invalid value';
95
-
96
- // Find the minimal version number to use
97
- let version: int;
98
- let dataUsedBits: int;
99
- for (version = minVersion; ; version++) {
100
- const dataCapacityBits: int = QrCode.getNumDataCodewords(version, ecl) * 8; // Number of data bits available
101
- const usedBits: number = QrSegment.getTotalBits(segs, version);
102
- if (usedBits <= dataCapacityBits) {
103
- dataUsedBits = usedBits;
104
- break; // This version number is found to be suitable
105
- }
106
- if (version >= maxVersion)
107
- // All versions in the range could not fit the given data
108
- throw 'Data too long';
109
- }
110
-
111
- // Increase the error correction level while the data still fits in the current version number
112
- for (const newEcl of [QrCode.Ecc.MEDIUM, QrCode.Ecc.QUARTILE, QrCode.Ecc.HIGH]) {
113
- // From low to high
114
- if (boostEcl && dataUsedBits <= QrCode.getNumDataCodewords(version, newEcl) * 8) ecl = newEcl;
115
- }
116
-
117
- // Concatenate all segments to create the data bit string
118
- let bb = new BitBuffer();
119
- for (const seg of segs) {
120
- bb.appendBits(seg.mode.modeBits, 4);
121
- bb.appendBits(seg.numChars, seg.mode.numCharCountBits(version));
122
- for (const b of seg.getData()) bb.array.push(b);
123
- }
124
- if (bb.array.length != dataUsedBits) throw 'Assertion error';
125
-
126
- // Add terminator and pad up to a byte if applicable
127
- const dataCapacityBits: int = QrCode.getNumDataCodewords(version, ecl) * 8;
128
- if (bb.array.length > dataCapacityBits) throw 'Assertion error';
129
- bb.appendBits(0, Math.min(4, dataCapacityBits - bb.array.length));
130
- bb.appendBits(0, (8 - (bb.array.length % 8)) % 8);
131
- if (bb.array.length % 8 != 0) throw 'Assertion error';
132
-
133
- // Pad with alternating bytes until data capacity is reached
134
- for (let padByte = 0xec; bb.array.length < dataCapacityBits; padByte ^= 0xec ^ 0x11) bb.appendBits(padByte, 8);
135
-
136
- // Pack bits into bytes in big endian
137
- let dataCodewords: Array<byte> = [];
138
- while (dataCodewords.length * 8 < bb.array.length) dataCodewords.push(0);
139
- bb.array.forEach((b: bit, i: int) => (dataCodewords[i >>> 3] |= b << (7 - (i & 7))));
140
-
141
- // Create the QR Code object
142
- return new QrCode(version, ecl, dataCodewords, mask);
143
- }
144
-
145
- /*-- Fields --*/
146
-
147
- // The width and height of this QR Code, measured in modules, between
148
- // 21 and 177 (inclusive). This is equal to version * 4 + 17.
149
- public readonly size: int;
150
-
151
- // The modules of this QR Code (false = white, true = black).
152
- // Immutable after constructor finishes. Accessed through getModule().
153
- public readonly modules: Array<Array<boolean>> = [];
154
-
155
- // Indicates function modules that are not subjected to masking. Discarded when constructor finishes.
156
- private readonly isFunction: Array<Array<boolean>> = [];
157
-
158
- /*-- Constructor (low level) and fields --*/
159
-
160
- // Creates a new QR Code with the given version number,
161
- // error correction level, data codeword bytes, and mask number.
162
- // This is a low-level API that most users should not use directly.
163
- // A mid-level API is the encodeSegments() function.
164
- public constructor(
165
- // The version number of this QR Code, which is between 1 and 40 (inclusive).
166
- // This determines the size of this barcode.
167
- public readonly version: int,
168
-
169
- // The error correction level used in this QR Code.
170
- public readonly errorCorrectionLevel: QrCode.Ecc,
171
-
172
- dataCodewords: Array<byte>,
173
-
174
- // The index of the mask pattern used in this QR Code, which is between 0 and 7 (inclusive).
175
- // Even if a QR Code is created with automatic masking requested (mask = -1),
176
- // the resulting object still has a mask value between 0 and 7.
177
- public readonly mask: int
178
- ) {
179
- // Check scalar arguments
180
- if (version < QrCode.MIN_VERSION || version > QrCode.MAX_VERSION) throw 'Version value out of range';
181
- if (mask < -1 || mask > 7) throw 'Mask value out of range';
182
- this.size = version * 4 + 17;
183
-
184
- // Initialize both grids to be size*size arrays of Boolean false
185
- let row: Array<boolean> = [];
186
- for (let i = 0; i < this.size; i++) row.push(false);
187
- for (let i = 0; i < this.size; i++) {
188
- this.modules.push(row.slice()); // Initially all white
189
- this.isFunction.push(row.slice());
190
- }
33
+ export const QrCodeEcc = {
34
+ LOW: { ordinal: 0, formatBits: 1 } as const, // The QR Code can tolerate about 7% erroneous codewords
35
+ MEDIUM: { ordinal: 1, formatBits: 0 } as const, // The QR Code can tolerate about 15% erroneous codewords
36
+ QUARTILE: { ordinal: 2, formatBits: 3 } as const, // The QR Code can tolerate about 25% erroneous codewords
37
+ HIGH: { ordinal: 3, formatBits: 2 } as const, // The QR Code can tolerate about 30% erroneous codewords
38
+ } as const;
191
39
 
192
- // Compute ECC, draw modules
193
- this.drawFunctionPatterns();
194
- const allCodewords: Array<byte> = this.addEccAndInterleave(dataCodewords);
195
- this.drawCodewords(allCodewords);
196
-
197
- // Do masking
198
- if (mask == -1) {
199
- // Automatically choose best mask
200
- let minPenalty: int = 1000000000;
201
- for (let i = 0; i < 8; i++) {
202
- this.applyMask(i);
203
- this.drawFormatBits(i);
204
- const penalty: int = this.getPenaltyScore();
205
- if (penalty < minPenalty) {
206
- mask = i;
207
- minPenalty = penalty;
208
- }
209
- this.applyMask(i); // Undoes the mask due to XOR
210
- }
211
- }
212
- if (mask < 0 || mask > 7) throw 'Assertion error';
213
- this.mask = mask;
214
- this.applyMask(mask); // Apply the final choice of mask
215
- this.drawFormatBits(mask); // Overwrite old format bits
40
+ export type QrCodeEccValue = (typeof QrCodeEcc)[keyof typeof QrCodeEcc];
216
41
 
217
- this.isFunction = [];
218
- }
42
+ /*
43
+ * Describes how a segment's data bits are interpreted.
44
+ */
45
+ export const QrSegmentMode = {
46
+ NUMERIC: { modeBits: 0x1, numBitsCharCount: [10, 12, 14] as const },
47
+ ALPHANUMERIC: { modeBits: 0x2, numBitsCharCount: [9, 11, 13] as const },
48
+ BYTE: { modeBits: 0x4, numBitsCharCount: [8, 16, 16] as const },
49
+ KANJI: { modeBits: 0x8, numBitsCharCount: [8, 10, 12] as const },
50
+ ECI: { modeBits: 0x7, numBitsCharCount: [0, 0, 0] as const },
51
+ } as const;
52
+
53
+ export type QrSegmentModeValue = (typeof QrSegmentMode)[keyof typeof QrSegmentMode];
54
+
55
+ function numCharCountBits(mode: QrSegmentModeValue, ver: int): int {
56
+ return mode.numBitsCharCount[Math.floor((ver + 7) / 17)];
57
+ }
219
58
 
220
- /*-- Accessor methods --*/
59
+ /*---- QR Code result type ----*/
221
60
 
222
- // Returns the color of the module (pixel) at the given coordinates, which is false
223
- // for white or true for black. The top left corner has the coordinates (x=0, y=0).
224
- // If the given coordinates are out of bounds, then false (white) is returned.
225
- public getModule(x: int, y: int): boolean {
226
- return 0 <= x && x < this.size && 0 <= y && y < this.size && this.modules[y][x];
227
- }
61
+ /*
62
+ * A QR Code symbol, which is a type of two-dimension barcode.
63
+ * Invented by Denso Wave and described in the ISO/IEC 18004 standard.
64
+ * Represents an immutable square grid of black and white cells.
65
+ */
66
+ export interface QrCode {
67
+ /** The version number of this QR Code, between 1 and 40 (inclusive). */
68
+ readonly version: int;
69
+ /** The error correction level used in this QR Code. */
70
+ readonly errorCorrectionLevel: QrCodeEccValue;
71
+ /** The width and height of this QR Code, measured in modules, between 21 and 177 (inclusive). */
72
+ readonly size: int;
73
+ /** The index of the mask pattern used in this QR Code, between 0 and 7 (inclusive). */
74
+ readonly mask: int;
75
+ /** The modules of this QR Code (false = white, true = black). */
76
+ readonly modules: boolean[][];
77
+ }
228
78
 
229
- /*-- Public instance methods --*/
230
-
231
- // Draws this QR Code, with the given module scale and border modules, onto the given HTML
232
- // canvas element. The canvas's width and height is resized to (this.size + border * 2) * scale.
233
- // The drawn image is be purely black and white, and fully opaque.
234
- // The scale must be a positive integer and the border must be a non-negative integer.
235
- public drawCanvas(scale: int, border: int, canvas: HTMLCanvasElement): void {
236
- if (scale <= 0 || border < 0) throw 'Value out of range';
237
- const width: int = (this.size + border * 2) * scale;
238
- canvas.width = width;
239
- canvas.height = width;
240
- let ctx = canvas.getContext('2d') as CanvasRenderingContext2D;
241
- for (let y = -border; y < this.size + border; y++) {
242
- for (let x = -border; x < this.size + border; x++) {
243
- ctx.fillStyle = this.getModule(x, y) ? '#000000' : '#FFFFFF';
244
- ctx.fillRect((x + border) * scale, (y + border) * scale, scale, scale);
245
- }
79
+ /*---- QR Code factory function (mid level) ----*/
80
+
81
+ // Returns a QR Code representing the given segments with the given encoding parameters.
82
+ // The smallest possible QR Code version within the given range is automatically
83
+ // chosen for the output. Iff boostEcl is true, then the ECC level of the result
84
+ // may be higher than the ecl argument if it can be done without increasing the
85
+ // version. The mask number is either between 0 to 7 (inclusive) to force that
86
+ // mask, or -1 to automatically choose an appropriate mask (which may be slow).
87
+ // This function allows the user to create a custom sequence of segments that switches
88
+ // between modes (such as alphanumeric and byte) to encode text in less space.
89
+ // This is a mid-level API; the high-level API is encodeText() and encodeBinary().
90
+ export function encodeSegments(
91
+ segs: QrSegment[],
92
+ ecl: QrCodeEccValue,
93
+ minVersion: int = 1,
94
+ maxVersion: int = 40,
95
+ mask: int = -1,
96
+ boostEcl: boolean = true,
97
+ ): QrCode {
98
+ if (
99
+ !(MIN_VERSION <= minVersion && minVersion <= maxVersion && maxVersion <= MAX_VERSION) ||
100
+ mask < -1 ||
101
+ mask > 7
102
+ )
103
+ throw 'Invalid value';
104
+
105
+ // Find the minimal version number to use
106
+ let version: int;
107
+ let dataUsedBits: int;
108
+ for (version = minVersion; ; version++) {
109
+ const dataCapacityBits: int = getNumDataCodewords(version, ecl) * 8; // Number of data bits available
110
+ const usedBits: number = getTotalBits(segs, version);
111
+ if (usedBits <= dataCapacityBits) {
112
+ dataUsedBits = usedBits;
113
+ break; // This version number is found to be suitable
246
114
  }
247
- }
115
+ if (version >= maxVersion)
116
+ // All versions in the range could not fit the given data
117
+ throw 'Data too long';
118
+ }
119
+
120
+ // Increase the error correction level while the data still fits in the current version number
121
+ let finalEcl = ecl;
122
+ for (const newEcl of [QrCodeEcc.MEDIUM, QrCodeEcc.QUARTILE, QrCodeEcc.HIGH]) {
123
+ // From low to high
124
+ if (boostEcl && dataUsedBits <= getNumDataCodewords(version, newEcl) * 8) finalEcl = newEcl;
125
+ }
126
+
127
+ // Concatenate all segments to create the data bit string
128
+ const bb = createBitBuffer();
129
+ for (const seg of segs) {
130
+ bb.appendBits(seg.mode.modeBits, 4);
131
+ bb.appendBits(seg.numChars, numCharCountBits(seg.mode, version));
132
+ for (const b of seg.getData()) bb.array.push(b);
133
+ }
134
+ if (bb.array.length !== dataUsedBits) throw 'Assertion error';
135
+
136
+ // Add terminator and pad up to a byte if applicable
137
+ const dataCapacityBits: int = getNumDataCodewords(version, finalEcl) * 8;
138
+ if (bb.array.length > dataCapacityBits) throw 'Assertion error';
139
+ bb.appendBits(0, Math.min(4, dataCapacityBits - bb.array.length));
140
+ bb.appendBits(0, (8 - (bb.array.length % 8)) % 8);
141
+ if (bb.array.length % 8 !== 0) throw 'Assertion error';
142
+
143
+ // Pad with alternating bytes until data capacity is reached
144
+ for (let padByte = 0xec; bb.array.length < dataCapacityBits; padByte ^= 0xec ^ 0x11)
145
+ bb.appendBits(padByte, 8);
146
+
147
+ // Pack bits into bytes in big endian
148
+ const dataCodewords: byte[] = [];
149
+ while (dataCodewords.length * 8 < bb.array.length) dataCodewords.push(0);
150
+ bb.array.forEach((b: bit, i: int) => {
151
+ dataCodewords[i >>> 3] |= b << (7 - (i & 7));
152
+ });
153
+
154
+ // Create the QR Code object
155
+ return createQrCode(version, finalEcl, dataCodewords, mask);
156
+ }
248
157
 
249
- // Returns a string of SVG code for an image depicting this QR Code, with the given number
250
- // of border modules. The string always uses Unix newlines (\n), regardless of the platform.
251
- public toSvgString(border: int): string {
252
- if (border < 0) throw 'Border must be non-negative';
253
- let parts: Array<string> = [];
254
- for (let y = 0; y < this.size; y++) {
255
- for (let x = 0; x < this.size; x++) {
256
- if (this.getModule(x, y)) parts.push(`M${x + border},${y + border}h1v1h-1z`);
158
+ /*---- Private QR Code creation function ----*/
159
+
160
+ // Creates a new QR Code with the given version number,
161
+ // error correction level, data codeword bytes, and mask number.
162
+ function createQrCode(
163
+ version: int,
164
+ errorCorrectionLevel: QrCodeEccValue,
165
+ dataCodewords: byte[],
166
+ mask: int,
167
+ ): QrCode {
168
+ // Check scalar arguments
169
+ if (version < MIN_VERSION || version > MAX_VERSION) throw 'Version value out of range';
170
+ if (mask < -1 || mask > 7) throw 'Mask value out of range';
171
+ const size = version * 4 + 17;
172
+
173
+ // Initialize both grids to be size*size arrays of Boolean false
174
+ const row: boolean[] = [];
175
+ for (let i = 0; i < size; i++) row.push(false);
176
+ const modules: boolean[][] = [];
177
+ const isFunction: boolean[][] = [];
178
+ for (let i = 0; i < size; i++) {
179
+ modules.push(row.slice()); // Initially all white
180
+ isFunction.push(row.slice());
181
+ }
182
+
183
+ // Helper to set function module
184
+ const setFunctionModule = (x: int, y: int, isBlack: boolean): void => {
185
+ modules[y][x] = isBlack;
186
+ isFunction[y][x] = true;
187
+ };
188
+
189
+ // Draw horizontal and vertical timing patterns
190
+ for (let i = 0; i < size; i++) {
191
+ setFunctionModule(6, i, i % 2 === 0);
192
+ setFunctionModule(i, 6, i % 2 === 0);
193
+ }
194
+
195
+ // Draw 3 finder patterns (all corners except bottom right; overwrites some timing modules)
196
+ const drawFinderPattern = (x: int, y: int): void => {
197
+ for (let dy = -4; dy <= 4; dy++) {
198
+ for (let dx = -4; dx <= 4; dx++) {
199
+ const dist: int = Math.max(Math.abs(dx), Math.abs(dy)); // Chebyshev/infinity norm
200
+ const xx: int = x + dx;
201
+ const yy: int = y + dy;
202
+ if (0 <= xx && xx < size && 0 <= yy && yy < size)
203
+ setFunctionModule(xx, yy, dist !== 2 && dist !== 4);
257
204
  }
258
205
  }
259
- return `<?xml version="1.0" encoding="UTF-8"?>
260
- <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
261
- <svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 ${this.size + border * 2} ${this.size +
262
- border * 2}" stroke="none">
263
- <rect width="100%" height="100%" fill="#FFFFFF"/>
264
- <path d="${parts.join(' ')}" fill="#000000"/>
265
- </svg>
266
- `;
267
- }
268
-
269
- /*-- Private helper methods for constructor: Drawing function modules --*/
270
-
271
- // Reads this object's version field, and draws and marks all function modules.
272
- private drawFunctionPatterns(): void {
273
- // Draw horizontal and vertical timing patterns
274
- for (let i = 0; i < this.size; i++) {
275
- this.setFunctionModule(6, i, i % 2 == 0);
276
- this.setFunctionModule(i, 6, i % 2 == 0);
277
- }
278
-
279
- // Draw 3 finder patterns (all corners except bottom right; overwrites some timing modules)
280
- this.drawFinderPattern(3, 3);
281
- this.drawFinderPattern(this.size - 4, 3);
282
- this.drawFinderPattern(3, this.size - 4);
283
-
284
- // Draw numerous alignment patterns
285
- const alignPatPos: Array<int> = this.getAlignmentPatternPositions();
286
- const numAlign: int = alignPatPos.length;
287
- for (let i = 0; i < numAlign; i++) {
288
- for (let j = 0; j < numAlign; j++) {
289
- // Don't draw on the three finder corners
290
- if (!((i == 0 && j == 0) || (i == 0 && j == numAlign - 1) || (i == numAlign - 1 && j == 0)))
291
- this.drawAlignmentPattern(alignPatPos[i], alignPatPos[j]);
206
+ };
207
+ drawFinderPattern(3, 3);
208
+ drawFinderPattern(size - 4, 3);
209
+ drawFinderPattern(3, size - 4);
210
+
211
+ // Draw numerous alignment patterns
212
+ const alignPatPos: int[] = getAlignmentPatternPositions(version, size);
213
+ const numAlign: int = alignPatPos.length;
214
+ for (let i = 0; i < numAlign; i++) {
215
+ for (let j = 0; j < numAlign; j++) {
216
+ // Don't draw on the three finder corners
217
+ if (
218
+ !(
219
+ (i === 0 && j === 0) ||
220
+ (i === 0 && j === numAlign - 1) ||
221
+ (i === numAlign - 1 && j === 0)
222
+ )
223
+ ) {
224
+ const drawAlignmentPattern = (x: int, y: int): void => {
225
+ for (let dy = -2; dy <= 2; dy++) {
226
+ for (let dx = -2; dx <= 2; dx++)
227
+ setFunctionModule(x + dx, y + dy, Math.max(Math.abs(dx), Math.abs(dy)) !== 1);
228
+ }
229
+ };
230
+ drawAlignmentPattern(alignPatPos[i], alignPatPos[j]);
292
231
  }
293
232
  }
294
-
295
- // Draw configuration data
296
- this.drawFormatBits(0); // Dummy mask value; overwritten later in the constructor
297
- this.drawVersion();
298
233
  }
299
234
 
300
- // Draws two copies of the format bits (with its own error correction code)
301
- // based on the given mask and this object's error correction level field.
302
- private drawFormatBits(mask: int): void {
235
+ // Draw configuration data
236
+ const drawFormatBits = (ecl: QrCodeEccValue, msk: int): void => {
303
237
  // Calculate error correction code and pack bits
304
- const data: int = (this.errorCorrectionLevel.formatBits << 3) | mask; // errCorrLvl is uint2, mask is uint3
238
+ const data: int = (ecl.formatBits << 3) | msk; // errCorrLvl is uint2, mask is uint3
305
239
  let rem: int = data;
306
240
  for (let i = 0; i < 10; i++) rem = (rem << 1) ^ ((rem >>> 9) * 0x537);
307
241
  const bits = ((data << 10) | rem) ^ 0x5412; // uint15
308
- if (bits >>> 15 != 0) throw 'Assertion error';
242
+ if (bits >>> 15 !== 0) throw 'Assertion error';
309
243
 
310
244
  // Draw first copy
311
- for (let i = 0; i <= 5; i++) this.setFunctionModule(8, i, getBit(bits, i));
312
- this.setFunctionModule(8, 7, getBit(bits, 6));
313
- this.setFunctionModule(8, 8, getBit(bits, 7));
314
- this.setFunctionModule(7, 8, getBit(bits, 8));
315
- for (let i = 9; i < 15; i++) this.setFunctionModule(14 - i, 8, getBit(bits, i));
245
+ for (let i = 0; i <= 5; i++) setFunctionModule(8, i, getBit(bits, i));
246
+ setFunctionModule(8, 7, getBit(bits, 6));
247
+ setFunctionModule(8, 8, getBit(bits, 7));
248
+ setFunctionModule(7, 8, getBit(bits, 8));
249
+ for (let i = 9; i < 15; i++) setFunctionModule(14 - i, 8, getBit(bits, i));
316
250
 
317
251
  // Draw second copy
318
- for (let i = 0; i < 8; i++) this.setFunctionModule(this.size - 1 - i, 8, getBit(bits, i));
319
- for (let i = 8; i < 15; i++) this.setFunctionModule(8, this.size - 15 + i, getBit(bits, i));
320
- this.setFunctionModule(8, this.size - 8, true); // Always black
321
- }
322
-
323
- // Draws two copies of the version bits (with its own error correction code),
324
- // based on this object's version field, iff 7 <= version <= 40.
325
- private drawVersion(): void {
326
- if (this.version < 7) return;
327
-
252
+ for (let i = 0; i < 8; i++) setFunctionModule(size - 1 - i, 8, getBit(bits, i));
253
+ for (let i = 8; i < 15; i++) setFunctionModule(8, size - 15 + i, getBit(bits, i));
254
+ setFunctionModule(8, size - 8, true); // Always black
255
+ };
256
+ drawFormatBits(errorCorrectionLevel, 0); // Dummy mask value; overwritten later
257
+
258
+ // Draw version
259
+ if (version >= 7) {
328
260
  // Calculate error correction code and pack bits
329
- let rem: int = this.version; // version is uint6, in the range [7, 40]
261
+ let rem: int = version; // version is uint6, in the range [7, 40]
330
262
  for (let i = 0; i < 12; i++) rem = (rem << 1) ^ ((rem >>> 11) * 0x1f25);
331
- const bits: int = (this.version << 12) | rem; // uint18
332
- if (bits >>> 18 != 0) throw 'Assertion error';
263
+ const bits: int = (version << 12) | rem; // uint18
264
+ if (bits >>> 18 !== 0) throw 'Assertion error';
333
265
 
334
266
  // Draw two copies
335
267
  for (let i = 0; i < 18; i++) {
336
268
  const color: boolean = getBit(bits, i);
337
- const a: int = this.size - 11 + (i % 3);
269
+ const a: int = size - 11 + (i % 3);
338
270
  const b: int = Math.floor(i / 3);
339
- this.setFunctionModule(a, b, color);
340
- this.setFunctionModule(b, a, color);
271
+ setFunctionModule(a, b, color);
272
+ setFunctionModule(b, a, color);
341
273
  }
342
274
  }
343
275
 
344
- // Draws a 9*9 finder pattern including the border separator,
345
- // with the center module at (x, y). Modules can be out of bounds.
346
- private drawFinderPattern(x: int, y: int): void {
347
- for (let dy = -4; dy <= 4; dy++) {
348
- for (let dx = -4; dx <= 4; dx++) {
349
- const dist: int = Math.max(Math.abs(dx), Math.abs(dy)); // Chebyshev/infinity norm
350
- const xx: int = x + dx;
351
- const yy: int = y + dy;
352
- if (0 <= xx && xx < this.size && 0 <= yy && yy < this.size)
353
- this.setFunctionModule(xx, yy, dist != 2 && dist != 4);
276
+ // Compute ECC, draw modules
277
+ const allCodewords: byte[] = addEccAndInterleave(version, errorCorrectionLevel, dataCodewords);
278
+
279
+ // Draw codewords
280
+ if (allCodewords.length !== Math.floor(getNumRawDataModules(version) / 8))
281
+ throw 'Invalid argument';
282
+ let bitIndex: int = 0; // Bit index into the data
283
+ // Do the funny zigzag scan
284
+ for (let right = size - 1; right >= 1; right -= 2) {
285
+ // Index of right column in each column pair
286
+ if (right === 6) right = 5;
287
+ for (let vert = 0; vert < size; vert++) {
288
+ // Vertical counter
289
+ for (let j = 0; j < 2; j++) {
290
+ const x: int = right - j; // Actual x coordinate
291
+ const upward: boolean = ((right + 1) & 2) === 0;
292
+ const y: int = upward ? size - 1 - vert : vert; // Actual y coordinate
293
+ if (!isFunction[y][x] && bitIndex < allCodewords.length * 8) {
294
+ modules[y][x] = getBit(allCodewords[bitIndex >>> 3], 7 - (bitIndex & 7));
295
+ bitIndex++;
296
+ }
297
+ // If this QR Code has any remainder bits (0 to 7), they were assigned as
298
+ // 0/false/white by the constructor and are left unchanged by this method
354
299
  }
355
300
  }
356
301
  }
357
-
358
- // Draws a 5*5 alignment pattern, with the center module
359
- // at (x, y). All modules must be in bounds.
360
- private drawAlignmentPattern(x: int, y: int): void {
361
- for (let dy = -2; dy <= 2; dy++) {
362
- for (let dx = -2; dx <= 2; dx++)
363
- this.setFunctionModule(x + dx, y + dy, Math.max(Math.abs(dx), Math.abs(dy)) != 1);
364
- }
365
- }
366
-
367
- // Sets the color of a module and marks it as a function module.
368
- // Only used by the constructor. Coordinates must be in bounds.
369
- private setFunctionModule(x: int, y: int, isBlack: boolean): void {
370
- this.modules[y][x] = isBlack;
371
- this.isFunction[y][x] = true;
372
- }
373
-
374
- /*-- Private helper methods for constructor: Codewords and masking --*/
375
-
376
- // Returns a new byte string representing the given data with the appropriate error correction
377
- // codewords appended to it, based on this object's version and error correction level.
378
- private addEccAndInterleave(data: Array<byte>): Array<byte> {
379
- const ver: int = this.version;
380
- const ecl: QrCode.Ecc = this.errorCorrectionLevel;
381
- if (data.length != QrCode.getNumDataCodewords(ver, ecl)) throw 'Invalid argument';
382
-
383
- // Calculate parameter numbers
384
- const numBlocks: int = QrCode.NUM_ERROR_CORRECTION_BLOCKS[ecl.ordinal][ver];
385
- const blockEccLen: int = QrCode.ECC_CODEWORDS_PER_BLOCK[ecl.ordinal][ver];
386
- const rawCodewords: int = Math.floor(QrCode.getNumRawDataModules(ver) / 8);
387
- const numShortBlocks: int = numBlocks - (rawCodewords % numBlocks);
388
- const shortBlockLen: int = Math.floor(rawCodewords / numBlocks);
389
-
390
- // Split data into blocks and append ECC to each block
391
- let blocks: Array<Array<byte>> = [];
392
- const rs = new ReedSolomonGenerator(blockEccLen);
393
- for (let i = 0, k = 0; i < numBlocks; i++) {
394
- let dat: Array<byte> = data.slice(k, k + shortBlockLen - blockEccLen + (i < numShortBlocks ? 0 : 1));
395
- k += dat.length;
396
- const ecc: Array<byte> = rs.getRemainder(dat);
397
- if (i < numShortBlocks) dat.push(0);
398
- blocks.push(dat.concat(ecc));
399
- }
400
-
401
- // Interleave (not concatenate) the bytes from every block into a single sequence
402
- let result: Array<byte> = [];
403
- for (let i = 0; i < blocks[0].length; i++) {
404
- for (let j = 0; j < blocks.length; j++) {
405
- // Skip the padding byte in short blocks
406
- if (i != shortBlockLen - blockEccLen || j >= numShortBlocks) result.push(blocks[j][i]);
302
+ if (bitIndex !== allCodewords.length * 8) throw 'Assertion error';
303
+
304
+ // Do masking
305
+ let finalMask = mask;
306
+ if (finalMask === -1) {
307
+ // Automatically choose best mask
308
+ let minPenalty: int = 1000000000;
309
+ for (let i = 0; i < 8; i++) {
310
+ applyMask(modules, isFunction, size, i);
311
+ drawFormatBits(errorCorrectionLevel, i);
312
+ const penalty: int = getPenaltyScore(modules, size);
313
+ if (penalty < minPenalty) {
314
+ finalMask = i;
315
+ minPenalty = penalty;
407
316
  }
317
+ applyMask(modules, isFunction, size, i); // Undoes the mask due to XOR
408
318
  }
409
- if (result.length != rawCodewords) throw 'Assertion error';
410
- return result;
411
319
  }
320
+ if (finalMask < 0 || finalMask > 7) throw 'Assertion error';
321
+ applyMask(modules, isFunction, size, finalMask);
322
+ // Redraw format bits with final mask
323
+ // We need to update format bits, but modules are already set
324
+ // So we need to redraw them
325
+ {
326
+ const data: int = (errorCorrectionLevel.formatBits << 3) | finalMask;
327
+ let rem: int = data;
328
+ for (let i = 0; i < 10; i++) rem = (rem << 1) ^ ((rem >>> 9) * 0x537);
329
+ const bits = ((data << 10) | rem) ^ 0x5412;
330
+ if (bits >>> 15 !== 0) throw 'Assertion error';
331
+ for (let i = 0; i <= 5; i++) modules[i][8] = getBit(bits, i);
332
+ modules[7][8] = getBit(bits, 6);
333
+ modules[8][8] = getBit(bits, 7);
334
+ modules[8][7] = getBit(bits, 8);
335
+ for (let i = 9; i < 15; i++) modules[8][14 - i] = getBit(bits, i);
336
+ for (let i = 0; i < 8; i++) modules[8][size - 1 - i] = getBit(bits, i);
337
+ for (let i = 8; i < 15; i++) modules[size - 15 + i][8] = getBit(bits, i);
338
+ modules[size - 8][8] = true; // Always black
339
+ }
340
+
341
+ return {
342
+ version,
343
+ errorCorrectionLevel,
344
+ size,
345
+ mask: finalMask,
346
+ modules,
347
+ };
348
+ }
412
349
 
413
- // Draws the given sequence of 8-bit codewords (data and error correction) onto the entire
414
- // data area of this QR Code. Function modules need to be marked off before this is called.
415
- private drawCodewords(data: Array<byte>): void {
416
- if (data.length != Math.floor(QrCode.getNumRawDataModules(this.version) / 8)) throw 'Invalid argument';
417
- let i: int = 0; // Bit index into the data
418
- // Do the funny zigzag scan
419
- for (let right = this.size - 1; right >= 1; right -= 2) {
420
- // Index of right column in each column pair
421
- if (right == 6) right = 5;
422
- for (let vert = 0; vert < this.size; vert++) {
423
- // Vertical counter
424
- for (let j = 0; j < 2; j++) {
425
- const x: int = right - j; // Actual x coordinate
426
- const upward: boolean = ((right + 1) & 2) == 0;
427
- const y: int = upward ? this.size - 1 - vert : vert; // Actual y coordinate
428
- if (!this.isFunction[y][x] && i < data.length * 8) {
429
- this.modules[y][x] = getBit(data[i >>> 3], 7 - (i & 7));
430
- i++;
431
- }
432
- // If this QR Code has any remainder bits (0 to 7), they were assigned as
433
- // 0/false/white by the constructor and are left unchanged by this method
434
- }
435
- }
436
- }
437
- if (i != data.length * 8) throw 'Assertion error';
350
+ /*---- Private helper functions ----*/
351
+
352
+ // Returns an ascending list of positions of alignment patterns for this version number.
353
+ // Each position is in the range [0,177), and are used on both the x and y axes.
354
+ function getAlignmentPatternPositions(version: int, size: int): int[] {
355
+ if (version === 1) return [];
356
+ else {
357
+ const numAlign: int = Math.floor(version / 7) + 2;
358
+ const step: int = version === 32 ? 26 : Math.ceil((size - 13) / (numAlign * 2 - 2)) * 2;
359
+ const result: int[] = [6];
360
+ for (let pos = size - 7; result.length < numAlign; pos -= step) result.splice(1, 0, pos);
361
+ return result;
438
362
  }
363
+ }
439
364
 
440
- // XORs the codeword modules in this QR Code with the given mask pattern.
441
- // The function modules must be marked and the codeword bits must be drawn
442
- // before masking. Due to the arithmetic of XOR, calling applyMask() with
443
- // the same mask value a second time will undo the mask. A final well-formed
444
- // QR Code needs exactly one (not zero, two, etc.) mask applied.
445
- private applyMask(mask: int): void {
446
- if (mask < 0 || mask > 7) throw 'Mask value out of range';
447
- for (let y = 0; y < this.size; y++) {
448
- for (let x = 0; x < this.size; x++) {
449
- let invert: boolean;
450
- switch (mask) {
451
- case 0:
452
- invert = (x + y) % 2 == 0;
453
- break;
454
- case 1:
455
- invert = y % 2 == 0;
456
- break;
457
- case 2:
458
- invert = x % 3 == 0;
459
- break;
460
- case 3:
461
- invert = (x + y) % 3 == 0;
462
- break;
463
- case 4:
464
- invert = (Math.floor(x / 3) + Math.floor(y / 2)) % 2 == 0;
465
- break;
466
- case 5:
467
- invert = ((x * y) % 2) + ((x * y) % 3) == 0;
468
- break;
469
- case 6:
470
- invert = (((x * y) % 2) + ((x * y) % 3)) % 2 == 0;
471
- break;
472
- case 7:
473
- invert = (((x + y) % 2) + ((x * y) % 3)) % 2 == 0;
474
- break;
475
- default:
476
- throw 'Assertion error';
477
- }
478
- if (!this.isFunction[y][x] && invert) this.modules[y][x] = !this.modules[y][x];
365
+ // XORs the codeword modules in this QR Code with the given mask pattern.
366
+ // The function modules must be marked and the codeword bits must be drawn
367
+ // before masking. Due to the arithmetic of XOR, calling applyMask() with
368
+ // the same mask value a second time will undo the mask. A final well-formed
369
+ // QR Code needs exactly one (not zero, two, etc.) mask applied.
370
+ function applyMask(modules: boolean[][], isFunction: boolean[][], size: int, mask: int): void {
371
+ if (mask < 0 || mask > 7) throw 'Mask value out of range';
372
+ for (let y = 0; y < size; y++) {
373
+ for (let x = 0; x < size; x++) {
374
+ let invert: boolean;
375
+ switch (mask) {
376
+ case 0:
377
+ invert = (x + y) % 2 === 0;
378
+ break;
379
+ case 1:
380
+ invert = y % 2 === 0;
381
+ break;
382
+ case 2:
383
+ invert = x % 3 === 0;
384
+ break;
385
+ case 3:
386
+ invert = (x + y) % 3 === 0;
387
+ break;
388
+ case 4:
389
+ invert = (Math.floor(x / 3) + Math.floor(y / 2)) % 2 === 0;
390
+ break;
391
+ case 5:
392
+ invert = ((x * y) % 2) + ((x * y) % 3) === 0;
393
+ break;
394
+ case 6:
395
+ invert = (((x * y) % 2) + ((x * y) % 3)) % 2 === 0;
396
+ break;
397
+ case 7:
398
+ invert = (((x + y) % 2) + ((x * y) % 3)) % 2 === 0;
399
+ break;
400
+ default:
401
+ throw 'Assertion error';
479
402
  }
403
+ if (!isFunction[y][x] && invert) modules[y][x] = !modules[y][x];
480
404
  }
481
405
  }
406
+ }
482
407
 
483
- // Calculates and returns the penalty score based on state of this QR Code's current modules.
484
- // This is used by the automatic mask choice algorithm to find the mask pattern that yields the lowest score.
485
- private getPenaltyScore(): int {
486
- let result: int = 0;
487
-
488
- // Adjacent modules in row having same color, and finder-like patterns
489
- for (let y = 0; y < this.size; y++) {
490
- let runHistory = [0, 0, 0, 0, 0, 0, 0];
491
- let color = false;
492
- let runX = 0;
493
- for (let x = 0; x < this.size; x++) {
494
- if (this.modules[y][x] == color) {
495
- runX++;
496
- if (runX == 5) result += QrCode.PENALTY_N1;
497
- else if (runX > 5) result++;
498
- } else {
499
- QrCode.addRunToHistory(runX, runHistory);
500
- if (!color && QrCode.hasFinderLikePattern(runHistory)) result += QrCode.PENALTY_N3;
501
- color = this.modules[y][x];
502
- runX = 1;
503
- }
504
- }
505
- QrCode.addRunToHistory(runX, runHistory);
506
- if (color) QrCode.addRunToHistory(0, runHistory); // Dummy run of white
507
- if (QrCode.hasFinderLikePattern(runHistory)) result += QrCode.PENALTY_N3;
508
- }
509
- // Adjacent modules in column having same color, and finder-like patterns
510
- for (let x = 0; x < this.size; x++) {
511
- let runHistory = [0, 0, 0, 0, 0, 0, 0];
512
- let color = false;
513
- let runY = 0;
514
- for (let y = 0; y < this.size; y++) {
515
- if (this.modules[y][x] == color) {
516
- runY++;
517
- if (runY == 5) result += QrCode.PENALTY_N1;
518
- else if (runY > 5) result++;
519
- } else {
520
- QrCode.addRunToHistory(runY, runHistory);
521
- if (!color && QrCode.hasFinderLikePattern(runHistory)) result += QrCode.PENALTY_N3;
522
- color = this.modules[y][x];
523
- runY = 1;
524
- }
525
- }
526
- QrCode.addRunToHistory(runY, runHistory);
527
- if (color) QrCode.addRunToHistory(0, runHistory); // Dummy run of white
528
- if (QrCode.hasFinderLikePattern(runHistory)) result += QrCode.PENALTY_N3;
529
- }
530
-
531
- // 2*2 blocks of modules having same color
532
- for (let y = 0; y < this.size - 1; y++) {
533
- for (let x = 0; x < this.size - 1; x++) {
534
- const color: boolean = this.modules[y][x];
535
- if (color == this.modules[y][x + 1] && color == this.modules[y + 1][x] && color == this.modules[y + 1][x + 1])
536
- result += QrCode.PENALTY_N2;
408
+ // Calculates and returns the penalty score based on state of this QR Code's current modules.
409
+ // This is used by the automatic mask choice algorithm to find the mask pattern that yields the lowest score.
410
+ function getPenaltyScore(modules: boolean[][], size: int): int {
411
+ let result: int = 0;
412
+
413
+ // Adjacent modules in row having same color, and finder-like patterns
414
+ for (let y = 0; y < size; y++) {
415
+ const runHistory = [0, 0, 0, 0, 0, 0, 0];
416
+ let color = false;
417
+ let runX = 0;
418
+ for (let x = 0; x < size; x++) {
419
+ if (modules[y][x] === color) {
420
+ runX++;
421
+ if (runX === 5) result += PENALTY_N1;
422
+ else if (runX > 5) result++;
423
+ } else {
424
+ addRunToHistory(runX, runHistory);
425
+ if (!color && hasFinderLikePattern(runHistory)) result += PENALTY_N3;
426
+ color = modules[y][x];
427
+ runX = 1;
537
428
  }
538
429
  }
539
-
540
- // Balance of black and white modules
541
- let black: int = 0;
542
- for (const row of this.modules) {
543
- for (const color of row) {
544
- if (color) black++;
430
+ addRunToHistory(runX, runHistory);
431
+ if (color) addRunToHistory(0, runHistory); // Dummy run of white
432
+ if (hasFinderLikePattern(runHistory)) result += PENALTY_N3;
433
+ }
434
+ // Adjacent modules in column having same color, and finder-like patterns
435
+ for (let x = 0; x < size; x++) {
436
+ const runHistory = [0, 0, 0, 0, 0, 0, 0];
437
+ let color = false;
438
+ let runY = 0;
439
+ for (let y = 0; y < size; y++) {
440
+ if (modules[y][x] === color) {
441
+ runY++;
442
+ if (runY === 5) result += PENALTY_N1;
443
+ else if (runY > 5) result++;
444
+ } else {
445
+ addRunToHistory(runY, runHistory);
446
+ if (!color && hasFinderLikePattern(runHistory)) result += PENALTY_N3;
447
+ color = modules[y][x];
448
+ runY = 1;
545
449
  }
546
450
  }
547
- const total: int = this.size * this.size; // Note that size is odd, so black/total != 1/2
548
- // Compute the smallest integer k >= 0 such that (45-5k)% <= black/total <= (55+5k)%
549
- const k: int = Math.ceil(Math.abs(black * 20 - total * 10) / total) - 1;
550
- result += k * QrCode.PENALTY_N4;
551
- return result;
552
- }
553
-
554
- /*-- Private helper functions --*/
555
-
556
- // Returns an ascending list of positions of alignment patterns for this version number.
557
- // Each position is in the range [0,177), and are used on both the x and y axes.
558
- // This could be implemented as lookup table of 40 variable-length lists of integers.
559
- private getAlignmentPatternPositions(): Array<int> {
560
- if (this.version == 1) return [];
561
- else {
562
- const numAlign: int = Math.floor(this.version / 7) + 2;
563
- const step: int = this.version == 32 ? 26 : Math.ceil((this.size - 13) / (numAlign * 2 - 2)) * 2;
564
- let result: Array<int> = [6];
565
- for (let pos = this.size - 7; result.length < numAlign; pos -= step) result.splice(1, 0, pos);
566
- return result;
451
+ addRunToHistory(runY, runHistory);
452
+ if (color) addRunToHistory(0, runHistory); // Dummy run of white
453
+ if (hasFinderLikePattern(runHistory)) result += PENALTY_N3;
454
+ }
455
+
456
+ // 2*2 blocks of modules having same color
457
+ for (let y = 0; y < size - 1; y++) {
458
+ for (let x = 0; x < size - 1; x++) {
459
+ const color: boolean = modules[y][x];
460
+ if (
461
+ color === modules[y][x + 1] &&
462
+ color === modules[y + 1][x] &&
463
+ color === modules[y + 1][x + 1]
464
+ )
465
+ result += PENALTY_N2;
567
466
  }
568
467
  }
569
468
 
570
- // Returns the number of data bits that can be stored in a QR Code of the given version number, after
571
- // all function modules are excluded. This includes remainder bits, so it might not be a multiple of 8.
572
- // The result is in the range [208, 29648]. This could be implemented as a 40-entry lookup table.
573
- private static getNumRawDataModules(ver: int): int {
574
- if (ver < QrCode.MIN_VERSION || ver > QrCode.MAX_VERSION) throw 'Version number out of range';
575
- let result: int = (16 * ver + 128) * ver + 64;
576
- if (ver >= 2) {
577
- const numAlign: int = Math.floor(ver / 7) + 2;
578
- result -= (25 * numAlign - 10) * numAlign - 55;
579
- if (ver >= 7) result -= 36;
469
+ // Balance of black and white modules
470
+ let black: int = 0;
471
+ for (const row of modules) {
472
+ for (const color of row) {
473
+ if (color) black++;
580
474
  }
581
- return result;
582
475
  }
476
+ const total: int = size * size; // Note that size is odd, so black/total != 1/2
477
+ // Compute the smallest integer k >= 0 such that (45-5k)% <= black/total <= (55+5k)%
478
+ const k: int = Math.ceil(Math.abs(black * 20 - total * 10) / total) - 1;
479
+ result += k * PENALTY_N4;
480
+ return result;
481
+ }
583
482
 
584
- // Returns the number of 8-bit data (i.e. not error correction) codewords contained in any
585
- // QR Code of the given version number and error correction level, with remainder bits discarded.
586
- // This stateless pure function could be implemented as a (40*4)-cell lookup table.
587
- private static getNumDataCodewords(ver: int, ecl: QrCode.Ecc): int {
588
- return (
589
- Math.floor(QrCode.getNumRawDataModules(ver) / 8) -
590
- QrCode.ECC_CODEWORDS_PER_BLOCK[ecl.ordinal][ver] * QrCode.NUM_ERROR_CORRECTION_BLOCKS[ecl.ordinal][ver]
591
- );
592
- }
483
+ // Inserts the given value to the front of the given array, which shifts over the
484
+ // existing values and deletes the last value. A helper function for getPenaltyScore().
485
+ function addRunToHistory(run: int, history: int[]): void {
486
+ history.pop();
487
+ history.unshift(run);
488
+ }
593
489
 
594
- // Inserts the given value to the front of the given array, which shifts over the
595
- // existing values and deletes the last value. A helper function for getPenaltyScore().
596
- private static addRunToHistory(run: int, history: Array<int>): void {
597
- history.pop();
598
- history.unshift(run);
599
- }
490
+ // Tests whether the given run history has the pattern of ratio 1:1:3:1:1 in the middle, and
491
+ // surrounded by at least 4 on either or both ends. A helper function for getPenaltyScore().
492
+ // Must only be called immediately after a run of white modules has ended.
493
+ function hasFinderLikePattern(runHistory: int[]): boolean {
494
+ const n: int = runHistory[1];
495
+ return (
496
+ n > 0 &&
497
+ runHistory[2] === n &&
498
+ runHistory[4] === n &&
499
+ runHistory[5] === n &&
500
+ runHistory[3] === n * 3 &&
501
+ Math.max(runHistory[0], runHistory[6]) >= n * 4
502
+ );
503
+ }
600
504
 
601
- // Tests whether the given run history has the pattern of ratio 1:1:3:1:1 in the middle, and
602
- // surrounded by at least 4 on either or both ends. A helper function for getPenaltyScore().
603
- // Must only be called immediately after a run of white modules has ended.
604
- private static hasFinderLikePattern(runHistory: Array<int>): boolean {
605
- const n: int = runHistory[1];
606
- return (
607
- n > 0 &&
608
- runHistory[2] == n &&
609
- runHistory[4] == n &&
610
- runHistory[5] == n &&
611
- runHistory[3] == n * 3 &&
612
- Math.max(runHistory[0], runHistory[6]) >= n * 4
505
+ // Returns a new byte string representing the given data with the appropriate error correction
506
+ // codewords appended to it, based on this object's version and error correction level.
507
+ function addEccAndInterleave(version: int, ecl: QrCodeEccValue, data: byte[]): byte[] {
508
+ if (data.length !== getNumDataCodewords(version, ecl)) throw 'Invalid argument';
509
+
510
+ // Calculate parameter numbers
511
+ const numBlocks: int = NUM_ERROR_CORRECTION_BLOCKS[ecl.ordinal][version];
512
+ const blockEccLen: int = ECC_CODEWORDS_PER_BLOCK[ecl.ordinal][version];
513
+ const rawCodewords: int = Math.floor(getNumRawDataModules(version) / 8);
514
+ const numShortBlocks: int = numBlocks - (rawCodewords % numBlocks);
515
+ const shortBlockLen: int = Math.floor(rawCodewords / numBlocks);
516
+
517
+ // Split data into blocks and append ECC to each block
518
+ const blocks: byte[][] = [];
519
+ const rs = createReedSolomonGenerator(blockEccLen);
520
+ for (let i = 0, k = 0; i < numBlocks; i++) {
521
+ const dat: byte[] = data.slice(
522
+ k,
523
+ k + shortBlockLen - blockEccLen + (i < numShortBlocks ? 0 : 1),
613
524
  );
525
+ k += dat.length;
526
+ const ecc: byte[] = rs.getRemainder(dat);
527
+ if (i < numShortBlocks) dat.push(0);
528
+ blocks.push(dat.concat(ecc));
529
+ }
530
+
531
+ // Interleave (not concatenate) the bytes from every block into a single sequence
532
+ const result: byte[] = [];
533
+ for (let i = 0; i < blocks[0].length; i++) {
534
+ for (let j = 0; j < blocks.length; j++) {
535
+ // Skip the padding byte in short blocks
536
+ if (i !== shortBlockLen - blockEccLen || j >= numShortBlocks) result.push(blocks[j][i]);
537
+ }
614
538
  }
539
+ if (result.length !== rawCodewords) throw 'Assertion error';
540
+ return result;
541
+ }
542
+
543
+ // Returns the number of data bits that can be stored in a QR Code of the given version number, after
544
+ // all function modules are excluded. This includes remainder bits, so it might not be a multiple of 8.
545
+ // The result is in the range [208, 29648]. This could be implemented as a 40-entry lookup table.
546
+ function getNumRawDataModules(ver: int): int {
547
+ if (ver < MIN_VERSION || ver > MAX_VERSION) throw 'Version number out of range';
548
+ let result: int = (16 * ver + 128) * ver + 64;
549
+ if (ver >= 2) {
550
+ const numAlign: int = Math.floor(ver / 7) + 2;
551
+ result -= (25 * numAlign - 10) * numAlign - 55;
552
+ if (ver >= 7) result -= 36;
553
+ }
554
+ return result;
555
+ }
615
556
 
616
- /*-- Constants and tables --*/
617
-
618
- // The minimum version number supported in the QR Code Model 2 standard.
619
- public static readonly MIN_VERSION: int = 1;
620
- // The maximum version number supported in the QR Code Model 2 standard.
621
- public static readonly MAX_VERSION: int = 40;
622
-
623
- // For use in getPenaltyScore(), when evaluating which mask is best.
624
- private static readonly PENALTY_N1: int = 3;
625
- private static readonly PENALTY_N2: int = 3;
626
- private static readonly PENALTY_N3: int = 40;
627
- private static readonly PENALTY_N4: int = 10;
628
-
629
- private static readonly ECC_CODEWORDS_PER_BLOCK: Array<Array<int>> = [
630
- /* eslint-disable */
631
- // Version: (note that index 0 is for padding, and is set to an illegal value)
632
- //0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level
633
- // prettier-ignore
634
- [-1, 7, 10, 15, 20, 26, 18, 20, 24, 30, 18, 20, 24, 26, 30, 22, 24, 28, 30, 28, 28, 28, 28, 30, 30, 26, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30], // Low
635
- // prettier-ignore
636
- [-1, 10, 16, 26, 18, 24, 16, 18, 22, 22, 26, 30, 22, 22, 24, 24, 28, 28, 26, 26, 26, 26, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28], // Medium
637
- // prettier-ignore
638
- [-1, 13, 22, 18, 26, 18, 24, 18, 22, 20, 24, 28, 26, 24, 20, 30, 24, 28, 28, 26, 30, 28, 30, 30, 30, 30, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30], // Quartile
639
- // prettier-ignore
640
- [-1, 17, 28, 22, 16, 22, 28, 26, 26, 24, 28, 24, 28, 22, 24, 24, 30, 28, 28, 26, 28, 30, 24, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30] // High
641
- /* eslint-enable */
642
- ];
643
-
644
- private static readonly NUM_ERROR_CORRECTION_BLOCKS: Array<Array<int>> = [
645
- /* eslint-disable */
646
- // Version: (note that index 0 is for padding, and is set to an illegal value)
647
- //0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level
648
- // prettier-ignore
649
- [-1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25], // Low
650
- // prettier-ignore
651
- [-1, 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49 ], // Medium
652
- // prettier-ignore
653
- [-1, 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68], // Quartile
654
- // prettier-ignore
655
- [-1, 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81] // High
656
- /* eslint-enable */
657
- ];
557
+ // Returns the number of 8-bit data (i.e. not error correction) codewords contained in any
558
+ // QR Code of the given version number and error correction level, with remainder bits discarded.
559
+ // This stateless pure function could be implemented as a (40*4)-cell lookup table.
560
+ function getNumDataCodewords(ver: int, ecl: QrCodeEccValue): int {
561
+ return (
562
+ Math.floor(getNumRawDataModules(ver) / 8) -
563
+ ECC_CODEWORDS_PER_BLOCK[ecl.ordinal][ver] * NUM_ERROR_CORRECTION_BLOCKS[ecl.ordinal][ver]
564
+ );
658
565
  }
659
566
 
660
567
  // Returns true iff the i'th bit of x is set to 1.
661
568
  function getBit(x: int, i: int): boolean {
662
- return ((x >>> i) & 1) != 0;
569
+ return ((x >>> i) & 1) !== 0;
663
570
  }
664
571
 
665
- /*---- Data segment class ----*/
572
+ /*---- Data segment type and factory functions ----*/
666
573
 
667
574
  /*
668
575
  * A segment of character/binary/control data in a QR Code symbol.
669
- * Instances of this class are immutable.
670
576
  * The mid-level way to create a segment is to take the payload data
671
- * and call a static factory function such as QrSegment.makeNumeric().
577
+ * and call a factory function such as makeNumeric().
672
578
  * The low-level way to create a segment is to custom-make the bit buffer
673
- * and call the QrSegment() constructor with appropriate values.
674
- * This segment class imposes no length restrictions, but QR Codes have restrictions.
579
+ * and call createQrSegment() with appropriate values.
580
+ * This segment imposes no length restrictions, but QR Codes have restrictions.
675
581
  * Even in the most favorable conditions, a QR Code can only hold 7089 characters of data.
676
582
  * Any segment longer than this is meaningless for the purpose of generating QR Codes.
677
583
  */
678
- export class QrSegment {
679
- /*-- Static factory functions (mid level) --*/
680
-
681
- // Returns a segment representing the given binary data encoded in
682
- // byte mode. All input byte arrays are acceptable. Any text string
683
- // can be converted to UTF-8 bytes and encoded as a byte mode segment.
684
- public static makeBytes(data: Array<byte>): QrSegment {
685
- let bb = new BitBuffer();
686
- for (const b of data) bb.appendBits(b, 8);
687
- return new QrSegment(QrSegment.Mode.BYTE, data.length, bb.array);
688
- }
689
-
690
- // Returns a segment representing the given string of decimal digits encoded in numeric mode.
691
- public static makeNumeric(digits: string): QrSegment {
692
- if (!this.NUMERIC_REGEX.test(digits)) throw 'String contains non-numeric characters';
693
- let bb = new BitBuffer();
694
- for (let i = 0; i < digits.length; ) {
695
- // Consume up to 3 digits per iteration
696
- const n: int = Math.min(digits.length - i, 3);
697
- bb.appendBits(parseInt(digits.substr(i, n), 10), n * 3 + 1);
698
- i += n;
699
- }
700
- return new QrSegment(QrSegment.Mode.NUMERIC, digits.length, bb.array);
701
- }
702
-
703
- // Returns a segment representing the given text string encoded in alphanumeric mode.
704
- // The characters allowed are: 0 to 9, A to Z (uppercase only), space,
705
- // dollar, percent, asterisk, plus, hyphen, period, slash, colon.
706
- public static makeAlphanumeric(text: string): QrSegment {
707
- if (!this.ALPHANUMERIC_REGEX.test(text)) throw 'String contains unencodable characters in alphanumeric mode';
708
- let bb = new BitBuffer();
709
- let i: int;
710
- for (i = 0; i + 2 <= text.length; i += 2) {
711
- // Process groups of 2
712
- let temp: int = QrSegment.ALPHANUMERIC_CHARSET.indexOf(text.charAt(i)) * 45;
713
- temp += QrSegment.ALPHANUMERIC_CHARSET.indexOf(text.charAt(i + 1));
714
- bb.appendBits(temp, 11);
715
- }
716
- if (i < text.length)
717
- // 1 character remaining
718
- bb.appendBits(QrSegment.ALPHANUMERIC_CHARSET.indexOf(text.charAt(i)), 6);
719
- return new QrSegment(QrSegment.Mode.ALPHANUMERIC, text.length, bb.array);
720
- }
584
+ export interface QrSegment {
585
+ /** The mode indicator of this segment. */
586
+ readonly mode: QrSegmentModeValue;
587
+ /** The length of this segment's unencoded data. Measured in characters for
588
+ * numeric/alphanumeric/kanji mode, bytes for byte mode, and 0 for ECI mode.
589
+ * Always zero or positive. Not the same as the data's bit length.
590
+ */
591
+ readonly numChars: int;
592
+ /** Returns a new copy of the data bits of this segment. */
593
+ getData(): bit[];
594
+ }
721
595
 
722
- // Returns a new mutable list of zero or more segments to represent the given Unicode text string.
723
- // The result may use various segment modes and switch modes to optimize the length of the bit stream.
724
- public static makeSegments(text: string): Array<QrSegment> {
725
- // Select the most efficient segment encoding automatically
726
- if (text == '') return [];
727
- else if (this.NUMERIC_REGEX.test(text)) return [QrSegment.makeNumeric(text)];
728
- else if (this.ALPHANUMERIC_REGEX.test(text)) return [QrSegment.makeAlphanumeric(text)];
729
- else return [QrSegment.makeBytes(QrSegment.toUtf8ByteArray(text))];
730
- }
596
+ // Creates a new QR Code segment with the given attributes and data.
597
+ // The character count (numChars) must agree with the mode and the bit buffer length,
598
+ // but the constraint isn't checked. The given bit buffer is cloned and stored.
599
+ function createQrSegment(mode: QrSegmentModeValue, numChars: int, bitData: bit[]): QrSegment {
600
+ if (numChars < 0) throw 'Invalid argument';
601
+ const data = bitData.slice(); // Make defensive copy
602
+ return {
603
+ mode,
604
+ numChars,
605
+ getData: () => data.slice(), // Make defensive copy
606
+ };
607
+ }
731
608
 
732
- // Returns a segment representing an Extended Channel Interpretation
733
- // (ECI) designator with the given assignment value.
734
- public static makeEci(assignVal: int): QrSegment {
735
- let bb = new BitBuffer();
736
- if (assignVal < 0) throw 'ECI assignment value out of range';
737
- else if (assignVal < 1 << 7) bb.appendBits(assignVal, 8);
738
- else if (assignVal < 1 << 14) {
739
- bb.appendBits(2, 2);
740
- bb.appendBits(assignVal, 14);
741
- } else if (assignVal < 1000000) {
742
- bb.appendBits(6, 3);
743
- bb.appendBits(assignVal, 21);
744
- } else throw 'ECI assignment value out of range';
745
- return new QrSegment(QrSegment.Mode.ECI, 0, bb.array);
746
- }
609
+ // Returns a segment representing the given binary data encoded in
610
+ // byte mode. All input byte arrays are acceptable. Any text string
611
+ // can be converted to UTF-8 bytes and encoded as a byte mode segment.
612
+ function makeBytes(data: byte[]): QrSegment {
613
+ const bb = createBitBuffer();
614
+ for (const b of data) bb.appendBits(b, 8);
615
+ return createQrSegment(QrSegmentMode.BYTE, data.length, bb.array);
616
+ }
747
617
 
748
- /*-- Constructor (low level) and fields --*/
749
-
750
- // Creates a new QR Code segment with the given attributes and data.
751
- // The character count (numChars) must agree with the mode and the bit buffer length,
752
- // but the constraint isn't checked. The given bit buffer is cloned and stored.
753
- public constructor(
754
- // The mode indicator of this segment.
755
- public readonly mode: QrSegment.Mode,
756
-
757
- // The length of this segment's unencoded data. Measured in characters for
758
- // numeric/alphanumeric/kanji mode, bytes for byte mode, and 0 for ECI mode.
759
- // Always zero or positive. Not the same as the data's bit length.
760
- public readonly numChars: int,
761
-
762
- // The data bits of this segment. Accessed through getData().
763
- private readonly bitData: Array<bit>
764
- ) {
765
- if (numChars < 0) throw 'Invalid argument';
766
- this.bitData = bitData.slice(); // Make defensive copy
767
- }
618
+ // Returns a segment representing the given string of decimal digits encoded in numeric mode.
619
+ function makeNumeric(digits: string): QrSegment {
620
+ if (!NUMERIC_REGEX.test(digits)) throw 'String contains non-numeric characters';
621
+ const bb = createBitBuffer();
622
+ for (let i = 0; i < digits.length; ) {
623
+ // Consume up to 3 digits per iteration
624
+ const n: int = Math.min(digits.length - i, 3);
625
+ bb.appendBits(parseInt(digits.substr(i, n), 10), n * 3 + 1);
626
+ i += n;
627
+ }
628
+ return createQrSegment(QrSegmentMode.NUMERIC, digits.length, bb.array);
629
+ }
768
630
 
769
- /*-- Methods --*/
631
+ // Returns a segment representing the given text string encoded in alphanumeric mode.
632
+ // The characters allowed are: 0 to 9, A to Z (uppercase only), space,
633
+ // dollar, percent, asterisk, plus, hyphen, period, slash, colon.
634
+ function makeAlphanumeric(text: string): QrSegment {
635
+ if (!ALPHANUMERIC_REGEX.test(text))
636
+ throw 'String contains unencodable characters in alphanumeric mode';
637
+ const bb = createBitBuffer();
638
+ let i: int;
639
+ for (i = 0; i + 2 <= text.length; i += 2) {
640
+ // Process groups of 2
641
+ let temp: int = ALPHANUMERIC_CHARSET.indexOf(text.charAt(i)) * 45;
642
+ temp += ALPHANUMERIC_CHARSET.indexOf(text.charAt(i + 1));
643
+ bb.appendBits(temp, 11);
644
+ }
645
+ if (i < text.length)
646
+ // 1 character remaining
647
+ bb.appendBits(ALPHANUMERIC_CHARSET.indexOf(text.charAt(i)), 6);
648
+ return createQrSegment(QrSegmentMode.ALPHANUMERIC, text.length, bb.array);
649
+ }
770
650
 
771
- // Returns a new copy of the data bits of this segment.
772
- public getData(): Array<bit> {
773
- return this.bitData.slice(); // Make defensive copy
774
- }
651
+ // Returns a new mutable list of zero or more segments to represent the given Unicode text string.
652
+ // The result may use various segment modes and switch modes to optimize the length of the bit stream.
653
+ export function makeSegments(text: string): QrSegment[] {
654
+ // Select the most efficient segment encoding automatically
655
+ if (text === '') return [];
656
+ else if (NUMERIC_REGEX.test(text)) return [makeNumeric(text)];
657
+ else if (ALPHANUMERIC_REGEX.test(text)) return [makeAlphanumeric(text)];
658
+ else return [makeBytes(toUtf8ByteArray(text))];
659
+ }
775
660
 
776
- // (Package-private) Calculates and returns the number of bits needed to encode the given segments at
777
- // the given version. The result is infinity if a segment has too many characters to fit its length field.
778
- public static getTotalBits(segs: Array<QrSegment>, version: int): number {
779
- let result: number = 0;
780
- for (const seg of segs) {
781
- const ccbits: int = seg.mode.numCharCountBits(version);
782
- if (seg.numChars >= 1 << ccbits) return Infinity; // The segment's length doesn't fit the field's bit width
783
- result += 4 + ccbits + seg.bitData.length;
784
- }
785
- return result;
661
+ // Calculates and returns the number of bits needed to encode the given segments at
662
+ // the given version. The result is infinity if a segment has too many characters to fit its length field.
663
+ function getTotalBits(segs: QrSegment[], version: int): number {
664
+ let result: number = 0;
665
+ for (const seg of segs) {
666
+ const ccbits: int = numCharCountBits(seg.mode, version);
667
+ if (seg.numChars >= 1 << ccbits) return Infinity; // The segment's length doesn't fit the field's bit width
668
+ result += 4 + ccbits + seg.getData().length;
786
669
  }
670
+ return result;
671
+ }
787
672
 
788
- // Returns a new array of bytes representing the given string encoded in UTF-8.
789
- private static toUtf8ByteArray(str: string): Array<byte> {
790
- str = encodeURI(str);
791
- let result: Array<byte> = [];
792
- for (let i = 0; i < str.length; i++) {
793
- if (str.charAt(i) != '%') result.push(str.charCodeAt(i));
794
- else {
795
- result.push(parseInt(str.substr(i + 1, 2), 16));
796
- i += 2;
797
- }
673
+ // Returns a new array of bytes representing the given string encoded in UTF-8.
674
+ function toUtf8ByteArray(str: string): byte[] {
675
+ str = encodeURI(str);
676
+ const result: byte[] = [];
677
+ for (let i = 0; i < str.length; i++) {
678
+ if (str.charAt(i) !== '%') result.push(str.charCodeAt(i));
679
+ else {
680
+ result.push(parseInt(str.substr(i + 1, 2), 16));
681
+ i += 2;
798
682
  }
799
- return result;
800
683
  }
684
+ return result;
685
+ }
801
686
 
802
- /*-- Constants --*/
803
-
804
- // Describes precisely all strings that are encodable in numeric mode. To test
805
- // whether a string s is encodable: let ok: boolean = NUMERIC_REGEX.test(s);
806
- // A string is encodable iff each character is in the range 0 to 9.
807
- public static readonly NUMERIC_REGEX: RegExp = /^[0-9]*$/;
687
+ /*---- Private helper: BitBuffer ----*/
808
688
 
809
- // Describes precisely all strings that are encodable in alphanumeric mode. To test
810
- // whether a string s is encodable: let ok: boolean = ALPHANUMERIC_REGEX.test(s);
811
- // A string is encodable iff each character is in the following set: 0 to 9, A to Z
812
- // (uppercase only), space, dollar, percent, asterisk, plus, hyphen, period, slash, colon.
813
- public static readonly ALPHANUMERIC_REGEX: RegExp = /^[A-Z0-9 $%*+.\/:-]*$/;
689
+ /*
690
+ * An appendable sequence of bits (0s and 1s). Mainly used by QrSegment.
691
+ */
692
+ interface BitBuffer {
693
+ array: bit[];
694
+ appendBits(val: int, len: int): void;
695
+ }
814
696
 
815
- // The set of all legal characters in alphanumeric mode,
816
- // where each character value maps to the index in the string.
817
- private static readonly ALPHANUMERIC_CHARSET: string = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:';
697
+ function createBitBuffer(): BitBuffer {
698
+ const array: bit[] = [];
699
+ return {
700
+ array,
701
+ appendBits(val: int, len: int): void {
702
+ if (len < 0 || len > 31 || val >>> len !== 0) throw 'Value out of range';
703
+ for (let i = len - 1; i >= 0; i--)
704
+ // Append bit by bit
705
+ array.push((val >>> i) & 1);
706
+ },
707
+ };
818
708
  }
819
709
 
820
- /*---- Private helper classes ----*/
710
+ /*---- Private helper: ReedSolomonGenerator ----*/
821
711
 
822
712
  /*
823
713
  * Computes the Reed-Solomon error correction codewords for a sequence of data codewords
824
714
  * at a given degree. Objects are immutable, and the state only depends on the degree.
825
- * This class exists because each data block in a QR Code shares the same the divisor polynomial.
715
+ * This exists because each data block in a QR Code shares the same the divisor polynomial.
826
716
  */
827
- class ReedSolomonGenerator {
828
- // Coefficients of the divisor polynomial, stored from highest to lowest power, excluding the leading term which
829
- // is always 1. For example the polynomial x^3 + 255x^2 + 8x + 93 is stored as the uint8 array {255, 8, 93}.
830
- private readonly coefficients: Array<byte> = [];
831
-
832
- // Creates a Reed-Solomon ECC generator for the given degree. This could be implemented
833
- // as a lookup table over all possible parameter values, instead of as an algorithm.
834
- public constructor(degree: int) {
835
- if (degree < 1 || degree > 255) throw 'Degree out of range';
836
- let coefs = this.coefficients;
837
-
838
- // Start with the monomial x^0
839
- for (let i = 0; i < degree - 1; i++) coefs.push(0);
840
- coefs.push(1);
841
-
842
- // Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x - r^{degree-1}),
843
- // drop the highest term, and store the rest of the coefficients in order of descending powers.
844
- // Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D).
845
- let root = 1;
846
- for (let i = 0; i < degree; i++) {
847
- // Multiply the current product by (x - r^i)
848
- for (let j = 0; j < coefs.length; j++) {
849
- coefs[j] = ReedSolomonGenerator.multiply(coefs[j], root);
850
- if (j + 1 < coefs.length) coefs[j] ^= coefs[j + 1];
851
- }
852
- root = ReedSolomonGenerator.multiply(root, 0x02);
853
- }
854
- }
855
-
856
- // Computes and returns the Reed-Solomon error correction codewords for the given
857
- // sequence of data codewords. The returned object is always a new byte array.
858
- // This method does not alter this object's state (because it is immutable).
859
- public getRemainder(data: Array<byte>): Array<byte> {
860
- // Compute the remainder by performing polynomial division
861
- let result: Array<byte> = this.coefficients.map(_ => 0);
862
- for (const b of data) {
863
- const factor: byte = b ^ (result.shift() as byte);
864
- result.push(0);
865
- this.coefficients.forEach((coef, i) => (result[i] ^= ReedSolomonGenerator.multiply(coef, factor)));
866
- }
867
- return result;
868
- }
869
-
870
- // Returns the product of the two given field elements modulo GF(2^8/0x11D). The arguments and result
871
- // are unsigned 8-bit integers. This could be implemented as a lookup table of 256*256 entries of uint8.
872
- private static multiply(x: byte, y: byte): byte {
873
- if (x >>> 8 != 0 || y >>> 8 != 0) throw 'Byte out of range';
874
- // Russian peasant multiplication
875
- let z: int = 0;
876
- for (let i = 7; i >= 0; i--) {
877
- z = (z << 1) ^ ((z >>> 7) * 0x11d);
878
- z ^= ((y >>> i) & 1) * x;
879
- }
880
- if (z >>> 8 != 0) throw 'Assertion error';
881
- return z as byte;
882
- }
717
+ interface ReedSolomonGenerator {
718
+ getRemainder(data: byte[]): byte[];
883
719
  }
884
720
 
885
- /*
886
- * An appendable sequence of bits (0s and 1s). Mainly used by QrSegment.
887
- * The implicit constructor creates an empty bit buffer (length 0).
888
- */
889
- class BitBuffer {
890
- public array: Array<bit> = [];
891
-
892
- // Appends the given number of low-order bits of the given value
893
- // to this buffer. Requires 0 <= len <= 31 and 0 <= val < 2^len.
894
- public appendBits(val: int, len: int): void {
895
- if (len < 0 || len > 31 || val >>> len != 0) throw 'Value out of range';
896
- for (
897
- let i = len - 1;
898
- i >= 0;
899
- i-- // Append bit by bit
900
- )
901
- this.array.push((val >>> i) & 1);
902
- }
721
+ function createReedSolomonGenerator(degree: int): ReedSolomonGenerator {
722
+ if (degree < 1 || degree > 255) throw 'Degree out of range';
723
+ const coefficients: byte[] = [];
724
+
725
+ // Start with the monomial x^0
726
+ for (let i = 0; i < degree - 1; i++) coefficients.push(0);
727
+ coefficients.push(1);
728
+
729
+ // Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x - r^{degree-1}),
730
+ // drop the highest term, and store the rest of the coefficients in order of descending powers.
731
+ // Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D).
732
+ let root = 1;
733
+ for (let i = 0; i < degree; i++) {
734
+ // Multiply the current product by (x - r^i)
735
+ for (let j = 0; j < coefficients.length; j++) {
736
+ coefficients[j] = rsMultiply(coefficients[j], root);
737
+ if (j + 1 < coefficients.length) coefficients[j] ^= coefficients[j + 1];
738
+ }
739
+ root = rsMultiply(root, 0x02);
740
+ }
741
+
742
+ return {
743
+ getRemainder(data: byte[]): byte[] {
744
+ // Compute the remainder by performing polynomial division
745
+ const result: byte[] = coefficients.map(() => 0);
746
+ for (const b of data) {
747
+ const factor: byte = b ^ (result.shift() as byte);
748
+ result.push(0);
749
+ coefficients.forEach((coef, i) => {
750
+ result[i] ^= rsMultiply(coef, factor);
751
+ });
752
+ }
753
+ return result;
754
+ },
755
+ };
903
756
  }
904
757
 
905
- /*---- Public helper enumeration ----*/
906
-
907
- export namespace QrCode {
908
- type int = number;
909
-
910
- /*
911
- * The error correction level in a QR Code symbol. Immutable.
912
- */
913
- export class Ecc {
914
- /*-- Constants --*/
915
-
916
- public static readonly LOW = new Ecc(0, 1); // The QR Code can tolerate about 7% erroneous codewords
917
- public static readonly MEDIUM = new Ecc(1, 0); // The QR Code can tolerate about 15% erroneous codewords
918
- public static readonly QUARTILE = new Ecc(2, 3); // The QR Code can tolerate about 25% erroneous codewords
919
- public static readonly HIGH = new Ecc(3, 2); // The QR Code can tolerate about 30% erroneous codewords
920
-
921
- /*-- Constructor and fields --*/
922
-
923
- private constructor(
924
- // In the range 0 to 3 (unsigned 2-bit integer).
925
- public readonly ordinal: int,
926
- // (Package-private) In the range 0 to 3 (unsigned 2-bit integer).
927
- public readonly formatBits: int
928
- ) {}
929
- }
758
+ // Returns the product of the two given field elements modulo GF(2^8/0x11D). The arguments and result
759
+ // are unsigned 8-bit integers. This could be implemented as a lookup table of 256*256 entries of uint8.
760
+ function rsMultiply(x: byte, y: byte): byte {
761
+ if (x >>> 8 !== 0 || y >>> 8 !== 0) throw 'Byte out of range';
762
+ // Russian peasant multiplication
763
+ let z: int = 0;
764
+ for (let i = 7; i >= 0; i--) {
765
+ z = (z << 1) ^ ((z >>> 7) * 0x11d);
766
+ z ^= ((y >>> i) & 1) * x;
767
+ }
768
+ if (z >>> 8 !== 0) throw 'Assertion error';
769
+ return z as byte;
930
770
  }
931
771
 
932
- /*---- Public helper enumeration ----*/
933
-
934
- export namespace QrSegment {
935
- type int = number;
936
-
937
- /*
938
- * Describes how a segment's data bits are interpreted. Immutable.
939
- */
940
- export class Mode {
941
- /*-- Constants --*/
942
-
943
- public static readonly NUMERIC = new Mode(0x1, [10, 12, 14]);
944
- public static readonly ALPHANUMERIC = new Mode(0x2, [9, 11, 13]);
945
- public static readonly BYTE = new Mode(0x4, [8, 16, 16]);
946
- public static readonly KANJI = new Mode(0x8, [8, 10, 12]);
947
- public static readonly ECI = new Mode(0x7, [0, 0, 0]);
948
-
949
- /*-- Constructor and fields --*/
950
-
951
- private constructor(
952
- // The mode indicator bits, which is a uint4 value (range 0 to 15).
953
- public readonly modeBits: int,
954
- // Number of character count bits for three different version ranges.
955
- private readonly numBitsCharCount: [int, int, int]
956
- ) {}
957
-
958
- /*-- Method --*/
959
-
960
- // (Package-private) Returns the bit width of the character count field for a segment in
961
- // this mode in a QR Code at the given version number. The result is in the range [0, 16].
962
- public numCharCountBits(ver: int): int {
963
- return this.numBitsCharCount[Math.floor((ver + 7) / 17)];
964
- }
965
- }
966
- }
772
+ /*---- Constants and tables ----*/
773
+
774
+ // The minimum version number supported in the QR Code Model 2 standard.
775
+ const MIN_VERSION: int = 1;
776
+ // The maximum version number supported in the QR Code Model 2 standard.
777
+ const MAX_VERSION: int = 40;
778
+
779
+ // For use in getPenaltyScore(), when evaluating which mask is best.
780
+ const PENALTY_N1: int = 3;
781
+ const PENALTY_N2: int = 3;
782
+ const PENALTY_N3: int = 40;
783
+ const PENALTY_N4: int = 10;
784
+
785
+ const ECC_CODEWORDS_PER_BLOCK: int[][] = [
786
+ // Version: (note that index 0 is for padding, and is set to an illegal value)
787
+ //0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level
788
+ [
789
+ -1, 7, 10, 15, 20, 26, 18, 20, 24, 30, 18, 20, 24, 26, 30, 22, 24, 28, 30, 28, 28, 28, 28, 30,
790
+ 30, 26, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30,
791
+ ], // Low
792
+ [
793
+ -1, 10, 16, 26, 18, 24, 16, 18, 22, 22, 26, 30, 22, 22, 24, 24, 28, 28, 26, 26, 26, 26, 28, 28,
794
+ 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
795
+ ], // Medium
796
+ [
797
+ -1, 13, 22, 18, 26, 18, 24, 18, 22, 20, 24, 28, 26, 24, 20, 30, 24, 28, 28, 26, 30, 28, 30, 30,
798
+ 30, 30, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30,
799
+ ], // Quartile
800
+ [
801
+ -1, 17, 28, 22, 16, 22, 28, 26, 26, 24, 28, 24, 28, 22, 24, 24, 30, 28, 28, 26, 28, 30, 24, 30,
802
+ 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30,
803
+ ], // High
804
+ ];
805
+
806
+ const NUM_ERROR_CORRECTION_BLOCKS: int[][] = [
807
+ // Version: (note that index 0 is for padding, and is set to an illegal value)
808
+ //0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level
809
+ [
810
+ -1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14,
811
+ 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25,
812
+ ], // Low
813
+ [
814
+ -1, 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23,
815
+ 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49,
816
+ ], // Medium
817
+ [
818
+ -1, 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34,
819
+ 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68,
820
+ ], // Quartile
821
+ [
822
+ -1, 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35,
823
+ 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81,
824
+ ], // High
825
+ ];
826
+
827
+ // Describes precisely all strings that are encodable in numeric mode. To test
828
+ // whether a string s is encodable: let ok: boolean = NUMERIC_REGEX.test(s);
829
+ // A string is encodable iff each character is in the range 0 to 9.
830
+ const NUMERIC_REGEX: RegExp = /^[0-9]*$/;
831
+
832
+ // Describes precisely all strings that are encodable in alphanumeric mode. To test
833
+ // whether a string s is encodable: let ok: boolean = ALPHANUMERIC_REGEX.test(s);
834
+ // A string is encodable iff each character is in the following set: 0 to 9, A to Z
835
+ // (uppercase only), space, dollar, percent, asterisk, plus, hyphen, period, slash, colon.
836
+ const ALPHANUMERIC_REGEX: RegExp = /^[A-Z0-9 $%*+./:-]*$/;
837
+
838
+ // The set of all legal characters in alphanumeric mode,
839
+ // where each character value maps to the index in the string.
840
+ const ALPHANUMERIC_CHARSET: string = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:';