cnf-qrcode
Version:
generate qrcode,support svg base64 utf8
503 lines (423 loc) • 15.2 kB
JavaScript
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);
}