@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/LICENSE +1 -1
- package/README.md +22 -3
- package/dist/constants.d.ts +10 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/figures.d.ts +9 -0
- package/dist/figures.d.ts.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1311 -0
- package/dist/index.js.map +1 -0
- package/dist/qr.d.ts +71 -0
- package/dist/qr.d.ts.map +1 -0
- package/dist/svg-path.d.ts +8 -0
- package/dist/svg-path.d.ts.map +1 -0
- package/dist/svg.d.ts +6 -0
- package/dist/svg.d.ts.map +1 -0
- package/dist/types.d.ts +69 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +55 -80
- package/src/constants.ts +9 -0
- package/src/figures.ts +106 -0
- package/src/index.ts +45 -27
- package/src/qr.ts +711 -837
- package/src/svg-path.ts +254 -0
- package/src/svg.ts +132 -257
- package/src/types.ts +30 -12
- package/dist/cjs/index.js +0 -1185
- package/dist/es/index.js +0 -1183
- package/dist/types/index.d.ts +0 -20
- package/dist/types/qr.d.ts +0 -87
- package/dist/types/svg.d.ts +0 -9
- package/dist/types/types.d.ts +0 -51
- package/dist/umd/qrCodeGenerator.js +0 -1191
- package/dist/umd/qrCodeGenerator.min.js +0 -1
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
|
-
/*----
|
|
28
|
+
/*---- Public helper constants ----*/
|
|
31
29
|
|
|
32
30
|
/*
|
|
33
|
-
*
|
|
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
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
59
|
+
/*---- QR Code result type ----*/
|
|
221
60
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
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
|
-
//
|
|
301
|
-
|
|
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 = (
|
|
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
|
|
242
|
+
if (bits >>> 15 !== 0) throw 'Assertion error';
|
|
309
243
|
|
|
310
244
|
// Draw first copy
|
|
311
|
-
for (let i = 0; i <= 5; i++)
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
for (let i = 9; i < 15; 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++)
|
|
319
|
-
for (let i = 8; i < 15; i++)
|
|
320
|
-
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
//
|
|
325
|
-
|
|
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 =
|
|
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 = (
|
|
332
|
-
if (bits >>> 18
|
|
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 =
|
|
269
|
+
const a: int = size - 11 + (i % 3);
|
|
338
270
|
const b: int = Math.floor(i / 3);
|
|
339
|
-
|
|
340
|
-
|
|
271
|
+
setFunctionModule(a, b, color);
|
|
272
|
+
setFunctionModule(b, a, color);
|
|
341
273
|
}
|
|
342
274
|
}
|
|
343
275
|
|
|
344
|
-
//
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
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
|
-
|
|
359
|
-
//
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
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
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
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
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
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
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
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
|
-
//
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
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
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
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
|
-
//
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
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
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
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
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
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
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
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
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
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)
|
|
569
|
+
return ((x >>> i) & 1) !== 0;
|
|
663
570
|
}
|
|
664
571
|
|
|
665
|
-
/*---- Data segment
|
|
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
|
|
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
|
|
674
|
-
* This segment
|
|
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
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
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
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
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
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
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
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
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
|
-
|
|
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
|
-
|
|
772
|
-
|
|
773
|
-
|
|
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
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
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
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
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
|
-
|
|
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
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
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
|
-
|
|
816
|
-
|
|
817
|
-
|
|
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
|
|
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
|
|
715
|
+
* This exists because each data block in a QR Code shares the same the divisor polynomial.
|
|
826
716
|
*/
|
|
827
|
-
|
|
828
|
-
|
|
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
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
//
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
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
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
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
|
-
/*----
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
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 $%*+-./:';
|