266 lines
7.7 KiB
JavaScript
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);
|
|
}
|
|
}
|
|
};
|
|
|