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