* Split core lib into multiple files * Refactor data encoding methods * Refactor data masking process * Improve qr code generation process * Increase minimum required node version to 0.10 * Add linter * Add tests and tests coverage * Update travis config to fix compilation issues * Add examples folder * Add missing license tag in package.json * Update build script and add sourcemap support * Publish only strictly needed files on npm * Update readme
494 lines
14 KiB
JavaScript
494 lines
14 KiB
JavaScript
var Buffer = require('../utils/buffer')
|
|
var Utils = require('./utils')
|
|
var ECLevel = require('./error-correction-level')
|
|
var ByteData = require('./byte-data')
|
|
var BitBuffer = require('./bit-buffer')
|
|
var BitMatrix = require('./bit-matrix')
|
|
var AlignmentPattern = require('./alignment-pattern')
|
|
var FinderPattern = require('./finder-pattern')
|
|
var MaskPattern = require('./mask-pattern')
|
|
var ECCode = require('./error-correction-code')
|
|
var ReedSolomonEncoder = require('./reed-solomon-encoder')
|
|
var Version = require('./version')
|
|
var FormatInfo = require('./format-info')
|
|
|
|
/**
|
|
* 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
|
|
*
|
|
* EXPORTS:
|
|
* {
|
|
* QRCode:QRCode
|
|
* QRErrorCorrectLevel:QRErrorCorrectLevel
|
|
* }
|
|
//---------------------------------------------------------------------
|
|
// 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
|
|
//
|
|
//---------------------------------------------------------------------
|
|
*/
|
|
|
|
module.exports = QRCode
|
|
|
|
/**
|
|
* Add finder patterns bits to matrix
|
|
*
|
|
* @param {BitMatrix} matrix Modules matrix
|
|
* @param {Number} version QR Code version
|
|
*/
|
|
function setupFinderPattern (matrix, version) {
|
|
var size = matrix.size
|
|
var pos = FinderPattern.getPositions(version)
|
|
|
|
for (var i = 0; i < pos.length; i++) {
|
|
var row = pos[i][0]
|
|
var col = pos[i][1]
|
|
|
|
for (var r = -1; r <= 7; r++) {
|
|
if (row + r <= -1 || size <= row + r) continue
|
|
|
|
for (var 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) {
|
|
var size = matrix.size
|
|
|
|
for (var r = 8; r < size - 8; r++) {
|
|
var 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) {
|
|
var pos = AlignmentPattern.getPositions(version)
|
|
|
|
for (var i = 0; i < pos.length; i++) {
|
|
var row = pos[i][0]
|
|
var col = pos[i][1]
|
|
|
|
for (var r = -2; r <= 2; r++) {
|
|
for (var 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
|
|
* @param {Boolean} reserve If true, marks bits as reserved and set their values to 0
|
|
*/
|
|
function setupVersionInfo (matrix, version, reserve) {
|
|
var size = matrix.size
|
|
var bits = Version.getEncodedBits(version)
|
|
var row, col, mod
|
|
|
|
for (var i = 0; i < 18; i++) {
|
|
row = Math.floor(i / 3)
|
|
col = i % 3 + size - 8 - 3
|
|
mod = (!reserve && ((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 {Number} errorCorrectionLevel Error correction level
|
|
* @param {Number} maskPattern Mask pattern reference value
|
|
* @param {Boolean} reserve If true, marks bits as reserved and set their values to 0
|
|
*/
|
|
function setupFormatInfo (matrix, errorCorrectionLevel, maskPattern, reserve) {
|
|
var size = matrix.size
|
|
var bits = FormatInfo.getEncodedBits(errorCorrectionLevel, maskPattern)
|
|
var i, mod
|
|
|
|
for (i = 0; i < 15; i++) {
|
|
mod = (!reserve && ((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, !reserve, true)
|
|
}
|
|
|
|
/**
|
|
* Add encoded data bits to matrix
|
|
*
|
|
* @param {BitMatrix} matrix Modules matrix
|
|
* @param {Buffer} data Data codewords
|
|
*/
|
|
function setupData (matrix, data) {
|
|
var size = matrix.size
|
|
var inc = -1
|
|
var row = size - 1
|
|
var bitIndex = 7
|
|
var byteIndex = 0
|
|
|
|
for (var col = size - 1; col > 0; col -= 2) {
|
|
if (col === 6) col--
|
|
|
|
while (true) {
|
|
for (var c = 0; c < 2; c++) {
|
|
if (!matrix.isReserved(row, col - c)) {
|
|
var 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 {Number} errorCorrectionLevel Error correction level
|
|
* @param {ByteData} data Data input
|
|
* @return {Buffer} Buffer containing encoded codewords
|
|
*/
|
|
function createData (version, errorCorrectionLevel, data) {
|
|
// Prepare data buffer
|
|
var buffer = new BitBuffer()
|
|
|
|
// prefix data with mode indicator (4 bits in byte mode)
|
|
buffer.put(data.mode, 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 ByteData.getCharCountIndicator}.
|
|
buffer.put(data.getLength(), data.getCharCountIndicator(version))
|
|
|
|
// add binary data sequence to buffer
|
|
data.write(buffer)
|
|
|
|
// Calculate required number of bits
|
|
var totalCodewords = Utils.getSymbolTotalCodewords(version)
|
|
var ecTotalCodewords = ECCode.getTotalCodewordsCount(version, errorCorrectionLevel)
|
|
var 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.
|
|
var remainingByte = (dataTotalCodewordsBits - buffer.getLengthInBits()) / 8
|
|
for (var 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 {Number} 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)
|
|
var totalCodewords = Utils.getSymbolTotalCodewords(version)
|
|
|
|
// Total number of error correction codewords
|
|
var ecTotalCodewords = ECCode.getTotalCodewordsCount(version, errorCorrectionLevel)
|
|
|
|
// Total number of data codewords
|
|
var dataTotalCodewords = totalCodewords - ecTotalCodewords
|
|
|
|
// Total number of blocks
|
|
var ecTotalBlocks = ECCode.getBlocksCount(version, errorCorrectionLevel)
|
|
|
|
// Calculate how many blocks each group should contain
|
|
var blocksInGroup2 = totalCodewords % ecTotalBlocks
|
|
var blocksInGroup1 = ecTotalBlocks - blocksInGroup2
|
|
|
|
var totalCodewordsInGroup1 = Math.floor(totalCodewords / ecTotalBlocks)
|
|
|
|
var dataCodewordsInGroup1 = Math.floor(dataTotalCodewords / ecTotalBlocks)
|
|
var dataCodewordsInGroup2 = dataCodewordsInGroup1 + 1
|
|
|
|
// Number of EC codewords is the same for both groups
|
|
var ecCount = totalCodewordsInGroup1 - dataCodewordsInGroup1
|
|
|
|
// Initialize a Reed-Solomon encoder with a generator polynomial of degree ecCount
|
|
var rs = new ReedSolomonEncoder(ecCount)
|
|
|
|
var offset = 0
|
|
var dcData = new Array(ecTotalBlocks)
|
|
var ecData = new Array(ecTotalBlocks)
|
|
var maxDataSize = 0
|
|
var buffer = new Buffer(bitBuffer.buffer)
|
|
|
|
// Divide the buffer into the required number of blocks
|
|
for (var b = 0; b < ecTotalBlocks; b++) {
|
|
var 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
|
|
var data = new Buffer(totalCodewords)
|
|
var index = 0
|
|
var 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++) {
|
|
if (i < ecData[r].length) {
|
|
data[index++] = ecData[r][i]
|
|
}
|
|
}
|
|
}
|
|
|
|
return data
|
|
}
|
|
|
|
/**
|
|
* QR Code
|
|
*
|
|
* @param {Number} version QR Code version
|
|
* @param {Number} errorCorrectionLevel Error correction level
|
|
*/
|
|
function QRCode (version, errorCorrectionLevel) {
|
|
this.version = version
|
|
this.errorCorrectionLevel = errorCorrectionLevel
|
|
this.modules = null
|
|
this.moduleCount = 0
|
|
this.dataCache = null
|
|
this.data = null
|
|
}
|
|
|
|
/**
|
|
* Add datas to store
|
|
*
|
|
* @param {String, Number, Array, Buffer} data
|
|
*/
|
|
QRCode.prototype.addData = function addData (data) {
|
|
if (this.data) this.data.append(data)
|
|
else this.data = new ByteData(data)
|
|
|
|
this.dataCache = null
|
|
}
|
|
|
|
/**
|
|
* Return value of module at position
|
|
*
|
|
* @param {Number} row Row
|
|
* @param {Number} col Column
|
|
* @return {Boolean} Module value (black/white)
|
|
*/
|
|
QRCode.prototype.isDark = function isDark (row, col) {
|
|
if (row < 0 || this.moduleCount <= row || col < 0 || this.moduleCount <= col) {
|
|
throw new Error(row + ',' + col)
|
|
}
|
|
|
|
return this.modules.get(row, col)
|
|
}
|
|
|
|
/**
|
|
* Return QR Code size (number of modules in row/col)
|
|
*
|
|
* @return {Number} size
|
|
*/
|
|
QRCode.prototype.getModuleCount = function getModuleCount () {
|
|
return this.moduleCount
|
|
}
|
|
|
|
/**
|
|
* Build QR Code symbol
|
|
*/
|
|
QRCode.prototype.make = function make () {
|
|
if (this.dataCache === null) {
|
|
// Use higher error correction level as default
|
|
if (typeof this.errorCorrectionLevel === 'undefined') this.errorCorrectionLevel = ECLevel.H
|
|
|
|
// Get the min version that can contain data
|
|
var bestVersion = Version.getBestVersionForData(this.data, this.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 (!this.version) {
|
|
this.version = bestVersion
|
|
|
|
// Check if the specified version can contain the data
|
|
} else if (this.version < bestVersion) {
|
|
throw new Error('\n' +
|
|
'The chosen QR Code version cannot contain this amount of data.\n' +
|
|
'Max characters allowed with current config: ' +
|
|
Version.getCapacity(this.version, this.errorCorrectionLevel) + '\n' +
|
|
'Minimum version required to store current data: ' + bestVersion + '\n'
|
|
)
|
|
}
|
|
|
|
this.dataCache = createData(this.version, this.errorCorrectionLevel, this.data)
|
|
}
|
|
|
|
// Allocate matrix buffer
|
|
this.moduleCount = Utils.getSymbolSize(this.version)
|
|
this.modules = new BitMatrix(this.moduleCount)
|
|
|
|
// Add function modules
|
|
setupFinderPattern(this.modules, this.version)
|
|
setupTimingPattern(this.modules)
|
|
setupAlignmentPattern(this.modules, this.version)
|
|
|
|
// Add temporary blank bits for format info and version 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(this.modules, this.errorCorrectionLevel, 0, true)
|
|
if (this.version >= 7) {
|
|
setupVersionInfo(this.modules, this.version, true)
|
|
}
|
|
|
|
// Add data codewords
|
|
setupData(this.modules, this.dataCache)
|
|
|
|
// Find best mask pattern
|
|
var maskPattern = MaskPattern.getBestMask(this.modules)
|
|
|
|
// Apply mask pattern
|
|
MaskPattern.applyMask(maskPattern, this.modules)
|
|
|
|
// Replace format info bits with correct values
|
|
setupFormatInfo(this.modules, this.errorCorrectionLevel, maskPattern)
|
|
|
|
// Replace version info bits with correct values
|
|
if (this.version >= 7) {
|
|
setupVersionInfo(this.modules, this.version)
|
|
}
|
|
}
|