liuyuqi-dellpc 4 years ago
commit
ac8d6c889d
5 changed files with 1538 additions and 0 deletions
  1. 141 0
      index.html
  2. 1395 0
      js/Signature/jSignature.js
  3. 1 0
      js/jquery-1.11.0.min.js
  4. 1 0
      js/layer/layer.js
  5. 0 0
      js/layer/theme/default/layer.css

+ 141 - 0
index.html

@@ -0,0 +1,141 @@
+
+
+
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width,initial-scale=1">
+    <title>签名</title>
+    <script type="text/javascript" src="js/jquery-1.11.0.min.js"></script>
+    <script type="text/javascript" src="js/layer/layer.js"></script>
+    <script type="text/javascript" src="js/Signature/jSignature.js"></script>
+    <style type="text/css">
+        body {
+            position: absolute;
+            top: 0;
+            bottom: 0;
+            right: 0;
+            left: 0;
+        }
+
+        h3 {
+            margin: 5px auto;
+            font-size: 22px;
+            color: #515e7e;
+            word-break: break-all;
+        }
+
+        .inputbtn {
+            display: inline-block;
+            width: 100px;
+            height: 52px;
+            border-radius: 5px;
+            font-size: 16px;
+            font-weight: bold;
+            vertical-align: middle;
+            line-height: 52px;
+            text-align: center;
+            margin-top: 10px;
+            border: none;
+            background-color: #108EE9;
+            color: #FFFFFF;
+        }
+
+            .inputbtn:hover {
+                cursor: pointer;
+                background-color: crimson;
+            }
+    </style>
+</head>
+<body>
+    <h3>请在下方签名</h3>
+    <div id="signature" style="border: 1px dashed #000; width: calc(100%); height: calc(100% - 150px);"></div>
+    <div id="tools" style="text-align: center; width: 100%;">
+        <input id="clear" class="inputbtn " type="button" value="清除">
+        <input id="btnSignConfirm" class="inputbtn " type="button" value="确认完成">
+    </div>
+    <script type="text/javascript" charset="utf-8">
+        $(document).ready(function () {
+            $("#signature").jSignature({
+                'width': '100%',
+                'height': '100%'
+            });
+        });
+        $("#clear").bind('click', function () {
+            $("#signature").jSignature("reset");
+        });
+        $("#btnSignConfirm").bind('click', function () {
+            var datapair = $("#signature").jSignature("getData", "image");
+            var array = datapair.splice(",");
+            var data = $("#signature").jSignature('getData', 'image');
+
+            var hasimg = $("#signature").jSignature('getData', 'native').length;
+            var signbase64 = "";
+            if (hasimg == 0) {
+                var strdivyy = '<div ><p class="auto-close">未签字,请先签字!</p></div>';
+                layer.open({
+                    type: 1,
+                    title: "温馨提示",
+                    area: ['400px', '200px'],
+                    content: strdivyy,
+                    skin: 'layer-ext-iframe',
+                    move: false,
+                    btn: "",
+                    time: 2000
+                });
+            } else {
+                //获取签字图片的base64
+                if (typeof data === 'string') {
+                    signbase64 = data.replace('image/png;base64,', '');
+                } else if ($.isArray(data) && data.length === 2) {
+                    signbase64 = data.join(',').replace('image/png;base64,', '');
+                } else {
+                    signbase64 = "";
+                }
+                if (successfunc) {
+                    successfunc(signbase64);
+                }
+            }
+        });
+
+        function successfunc(signbase64) {
+
+            var load = layer.msg("提交中,请稍等", {
+                icon: 16,
+                shade: [0.1, '#fff'],
+                time: false  //取消自动关闭
+            });
+
+            $.ajax({
+                url: '../DataHandle.ashx',
+                type: 'POST',
+                async: false,
+                data: {
+                    "InitType": "updateSign",
+                    "RowGuid": "b8a694b2-984e-4a3c-9961-a300a8e13f0f",
+                    "tno": "794027",
+                    "Sign": signbase64
+                },
+                dataType: 'json',
+                contentType: "application/x-www-form-urlencoded; charset=utf-8",
+                success: function (data) {
+                    if (data.Flag) {
+                        layer.closeAll();
+                        layer.alert("签名成功,请关闭!");
+                        $("#clear").unbind('click');
+                        $("#btnSignConfirm").unbind('click');
+                    }
+                    else {
+                        layer.alert(data.Message);
+                    }
+                },
+                error: function (err) {
+                    layer.alert('系统异常!');
+                    console.error(err);
+                }
+            });
+        }
+    </script>
+</body>
+</html>

+ 1395 - 0
js/Signature/jSignature.js

@@ -0,0 +1,1395 @@
+/** @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 = []
+			, 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)
+				}
+			   	fn.apply(this.context, args)
+			}
+			for (i = 0, l = toremove.length; i < l; i++) {
+			  currentTopic.splice(toremove[i], 1)
+			}
+		}
+	}
+	/**
+	 * 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][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' : '1160'
+		,'height' : '450'
+		,'sizeRatio': 3 // only used when height = 'ratio'
+		,'color' : '#000'
+		,'background-color': '#fff'
+		,'decor-color': '#eee'
+		,'lineWidth' : 0
+		,'minFatFingerCompensation' : -10
+		,'showUndoButton': false
+		,'data': []
+	}
+	
+	$.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;'+
+			'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;'+
+			'margin-top:-1.5em !important;margin-bottom:1.5em !important;'
+		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) {
+			try { e.preventDefault() } catch (ex) {}
+			timer.clear()
+			jSignatureInstance.dataEngine.endStroke()
+		}
+		this.drawStartHandler = function(e) {
+			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) {
+			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 {
+			canvas.ontouchstart = function(e) {
+				canvas.onmousedown = undef
+				canvas.onmouseup = undef
+				canvas.onmousemove = undef
+
+				this.fatFingerCompensation = (
+					settings.minFatFingerCompensation && 
+					settings.lineWidth * -3 > settings.minFatFingerCompensation
+				) ? settings.lineWidth * -3 : settings.minFatFingerCompensation
+
+				drawStartHandler(e)
+
+				canvas.ontouchend = drawEndHandler
+				canvas.ontouchstart = drawStartHandler
+				canvas.ontouchmove = drawMoveHandler
+			}
+			canvas.onmousedown = function(e) {
+				canvas.ontouchstart = undef
+				canvas.ontouchend = undef
+				canvas.ontouchmove = undef
+
+				drawStartHandler(e)
+
+				canvas.onmousedown = drawStartHandler
+				canvas.onmouseup = drawEndHandler
+				canvas.onmousemove = 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){
+	var canvas = this.canvas
+	, settings = this.settings
+	, ctx = this.canvasContext
+	, isCanvasEmulator = this.isCanvasEmulator
+
+	, cw = canvas.width
+	, ch = canvas.height
+	
+	// preparing colors, drawing area
+
+	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
+	//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)
+	//ctx.strokeStyle = settings.color
+
+	//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
+	).css(
+		'padding'
+		, 0
+	).css(
+		'border'
+		, 'none'
+	).css(
+		'height'
+		, settings.height === 'ratio' || !settings.height ? 1 : settings.height.toString(10)
+	).css(
+		'width'
+		, settings.width === 'ratio' || !settings.width ? 1 : settings.width.toString(10)
+	)
+
+	$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").drawImage( 
+				img, 0, 0
+				, ( img.width < c.width) ? img.width : c.width
+				, ( img.height < c.height) ? img.height : c.height
+			)
+		}
+
+		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 ) {
+		this.find('canvas.'+apinamespace)
+			.add(this.filter('canvas.'+apinamespace))
+			.data(apinamespace+'.this').resetCanvas( data )
+		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)					
+				}
+			})
+		}
+		, 'getSettings' : function() {
+			return this.find('canvas.'+apinamespace)
+				.add(this.filter('canvas.'+apinamespace))
+				.data(apinamespace+'.this').settings
+		}
+		// 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
+				)
+			}
+		}
+		// 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}
+		// 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)
+
+})();

File diff suppressed because it is too large
+ 1 - 0
js/jquery-1.11.0.min.js


File diff suppressed because it is too large
+ 1 - 0
js/layer/layer.js


File diff suppressed because it is too large
+ 0 - 0
js/layer/theme/default/layer.css


Some files were not shown because too many files changed in this diff