Merge pull request #233 from bambooCZ/renderer-terminal-small
Add support for smaller terminal QR codes
This commit is contained in:
commit
f08fd572d7
6 changed files with 203 additions and 46 deletions
|
@ -61,6 +61,7 @@ Renderer options:
|
|||
-q, --qzone Quiet zone size [number]
|
||||
-l, --lightcolor Light RGBA hex color
|
||||
-d, --darkcolor Dark RGBA hex color
|
||||
--small Output smaller QR code to terminal [boolean]
|
||||
|
||||
Options:
|
||||
-o, --output Output file
|
||||
|
@ -725,6 +726,12 @@ See [Options](#options).
|
|||
|
||||
Scale factor. A value of `1` means 1px per modules (black dots).
|
||||
|
||||
##### `small`
|
||||
Type: `Boolean`<br>
|
||||
Default: `false`
|
||||
|
||||
Relevant only for terminal renderer. Outputs smaller QR code.
|
||||
|
||||
##### `width`
|
||||
Type: `Number`<br>
|
||||
|
||||
|
|
14
bin/qrcode
14
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,12 @@ var argv = yargs
|
|||
implies: 'output',
|
||||
group: 'Renderer options:'
|
||||
})
|
||||
.option('i', {
|
||||
alias: 'inverse',
|
||||
type: 'boolean',
|
||||
description: 'Invert colors',
|
||||
group: 'Renderer options:'
|
||||
})
|
||||
.option('w', {
|
||||
alias: 'width',
|
||||
description: 'Image width (px)',
|
||||
|
@ -112,6 +120,12 @@ var argv = yargs
|
|||
description: 'Dark RGBA hex color',
|
||||
group: 'Renderer options:'
|
||||
})
|
||||
.option('small', {
|
||||
type: 'boolean',
|
||||
description: 'Output smaller QR code to terminal',
|
||||
conflicts: 'type',
|
||||
group: 'Renderer options:'
|
||||
})
|
||||
.option('o', {
|
||||
alias: 'output',
|
||||
description: 'Output file'
|
||||
|
|
|
@ -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'
|
||||
if (options && options.small) {
|
||||
return small.render(qrData, options, cb)
|
||||
}
|
||||
|
||||
output += hMargin + '\n'
|
||||
|
||||
if (typeof cb === 'function') {
|
||||
cb(null, output)
|
||||
}
|
||||
|
||||
return output
|
||||
return big.render(qrData, options, cb)
|
||||
}
|
||||
/*
|
||||
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)
|
||||
}
|
||||
*/
|
||||
|
|
85
lib/renderer/terminal/terminal-small.js
Normal file
85
lib/renderer/terminal/terminal-small.js
Normal file
|
@ -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
|
||||
}
|
49
lib/renderer/terminal/terminal.js
Normal file
49
lib/renderer/terminal/terminal.js
Normal file
|
@ -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)
|
||||
}
|
||||
*/
|
|
@ -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()
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue