diff --git a/README.md b/README.md index 8d77f87e..448f6646 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,9 @@ Renderer options: -l, --lightcolor Light RGBA hex color -d, --darkcolor Dark RGBA hex color +Terminal options: + --small Output smaller QR code to terminal [boolean] + Options: -o, --output Output file -h, --help Show help [boolean] @@ -725,6 +728,12 @@ See [Options](#options). Scale factor. A value of `1` means 1px per modules (black dots). +##### `small` + Type: `Boolean`
+ Default: `false` + + Relevant only for terminal renderer. Outputs smaller QR code. + ##### `width` Type: `Number`
diff --git a/bin/qrcode b/bin/qrcode index 7f1bca11..388a2a16 100755 --- a/bin/qrcode +++ b/bin/qrcode @@ -30,6 +30,8 @@ function parseOptions (args) { version: args.qversion, errorCorrectionLevel: args.error, type: args.type, + small: !!args.small, + inverse: !!args.inverse, maskPattern: args.mask, margin: args.qzone, width: args.width, @@ -82,6 +84,18 @@ var argv = yargs implies: 'output', group: 'Renderer options:' }) + .option('small', { + type: 'boolean', + description: 'Output smaller QR code to terminal', + conflicts: 'type', + group: 'Renderer options:' + }) + .option('i', { + alias: 'inverse', + type: 'boolean', + description: 'Invert colors', + group: 'Renderer options:' + }) .option('w', { alias: 'width', description: 'Image width (px)', diff --git a/lib/renderer/terminal.js b/lib/renderer/terminal.js index f15610b5..36ddfde7 100644 --- a/lib/renderer/terminal.js +++ b/lib/renderer/terminal.js @@ -1,49 +1,9 @@ -// let Utils = require('./utils') +const big = require('./terminal/terminal') +const small = require('./terminal/terminal-small') exports.render = function (qrData, options, cb) { - const size = qrData.modules.size - const data = qrData.modules.data - - // let opts = Utils.getOptions(options) - - // use same scheme as https://github.com/gtanner/qrcode-terminal because it actually works! =) - const black = '\x1b[40m \x1b[0m' - const white = '\x1b[47m \x1b[0m' - - let output = '' - const hMargin = Array(size + 3).join(white) - const vMargin = Array(2).join(white) - - output += hMargin + '\n' - for (let i = 0; i < size; ++i) { - output += white - for (let j = 0; j < size; j++) { - // let topModule = data[i * size + j] - // let bottomModule = data[(i + 1) * size + j] - - output += data[i * size + j] ? black : white// getBlockChar(topModule, bottomModule) - } - // output += white+'\n' - output += vMargin + '\n' - } - - output += hMargin + '\n' - - if (typeof cb === 'function') { - cb(null, output) - } - - return output -} -/* -exports.renderToFile = function renderToFile (path, qrData, options, cb) { - if (typeof cb === 'undefined') { - cb = options - options = undefined + if (options && options.small) { + return small.render(qrData, options, cb) } - - let fs = require('fs') - let utf8 = exports.render(qrData, options) - fs.writeFile(path, utf8, cb) + return big.render(qrData, options, cb) } -*/ diff --git a/lib/renderer/terminal/terminal-small.js b/lib/renderer/terminal/terminal-small.js new file mode 100644 index 00000000..9810d4e1 --- /dev/null +++ b/lib/renderer/terminal/terminal-small.js @@ -0,0 +1,85 @@ +const backgroundWhite = '\x1b[47m' +const backgroundBlack = '\x1b[40m' +const foregroundWhite = '\x1b[37m' +const foregroundBlack = '\x1b[30m' +const reset = '\x1b[0m' +const lineSetupNormal = backgroundWhite + foregroundBlack // setup colors +const lineSetupInverse = backgroundBlack + foregroundWhite // setup colors + +const createPalette = function (lineSetup, foregroundWhite, foregroundBlack) { + return { + // 1 ... white, 2 ... black, 0 ... transparent (default) + + '00': reset + ' ' + lineSetup, + '01': reset + foregroundWhite + '▄' + lineSetup, + '02': reset + foregroundBlack + '▄' + lineSetup, + 10: reset + foregroundWhite + '▀' + lineSetup, + 11: ' ', + 12: '▄', + 20: reset + foregroundBlack + '▀' + lineSetup, + 21: '▀', + 22: '█' + } +} + +/** + * Returns code for QR pixel + * @param {boolean[][]} modules + * @param {number} size + * @param {number} x + * @param {number} y + * @return {'0' | '1' | '2'} + */ +const mkCodePixel = function (modules, size, x, y) { + const sizePlus = size + 1 + if ((x >= sizePlus) || (y >= sizePlus) || (y < -1) || (x < -1)) return '0' + if ((x >= size) || (y >= size) || (y < 0) || (x < 0)) return '1' + const idx = (y * size) + x + return modules[idx] ? '2' : '1' +} + +/** + * Returns code for four QR pixels. Suitable as key in palette. + * @param {boolean[][]} modules + * @param {number} size + * @param {number} x + * @param {number} y + * @return {keyof palette} + */ +const mkCode = function (modules, size, x, y) { + return ( + mkCodePixel(modules, size, x, y) + + mkCodePixel(modules, size, x, y + 1) + ) +} + +exports.render = function (qrData, options, cb) { + const size = qrData.modules.size + const data = qrData.modules.data + + const inverse = !!(options && options.inverse) + const lineSetup = options && options.inverse ? lineSetupInverse : lineSetupNormal + const white = inverse ? foregroundBlack : foregroundWhite + const black = inverse ? foregroundWhite : foregroundBlack + + const palette = createPalette(lineSetup, white, black) + const newLine = reset + '\n' + lineSetup + + let output = lineSetup // setup colors + + for (let y = -1; y < size + 1; y += 2) { + for (let x = -1; x < size; x++) { + output += palette[mkCode(data, size, x, y)] + } + + output += palette[mkCode(data, size, size, y)] + newLine + } + + output += reset + + if (typeof cb === 'function') { + cb(null, output) + } + + return output +} diff --git a/lib/renderer/terminal/terminal.js b/lib/renderer/terminal/terminal.js new file mode 100644 index 00000000..f15610b5 --- /dev/null +++ b/lib/renderer/terminal/terminal.js @@ -0,0 +1,49 @@ +// let Utils = require('./utils') + +exports.render = function (qrData, options, cb) { + const size = qrData.modules.size + const data = qrData.modules.data + + // let opts = Utils.getOptions(options) + + // use same scheme as https://github.com/gtanner/qrcode-terminal because it actually works! =) + const black = '\x1b[40m \x1b[0m' + const white = '\x1b[47m \x1b[0m' + + let output = '' + const hMargin = Array(size + 3).join(white) + const vMargin = Array(2).join(white) + + output += hMargin + '\n' + for (let i = 0; i < size; ++i) { + output += white + for (let j = 0; j < size; j++) { + // let topModule = data[i * size + j] + // let bottomModule = data[(i + 1) * size + j] + + output += data[i * size + j] ? black : white// getBlockChar(topModule, bottomModule) + } + // output += white+'\n' + output += vMargin + '\n' + } + + output += hMargin + '\n' + + if (typeof cb === 'function') { + cb(null, output) + } + + return output +} +/* +exports.renderToFile = function renderToFile (path, qrData, options, cb) { + if (typeof cb === 'undefined') { + cb = options + options = undefined + } + + let fs = require('fs') + let utf8 = exports.render(qrData, options) + fs.writeFile(path, utf8, cb) +} +*/ diff --git a/test/unit/renderer/terminal.test.js b/test/unit/renderer/terminal.test.js index b0ada4d0..a1a23caa 100644 --- a/test/unit/renderer/terminal.test.js +++ b/test/unit/renderer/terminal.test.js @@ -9,7 +9,7 @@ test('TerminalRenderer interface', function (t) { t.end() }) -test('TerminalRenderer render', function (t) { +test('TerminalRenderer render big', function (t) { const sampleQrData = QRCode.create('sample text', { version: 2 }) let str @@ -35,3 +35,45 @@ test('TerminalRenderer render', function (t) { t.end() }) + +test('TerminalRenderer render small', function (t) { + const sampleQrData = QRCode.create('sample text', { version: 2 }) + let str + let calledCallback = false + const callback = function () { calledCallback = true } + + t.notThrow(function () { str = TerminalRenderer.render(sampleQrData) }, + 'Should not throw with only qrData param') + + t.notThrow(function () { + str = TerminalRenderer.render(sampleQrData, { + margin: 10, + scale: 1, + small: true + }) + }, 'Should not throw with options param and without callback') + + t.notThrow(function () { + str = TerminalRenderer.render(sampleQrData, { + margin: 10, + scale: 1, + small: true + }, + callback) + }, 'Should not throw with options param and callback') + + t.type(str, 'string', + 'Should return a string') + + t.equal(calledCallback, true, 'string', + 'Should call a callback') + + t.notThrow(function () { + str = TerminalRenderer.render(sampleQrData, { small: true, inverse: true }) + }, 'Should not throw with inverse options') + + t.type(str, 'string', + 'Should return a string if inverse option is set') + + t.end() +})