/* * 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); } } };