From fffd25643ba19ff6ecb60e3f1af8f282d0c2e135 Mon Sep 17 00:00:00 2001 From: ahwayakchih Date: Tue, 19 Jun 2018 11:27:13 +0200 Subject: [PATCH 1/2] feat: add `createASCII` method - Added `createASCII` method that outputs QR code rendered using ASCII blocks. - Added JS development dependency on `mocha` to run new tests for basic GIF and ASCII output. --- js/package.json | 11 ++++- js/qrcode.d.ts | 1 + js/qrcode.js | 92 ++++++++++++++++++++++++++++++++++++++++++ js/test/qrcode.js | 100 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 203 insertions(+), 1 deletion(-) create mode 100644 js/test/qrcode.js diff --git a/js/package.json b/js/package.json index caf01c6..3b0ebd4 100644 --- a/js/package.json +++ b/js/package.json @@ -17,5 +17,14 @@ "qrcode", "generator" ], - "license": "MIT" + "license": "MIT", + "directories": { + "test": "test" + }, + "scripts": { + "test": "mocha" + }, + "devDependencies": { + "mocha": "^5.2.0" + } } diff --git a/js/qrcode.d.ts b/js/qrcode.d.ts index dbe4305..ad55e39 100644 --- a/js/qrcode.d.ts +++ b/js/qrcode.d.ts @@ -43,6 +43,7 @@ interface QRCode { createImgTag(cellSize?: number, margin?: number) : string; createSvgTag(cellSize?: number, margin?: number) : string; createTableTag(cellSize?: number, margin?: number) : string; + createASCII(cellSize?: number, margin?: number) : string; renderTo2dContext(context: CanvasRenderingContext2D, cellSize?: number): void; } diff --git a/js/qrcode.js b/js/qrcode.js index 8e93c2e..ce6ace3 100644 --- a/js/qrcode.js +++ b/js/qrcode.js @@ -574,6 +574,98 @@ var qrcode = function() { return img; }; + 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 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 += blocks[p]; + } + + ascii += '\n'; + } + + if (size % 2) { + 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(); diff --git a/js/test/qrcode.js b/js/test/qrcode.js new file mode 100644 index 0000000..1da6651 --- /dev/null +++ b/js/test/qrcode.js @@ -0,0 +1,100 @@ +var assert = require('assert'); +var qrcode = require('../qrcode.js'); + +describe('QRCode', function(){ + it('should exist', function(){ + assert.ok(qrcode); + }); + + it('should be a callable function', function(){ + assert.ok(qrcode instanceof Function); + }); + + it('should generate correct GIF image tag', function(){ + var sourceText = 'http://www.example.com/ążśźęćńół'; + var correctImgTag = ''; + + var qr = qrcode(-1, 'M'); + qr.addData(unescape(encodeURI(sourceText))); + qr.make(); + + assert.strictEqual(qr.createImgTag(), correctImgTag); + }); + + it('should generate correct GIF image data', function(){ + var sourceText = 'http://www.example.com/ążśźęćńół'; + var correctImgData = 'R0lGODdhSgBKAIAAAAAAAP///ywAAAAASgBKAAAC/4yPqcvtD6OctNqLs968+w+G4kiWZgKk6roa7ZEiccACco1TeE6r9997uXYsna0xmw0VLyXsyHBCpKijk9qEPh1UrrXYAy4Xu3FtLEmCr9pucP3NGofyN9peBZ7DE3W9Tqd15+fDlxZXiPVlltiGqDb3BpnHRzj4uNgnSHkjZCiZGbXpRVrlyQZINopE9Jc5CdtaeCj7asvIWEvimet4oqkYKDZiCSrMWtobmcW8esfki2exN4XY2cj7aZQtehxIGHwR+41dDg3tdogLCm7N+ZTOOom+irqZGk//DF/vqDiv7cEpTMW4CZxVYSC5gghpOTt46dg4PGxE5aNUkeItje3Drm3zh9DewIytPl7LuBCkHiImpbEjaAvlloTm+DVzJXNftZM89lAz5vLiOZs94YysqZNmtZ8/vQiFeJApmKUPu30qU6mhKola1V0t6g2ZxqcaAK4biraE2axW004LyeNs1loGHfIkGY3uVLV7Ge4rSayvu7WAHQIGGMvn4KqqdLGd2S5xKE2lit2dfIlswJVhU/5VSRmoyEbr9NbdKhqkq156l8GJCFQZ0aRWFcOWGtZ2aN2IN3LlvPv149Kg8UZqi5ElP+JvkyXH+ns4MFnR/+0F/vEw6YDQk//6Dj68+PHky5s/jz69+vULCgAAOw=='; + + var qr = qrcode(-1, 'M'); + qr.addData(unescape(encodeURI(sourceText))); + qr.make(); + + var data = Buffer.from(qr.createDataURL().replace('data:image/gif;base64,', ''), 'base64'); + + assert.strictEqual(data.toString('base64'), correctImgData); + }); + + it('should generate correct UTF8 text data', function(){ + var sourceText = 'http://www.example.com/ążśźęćńół'; + var correctTextData1 = [ + '█████████████████████████████████', + '██ ▄▄▄▄▄ █ ▄█▀▄██ ▀▄ ▄██ ▄▄▄▄▄ ██', + '██ █ █ █▀ █▀▄█▀▀█▄▄ ▄█ █ █ ██', + '██ █▄▄▄█ ███ ▀ █▄▀▄▄▀ ▀█ █▄▄▄█ ██', + '██▄▄▄▄▄▄▄█ ▀▄█▄▀▄▀ █▄▀▄█▄▄▄▄▄▄▄██', + '██▄█ ▀▄▄▄█▄▄█▀█▀▀▄█▀▀▀█ ▀▀▄█▄ ██', + '██▀▀▄▀▄█▄█ ▄▄█▄ ▄█ ███ ▀█▀▄▄▀██', + '██ ▄▀█▄▀▄ ▄▀▄ █ ▄▀▀█▀██▀██▄▄▀▀██', + '██ █▀ ▄▄▀▀ ▀█ █▀▄ ▀█▄▀▄▄▄ ▄██', + '██▄██ ▄▄▄▄▄█ ▀▄▄ ▀▀▄▄▄█▄▄█▀ ███', + '██▄█▄██▄▄▄ █ █▄▄▀█▀███▄▀▄▄█▄ ████', + '███▄▄██▄▄▄ ▀▀▄█ █▀ █ ▄ ▄▄▄ ▀▀██', + '██ ▄▄▄▄▄ █ █ ▀█▄█ ▀ ▄ █▄█ ▄█ ▀██', + '██ █ █ █▀▄▄ ▄▀ ▀▄█ █ ▄▄ ▄ ██', + '██ █▄▄▄█ █▄█▄▀█ ▀ ▀ ██ █ █▀▄▀▄██', + '██▄▄▄▄▄▄▄█▄▄█▄█▄███▄▄▄▄████▄█▄███', + '▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀'].join('\n'); + + var correctTextData2 = [ + '██████████████████████████████████████████████████████████████████', + '██████████████████████████████████████████████████████████████████', + '████ ██ ████ ████ ██ ████ ████', + '████ ██████████ ██ ████ ██████ ██ ██████ ██████████ ████', + '████ ██ ██ ████ ████ ████████ ██ ██ ██ ████', + '████ ██ ██ ██ ██ ████ ██████ ████ ██ ██ ████', + '████ ██ ██ ██████ ██ ██ ██ ██ ████ ██ ██ ████', + '████ ██████████ ██████ ████ ████ ██ ██████████ ████', + '████ ██ ██ ██ ██ ██ ██ ██ ██ ████', + '████████████████████ ██████ ██ ████ ██████████████████████', + '████ ██ ██ ██ ██████████ ██████████ ████ ██ ████', + '████████ ██████████████ ██ ████ ██ ██████ ████', + '████████ ██ ██ ██ ██ ██ ██████ ██████ ██████', + '████ ██ ████████ ████████ ████ ██████ ██ ████ ████', + '████ ████ ██ ██ ██ ██████████████████ ████████', + '████ ██ ████ ██ ██ ██ ██ ██ ██ ████ ████████ ████', + '████ ████ ████ ████ ████ ████ ██ ████', + '████ ██ ████ ██ ██ ██ ████ ██████ ██████', + '████ ████ ██ ██ ████ ██ ████ ██████', + '██████████ ████████████ ████ ██████████████ ██████', + '████ ██ ████ ██ ██ ████████████ ██ ██ ████████', + '████████████████████ ██ ██████ ██ ████████ ████████ ████████', + '██████ ████ ████ ██ ████ ██ ████████', + '████████████████████ ████ ██ ██ ██ ██████ ████', + '████ ██ ██ ████ ██ ██ ██ ██ ██ ██████', + '████ ██████████ ██ ██ ██████ ██ ██████ ████ ████', + '████ ██ ██ ████ ██ ██ ██ ██ ████', + '████ ██ ██ ██ ████ ██ ████ ██ ████ ██ ████', + '████ ██ ██ ██ ██ ████ ██ ██ ████ ██ ████ ██ ████', + '████ ██████████ ████████ ██ ████ ██ ██ ██ ██████', + '████ ██ ██ ██ ██████ ████████ ██ ██████', + '██████████████████████████████████████████████████████████████████', + '██████████████████████████████████████████████████████████████████',].join('\n'); + + var qr = qrcode(-1, 'M'); + qr.addData(unescape(encodeURI(sourceText))); + qr.make(); + + assert.strictEqual(qr.createASCII(), correctTextData1, 'ASCII QRCode of size 1 is incorrect'); + assert.strictEqual(qr.createASCII(2), correctTextData2, 'ASCII QRCode of size 2 is incorrect'); + }); +}); \ No newline at end of file From fee569213578191c8fcfcf99de04b20ef0a9eea4 Mon Sep 17 00:00:00 2001 From: ahwayakchih Date: Tue, 19 Jun 2018 11:44:52 +0200 Subject: [PATCH 2/2] doc: update README --- js/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/js/README.md b/js/README.md index b34984a..167d75f 100644 --- a/js/README.md +++ b/js/README.md @@ -72,6 +72,7 @@ _[Note] call make() before this function._ #### createImgTag(cellSize, margin, alt) => string #### createSvgTag(cellSize, margin) => string #### createTableTag(cellSize, margin) => string +#### createASCII(cellSize, margin) => string Helper functions for HTML. _[Note] call make() before these functions._