/home/smartonegroup/www/system/ui/lib/jSignature.min.js
/** @preserve
 jSignature v2 "${buildDate}" "${commitID}"
 Copyright (c) 2012 Willow Systems Corp http://willow-systems.com
 Copyright (c) 2010 Brinley Ang http://www.unbolt.net
 MIT License <http://www.opensource.org/licenses/mit-license.php>

 */
;(function() {

    var apinamespace = 'jSignature'

    /**
     Allows one to delay certain eventual action by setting up a timer for it and allowing one to delay it
     by "kick"ing it. Sorta like "kick the can down the road"

     @public
     @class
     @param
     @returns {Type}
     */
    var KickTimerClass = function(time, callback) {
        var timer;
        this.kick = function() {
            clearTimeout(timer);
            timer = setTimeout(
                callback
                , time
            );
        }
        this.clear = function() {
            clearTimeout(timer);
        }
        return this;
    }

    var PubSubClass = function(context){
        'use strict'
        /*  @preserve
        -----------------------------------------------------------------------------------------------
        JavaScript PubSub library
        2012 (c) Willow Systems Corp (www.willow-systems.com)
        based on Peter Higgins (dante@dojotoolkit.org)
        Loosely based on Dojo publish/subscribe API, limited in scope. Rewritten blindly.
        Original is (c) Dojo Foundation 2004-2010. Released under either AFL or new BSD, see:
        http://dojofoundation.org/license for more information.
        -----------------------------------------------------------------------------------------------
        */
        this.topics = {};
        // here we choose what will be "this" for the called events.
        // if context is defined, it's context. Else, 'this' is this instance of PubSub
        this.context = context ? context : this;
        /**
         * Allows caller to emit an event and pass arguments to event listeners.
         * @public
         * @function
         * @param topic {String} Name of the channel on which to voice this event
         * @param **arguments Any number of arguments you want to pass to the listeners of this event.
         */
        this.publish = function(topic, arg1, arg2, etc) {
            'use strict'
            if (this.topics[topic]) {
                var currentTopic = this.topics[topic]
                    , args = Array.prototype.slice.call(arguments, 1)
                    , toremove = []
                    , torun = []
                    , fn
                    , i, l
                    , pair;

                for (i = 0, l = currentTopic.length; i < l; i++) {
                    pair = currentTopic[i]; // this is a [function, once_flag] array
                    fn = pair[0];
                    if (pair[1] /* 'run once' flag set */){
                        pair[0] = function(){};
                        toremove.push(i);
                    }
                    /* don't call the callback right now, it might decide to add or
                     * remove subscribers which will wreak havoc on our index-based
                     * iteration */
                    torun.push(fn);
                }
                for (i = 0, l = toremove.length; i < l; i++) {
                    currentTopic.splice(toremove[i], 1);
                }
                for (i = 0, l = torun.length; i < l; i++) {
                    torun[i].apply(this.context, args);
                }
            }
        }
        /**
         * Allows listener code to subscribe to channel and be called when data is available
         * @public
         * @function
         * @param topic {String} Name of the channel on which to voice this event
         * @param callback {Function} Executable (function pointer) that will be ran when event is voiced on this channel.
         * @param once {Boolean} (optional. False by default) Flag indicating if the function is to be triggered only once.
         * @returns {Object} A token object that cen be used for unsubscribing.
         */
        this.subscribe = function(topic, callback, once) {
            'use strict'
            if (!this.topics[topic]) {
                this.topics[topic] = [[callback, once]];
            } else {
                this.topics[topic].push([callback,once]);
            }
            return {
                "topic": topic,
                "callback": callback
            };
        };
        /**
         * Allows listener code to unsubscribe from a channel
         * @public
         * @function
         * @param token {Object} A token object that was returned by `subscribe` method
         */
        this.unsubscribe = function(token) {
            if (this.topics[token.topic]) {
                var currentTopic = this.topics[token.topic];

                for (var i = 0, l = currentTopic.length; i < l; i++) {
                    if (currentTopic[i] && currentTopic[i][0] === token.callback) {
                        currentTopic.splice(i, 1);
                    }
                }
            }
        }
    }

/// Returns front, back and "decor" colors derived from element (as jQuery obj)
    function getColors($e){
        var tmp
            , undef
            , frontcolor = $e.css('color')
            , backcolor
            , e = $e[0];

        var toOfDOM = false;
        while(e && !backcolor && !toOfDOM){
            try{
                tmp = $(e).css('background-color');
            } catch (ex) {
                tmp = 'transparent';
            }
            if (tmp !== 'transparent' && tmp !== 'rgba(0, 0, 0, 0)'){
                backcolor = tmp;
            }
            toOfDOM = e.body;
            e = e.parentNode;
        }

        var rgbaregex = /rgb[a]*\((\d+),\s*(\d+),\s*(\d+)/ // modern browsers
            , hexregex = /#([AaBbCcDdEeFf\d]{2})([AaBbCcDdEeFf\d]{2})([AaBbCcDdEeFf\d]{2})/ // IE 8 and less.
            , frontcolorcomponents;

        // Decomposing Front color into R, G, B ints
        tmp = undef;
        tmp = frontcolor.match(rgbaregex);
        if (tmp){
            frontcolorcomponents = {'r':parseInt(tmp[1],10),'g':parseInt(tmp[2],10),'b':parseInt(tmp[3],10)};
        } else {
            tmp = frontcolor.match(hexregex);
            if (tmp) {
                frontcolorcomponents = {'r':parseInt(tmp[1],16),'g':parseInt(tmp[2],16),'b':parseInt(tmp[3],16)};
            }
        }
//		if(!frontcolorcomponents){
//			frontcolorcomponents = {'r':255,'g':255,'b':255}
//		}

        var backcolorcomponents
        // Decomposing back color into R, G, B ints
        if(!backcolor){
            // HIghly unlikely since this means that no background styling was applied to any element from here to top of dom.
            // we'll pick up back color from front color
            if(frontcolorcomponents){
                if (Math.max.apply(null, [frontcolorcomponents.r, frontcolorcomponents.g, frontcolorcomponents.b]) > 127){
                    backcolorcomponents = {'r':0,'g':0,'b':0};
                } else {
                    backcolorcomponents = {'r':255,'g':255,'b':255};
                }
            } else {
                // arg!!! front color is in format we don't understand (hsl, named colors)
                // Let's just go with white background.
                backcolorcomponents = {'r':255,'g':255,'b':255};
            }
        } else {
            tmp = undef;
            tmp = backcolor.match(rgbaregex);
            if (tmp){
                backcolorcomponents = {'r':parseInt(tmp[1],10),'g':parseInt(tmp[2],10),'b':parseInt(tmp[3],10)};
            } else {
                tmp = backcolor.match(hexregex);
                if (tmp) {
                    backcolorcomponents = {'r':parseInt(tmp[1],16),'g':parseInt(tmp[2],16),'b':parseInt(tmp[3],16)};
                }
            }
//			if(!backcolorcomponents){
//				backcolorcomponents = {'r':0,'g':0,'b':0}
//			}
        }

        // Deriving Decor color
        // THis is LAZY!!!! Better way would be to use HSL and adjust luminocity. However, that could be an overkill.

        var toRGBfn = function(o){return 'rgb(' + [o.r, o.g, o.b].join(', ') + ')'}
            , decorcolorcomponents
            , frontcolorbrightness
            , adjusted;

        if (frontcolorcomponents && backcolorcomponents){
            var backcolorbrightness = Math.max.apply(null, [frontcolorcomponents.r, frontcolorcomponents.g, frontcolorcomponents.b]);

            frontcolorbrightness = Math.max.apply(null, [backcolorcomponents.r, backcolorcomponents.g, backcolorcomponents.b]);
            adjusted = Math.round(frontcolorbrightness + (-1 * (frontcolorbrightness - backcolorbrightness) * 0.75)); // "dimming" the difference between pen and back.
            decorcolorcomponents = {'r':adjusted,'g':adjusted,'b':adjusted}; // always shade of gray
        } else if (frontcolorcomponents) {
            frontcolorbrightness = Math.max.apply(null, [frontcolorcomponents.r, frontcolorcomponents.g, frontcolorcomponents.b]);
            var polarity = +1;
            if (frontcolorbrightness > 127){
                polarity = -1;
            }
            // shifting by 25% (64 points on RGB scale)
            adjusted = Math.round(frontcolorbrightness + (polarity * 96)); // "dimming" the pen's color by 75% to get decor color.
            decorcolorcomponents = {'r':adjusted,'g':adjusted,'b':adjusted}; // always shade of gray
        } else {
            decorcolorcomponents = {'r':191,'g':191,'b':191}; // always shade of gray
        }

        return {
            'color': frontcolor
            , 'background-color': backcolorcomponents? toRGBfn(backcolorcomponents) : backcolor
            , 'decor-color': toRGBfn(decorcolorcomponents)
        };
    }

    function Vector(x,y){
        this.x = x;
        this.y = y;
        this.reverse = function(){
            return new this.constructor(
                this.x * -1
                , this.y * -1
            );
        };
        this._length = null;
        this.getLength = function(){
            if (!this._length){
                this._length = Math.sqrt( Math.pow(this.x, 2) + Math.pow(this.y, 2) );
            }
            return this._length;
        };

        var polarity = function (e){
            return Math.round(e / Math.abs(e));
        };
        this.resizeTo = function(length){
            // proportionally changes x,y such that the hypotenuse (vector length) is = new length
            if (this.x === 0 && this.y === 0){
                this._length = 0;
            } else if (this.x === 0){
                this._length = length;
                this.y = length * polarity(this.y);
            } else if(this.y === 0){
                this._length = length;
                this.x = length * polarity(this.x);
            } else {
                var proportion = Math.abs(this.y / this.x)
                    , x = Math.sqrt(Math.pow(length, 2) / (1 + Math.pow(proportion, 2)))
                    , y = proportion * x;
                this._length = length;
                this.x = x * polarity(this.x);
                this.y = y * polarity(this.y);
            }
            return this;
        };

        /**
         * Calculates the angle between 'this' vector and another.
         * @public
         * @function
         * @returns {Number} The angle between the two vectors as measured in PI.
         */
        this.angleTo = function(vectorB) {
            var divisor = this.getLength() * vectorB.getLength();
            if (divisor === 0) {
                return 0;
            } else {
                // JavaScript floating point math is screwed up.
                // because of it, the core of the formula can, on occasion, have values
                // over 1.0 and below -1.0.
                return Math.acos(
                    Math.min(
                        Math.max(
                            ( this.x * vectorB.x + this.y * vectorB.y ) / divisor
                            , -1.0
                        )
                        , 1.0
                    )
                ) / Math.PI;
            }
        };
    }

    function Point(x,y){
        this.x = x;
        this.y = y;

        this.getVectorToCoordinates = function (x, y) {
            return new Vector(x - this.x, y - this.y);
        };
        this.getVectorFromCoordinates = function (x, y) {
            return this.getVectorToCoordinates(x, y).reverse();
        };
        this.getVectorToPoint = function (point) {
            return new Vector(point.x - this.x, point.y - this.y);
        };
        this.getVectorFromPoint = function (point) {
            return this.getVectorToPoint(point).reverse();
        };
    }

    /*
     * About data structure:
     * We don't store / deal with "pictures" this signature capture code captures "vectors"
     *
     * We don't store bitmaps. We store "strokes" as arrays of arrays. (Actually, arrays of objects containing arrays of coordinates.
     *
     * Stroke = mousedown + mousemoved * n (+ mouseup but we don't record that as that was the "end / lack of movement" indicator)
     *
     * Vectors = not classical vectors where numbers indicated shift relative last position. Our vectors are actually coordinates against top left of canvas.
     * 			we could calc the classical vectors, but keeping the the actual coordinates allows us (through Math.max / min)
     * 			to calc the size of resulting drawing very quickly. If we want classical vectors later, we can always get them in backend code.
     *
     * So, the data structure:
     *
     * var data = [
     * 	{ // stroke starts
     * 		x : [101, 98, 57, 43] // x points
     * 		, y : [1, 23, 65, 87] // y points
     * 	} // stroke ends
     * 	, { // stroke starts
     * 		x : [55, 56, 57, 58] // x points
     * 		, y : [101, 97, 54, 4] // y points
     * 	} // stroke ends
     * 	, { // stroke consisting of just a dot
     * 		x : [53] // x points
     * 		, y : [151] // y points
     * 	} // stroke ends
     * ]
     *
     * 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.
     *
     */
    function DataEngine(storageObject, context, startStrokeFn, addToStrokeFn, endStrokeFn){
        this.data = storageObject; // we expect this to be an instance of Array
        this.context = context;

        if (storageObject.length){
            // we have data to render
            var numofstrokes = storageObject.length
                , stroke
                , numofpoints;

            for (var i = 0; i < numofstrokes; i++){
                stroke = storageObject[i];
                numofpoints = stroke.x.length;
                startStrokeFn.call(context, stroke);
                for(var j = 1; j < numofpoints; j++){
                    addToStrokeFn.call(context, stroke, j);
                }
                endStrokeFn.call(context, stroke);
            }
        }

        this.changed = function(){};

        this.startStrokeFn = startStrokeFn;
        this.addToStrokeFn = addToStrokeFn;
        this.endStrokeFn = endStrokeFn;

        this.inStroke = false;

        this._lastPoint = null;
        this._stroke = null;
        this.startStroke = function(point){
            if(point && typeof(point.x) == "number" && typeof(point.y) == "number"){
                this._stroke = {'x':[point.x], 'y':[point.y]};
                this.data.push(this._stroke);
                this._lastPoint = point;
                this.inStroke = true;
                // 'this' does not work same inside setTimeout(
                var stroke = this._stroke
                    , fn = this.startStrokeFn
                    , context = this.context;
                setTimeout(
                    // some IE's don't support passing args per setTimeout API. Have to create closure every time instead.
                    function() {fn.call(context, stroke)}
                    , 3
                );
                return point;
            } else {
                return null;
            }
        };
        // that "5" at the very end of this if is important to explain.
        // we do NOT render links between two captured points (in the middle of the stroke) if the distance is shorter than that number.
        // not only do we NOT render it, we also do NOT capture (add) these intermediate points to storage.
        // when clustering of these is too tight, it produces noise on the line, which, because of smoothing, makes lines too curvy.
        // maybe, later, we can expose this as a configurable setting of some sort.
        this.addToStroke = function(point){
            if (this.inStroke &&
                typeof(point.x) === "number" &&
                typeof(point.y) === "number" &&
                // calculates absolute shift in diagonal pixels away from original point
                (Math.abs(point.x - this._lastPoint.x) + Math.abs(point.y - this._lastPoint.y)) > 4
            ){
                var positionInStroke = this._stroke.x.length;
                this._stroke.x.push(point.x);
                this._stroke.y.push(point.y);
                this._lastPoint = point;

                var stroke = this._stroke
                    , fn = this.addToStrokeFn
                    , context = this.context;
                setTimeout(
                    // some IE's don't support passing args per setTimeout API. Have to create closure every time instead.
                    function() {fn.call(context, stroke, positionInStroke)}
                    , 3
                );
                return point;
            } else {
                return null;
            }
        };
        this.endStroke = function(){
            var c = this.inStroke;
            this.inStroke = false;
            this._lastPoint = null;
            if (c){
                var stroke = this._stroke
                    , fn = this.endStrokeFn // 'this' does not work same inside setTimeout(
                    , context = this.context
                    , changedfn = this.changed;
                setTimeout(
                    // some IE's don't support passing args per setTimeout API. Have to create closure every time instead.
                    function(){
                        fn.call(context, stroke);
                        changedfn.call(context);
                    }
                    , 3
                );
                return true;
            } else {
                return null;
            }
        };
    }

    var basicDot = function(ctx, x, y, size){
        var fillStyle = ctx.fillStyle;
        ctx.fillStyle = ctx.strokeStyle;
        ctx.fillRect(x + size / -2 , y + size / -2, size, size);
        ctx.fillStyle = fillStyle;
    }
        , basicLine = function(ctx, startx, starty, endx, endy){
        ctx.beginPath();
        ctx.moveTo(startx, starty);
        ctx.lineTo(endx, endy);
        ctx.closePath();
        ctx.stroke();
    }
        , basicCurve = function(ctx, startx, starty, endx, endy, cp1x, cp1y, cp2x, cp2y){
        ctx.beginPath();
        ctx.moveTo(startx, starty);
        ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, endx, endy);
        ctx.closePath();
        ctx.stroke();
    }
        , strokeStartCallback = function(stroke) {
        // this = jSignatureClass instance
        basicDot(this.canvasContext, stroke.x[0], stroke.y[0], this.settings.lineWidth);
    }
        , strokeAddCallback = function(stroke, positionInStroke){
        // this = jSignatureClass instance

        // Because we are funky this way, here we draw TWO curves.
        // 1. POSSIBLY "this line" - spanning from point right before us, to this latest point.
        // 2. POSSIBLY "prior curve" - spanning from "latest point" to the one before it.

        // Why you ask?
        // long lines (ones with many pixels between them) do not look good when they are part of a large curvy stroke.
        // You know, the jaggedy crocodile spine instead of a pretty, smooth curve. Yuck!
        // We want to approximate pretty curves in-place of those ugly lines.
        // To approximate a very nice curve we need to know the direction of line before and after.
        // Hence, on long lines we actually wait for another point beyond it to come back from
        // mousemoved before we draw this curve.

        // So for "prior curve" to be calc'ed we need 4 points
        // 	A, B, C, D (we are on D now, A is 3 points in the past.)
        // and 3 lines:
        //  pre-line (from points A to B),
        //  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.)
        //  post-line (from points C to D) (even through D point is 'current' we don't know how we can draw it yet)
        //
        // Well, actually, we don't need to *know* the point A, just the vector A->B
        var Cpoint = new Point(stroke.x[positionInStroke-1], stroke.y[positionInStroke-1])
            , Dpoint = new Point(stroke.x[positionInStroke], stroke.y[positionInStroke])
            , CDvector = Cpoint.getVectorToPoint(Dpoint);

        // Again, we have a chance here to draw TWO things:
        //  BC Curve (only if it's long, because if it was short, it was drawn by previous callback) and
        //  CD Line (only if it's short)

        // So, let's start with BC curve.
        // if there is only 2 points in stroke array, we don't have "history" long enough to have point B, let alone point A.
        // Falling through to drawing line CD is proper, as that's the only line we have points for.
        if(positionInStroke > 1) {
            // we are here when there are at least 3 points in stroke array.
            var Bpoint = new Point(stroke.x[positionInStroke-2], stroke.y[positionInStroke-2])
                , BCvector = Bpoint.getVectorToPoint(Cpoint)
                , ABvector;
            if(BCvector.getLength() > this.lineCurveThreshold){
                // Yey! Pretty curves, here we come!
                if(positionInStroke > 2) {
                    // we are here when at least 4 points in stroke array.
                    ABvector = (new Point(stroke.x[positionInStroke-3], stroke.y[positionInStroke-3])).getVectorToPoint(Bpoint);
                } else {
                    ABvector = new Vector(0,0);
                }

                var minlenfraction = 0.05
                    , maxlen = BCvector.getLength() * 0.35
                    , ABCangle = BCvector.angleTo(ABvector.reverse())
                    , BCDangle = CDvector.angleTo(BCvector.reverse())
                    , BCP1vector = new Vector(ABvector.x + BCvector.x, ABvector.y + BCvector.y).resizeTo(
                    Math.max(minlenfraction, ABCangle) * maxlen
                )
                    , CCP2vector = (new Vector(BCvector.x + CDvector.x, BCvector.y + CDvector.y)).reverse().resizeTo(
                    Math.max(minlenfraction, BCDangle) * maxlen
                );

                basicCurve(
                    this.canvasContext
                    , Bpoint.x
                    , Bpoint.y
                    , Cpoint.x
                    , Cpoint.y
                    , Bpoint.x + BCP1vector.x
                    , Bpoint.y + BCP1vector.y
                    , Cpoint.x + CCP2vector.x
                    , Cpoint.y + CCP2vector.y
                );
            }
        }
        if(CDvector.getLength() <= this.lineCurveThreshold){
            basicLine(
                this.canvasContext
                , Cpoint.x
                , Cpoint.y
                , Dpoint.x
                , Dpoint.y
            );
        }
    }
        , strokeEndCallback = function(stroke){
        // this = jSignatureClass instance

        // Here we tidy up things left unfinished in last strokeAddCallback run.

        // What's POTENTIALLY left unfinished there is the curve between the last points
        // in the stroke, if the len of that line is more than lineCurveThreshold
        // If the last line was shorter than lineCurveThreshold, it was drawn there, and there
        // is nothing for us here to do.
        // We can also be called when there is only one point in the stroke (meaning, the
        // stroke was just a dot), in which case, again, there is nothing for us to do.

        // So for "this curve" to be calc'ed we need 3 points
        // 	A, B, C
        // and 2 lines:
        //  pre-line (from points A to B),
        //  this line (from points B to C)
        // Well, actually, we don't need to *know* the point A, just the vector A->B
        // so, we really need points B, C and AB vector.
        var positionInStroke = stroke.x.length - 1;

        if (positionInStroke > 0){
            // there are at least 2 points in the stroke.we are in business.
            var Cpoint = new Point(stroke.x[positionInStroke], stroke.y[positionInStroke])
                , Bpoint = new Point(stroke.x[positionInStroke-1], stroke.y[positionInStroke-1])
                , BCvector = Bpoint.getVectorToPoint(Cpoint)
                , ABvector;
            if (BCvector.getLength() > this.lineCurveThreshold){
                // yep. This one was left undrawn in prior callback. Have to draw it now.
                if (positionInStroke > 1){
                    // we have at least 3 elems in stroke
                    ABvector = (new Point(stroke.x[positionInStroke-2], stroke.y[positionInStroke-2])).getVectorToPoint(Bpoint);
                    var BCP1vector = new Vector(ABvector.x + BCvector.x, ABvector.y + BCvector.y).resizeTo(BCvector.getLength() / 2);
                    basicCurve(
                        this.canvasContext
                        , Bpoint.x
                        , Bpoint.y
                        , Cpoint.x
                        , Cpoint.y
                        , Bpoint.x + BCP1vector.x
                        , Bpoint.y + BCP1vector.y
                        , Cpoint.x
                        , Cpoint.y
                    );
                } else {
                    // Since there is no AB leg, there is no curve to draw. This line is still "long" but no curve.
                    basicLine(
                        this.canvasContext
                        , Bpoint.x
                        , Bpoint.y
                        , Cpoint.x
                        , Cpoint.y
                    );
                }
            }
        }
    }


    /*
    var getDataStats = function(){
        var strokecnt = strokes.length
            , stroke
            , pointid
            , pointcnt
            , x, y
            , maxX = Number.NEGATIVE_INFINITY
            , maxY = Number.NEGATIVE_INFINITY
            , minX = Number.POSITIVE_INFINITY
            , minY = Number.POSITIVE_INFINITY
        for(strokeid = 0; strokeid < strokecnt; strokeid++){
            stroke = strokes[strokeid]
            pointcnt = stroke.length
            for(pointid = 0; pointid < pointcnt; pointid++){
                x = stroke.x[pointid]
                y = stroke.y[pointid]
                if (x > maxX){
                    maxX = x
                } else if (x < minX) {
                    minX = x
                }
                if (y > maxY){
                    maxY = y
                } else if (y < minY) {
                    minY = y
                }
            }
        }
        return {'maxX': maxX, 'minX': minX, 'maxY': maxY, 'minY': minY}
    }
    */

    function conditionallyLinkCanvasResizeToWindowResize(jSignatureInstance, settingsWidth, apinamespace, globalEvents){
        'use strict'
        if ( settingsWidth === 'ratio' || settingsWidth.split('')[settingsWidth.length - 1] === '%' ) {

            this.eventTokens[apinamespace + '.parentresized'] = globalEvents.subscribe(
                apinamespace + '.parentresized'
                , (function(eventTokens, $parent, originalParentWidth, sizeRatio){
                    'use strict'

                    return function(){
                        'use strict'

                        var w = $parent.width();
                        if (w !== originalParentWidth) {

                            // UNsubscribing this particular instance of signature pad only.
                            // there is a separate `eventTokens` per each instance of signature pad
                            for (var key in eventTokens){
                                if (eventTokens.hasOwnProperty(key)) {
                                    globalEvents.unsubscribe(eventTokens[key]);
                                    delete eventTokens[key];
                                }
                            }

                            var settings = jSignatureInstance.settings;
                            jSignatureInstance.$parent.children().remove();
                            for (var key in jSignatureInstance){
                                if (jSignatureInstance.hasOwnProperty(key)) {
                                    delete jSignatureInstance[key];
                                }
                            }

                            // scale data to new signature pad size
                            settings.data = (function(data, scale){
                                var newData = [];
                                var o, i, l, j, m, stroke;
                                for ( i = 0, l = data.length; i < l; i++) {
                                    stroke = data[i];

                                    o = {'x':[],'y':[]};

                                    for ( j = 0, m = stroke.x.length; j < m; j++) {
                                        o.x.push(stroke.x[j] * scale);
                                        o.y.push(stroke.y[j] * scale);
                                    }

                                    newData.push(o);
                                }
                                return newData;
                            })(
                                settings.data
                                , w * 1.0 / originalParentWidth
                            )

                            $parent[apinamespace](settings);
                        }
                    }
                })(
                    this.eventTokens
                    , this.$parent
                    , this.$parent.width()
                    , this.canvas.width * 1.0 / this.canvas.height
                )
            )
        }
    };


    function jSignatureClass(parent, options, instanceExtensions) {

        var $parent = this.$parent = $(parent)
            , eventTokens = this.eventTokens = {}
            , events = this.events = new PubSubClass(this)
            , globalEvents = $.fn[apinamespace]('globalEvents')
            , settings = {
            'width' : 'ratio'
            ,'height' : 'ratio'
            ,'sizeRatio': 4 // only used when height = 'ratio'
            ,'color' : '#000'
            ,'background-color': '#fff'
            ,'decor-color': '#eee'
            ,'lineWidth' : 0
            ,'minFatFingerCompensation' : -10
            ,'showUndoButton': false
            ,'readOnly': false
            ,'data': []
            ,'signatureLine': false
        };

        $.extend(settings, getColors($parent));
        if (options) {
            $.extend(settings, options);
        }
        this.settings = settings;

        for (var extensionName in instanceExtensions){
            if (instanceExtensions.hasOwnProperty(extensionName)) {
                instanceExtensions[extensionName].call(this, extensionName);
            }
        }

        this.events.publish(apinamespace+'.initializing');

        // these, when enabled, will hover above the sig area. Hence we append them to DOM before canvas.
        this.$controlbarUpper = (function(){
            var controlbarstyle = 'padding:0 !important; margin:0 !important;'+
                'width: 100% !important; height: 0 !important; -ms-touch-action: none; touch-action: none;'+
                'margin-top:-1em !important; margin-bottom:1em !important;';
            return $('<div style="'+controlbarstyle+'"></div>').appendTo($parent);
        })();

        this.isCanvasEmulator = false; // will be flipped by initializer when needed.
        var canvas = this.canvas = this.initializeCanvas(settings)
            , $canvas = $(canvas);

        this.$controlbarLower = (function(){
            var controlbarstyle = 'padding:0 !important; margin:0 !important;'+
                'width: 100% !important; height: 0 !important; -ms-touch-action: none; touch-action: none;'+
                'margin-top:-1.5em !important; margin-bottom:1.5em !important; position: relative;';
            return $('<div style="'+controlbarstyle+'"></div>').appendTo($parent);
        })();

        this.canvasContext = canvas.getContext("2d");

        // Most of our exposed API will be looking for this:
        $canvas.data(apinamespace + '.this', this);

        settings.lineWidth = (function(defaultLineWidth, canvasWidth){
            if (!defaultLineWidth){
                return Math.max(
                    Math.round(canvasWidth / 400) /*+1 pixel for every extra 300px of width.*/
                    , 2 /* minimum line width */
                );
            } else {
                return defaultLineWidth;
            }
        })(settings.lineWidth, canvas.width);

        this.lineCurveThreshold = settings.lineWidth * 3;

        // Add custom class if defined
        if(settings.cssclass && $.trim(settings.cssclass) != "") {
            $canvas.addClass(settings.cssclass);
        }

        // used for shifting the drawing point up on touch devices, so one can see the drawing above the finger.
        this.fatFingerCompensation = 0;

        var movementHandlers = (function(jSignatureInstance) {

                //================================
                // mouse down, move, up handlers:

                // shifts - adjustment values in viewport pixels drived from position of canvas on the page
                var shiftX
                    , shiftY
                    , setStartValues = function(){
                    var tos = $(jSignatureInstance.canvas).offset()
                    shiftX = tos.left * -1
                    shiftY = tos.top * -1
                }
                    , getPointFromEvent = function(e) {
                    var firstEvent = (e.changedTouches && e.changedTouches.length > 0 ? e.changedTouches[0] : e);
                    // All devices i tried report correct coordinates in pageX,Y
                    // Android Chrome 2.3.x, 3.1, 3.2., Opera Mobile,  safari iOS 4.x,
                    // Windows: Chrome, FF, IE9, Safari
                    // None of that scroll shift calc vs screenXY other sigs do is needed.
                    // ... oh, yeah, the "fatFinger.." is for tablets so that people see what they draw.
                    return new Point(
                        Math.round(firstEvent.pageX + shiftX)
                        , Math.round(firstEvent.pageY + shiftY) + jSignatureInstance.fatFingerCompensation
                    );
                }
                    , timer = new KickTimerClass(
                    750
                    , function() { jSignatureInstance.dataEngine.endStroke(); }
                );

                this.drawEndHandler = function(e) {
                    if (!jSignatureInstance.settings.readOnly) {
                        try { e.preventDefault(); } catch (ex) {}
                        timer.clear();
                        jSignatureInstance.dataEngine.endStroke();
                    }
                };
                this.drawStartHandler = function(e) {
                    if (!jSignatureInstance.settings.readOnly) {
                        e.preventDefault();
                        // for performance we cache the offsets
                        // we recalc these only at the beginning the stroke
                        setStartValues();
                        jSignatureInstance.dataEngine.startStroke( getPointFromEvent(e) );
                        timer.kick();
                    }
                };
                this.drawMoveHandler = function(e) {
                    if (!jSignatureInstance.settings.readOnly) {
                        e.preventDefault();
                        if (!jSignatureInstance.dataEngine.inStroke){
                            return;
                        }
                        jSignatureInstance.dataEngine.addToStroke( getPointFromEvent(e) );
                        timer.kick();
                    }
                };

                return this;

            }).call( {}, this )

            //
            //================================

        ;(function(drawEndHandler, drawStartHandler, drawMoveHandler) {
            var canvas = this.canvas
                , $canvas = $(canvas)
                , undef;
            if (this.isCanvasEmulator){
                $canvas.bind('mousemove.'+apinamespace, drawMoveHandler);
                $canvas.bind('mouseup.'+apinamespace, drawEndHandler);
                $canvas.bind('mousedown.'+apinamespace, drawStartHandler);
            } else {
                var hasEventListener = typeof canvas.addEventListener === 'function';
                this.ontouchstart = function(e) {
                    canvas.onmousedown = canvas.onmouseup = canvas.onmousemove = undef;

                    this.fatFingerCompensation = (
                        settings.minFatFingerCompensation &&
                        settings.lineWidth * -3 > settings.minFatFingerCompensation
                    ) ? settings.lineWidth * -3 : settings.minFatFingerCompensation;

                    drawStartHandler(e);

                    if (hasEventListener) {
                        canvas.addEventListener('touchend', drawEndHandler);
                        canvas.addEventListener('touchstart', drawStartHandler);
                        canvas.addEventListener('touchmove', drawMoveHandler);
                    } else {
                        canvas.ontouchend = drawEndHandler;
                        canvas.ontouchstart = drawStartHandler;
                        canvas.ontouchmove = drawMoveHandler;
                    }
                };

                if (hasEventListener) {
                    canvas.addEventListener('touchstart', this.ontouchstart);
                } else {
                    canvas.ontouchstart = ontouchstart;
                }

                canvas.onmousedown = function(e) {
                    if (hasEventListener) {
                        canvas.removeEventListener('touchstart', this.ontouchstart);
                    } else {
                        canvas.ontouchstart = canvas.ontouchend = canvas.ontouchmove = undef;
                    }

                    drawStartHandler(e);

                    canvas.onmousedown = drawStartHandler;
                    canvas.onmouseup = drawEndHandler;
                    canvas.onmousemove = drawMoveHandler;
                }
                if (window.navigator.msPointerEnabled) {
                    canvas.onmspointerdown = drawStartHandler;
                    canvas.onmspointerup = drawEndHandler;
                    canvas.onmspointermove = drawMoveHandler;
                }
            }
        }).call(
            this
            , movementHandlers.drawEndHandler
            , movementHandlers.drawStartHandler
            , movementHandlers.drawMoveHandler
        )

        //=========================================
        // various event handlers

        // on mouseout + mouseup canvas did not know that mouseUP fired. Continued to draw despite mouse UP.
        // it is bettr than
        // $canvas.bind('mouseout', drawEndHandler)
        // because we don't want to break the stroke where user accidentally gets ouside and wants to get back in quickly.
        eventTokens[apinamespace + '.windowmouseup'] = globalEvents.subscribe(
            apinamespace + '.windowmouseup'
            , movementHandlers.drawEndHandler
        );

        this.events.publish(apinamespace+'.attachingEventHandlers');

        // If we have proportional width, we sign up to events broadcasting "window resized" and checking if
        // parent's width changed. If so, we (1) extract settings + data from current signature pad,
        // (2) remove signature pad from parent, and (3) reinit new signature pad at new size with same settings, (rescaled) data.
        conditionallyLinkCanvasResizeToWindowResize.call(
            this
            , this
            , settings.width.toString(10)
            , apinamespace, globalEvents
        );

        // end of event handlers.
        // ===============================

        this.resetCanvas(settings.data);

        // resetCanvas renders the data on the screen and fires ONE "change" event
        // if there is data. If you have controls that rely on "change" firing
        // attach them to something that runs before this.resetCanvas, like
        // apinamespace+'.attachingEventHandlers' that fires a bit higher.
        this.events.publish(apinamespace+'.initialized');

        return this;
    } // end of initBase

//=========================================================================
// jSignatureClass's methods and supporting fn's

    jSignatureClass.prototype.resetCanvas = function(data, dontClear){
        var canvas = this.canvas
            , settings = this.settings
            , ctx = this.canvasContext
            , isCanvasEmulator = this.isCanvasEmulator
            , cw = canvas.width
            , ch = canvas.height;

        // preparing colors, drawing area
        if (!dontClear){
            ctx.clearRect(0, 0, cw + 30, ch + 30);
        }

        ctx.shadowColor = ctx.fillStyle = settings['background-color']
        if (isCanvasEmulator){
            // FLashCanvas fills with Black by default, covering up the parent div's background
            // hence we refill
            ctx.fillRect(0,0,cw + 30, ch + 30);
        }

        ctx.lineWidth = Math.ceil(parseInt(settings.lineWidth, 10));
        ctx.lineCap = ctx.lineJoin = "round";

        // signature line
        if(settings.signatureLine) {
            if (null != settings['decor-color']) {
                ctx.strokeStyle = settings['decor-color'];
                ctx.shadowOffsetX = 0;
                ctx.shadowOffsetY = 0;
                var lineoffset = Math.round( ch / 5 );
                basicLine(ctx, lineoffset * 1.5, ch - lineoffset, cw - (lineoffset * 1.5), ch - lineoffset);
            }

            if (!isCanvasEmulator){
                ctx.shadowColor = ctx.strokeStyle;
                ctx.shadowOffsetX = ctx.lineWidth * 0.5;
                ctx.shadowOffsetY = ctx.lineWidth * -0.6;
                ctx.shadowBlur = 0;
            }
        }

        ctx.strokeStyle = settings.color;

        // setting up new dataEngine

        if (!data) { data = []; }

        var dataEngine = this.dataEngine = new DataEngine(
            data
            , this
            , strokeStartCallback
            , strokeAddCallback
            , strokeEndCallback
        );

        settings.data = data; // onwindowresize handler uses it, i think.
        $(canvas).data(apinamespace+'.data', data)
            .data(apinamespace+'.settings', settings);

        // we fire "change" event on every change in data.
        // setting this up:
        dataEngine.changed = (function(target, events, apinamespace) {
            'use strict'
            return function() {
                events.publish(apinamespace+'.change');
                target.trigger('change');
            }
        })(this.$parent, this.events, apinamespace);
        // let's trigger change on all data reloads
        dataEngine.changed();

        // import filters will be passing this back as indication of "we rendered"
        return true;
    };

    function initializeCanvasEmulator(canvas){
        if (canvas.getContext){
            return false;
        } else {
            // for cases when jSignature, FlashCanvas is inserted
            // from one window into another (child iframe)
            // 'window' and 'FlashCanvas' may be stuck behind
            // in that other parent window.
            // we need to find it
            var window = canvas.ownerDocument.parentWindow;
            var FC = window.FlashCanvas ?
                canvas.ownerDocument.parentWindow.FlashCanvas :
                (
                    typeof FlashCanvas === "undefined" ?
                        undefined :
                        FlashCanvas
                );

            if (FC) {
                canvas = FC.initElement(canvas);

                var zoom = 1;
                // FlashCanvas uses flash which has this annoying habit of NOT scaling with page zoom.
                // It matches pixel-to-pixel to screen instead.
                // Since we are targeting ONLY IE 7, 8 with FlashCanvas, we will test the zoom only the IE8, IE7 way
                if (window && window.screen && window.screen.deviceXDPI && window.screen.logicalXDPI){
                    zoom = window.screen.deviceXDPI * 1.0 / window.screen.logicalXDPI;
                }
                if (zoom !== 1){
                    try {
                        // We effectively abuse the brokenness of FlashCanvas and force the flash rendering surface to
                        // occupy larger pixel dimensions than the wrapping, scaled up DIV and Canvas elems.
                        $(canvas).children('object').get(0).resize(Math.ceil(canvas.width * zoom), Math.ceil(canvas.height * zoom));
                        // And by applying "scale" transformation we can talk "browser pixels" to FlashCanvas
                        // and have it translate the "browser pixels" to "screen pixels"
                        canvas.getContext('2d').scale(zoom, zoom);
                        // Note to self: don't reuse Canvas element. Repeated "scale" are cumulative.
                    } catch (ex) {}
                }
                return true;
            } else {
                throw new Error("Canvas element does not support 2d context. jSignature cannot proceed.");
            }
        }

    }

    jSignatureClass.prototype.initializeCanvas = function(settings) {
        // ===========
        // Init + Sizing code

        var canvas = document.createElement('canvas')
            , $canvas = $(canvas);

        // We cannot work with circular dependency
        if (settings.width === settings.height && settings.height === 'ratio') {
            settings.width = '100%';
        }

        $canvas.css(
            {
                'margin': 0,
                'padding': 0,
                'border': 'none',
                'height': settings.height === 'ratio' || !settings.height ? 1 : settings.height.toString(10),
                'width': settings.width === 'ratio' || !settings.width ? 1 : settings.width.toString(10),
                '-ms-touch-action': 'none',
                'touch-action': 'none',
                'background-color': settings['background-color']
            }
        );

        $canvas.appendTo(this.$parent);

        // we could not do this until canvas is rendered (appended to DOM)
        if (settings.height === 'ratio') {
            $canvas.css(
                'height'
                , Math.round( $canvas.width() / settings.sizeRatio )
            );
        } else if (settings.width === 'ratio') {
            $canvas.css(
                'width'
                , Math.round( $canvas.height() * settings.sizeRatio )
            );
        }

        $canvas.addClass(apinamespace);

        // canvas's drawing area resolution is independent from canvas's size.
        // pixels are just scaled up or down when internal resolution does not
        // match external size. So...

        canvas.width = $canvas.width();
        canvas.height = $canvas.height();

        // Special case Sizing code

        this.isCanvasEmulator = initializeCanvasEmulator(canvas);

        // End of Sizing Code
        // ===========

        // normally select preventer would be short, but
        // Canvas emulator on IE does NOT provide value for Event. Hence this convoluted line.
        canvas.onselectstart = function(e){if(e && e.preventDefault){e.preventDefault()}; if(e && e.stopPropagation){e.stopPropagation()}; return false;};

        return canvas;
    }


    var GlobalJSignatureObjectInitializer = function(window){

        var globalEvents = new PubSubClass();

        // common "window resized" event listener.
        // jSignature instances will subscribe to this chanel.
        // to resize themselves when needed.
        ;(function(globalEvents, apinamespace, $, window){
            'use strict'

            var resizetimer
                , runner = function(){
                globalEvents.publish(
                    apinamespace + '.parentresized'
                )
            };

            // jSignature knows how to resize its content when its parent is resized
            // window resize is the only way we can catch resize events though...
            $(window).bind('resize.'+apinamespace, function(){
                if (resizetimer) {
                    clearTimeout(resizetimer);
                }
                resizetimer = setTimeout(
                    runner
                    , 500
                );
            })
            // when mouse exists canvas element and "up"s outside, we cannot catch it with
            // callbacks attached to canvas. This catches it outside.
                .bind('mouseup.'+apinamespace, function(e){
                    globalEvents.publish(
                        apinamespace + '.windowmouseup'
                    )
                });

        })(globalEvents, apinamespace, $, window)

        var jSignatureInstanceExtensions = {
            /*
            'exampleExtension':function(extensionName){
                // we are called very early in instance's life.
                // right after the settings are resolved and
                // jSignatureInstance.events is created
                // and right before first ("jSignature.initializing") event is called.
                // You don't really need to manupilate
                // jSignatureInstance directly, just attach
                // a bunch of events to jSignatureInstance.events
                // (look at the source of jSignatureClass to see when these fire)
                // and your special pieces of code will attach by themselves.

                // this function runs every time a new instance is set up.
                // this means every var you create will live only for one instance
                // unless you attach it to something outside, like "window."
                // and pick it up later from there.

                // when globalEvents' events fire, 'this' is globalEvents object
                // when jSignatureInstance's events fire, 'this' is jSignatureInstance

                // Here,
                // this = is new jSignatureClass's instance.

                // The way you COULD approch setting this up is:
                // if you have multistep set up, attach event to "jSignature.initializing"
                // that attaches other events to be fired further lower the init stream.
                // Or, if you know for sure you rely on only one jSignatureInstance's event,
                // just attach to it directly

                this.events.subscribe(
                    // name of the event
                    apinamespace + '.initializing'
                    // event handlers, can pass args too, but in majority of cases,
                    // 'this' which is jSignatureClass object instance pointer is enough to get by.
                    , function(){
                        if (this.settings.hasOwnProperty('non-existent setting category?')) {
                            console.log(extensionName + ' is here')
                        }
                    }
                )
            }
            */
        };

        var exportplugins = {
            'default':function(data){return this.toDataURL()}
            , 'native':function(data){return data}
            , 'image':function(data){
                /*this = canvas elem */
                var imagestring = this.toDataURL();

                if (typeof imagestring === 'string' &&
                    imagestring.length > 4 &&
                    imagestring.slice(0,5) === 'data:' &&
                    imagestring.indexOf(',') !== -1){

                    var splitterpos = imagestring.indexOf(',');

                    return [
                        imagestring.slice(5, splitterpos)
                        , imagestring.substr(splitterpos + 1)
                    ];
                }
                return [];
            }
        };

        // will be part of "importplugins"
        function _renderImageOnCanvas( data, formattype, rerendercallable ) {
            'use strict'
            // #1. Do NOT rely on this. No worky on IE
            //   (url max len + lack of base64 decoder + possibly other issues)
            // #2. This does NOT affect what is captured as "signature" as far as vector data is
            // concerned. This is treated same as "signature line" - i.e. completely ignored
            // the only time you see imported image data exported is if you export as image.

            // we do NOT call rerendercallable here (unlike in other import plugins)
            // because importing image does absolutely nothing to the underlying vector data storage
            // This could be a way to "import" old signatures stored as images
            // This could also be a way to import extra decor into signature area.

            var img = new Image()
                // this = Canvas DOM elem. Not jQuery object. Not Canvas's parent div.
                , c = this;

            img.onload = function () {
                var ctx = c.getContext("2d");
                var oldShadowColor = ctx.shadowColor;
                ctx.shadowColor = "transparent";
                ctx.drawImage(
                    img, 0, 0
                    , ( img.width < c.width) ? img.width : c.width
                    , ( img.height < c.height) ? img.height : c.height
                );
                ctx.shadowColor = oldShadowColor;
            };

            img.src = 'data:' + formattype + ',' + data;
        }

        var importplugins = {
            'native':function(data, formattype, rerendercallable){
                // we expect data as Array of objects of arrays here - whatever 'default' EXPORT plugin spits out.
                // returning Truthy to indicate we are good, all updated.
                rerendercallable( data );
            }
            , 'image': _renderImageOnCanvas
            , 'image/png;base64': _renderImageOnCanvas
            , 'image/jpeg;base64': _renderImageOnCanvas
            , 'image/jpg;base64': _renderImageOnCanvas
        };

        function _clearDrawingArea( data, dontClear ) {
            this.find('canvas.'+apinamespace)
                .add(this.filter('canvas.'+apinamespace))
                .data(apinamespace+'.this').resetCanvas( data, dontClear );
            return this;
        }

        function _setDrawingData( data, formattype ) {
            var undef;

            if (formattype === undef && typeof data === 'string' && data.substr(0,5) === 'data:') {
                formattype = data.slice(5).split(',')[0];
                // 5 chars of "data:" + mimetype len + 1 "," char = all skipped.
                data = data.slice(6 + formattype.length);
                if (formattype === data) {
                    return;
                }
            }

            var $canvas = this.find('canvas.'+apinamespace).add(this.filter('canvas.'+apinamespace));

            if (!importplugins.hasOwnProperty(formattype)) {
                throw new Error(apinamespace + " is unable to find import plugin with for format '"+ String(formattype) +"'");
            } else if ($canvas.length !== 0) {
                importplugins[formattype].call(
                    $canvas[0]
                    , data
                    , formattype
                    , (function(jSignatureInstance){
                        return function(){ return jSignatureInstance.resetCanvas.apply(jSignatureInstance, arguments) }
                    })($canvas.data(apinamespace+'.this'))
                );
            }

            return this;
        }

        var elementIsOrphan = function(e){
            var topOfDOM = false;
            e = e.parentNode;
            while (e && !topOfDOM){
                topOfDOM = e.body;
                e = e.parentNode;
            }
            return !topOfDOM;
        }

        //These are exposed as methods under $obj.jSignature('methodname', *args)
        var plugins = {'export':exportplugins, 'import':importplugins, 'instance': jSignatureInstanceExtensions}
            , methods = {
            'init' : function( options ) {
                return this.each( function() {
                    if (!elementIsOrphan(this)) {
                        new jSignatureClass(this, options, jSignatureInstanceExtensions);
                    }
                })
            }
            , 'destroy': function() {
                return this.each(function() {
                    if(!elementIsOrphan(this)) {
                        var sig = $(this).find('canvas').data(apinamespace + '.this');
                        if(sig) {
                            sig.$controlbarLower.remove();
                            sig.$controlbarUpper.remove();
                            $(sig.canvas).remove();
                            for (var e in sig.eventTokens){
                                if (sig.eventTokens.hasOwnProperty(e)){
                                    globalEvents.unsubscribe(sig.eventTokens[e]);
                                }
                            }
                        }
                    }
                });
            }
            , 'getSettings' : function() {
                return this.find('canvas.'+apinamespace)
                    .add(this.filter('canvas.'+apinamespace))
                    .data(apinamespace+'.this').settings;
            }
            , 'isModified' : function() {
                return this.find('canvas.'+apinamespace)
                    .add(this.filter('canvas.'+apinamespace))
                    .data(apinamespace+'.this')
                    .dataEngine
                    ._stroke !== null;
            }
            , 'updateSetting' : function(param, val, forFuture) {
                var $canvas = this.find('canvas.'+apinamespace)
                    .add(this.filter('canvas.'+apinamespace))
                    .data(apinamespace+'.this');
                $canvas.settings[param] = val;
                $canvas.resetCanvas(( forFuture ? null : $canvas.settings.data ), true);
                return $canvas.settings[param];
            }
            // around since v1
            , 'clear' : _clearDrawingArea
            // was mistakenly introduced instead of 'clear' in v2
            , 'reset' : _clearDrawingArea
            , 'addPlugin' : function(pluginType, pluginName, callable){
                if (plugins.hasOwnProperty(pluginType)){
                    plugins[pluginType][pluginName] = callable;
                }
                return this;
            }
            , 'listPlugins' : function(pluginType){
                var answer = [];
                if (plugins.hasOwnProperty(pluginType)){
                    var o = plugins[pluginType];
                    for (var k in o){
                        if (o.hasOwnProperty(k)){
                            answer.push(k);
                        }
                    }
                }
                return answer;
            }
            , 'getData' : function( formattype ) {
                var undef, $canvas=this.find('canvas.'+apinamespace).add(this.filter('canvas.'+apinamespace));
                if (formattype === undef) {
                    formattype = 'default';
                }
                if ($canvas.length !== 0 && exportplugins.hasOwnProperty(formattype)){
                    return exportplugins[formattype].call(
                        $canvas.get(0) // canvas dom elem
                        , $canvas.data(apinamespace+'.data') // raw signature data as array of objects of arrays
                        , $canvas.data(apinamespace+'.settings')
                    );
                }
            }
            // around since v1. Took only one arg - data-url-formatted string with (likely png of) signature image
            , 'importData' : _setDrawingData
            // was mistakenly introduced instead of 'importData' in v2
            , 'setData' : _setDrawingData
            // this is one and same instance for all jSignature.
            , 'globalEvents' : function(){return globalEvents}
            , 'disable' : function() {
                this.find("input").attr("disabled", 1);
                this.find('canvas.'+apinamespace)
                    .addClass("disabled")
                    .data(apinamespace+'.this')
                    .settings
                    .readOnly=true;
            }
            , 'enable' : function() {
                this.find("input").removeAttr("disabled");
                this.find('canvas.'+apinamespace)
                    .removeClass("disabled")
                    .data(apinamespace+'.this')
                    .settings
                    .readOnly=false;
            }
            // there will be a separate one for each jSignature instance.
            , 'events' : function() {
                return this.find('canvas.'+apinamespace)
                    .add(this.filter('canvas.'+apinamespace))
                    .data(apinamespace+'.this').events;
            }
        } // end of methods declaration.

        $.fn[apinamespace] = function(method) {
            'use strict'
            if ( !method || typeof method === 'object' ) {
                return methods.init.apply( this, arguments );
            } else if ( typeof method === 'string' && methods[method] ) {
                return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
            } else {
                $.error( 'Method ' +  String(method) + ' does not exist on jQuery.' + apinamespace );
            }
        }

    } // end of GlobalJSignatureObjectInitializer

    GlobalJSignatureObjectInitializer(window)

})();