@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/README.md +21 -2
- 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 +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +848 -672
- package/dist/index.js.map +1 -1
- package/dist/qr.d.ts +62 -70
- package/dist/qr.d.ts.map +1 -1
- package/dist/svg-path.d.ts +8 -0
- package/dist/svg-path.d.ts.map +1 -0
- package/dist/svg.d.ts +5 -9
- package/dist/svg.d.ts.map +1 -1
- package/dist/types.d.ts +13 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +7 -7
- package/src/constants.ts +9 -0
- package/src/figures.ts +106 -0
- package/src/index.ts +16 -10
- package/src/qr.ts +707 -871
- package/src/svg-path.ts +254 -0
- package/src/svg.ts +129 -270
- package/src/types.ts +15 -1
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
|
-
/*----
|
|
28
|
+
/*---- Public helper constants ----*/
|
|
29
29
|
|
|
30
30
|
/*
|
|
31
|
-
*
|
|
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
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
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
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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 (
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
//
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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
|
-
|
|
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
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 = (
|
|
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 =
|
|
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: 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
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
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
|
-
|
|
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
|
-
|
|
479
|
-
|
|
480
|
-
|
|
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
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
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
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
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
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
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
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
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
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
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
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
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
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
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
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
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
|
|
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
|
|
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
|
|
708
|
-
* This segment
|
|
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
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
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
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
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
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
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
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
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
|
-
|
|
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
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
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
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
const
|
|
900
|
-
|
|
901
|
-
|
|
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
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
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
|
-
|
|
930
|
-
|
|
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
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
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
|
-
/*----
|
|
710
|
+
/*---- Private helper: ReedSolomonGenerator ----*/
|
|
972
711
|
|
|
973
712
|
/*
|
|
974
|
-
*
|
|
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
|
-
|
|
977
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
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 $%*+-./:';
|