496 lines
15 KiB
JavaScript
496 lines
15 KiB
JavaScript
|
const Utils = require('./utils')
|
||
|
const ECLevel = require('./error-correction-level')
|
||
|
const BitBuffer = require('./bit-buffer')
|
||
|
const BitMatrix = require('./bit-matrix')
|
||
|
const AlignmentPattern = require('./alignment-pattern')
|
||
|
const FinderPattern = require('./finder-pattern')
|
||
|
const MaskPattern = require('./mask-pattern')
|
||
|
const ECCode = require('./error-correction-code')
|
||
|
const ReedSolomonEncoder = require('./reed-solomon-encoder')
|
||
|
const Version = require('./version')
|
||
|
const FormatInfo = require('./format-info')
|
||
|
const Mode = require('./mode')
|
||
|
const Segments = require('./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.size
|
||
|
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.size
|
||
|
|
||
|
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.size
|
||
|
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.size
|
||
|
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 {Uint8Array} data Data codewords
|
||
|
*/
|
||
|
function setupData (matrix, data) {
|
||
|
const size = matrix.size
|
||
|
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 {Uint8Array} Buffer containing encoded codewords
|
||
|
*/
|
||
|
function createData (version, errorCorrectionLevel, segments) {
|
||
|
// Prepare data buffer
|
||
|
const buffer = new BitBuffer()
|
||
|
|
||
|
segments.forEach(function (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 {Uint8Array} 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 Uint8Array(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 Uint8Array(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 (Array.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: modules,
|
||
|
version: version,
|
||
|
errorCorrectionLevel: errorCorrectionLevel,
|
||
|
maskPattern: maskPattern,
|
||
|
segments: 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
|
||
|
*/
|
||
|
exports.create = 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)
|
||
|
}
|