UNPKG

cnf-qrcode

Version:

generate qrcode,support svg base64 utf8

503 lines (423 loc) 15.2 kB
import isArray from 'isarray'; import Buffer from '../utils/buffer'; import * as Utils from './utils'; import * as ECLevel from './error-correction-level'; import BitBuffer from './bit-buffer'; import BitMatrix from './bit-matrix'; import * as AlignmentPattern from './alignment-pattern'; import * as FinderPattern from './finder-pattern'; import * as MaskPattern from './mask-pattern'; import * as ECCode from './error-correction-code'; import ReedSolomonEncoder from './reed-solomon-encoder'; import * as Version from './version'; import * as FormatInfo from './format-info'; import * as Mode from './mode'; import * as Segments from './segments'; /** * QRCode for JavaScript * * modified by Ryan Day for nodejs support * Copyright (c) 2011 Ryan Day * * Licensed under the MIT license: * http://www.opensource.org/licenses/mit-license.php * //--------------------------------------------------------------------- // QRCode for JavaScript // // Copyright (c) 2009 Kazuhiko Arase // // URL: http://www.d-project.com/ // // Licensed under the MIT license: // http://www.opensource.org/licenses/mit-license.php // // The word "QR Code" is registered trademark of // DENSO WAVE INCORPORATED // http://www.denso-wave.com/qrcode/faqpatent-e.html // //--------------------------------------------------------------------- */ /** * Add finder patterns bits to matrix * * @param {BitMatrix} matrix Modules matrix * @param {Number} version QR Code version */ function setupFinderPattern(matrix, version) { const { size } = matrix; const pos = FinderPattern.getPositions(version); for (let i = 0; i < pos.length; i++) { const row = pos[i][0]; const col = pos[i][1]; for (let r = -1; r <= 7; r++) { if (row + r <= -1 || size <= row + r) continue; for (let c = -1; c <= 7; c++) { if (col + c <= -1 || size <= col + c) continue; if ((r >= 0 && r <= 6 && (c === 0 || c === 6)) || (c >= 0 && c <= 6 && (r === 0 || r === 6)) || (r >= 2 && r <= 4 && c >= 2 && c <= 4)) { matrix.set(row + r, col + c, true, true); } else { matrix.set(row + r, col + c, false, true); } } } } } /** * Add timing pattern bits to matrix * * Note: this function must be called before {@link setupAlignmentPattern} * * @param {BitMatrix} matrix Modules matrix */ function setupTimingPattern(matrix) { const { size } = matrix; for (let r = 8; r < size - 8; r++) { const value = r % 2 === 0; matrix.set(r, 6, value, true); matrix.set(6, r, value, true); } } /** * Add alignment patterns bits to matrix * * Note: this function must be called after {@link setupTimingPattern} * * @param {BitMatrix} matrix Modules matrix * @param {Number} version QR Code version */ function setupAlignmentPattern(matrix, version) { const pos = AlignmentPattern.getPositions(version); for (let i = 0; i < pos.length; i++) { const row = pos[i][0]; const col = pos[i][1]; for (let r = -2; r <= 2; r++) { for (let c = -2; c <= 2; c++) { if (r === -2 || r === 2 || c === -2 || c === 2 || (r === 0 && c === 0)) { matrix.set(row + r, col + c, true, true); } else { matrix.set(row + r, col + c, false, true); } } } } } /** * Add version info bits to matrix * * @param {BitMatrix} matrix Modules matrix * @param {Number} version QR Code version */ function setupVersionInfo(matrix, version) { const { size } = matrix; const bits = Version.getEncodedBits(version); let row, col, mod; for (let i = 0; i < 18; i++) { row = Math.floor(i / 3); col = i % 3 + size - 8 - 3; mod = ((bits >> i) & 1) === 1; matrix.set(row, col, mod, true); matrix.set(col, row, mod, true); } } /** * Add format info bits to matrix * * @param {BitMatrix} matrix Modules matrix * @param {ErrorCorrectionLevel} errorCorrectionLevel Error correction level * @param {Number} maskPattern Mask pattern reference value */ function setupFormatInfo(matrix, errorCorrectionLevel, maskPattern) { const { size } = matrix; const bits = FormatInfo.getEncodedBits(errorCorrectionLevel, maskPattern); let i, mod; for (i = 0; i < 15; i++) { mod = ((bits >> i) & 1) === 1; // vertical if (i < 6) { matrix.set(i, 8, mod, true); } else if (i < 8) { matrix.set(i + 1, 8, mod, true); } else { matrix.set(size - 15 + i, 8, mod, true); } // horizontal if (i < 8) { matrix.set(8, size - i - 1, mod, true); } else if (i < 9) { matrix.set(8, 15 - i - 1 + 1, mod, true); } else { matrix.set(8, 15 - i - 1, mod, true); } } // fixed module matrix.set(size - 8, 8, 1, true); } /** * Add encoded data bits to matrix * * @param {BitMatrix} matrix Modules matrix * @param {Buffer} data Data codewords */ function setupData(matrix, data) { const { size } = matrix; let inc = -1; let row = size - 1; let bitIndex = 7; let byteIndex = 0; for (let col = size - 1; col > 0; col -= 2) { if (col === 6) col--; while (true) { for (let c = 0; c < 2; c++) { if (!matrix.isReserved(row, col - c)) { let dark = false; if (byteIndex < data.length) { dark = (((data[byteIndex] >>> bitIndex) & 1) === 1); } matrix.set(row, col - c, dark); bitIndex--; if (bitIndex === -1) { byteIndex++; bitIndex = 7; } } } row += inc; if (row < 0 || size <= row) { row -= inc; inc = -inc; break; } } } } /** * Create encoded codewords from data input * * @param {Number} version QR Code version * @param {ErrorCorrectionLevel} errorCorrectionLevel Error correction level * @param {ByteData} data Data input * @return {Buffer} Buffer containing encoded codewords */ function createData(version, errorCorrectionLevel, segments) { // Prepare data buffer const buffer = new BitBuffer(); segments.forEach((data) => { // prefix data with mode indicator (4 bits) buffer.put(data.mode.bit, 4); // Prefix data with character count indicator. // The character count indicator is a string of bits that represents the // number of characters that are being encoded. // The character count indicator must be placed after the mode indicator // and must be a certain number of bits long, depending on the QR version // and data mode // @see {@link Mode.getCharCountIndicator}. buffer.put(data.getLength(), Mode.getCharCountIndicator(data.mode, version)); // add binary data sequence to buffer data.write(buffer); }); // Calculate required number of bits const totalCodewords = Utils.getSymbolTotalCodewords(version); const ecTotalCodewords = ECCode.getTotalCodewordsCount(version, errorCorrectionLevel); const dataTotalCodewordsBits = (totalCodewords - ecTotalCodewords) * 8; // Add a terminator. // If the bit string is shorter than the total number of required bits, // a terminator of up to four 0s must be added to the right side of the string. // If the bit string is more than four bits shorter than the required number of bits, // add four 0s to the end. if (buffer.getLengthInBits() + 4 <= dataTotalCodewordsBits) { buffer.put(0, 4); } // If the bit string is fewer than four bits shorter, add only the number of 0s that // are needed to reach the required number of bits. // After adding the terminator, if the number of bits in the string is not a multiple of 8, // pad the string on the right with 0s to make the string's length a multiple of 8. while (buffer.getLengthInBits() % 8 !== 0) { buffer.putBit(0); } // Add pad bytes if the string is still shorter than the total number of required bits. // Extend the buffer to fill the data capacity of the symbol corresponding to // the Version and Error Correction Level by adding the Pad Codewords 11101100 (0xEC) // and 00010001 (0x11) alternately. const remainingByte = (dataTotalCodewordsBits - buffer.getLengthInBits()) / 8; for (let i = 0; i < remainingByte; i++) { buffer.put(i % 2 ? 0x11 : 0xEC, 8); } return createCodewords(buffer, version, errorCorrectionLevel); } /** * Encode input data with Reed-Solomon and return codewords with * relative error correction bits * * @param {BitBuffer} bitBuffer Data to encode * @param {Number} version QR Code version * @param {ErrorCorrectionLevel} errorCorrectionLevel Error correction level * @return {Buffer} Buffer containing encoded codewords */ function createCodewords(bitBuffer, version, errorCorrectionLevel) { // Total codewords for this QR code version (Data + Error correction) const totalCodewords = Utils.getSymbolTotalCodewords(version); // Total number of error correction codewords const ecTotalCodewords = ECCode.getTotalCodewordsCount(version, errorCorrectionLevel); // Total number of data codewords const dataTotalCodewords = totalCodewords - ecTotalCodewords; // Total number of blocks const ecTotalBlocks = ECCode.getBlocksCount(version, errorCorrectionLevel); // Calculate how many blocks each group should contain const blocksInGroup2 = totalCodewords % ecTotalBlocks; const blocksInGroup1 = ecTotalBlocks - blocksInGroup2; const totalCodewordsInGroup1 = Math.floor(totalCodewords / ecTotalBlocks); const dataCodewordsInGroup1 = Math.floor(dataTotalCodewords / ecTotalBlocks); const dataCodewordsInGroup2 = dataCodewordsInGroup1 + 1; // Number of EC codewords is the same for both groups const ecCount = totalCodewordsInGroup1 - dataCodewordsInGroup1; // Initialize a Reed-Solomon encoder with a generator polynomial of degree ecCount const rs = new ReedSolomonEncoder(ecCount); let offset = 0; const dcData = new Array(ecTotalBlocks); const ecData = new Array(ecTotalBlocks); let maxDataSize = 0; const buffer = new Buffer(bitBuffer.buffer); // Divide the buffer into the required number of blocks for (let b = 0; b < ecTotalBlocks; b++) { const dataSize = b < blocksInGroup1 ? dataCodewordsInGroup1 : dataCodewordsInGroup2; // extract a block of data from buffer dcData[b] = buffer.slice(offset, offset + dataSize); // Calculate EC codewords for this data block ecData[b] = rs.encode(dcData[b]); offset += dataSize; maxDataSize = Math.max(maxDataSize, dataSize); } // Create final data // Interleave the data and error correction codewords from each block const data = new Buffer(totalCodewords); let index = 0; let i, r; // Add data codewords for (i = 0; i < maxDataSize; i++) { for (r = 0; r < ecTotalBlocks; r++) { if (i < dcData[r].length) { data[index++] = dcData[r][i]; } } } // Apped EC codewords for (i = 0; i < ecCount; i++) { for (r = 0; r < ecTotalBlocks; r++) { data[index++] = ecData[r][i]; } } return data; } /** * Build QR Code symbol * * @param {String} data Input string * @param {Number} version QR Code version * @param {ErrorCorretionLevel} errorCorrectionLevel Error level * @param {MaskPattern} maskPattern Mask pattern * @return {Object} Object containing symbol data */ function createSymbol(data, version, errorCorrectionLevel, maskPattern) { let segments; if (isArray(data)) { segments = Segments.fromArray(data); } else if (typeof data === 'string') { let estimatedVersion = version; if (!estimatedVersion) { const rawSegments = Segments.rawSplit(data); // Estimate best version that can contain raw splitted segments estimatedVersion = Version.getBestVersionForData(rawSegments, errorCorrectionLevel); } // Build optimized segments // If estimated version is undefined, try with the highest version segments = Segments.fromString(data, estimatedVersion || 40); } else { throw new Error('Invalid data'); } // Get the min version that can contain data const bestVersion = Version.getBestVersionForData(segments, errorCorrectionLevel); // If no version is found, data cannot be stored if (!bestVersion) { throw new Error('The amount of data is too big to be stored in a QR Code'); } // If not specified, use min version as default if (!version) { version = bestVersion; // Check if the specified version can contain the data } else if (version < bestVersion) { throw new Error(`${'\n' + 'The chosen QR Code version cannot contain this amount of data.\n' + 'Minimum version required to store current data is: '}${bestVersion}.\n`); } const dataBits = createData(version, errorCorrectionLevel, segments); // Allocate matrix buffer const moduleCount = Utils.getSymbolSize(version); const modules = new BitMatrix(moduleCount); // Add function modules setupFinderPattern(modules, version); setupTimingPattern(modules); setupAlignmentPattern(modules, version); // Add temporary dummy bits for format info just to set them as reserved. // This is needed to prevent these bits from being masked by {@link MaskPattern.applyMask} // since the masking operation must be performed only on the encoding region. // These blocks will be replaced with correct values later in code. setupFormatInfo(modules, errorCorrectionLevel, 0); if (version >= 7) { setupVersionInfo(modules, version); } // Add data codewords setupData(modules, dataBits); if (isNaN(maskPattern)) { // Find best mask pattern maskPattern = MaskPattern.getBestMask(modules, setupFormatInfo.bind(null, modules, errorCorrectionLevel)); } // Apply mask pattern MaskPattern.applyMask(maskPattern, modules); // Replace format info bits with correct values setupFormatInfo(modules, errorCorrectionLevel, maskPattern); return { modules, version, errorCorrectionLevel, maskPattern, segments, }; } /** * QR Code * * @param {String | Array} data Input data * @param {Object} options Optional configurations * @param {Number} options.version QR Code version * @param {String} options.errorCorrectionLevel Error correction level * @param {Function} options.toSJISFunc Helper func to convert utf8 to sjis */ export function create(data, options) { if (typeof data === 'undefined' || data === '') { throw new Error('No input text'); } let errorCorrectionLevel = ECLevel.M; let version; let mask; if (typeof options !== 'undefined') { // Use higher error correction level as default errorCorrectionLevel = ECLevel.from(options.errorCorrectionLevel, ECLevel.M); version = Version.from(options.version); mask = MaskPattern.from(options.maskPattern); if (options.toSJISFunc) { Utils.setToSJISFunction(options.toSJISFunc); } } return createSymbol(data, version, errorCorrectionLevel, mask); }