node-qrcode-lite/lib/qrcode-draw.js
2016-09-26 09:30:10 +02:00

266 lines
7.7 KiB
JavaScript

/*
* copyright 2010-2012 Ryan Day
* http://github.com/soldair/node-qrcode
*
* Licensed under the MIT license:
* http://www.opensource.org/licenses/mit-license.php
*
* canvas example and fallback support example provided by Joshua Koo
* http://jabtunes.com/labs/qrcode.html
* "Instant QRCode Mashup by Joshua Koo!"
* as far as i can tell the page and the code on the page are public domain
*
* original table example and library provided by Kazuhiko Arase
* http://d-project.googlecode.com/svn/trunk/misc/qrcode/js/
*
*/
var bops = require('bops')
var QRCodeLib = require('./qrcode.js');
var QRVersionCapacityTable = require('./qrcapacitytable.js').QRCapacityTable;
var QRCode = QRCodeLib.QRCode;
exports.QRCodeDraw = QRCodeDraw;
exports.QRVersionCapacityTable = QRVersionCapacityTable;
exports.QRErrorCorrectLevel = QRCodeLib.QRErrorCorrectLevel;
exports.QRCode = QRCodeLib.QRCode;
function QRCodeDraw(){}
QRCodeDraw.prototype = {
scale:4,//4 px module size
defaultMargin:20,
marginScaleFactor:5,
// you may configure the error behavior for input string too long
errorBehavior:{
length:'trim'
},
color:{
dark:'black',
light:'white'
},
defaultErrorCorrectLevel:QRCodeLib.QRErrorCorrectLevel.H,
QRErrorCorrectLevel:QRCodeLib.QRErrorCorrectLevel,
draw:function(canvas,text,options,cb){
var level,
error,
errorCorrectLevel;
var args = Array.prototype.slice.call(arguments);
cb = args.pop();
canvas = args.shift();
text = args.shift();
options = args.shift()||{};
if(typeof cb != 'function') {
//enforce callback api just in case the processing can be made async in the future
// or support proc open to libqrencode
throw new Error('callback required');
}
if(typeof options !== "object"){
options.errorCorrectLevel = options;
}
this.QRVersion(
text
,options.errorCorrectLevel||this.QRErrorCorrectLevel.H
,options.version
,function(e,t,l,ec){
text = t,level = l,error = e,errorCorrectLevel = ec;
});
this.scale = options.scale||this.scale;
this.margin = typeof(options.margin) === 'undefined' ? this.defaultMargin : options.margin;
if(!level) {
//if we are unable to find an appropriate qr level error out
cb(error,canvas);
return;
}
//create qrcode!
try{
var qr = new QRCodeLib.QRCode(level, errorCorrectLevel)
, scale = this.scale||4
, ctx = canvas.getContext('2d')
, width = 0;
qr.addData(text);
qr.make();
var margin = this.marginWidth();
var currenty = margin;
width = this.dataWidth(qr)+ margin*2;
this.resetCanvas(canvas,ctx,width);
for (var r = 0,rl=qr.getModuleCount(); r < rl; r++) {
var currentx = margin;
for (var c = 0,cl=qr.getModuleCount(); c < cl; c++) {
if (qr.isDark(r, c) ) {
ctx.fillStyle = this.color.dark;
ctx.fillRect (currentx, currenty, scale, scale);
} else if(this.color.light){
//if falsy configured color
ctx.fillStyle = this.color.light;
ctx.fillRect (currentx, currenty, scale, scale);
}
currentx += scale;
}
currenty += scale;
}
} catch (e) {
error = e;
}
cb(error,canvas,width);
},
drawBitArray:function(text/*,errorCorrectLevel,options,cb*/) {
var args = Array.prototype.slice.call(arguments),
cb = args.pop(),
text = args.shift(),
options = args.shift() || {};
//argument processing
if(typeof cb != 'function') {
//enforce callback api just in case the processing can be made async in the future
// or support proc open to libqrencode
throw new Error('callback required as last argument');
}
//this interface kinda sucks - there is very small likelyhood of this ever being async
this.QRVersion(text,options.errorCorrectLevel,options.version,function(e,t,l,ec){
text = t,level = l,error = e,errorCorrectLevel = ec;
});
if(!level) {
//if we are unable to find an appropriate qr level error out
cb(error,[],0);
return;
}
//create qrcode!
try{
var qr = new QRCodeLib.QRCode(level, errorCorrectLevel)
, scale = this.scale||4
, width = 0,bits,bitc=0,currenty=0;
qr.addData(text);
qr.make();
width = this.dataWidth(qr,1);
bits = new Array(width*width);
for (var r = 0,rl=qr.getModuleCount(); r < rl; r++) {
for (var c = 0,cl=qr.getModuleCount(); c < cl; c++) {
if (qr.isDark(r, c) ) {
bits[bitc] = 1;
} else {
bits[bitc] = 0;
}
bitc++;
}
}
} catch (e) {
error = e;
console.log(e.stack);
}
cb(error,bits,width);
},
QRVersion:function(text,errorCorrectLevel,version,cb){
var c = bops.from(text).length,// BINARY LENGTH!
error,
errorCorrectLevel = this.QRErrorCorrectLevel[errorCorrectLevel];
if(errorCorrectLevel === undefined) errorCorrectLevel = this.defaultErrorCorrectLevel
var errorCorrectIndex = [1,0,3,2],//fix odd mapping to order in table
keys = ['L','M','Q','H'],
capacity = 0,
versionSpecified = false;
if(typeof version !== "undefined" && version !== null) {
versionSpecified = true;
}
//TODO ADD THROW FOR INVALID errorCorrectLevel...?
if(versionSpecified){
//console.log('SPECIFIED VERSION! ',version);
//i have specified a version. this will give me a fixed size qr code. version must be valid. 1-40
capacity = QRVersionCapacityTable[version][errorCorrectIndex[errorCorrectLevel]];
} else {
//figure out what version can hold the amount of text
for(var i=0,j=QRVersionCapacityTable.length;i<j;i++) {
capacity = QRVersionCapacityTable[i][errorCorrectIndex[errorCorrectLevel]];
if(c < QRVersionCapacityTable[i][errorCorrectIndex[errorCorrectLevel]]){
version = i+1;
break;
}
}
//if not version set to max
if(!version) {
version = QRVersionCapacityTable.length-1;
}
}
if(capacity < c){
if(this.errorBehavior.length == 'trim'){
text = text.substr(0,capacity);
level = QRVersionCapacityTable.length;
} else {
error = new Error('input string too long for error correction '
+keys[errorCorrectIndex[errorCorrectLevel]]
+' max length '
+ capacity
+' for qrcode version '+version
);
}
}
if(cb) {
cb(error,text,version,errorCorrectLevel);
}
return version;
},
marginWidth:function(){
var margin = this.margin;
this.scale = this.scale||4;
//elegant white space next to code is required by spec
if ((this.scale * this.marginScaleFactor > margin) && margin > 0){
margin = this.scale * this.marginScaleFactor;
}
return margin;
},
dataWidth:function(qr,scale){
return qr.getModuleCount()*(scale||this.scale||4);
},
resetCanvas:function(canvas,ctx,width){
ctx.clearRect(0,0,canvas.width,canvas.height);
if(!canvas.style) canvas.style = {};
canvas.style.height = canvas.height = width;//square!
canvas.style.width = canvas.width = width;
if(this.color.light){
ctx.fillStyle = this.color.light;
ctx.fillRect(0,0,canvas.width,canvas.height);
} else {
//support transparent backgrounds?
//not exactly to spec but i really would like someone to be able to add a background with heavily reduced luminosity for simple branding
//i could just ditch this because you could also just set #******00 as the color =P
ctx.clearRect(0,0,canvas.width,canvas.height);
}
}
};