UNPKG

qrcoder

Version:

QRCoder is *a pure browser qrcode generation* which is standalone. It is based on a <a href='http://www.d-project.com/qrcode/index.html'>library</a> which build qrcode in various language.

593 lines (512 loc) 15.8 kB
import { QRRSBlock } from './qrrsblock' import QRUtil from './qrutil' import QRPolynomial from './qrpolynomial' import QRBitBuffer from './qrbitbuffer' import QRNumber from './qrnumber' import QRAlphaNum from './qralphanum' import QR8BitByte from './qr8bitbyte' import QRKanji from './qrkanji' import GifImage from './gifimage' import Base64EncodeOutputStream from './base64encode' import ByteArrayOutputStream from './bytearrayoutputstream' import type from './type' import { QRErrorCorrectionLevel, PAD0, PAD1, QRCapacity } from './constant' import { stringToBytesFuncs, createStringToBytes } from './utils' /** * qrcode * @param typeNumber 1 to 40 * @param errorCorrectionLevel 'L','M','Q','H' */ function QRCoder (options) { if (type(options) !== 'object') { throw new Error('options should be a Object.') } this._options = options const errorCorrectionLevel = QRErrorCorrectionLevel[this._options.errorCorrectionLevel || 'L'] const typeNumber = this._options.typeNumber || this.calcTypeNumber(this._options.data, errorCorrectionLevel, this._options.mode) || 4 this._typeNumber = typeNumber this._errorCorrectionLevel = errorCorrectionLevel this._modules = null this._moduleCount = 0 this._dataCache = null this._dataList = [] if (this._options.data) { this.addData(this._options.data) this.make() } } QRCoder.prototype = { makeImpl: function (test, maskPattern) { this._moduleCount = this._typeNumber * 4 + 17 this._modules = (function (moduleCount) { const modules = new Array(moduleCount) for (let row = 0; row < moduleCount; row += 1) { modules[row] = new Array(moduleCount) for (let col = 0; col < moduleCount; col += 1) { modules[row][col] = null } } return modules })(this._moduleCount) this.setupPositionProbePattern(0, 0) this.setupPositionProbePattern(this._moduleCount - 7, 0) this.setupPositionProbePattern(0, this._moduleCount - 7) this.setupPositionAdjustPattern() this.setupTimingPattern() this.setupTypeInfo(test, maskPattern) if (this._typeNumber >= 7) { this.setupTypeNumber(test) } if (this._dataCache == null) { this._dataCache = this.createData(this._typeNumber, this._errorCorrectionLevel, this._dataList) } this.mapData(this._dataCache, maskPattern) }, setupPositionProbePattern: function (row, col) { for (let r = -1; r <= 7; r += 1) { if (row + r <= -1 || this._moduleCount <= row + r) { continue } for (let c = -1; c <= 7; c += 1) { if (col + c <= -1 || this._moduleCount <= 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)) { this._modules[row + r][col + c] = true } else { this._modules[row + r][col + c] = false } } } }, getBestMaskPattern: function () { let minLostPoint = 0 let pattern = 0 for (var i = 0; i < 8; i += 1) { this.makeImpl(true, i) let lostPoint = QRUtil.getLostPoint(this) if (i === 0 || minLostPoint > lostPoint) { minLostPoint = lostPoint pattern = i } } return pattern }, setupTimingPattern: function () { for (let r = 8; r < this._moduleCount - 8; r += 1) { if (this._modules[r][6] != null) { continue } this._modules[r][6] = (r % 2 === 0) } for (let c = 8; c < this._moduleCount - 8; c += 1) { if (this._modules[6][c] != null) { continue } this._modules[6][c] = (c % 2 === 0) } }, setupPositionAdjustPattern: function () { let pos = QRUtil.getPatternPosition(this._typeNumber) for (let i = 0; i < pos.length; i += 1) { for (let j = 0; j < pos.length; j += 1) { let row = pos[i] let col = pos[j] if (this._modules[row][col] != null) { continue } for (let r = -2; r <= 2; r += 1) { for (let c = -2; c <= 2; c += 1) { if (r === -2 || r === 2 || c === -2 || c === 2 || (r === 0 && c === 0)) { this._modules[row + r][col + c] = true } else { this._modules[row + r][col + c] = false } } } } } }, setupTypeNumber: function (test) { let bits = QRUtil.getBCHTypeNumber(this._typeNumber) for (let i = 0; i < 18; i += 1) { let mod = (!test && ((bits >> i) & 1) === 1) this._modules[Math.floor(i / 3)][i % 3 + this._moduleCount - 8 - 3] = mod } for (let i = 0; i < 18; i += 1) { let mod = (!test && ((bits >> i) & 1) === 1) this._modules[i % 3 + this._moduleCount - 8 - 3][Math.floor(i / 3)] = mod } }, setupTypeInfo: function (test, maskPattern) { let data = (this._errorCorrectionLevel << 3) | maskPattern let bits = QRUtil.getBCHTypeInfo(data) // vertical for (let i = 0; i < 15; i += 1) { let mod = (!test && ((bits >> i) & 1) === 1) if (i < 6) { this._modules[i][8] = mod } else if (i < 8) { this._modules[i + 1][8] = mod } else { this._modules[this._moduleCount - 15 + i][8] = mod } } // horizontal for (let i = 0; i < 15; i += 1) { let mod = (!test && ((bits >> i) & 1) === 1) if (i < 8) { this._modules[8][this._moduleCount - i - 1] = mod } else if (i < 9) { this._modules[8][15 - i - 1 + 1] = mod } else { this._modules[8][15 - i - 1] = mod } } // fixed module this._modules[this._moduleCount - 8][8] = (!test) }, mapData: function (data, maskPattern) { let inc = -1 let row = this._moduleCount - 1 let bitIndex = 7 let byteIndex = 0 let maskFunc = QRUtil.getMaskFunction(maskPattern) for (let col = this._moduleCount - 1; col > 0; col -= 2) { if (col === 6) { col -= 1 } // eslint-disable-next-line no-constant-condition while (true) { for (let c = 0; c < 2; c += 1) { if (this._modules[row][col - c] == null) { let dark = false if (byteIndex < data.length) { dark = (((data[byteIndex] >>> bitIndex) & 1) === 1) } let mask = maskFunc(row, col - c) if (mask) { dark = !dark } this._modules[row][col - c] = dark bitIndex -= 1 if (bitIndex === -1) { byteIndex += 1 bitIndex = 7 } } } row += inc if (row < 0 || this._moduleCount <= row) { row -= inc inc = -inc break } } } }, createBytes: function (buffer, rsBlocks) { let offset = 0 let maxDcCount = 0 let maxEcCount = 0 let dcdata = new Array(rsBlocks.length) let ecdata = new Array(rsBlocks.length) for (let r = 0; r < rsBlocks.length; r += 1) { let dcCount = rsBlocks[r].dataCount let ecCount = rsBlocks[r].totalCount - dcCount maxDcCount = Math.max(maxDcCount, dcCount) maxEcCount = Math.max(maxEcCount, ecCount) dcdata[r] = new Array(dcCount) for (let i = 0; i < dcdata[r].length; i += 1) { dcdata[r][i] = 0xff & buffer.getBuffer()[i + offset] } offset += dcCount let rsPoly = QRUtil.getErrorCorrectPolynomial(ecCount) let rawPoly = new QRPolynomial(dcdata[r], rsPoly.getLength() - 1) let modPoly = rawPoly.mod(rsPoly) ecdata[r] = new Array(rsPoly.getLength() - 1) for (let i = 0; i < ecdata[r].length; i += 1) { let modIndex = i + modPoly.getLength() - ecdata[r].length ecdata[r][i] = (modIndex >= 0) ? modPoly.getAt(modIndex) : 0 } } let totalCodeCount = 0 for (let i = 0; i < rsBlocks.length; i += 1) { totalCodeCount += rsBlocks[i].totalCount } let data = new Array(totalCodeCount) let index = 0 for (let i = 0; i < maxDcCount; i += 1) { for (let r = 0; r < rsBlocks.length; r += 1) { if (i < dcdata[r].length) { data[index] = dcdata[r][i] index += 1 } } } for (let i = 0; i < maxEcCount; i += 1) { for (let r = 0; r < rsBlocks.length; r += 1) { if (i < ecdata[r].length) { data[index] = ecdata[r][i] index += 1 } } } return data }, createData: function (typeNumber, errorCorrectionLevel, dataList) { let rsBlocks = QRRSBlock.getRSBlocks(typeNumber, errorCorrectionLevel) let buffer = new QRBitBuffer() for (let i = 0; i < dataList.length; i += 1) { let data = dataList[i] buffer.put(data.getMode(), 4) buffer.put(data.getLength(), QRUtil.getLengthInBits(data.getMode(), typeNumber)) data.write(buffer) } // calc num max data. let totalDataCount = 0 for (let i = 0; i < rsBlocks.length; i += 1) { totalDataCount += rsBlocks[i].dataCount } if (buffer.getLengthInBits() > totalDataCount * 8) { throw new Error('code length overflow. (' + buffer.getLengthInBits() + '>' + totalDataCount * 8 + ')') } // end code if (buffer.getLengthInBits() + 4 <= totalDataCount * 8) { buffer.put(0, 4) } // padding while (buffer.getLengthInBits() % 8 !== 0) { buffer.putBit(false) } // padding // eslint-disable-next-line no-constant-condition while (true) { if (buffer.getLengthInBits() >= totalDataCount * 8) { break } buffer.put(PAD0, 8) if (buffer.getLengthInBits() >= totalDataCount * 8) { break } buffer.put(PAD1, 8) } return this.createBytes(buffer, rsBlocks) }, addData: function (data, mode) { mode = mode || this._options.mode || 'Byte' let newData = null switch (mode) { case 'Numeric': newData = new QRNumber(data) break case 'Alphanumeric': newData = new QRAlphaNum(data) break case 'Byte': newData = new QR8BitByte(data) break case 'Kanji': newData = new QRKanji(data) break default: throw new Error('mode:' + mode) } this._dataList.push(newData) this._dataCache = null }, isDark: function (row, col) { if (row < 0 || this._moduleCount <= row || col < 0 || this._moduleCount <= col) { throw new Error(row + ',' + col) } return this._modules[row][col] }, getModuleCount: function () { return this._moduleCount }, make: function () { this.makeImpl(false, this.getBestMaskPattern()) }, createTableTag: function () { const { cellSize, margin } = this.calcSpec() let qrHtml = '' qrHtml += '<table style="' qrHtml += ' border-width: 0px; border-style: none;' qrHtml += ' border-collapse: collapse;' qrHtml += ' padding: 0px; margin: ' + margin + 'px;' qrHtml += '">' qrHtml += '<tbody>' for (let r = 0; r < this.getModuleCount(); r += 1) { qrHtml += '<tr>' for (let c = 0; c < this.getModuleCount(); c += 1) { qrHtml += '<td style="' qrHtml += ' border-width: 0px; border-style: none;' qrHtml += ' border-collapse: collapse;' qrHtml += ' padding: 0px; margin: 0px;' qrHtml += ' width: ' + cellSize + 'px;' qrHtml += ' height: ' + cellSize + 'px;' qrHtml += ' background-color: ' qrHtml += this.isDark(r, c) ? '#000000' : '#ffffff' qrHtml += ';' qrHtml += '"/>' } qrHtml += '</tr>' } qrHtml += '</tbody>' qrHtml += '</table>' return qrHtml }, createSvgTag: function () { const { cellSize, margin, size } = this.calcSpec() let c let mc let r let mr let qrSvg = '' let rect rect = 'l' + cellSize + ',0 0,' + cellSize + ' -' + cellSize + ',0 0,-' + cellSize + 'z ' qrSvg += '<svg' qrSvg += ' width="' + size + 'px"' qrSvg += ' height="' + size + 'px"' qrSvg += ' xmlns="http://www.w3.org/2000/svg"' qrSvg += '>' qrSvg += '<path d="' for (r = 0; r < this.getModuleCount(); r += 1) { mr = r * cellSize + margin for (c = 0; c < this.getModuleCount(); c += 1) { if (this.isDark(r, c)) { mc = c * cellSize + margin qrSvg += 'M' + mc + ',' + mr + rect } } } qrSvg += '" stroke="transparent" fill="black"/>' qrSvg += '</svg>' return qrSvg }, createImgTag: function () { const { cellSize, margin, size } = this.calcSpec() const alt = this._options.alt || '' let dataUrl = this.getDataURL(cellSize, margin) return createImgTag(size, size, dataUrl, alt) }, getDataURL: function () { const { cellSize, margin, size } = this.calcSpec() let min = margin let max = size - margin return getGifDataURL(size, size, (x, y) => { if (min <= x && x < max && min <= y && y < max) { let c = Math.floor((x - min) / cellSize) let r = Math.floor((y - min) / cellSize) return this.isDark(r, c) ? 0 : 1 } else { return 1 } }) }, calcTypeNumber: function (data, errorCorrectionLevel, mode) { mode = mode || 'Byte' if (!data) { return } const arrCapacity = QRCapacity[mode][errorCorrectionLevel] for(let i = 0; i<arrCapacity.length; i++) { if (data.length < +arrCapacity[i]) { return i + 1 } } throw new Error('code length overflow. ') }, calcSpec: function () { let cellSize let margin let size const moduleCount = this.getModuleCount() if (!this._options.cellSize && !this._options.margin && this._options.size) { // clac cellSize, margin const size = this._options.size cellSize = Math.floor(size / moduleCount) if (cellSize < 1) { console.log('size is less than moduleCount') cellSize = 1 margin = 0 } else { margin = Math.floor((size % moduleCount) / 2) } } else { cellSize = cellSize || this._options.cellSize || 2, margin = margin || this._options.margin || cellSize * 4 } size = moduleCount * cellSize + margin * 2 return { cellSize, margin, size } }, getSize() { const { size } = this.calcSpec() return size }, getCellSize() { const { cellSize } = this.calcSpec() return cellSize }, getMargin() { const { margin } = this.calcSpec() return margin } } QRCoder.stringToBytesFuncs = stringToBytesFuncs QRCoder.stringToBytes = stringToBytesFuncs['default'] /** * @param unicodeData base64 string of byte array. * [16bit Unicode],[16bit Bytes], ... * @param numChars */ QRCoder.createStringToBytes = createStringToBytes function createImgTag (width, height, dataUrl, alt) { let img = '' img += '<img' img += '\u0020src="' img += dataUrl img += '"' img += '\u0020width="' img += width img += '"' img += '\u0020height="' img += height img += '"' if (alt) { img += '\u0020alt="' img += alt img += '"' } img += '/>' return img } function getGifDataURL (width, height, getPixel) { let gif = new GifImage(width, height) for (let y = 0; y < height; y += 1) { for (let x = 0; x < width; x += 1) { gif.setPixel(x, y, getPixel(x, y)) } } var b = new ByteArrayOutputStream() gif.write(b) let base64 = new Base64EncodeOutputStream() let bytes = b.toByteArray() for (let i = 0; i < bytes.length; i += 1) { base64.writeByte(bytes[i]) } base64.flush() return 'data:image/gif;base64,' + base64 } export default QRCoder