PageSourceSearch

https://www.american-stone.com/web/static/lib/jSignature/jSignatureCustom.js

js american-stone.com collected 2026-10-08 20:38:18 UTC 56,303 bytes, 1,446 lines download raw bytes

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.