diff --git a/es/QR.js b/es/QR.js new file mode 100644 index 0000000..95b6ba4 --- /dev/null +++ b/es/QR.js @@ -0,0 +1,76 @@ +import { qrcode, qrDetectMode } from "./qrcode.js"; + +const MAX_TYPE_NUMBER = 40; + +// data: data or array of data +// errorCorrectionLevel 0: L, 1: M, 2: Q, 3: H +// typeNumber 1 to 40 +// mode: null (auto detect) "Byte" 'Numeric' 'Alphanumeric' 'Kanji' or array of mode +const encode = (data, errorCorrectionLevel, typeNumber, mode) => { + if (typeof errorCorrectionLevel == "number") { + errorCorrectionLevel = "LMQH".charAt(errorCorrectionLevel); + } + errorCorrectionLevel = errorCorrectionLevel || "L"; + if (Array.isArray(data)) { + if (mode == null) { + mode = new Array(data.length); + for (let i = 0; i < data.length; i++) { + mode[i] = qrDetectMode(data[i]); + } + } else if (Array.isArray(mode)) { + for (let i = 0; i < data.length; i++) { + mode[i] = mode[i] || qrDetectMode(data[i]); + } + } else { + const orgmode = mode; + mode = new Array(data.length); + for (let i = 0; i < data.length; i++) { + mode[i] = orgmode; + } + } + } else { + mode = mode || qrDetectMode(data); + } + const makeQR = (typenum, errorCorrectionLevel) => { + const qr = qrcode(typenum, errorCorrectionLevel); + if (Array.isArray(data)) { + for (let i = 0; i < data.length; i++) { + qr.addData(data[i], mode[i]); + } + } else { + qr.addData(data, mode); + } + qr.make(); + return qr; + }; + const getQR = () => { + if (!typeNumber) { + for (let i = 1; i <= MAX_TYPE_NUMBER; i++) { + try { + return makeQR(i, errorCorrectionLevel); + } catch (e) { + if (!e.toString().startsWith("code length overflow.")) { + throw e; + } + } + } + throw Error("overflow"); + } else { + return makeQR(typeNumber, errorCorrectionLevel); + } + }; + const qr = getQR(); + const w = qr.getModuleCount(); + const res = []; + for (let i = 0; i < w; i++) { + const line = []; + for (let j = 0; j < w; j++) { + line.push(qr.isDark(i, j) ? 1 : 0); + } + res.push(line); + } + return res; +}; + +const QR = { encode }; +export { QR }; diff --git a/es/QRMode.js b/es/QRMode.js new file mode 100644 index 0000000..6728bd1 --- /dev/null +++ b/es/QRMode.js @@ -0,0 +1,8 @@ +const QRMode = { + MODE_NUMBER : 1 << 0, + MODE_ALPHA_NUM : 1 << 1, + MODE_8BIT_BYTE : 1 << 2, + MODE_KANJI : 1 << 3 +}; + +export { QRMode }; diff --git a/es/README.md b/es/README.md new file mode 100644 index 0000000..fa72da1 --- /dev/null +++ b/es/README.md @@ -0,0 +1,36 @@ +# QR Code Generator ES Module + +## Getting Started + +```js +import { QR } from "https://taisukef.github.io/qrcode-generator/es/QR.js"; + +const data = QR.encode("Hi!"); +console.log(data); +``` + +for browsers +```js +import { QRCode } from "https://taisukef.github.io/qrcode-generator/es/qr-code.js"; + +document.body.appendChild(new QRCode("abc")); +``` + +## API Documentation + +#### encode(data, errorCorrectionLevel?, typeNumber?) => QRCode + +Create a QRCode array data + +| Param | Type | Description | +| -------------------- | --------------------------------- | ---------------------------------------------- | +| data | string or Uint8Array | Data | +| errorCorrectionLevel | number | Error correction level (0:L, 1:M, 2:Q, 3:H) | +| typeNumber | number | Type number (1 ~ 40), or 0 for auto detection. | + +-- + +This implementation is based on JIS X 0510:1999. + +The word 'QR Code' is registered trademark of DENSO WAVE INCORPORATED +
http://www.denso-wave.com/qrcode/faqpatent-e.html diff --git a/es/bintest.html b/es/bintest.html new file mode 100644 index 0000000..9df5f24 --- /dev/null +++ b/es/bintest.html @@ -0,0 +1,21 @@ + + + diff --git a/es/index.html b/es/index.html new file mode 100644 index 0000000..ca54b04 --- /dev/null +++ b/es/index.html @@ -0,0 +1,14 @@ + + + +
+ + + diff --git a/es/qr-code.js b/es/qr-code.js new file mode 100644 index 0000000..ffc4f52 --- /dev/null +++ b/es/qr-code.js @@ -0,0 +1,68 @@ +import { QR } from "./QR.js"; +import { qrdata2svg } from "./qrdata2svg.js"; + +export const encodeImageData = (code, r = 3, margin = 4) => { + const data = QR.encode(code); + const iw = data.length; + //const cw = Math.min(document.body.clientWidth, 600); + //const r = 3; // Math.floor(cw / (iw + w * 2)) || 1; + const qw = (iw + margin * 2) * r; + + const idata = new Uint8ClampedArray(qw * qw * 4); + for (let i = 0; i < idata.length / 4; i++) { + idata[i * 4 + 0] = 255; + idata[i * 4 + 1] = 255; + idata[i * 4 + 2] = 255; + idata[i * 4 + 3] = 255; + } + for (let i = 0; i < iw; i++) { + for (let j = 0; j < iw; j++) { + const c = data[i][j] ? 0 : 255; + for (let k = 0; k < r * r; k++) { + const x = (i + margin) * r + Math.floor(k / r); + const y = (j + margin) * r + (k % r); + idata[(x + y * qw) * 4] = c; + idata[(x + y * qw) * 4 + 1] = c; + idata[(x + y * qw) * 4 + 2] = c; + idata[(x + y * qw) * 4 + 3] = 255; + } + } + } + const imgdata = new ImageData(idata, qw, qw); + return imgdata; +}; + +class QRCode extends HTMLElement { + constructor(value, pixelsize, margin) { + super(); + value = this.getAttribute("value") || value; + this.pixelsize = pixelsize; + this.margin = margin; + this.canvas = document.createElement("canvas"); + this.canvas.style.imageRendering = "pixelated"; + this.g = this.canvas.getContext("2d"); + this.appendChild(this.canvas); + + if (value) { + this.value = value; + } else { + this.value = document.location.toString(); + window.addEventListener("hashchange", () => this.value = document.location.toString(), false); + } + } + set value(value) { + this._value = value; + const imgdata = encodeImageData(value, this.pixelsize, this.margin); + this.canvas.width = this.canvas.height = imgdata.width; + this.g.putImageData(imgdata, 0, 0); + } + toSVG(dotw = 10) { + const data = QR.encode(this._value); + const svg = qrdata2svg(data, dotw); + return svg; + } +} + +customElements.define('qr-code', QRCode); + +export { QRCode }; diff --git a/es/qr.svg b/es/qr.svg new file mode 100644 index 0000000..764304e --- /dev/null +++ b/es/qr.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/es/qrcode.js b/es/qrcode.js new file mode 100644 index 0000000..8dc8cd1 --- /dev/null +++ b/es/qrcode.js @@ -0,0 +1,1646 @@ +// deno-fmt-ignore-file +//--------------------------------------------------------------------- +// +// QR Code Generator 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 +// +//--------------------------------------------------------------------- + +import { qrNumber, qrAlphaNum, qrKanji, qr8BitByte, qrDetectMode, bin2str } from "./qrcodec.js"; +import { QRMode } from "./QRMode.js"; + +var qrcode = function() { + + //--------------------------------------------------------------------- + // qrcode + //--------------------------------------------------------------------- + + /** + * qrcode + * @param typeNumber 1 to 40 + * @param errorCorrectionLevel 'L','M','Q','H' + */ + var qrcode = function(typeNumber, errorCorrectionLevel) { + + var PAD0 = 0xEC; + var PAD1 = 0x11; + + var _typeNumber = typeNumber; + var _errorCorrectionLevel = QRErrorCorrectionLevel[errorCorrectionLevel]; + var _modules = null; + var _moduleCount = 0; + var _dataCache = null; + var _dataList = []; + + var _this = {}; + + var makeImpl = function(test, maskPattern) { + + _moduleCount = _typeNumber * 4 + 17; + _modules = function(moduleCount) { + var modules = new Array(moduleCount); + for (var row = 0; row < moduleCount; row += 1) { + modules[row] = new Array(moduleCount); + for (var col = 0; col < moduleCount; col += 1) { + modules[row][col] = null; + } + } + return modules; + }(_moduleCount); + + setupPositionProbePattern(0, 0); + setupPositionProbePattern(_moduleCount - 7, 0); + setupPositionProbePattern(0, _moduleCount - 7); + setupPositionAdjustPattern(); + setupTimingPattern(); + setupTypeInfo(test, maskPattern); + + if (_typeNumber >= 7) { + setupTypeNumber(test); + } + + if (_dataCache == null) { + _dataCache = createData(_typeNumber, _errorCorrectionLevel, _dataList); + } + + mapData(_dataCache, maskPattern); + }; + + var setupPositionProbePattern = function(row, col) { + + for (var r = -1; r <= 7; r += 1) { + + if (row + r <= -1 || _moduleCount <= row + r) continue; + + for (var c = -1; c <= 7; c += 1) { + + if (col + c <= -1 || _moduleCount <= col + c) continue; + + if ( (0 <= r && r <= 6 && (c == 0 || c == 6) ) + || (0 <= c && c <= 6 && (r == 0 || r == 6) ) + || (2 <= r && r <= 4 && 2 <= c && c <= 4) ) { + _modules[row + r][col + c] = true; + } else { + _modules[row + r][col + c] = false; + } + } + } + }; + + var getBestMaskPattern = function() { + + var minLostPoint = 0; + var pattern = 0; + + for (var i = 0; i < 8; i += 1) { + + makeImpl(true, i); + + var lostPoint = QRUtil.getLostPoint(_this); + + if (i == 0 || minLostPoint > lostPoint) { + minLostPoint = lostPoint; + pattern = i; + } + } + + return pattern; + }; + + var setupTimingPattern = function() { + + for (var r = 8; r < _moduleCount - 8; r += 1) { + if (_modules[r][6] != null) { + continue; + } + _modules[r][6] = (r % 2 == 0); + } + + for (var c = 8; c < _moduleCount - 8; c += 1) { + if (_modules[6][c] != null) { + continue; + } + _modules[6][c] = (c % 2 == 0); + } + }; + + var setupPositionAdjustPattern = function() { + + var pos = QRUtil.getPatternPosition(_typeNumber); + + for (var i = 0; i < pos.length; i += 1) { + + for (var j = 0; j < pos.length; j += 1) { + + var row = pos[i]; + var col = pos[j]; + + if (_modules[row][col] != null) { + continue; + } + + for (var r = -2; r <= 2; r += 1) { + + for (var c = -2; c <= 2; c += 1) { + + if (r == -2 || r == 2 || c == -2 || c == 2 + || (r == 0 && c == 0) ) { + _modules[row + r][col + c] = true; + } else { + _modules[row + r][col + c] = false; + } + } + } + } + } + }; + + var setupTypeNumber = function(test) { + + var bits = QRUtil.getBCHTypeNumber(_typeNumber); + + for (var i = 0; i < 18; i += 1) { + var mod = (!test && ( (bits >> i) & 1) == 1); + _modules[Math.floor(i / 3)][i % 3 + _moduleCount - 8 - 3] = mod; + } + + for (var i = 0; i < 18; i += 1) { + var mod = (!test && ( (bits >> i) & 1) == 1); + _modules[i % 3 + _moduleCount - 8 - 3][Math.floor(i / 3)] = mod; + } + }; + + var setupTypeInfo = function(test, maskPattern) { + + var data = (_errorCorrectionLevel << 3) | maskPattern; + var bits = QRUtil.getBCHTypeInfo(data); + + // vertical + for (var i = 0; i < 15; i += 1) { + + var mod = (!test && ( (bits >> i) & 1) == 1); + + if (i < 6) { + _modules[i][8] = mod; + } else if (i < 8) { + _modules[i + 1][8] = mod; + } else { + _modules[_moduleCount - 15 + i][8] = mod; + } + } + + // horizontal + for (var i = 0; i < 15; i += 1) { + + var mod = (!test && ( (bits >> i) & 1) == 1); + + if (i < 8) { + _modules[8][_moduleCount - i - 1] = mod; + } else if (i < 9) { + _modules[8][15 - i - 1 + 1] = mod; + } else { + _modules[8][15 - i - 1] = mod; + } + } + + // fixed module + _modules[_moduleCount - 8][8] = (!test); + }; + + var mapData = function(data, maskPattern) { + + var inc = -1; + var row = _moduleCount - 1; + var bitIndex = 7; + var byteIndex = 0; + var maskFunc = QRUtil.getMaskFunction(maskPattern); + + for (var col = _moduleCount - 1; col > 0; col -= 2) { + + if (col == 6) col -= 1; + + while (true) { + + for (var c = 0; c < 2; c += 1) { + + if (_modules[row][col - c] == null) { + + var dark = false; + + if (byteIndex < data.length) { + dark = ( ( (data[byteIndex] >>> bitIndex) & 1) == 1); + } + + var mask = maskFunc(row, col - c); + + if (mask) { + dark = !dark; + } + + _modules[row][col - c] = dark; + bitIndex -= 1; + + if (bitIndex == -1) { + byteIndex += 1; + bitIndex = 7; + } + } + } + + row += inc; + + if (row < 0 || _moduleCount <= row) { + row -= inc; + inc = -inc; + break; + } + } + } + }; + + var createBytes = function(buffer, rsBlocks) { + + var offset = 0; + + var maxDcCount = 0; + var maxEcCount = 0; + + var dcdata = new Array(rsBlocks.length); + var ecdata = new Array(rsBlocks.length); + + for (var r = 0; r < rsBlocks.length; r += 1) { + + var dcCount = rsBlocks[r].dataCount; + var ecCount = rsBlocks[r].totalCount - dcCount; + + maxDcCount = Math.max(maxDcCount, dcCount); + maxEcCount = Math.max(maxEcCount, ecCount); + + dcdata[r] = new Array(dcCount); + + for (var i = 0; i < dcdata[r].length; i += 1) { + dcdata[r][i] = 0xff & buffer.getBuffer()[i + offset]; + } + offset += dcCount; + + var rsPoly = QRUtil.getErrorCorrectPolynomial(ecCount); + var rawPoly = qrPolynomial(dcdata[r], rsPoly.getLength() - 1); + + var modPoly = rawPoly.mod(rsPoly); + ecdata[r] = new Array(rsPoly.getLength() - 1); + for (var i = 0; i < ecdata[r].length; i += 1) { + var modIndex = i + modPoly.getLength() - ecdata[r].length; + ecdata[r][i] = (modIndex >= 0)? modPoly.getAt(modIndex) : 0; + } + } + + var totalCodeCount = 0; + for (var i = 0; i < rsBlocks.length; i += 1) { + totalCodeCount += rsBlocks[i].totalCount; + } + + var data = new Array(totalCodeCount); + var index = 0; + + for (var i = 0; i < maxDcCount; i += 1) { + for (var r = 0; r < rsBlocks.length; r += 1) { + if (i < dcdata[r].length) { + data[index] = dcdata[r][i]; + index += 1; + } + } + } + + for (var i = 0; i < maxEcCount; i += 1) { + for (var r = 0; r < rsBlocks.length; r += 1) { + if (i < ecdata[r].length) { + data[index] = ecdata[r][i]; + index += 1; + } + } + } + + return data; + }; + + var createData = function(typeNumber, errorCorrectionLevel, dataList) { + + var rsBlocks = QRRSBlock.getRSBlocks(typeNumber, errorCorrectionLevel); + + var buffer = qrBitBuffer(); + + for (var i = 0; i < dataList.length; i += 1) { + var data = dataList[i]; + buffer.put(data.getMode(), 4); + buffer.put(data.getLength(), QRUtil.getLengthInBits(data.getMode(), typeNumber) ); + data.write(buffer); + } + + // calc num max data. + var totalDataCount = 0; + for (var i = 0; i < rsBlocks.length; i += 1) { + totalDataCount += rsBlocks[i].dataCount; + } + + if (buffer.getLengthInBits() > totalDataCount * 8) { + throw '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 + while (true) { + + if (buffer.getLengthInBits() >= totalDataCount * 8) { + break; + } + buffer.put(PAD0, 8); + + if (buffer.getLengthInBits() >= totalDataCount * 8) { + break; + } + buffer.put(PAD1, 8); + } + + return createBytes(buffer, rsBlocks); + }; + + _this.addData = function(data, mode) { + //data = bin2str(data); + + mode = mode || 'Byte'; + + var newData = null; + + switch(mode) { + case 'Numeric' : + newData = qrNumber(data); + break; + case 'Alphanumeric' : + newData = qrAlphaNum(data); + break; + case 'Byte' : + newData = qr8BitByte(data); + break; + case 'Kanji' : + newData = qrKanji(data); + break; + default : + throw 'mode:' + mode; + } + _dataList.push(newData); + _dataCache = null; + }; + + _this.isDark = function(row, col) { + if (row < 0 || _moduleCount <= row || col < 0 || _moduleCount <= col) { + throw row + ',' + col; + } + return _modules[row][col]; + }; + + _this.getModuleCount = function() { + return _moduleCount; + }; + + _this.make = function() { + if (_typeNumber < 1) { + var typeNumber = 1; + + for (; typeNumber < 40; typeNumber++) { + var rsBlocks = QRRSBlock.getRSBlocks(typeNumber, _errorCorrectionLevel); + var buffer = qrBitBuffer(); + + for (var i = 0; i < _dataList.length; i++) { + var data = _dataList[i]; + buffer.put(data.getMode(), 4); + buffer.put(data.getLength(), QRUtil.getLengthInBits(data.getMode(), typeNumber) ); + data.write(buffer); + } + + var totalDataCount = 0; + for (var i = 0; i < rsBlocks.length; i++) { + totalDataCount += rsBlocks[i].dataCount; + } + + if (buffer.getLengthInBits() <= totalDataCount * 8) { + break; + } + } + + _typeNumber = typeNumber; + } + + makeImpl(false, getBestMaskPattern() ); + }; + + _this.createTableTag = function(cellSize, margin) { + + cellSize = cellSize || 2; + margin = (typeof margin == 'undefined')? cellSize * 4 : margin; + + var qrHtml = ''; + + qrHtml += ''; + qrHtml += ''; + + for (var r = 0; r < _this.getModuleCount(); r += 1) { + + qrHtml += ''; + + for (var c = 0; c < _this.getModuleCount(); c += 1) { + qrHtml += ''; + } + + qrHtml += ''; + qrHtml += '
'; + } + + qrHtml += '
'; + + return qrHtml; + }; + + _this.createSvgTag = function(cellSize, margin, alt, title) { + + var opts = {}; + if (typeof arguments[0] == 'object') { + // Called by options. + opts = arguments[0]; + // overwrite cellSize and margin. + cellSize = opts.cellSize; + margin = opts.margin; + alt = opts.alt; + title = opts.title; + } + + cellSize = cellSize || 2; + margin = (typeof margin == 'undefined')? cellSize * 4 : margin; + + // Compose alt property surrogate + alt = (typeof alt === 'string') ? {text: alt} : alt || {}; + alt.text = alt.text || null; + alt.id = (alt.text) ? alt.id || 'qrcode-description' : null; + + // Compose title property surrogate + title = (typeof title === 'string') ? {text: title} : title || {}; + title.text = title.text || null; + title.id = (title.text) ? title.id || 'qrcode-title' : null; + + var size = _this.getModuleCount() * cellSize + margin * 2; + var c, mc, r, mr, qrSvg='', rect; + + rect = 'l' + cellSize + ',0 0,' + cellSize + + ' -' + cellSize + ',0 0,-' + cellSize + 'z '; + + qrSvg += '' + + escapeXml(title.text) + '' : ''; + qrSvg += (alt.text) ? '' + + escapeXml(alt.text) + '' : ''; + qrSvg += ''; + qrSvg += ''; + qrSvg += ''; + + return qrSvg; + }; + + _this.createDataURL = function(cellSize, margin) { + + cellSize = cellSize || 2; + margin = (typeof margin == 'undefined')? cellSize * 4 : margin; + + var size = _this.getModuleCount() * cellSize + margin * 2; + var min = margin; + var max = size - margin; + + return createDataURL(size, size, function(x, y) { + if (min <= x && x < max && min <= y && y < max) { + var c = Math.floor( (x - min) / cellSize); + var r = Math.floor( (y - min) / cellSize); + return _this.isDark(r, c)? 0 : 1; + } else { + return 1; + } + } ); + }; + + _this.createImgTag = function(cellSize, margin, alt) { + + cellSize = cellSize || 2; + margin = (typeof margin == 'undefined')? cellSize * 4 : margin; + + var size = _this.getModuleCount() * cellSize + margin * 2; + + var img = ''; + img += '': escaped += '>'; break; + case '&': escaped += '&'; break; + case '"': escaped += '"'; break; + default : escaped += c; break; + } + } + return escaped; + }; + + var _createHalfASCII = function(margin) { + var cellSize = 1; + margin = (typeof margin == 'undefined')? cellSize * 2 : margin; + + var size = _this.getModuleCount() * cellSize + margin * 2; + var min = margin; + var max = size - margin; + + var y, x, r1, r2, p; + + var blocks = { + '██': '█', + '█ ': '▀', + ' █': '▄', + ' ': ' ' + }; + + var blocksLastLineNoMargin = { + '██': '▀', + '█ ': '▀', + ' █': ' ', + ' ': ' ' + }; + + var ascii = ''; + for (y = 0; y < size; y += 2) { + r1 = Math.floor((y - min) / cellSize); + r2 = Math.floor((y + 1 - min) / cellSize); + for (x = 0; x < size; x += 1) { + p = '█'; + + if (min <= x && x < max && min <= y && y < max && _this.isDark(r1, Math.floor((x - min) / cellSize))) { + p = ' '; + } + + if (min <= x && x < max && min <= y+1 && y+1 < max && _this.isDark(r2, Math.floor((x - min) / cellSize))) { + p += ' '; + } + else { + p += '█'; + } + + // Output 2 characters per pixel, to create full square. 1 character per pixels gives only half width of square. + ascii += (margin < 1 && y+1 >= max) ? blocksLastLineNoMargin[p] : blocks[p]; + } + + ascii += '\n'; + } + + if (size % 2 && margin > 0) { + return ascii.substring(0, ascii.length - size - 1) + Array(size+1).join('▀'); + } + + return ascii.substring(0, ascii.length-1); + }; + + _this.createASCII = function(cellSize, margin) { + cellSize = cellSize || 1; + + if (cellSize < 2) { + return _createHalfASCII(margin); + } + + cellSize -= 1; + margin = (typeof margin == 'undefined')? cellSize * 2 : margin; + + var size = _this.getModuleCount() * cellSize + margin * 2; + var min = margin; + var max = size - margin; + + var y, x, r, p; + + var white = Array(cellSize+1).join('██'); + var black = Array(cellSize+1).join(' '); + + var ascii = ''; + var line = ''; + for (y = 0; y < size; y += 1) { + r = Math.floor( (y - min) / cellSize); + line = ''; + for (x = 0; x < size; x += 1) { + p = 1; + + if (min <= x && x < max && min <= y && y < max && _this.isDark(r, Math.floor((x - min) / cellSize))) { + p = 0; + } + + // Output 2 characters per pixel, to create full square. 1 character per pixels gives only half width of square. + line += p ? white : black; + } + + for (r = 0; r < cellSize; r += 1) { + ascii += line + '\n'; + } + } + + return ascii.substring(0, ascii.length-1); + }; + + _this.renderTo2dContext = function(context, cellSize) { + cellSize = cellSize || 2; + var length = _this.getModuleCount(); + for (var row = 0; row < length; row++) { + for (var col = 0; col < length; col++) { + context.fillStyle = _this.isDark(row, col) ? 'black' : 'white'; + context.fillRect(row * cellSize, col * cellSize, cellSize, cellSize); + } + } + } + + return _this; + }; + + + //--------------------------------------------------------------------- + // QRErrorCorrectionLevel + //--------------------------------------------------------------------- + + var QRErrorCorrectionLevel = { + L : 1, + M : 0, + Q : 3, + H : 2 + }; + + //--------------------------------------------------------------------- + // QRMaskPattern + //--------------------------------------------------------------------- + + var QRMaskPattern = { + PATTERN000 : 0, + PATTERN001 : 1, + PATTERN010 : 2, + PATTERN011 : 3, + PATTERN100 : 4, + PATTERN101 : 5, + PATTERN110 : 6, + PATTERN111 : 7 + }; + + //--------------------------------------------------------------------- + // QRUtil + //--------------------------------------------------------------------- + + var QRUtil = function() { + + var PATTERN_POSITION_TABLE = [ + [], + [6, 18], + [6, 22], + [6, 26], + [6, 30], + [6, 34], + [6, 22, 38], + [6, 24, 42], + [6, 26, 46], + [6, 28, 50], + [6, 30, 54], + [6, 32, 58], + [6, 34, 62], + [6, 26, 46, 66], + [6, 26, 48, 70], + [6, 26, 50, 74], + [6, 30, 54, 78], + [6, 30, 56, 82], + [6, 30, 58, 86], + [6, 34, 62, 90], + [6, 28, 50, 72, 94], + [6, 26, 50, 74, 98], + [6, 30, 54, 78, 102], + [6, 28, 54, 80, 106], + [6, 32, 58, 84, 110], + [6, 30, 58, 86, 114], + [6, 34, 62, 90, 118], + [6, 26, 50, 74, 98, 122], + [6, 30, 54, 78, 102, 126], + [6, 26, 52, 78, 104, 130], + [6, 30, 56, 82, 108, 134], + [6, 34, 60, 86, 112, 138], + [6, 30, 58, 86, 114, 142], + [6, 34, 62, 90, 118, 146], + [6, 30, 54, 78, 102, 126, 150], + [6, 24, 50, 76, 102, 128, 154], + [6, 28, 54, 80, 106, 132, 158], + [6, 32, 58, 84, 110, 136, 162], + [6, 26, 54, 82, 110, 138, 166], + [6, 30, 58, 86, 114, 142, 170] + ]; + var G15 = (1 << 10) | (1 << 8) | (1 << 5) | (1 << 4) | (1 << 2) | (1 << 1) | (1 << 0); + var G18 = (1 << 12) | (1 << 11) | (1 << 10) | (1 << 9) | (1 << 8) | (1 << 5) | (1 << 2) | (1 << 0); + var G15_MASK = (1 << 14) | (1 << 12) | (1 << 10) | (1 << 4) | (1 << 1); + + var _this = {}; + + var getBCHDigit = function(data) { + var digit = 0; + while (data != 0) { + digit += 1; + data >>>= 1; + } + return digit; + }; + + _this.getBCHTypeInfo = function(data) { + var d = data << 10; + while (getBCHDigit(d) - getBCHDigit(G15) >= 0) { + d ^= (G15 << (getBCHDigit(d) - getBCHDigit(G15) ) ); + } + return ( (data << 10) | d) ^ G15_MASK; + }; + + _this.getBCHTypeNumber = function(data) { + var d = data << 12; + while (getBCHDigit(d) - getBCHDigit(G18) >= 0) { + d ^= (G18 << (getBCHDigit(d) - getBCHDigit(G18) ) ); + } + return (data << 12) | d; + }; + + _this.getPatternPosition = function(typeNumber) { + return PATTERN_POSITION_TABLE[typeNumber - 1]; + }; + + _this.getMaskFunction = function(maskPattern) { + + switch (maskPattern) { + + case QRMaskPattern.PATTERN000 : + return function(i, j) { return (i + j) % 2 == 0; }; + case QRMaskPattern.PATTERN001 : + return function(i, j) { return i % 2 == 0; }; + case QRMaskPattern.PATTERN010 : + return function(i, j) { return j % 3 == 0; }; + case QRMaskPattern.PATTERN011 : + return function(i, j) { return (i + j) % 3 == 0; }; + case QRMaskPattern.PATTERN100 : + return function(i, j) { return (Math.floor(i / 2) + Math.floor(j / 3) ) % 2 == 0; }; + case QRMaskPattern.PATTERN101 : + return function(i, j) { return (i * j) % 2 + (i * j) % 3 == 0; }; + case QRMaskPattern.PATTERN110 : + return function(i, j) { return ( (i * j) % 2 + (i * j) % 3) % 2 == 0; }; + case QRMaskPattern.PATTERN111 : + return function(i, j) { return ( (i * j) % 3 + (i + j) % 2) % 2 == 0; }; + + default : + throw 'bad maskPattern:' + maskPattern; + } + }; + + _this.getErrorCorrectPolynomial = function(errorCorrectLength) { + var a = qrPolynomial([1], 0); + for (var i = 0; i < errorCorrectLength; i += 1) { + a = a.multiply(qrPolynomial([1, QRMath.gexp(i)], 0) ); + } + return a; + }; + + _this.getLengthInBits = function(mode, type) { + + if (1 <= type && type < 10) { + + // 1 - 9 + + switch(mode) { + case QRMode.MODE_NUMBER : return 10; + case QRMode.MODE_ALPHA_NUM : return 9; + case QRMode.MODE_8BIT_BYTE : return 8; + case QRMode.MODE_KANJI : return 8; + default : + throw 'mode:' + mode; + } + + } else if (type < 27) { + + // 10 - 26 + + switch(mode) { + case QRMode.MODE_NUMBER : return 12; + case QRMode.MODE_ALPHA_NUM : return 11; + case QRMode.MODE_8BIT_BYTE : return 16; + case QRMode.MODE_KANJI : return 10; + default : + throw 'mode:' + mode; + } + + } else if (type < 41) { + + // 27 - 40 + + switch(mode) { + case QRMode.MODE_NUMBER : return 14; + case QRMode.MODE_ALPHA_NUM : return 13; + case QRMode.MODE_8BIT_BYTE : return 16; + case QRMode.MODE_KANJI : return 12; + default : + throw 'mode:' + mode; + } + + } else { + throw 'type:' + type; + } + }; + + _this.getLostPoint = function(qrcode) { + + var moduleCount = qrcode.getModuleCount(); + + var lostPoint = 0; + + // LEVEL1 + + for (var row = 0; row < moduleCount; row += 1) { + for (var col = 0; col < moduleCount; col += 1) { + + var sameCount = 0; + var dark = qrcode.isDark(row, col); + + for (var r = -1; r <= 1; r += 1) { + + if (row + r < 0 || moduleCount <= row + r) { + continue; + } + + for (var c = -1; c <= 1; c += 1) { + + if (col + c < 0 || moduleCount <= col + c) { + continue; + } + + if (r == 0 && c == 0) { + continue; + } + + if (dark == qrcode.isDark(row + r, col + c) ) { + sameCount += 1; + } + } + } + + if (sameCount > 5) { + lostPoint += (3 + sameCount - 5); + } + } + }; + + // LEVEL2 + + for (var row = 0; row < moduleCount - 1; row += 1) { + for (var col = 0; col < moduleCount - 1; col += 1) { + var count = 0; + if (qrcode.isDark(row, col) ) count += 1; + if (qrcode.isDark(row + 1, col) ) count += 1; + if (qrcode.isDark(row, col + 1) ) count += 1; + if (qrcode.isDark(row + 1, col + 1) ) count += 1; + if (count == 0 || count == 4) { + lostPoint += 3; + } + } + } + + // LEVEL3 + + for (var row = 0; row < moduleCount; row += 1) { + for (var col = 0; col < moduleCount - 6; col += 1) { + if (qrcode.isDark(row, col) + && !qrcode.isDark(row, col + 1) + && qrcode.isDark(row, col + 2) + && qrcode.isDark(row, col + 3) + && qrcode.isDark(row, col + 4) + && !qrcode.isDark(row, col + 5) + && qrcode.isDark(row, col + 6) ) { + lostPoint += 40; + } + } + } + + for (var col = 0; col < moduleCount; col += 1) { + for (var row = 0; row < moduleCount - 6; row += 1) { + if (qrcode.isDark(row, col) + && !qrcode.isDark(row + 1, col) + && qrcode.isDark(row + 2, col) + && qrcode.isDark(row + 3, col) + && qrcode.isDark(row + 4, col) + && !qrcode.isDark(row + 5, col) + && qrcode.isDark(row + 6, col) ) { + lostPoint += 40; + } + } + } + + // LEVEL4 + + var darkCount = 0; + + for (var col = 0; col < moduleCount; col += 1) { + for (var row = 0; row < moduleCount; row += 1) { + if (qrcode.isDark(row, col) ) { + darkCount += 1; + } + } + } + + var ratio = Math.abs(100 * darkCount / moduleCount / moduleCount - 50) / 5; + lostPoint += ratio * 10; + + return lostPoint; + }; + + return _this; + }(); + + //--------------------------------------------------------------------- + // QRMath + //--------------------------------------------------------------------- + + var QRMath = function() { + + var EXP_TABLE = new Array(256); + var LOG_TABLE = new Array(256); + + // initialize tables + for (var i = 0; i < 8; i += 1) { + EXP_TABLE[i] = 1 << i; + } + for (var i = 8; i < 256; i += 1) { + EXP_TABLE[i] = EXP_TABLE[i - 4] + ^ EXP_TABLE[i - 5] + ^ EXP_TABLE[i - 6] + ^ EXP_TABLE[i - 8]; + } + for (var i = 0; i < 255; i += 1) { + LOG_TABLE[EXP_TABLE[i] ] = i; + } + + var _this = {}; + + _this.glog = function(n) { + + if (n < 1) { + throw 'glog(' + n + ')'; + } + + return LOG_TABLE[n]; + }; + + _this.gexp = function(n) { + + while (n < 0) { + n += 255; + } + + while (n >= 256) { + n -= 255; + } + + return EXP_TABLE[n]; + }; + + return _this; + }(); + + //--------------------------------------------------------------------- + // qrPolynomial + //--------------------------------------------------------------------- + + function qrPolynomial(num, shift) { + + if (typeof num.length == 'undefined') { + throw num.length + '/' + shift; + } + + var _num = function() { + var offset = 0; + while (offset < num.length && num[offset] == 0) { + offset += 1; + } + var _num = new Array(num.length - offset + shift); + for (var i = 0; i < num.length - offset; i += 1) { + _num[i] = num[i + offset]; + } + return _num; + }(); + + var _this = {}; + + _this.getAt = function(index) { + return _num[index]; + }; + + _this.getLength = function() { + return _num.length; + }; + + _this.multiply = function(e) { + + var num = new Array(_this.getLength() + e.getLength() - 1); + + for (var i = 0; i < _this.getLength(); i += 1) { + for (var j = 0; j < e.getLength(); j += 1) { + num[i + j] ^= QRMath.gexp(QRMath.glog(_this.getAt(i) ) + QRMath.glog(e.getAt(j) ) ); + } + } + + return qrPolynomial(num, 0); + }; + + _this.mod = function(e) { + + if (_this.getLength() - e.getLength() < 0) { + return _this; + } + + var ratio = QRMath.glog(_this.getAt(0) ) - QRMath.glog(e.getAt(0) ); + + var num = new Array(_this.getLength() ); + for (var i = 0; i < _this.getLength(); i += 1) { + num[i] = _this.getAt(i); + } + + for (var i = 0; i < e.getLength(); i += 1) { + num[i] ^= QRMath.gexp(QRMath.glog(e.getAt(i) ) + ratio); + } + + // recursive call + return qrPolynomial(num, 0).mod(e); + }; + + return _this; + }; + + //--------------------------------------------------------------------- + // QRRSBlock + //--------------------------------------------------------------------- + + var QRRSBlock = function() { + + var RS_BLOCK_TABLE = [ + + // L + // M + // Q + // H + + // 1 + [1, 26, 19], + [1, 26, 16], + [1, 26, 13], + [1, 26, 9], + + // 2 + [1, 44, 34], + [1, 44, 28], + [1, 44, 22], + [1, 44, 16], + + // 3 + [1, 70, 55], + [1, 70, 44], + [2, 35, 17], + [2, 35, 13], + + // 4 + [1, 100, 80], + [2, 50, 32], + [2, 50, 24], + [4, 25, 9], + + // 5 + [1, 134, 108], + [2, 67, 43], + [2, 33, 15, 2, 34, 16], + [2, 33, 11, 2, 34, 12], + + // 6 + [2, 86, 68], + [4, 43, 27], + [4, 43, 19], + [4, 43, 15], + + // 7 + [2, 98, 78], + [4, 49, 31], + [2, 32, 14, 4, 33, 15], + [4, 39, 13, 1, 40, 14], + + // 8 + [2, 121, 97], + [2, 60, 38, 2, 61, 39], + [4, 40, 18, 2, 41, 19], + [4, 40, 14, 2, 41, 15], + + // 9 + [2, 146, 116], + [3, 58, 36, 2, 59, 37], + [4, 36, 16, 4, 37, 17], + [4, 36, 12, 4, 37, 13], + + // 10 + [2, 86, 68, 2, 87, 69], + [4, 69, 43, 1, 70, 44], + [6, 43, 19, 2, 44, 20], + [6, 43, 15, 2, 44, 16], + + // 11 + [4, 101, 81], + [1, 80, 50, 4, 81, 51], + [4, 50, 22, 4, 51, 23], + [3, 36, 12, 8, 37, 13], + + // 12 + [2, 116, 92, 2, 117, 93], + [6, 58, 36, 2, 59, 37], + [4, 46, 20, 6, 47, 21], + [7, 42, 14, 4, 43, 15], + + // 13 + [4, 133, 107], + [8, 59, 37, 1, 60, 38], + [8, 44, 20, 4, 45, 21], + [12, 33, 11, 4, 34, 12], + + // 14 + [3, 145, 115, 1, 146, 116], + [4, 64, 40, 5, 65, 41], + [11, 36, 16, 5, 37, 17], + [11, 36, 12, 5, 37, 13], + + // 15 + [5, 109, 87, 1, 110, 88], + [5, 65, 41, 5, 66, 42], + [5, 54, 24, 7, 55, 25], + [11, 36, 12, 7, 37, 13], + + // 16 + [5, 122, 98, 1, 123, 99], + [7, 73, 45, 3, 74, 46], + [15, 43, 19, 2, 44, 20], + [3, 45, 15, 13, 46, 16], + + // 17 + [1, 135, 107, 5, 136, 108], + [10, 74, 46, 1, 75, 47], + [1, 50, 22, 15, 51, 23], + [2, 42, 14, 17, 43, 15], + + // 18 + [5, 150, 120, 1, 151, 121], + [9, 69, 43, 4, 70, 44], + [17, 50, 22, 1, 51, 23], + [2, 42, 14, 19, 43, 15], + + // 19 + [3, 141, 113, 4, 142, 114], + [3, 70, 44, 11, 71, 45], + [17, 47, 21, 4, 48, 22], + [9, 39, 13, 16, 40, 14], + + // 20 + [3, 135, 107, 5, 136, 108], + [3, 67, 41, 13, 68, 42], + [15, 54, 24, 5, 55, 25], + [15, 43, 15, 10, 44, 16], + + // 21 + [4, 144, 116, 4, 145, 117], + [17, 68, 42], + [17, 50, 22, 6, 51, 23], + [19, 46, 16, 6, 47, 17], + + // 22 + [2, 139, 111, 7, 140, 112], + [17, 74, 46], + [7, 54, 24, 16, 55, 25], + [34, 37, 13], + + // 23 + [4, 151, 121, 5, 152, 122], + [4, 75, 47, 14, 76, 48], + [11, 54, 24, 14, 55, 25], + [16, 45, 15, 14, 46, 16], + + // 24 + [6, 147, 117, 4, 148, 118], + [6, 73, 45, 14, 74, 46], + [11, 54, 24, 16, 55, 25], + [30, 46, 16, 2, 47, 17], + + // 25 + [8, 132, 106, 4, 133, 107], + [8, 75, 47, 13, 76, 48], + [7, 54, 24, 22, 55, 25], + [22, 45, 15, 13, 46, 16], + + // 26 + [10, 142, 114, 2, 143, 115], + [19, 74, 46, 4, 75, 47], + [28, 50, 22, 6, 51, 23], + [33, 46, 16, 4, 47, 17], + + // 27 + [8, 152, 122, 4, 153, 123], + [22, 73, 45, 3, 74, 46], + [8, 53, 23, 26, 54, 24], + [12, 45, 15, 28, 46, 16], + + // 28 + [3, 147, 117, 10, 148, 118], + [3, 73, 45, 23, 74, 46], + [4, 54, 24, 31, 55, 25], + [11, 45, 15, 31, 46, 16], + + // 29 + [7, 146, 116, 7, 147, 117], + [21, 73, 45, 7, 74, 46], + [1, 53, 23, 37, 54, 24], + [19, 45, 15, 26, 46, 16], + + // 30 + [5, 145, 115, 10, 146, 116], + [19, 75, 47, 10, 76, 48], + [15, 54, 24, 25, 55, 25], + [23, 45, 15, 25, 46, 16], + + // 31 + [13, 145, 115, 3, 146, 116], + [2, 74, 46, 29, 75, 47], + [42, 54, 24, 1, 55, 25], + [23, 45, 15, 28, 46, 16], + + // 32 + [17, 145, 115], + [10, 74, 46, 23, 75, 47], + [10, 54, 24, 35, 55, 25], + [19, 45, 15, 35, 46, 16], + + // 33 + [17, 145, 115, 1, 146, 116], + [14, 74, 46, 21, 75, 47], + [29, 54, 24, 19, 55, 25], + [11, 45, 15, 46, 46, 16], + + // 34 + [13, 145, 115, 6, 146, 116], + [14, 74, 46, 23, 75, 47], + [44, 54, 24, 7, 55, 25], + [59, 46, 16, 1, 47, 17], + + // 35 + [12, 151, 121, 7, 152, 122], + [12, 75, 47, 26, 76, 48], + [39, 54, 24, 14, 55, 25], + [22, 45, 15, 41, 46, 16], + + // 36 + [6, 151, 121, 14, 152, 122], + [6, 75, 47, 34, 76, 48], + [46, 54, 24, 10, 55, 25], + [2, 45, 15, 64, 46, 16], + + // 37 + [17, 152, 122, 4, 153, 123], + [29, 74, 46, 14, 75, 47], + [49, 54, 24, 10, 55, 25], + [24, 45, 15, 46, 46, 16], + + // 38 + [4, 152, 122, 18, 153, 123], + [13, 74, 46, 32, 75, 47], + [48, 54, 24, 14, 55, 25], + [42, 45, 15, 32, 46, 16], + + // 39 + [20, 147, 117, 4, 148, 118], + [40, 75, 47, 7, 76, 48], + [43, 54, 24, 22, 55, 25], + [10, 45, 15, 67, 46, 16], + + // 40 + [19, 148, 118, 6, 149, 119], + [18, 75, 47, 31, 76, 48], + [34, 54, 24, 34, 55, 25], + [20, 45, 15, 61, 46, 16] + ]; + + var qrRSBlock = function(totalCount, dataCount) { + var _this = {}; + _this.totalCount = totalCount; + _this.dataCount = dataCount; + return _this; + }; + + var _this = {}; + + var getRsBlockTable = function(typeNumber, errorCorrectionLevel) { + + switch(errorCorrectionLevel) { + case QRErrorCorrectionLevel.L : + return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 0]; + case QRErrorCorrectionLevel.M : + return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 1]; + case QRErrorCorrectionLevel.Q : + return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 2]; + case QRErrorCorrectionLevel.H : + return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 3]; + default : + return undefined; + } + }; + + _this.getRSBlocks = function(typeNumber, errorCorrectionLevel) { + + var rsBlock = getRsBlockTable(typeNumber, errorCorrectionLevel); + + if (typeof rsBlock == 'undefined') { + throw 'bad rs block @ typeNumber:' + typeNumber + + '/errorCorrectionLevel:' + errorCorrectionLevel; + } + + var length = rsBlock.length / 3; + + var list = []; + + for (var i = 0; i < length; i += 1) { + + var count = rsBlock[i * 3 + 0]; + var totalCount = rsBlock[i * 3 + 1]; + var dataCount = rsBlock[i * 3 + 2]; + + for (var j = 0; j < count; j += 1) { + list.push(qrRSBlock(totalCount, dataCount) ); + } + } + + return list; + }; + + return _this; + }(); + + //--------------------------------------------------------------------- + // qrBitBuffer + //--------------------------------------------------------------------- + + var qrBitBuffer = function() { + + var _buffer = []; + var _length = 0; + + var _this = {}; + + _this.getBuffer = function() { + return _buffer; + }; + + _this.getAt = function(index) { + var bufIndex = Math.floor(index / 8); + return ( (_buffer[bufIndex] >>> (7 - index % 8) ) & 1) == 1; + }; + + _this.put = function(num, length) { + for (var i = 0; i < length; i += 1) { + _this.putBit( ( (num >>> (length - i - 1) ) & 1) == 1); + } + }; + + _this.getLengthInBits = function() { + return _length; + }; + + _this.putBit = function(bit) { + + var bufIndex = Math.floor(_length / 8); + if (_buffer.length <= bufIndex) { + _buffer.push(0); + } + + if (bit) { + _buffer[bufIndex] |= (0x80 >>> (_length % 8) ); + } + + _length += 1; + }; + + return _this; + }; + + //--------------------------------------------------------------------- + // base64EncodeOutputStream + //--------------------------------------------------------------------- + + var base64EncodeOutputStream = function() { + + var _buffer = 0; + var _buflen = 0; + var _length = 0; + var _base64 = ''; + + var _this = {}; + + var writeEncoded = function(b) { + _base64 += String.fromCharCode(encode(b & 0x3f) ); + }; + + var encode = function(n) { + if (n < 0) { + // error. + } else if (n < 26) { + return 0x41 + n; + } else if (n < 52) { + return 0x61 + (n - 26); + } else if (n < 62) { + return 0x30 + (n - 52); + } else if (n == 62) { + return 0x2b; + } else if (n == 63) { + return 0x2f; + } + throw 'n:' + n; + }; + + _this.writeByte = function(n) { + + _buffer = (_buffer << 8) | (n & 0xff); + _buflen += 8; + _length += 1; + + while (_buflen >= 6) { + writeEncoded(_buffer >>> (_buflen - 6) ); + _buflen -= 6; + } + }; + + _this.flush = function() { + + if (_buflen > 0) { + writeEncoded(_buffer << (6 - _buflen) ); + _buffer = 0; + _buflen = 0; + } + + if (_length % 3 != 0) { + // padding + var padlen = 3 - _length % 3; + for (var i = 0; i < padlen; i += 1) { + _base64 += '='; + } + } + }; + + _this.toString = function() { + return _base64; + }; + + return _this; + }; + + //--------------------------------------------------------------------- + // base64DecodeInputStream + //--------------------------------------------------------------------- + + var base64DecodeInputStream = function(str) { + + var _str = str; + var _pos = 0; + var _buffer = 0; + var _buflen = 0; + + var _this = {}; + + _this.read = function() { + + while (_buflen < 8) { + + if (_pos >= _str.length) { + if (_buflen == 0) { + return -1; + } + throw 'unexpected end of file./' + _buflen; + } + + var c = _str.charAt(_pos); + _pos += 1; + + if (c == '=') { + _buflen = 0; + return -1; + } else if (c.match(/^\s$/) ) { + // ignore if whitespace. + continue; + } + + _buffer = (_buffer << 6) | decode(c.charCodeAt(0) ); + _buflen += 6; + } + + var n = (_buffer >>> (_buflen - 8) ) & 0xff; + _buflen -= 8; + return n; + }; + + var decode = function(c) { + if (0x41 <= c && c <= 0x5a) { + return c - 0x41; + } else if (0x61 <= c && c <= 0x7a) { + return c - 0x61 + 26; + } else if (0x30 <= c && c <= 0x39) { + return c - 0x30 + 52; + } else if (c == 0x2b) { + return 62; + } else if (c == 0x2f) { + return 63; + } else { + throw 'c:' + c; + } + }; + + return _this; + }; + + return qrcode; +}(); + +export { qrcode, qrDetectMode }; diff --git a/es/qrcodec.js b/es/qrcodec.js new file mode 100644 index 0000000..7041ef8 --- /dev/null +++ b/es/qrcodec.js @@ -0,0 +1,269 @@ +import { QRMode } from "./QRMode.js"; + +export const bin2str = (data) => { + if (data instanceof Uint8Array) { + const s = []; + for (let i = 0; i < data.length; i++) { + s[i] = String.fromCharCode(data[i]); + } + data = s.join(""); + } + return data; +}; + +const stringToBytes = (s) => { + const bytes = []; + if (typeof s == "string") { + s = new TextEncoder().encode(s); + } + for (let i = 0; i < s.length; i++) { + bytes.push(s[i]); + } + return bytes; +}; + +//--------------------------------------------------------------------- +// qrNumber +//--------------------------------------------------------------------- + +var qrNumber = function(data) { + + var _mode = QRMode.MODE_NUMBER; + var _data = data; + + var _this = {}; + + _this.getMode = function() { + return _mode; + }; + + _this.getLength = function(buffer) { + return _data.length; + }; + + _this.write = function(buffer) { + + var data = _data; + + var i = 0; + + while (i + 2 < data.length) { + buffer.put(strToNum(data.substring(i, i + 3) ), 10); + i += 3; + } + + if (i < data.length) { + if (data.length - i == 1) { + buffer.put(strToNum(data.substring(i, i + 1) ), 4); + } else if (data.length - i == 2) { + buffer.put(strToNum(data.substring(i, i + 2) ), 7); + } + } + }; + + var strToNum = function(s) { + var num = 0; + for (var i = 0; i < s.length; i += 1) { + num = num * 10 + chatToNum(s.charAt(i) ); + } + return num; + }; + + var chatToNum = function(c) { + if ('0' <= c && c <= '9') { + return c.charCodeAt(0) - '0'.charCodeAt(0); + } + throw 'illegal char :' + c; + }; + + return _this; +}; + +//--------------------------------------------------------------------- +// qrAlphaNum +//--------------------------------------------------------------------- + +var qrAlphaNum = function(data) { + + var _mode = QRMode.MODE_ALPHA_NUM; + var _data = data; + + var _this = {}; + + _this.getMode = function() { + return _mode; + }; + + _this.getLength = function(buffer) { + return _data.length; + }; + + _this.write = function(buffer) { + + var s = _data; + + var i = 0; + + while (i + 1 < s.length) { + buffer.put( + getCode(s.charAt(i) ) * 45 + + getCode(s.charAt(i + 1) ), 11); + i += 2; + } + + if (i < s.length) { + buffer.put(getCode(s.charAt(i) ), 6); + } + }; + + var getCode = function(c) { + + if ('0' <= c && c <= '9') { + return c.charCodeAt(0) - '0'.charCodeAt(0); + } else if ('A' <= c && c <= 'Z') { + return c.charCodeAt(0) - 'A'.charCodeAt(0) + 10; + } else { + switch (c) { + case ' ' : return 36; + case '$' : return 37; + case '%' : return 38; + case '*' : return 39; + case '+' : return 40; + case '-' : return 41; + case '.' : return 42; + case '/' : return 43; + case ':' : return 44; + default : + throw 'illegal char :' + c; + } + } + }; + + return _this; +}; + +//--------------------------------------------------------------------- +// qr8BitByte +//--------------------------------------------------------------------- + +var qr8BitByte = function(data) { + if (typeof data == "string") { + data = new TextEncoder().encode(data); + } + var _mode = QRMode.MODE_8BIT_BYTE; + + var _this = {}; + + _this.getMode = function() { + return _mode; + }; + + _this.getLength = function(buffer) { + return data.length; + }; + + _this.write = function(buffer) { + for (var i = 0; i < data.length; i += 1) { + buffer.put(data[i], 8); + } + }; + + return _this; +}; + +//--------------------------------------------------------------------- +// qrKanji +//--------------------------------------------------------------------- + +var qrKanji = function(data) { + var _mode = QRMode.MODE_KANJI; + var _data = data; + + /* + const encoder = new TextEncoder(""); //qrcode.stringToBytesFuncs['SJIS']; + if (!encoder) { + throw 'sjis not supported.'; + } + */ + !function(c, code) { + // self test for sjis support. + //var test = encoder.encode(c); + const test = encodeSJIS(c); + if (test.length != 2 || ( (test[0] << 8) | test[1]) != code) { + throw 'sjis not supported.'; + } + }('\u53cb', 0x9746); + + var _bytes = stringToBytes(data); + + var _this = {}; + + _this.getMode = function() { + return _mode; + }; + + _this.getLength = function(buffer) { + return ~~(_bytes.length / 2); + }; + + _this.write = function(buffer) { + + var data = _bytes; + + var i = 0; + + while (i + 1 < data.length) { + + var c = ( (0xff & data[i]) << 8) | (0xff & data[i + 1]); + + if (0x8140 <= c && c <= 0x9FFC) { + c -= 0x8140; + } else if (0xE040 <= c && c <= 0xEBBF) { + c -= 0xC140; + } else { + throw 'illegal char at ' + (i + 1) + '/' + c; + } + + c = ( (c >>> 8) & 0xff) * 0xC0 + (c & 0xff); + + buffer.put(c, 13); + + i += 2; + } + + if (i < data.length) { + throw 'illegal char at ' + (i + 1); + } + }; + + return _this; +}; + +const qrCheck = (func, s) => { + const dummy = { put: (c) => {}}; + try { + func(s).write(dummy); + return true; + } catch (e) { + //console.log(e); + } + return false; +}; + +const qrDetectMode = (s) => { + s = bin2str(s); + if (qrCheck(qrNumber, s)) { + return "Numeric"; + } + if (qrCheck(qrAlphaNum, s)) { + return "Alphanumeric"; + } + /* + if (qrCheck(qrKanji, s)) { + return "Kanji"; + } + */ + return "Byte"; +}; + +export { qrNumber, qrAlphaNum, qrKanji, qr8BitByte, qrCheck, qrDetectMode }; diff --git a/es/qrdata2svg.js b/es/qrdata2svg.js new file mode 100644 index 0000000..7cc1451 --- /dev/null +++ b/es/qrdata2svg.js @@ -0,0 +1,28 @@ +import { dot2svg } from "https://code4fukui.github.io/dot2svg/dot2svg.js"; + +const addMargin = (data, marginw) => { + const spcline = []; + for (let i = 0; i < data.length; i++) { + for (let j = 0; j < marginw; j++) { + data[i].unshift(0); + data[i].push(0); + } + } + for (let i = 0; i < data[0].length; i++) { + spcline.push(0); + } + for (let i = 0; i < marginw; i++) { + data.unshift(spcline); + data.push(spcline); + } +}; + +export const qrdata2svg = (data, dotw) => { + const marginw = 4; + addMargin(data, marginw); + + const dot = data.map(i => i.join("")).join("\n"); + const svg = dot2svg(dot, dotw); + const svg2 = `${svg}`; + return svg2; +}; diff --git a/es/sample.js b/es/sample.js new file mode 100644 index 0000000..bb9bf0a --- /dev/null +++ b/es/sample.js @@ -0,0 +1,4 @@ +import { QR } from "https://taisukef.github.io/qrcode-generator/es/QR.js"; + +const data = QR.encode("Hi!"); +console.log(data); diff --git a/es/sample_qrcode.js b/es/sample_qrcode.js new file mode 100644 index 0000000..aeb164a --- /dev/null +++ b/es/sample_qrcode.js @@ -0,0 +1,22 @@ +import { qrcode } from "./qrcode.js"; + +const typeNumber = 4; // 1 to 40 (MAX_TYPE_NUMBER) +const errorCorrectionLevel = "L"; // 'L','M','Q','H' +const mode = "Byte"; // 'Numeric' 'Alphanumeric' 'Byte'(base64?) 'Kanji' +const src = "Hi!Hi!Hi!Hi!Hi!Hi!Hi!Hi!Hi!Hi!Hi!Hi!Hi!Hi!Hi!Hi!Hi!Hi!"; + +const qr = qrcode(typeNumber, errorCorrectionLevel); +qr.addData(src, mode); +qr.make(); + +const data = qr.createASCII(2); +console.log(data); +console.log(qr._modules); +const w = qr.getModuleCount(); +for (let i = 0; i < w; i++) { + const line = []; + for (let j = 0; j < w; j++) { + line.push(qr.isDark(i, j) ? "1" : "0"); + } + console.log(line.join("")); +} diff --git a/es/sample_svg.js b/es/sample_svg.js new file mode 100644 index 0000000..d98ef3a --- /dev/null +++ b/es/sample_svg.js @@ -0,0 +1,6 @@ +import { QR } from "https://taisukef.github.io/qrcode-generator/es/QR.js"; +import { qrdata2svg } from "./qrdata2svg.js"; + +const data = QR.encode("Hello!"); +const svg = qrdata2svg(data, 10); +await Deno.writeTextFile("qr.svg", svg); diff --git a/es/smalltest.html b/es/smalltest.html new file mode 100644 index 0000000..08d4582 --- /dev/null +++ b/es/smalltest.html @@ -0,0 +1,10 @@ + + diff --git a/es/tagtest.html b/es/tagtest.html new file mode 100644 index 0000000..765ed16 --- /dev/null +++ b/es/tagtest.html @@ -0,0 +1,2 @@ + + diff --git a/es/test/QR.test.js b/es/test/QR.test.js new file mode 100644 index 0000000..b043fb5 --- /dev/null +++ b/es/test/QR.test.js @@ -0,0 +1,102 @@ +import * as t from "https://deno.land/std/testing/asserts.ts"; +import { QR } from "../QR.js"; +import { qrNumber, qrAlphaNum, qrKanji, qr8BitByte, qrCheck, qrDetectMode } from "../qrcodec.js"; + +Deno.test("qrNumber", () => { + t.assert(qrCheck(qrNumber, "1234")); + t.assert(!qrCheck(qrNumber, "33a")); +}); +Deno.test("qrAlphaNum", () => { + t.assert(qrCheck(qrAlphaNum, "A23")); // only Uppercase + t.assert(!qrCheck(qrAlphaNum, "a23")); + t.assert(qrCheck(qrAlphaNum, "A:")); + t.assert(qrCheck(qrAlphaNum, "HTTPS://JIG.JP/")); // ok + t.assert(!qrCheck(qrAlphaNum, "https://jig.jp/")); // NG +}); +Deno.test("qrDetectMode", () => { + t.assertEquals(qrDetectMode("A23"), "Alphanumeric"); + t.assertEquals(qrDetectMode("123"), "Numeric"); + t.assertEquals(qrDetectMode("jig.jp"), "Byte"); +}); + +Deno.test("should exist", () => { + t.assert(QR); +}); +Deno.test("should be a callable function", () => { + t.assert(QR.encode instanceof Function); +}); +Deno.test("encode Hi!", () => { + const data = QR.encode("Hi!"); + //console.log(JSON.stringify(data)) + const chk = [[1,1,1,1,1,1,1,0,0,1,0,1,1,0,1,1,1,1,1,1,1],[1,0,0,0,0,0,1,0,0,1,1,1,0,0,1,0,0,0,0,0,1],[1,0,1,1,1,0,1,0,1,1,0,1,1,0,1,0,1,1,1,0,1],[1,0,1,1,1,0,1,0,0,1,0,1,0,0,1,0,1,1,1,0,1],[1,0,1,1,1,0,1,0,0,0,1,0,1,0,1,0,1,1,1,0,1],[1,0,0,0,0,0,1,0,0,0,0,0,1,0,1,0,0,0,0,0,1],[1,1,1,1,1,1,1,0,1,0,1,0,1,0,1,1,1,1,1,1,1],[0,0,0,0,0,0,0,0,1,1,0,1,1,0,0,0,0,0,0,0,0],[1,1,1,0,1,1,1,1,1,1,1,1,0,1,1,0,0,0,1,0,0],[0,0,1,1,0,1,0,1,0,0,0,0,0,0,1,0,0,1,1,1,1],[0,0,0,1,0,1,1,0,1,0,1,0,1,0,0,0,1,1,1,1,1],[0,1,0,0,1,0,0,0,1,1,0,0,0,0,1,0,0,1,0,1,0],[1,1,1,1,0,0,1,1,0,0,0,0,1,0,1,0,1,0,0,0,0],[0,0,0,0,0,0,0,0,1,0,0,1,0,1,0,1,0,1,0,1,0],[1,1,1,1,1,1,1,0,1,0,1,1,0,1,1,1,0,1,1,1,1],[1,0,0,0,0,0,1,0,1,0,0,1,1,1,0,1,1,1,0,0,1],[1,0,1,1,1,0,1,0,1,0,0,1,0,1,1,1,0,0,1,0,1],[1,0,1,1,1,0,1,0,0,0,0,0,0,0,1,0,0,0,1,1,0],[1,0,1,1,1,0,1,0,1,1,0,0,1,0,0,0,1,0,0,0,1],[1,0,0,0,0,0,1,0,1,1,1,0,0,0,1,0,0,0,1,1,0],[1,1,1,1,1,1,1,0,1,1,0,0,1,0,1,0,1,0,1,1,1]]; + t.assertEquals(data, chk); + t.assertEquals(data.length, 21); +}); +Deno.test("encode HI!", () => { + const data = QR.encode("HI!"); + // console.log(JSON.stringify(data)) + const chk = [[1,1,1,1,1,1,1,0,0,1,0,1,1,0,1,1,1,1,1,1,1],[1,0,0,0,0,0,1,0,1,1,0,1,0,0,1,0,0,0,0,0,1],[1,0,1,1,1,0,1,0,1,1,0,0,1,0,1,0,1,1,1,0,1],[1,0,1,1,1,0,1,0,0,1,0,1,0,0,1,0,1,1,1,0,1],[1,0,1,1,1,0,1,0,1,0,0,0,1,0,1,0,1,1,1,0,1],[1,0,0,0,0,0,1,0,1,0,0,1,1,0,1,0,0,0,0,0,1],[1,1,1,1,1,1,1,0,1,0,1,0,1,0,1,1,1,1,1,1,1],[0,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,0,0],[1,1,0,1,0,0,1,1,0,1,1,0,0,0,1,1,1,0,1,1,0],[0,1,1,1,1,0,0,0,0,1,1,0,0,0,1,0,0,1,1,1,0],[0,1,0,0,1,0,1,0,0,0,1,0,1,1,0,0,0,1,1,0,1],[0,0,1,0,0,1,0,0,1,1,0,1,0,0,0,0,0,0,0,1,1],[0,0,1,0,0,0,1,1,1,1,0,0,1,0,1,0,1,0,0,0,0],[0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,1,1,1,0,0,0],[1,1,1,1,1,1,1,0,1,0,0,0,0,1,0,1,0,0,1,1,0],[1,0,0,0,0,0,1,0,0,0,1,1,1,1,0,1,1,1,0,0,1],[1,0,1,1,1,0,1,0,0,1,0,1,0,0,1,1,1,0,1,1,1],[1,0,1,1,1,0,1,0,1,1,1,1,0,0,0,0,0,1,1,1,1],[1,0,1,1,1,0,1,0,0,1,0,0,1,0,0,0,1,0,0,0,1],[1,0,0,0,0,0,1,0,1,1,0,0,0,1,1,0,1,0,1,0,0],[1,1,1,1,1,1,1,0,1,1,1,1,1,0,0,0,1,1,1,1,0]]; + t.assertEquals(data, chk); + t.assertEquals(data.length, 21); +}); +Deno.test("encode Numeric", () => { + const s = "12345678901234567890123456789012345678901"; + const data = QR.encode(s); + t.assertEquals(qrDetectMode(s), "Numeric"); + t.assertEquals(data.length, 21); +}); +Deno.test("encode Alphanumeric", () => { + const s = "HIHIHIHIHIHIHIHIHIHIHIHIH"; + const data = QR.encode(s); + t.assertEquals(qrDetectMode(s), "Alphanumeric"); + t.assertEquals(data.length, 21); +}); +Deno.test("encode Byte", () => { + const s = "hIHIHIHIHIHIHIHIHIHIHIHIH"; + const data = QR.encode(s); + t.assertEquals(qrDetectMode(s), "Byte"); + t.assertEquals(data.length, 25); +}); +Deno.test("encode unicode", () => { + const s = "漢字漢字漢字漢字漢字漢字漢字漢字漢字"; + const data = QR.encode(s); + t.assertEquals(qrDetectMode(s), "Byte"); + t.assertEquals(data.length, 33); +}); +Deno.test("encode bin", () => { + const bin = new Uint8Array([1, 2, 3]); + const data = QR.encode(bin); + t.assertEquals(qrDetectMode(bin), "Byte"); + t.assertEquals(data.length, 21); +}); +Deno.test("multi chunks", () => { + const bin = new Uint8Array([1, 2, 3]); + const data = QR.encode(["abc", bin]); + t.assertEquals(data.length, 21); + const data2 = QR.encode(["ABC100029130904930913094", bin], null, null, ["Alphanumeric", "Byte"]); + t.assertEquals(data2.length, 25); +}); +Deno.test("encode Numeric by Uint8Array", () => { + const s = new TextEncoder().encode("12345678901234567890123456789012345678901"); + const data = QR.encode(s); + t.assertEquals(qrDetectMode(s), "Numeric"); + t.assertEquals(data.length, 21); +}); +Deno.test("encode Alphanumeric by Uint8Array", () => { + const s = new TextEncoder().encode("HIHIHIHIHIHIHIHIHIHIHIHIH"); + const data = QR.encode(s); + t.assertEquals(qrDetectMode(s), "Alphanumeric"); + t.assertEquals(data.length, 21); +}); +Deno.test("encode Byte by Uint8Array", () => { + const s = new TextEncoder().encode("hIHIHIHIHIHIHIHIHIHIHIHIH"); + const data = QR.encode(s); + t.assertEquals(qrDetectMode(s), "Byte"); + t.assertEquals(data.length, 25); +}); +Deno.test("encode Byte by Uint8Array", () => { + const s = new Uint8Array([1, 2, 3]); + const data = QR.encode(s); + t.assertEquals(qrDetectMode(s), "Byte"); + t.assertEquals(data.length, 21); +});