UNPKG

@cicciosgamino/qr-code-element

Version:

QR-Code WebComponent based on Project Nayuki Qr Code Library

1,038 lines (900 loc) 43.8 kB
/* * QR Code generator library Javascript v1.8.0 ES6 * * 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. */ import { svg } from 'lit' /** * Returns the number of Unicode code points in the given UTF-16 string. * @param {String} str - Input string * @returns {Number} */ const countUnicodeChars = (str) => { let result = 0 for (const ch of str) { const cc = Number(ch.codePointAt(0)) if (0xD800 <= cc && cc < 0xE000) throw new RangeError("Invalid UTF-16 string") result++ } return result } /** * Appends the given number of low-order bits of the given value * to the given buffer. Requires 0 <= len <= 31 and 0 <= val < 2^len. * @param {*} val * @param {*} len * @param {*} bb */ const appendBits = (val, len, bb) => { if (len < 0 || len > 31 || val >>> len != 0) throw new RangeError("Value out of range") for (let i = len - 1; i >= 0; i--) // Append bit by bit bb.push((val >>> i) & 1) } /** * Returns true iff the i'th bit of x is set to 1. * @param {*} x * @param {*} i * @returns */ const getBit = (x, i) => { return ((x >>> i) & 1) != 0 } /** * Throws an exception if the given condition is false. * @param {*} cond */ const assert = (cond) => { if (!cond) throw new Error("Assertion error") } /* ---------------------------------- QrCode ------------------------------- */ class QrCode { /** * Constructor (low level) and fields. * Creates new QR Code with the given version number, error correction * level, data codeword bytes, and mask number. This is a low-level API * t most users should not use directly. A mid-level API is encodeSegments() * @param {number} version - version number of this QR Code, between 1 and 40 (inclusive), * This determines the size of this barcode. * @param {*} errorCorrectionLevel - error correction level used in this QR Code. * @param {*} dataCodewords * @param {*} msk */ constructor( version, errorCorrectionLevel, dataCodewords, msk) { this.version = version this.errorCorrectionLevel = errorCorrectionLevel // The modules of this QR Code (false = light, true = dark). // Immutable after constructor finishes. Accessed through getModule(). this.modules = [] // Indicates function modules that are not subjected to masking. // Discarded when constructor finishes. this.isFunction = [] // 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 = [] 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 = this.addEccAndInterleave(dataCodewords) this.drawCodewords(allCodewords) // Do masking if (msk == -1) { // Automatically choose best mask let minPenalty = 1000000000 for (let i = 0; i < 8; i++) { this.applyMask(i) this.drawFormatBits(i) const penalty = 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 = [] } /*-- 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. static encodeText(text, ecl) { const segs = 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. static encodeBinary(data, ecl) { const seg = 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(). static encodeSegments(segs, ecl, minVersion = 1, maxVersion = 40, mask = -1, boostEcl = true) { 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 let dataUsedBits for (version = minVersion;; version++) { const dataCapacityBits = QrCode.getNumDataCodewords(version, ecl) * 8 // Number of data bits available const usedBits = 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 [Ecc.MEDIUM, Ecc.QUARTILE, 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 = [] 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 = 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 = [] while (dataCodewords.length * 8 < bb.length) dataCodewords.push(0) bb.forEach((b, i) => dataCodewords[i >>> 3] |= b << (7 - (i & 7))) // Create the QR Code object return new QrCode(version, ecl, dataCodewords, mask) } /*-- 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. getModule(x, y) { 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. drawFunctionPatterns() { // 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 = this.getAlignmentPatternPositions() const numAlign = 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. drawFormatBits(mask) { // Calculate error correction code and pack bits const data = this.errorCorrectionLevel.formatBits << 3 | mask // errCorrLvl is uint2, mask is uint3 let rem = 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. drawVersion() { if (this.version < 7) return // Calculate error correction code and pack bits let rem = 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 = this.version << 12 | rem // uint18 assert(bits >>> 18 == 0) // Draw two copies for (let i = 0; i < 18; i++) { const color = getBit(bits, i) const a = this.size - 11 + i % 3 const b = 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. drawFinderPattern(x, y) { for (let dy = -4; dy <= 4; dy++) { for (let dx = -4; dx <= 4; dx++) { const dist = Math.max(Math.abs(dx), Math.abs(dy)) // Chebyshev/infinity norm const xx = x + dx const yy = 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. drawAlignmentPattern(x, y) { 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. setFunctionModule(x, y, isDark) { 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. addEccAndInterleave(data) { const ver = this.version const ecl = this.errorCorrectionLevel if (data.length != QrCode.getNumDataCodewords(ver, ecl)) throw new RangeError("Invalid argument") // Calculate parameter numbers const numBlocks = QrCode.NUM_ERROR_CORRECTION_BLOCKS[ecl.ordinal][ver] const blockEccLen = QrCode.ECC_CODEWORDS_PER_BLOCK[ecl.ordinal][ver] const rawCodewords = Math.floor(QrCode.getNumRawDataModules(ver) / 8) const numShortBlocks = numBlocks - rawCodewords % numBlocks const shortBlockLen = Math.floor(rawCodewords / numBlocks) // Split data into blocks and append ECC to each block let blocks = [] const rsDiv = QrCode.reedSolomonComputeDivisor(blockEccLen) for (let i = 0, k = 0; i < numBlocks; i++) { let dat = data.slice(k, k + shortBlockLen - blockEccLen + (i < numShortBlocks ? 0 : 1)) k += dat.length const ecc = 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 = [] 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. drawCodewords(data) { if (data.length != Math.floor(QrCode.getNumRawDataModules(this.version) / 8)) throw new RangeError("Invalid argument") let i = 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 = right - j // Actual x coordinate const upward = ((right + 1) & 2) == 0 const y = 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. applyMask(mask) { 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 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. getPenaltyScore() { let result = 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) result += this.finderPenaltyCountPatterns(runHistory) * QrCode.PENALTY_N3 runColor = this.modules[y][x] runY = 1 } } result += this.finderPenaltyTerminateAndCount(runColor, runY, runHistory) * QrCode.PENALTY_N3 } // 2*2 blocks of modules having same color for (let y = 0; y < this.size - 1; y++) { for (let x = 0; x < this.size - 1; x++) { const color = this.modules[y][x] if (color == this.modules[y][x + 1] && color == this.modules[y + 1][x] && color == this.modules[y + 1][x + 1]) result += QrCode.PENALTY_N2 } } // Balance of dark and light modules let dark = 0 for (const row of this.modules) dark = row.reduce((sum, color) => sum + (color ? 1 : 0), dark) const total = this.size * this.size; // Note that size is odd, so dark/total != 1/2 // Compute the smallest integer k >= 0 such that (45-5k)% <= dark/total <= (55+5k)% const k = Math.ceil(Math.abs(dark * 20 - total * 10) / total) - 1 assert(0 <= k && k <= 9) result += k * QrCode.PENALTY_N4 assert(0 <= result && result <= 2568888) // Non-tight upper bound based on default values of PENALTY_N1, ..., N4 return result } /*-- Private helper functions --*/ // Returns an ascending list of positions of alignment patterns for this version number. // Each position is in the range [0,177), and are used on both the x and y axes. // This could be implemented as lookup table of 40 variable-length lists of integers. getAlignmentPatternPositions() { if (this.version == 1) return [] else { const numAlign = Math.floor(this.version / 7) + 2 const step = (this.version == 32) ? 26 : Math.ceil((this.version * 4 + 4) / (numAlign * 2 - 2)) * 2 let result = [6] for (let pos = this.size - 7; result.length < numAlign; pos -= step) result.splice(1, 0, pos) return result } } // Returns the number of data bits that can be stored in a QR Code of the given version number, after // all function modules are excluded. This includes remainder bits, so it might not be a multiple of 8. // The result is in the range [208, 29648]. This could be implemented as a 40-entry lookup table. static getNumRawDataModules(ver) { if (ver < QrCode.MIN_VERSION || ver > QrCode.MAX_VERSION) throw new RangeError("Version number out of range") let result = (16 * ver + 128) * ver + 64 if (ver >= 2) { const numAlign = Math.floor(ver / 7) + 2 result -= (25 * numAlign - 10) * numAlign - 55 if (ver >= 7) result -= 36 } assert(208 <= result && result <= 29648) return result } // Returns the number of 8-bit data (i.e. not error correction) codewords contained in any // QR Code of the given version number and error correction level, with remainder bits discarded. // This stateless pure function could be implemented as a (40*4)-cell lookup table. static getNumDataCodewords(ver, ecl) { return Math.floor(QrCode.getNumRawDataModules(ver) / 8) - QrCode.ECC_CODEWORDS_PER_BLOCK[ecl.ordinal][ver] * QrCode.NUM_ERROR_CORRECTION_BLOCKS[ecl.ordinal][ver] } // Returns a Reed-Solomon ECC generator polynomial for the given degree. This could be // implemented as a lookup table over all possible parameter values, instead of as an algorithm. static reedSolomonComputeDivisor(degree) { if (degree < 1 || degree > 255) throw new RangeError("Degree out of range") // Polynomial coefficients are stored from highest to lowest power, excluding the leading term which is always 1. // For example the polynomial x^3 + 255x^2 + 8x + 93 is stored as the uint8 array [255, 8, 93]. let result = [] for (let i = 0; i < degree - 1; i++) result.push(0) result.push(1) // Start off with the monomial x^0 // Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x - r^{degree-1}), // and drop the highest monomial term which is always 1x^degree. // Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D). let root = 1 for (let i = 0; i < degree; i++) { // Multiply the current product by (x - r^i) for (let j = 0; j < result.length; j++) { result[j] = QrCode.reedSolomonMultiply(result[j], root); if (j + 1 < result.length) result[j] ^= result[j + 1] } root = QrCode.reedSolomonMultiply(root, 0x02) } return result } // Returns the Reed-Solomon error correction codeword for the given data and divisor polynomials. static reedSolomonComputeRemainder(data, divisor) { let result = divisor.map(_ => 0) for (const b of data) { // Polynomial division const factor = b ^ result.shift() result.push(0) divisor.forEach((coef, i) => result[i] ^= QrCode.reedSolomonMultiply(coef, factor)) } return result } // Returns the product of the two given field elements modulo GF(2^8/0x11D). The arguments and result // are unsigned 8-bit integers. This could be implemented as a lookup table of 256*256 entries of uint8. static reedSolomonMultiply(x, y) { if (x >>> 8 != 0 || y >>> 8 != 0) throw new RangeError("Byte out of range") // Russian peasant multiplication let z = 0 for (let i = 7; i >= 0; i--) { z = (z << 1) ^ ((z >>> 7) * 0x11D) z ^= ((y >>> i) & 1) * x } assert(z >>> 8 == 0) return z } // Can only be called immediately after a light run is added, and // returns either 0, 1, or 2. A helper function for getPenaltyScore(). finderPenaltyCountPatterns(runHistory) { const n = runHistory[1] assert(n <= this.size * 3) const core = n > 0 && runHistory[2] == n && runHistory[3] == n * 3 && runHistory[4] == n && runHistory[5] == n return (core && runHistory[0] >= n * 4 && runHistory[6] >= n ? 1 : 0) + (core && runHistory[6] >= n * 4 && runHistory[0] >= n ? 1 : 0) } // Must be called at the end of a line (row or column) of modules. A helper function for getPenaltyScore(). finderPenaltyTerminateAndCount(currentRunColor, currentRunLength, runHistory) { if (currentRunColor) { // Terminate dark run this.finderPenaltyAddHistory(currentRunLength, runHistory) currentRunLength = 0 } currentRunLength += this.size // Add light border to final run this.finderPenaltyAddHistory(currentRunLength, runHistory) return this.finderPenaltyCountPatterns(runHistory) } // Pushes the given value to the front and drops the last value. A helper function for getPenaltyScore(). finderPenaltyAddHistory(currentRunLength, runHistory) { if (runHistory[0] == 0) currentRunLength += this.size // Add light border to initial run runHistory.pop() runHistory.unshift(currentRunLength) } } /* ----------------------------- QrCode Constants -------------------------- */ // The minimum version number supported in the QR Code Model 2 standard. QrCode.MIN_VERSION = 1 // The maximum version number supported in the QR Code Model 2 standard. QrCode.MAX_VERSION = 40 // For use in getPenaltyScore(), when evaluating which mask is best. QrCode.PENALTY_N1 = 3 QrCode.PENALTY_N2 = 3 QrCode.PENALTY_N3 = 40 QrCode.PENALTY_N4 = 10 QrCode.ECC_CODEWORDS_PER_BLOCK = [ // Version: (note that index 0 is for padding, and is set to an illegal value) //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 [-1, 7, 10, 15, 20, 26, 18, 20, 24, 30, 18, 20, 24, 26, 30, 22, 24, 28, 30, 28, 28, 28, 28, 30, 30, 26, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30], [-1, 10, 16, 26, 18, 24, 16, 18, 22, 22, 26, 30, 22, 22, 24, 24, 28, 28, 26, 26, 26, 26, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28], [-1, 13, 22, 18, 26, 18, 24, 18, 22, 20, 24, 28, 26, 24, 20, 30, 24, 28, 28, 26, 30, 28, 30, 30, 30, 30, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30], [-1, 17, 28, 22, 16, 22, 28, 26, 26, 24, 28, 24, 28, 22, 24, 24, 30, 28, 28, 26, 28, 30, 24, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30], // High ] QrCode.NUM_ERROR_CORRECTION_BLOCKS = [ // Version: (note that index 0 is for padding, and is set to an illegal value) //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 [-1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25], [-1, 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49], [-1, 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68], [-1, 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81], // High ] /* --------------------------------- QrSegment ----------------------------- */ /* * A segment of character/binary/control data in a QR Code symbol. * Instances of this class are immutable. * The mid-level way to create a segment is to take the payload data * and call a static factory function such as QrSegment.makeNumeric(). * The low-level way to create a segment is to custom-make the bit buffer * and call the QrSegment() constructor with appropriate values. * This segment class imposes no length restrictions, but QR Codes have restrictions. * Even in the most favorable conditions, a QR Code can only hold 7089 characters of data. * Any segment longer than this is meaningless for the purpose of generating QR Codes. */ class QrSegment { /*-- Constructor (low level) and fields --*/ // Creates a new QR Code segment with the given attributes and data. // The character count (numChars) must agree with the mode and the bit buffer length, // but the constraint isn't checked. The given bit buffer is cloned and stored. constructor( // The mode indicator of this segment. mode, // The length of this segment's unencoded data. Measured in characters for // numeric/alphanumeric/kanji mode, bytes for byte mode, and 0 for ECI mode. // Always zero or positive. Not the same as the data's bit length. numChars, // The data bits of this segment. Accessed through getData(). bitData) { this.mode = mode this.numChars = numChars this.bitData = bitData if (numChars < 0) throw new RangeError("Invalid argument") this.bitData = bitData.slice() // Make defensive copy } /*-- Static factory functions (mid level) --*/ // Returns a segment representing the given binary data encoded in // byte mode. All input byte arrays are acceptable. Any text string // can be converted to UTF-8 bytes and encoded as a byte mode segment. static makeBytes(data) { let bb = [] for (const b of data) appendBits(b, 8, bb) return new QrSegment(Mode.BYTE, data.length, bb) } // Returns a segment representing the given string of decimal digits encoded in numeric mode. static makeNumeric(digits) { if (!QrSegment.isNumeric(digits)) throw new RangeError("String contains non-numeric characters") let bb = [] for (let i = 0; i < digits.length;) { // Consume up to 3 digits per iteration const n = Math.min(digits.length - i, 3) appendBits(parseInt(digits.substr(i, n), 10), n * 3 + 1, bb) i += n } return new QrSegment(Mode.NUMERIC, digits.length, bb) } // Returns a segment representing the given text string encoded in alphanumeric mode. // The characters allowed are: 0 to 9, A to Z (uppercase only), space, // dollar, percent, asterisk, plus, hyphen, period, slash, colon. static makeAlphanumeric(text) { if (!QrSegment.isAlphanumeric(text)) throw new RangeError("String contains unencodable characters in alphanumeric mode") let bb = [] let i for (i = 0; i + 2 <= text.length; i += 2) { // Process groups of 2 let temp = QrSegment.ALPHANUMERIC_CHARSET.indexOf(text.charAt(i)) * 45 temp += QrSegment.ALPHANUMERIC_CHARSET.indexOf(text.charAt(i + 1)) appendBits(temp, 11, bb) } if (i < text.length) // 1 character remaining appendBits(QrSegment.ALPHANUMERIC_CHARSET.indexOf(text.charAt(i)), 6, bb) return new QrSegment(Mode.ALPHANUMERIC, text.length, bb) } // Returns a new mutable list of zero or more segments to represent the given Unicode text string. // The result may use various segment modes and switch modes to optimize the length of the bit stream. static makeSegments(text) { // Select the most efficient segment encoding automatically if (text == "") return [] else if (QrSegment.isNumeric(text)) return [QrSegment.makeNumeric(text)] else if (QrSegment.isAlphanumeric(text)) return [QrSegment.makeAlphanumeric(text)] else return [QrSegment.makeBytes(QrSegment.toUtf8ByteArray(text))] } // Returns a segment representing an Extended Channel Interpretation // (ECI) designator with the given assignment value. static makeEci(assignVal) { let bb = []; if (assignVal < 0) throw new RangeError("ECI assignment value out of range") else if (assignVal < (1 << 7)) appendBits(assignVal, 8, bb) else if (assignVal < (1 << 14)) { appendBits(0b10, 2, bb) appendBits(assignVal, 14, bb) } else if (assignVal < 1000000) { appendBits(0b110, 3, bb) appendBits(assignVal, 21, bb) } else throw new RangeError("ECI assignment value out of range") return new QrSegment(QrSegment.Mode.ECI, 0, bb) } // Tests whether the given string can be encoded as a segment in numeric mode. // A string is encodable iff each character is in the range 0 to 9. static isNumeric(text) { return QrSegment.NUMERIC_REGEX.test(text) } // Tests whether the given string can be encoded as a segment in alphanumeric mode. // A string is encodable iff each character is in the following set: 0 to 9, A to Z // (uppercase only), space, dollar, percent, asterisk, plus, hyphen, period, slash, colon. static isAlphanumeric(text) { return QrSegment.ALPHANUMERIC_REGEX.test(text) } /*-- Methods --*/ // Returns a new copy of the data bits of this segment. getData() { return this.bitData.slice() // Make defensive copy } // (Package-private) Calculates and returns the number of bits needed to encode the given segments at // the given version. The result is infinity if a segment has too many characters to fit its length field. static getTotalBits(segs, version) { let result = 0 for (const seg of segs) { const ccbits = seg.mode.numCharCountBits(version) if (seg.numChars >= (1 << ccbits)) return Infinity // The segment's length doesn't fit the field's bit width result += 4 + ccbits + seg.bitData.length } return result } // Returns a new array of bytes representing the given string encoded in UTF-8. static toUtf8ByteArray(str) { str = encodeURI(str) let result = [] for (let i = 0; i < str.length; i++) { if (str.charAt(i) != "%") result.push(str.charCodeAt(i)) else { result.push(parseInt(str.substr(i + 1, 2), 16)) i += 2 } } return result } } /* ---------------------------- QrSegment Constants ------------------------ */ // Describes precisely all strings that are encodable in numeric mode. QrSegment.NUMERIC_REGEX = /^[0-9]*$/; // Describes precisely all strings that are encodable in alphanumeric mode. QrSegment.ALPHANUMERIC_REGEX = /^[A-Z0-9 $%*+.\/:-]*$/; // The set of all legal characters in alphanumeric mode, // where each character value maps to the index in the string. QrSegment.ALPHANUMERIC_CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:" /* ---------------------------------- Ecc ---------------------------------- */ /* * The error correction level in a QR Code symbol. Immutable. */ class Ecc { /*-- Constructor and fields --*/ constructor( // In the range 0 to 3 (unsigned 2-bit integer). ordinal, // (Package-private) In the range 0 to 3 (unsigned 2-bit integer). formatBits) { this.ordinal = ordinal this.formatBits = formatBits } } /* ---------------------------- Ecc Constants ------------------------------ */ Ecc.LOW = new Ecc(0, 1) // The QR Code can tolerate about 7% erroneous codewords Ecc.MEDIUM = new Ecc(1, 0) // The QR Code can tolerate about 15% erroneous codewords Ecc.QUARTILE = new Ecc(2, 3) // The QR Code can tolerate about 25% erroneous codewords Ecc.HIGH = new Ecc(3, 2) // The QR Code can tolerate about 30% erroneous codewords /* --------------------------------- Mode ---------------------------------- */ /* * Describes how a segment's data bits are interpreted. Immutable. */ class Mode { /*-- Constructor and fields --*/ constructor( // The mode indicator bits, which is a uint4 value (range 0 to 15). modeBits, // Number of character count bits for three different version ranges. numBitsCharCount) { this.modeBits = modeBits this.numBitsCharCount = numBitsCharCount } /*-- Method --*/ // (Package-private) Returns the bit width of the character count field for a segment in // this mode in a QR Code at the given version number. The result is in the range [0, 16]. numCharCountBits(ver) { return this.numBitsCharCount[Math.floor((ver + 7) / 17)] } } /* -------------------------- Mode Constants ------------------------------- */ Mode.NUMERIC = new Mode(0x1, [10, 12, 14]) Mode.ALPHANUMERIC = new Mode(0x2, [9, 11, 13]) Mode.BYTE = new Mode(0x4, [8, 16, 16]) Mode.KANJI = new Mode(0x8, [8, 10, 12]) Mode.ECI = new Mode(0x7, [0, 0, 0]) /** * * @param {QrCode} qr - QrCode object get from eg. QrCode.encodeText * @param {Number} scale - Number of the scale of qr, handling the size * @param {Number} border - Number >= 0, thickness of the border of Qr * @param {String} lightColor - Color for QrCode background * @param {String} darkColor - Color fot tiles of QrCode * @param {HTMLCanvasElement} canvas - The Canvas that will be draw with QrCode */ const drawOnCanvas = ( qr, scale, border, lightColor, darkColor, canvas ) => { if (scale <= 0 || border < 0) { throw new RangeError('@RangeError >> scale or border values') } const width = (qr.size + border * 2) * scale canvas.width = width canvas.height = width const ctx = canvas.getContext('2d') for(let y = -border; y < qr.size + border; y++) { for (let x = -border; x < qr.size + border; x++) { ctx.fillStyle = qr.getModule(x, y) ? darkColor : lightColor ctx.fillRect((x + border) * scale, (y + border) * scale, scale, scale) } } } // Returns a string of SVG code for an image depicting the given QR Code, with the given number // of border modules. The string always uses Unix newlines (\n), regardless of the platform. /** * * @param {QrCode} qr - QrCode Object get from eg. QrCode.encodeText * @param {Number} border - Number >= 0, thickness of the border of Qr * @param {String} lightColor - Color for QrCode background * @param {String} darkColor - Color for QrCode background * @returns {String} - String with the SVG code of the QrCode */ const toSvgString = ( qr, border, lightColor, darkColor ) => { if (border < 0) throw new RangeError('@BORDER >> must be non-negative') let parts = [] for (let y = 0; y < qr.size; y++) { for (let x = 0; x < qr.size; x++) { if (qr.getModule(x, y)) { parts.push(`M${x + border},${y + border}h1v1h-1z`) } } } return svg`<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> <svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 ${qr.size + border * 2} ${qr.size + border * 2}" stroke="none"> <rect width="100%" height="100%" fill="${lightColor}"/> <path d="${parts.join(" ")}" fill="${darkColor}"/> </svg> ` } export { QrCode, QrSegment, Ecc, countUnicodeChars, drawOnCanvas, toSvgString }