1/** @preserve 2jSignature v2 "${buildDate}" "${commitID}" 3Copyright (c) 2012 Willow Systems Corp http://willow-systems.com 4Copyright (c) 2010 Brinley Ang http://www.unbolt.net 5MIT License <http://www.opensource.org/licenses/mit-license.php> 6 7*/ 8;(function() { 9 10var apinamespace = 'jSignature' 11 12/** 13Allows one to delay certain eventual action by setting up a timer for it and allowing one to delay it 14by "kick"ing it. Sorta like "kick the can down the road" 15 16@public 17@class 18@param 19@returns {Type} 20*/ 21var KickTimerClass = function(time, callback) { 22 var timer; 23 this.kick = function() { 24 clearTimeout(timer); 25 timer = setTimeout( 26 callback 27 , time 28 ); 29 } 30 this.clear = function() { 31 clearTimeout(timer); 32 } 33 return this; 34} 35 36var PubSubClass = function(context){ 37 'use strict' 38 /* @preserve 39 ----------------------------------------------------------------------------------------------- 40 JavaScript PubSub library 41 2012 (c) Willow Systems Corp (www.willow-systems.com) 42 based on Peter Higgins ([email protected])
43 Loosely based on Dojo publish/subscribe API, limited in scope. Rewritten blindly. 44 Original is (c) Dojo Foundation 2004-2010. Released under either AFL or new BSD, see: 45 http://dojofoundation.org/license for more information. 46 ----------------------------------------------------------------------------------------------- 47 */ 48 this.topics = {}; 49 // here we choose what will be "this" for the called events. 50 // if context is defined, it's context. Else, 'this' is this instance of PubSub 51 this.context = context ? context : this; 52 /** 53 * Allows caller to emit an event and pass arguments to event listeners. 54 * @public 55 * @function 56 * @param topic {String} Name of the channel on which to voice this event 57 * @param **arguments Any number of arguments you want to pass to the listeners of this event. 58 */ 59 this.publish = function(topic, arg1, arg2, etc) { 60 'use strict' 61 if (this.topics[topic]) { 62 var currentTopic = this.topics[topic] 63 , args = Array.prototype.slice.call(arguments, 1) 64 , toremove = [] 65 , torun = [] 66 , fn 67 , i, l 68 , pair; 69 70 for (i = 0, l = currentTopic.length; i < l; i++) { 71 pair = currentTopic[i]; // this is a [function, once_flag] array 72 fn = pair[0]; 73 if (pair[1] /* 'run once' flag set */){ 74 pair[0] = function(){}; 75 toremove.push(i); 76 } 77 /* don't call the callback right now, it might decide to add or 78 * remove subscribers which will wreak havoc on our index-based 79 * iteration */ 80 torun.push(fn); 81 } 82 for (i = 0, l = toremove.length; i < l; i++) { 83 currentTopic.splice(toremove[i], 1); 84 } 85 for (i = 0, l = torun.length; i < l; i++) { 86 torun[i].apply(this.context, args); 87 } 88 } 89 } 90 /** 91 * Allows listener code to subscribe to channel and be called when data is available 92 * @public 93 * @function 94 * @param topic {String} Name of the channel on which to voice this event 95 * @param callback {Function} Executable (function pointer) that will be ran when event is voiced on this channel. 96 * @param once {Boolean} (optional. False by default) Flag indicating if the function is to be triggered only once. 97 * @returns {Object} A token object that cen be used for unsubscribing. 98 */ 99 this.subscribe = function(topic, callback, once) { 100 'use strict' 101 if (!this.topics[topic]) { 102 this.topics[topic] = [[callback, once]]; 103 } else { 104 this.topics[topic].push([callback,once]); 105 } 106 return { 107 "topic": topic, 108 "callback": callback 109 }; 110 }; 111 /** 112 * Allows listener code to unsubscribe from a channel 113 * @public 114 * @function 115 * @param token {Object} A token object that was returned by `subscribe` method 116 */ 117 this.unsubscribe = function(token) { 118 if (this.topics[token.topic]) { 119 var currentTopic = this.topics[token.topic]; 120 121 for (var i = 0, l = currentTopic.length; i < l; i++) { 122 if (currentTopic[i] && currentTopic[i][0] === token.callback) { 123 currentTopic.splice(i, 1); 124 } 125 } 126 } 127 } 128} 129 130/// Returns front, back and "decor" colors derived from element (as jQuery obj) 131function getColors($e){ 132 var tmp 133 , undef 134 , frontcolor = $e.css('color') 135 , backcolor 136 , e = $e[0]; 137 138 var toOfDOM = false; 139 while(e && !backcolor && !toOfDOM){ 140 try{ 141 tmp = $(e).css('background-color'); 142 } catch (ex) { 143 tmp = 'transparent'; 144 } 145 if (tmp !== 'transparent' && tmp !== 'rgba(0, 0, 0, 0)'){ 146 backcolor = tmp; 147 } 148 toOfDOM = e.body; 149 e = e.parentNode; 150 } 151 152 var rgbaregex = /rgb[a]*\((\d+),\s*(\d+),\s*(\d+)/ // modern browsers 153 , hexregex = /#([AaBbCcDdEeFf\d]{2})([AaBbCcDdEeFf\d]{2})([AaBbCcDdEeFf\d]{2})/ // IE 8 and less. 154 , frontcolorcomponents; 155 156 // Decomposing Front color into R, G, B ints 157 tmp = undef; 158 tmp = frontcolor.match(rgbaregex); 159 if (tmp){ 160 frontcolorcomponents = {'r':parseInt(tmp[1],10),'g':parseInt(tmp[2],10),'b':parseInt(tmp[3],10)}; 161 } else { 162 tmp = frontcolor.match(hexregex); 163 if (tmp) { 164 frontcolorcomponents = {'r':parseInt(tmp[1],16),'g':parseInt(tmp[2],16),'b':parseInt(tmp[3],16)}; 165 } 166 }
167// if(!frontcolorcomponents){ 168// frontcolorcomponents = {'r':255,'g':255,'b':255} 169// } 170 171 var backcolorcomponents 172 // Decomposing back color into R, G, B ints 173 if(!backcolor){ 174 // HIghly unlikely since this means that no background styling was applied to any element from here to top of dom. 175 // we'll pick up back color from front color 176 if(frontcolorcomponents){ 177 if (Math.max.apply(null, [frontcolorcomponents.r, frontcolorcomponents.g, frontcolorcomponents.b]) > 127){ 178 backcolorcomponents = {'r':0,'g':0,'b':0}; 179 } else { 180 backcolorcomponents = {'r':255,'g':255,'b':255}; 181 } 182 } else { 183 // arg!!! front color is in format we don't understand (hsl, named colors) 184 // Let's just go with white background. 185 backcolorcomponents = {'r':255,'g':255,'b':255}; 186 } 187 } else { 188 tmp = undef; 189 tmp = backcolor.match(rgbaregex); 190 if (tmp){ 191 backcolorcomponents = {'r':parseInt(tmp[1],10),'g':parseInt(tmp[2],10),'b':parseInt(tmp[3],10)}; 192 } else { 193 tmp = backcolor.match(hexregex); 194 if (tmp) { 195 backcolorcomponents = {'r':parseInt(tmp[1],16),'g':parseInt(tmp[2],16),'b':parseInt(tmp[3],16)}; 196 } 197 } 198// if(!backcolorcomponents){ 199// backcolorcomponents = {'r':0,'g':0,'b':0} 200// } 201 } 202 203 // Deriving Decor color 204 // THis is LAZY!!!! Better way would be to use HSL and adjust luminocity. However, that could be an overkill. 205 206 var toRGBfn = function(o){return 'rgb(' + [o.r, o.g, o.b].join(', ') + ')'} 207 , decorcolorcomponents 208 , frontcolorbrightness 209 , adjusted; 210 211 if (frontcolorcomponents && backcolorcomponents){ 212 var backcolorbrightness = Math.max.apply(null, [frontcolorcomponents.r, frontcolorcomponents.g, frontcolorcomponents.b]); 213 214 frontcolorbrightness = Math.max.apply(null, [backcolorcomponents.r, backcolorcomponents.g, backcolorcomponents.b]); 215 adjusted = Math.round(frontcolorbrightness + (-1 * (frontcolorbrightness - backcolorbrightness) * 0.75)); // "dimming" the difference between pen and back.
216 decorcolorcomponents = {'r':adjusted,'g':adjusted,'b':adjusted}; // always shade of gray 217 } else if (frontcolorcomponents) { 218 frontcolorbrightness = Math.max.apply(null, [frontcolorcomponents.r, frontcolorcomponents.g, frontcolorcomponents.b]); 219 var polarity = +1; 220 if (frontcolorbrightness > 127){ 221 polarity = -1; 222 } 223 // shifting by 25% (64 points on RGB scale) 224 adjusted = Math.round(frontcolorbrightness + (polarity * 96)); // "dimming" the pen's color by 75% to get decor color. 225 decorcolorcomponents = {'r':adjusted,'g':adjusted,'b':adjusted}; // always shade of gray 226 } else { 227 decorcolorcomponents = {'r':191,'g':191,'b':191}; // always shade of gray 228 } 229 230 return { 231 'color': frontcolor 232 , 'background-color': backcolorcomponents? toRGBfn(backcolorcomponents) : backcolor 233 , 'decor-color': toRGBfn(decorcolorcomponents) 234 }; 235} 236 237function Vector(x,y){ 238 this.x = x; 239 this.y = y; 240 this.reverse = function(){ 241 return new this.constructor( 242 this.x * -1 243 , this.y * -1 244 ); 245 }; 246 this._length = null; 247 this.getLength = function(){ 248 if (!this._length){ 249 this._length = Math.sqrt( Math.pow(this.x, 2) + Math.pow(this.y, 2) ); 250 } 251 return this._length; 252 }; 253 254 var polarity = function (e){ 255 return Math.round(e / Math.abs(e)); 256 }; 257 this.resizeTo = function(length){ 258 // proportionally changes x,y such that the hypotenuse (vector length) is = new length 259 if (this.x === 0 && this.y === 0){ 260 this._length = 0; 261 } else if (this.x === 0){ 262 this._length = length; 263 this.y = length * polarity(this.y); 264 } else if(this.y === 0){ 265 this._length = length; 266 this.x = length * polarity(this.x); 267 } else { 268 var proportion = Math.abs(this.y / this.x) 269 , x = Math.sqrt(Math.pow(length, 2) / (1 + Math.pow(proportion, 2))) 270 , y = proportion * x; 271 this._length = length; 272 this.x = x * polarity(this.x); 273 this.y = y * polarity(this.y); 274 } 275 return this; 276 }; 277 278 /** 279 * Calculates the angle between 'this' vector and another. 280 * @public 281 * @function 282 * @returns {Number} The angle between the two vectors as measured in PI. 283 */ 284 this.angleTo = function(vectorB) { 285 var divisor = this.getLength() * vectorB.getLength(); 286 if (divisor === 0) { 287 return 0; 288 } else { 289 // JavaScript floating point math is screwed up. 290 // because of it, the core of the formula can, on occasion, have values 291 // over 1.0 and below -1.0. 292 return Math.acos( 293 Math.min( 294 Math.max( 295 ( this.x * vectorB.x + this.y * vectorB.y ) / divisor 296 , -1.0 297 ) 298 , 1.0 299 ) 300 ) / Math.PI; 301 } 302 }; 303} 304 305function Point(x,y){ 306 this.x = x; 307 this.y = y; 308 309 this.getVectorToCoordinates = function (x, y) { 310 return new Vector(x - this.x, y - this.y); 311 }; 312 this.getVectorFromCoordinates = function (x, y) { 313 return this.getVectorToCoordinates(x, y).reverse(); 314 }; 315 this.getVectorToPoint = function (point) { 316 return new Vector(point.x - this.x, point.y - this.y); 317 }; 318 this.getVectorFromPoint = function (point) { 319 return this.getVectorToPoint(point).reverse(); 320 }; 321} 322 323/* 324 * About data structure: 325 * We don't store / deal with "pictures" this signature capture code captures "vectors" 326 * 327 * We don't store bitmaps. We store "strokes" as arrays of arrays. (Actually, arrays of objects containing arrays of coordinates. 328 * 329 * Stroke = mousedown + mousemoved * n (+ mouseup but we don't record that as that was the "end / lack of movement" indicator) 330 * 331 * Vectors = not classical vectors where numbers indicated shift relative last position. Our vectors are actually coordinates against top left of canvas. 332 * we could calc the classical vectors, but keeping the actual coordinates allows us (through Math.max / min) 333 * to calc the size of resulting drawing very quickly. If we want classical vectors later, we can always get them in backend code. 334 * 335 * So, the data structure: 336 * 337 * var data = [ 338 * { // stroke starts 339 * x : [101, 98, 57, 43] // x points 340 * , y : [1, 23, 65, 87] // y points 341 * } // stroke ends 342 * , { // stroke starts 343 * x : [55, 56, 57, 58] // x points 344 * , y : [101, 97, 54, 4] // y points 345 * } // stroke ends 346 * , { // stroke consisting of just a dot 347 * x : [53] // x points
348 * , y : [151] // y points 349 * } // stroke ends 350 * ] 351 * 352 * we don't care or store stroke width (it's canvas-size-relative), color, shadow values. These can be added / changed on whim post-capture. 353 * 354 */ 355function DataEngine(storageObject, context, startStrokeFn, addToStrokeFn, endStrokeFn){ 356 this.data = storageObject; // we expect this to be an instance of Array 357 this.context = context; 358 359 if (storageObject.length){ 360 // we have data to render 361 var numofstrokes = storageObject.length 362 , stroke 363 , numofpoints; 364 365 for (var i = 0; i < numofstrokes; i++){ 366 stroke = storageObject[i]; 367 numofpoints = stroke.x.length; 368 startStrokeFn.call(context, stroke); 369 for(var j = 1; j < numofpoints; j++){ 370 addToStrokeFn.call(context, stroke, j); 371 } 372 endStrokeFn.call(context, stroke); 373 } 374 } 375 376 this.changed = function(){}; 377 378 this.startStrokeFn = startStrokeFn; 379 this.addToStrokeFn = addToStrokeFn; 380 this.endStrokeFn = endStrokeFn; 381 382 this.inStroke = false; 383 384 this._lastPoint = null; 385 this._stroke = null; 386 this.startStroke = function(point){ 387 if(point && typeof(point.x) == "number" && typeof(point.y) == "number"){ 388 this._stroke = {'x':[point.x], 'y':[point.y]}; 389 this.data.push(this._stroke); 390 this._lastPoint = point; 391 this.inStroke = true; 392 // 'this' does not work same inside setTimeout( 393 var stroke = this._stroke 394 , fn = this.startStrokeFn 395 , context = this.context; 396 setTimeout( 397 // some IE's don't support passing args per setTimeout API. Have to create closure every time instead. 398 function() {fn.call(context, stroke)} 399 , 3 400 ); 401 return point; 402 } else { 403 return null; 404 } 405 }; 406 // that "5" at the very end of this if is important to explain. 407 // we do NOT render links between two captured points (in the middle of the stroke) if the distance is shorter than that number. 408 // not only do we NOT render it, we also do NOT capture (add) these intermediate points to storage. 409 // when clustering of these is too tight, it produces noise on the line, which, because of smoothing, makes lines too curvy. 410 // maybe, later, we can expose this as a configurable setting of some sort. 411 this.addToStroke = function(point){ 412 if (this.inStroke && 413 typeof(point.x) === "number" && 414 typeof(point.y) === "number" && 415 // calculates absolute shift in diagonal pixels away from original point 416 (Math.abs(point.x - this._lastPoint.x) + Math.abs(point.y - this._lastPoint.y)) > 4 417 ){ 418 var positionInStroke = this._stroke.x.length; 419 this._stroke.x.push(point.x); 420 this._stroke.y.push(point.y); 421 this._lastPoint = point; 422 423 var stroke = this._stroke 424 , fn = this.addToStrokeFn 425 , context = this.context; 426 setTimeout( 427 // some IE's don't support passing args per setTimeout API. Have to create closure every time instead. 428 function() {fn.call(context, stroke, positionInStroke)} 429 , 3 430 ); 431 return point; 432 } else { 433 return null; 434 } 435 }; 436 this.endStroke = function(){ 437 var c = this.inStroke; 438 this.inStroke = false; 439 this._lastPoint = null; 440 if (c){ 441 var stroke = this._stroke 442 , fn = this.endStrokeFn // 'this' does not work same inside setTimeout( 443 , context = this.context 444 , changedfn = this.changed; 445 setTimeout( 446 // some IE's don't support passing args per setTimeout API. Have to create closure every time instead. 447 function(){ 448 fn.call(context, stroke); 449 changedfn.call(context); 450 } 451 , 3 452 ); 453 return true; 454 } else { 455 return null; 456 } 457 }; 458} 459 460var basicDot = function(ctx, x, y, size){ 461 var fillStyle = ctx.fillStyle; 462 ctx.fillStyle = ctx.strokeStyle; 463 ctx.fillRect(x + size / -2 , y + size / -2, size, size); 464 ctx.fillStyle = fillStyle; 465}
466, basicLine = function(ctx, startx, starty, endx, endy){ 467 ctx.beginPath(); 468 ctx.moveTo(startx, starty); 469 ctx.lineTo(endx, endy); 470 ctx.closePath(); 471 ctx.stroke(); 472} 473, basicCurve = function(ctx, startx, starty, endx, endy, cp1x, cp1y, cp2x, cp2y){ 474 ctx.beginPath(); 475 ctx.moveTo(startx, starty); 476 ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, endx, endy); 477 ctx.closePath(); 478 ctx.stroke(); 479} 480, strokeStartCallback = function(stroke) { 481 // this = jSignatureClass instance 482 basicDot(this.canvasContext, stroke.x[0], stroke.y[0], this.settings.lineWidth); 483} 484, strokeAddCallback = function(stroke, positionInStroke){ 485 // this = jSignatureClass instance 486 487 // Because we are funky this way, here we draw TWO curves. 488 // 1. POSSIBLY "this line" - spanning from point right before us, to this latest point. 489 // 2. POSSIBLY "prior curve" - spanning from "latest point" to the one before it. 490 491 // Why you ask? 492 // long lines (ones with many pixels between them) do not look good when they are part of a large curvy stroke. 493 // You know, the jaggedy crocodile spine instead of a pretty, smooth curve. Yuck! 494 // We want to approximate pretty curves in-place of those ugly lines. 495 // To approximate a very nice curve we need to know the direction of line before and after. 496 // Hence, on long lines we actually wait for another point beyond it to come back from 497 // mousemoved before we draw this curve. 498 499 // So for "prior curve" to be calc'ed we need 4 points 500 // A, B, C, D (we are on D now, A is 3 points in the past.) 501 // and 3 lines: 502 // pre-line (from points A to B), 503 // this line (from points B to C), (we call it "this" because if it was not yet, it's the only one we can draw for sure.) 504 // post-line (from points C to D) (even through D point is 'current' we don't know how we can draw it yet) 505 // 506 // Well, actually, we don't need to *know* the point A, just the vector A->B 507 var Cpoint = new Point(stroke.x[positionInStroke-1], stroke.y[positionInStroke-1]) 508 , Dpoint = new Point(stroke.x[positionInStroke], stroke.y[positionInStroke]) 509 , CDvector = Cpoint.getVectorToPoint(Dpoint); 510 511 // Again, we have a chance here to draw TWO things: 512 // BC Curve (only if it's long, because if it was short, it was drawn by previous callback) and 513 // CD Line (only if it's short) 514 515 // So, let's start with BC curve. 516 // if there is only 2 points in stroke array, we don't have "history" long enough to have point B, let alone point A. 517 // Falling through to drawing line CD is proper, as that's the only line we have points for. 518 if(positionInStroke > 1) { 519 // we are here when there are at least 3 points in stroke array. 520 var Bpoint = new Point(stroke.x[positionInStroke-2], stroke.y[positionInStroke-2]) 521 , BCvector = Bpoint.getVectorToPoint(Cpoint) 522 , ABvector; 523 if(BCvector.getLength() > this.lineCurveThreshold){ 524 // Yey! Pretty curves, here we come! 525 if(positionInStroke > 2) { 526 // we are here when at least 4 points in stroke array. 527 ABvector = (new Point(stroke.x[positionInStroke-3], stroke.y[positionInStroke-3])).getVectorToPoint(Bpoint); 528 } else { 529 ABvector = new Vector(0,0); 530 } 531 532 var minlenfraction = 0.05 533 , maxlen = BCvector.getLength() * 0.35 534 , ABCangle = BCvector.angleTo(ABvector.reverse()) 535 , BCDangle = CDvector.angleTo(BCvector.reverse()) 536 , BCP1vector = new Vector(ABvector.x + BCvector.x, ABvector.y + BCvector.y).resizeTo( 537 Math.max(minlenfraction, ABCangle) * maxlen 538 ) 539 , CCP2vector = (new Vector(BCvector.x + CDvector.x, BCvector.y + CDvector.y)).reverse().resizeTo( 540 Math.max(minlenfraction, BCDangle) * maxlen 541 ); 542 543 basicCurve( 544 this.canvasContext 545 , Bpoint.x 546 , Bpoint.y 547 , Cpoint.x 548 , Cpoint.y 549 , Bpoint.x + BCP1vector.x 550 , Bpoint.y + BCP1vector.y 551 , Cpoint.x + CCP2vector.x 552 , Cpoint.y + CCP2vector.y 553 ); 554 } 555 } 556 if(CDvector.getLength() <= this.lineCurveThreshold){ 557 basicLine( 558 this.canvasContext 559 , Cpoint.x 560 , Cpoint.y
561 , Dpoint.x 562 , Dpoint.y 563 ); 564 } 565} 566, strokeEndCallback = function(stroke){ 567 // this = jSignatureClass instance 568 569 // Here we tidy up things left unfinished in last strokeAddCallback run. 570 571 // What's POTENTIALLY left unfinished there is the curve between the last points 572 // in the stroke, if the len of that line is more than lineCurveThreshold 573 // If the last line was shorter than lineCurveThreshold, it was drawn there, and there 574 // is nothing for us here to do. 575 // We can also be called when there is only one point in the stroke (meaning, the 576 // stroke was just a dot), in which case, again, there is nothing for us to do. 577 578 // So for "this curve" to be calc'ed we need 3 points 579 // A, B, C 580 // and 2 lines: 581 // pre-line (from points A to B), 582 // this line (from points B to C) 583 // Well, actually, we don't need to *know* the point A, just the vector A->B 584 // so, we really need points B, C and AB vector. 585 var positionInStroke = stroke.x.length - 1; 586 587 if (positionInStroke > 0){ 588 // there are at least 2 points in the stroke.we are in business. 589 var Cpoint = new Point(stroke.x[positionInStroke], stroke.y[positionInStroke]) 590 , Bpoint = new Point(stroke.x[positionInStroke-1], stroke.y[positionInStroke-1]) 591 , BCvector = Bpoint.getVectorToPoint(Cpoint) 592 , ABvector; 593 if (BCvector.getLength() > this.lineCurveThreshold){ 594 // yep. This one was left undrawn in prior callback. Have to draw it now. 595 if (positionInStroke > 1){ 596 // we have at least 3 elems in stroke 597 ABvector = (new Point(stroke.x[positionInStroke-2], stroke.y[positionInStroke-2])).getVectorToPoint(Bpoint); 598 var BCP1vector = new Vector(ABvector.x + BCvector.x, ABvector.y + BCvector.y).resizeTo(BCvector.getLength() / 2); 599 basicCurve( 600 this.canvasContext 601 , Bpoint.x 602 , Bpoint.y 603 , Cpoint.x 604 , Cpoint.y 605 , Bpoint.x + BCP1vector.x 606 , Bpoint.y + BCP1vector.y 607 , Cpoint.x 608 , Cpoint.y 609 ); 610 } else { 611 // Since there is no AB leg, there is no curve to draw. This line is still "long" but no curve. 612 basicLine( 613 this.canvasContext 614 , Bpoint.x 615 , Bpoint.y 616 , Cpoint.x 617 , Cpoint.y 618 ); 619 } 620 } 621 } 622} 623 624 625/* 626var getDataStats = function(){ 627 var strokecnt = strokes.length 628 , stroke 629 , pointid 630 , pointcnt 631 , x, y 632 , maxX = Number.NEGATIVE_INFINITY 633 , maxY = Number.NEGATIVE_INFINITY 634 , minX = Number.POSITIVE_INFINITY 635 , minY = Number.POSITIVE_INFINITY 636 for(strokeid = 0; strokeid < strokecnt; strokeid++){ 637 stroke = strokes[strokeid] 638 pointcnt = stroke.length 639 for(pointid = 0; pointid < pointcnt; pointid++){ 640 x = stroke.x[pointid] 641 y = stroke.y[pointid] 642 if (x > maxX){ 643 maxX = x 644 } else if (x < minX) { 645 minX = x 646 } 647 if (y > maxY){ 648 maxY = y 649 } else if (y < minY) { 650 minY = y 651 } 652 } 653 } 654 return {'maxX': maxX, 'minX': minX, 'maxY': maxY, 'minY': minY} 655} 656*/ 657 658function conditionallyLinkCanvasResizeToWindowResize(jSignatureInstance, settingsWidth, apinamespace, globalEvents){ 659 'use strict' 660 if ( settingsWidth === 'ratio' || settingsWidth.split('')[settingsWidth.length - 1] === '%' ) { 661 662 this.eventTokens[apinamespace + '.parentresized'] = globalEvents.subscribe( 663 apinamespace + '.parentresized' 664 , (function(eventTokens, $parent, originalParentWidth, sizeRatio){ 665 'use strict' 666 667 return function(){ 668 'use strict' 669 670 var w = $parent.width(); 671 if (w !== originalParentWidth) { 672 673 // UNsubscribing this particular instance of signature pad only. 674 // there is a separate `eventTokens` per each instance of signature pad 675 for (var key in eventTokens){ 676 if (eventTokens.hasOwnProperty(key)) { 677 globalEvents.unsubscribe(eventTokens[key]); 678 delete eventTokens[key]; 679 } 680 } 681 682 var settings = jSignatureInstance.settings; 683 jSignatureInstance.$parent.children().remove(); 684 for (var key in jSignatureInstance){ 685 if (jSignatureInstance.hasOwnProperty(key)) { 686 delete jSignatureInstance[key]; 687 } 688 } 689 690 // scale data to new signature pad size 691 settings.data = (function(data, scale){ 692 var newData = []; 693 var o, i, l, j, m, stroke; 694 for ( i = 0, l = data.length; i < l; i++) { 695 stroke = data[i]; 696 697 o = {'x':[],'y':[]}; 698 699 for ( j = 0, m = stroke.x.length; j < m; j++) { 700 o.x.push(stroke.x[j] * scale); 701 o.y.push(stroke.y[j] * scale); 702 } 703 704 newData.push(o); 705 } 706 return newData; 707 })( 708 settings.data 709 , w * 1.0 / originalParentWidth 710 ) 711 712 $parent[apinamespace](settings); 713 } 714 } 715 })( 716 this.eventTokens 717 , this.$parent 718 , this.$parent.width() 719 , this.canvas.width * 1.0 / this.canvas.height 720 ) 721 ) 722 } 723}; 724 725 726function jSignatureClass(parent, options, instanceExtensions) { 727 728 var $parent = this.$parent = $(parent) 729 , eventTokens = this.eventTokens = {} 730 , events = this.events = new PubSubClass(this) 731 , globalEvents = $.fn[apinamespace]('globalEvents') 732 , settings = { 733 'width' : 'ratio' 734 ,'height' : 'ratio' 735 ,'sizeRatio': 4 // only used when height = 'ratio' 736 ,'color' : '#000' 737 ,'background-color': '#fff' 738 ,'decor-color': '#eee' 739 ,'show-stroke': true 740 ,'lineWidth' : 0 741 ,'minFatFingerCompensation' : -10 742 ,'showUndoButton': false 743 ,'readOnly': false 744 ,'data': [] 745 }; 746 747 $.extend(settings, getColors($parent)); 748 if (options) { 749 $.extend(settings, options); 750 } 751 this.settings = settings; 752 753 for (var extensionName in instanceExtensions){ 754 if (instanceExtensions.hasOwnProperty(extensionName)) { 755 instanceExtensions[extensionName].call(this, extensionName); 756 } 757 } 758 759 this.events.publish(apinamespace+'.initializing'); 760 761 // these, when enabled, will hover above the sig area. Hence we append them to DOM before canvas. 762 this.$controlbarUpper = (function(){ 763 var controlbarstyle = 'padding:0 !important; margin:0 !important;'+ 764 'width: 100% !important; height: 0 !important; -ms-touch-action: none; touch-action: none;'+ 765 'margin-top:-1em !important; margin-bottom:1em !important;'; 766 return $('<div style="'+controlbarstyle+'"></div>').appendTo($parent); 767 })(); 768 769 this.isCanvasEmulator = false; // will be flipped by initializer when needed. 770 var canvas = this.canvas = this.initializeCanvas(settings) 771 , $canvas = $(canvas); 772 773 this.$controlbarLower = (function(){ 774 var controlbarstyle = 'padding:0 !important; margin:0 !important;'+ 775 'width: 100% !important; height: 0 !important; -ms-touch-action: none; touch-action: none;'+ 776 'margin-top:-1.5em !important; margin-bottom:1.5em !important; position: relative;';
777 return $('<div style="'+controlbarstyle+'"></div>').appendTo($parent); 778 })(); 779 780 this.canvasContext = canvas.getContext("2d"); 781 782 // Most of our exposed API will be looking for this: 783 $canvas.data(apinamespace + '.this', this); 784 785 settings.lineWidth = (function(defaultLineWidth, canvasWidth){ 786 if (!defaultLineWidth){ 787 return Math.max( 788 Math.round(canvasWidth / 400) /*+1 pixel for every extra 300px of width.*/ 789 , 2 /* minimum line width */ 790 ); 791 } else { 792 return defaultLineWidth; 793 } 794 })(settings.lineWidth, canvas.width); 795 796 this.lineCurveThreshold = settings.lineWidth * 3; 797 798 // Add custom class if defined 799 if(settings.cssclass && $.trim(settings.cssclass) != "") { 800 $canvas.addClass(settings.cssclass); 801 } 802 803 // used for shifting the drawing point up on touch devices, so one can see the drawing above the finger. 804 this.fatFingerCompensation = 0; 805 806 var movementHandlers = (function(jSignatureInstance) { 807 808 //================================ 809 // mouse down, move, up handlers: 810 811 // shifts - adjustment values in viewport pixels drived from position of canvas on the page 812 var shiftX 813 , shiftY 814 , setStartValues = function(){ 815 var tos = $(jSignatureInstance.canvas).offset() 816 shiftX = tos.left * -1 817 shiftY = tos.top * -1 818 } 819 , getPointFromEvent = function(e) { 820 var firstEvent = (e.changedTouches && e.changedTouches.length > 0 ? e.changedTouches[0] : e); 821 // All devices i tried report correct coordinates in pageX,Y 822 // Android Chrome 2.3.x, 3.1, 3.2., Opera Mobile, safari iOS 4.x,
823 // Windows: Chrome, FF, IE9, Safari 824 // None of that scroll shift calc vs screenXY other sigs do is needed. 825 // ... oh, yeah, the "fatFinger.." is for tablets so that people see what they draw. 826 return new Point( 827 Math.round(firstEvent.pageX + shiftX) 828 , Math.round(firstEvent.pageY + shiftY) + jSignatureInstance.fatFingerCompensation 829 ); 830 } 831 , timer = new KickTimerClass( 832 750 833 , function() { jSignatureInstance.dataEngine.endStroke(); } 834 ); 835 836 this.drawEndHandler = function(e) { 837 if (!jSignatureInstance.settings.readOnly) { 838 try { e.preventDefault(); } catch (ex) {} 839 timer.clear(); 840 jSignatureInstance.dataEngine.endStroke(); 841 } 842 }; 843 this.drawStartHandler = function(e) { 844 if (!jSignatureInstance.settings.readOnly) { 845 e.preventDefault(); 846 // for performance we cache the offsets 847 // we recalc these only at the beginning the stroke 848 setStartValues(); 849 jSignatureInstance.dataEngine.startStroke( getPointFromEvent(e) ); 850 timer.kick(); 851 } 852 }; 853 this.drawMoveHandler = function(e) { 854 if (!jSignatureInstance.settings.readOnly) { 855 e.preventDefault(); 856 if (!jSignatureInstance.dataEngine.inStroke){ 857 return; 858 } 859 jSignatureInstance.dataEngine.addToStroke( getPointFromEvent(e) ); 860 timer.kick(); 861 } 862 }; 863 864 return this; 865 866 }).call( {}, this ) 867 868 // 869 //================================ 870 871 ;(function(drawEndHandler, drawStartHandler, drawMoveHandler) { 872 var canvas = this.canvas 873 , $canvas = $(canvas) 874 , undef; 875 if (this.isCanvasEmulator){ 876 $canvas.bind('mousemove.'+apinamespace, drawMoveHandler); 877 $canvas.bind('mouseup.'+apinamespace, drawEndHandler); 878 $canvas.bind('mousedown.'+apinamespace, drawStartHandler); 879 } else { 880 canvas.addEventListener('touchstart', function(e) { 881 canvas.onmousedown = canvas.onmouseup = canvas.onmousemove = undef; 882 883 this.fatFingerCompensation = ( 884 settings.minFatFingerCompensation && 885 settings.lineWidth * -3 > settings.minFatFingerCompensation 886 ) ? settings.lineWidth * -3 : settings.minFatFingerCompensation; 887 888 drawStartHandler(e); 889 890 canvas.addEventListener('touchend', drawEndHandler); 891 canvas.addEventListener('touchstart', drawStartHandler); 892 canvas.addEventListener('touchmove', drawMoveHandler); 893 }); 894 canvas.addEventListener('mousedown', function(e) { 895 canvas.ontouchstart = canvas.ontouchend = canvas.ontouchmove = undef; 896 897 drawStartHandler(e); 898 899 canvas.addEventListener('mousedown', drawStartHandler); 900 canvas.addEventListener('mouseup', drawEndHandler); 901 canvas.addEventListener('mousemove', drawMoveHandler); 902 }); 903 if (window.navigator.msPointerEnabled) { 904 canvas.onmspointerdown = drawStartHandler; 905 canvas.onmspointerup = drawEndHandler; 906 canvas.onmspointermove = drawMoveHandler; 907 } 908 } 909 }).call( 910 this 911 , movementHandlers.drawEndHandler 912 , movementHandlers.drawStartHandler 913 , movementHandlers.drawMoveHandler 914 ) 915 916 //========================================= 917 // various event handlers 918 919 // on mouseout + mouseup canvas did not know that mouseUP fired. Continued to draw despite mouse UP. 920 // it is bettr than 921 // $canvas.bind('mouseout', drawEndHandler) 922 // because we don't want to break the stroke where user accidentally gets ouside and wants to get back in quickly. 923 eventTokens[apinamespace + '.windowmouseup'] = globalEvents.subscribe( 924 apinamespace + '.windowmouseup' 925 , movementHandlers.drawEndHandler 926 ); 927 928 this.events.publish(apinamespace+'.attachingEventHandlers'); 929 930 // If we have proportional width, we sign up to events broadcasting "window resized" and checking if 931 // parent's width changed. If so, we (1) extract settings + data from current signature pad, 932 // (2) remove signature pad from parent, and (3) reinit new signature pad at new size with same settings, (rescaled) data. 933 conditionallyLinkCanvasResizeToWindowResize.call( 934 this 935 , this 936 , settings.width.toString(10) 937 , apinamespace, globalEvents 938 ); 939 940 // end of event handlers. 941 // =============================== 942 943 this.resetCanvas(settings.data); 944 945 // resetCanvas renders the data on the screen and fires ONE "change" event 946 // if there is data. If you have controls that rely on "change" firing 947 // attach them to something that runs before this.resetCanvas, like 948 // apinamespace+'.attachingEventHandlers' that fires a bit higher. 949 this.events.publish(apinamespace+'.initialized'); 950 951 return this; 952} // end of initBase 953 954//========================================================================= 955// jSignatureClass's methods and supporting fn's 956 957jSignatureClass.prototype.resetCanvas = function(data, dontClear){ 958 var canvas = this.canvas 959 , settings = this.settings 960 , ctx = this.canvasContext 961 , isCanvasEmulator = this.isCanvasEmulator 962 , cw = canvas.width 963 , ch = canvas.height; 964 965 // preparing colors, drawing area 966 if (!dontClear){ 967 ctx.clearRect(0, 0, cw + 30, ch + 30); 968 } 969 970 ctx.shadowColor = ctx.fillStyle = settings['background-color'] 971 if (isCanvasEmulator){ 972 // FLashCanvas fills with Black by default, covering up the parent div's background 973 // hence we refill 974 ctx.fillRect(0,0,cw + 30, ch + 30); 975 } 976 977 ctx.lineWidth = Math.ceil(parseInt(settings.lineWidth, 10)); 978 ctx.lineCap = ctx.lineJoin = "round"; 979 980 // signature line 981 if (null != settings['decor-color'] && settings['show-stroke']) { 982 ctx.strokeStyle = settings['decor-color']; 983 ctx.shadowOffsetX = 0; 984 ctx.shadowOffsetY = 0; 985 var lineoffset = Math.round( ch / 5 ); 986 basicLine(ctx, lineoffset * 1.5, ch - lineoffset, cw - (lineoffset * 1.5), ch - lineoffset); 987 } 988 ctx.strokeStyle = settings.color; 989 990 if (!isCanvasEmulator){ 991 ctx.shadowColor = ctx.strokeStyle; 992 ctx.shadowOffsetX = ctx.lineWidth * 0.5; 993 ctx.shadowOffsetY = ctx.lineWidth * -0.6; 994 ctx.shadowBlur = 0; 995 } 996 997 // setting up new dataEngine 998 999 if (!data) { data = []; } 1000 1001 var dataEngine = this.dataEngine = new DataEngine( 1002 data 1003 , this 1004 , strokeStartCallback 1005 , strokeAddCallback 1006 , strokeEndCallback 1007 ); 1008 1009 settings.data = data; // onwindowresize handler uses it, i think. 1010 $(canvas).data(apinamespace+'.data', data) 1011 .data(apinamespace+'.settings', settings); 1012 1013 // we fire "change" event on every change in data. 1014 // setting this up: 1015 dataEngine.changed = (function(target, events, apinamespace) { 1016 'use strict' 1017 return function() { 1018 events.publish(apinamespace+'.change'); 1019 target.trigger('change'); 1020 } 1021 })(this.$parent, this.events, apinamespace); 1022 // let's trigger change on all data reloads 1023 dataEngine.changed(); 1024 1025 // import filters will be passing this back as indication of "we rendered" 1026 return true; 1027}; 1028 1029function initializeCanvasEmulator(canvas){ 1030 if (canvas.getContext){ 1031 return false;
1032 } else { 1033 // for cases when jSignature, FlashCanvas is inserted 1034 // from one window into another (child iframe) 1035 // 'window' and 'FlashCanvas' may be stuck behind 1036 // in that other parent window. 1037 // we need to find it 1038 var window = canvas.ownerDocument.parentWindow; 1039 var FC = window.FlashCanvas ? 1040 canvas.ownerDocument.parentWindow.FlashCanvas : 1041 ( 1042 typeof FlashCanvas === "undefined" ? 1043 undefined : 1044 FlashCanvas 1045 ); 1046 1047 if (FC) { 1048 canvas = FC.initElement(canvas); 1049 1050 var zoom = 1; 1051 // FlashCanvas uses flash which has this annoying habit of NOT scaling with page zoom. 1052 // It matches pixel-to-pixel to screen instead. 1053 // Since we are targeting ONLY IE 7, 8 with FlashCanvas, we will test the zoom only the IE8, IE7 way 1054 if (window && window.screen && window.screen.deviceXDPI && window.screen.logicalXDPI){ 1055 zoom = window.screen.deviceXDPI * 1.0 / window.screen.logicalXDPI; 1056 } 1057 if (zoom !== 1){ 1058 try { 1059 // We effectively abuse the brokenness of FlashCanvas and force the flash rendering surface to 1060 // occupy larger pixel dimensions than the wrapping, scaled up DIV and Canvas elems. 1061 $(canvas).children('object').get(0).resize(Math.ceil(canvas.width * zoom), Math.ceil(canvas.height * zoom)); 1062 // And by applying "scale" transformation we can talk "browser pixels" to FlashCanvas 1063 // and have it translate the "browser pixels" to "screen pixels" 1064 canvas.getContext('2d').scale(zoom, zoom); 1065 // Note to self: don't reuse Canvas element. Repeated "scale" are cumulative. 1066 } catch (ex) {} 1067 } 1068 return true; 1069 } else { 1070 throw new Error("Canvas element does not support 2d context. jSignature cannot proceed."); 1071 } 1072 } 1073 1074} 1075 1076jSignatureClass.prototype.initializeCanvas = function(settings) { 1077 // =========== 1078 // Init + Sizing code 1079 1080 var canvas = document.createElement('canvas') 1081 , $canvas = $(canvas); 1082 1083 // We cannot work with circular dependency 1084 if (settings.width === settings.height && settings.height === 'ratio') { 1085 settings.width = '100%'; 1086 } 1087 1088 $canvas.css({ 1089 'margin': 0, 1090 'padding': 0, 1091 'border': 'none', 1092 'height': settings.height === 'ratio' || !settings.height ? 1 : settings.height.toString(10), 1093 'width': settings.width === 'ratio' || !settings.width ? 1 : settings.width.toString(10), 1094 '-ms-touch-action': 'none', 1095 'touch-action': 'none', 1096 'background-color': settings['background-color'], 1097 }); 1098 1099 $canvas.appendTo(this.$parent); 1100 1101 // we could not do this until canvas is rendered (appended to DOM) 1102 if (settings.height === 'ratio') { 1103 $canvas.css( 1104 'height' 1105 , Math.round( $canvas.width() / settings.sizeRatio ) 1106 ); 1107 } else if (settings.width === 'ratio') { 1108 $canvas.css( 1109 'width' 1110 , Math.round( $canvas.height() * settings.sizeRatio ) 1111 ); 1112 } 1113 1114 $canvas.addClass(apinamespace); 1115 1116 // canvas's drawing area resolution is independent from canvas's size. 1117 // pixels are just scaled up or down when internal resolution does not 1118 // match external size. So... 1119 1120 canvas.width = $canvas.width(); 1121 canvas.height = $canvas.height(); 1122 1123 // Special case Sizing code 1124 1125 this.isCanvasEmulator = initializeCanvasEmulator(canvas); 1126 1127 // End of Sizing Code 1128 // =========== 1129 1130 // normally select preventer would be short, but 1131 // Canvas emulator on IE does NOT provide value for Event. Hence this convoluted line. 1132 canvas.onselectstart = function(e){if(e && e.preventDefault){e.preventDefault()}; if(e && e.stopPropagation){e.stopPropagation()}; return false;}; 1133 1134 return canvas; 1135} 1136 1137 1138var GlobalJSignatureObjectInitializer = function(window){ 1139 1140 var globalEvents = new PubSubClass(); 1141 1142 // common "window resized" event listener. 1143 // jSignature instances will subscribe to this chanel. 1144 // to resize themselves when needed. 1145 ;(function(globalEvents, apinamespace, $, window){ 1146 'use strict' 1147 1148 var resizetimer 1149 , runner = function(){ 1150 globalEvents.publish( 1151 apinamespace + '.parentresized' 1152 ) 1153 }; 1154 1155 // jSignature knows how to resize its content when its parent is resized 1156 // window resize is the only way we can catch resize events though... 1157 $(window).bind('resize.'+apinamespace, function(){ 1158 if (resizetimer) { 1159 clearTimeout(resizetimer); 1160 } 1161 resizetimer = setTimeout( 1162 runner 1163 , 500 1164 ); 1165 }) 1166 // when mouse exists canvas element and "up"s outside, we cannot catch it with 1167 // callbacks attached to canvas. This catches it outside. 1168 .bind('mouseup.'+apinamespace, function(e){ 1169 globalEvents.publish( 1170 apinamespace + '.windowmouseup' 1171 ) 1172 }); 1173 1174 })(globalEvents, apinamespace, $, window) 1175 1176 var jSignatureInstanceExtensions = { 1177 /* 1178 'exampleExtension':function(extensionName){ 1179 // we are called very early in instance's life. 1180 // right after the settings are resolved and 1181 // jSignatureInstance.events is created 1182 // and right before first ("jSignature.initializing") event is called. 1183 // You don't really need to manupilate 1184 // jSignatureInstance directly, just attach 1185 // a bunch of events to jSignatureInstance.events 1186 // (look at the source of jSignatureClass to see when these fire) 1187 // and your special pieces of code will attach by themselves. 1188 1189 // this function runs every time a new instance is set up. 1190 // this means every var you create will live only for one instance 1191 // unless you attach it to something outside, like "window." 1192 // and pick it up later from there. 1193 1194 // when globalEvents' events fire, 'this' is globalEvents object 1195 // when jSignatureInstance's events fire, 'this' is jSignatureInstance 1196 1197 // Here,
1198 // this = is new jSignatureClass's instance. 1199 1200 // The way you COULD approch setting this up is: 1201 // if you have multistep set up, attach event to "jSignature.initializing" 1202 // that attaches other events to be fired further lower the init stream. 1203 // Or, if you know for sure you rely on only one jSignatureInstance's event, 1204 // just attach to it directly 1205 1206 this.events.subscribe( 1207 // name of the event 1208 apinamespace + '.initializing' 1209 // event handlers, can pass args too, but in majority of cases, 1210 // 'this' which is jSignatureClass object instance pointer is enough to get by. 1211 , function(){ 1212 if (this.settings.hasOwnProperty('non-existent setting category?')) { 1213 console.log(extensionName + ' is here') 1214 } 1215 } 1216 ) 1217 } 1218 */ 1219 }; 1220 1221 var exportplugins = { 1222 'default':function(data){return this.toDataURL()} 1223 , 'native':function(data){return data} 1224 , 'image':function(data){ 1225 /*this = canvas elem */ 1226 var imagestring = this.toDataURL(); 1227 1228 if (typeof imagestring === 'string' && 1229 imagestring.length > 4 && 1230 imagestring.slice(0,5) === 'data:' && 1231 imagestring.indexOf(',') !== -1){ 1232
1233 var splitterpos = imagestring.indexOf(','); 1234 1235 return [ 1236 imagestring.slice(5, splitterpos) 1237 , imagestring.substr(splitterpos + 1) 1238 ]; 1239 } 1240 return []; 1241 } 1242 }; 1243 1244 // will be part of "importplugins" 1245 function _renderImageOnCanvas( data, formattype, rerendercallable ) { 1246 'use strict' 1247 // #1. Do NOT rely on this. No worky on IE 1248 // (url max len + lack of base64 decoder + possibly other issues) 1249 // #2. This does NOT affect what is captured as "signature" as far as vector data is 1250 // concerned. This is treated same as "signature line" - i.e. completely ignored 1251 // the only time you see imported image data exported is if you export as image. 1252 1253 // we do NOT call rerendercallable here (unlike in other import plugins) 1254 // because importing image does absolutely nothing to the underlying vector data storage 1255 // This could be a way to "import" old signatures stored as images 1256 // This could also be a way to import extra decor into signature area. 1257 1258 var img = new Image() 1259 // this = Canvas DOM elem. Not jQuery object. Not Canvas's parent div. 1260 , c = this; 1261 1262 img.onload = function () { 1263 var ctx = c.getContext("2d"); 1264 var oldShadowColor = ctx.shadowColor; 1265 ctx.shadowColor = "transparent"; 1266 ctx.drawImage( 1267 img, 0, 0 1268 , ( img.width < c.width) ? img.width : c.width 1269 , ( img.height < c.height) ? img.height : c.height 1270 ); 1271 ctx.shadowColor = oldShadowColor; 1272 }; 1273 1274 img.src = 'data:' + formattype + ',' + data; 1275 } 1276 1277 var importplugins = { 1278 'native':function(data, formattype, rerendercallable){ 1279 // we expect data as Array of objects of arrays here - whatever 'default' EXPORT plugin spits out. 1280 // returning Truthy to indicate we are good, all updated. 1281 rerendercallable( data ); 1282 } 1283 , 'image': _renderImageOnCanvas 1284 , 'image/png;base64': _renderImageOnCanvas 1285 , 'image/jpeg;base64': _renderImageOnCanvas 1286 , 'image/jpg;base64': _renderImageOnCanvas 1287 }; 1288 1289 function _clearDrawingArea( data, dontClear ) { 1290 this.find('canvas.'+apinamespace) 1291 .add(this.filter('canvas.'+apinamespace)) 1292 .data(apinamespace+'.this').resetCanvas( data, dontClear ); 1293 return this; 1294 } 1295 1296 function _setDrawingData( data, formattype ) { 1297 var undef; 1298 1299 if (formattype === undef && typeof data === 'string' && data.substr(0,5) === 'data:') { 1300 formattype = data.slice(5).split(',')[0]; 1301 // 5 chars of "data:" + mimetype len + 1 "," char = all skipped. 1302 data = data.slice(6 + formattype.length); 1303 if (formattype === data) { 1304 return; 1305 } 1306 } 1307 1308 var $canvas = this.find('canvas.'+apinamespace).add(this.filter('canvas.'+apinamespace)); 1309 1310 if (!importplugins.hasOwnProperty(formattype)) { 1311 throw new Error(apinamespace + " is unable to find import plugin with for format '"+ String(formattype) +"'"); 1312 } else if ($canvas.length !== 0) { 1313 importplugins[formattype].call( 1314 $canvas[0] 1315 , data 1316 , formattype 1317 , (function(jSignatureInstance){ 1318 return function(){ return jSignatureInstance.resetCanvas.apply(jSignatureInstance, arguments) } 1319 })($canvas.data(apinamespace+'.this')) 1320 ); 1321 } 1322 1323 return this; 1324 } 1325 1326 var elementIsOrphan = function(e){ 1327 var topOfDOM = false; 1328 e = e.parentNode; 1329 while (e && !topOfDOM){ 1330 topOfDOM = e.body; 1331 e = e.parentNode; 1332 } 1333 return !topOfDOM; 1334 } 1335 1336 //These are exposed as methods under $obj.jSignature('methodname', *args) 1337 var plugins = {'export':exportplugins, 'import':importplugins, 'instance': jSignatureInstanceExtensions} 1338 , methods = { 1339 'init' : function( options ) { 1340 return this.each( function() { 1341 if (!elementIsOrphan(this)) { 1342 new jSignatureClass(this, options, jSignatureInstanceExtensions); 1343 } 1344 }) 1345 } 1346 , 'getSettings' : function() { 1347 return this.find('canvas.'+apinamespace) 1348 .add(this.filter('canvas.'+apinamespace)) 1349 .data(apinamespace+'.this').settings; 1350 } 1351 , 'isModified' : function() { 1352 return this.find('canvas.'+apinamespace) 1353 .add(this.filter('canvas.'+apinamespace)) 1354 .data(apinamespace+'.this') 1355 .dataEngine 1356 ._stroke !== null; 1357 } 1358 , 'updateSetting' : function(param, val, forFuture) { 1359 var $canvas = this.find('canvas.'+apinamespace) 1360 .add(this.filter('canvas.'+apinamespace)) 1361 .data(apinamespace+'.this'); 1362 $canvas.settings[param] = val; 1363 $canvas.resetCanvas(( forFuture ? null : $canvas.settings.data ), true); 1364 return $canvas.settings[param]; 1365 } 1366 // around since v1 1367 , 'clear' : _clearDrawingArea 1368 // was mistakenly introduced instead of 'clear' in v2 1369 , 'reset' : _clearDrawingArea 1370 , 'addPlugin' : function(pluginType, pluginName, callable){ 1371 if (plugins.hasOwnProperty(pluginType)){ 1372 plugins[pluginType][pluginName] = callable; 1373 } 1374 return this; 1375 } 1376 , 'listPlugins' : function(pluginType){ 1377 var answer = []; 1378 if (plugins.hasOwnProperty(pluginType)){ 1379 var o = plugins[pluginType]; 1380 for (var k in o){ 1381 if (o.hasOwnProperty(k)){ 1382 answer.push(k); 1383 } 1384 } 1385 } 1386 return answer; 1387 } 1388 , 'getData' : function( formattype ) { 1389 var undef, $canvas=this.find('canvas.'+apinamespace).add(this.filter('canvas.'+apinamespace)); 1390 if (formattype === undef) { 1391 formattype = 'default'; 1392 } 1393 if ($canvas.length !== 0 && exportplugins.hasOwnProperty(formattype)){ 1394 return exportplugins[formattype].call( 1395 $canvas.get(0) // canvas dom elem 1396 , $canvas.data(apinamespace+'.data') // raw signature data as array of objects of arrays 1397 , $canvas.data(apinamespace+'.settings') 1398 ); 1399 } 1400 } 1401 // around since v1. Took only one arg - data-url-formatted string with (likely png of) signature image 1402 , 'importData' : _setDrawingData 1403 // was mistakenly introduced instead of 'importData' in v2 1404 , 'setData' : _setDrawingData 1405 // this is one and same instance for all jSignature. 1406 , 'globalEvents' : function(){return globalEvents} 1407 , 'disable' : function() { 1408 this.find("input").attr("disabled", 1); 1409 this.find('canvas.'+apinamespace) 1410 .addClass("disabled") 1411 .data(apinamespace+'.this') 1412 .settings 1413 .readOnly=true; 1414 } 1415 , 'enable' : function() { 1416 this.find("input").removeAttr("disabled"); 1417 this.find('canvas.'+apinamespace) 1418 .removeClass("disabled") 1419 .data(apinamespace+'.this') 1420 .settings 1421 .readOnly=false; 1422 } 1423 // there will be a separate one for each jSignature instance. 1424 , 'events' : function() { 1425 return this.find('canvas.'+apinamespace) 1426 .add(this.filter('canvas.'+apinamespace)) 1427 .data(apinamespace+'.this').events; 1428 } 1429 } // end of methods declaration. 1430 1431 $.fn[apinamespace] = function(method) { 1432 'use strict' 1433 if ( !method || typeof method === 'object' ) { 1434 return methods.init.apply( this, arguments ); 1435 } else if ( typeof method === 'string' && methods[method] ) { 1436 return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 )); 1437 } else { 1438 $.error( 'Method ' + String(method) + ' does not exist on jQuery.' + apinamespace ); 1439 } 1440 } 1441 1442} // end of GlobalJSignatureObjectInitializer 1443 1444GlobalJSignatureObjectInitializer(window) 1445 1446})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.