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