984 lines · 42.3 KB · TypeScript
RawDownload
// @ts-nocheck/* oxlint-disable eslint/no-useless-escape *//* * QR Code generator library (TypeScript) * * Copyright (c) Project Nayuki. (MIT License) * https://www.nayuki.io/page/qr-code-generator-library * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in * the Software without restriction, including without limitation the rights to * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of * the Software, and to permit persons to whom the Software is furnished to do so, * subject to the following conditions: * - The above copyright notice and this permission notice shall be included in *   all copies or substantial portions of the Software. * - The Software is provided "as is", without warranty of any kind, express or *   implied, including but not limited to the warranties of merchantability, *   fitness for a particular purpose and noninfringement. In no event shall the *   authors or copyright holders be liable for any claim, damages or other *   liability, whether in an action of contract, tort or otherwise, arising from, *   out of or in connection with the Software or the use or other dealings in the *   Software. */ "use strict"; namespace qrcodegen {  type bit = number;  type byte = number;  type int = number;   /*---- QR Code symbol class ----*/   /*   * A QR Code symbol, which is a type of two-dimension barcode.   * Invented by Denso Wave and described in the ISO/IEC 18004 standard.   * Instances of this class represent an immutable square grid of dark and light cells.   * The class provides static factory functions to create a QR Code from text or binary data.   * The class covers the QR Code Model 2 specification, supporting all versions (sizes)   * from 1 to 40, all 4 error correction levels, and 4 character encoding modes.   *   * Ways to create a QR Code object:   * - High level: Take the payload data and call QrCode.encodeText() or QrCode.encodeBinary().   * - Mid level: Custom-make the list of segments and call QrCode.encodeSegments().   * - Low level: Custom-make the array of data codeword bytes (including   *   segment headers and final padding, excluding error correction codewords),   *   supply the appropriate version number, and call the QrCode() constructor.   * (Note that all ways require supplying the desired error correction level.)   */  export class QrCode {    /*-- Static factory functions (high level) --*/     // Returns a QR Code representing the given Unicode text string at the given error correction level.    // As a conservative upper bound, this function is guaranteed to succeed for strings that have 738 or fewer    // Unicode code points (not UTF-16 code units) if the low error correction level is used. The smallest possible    // QR Code version is automatically chosen for the output. The ECC level of the result may be higher than the    // ecl argument if it can be done without increasing the version.    public static encodeText(text: string, ecl: QrCode.Ecc): QrCode {      const segs: Array<QrSegment> = qrcodegen.QrSegment.makeSegments(text);      return QrCode.encodeSegments(segs, ecl);    }     // Returns a QR Code representing the given binary data at the given error correction level.    // This function always encodes using the binary segment mode, not any text mode. The maximum number of    // bytes allowed is 2953. The smallest possible QR Code version is automatically chosen for the output.    // The ECC level of the result may be higher than the ecl argument if it can be done without increasing the version.    public static encodeBinary(data: Readonly<Array<byte>>, ecl: QrCode.Ecc): QrCode {      const seg: QrSegment = qrcodegen.QrSegment.makeBytes(data);      return QrCode.encodeSegments([seg], ecl);    }     /*-- Static factory functions (mid level) --*/     // Returns a QR Code representing the given segments with the given encoding parameters.    // The smallest possible QR Code version within the given range is automatically    // chosen for the output. Iff boostEcl is true, then the ECC level of the result    // may be higher than the ecl argument if it can be done without increasing the    // version. The mask number is either between 0 to 7 (inclusive) to force that    // mask, or -1 to automatically choose an appropriate mask (which may be slow).    // This function allows the user to create a custom sequence of segments that switches    // between modes (such as alphanumeric and byte) to encode text in less space.    // This is a mid-level API; the high-level API is encodeText() and encodeBinary().    public static encodeSegments(      segs: Readonly<Array<QrSegment>>,      ecl: QrCode.Ecc,      minVersion: int = 1,      maxVersion: int = 40,      mask: int = -1,      boostEcl: boolean = true,    ): QrCode {      if (        !(          QrCode.MIN_VERSION <= minVersion &&          minVersion <= maxVersion &&          maxVersion <= QrCode.MAX_VERSION        ) ||        mask < -1 ||        mask > 7      )        throw new RangeError("Invalid value");       // Find the minimal version number to use      let version: int;      let dataUsedBits: int;      for (version = minVersion; ; version++) {        const dataCapacityBits: int = QrCode.getNumDataCodewords(version, ecl) * 8; // Number of data bits available        const usedBits: number = QrSegment.getTotalBits(segs, version);        if (usedBits <= dataCapacityBits) {          dataUsedBits = usedBits;          break; // This version number is found to be suitable        }        if (version >= maxVersion)          // All versions in the range could not fit the given data          throw new RangeError("Data too long");      }       // Increase the error correction level while the data still fits in the current version number      for (const newEcl of [QrCode.Ecc.MEDIUM, QrCode.Ecc.QUARTILE, QrCode.Ecc.HIGH]) {        // From low to high        if (boostEcl && dataUsedBits <= QrCode.getNumDataCodewords(version, newEcl) * 8)          ecl = newEcl;      }       // Concatenate all segments to create the data bit string      let bb: Array<bit> = [];      for (const seg of segs) {        appendBits(seg.mode.modeBits, 4, bb);        appendBits(seg.numChars, seg.mode.numCharCountBits(version), bb);        for (const b of seg.getData()) bb.push(b);      }      assert(bb.length == dataUsedBits);       // Add terminator and pad up to a byte if applicable      const dataCapacityBits: int = QrCode.getNumDataCodewords(version, ecl) * 8;      assert(bb.length <= dataCapacityBits);      appendBits(0, Math.min(4, dataCapacityBits - bb.length), bb);      appendBits(0, (8 - (bb.length % 8)) % 8, bb);      assert(bb.length % 8 == 0);       // Pad with alternating bytes until data capacity is reached      for (let padByte = 0xec; bb.length < dataCapacityBits; padByte ^= 0xec ^ 0x11)        appendBits(padByte, 8, bb);       // Pack bits into bytes in big endian      let dataCodewords: Array<byte> = [];      while (dataCodewords.length * 8 < bb.length) dataCodewords.push(0);      bb.forEach((b: bit, i: int) => (dataCodewords[i >>> 3] |= b << (7 - (i & 7))));       // Create the QR Code object      return new QrCode(version, ecl, dataCodewords, mask);    }     /*-- Fields --*/     // The width and height of this QR Code, measured in modules, between    // 21 and 177 (inclusive). This is equal to version * 4 + 17.    public readonly size: int;     // The index of the mask pattern used in this QR Code, which is between 0 and 7 (inclusive).    // Even if a QR Code is created with automatic masking requested (mask = -1),    // the resulting object still has a mask value between 0 and 7.    public readonly mask: int;     // The modules of this QR Code (false = light, true = dark).    // Immutable after constructor finishes. Accessed through getModule().    private readonly modules: Array<Array<boolean>> = [];     // Indicates function modules that are not subjected to masking. Discarded when constructor finishes.    private readonly isFunction: Array<Array<boolean>> = [];     /*-- Constructor (low level) and fields --*/     // Creates a new QR Code with the given version number,    // error correction level, data codeword bytes, and mask number.    // This is a low-level API that most users should not use directly.    // A mid-level API is the encodeSegments() function.    public constructor(      // The version number of this QR Code, which is between 1 and 40 (inclusive).      // This determines the size of this barcode.      public readonly version: int,       // The error correction level used in this QR Code.      public readonly errorCorrectionLevel: QrCode.Ecc,       dataCodewords: Readonly<Array<byte>>,       msk: int,    ) {      // Check scalar arguments      if (version < QrCode.MIN_VERSION || version > QrCode.MAX_VERSION)        throw new RangeError("Version value out of range");      if (msk < -1 || msk > 7) throw new RangeError("Mask value out of range");      this.size = version * 4 + 17;       // Initialize both grids to be size*size arrays of Boolean false      let row: Array<boolean> = [];      for (let i = 0; i < this.size; i++) row.push(false);      for (let i = 0; i < this.size; i++) {        this.modules.push(row.slice()); // Initially all light        this.isFunction.push(row.slice());      }       // Compute ECC, draw modules      this.drawFunctionPatterns();      const allCodewords: Array<byte> = this.addEccAndInterleave(dataCodewords);      this.drawCodewords(allCodewords);       // Do masking      if (msk == -1) {        // Automatically choose best mask        let minPenalty: int = 1000000000;        for (let i = 0; i < 8; i++) {          this.applyMask(i);          this.drawFormatBits(i);          const penalty: int = this.getPenaltyScore();          if (penalty < minPenalty) {            msk = i;            minPenalty = penalty;          }          this.applyMask(i); // Undoes the mask due to XOR        }      }      assert(0 <= msk && msk <= 7);      this.mask = msk;      this.applyMask(msk); // Apply the final choice of mask      this.drawFormatBits(msk); // Overwrite old format bits       this.isFunction = [];    }     /*-- Accessor methods --*/     // Returns the color of the module (pixel) at the given coordinates, which is false    // for light or true for dark. The top left corner has the coordinates (x=0, y=0).    // If the given coordinates are out of bounds, then false (light) is returned.    public getModule(x: int, y: int): boolean {      return 0 <= x && x < this.size && 0 <= y && y < this.size && this.modules[y][x];    }     /*-- Private helper methods for constructor: Drawing function modules --*/     // Reads this object's version field, and draws and marks all function modules.    private drawFunctionPatterns(): void {      // Draw horizontal and vertical timing patterns      for (let i = 0; i < this.size; i++) {        this.setFunctionModule(6, i, i % 2 == 0);        this.setFunctionModule(i, 6, i % 2 == 0);      }       // Draw 3 finder patterns (all corners except bottom right; overwrites some timing modules)      this.drawFinderPattern(3, 3);      this.drawFinderPattern(this.size - 4, 3);      this.drawFinderPattern(3, this.size - 4);       // Draw numerous alignment patterns      const alignPatPos: Array<int> = this.getAlignmentPatternPositions();      const numAlign: int = alignPatPos.length;      for (let i = 0; i < numAlign; i++) {        for (let j = 0; j < numAlign; j++) {          // Don't draw on the three finder corners          if (            !((i == 0 && j == 0) || (i == 0 && j == numAlign - 1) || (i == numAlign - 1 && j == 0))          )            this.drawAlignmentPattern(alignPatPos[i], alignPatPos[j]);        }      }       // Draw configuration data      this.drawFormatBits(0); // Dummy mask value; overwritten later in the constructor      this.drawVersion();    }     // Draws two copies of the format bits (with its own error correction code)    // based on the given mask and this object's error correction level field.    private drawFormatBits(mask: int): void {      // Calculate error correction code and pack bits      const data: int = (this.errorCorrectionLevel.formatBits << 3) | mask; // errCorrLvl is uint2, mask is uint3      let rem: int = data;      for (let i = 0; i < 10; i++) rem = (rem << 1) ^ ((rem >>> 9) * 0x537);      const bits = ((data << 10) | rem) ^ 0x5412; // uint15      assert(bits >>> 15 == 0);       // Draw first copy      for (let i = 0; i <= 5; i++) this.setFunctionModule(8, i, getBit(bits, i));      this.setFunctionModule(8, 7, getBit(bits, 6));      this.setFunctionModule(8, 8, getBit(bits, 7));      this.setFunctionModule(7, 8, getBit(bits, 8));      for (let i = 9; i < 15; i++) this.setFunctionModule(14 - i, 8, getBit(bits, i));       // Draw second copy      for (let i = 0; i < 8; i++) this.setFunctionModule(this.size - 1 - i, 8, getBit(bits, i));      for (let i = 8; i < 15; i++) this.setFunctionModule(8, this.size - 15 + i, getBit(bits, i));      this.setFunctionModule(8, this.size - 8, true); // Always dark    }     // Draws two copies of the version bits (with its own error correction code),    // based on this object's version field, iff 7 <= version <= 40.    private drawVersion(): void {      if (this.version < 7) return;       // Calculate error correction code and pack bits      let rem: int = this.version; // version is uint6, in the range [7, 40]      for (let i = 0; i < 12; i++) rem = (rem << 1) ^ ((rem >>> 11) * 0x1f25);      const bits: int = (this.version << 12) | rem; // uint18      assert(bits >>> 18 == 0);       // Draw two copies      for (let i = 0; i < 18; i++) {        const color: boolean = getBit(bits, i);        const a: int = this.size - 11 + (i % 3);        const b: int = Math.floor(i / 3);        this.setFunctionModule(a, b, color);        this.setFunctionModule(b, a, color);      }    }     // Draws a 9*9 finder pattern including the border separator,    // with the center module at (x, y). Modules can be out of bounds.    private drawFinderPattern(x: int, y: int): void {      for (let dy = -4; dy <= 4; dy++) {        for (let dx = -4; dx <= 4; dx++) {          const dist: int = Math.max(Math.abs(dx), Math.abs(dy)); // Chebyshev/infinity norm          const xx: int = x + dx;          const yy: int = y + dy;          if (0 <= xx && xx < this.size && 0 <= yy && yy < this.size)            this.setFunctionModule(xx, yy, dist != 2 && dist != 4);        }      }    }     // Draws a 5*5 alignment pattern, with the center module    // at (x, y). All modules must be in bounds.    private drawAlignmentPattern(x: int, y: int): void {      for (let dy = -2; dy <= 2; dy++) {        for (let dx = -2; dx <= 2; dx++)          this.setFunctionModule(x + dx, y + dy, Math.max(Math.abs(dx), Math.abs(dy)) != 1);      }    }     // Sets the color of a module and marks it as a function module.    // Only used by the constructor. Coordinates must be in bounds.    private setFunctionModule(x: int, y: int, isDark: boolean): void {      this.modules[y][x] = isDark;      this.isFunction[y][x] = true;    }     /*-- Private helper methods for constructor: Codewords and masking --*/     // Returns a new byte string representing the given data with the appropriate error correction    // codewords appended to it, based on this object's version and error correction level.    private addEccAndInterleave(data: Readonly<Array<byte>>): Array<byte> {      const ver: int = this.version;      const ecl: QrCode.Ecc = this.errorCorrectionLevel;      if (data.length != QrCode.getNumDataCodewords(ver, ecl))        throw new RangeError("Invalid argument");       // Calculate parameter numbers      const numBlocks: int = QrCode.NUM_ERROR_CORRECTION_BLOCKS[ecl.ordinal][ver];      const blockEccLen: int = QrCode.ECC_CODEWORDS_PER_BLOCK[ecl.ordinal][ver];      const rawCodewords: int = Math.floor(QrCode.getNumRawDataModules(ver) / 8);      const numShortBlocks: int = numBlocks - (rawCodewords % numBlocks);      const shortBlockLen: int = Math.floor(rawCodewords / numBlocks);       // Split data into blocks and append ECC to each block      let blocks: Array<Array<byte>> = [];      const rsDiv: Array<byte> = QrCode.reedSolomonComputeDivisor(blockEccLen);      for (let i = 0, k = 0; i < numBlocks; i++) {        let dat: Array<byte> = data.slice(          k,          k + shortBlockLen - blockEccLen + (i < numShortBlocks ? 0 : 1),        );        k += dat.length;        const ecc: Array<byte> = QrCode.reedSolomonComputeRemainder(dat, rsDiv);        if (i < numShortBlocks) dat.push(0);        blocks.push(dat.concat(ecc));      }       // Interleave (not concatenate) the bytes from every block into a single sequence      let result: Array<byte> = [];      for (let i = 0; i < blocks[0].length; i++) {        blocks.forEach((block, j) => {          // Skip the padding byte in short blocks          if (i != shortBlockLen - blockEccLen || j >= numShortBlocks) result.push(block[i]);        });      }      assert(result.length == rawCodewords);      return result;    }     // Draws the given sequence of 8-bit codewords (data and error correction) onto the entire    // data area of this QR Code. Function modules need to be marked off before this is called.    private drawCodewords(data: Readonly<Array<byte>>): void {      if (data.length != Math.floor(QrCode.getNumRawDataModules(this.version) / 8))        throw new RangeError("Invalid argument");      let i: int = 0; // Bit index into the data      // Do the funny zigzag scan      for (let right = this.size - 1; right >= 1; right -= 2) {        // Index of right column in each column pair        if (right == 6) right = 5;        for (let vert = 0; vert < this.size; vert++) {          // Vertical counter          for (let j = 0; j < 2; j++) {            const x: int = right - j; // Actual x coordinate            const upward: boolean = ((right + 1) & 2) == 0;            const y: int = upward ? this.size - 1 - vert : vert; // Actual y coordinate            if (!this.isFunction[y][x] && i < data.length * 8) {              this.modules[y][x] = getBit(data[i >>> 3], 7 - (i & 7));              i++;            }            // If this QR Code has any remainder bits (0 to 7), they were assigned as            // 0/false/light by the constructor and are left unchanged by this method          }        }      }      assert(i == data.length * 8);    }     // XORs the codeword modules in this QR Code with the given mask pattern.    // The function modules must be marked and the codeword bits must be drawn    // before masking. Due to the arithmetic of XOR, calling applyMask() with    // the same mask value a second time will undo the mask. A final well-formed    // QR Code needs exactly one (not zero, two, etc.) mask applied.    private applyMask(mask: int): void {      if (mask < 0 || mask > 7) throw new RangeError("Mask value out of range");      for (let y = 0; y < this.size; y++) {        for (let x = 0; x < this.size; x++) {          let invert: boolean;          switch (mask) {            case 0:              invert = (x + y) % 2 == 0;              break;            case 1:              invert = y % 2 == 0;              break;            case 2:              invert = x % 3 == 0;              break;            case 3:              invert = (x + y) % 3 == 0;              break;            case 4:              invert = (Math.floor(x / 3) + Math.floor(y / 2)) % 2 == 0;              break;            case 5:              invert = ((x * y) % 2) + ((x * y) % 3) == 0;              break;            case 6:              invert = (((x * y) % 2) + ((x * y) % 3)) % 2 == 0;              break;            case 7:              invert = (((x + y) % 2) + ((x * y) % 3)) % 2 == 0;              break;            default:              throw new Error("Unreachable");          }          if (!this.isFunction[y][x] && invert) this.modules[y][x] = !this.modules[y][x];        }      }    }     // Calculates and returns the penalty score based on state of this QR Code's current modules.    // This is used by the automatic mask choice algorithm to find the mask pattern that yields the lowest score.    private getPenaltyScore(): int {      let result: int = 0;       // Adjacent modules in row having same color, and finder-like patterns      for (let y = 0; y < this.size; y++) {        let runColor = false;        let runX = 0;        let runHistory = [0, 0, 0, 0, 0, 0, 0];        for (let x = 0; x < this.size; x++) {          if (this.modules[y][x] == runColor) {            runX++;            if (runX == 5) result += QrCode.PENALTY_N1;            else if (runX > 5) result++;          } else {            this.finderPenaltyAddHistory(runX, runHistory);            if (!runColor)              result += this.finderPenaltyCountPatterns(runHistory) * QrCode.PENALTY_N3;            runColor = this.modules[y][x];            runX = 1;          }        }        result +=          this.finderPenaltyTerminateAndCount(runColor, runX, runHistory) * QrCode.PENALTY_N3;      }      // Adjacent modules in column having same color, and finder-like patterns      for (let x = 0; x < this.size; x++) {        let runColor = false;        let runY = 0;        let runHistory = [0, 0, 0, 0, 0, 0, 0];        for (let y = 0; y < this.size; y++) {          if (this.modules[y][x] == runColor) {            runY++;            if (runY == 5) result += QrCode.PENALTY_N1;            else if (runY > 5) result++;          } else {            this.finderPenaltyAddHistory(runY, runHistory);            if (!runColor)

Lines 1–500 of 984.