summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorqsm-odoo <qsm@odoo.com>2015-03-27 11:18:25 +0100
committerqsm-odoo <qsm@odoo.com>2015-07-16 18:03:06 +0200
commiteddcb46c82f17d75c99b8dacdee15d6489467b59 (patch)
treec41cf8e0718c10ab2ca96c510289775b313d8c96
parent63567e2b5a82b02cf8acc6d0676d29602111a382 (diff)
[ADD] website, website_quote: new jSignature lib
Note: This is a custom version of the lib which allows to enable/disable the signature area and which introduces the compatibility with IE + bug fix of the official version.
-rw-r--r--addons/website/static/lib/jSignature/jSignatureCustom.js1457
-rw-r--r--addons/website_quote/static/lib/jSignature/jSignature.min.js77
-rw-r--r--addons/website_quote/views/website_quotation.xml2
3 files changed, 1458 insertions, 78 deletions
diff --git a/addons/website/static/lib/jSignature/jSignatureCustom.js b/addons/website/static/lib/jSignature/jSignatureCustom.js
new file mode 100644
index 00000000000..ae51103da3d
--- /dev/null
+++ b/addons/website/static/lib/jSignature/jSignatureCustom.js
@@ -0,0 +1,1457 @@
+/** @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': []
+ };
+
+ $.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;'+
+ '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;'+
+ '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 {
+ canvas.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);
+
+ canvas.ontouchend = drawEndHandler;
+ canvas.ontouchstart = drawStartHandler;
+ canvas.ontouchmove = drawMoveHandler;
+ };
+ canvas.onmousedown = function(e) {
+ 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 (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);
+ }
+ 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;
+ }
+
+ // 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)
+ ).css(
+ '-ms-touch-action'
+ , 'none'
+ ).css(
+ '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);
+ }
+ })
+ }
+ , '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)
+
+})();
diff --git a/addons/website_quote/static/lib/jSignature/jSignature.min.js b/addons/website_quote/static/lib/jSignature/jSignature.min.js
deleted file mode 100644
index 3c5590659a8..00000000000
--- a/addons/website_quote/static/lib/jSignature/jSignature.min.js
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
-
-jSignature v2 "2012-11-01T22:48" "commit ID 1c15dfafecc75925c3b7d529356a558b59220edb"
-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>
-
-
-Simplify.js BSD
-(c) 2012, Vladimir Agafonkin
-mourner.github.com/simplify-js
-
-
-base64 encoder
-MIT, GPL
-http://phpjs.org/functions/base64_encode
-+ original by: Tyler Akins (http://rumkin.com)
-+ improved by: Bayron Guevara
-+ improved by: Thunder.m
-+ improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
-+ bugfixed by: Pellentesque Malesuada
-+ improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
-+ improved by: Rafal Kukawski (http://kukawski.pl)
-
-
-jSignature v2 jSignature's Undo Button and undo functionality plugin
-
-
-jSignature v2 jSignature's custom "base30" format export and import plugins.
-
-
-jSignature v2 SVG export plugin.
-
-*/
-(function(){function q(b){for(var j,s=b.css("color"),g,b=b[0],c=!1;b&&!g&&!c;){try{j=$(b).css("background-color")}catch(d){j="transparent"}"transparent"!==j&&"rgba(0, 0, 0, 0)"!==j&&(g=j);c=b.body;b=b.parentNode}var b=/rgb[a]*\((\d+),\s*(\d+),\s*(\d+)/,c=/#([AaBbCcDdEeFf\d]{2})([AaBbCcDdEeFf\d]{2})([AaBbCcDdEeFf\d]{2})/,a;j=void 0;(j=s.match(b))?a={r:parseInt(j[1],10),g:parseInt(j[2],10),b:parseInt(j[3],10)}:(j=s.match(c))&&(a={r:parseInt(j[1],16),g:parseInt(j[2],16),b:parseInt(j[3],16)});var f;g?
-(j=void 0,(j=g.match(b))?f={r:parseInt(j[1],10),g:parseInt(j[2],10),b:parseInt(j[3],10)}:(j=g.match(c))&&(f={r:parseInt(j[1],16),g:parseInt(j[2],16),b:parseInt(j[3],16)})):f=a?127<Math.max.apply(null,[a.r,a.g,a.b])?{r:0,g:0,b:0}:{r:255,g:255,b:255}:{r:255,g:255,b:255};j=function(b){return"rgb("+[b.r,b.g,b.b].join(", ")+")"};a&&f?(b=Math.max.apply(null,[a.r,a.g,a.b]),a=Math.max.apply(null,[f.r,f.g,f.b]),a=Math.round(a+-0.75*(a-b)),a={r:a,g:a,b:a}):a?(a=Math.max.apply(null,[a.r,a.g,a.b]),b=1,127<a&&
-(b=-1),a=Math.round(a+96*b),a={r:a,g:a,b:a}):a={r:191,g:191,b:191};return{color:s,"background-color":f?j(f):g,"decor-color":j(a)}}function m(b,j){this.x=b;this.y=j;this.reverse=function(){return new this.constructor(-1*this.x,-1*this.y)};this._length=null;this.getLength=function(){this._length||(this._length=Math.sqrt(Math.pow(this.x,2)+Math.pow(this.y,2)));return this._length};var s=function(b){return Math.round(b/Math.abs(b))};this.resizeTo=function(b){if(0===this.x&&0===this.y)this._length=0;else if(0===
-this.x)this._length=b,this.y=b*s(this.y);else if(0===this.y)this._length=b,this.x=b*s(this.x);else{var j=Math.abs(this.y/this.x),a=Math.sqrt(Math.pow(b,2)/(1+Math.pow(j,2))),j=j*a;this._length=b;this.x=a*s(this.x);this.y=j*s(this.y)}return this};this.angleTo=function(b){var j=this.getLength()*b.getLength();return 0===j?0:Math.acos(Math.min(Math.max((this.x*b.x+this.y*b.y)/j,-1),1))/Math.PI}}function k(b,j){this.x=b;this.y=j;this.getVectorToCoordinates=function(b,j){return new m(b-this.x,j-this.y)};
-this.getVectorFromCoordinates=function(b,j){return this.getVectorToCoordinates(b,j).reverse()};this.getVectorToPoint=function(b){return new m(b.x-this.x,b.y-this.y)};this.getVectorFromPoint=function(b){return this.getVectorToPoint(b).reverse()}}function t(b,j,a,g,c){this.data=b;this.context=j;if(b.length)for(var d=b.length,f,l,i=0;i<d;i++){f=b[i];l=f.x.length;a.call(j,f);for(var e=1;e<l;e++)g.call(j,f,e);c.call(j,f)}this.changed=function(){};this.startStrokeFn=a;this.addToStrokeFn=g;this.endStrokeFn=
-c;this.inStroke=!1;this._stroke=this._lastPoint=null;this.startStroke=function(b){if(b&&"number"==typeof b.x&&"number"==typeof b.y){this._stroke={x:[b.x],y:[b.y]};this.data.push(this._stroke);this._lastPoint=b;this.inStroke=!0;var j=this._stroke,a=this.startStrokeFn,g=this.context;setTimeout(function(){a.call(g,j)},3);return b}return null};this.addToStroke=function(b){if(this.inStroke&&"number"===typeof b.x&&"number"===typeof b.y&&4<Math.abs(b.x-this._lastPoint.x)+Math.abs(b.y-this._lastPoint.y)){var j=
-this._stroke.x.length;this._stroke.x.push(b.x);this._stroke.y.push(b.y);this._lastPoint=b;var a=this._stroke,g=this.addToStrokeFn,s=this.context;setTimeout(function(){g.call(s,a,j)},3);return b}return null};this.endStroke=function(){var b=this.inStroke;this.inStroke=!1;this._lastPoint=null;if(b){var j=this._stroke,a=this.endStrokeFn,g=this.context,s=this.changed;setTimeout(function(){a.call(g,j);s.call(g)},3);return!0}return null}}function r(b,j,a){var g=this.$parent=$(b),b=this.eventTokens={};this.events=
-new v(this);var c=$.fn[e]("globalEvents"),d={width:"ratio",height:"ratio",sizeRatio:4,color:"#000","background-color":"#fff","decor-color":"#eee",lineWidth:0,minFatFingerCompensation:-10,showUndoButton:!1,data:[]};$.extend(d,q(g));j&&$.extend(d,j);this.settings=d;for(var f in a)a.hasOwnProperty(f)&&a[f].call(this,f);this.events.publish(e+".initializing");this.$controlbarUpper=$('<div style="padding:0 !important;margin:0 !important;width: 100% !important; height: 0 !important;margin-top:-1em !important;margin-bottom:1em !important;"></div>').appendTo(g);
-this.isCanvasEmulator=!1;a=this.canvas=this.initializeCanvas(d);j=$(a);this.$controlbarLower=$('<div style="padding:0 !important;margin:0 !important;width: 100% !important; height: 0 !important;margin-top:-1.5em !important;margin-bottom:1.5em !important;"></div>').appendTo(g);this.canvasContext=a.getContext("2d");j.data(e+".this",this);g=(g=d.lineWidth)?g:Math.max(Math.round(a.width/400),2);d.lineWidth=g;this.lineCurveThreshold=3*d.lineWidth;d.cssclass&&""!=$.trim(d.cssclass)&&j.addClass(d.cssclass);
-this.fatFingerCompensation=0;var g=function(b){var j,a,g=function(g){g=g.changedTouches&&0<g.changedTouches.length?g.changedTouches[0]:g;return new k(Math.round(g.pageX+j),Math.round(g.pageY+a)+b.fatFingerCompensation)},d=new y(750,function(){b.dataEngine.endStroke()});this.drawEndHandler=function(j){try{j.preventDefault()}catch(a){}d.clear();b.dataEngine.endStroke()};this.drawStartHandler=function(c){c.preventDefault();var s=$(b.canvas).offset();j=-1*s.left;a=-1*s.top;b.dataEngine.startStroke(g(c));
-d.kick()};this.drawMoveHandler=function(j){j.preventDefault();b.dataEngine.inStroke&&(b.dataEngine.addToStroke(g(j)),d.kick())};return this}.call({},this),l=g.drawEndHandler,i=g.drawStartHandler,p=g.drawMoveHandler,h=this.canvas,j=$(h);this.isCanvasEmulator?(j.bind("mousemove."+e,p),j.bind("mouseup."+e,l),j.bind("mousedown."+e,i)):(h.ontouchstart=function(b){h.onmousedown=void 0;h.onmouseup=void 0;h.onmousemove=void 0;this.fatFingerCompensation=d.minFatFingerCompensation&&-3*d.lineWidth>d.minFatFingerCompensation?
--3*d.lineWidth:d.minFatFingerCompensation;i(b);h.ontouchend=l;h.ontouchstart=i;h.ontouchmove=p},h.onmousedown=function(b){h.ontouchstart=void 0;h.ontouchend=void 0;h.ontouchmove=void 0;i(b);h.onmousedown=i;h.onmouseup=l;h.onmousemove=p});b[e+".windowmouseup"]=c.subscribe(e+".windowmouseup",g.drawEndHandler);this.events.publish(e+".attachingEventHandlers");var n=this,b=d.width.toString(10),w=e;if("ratio"===b||"%"===b.split("")[b.length-1])this.eventTokens[w+".parentresized"]=c.subscribe(w+".parentresized",
-function(b,j,a){return function(){var g=j.width();if(g!==a){for(var d in b)b.hasOwnProperty(d)&&(c.unsubscribe(b[d]),delete b[d]);var s=n.settings;n.$parent.children().remove();for(d in n)n.hasOwnProperty(d)&&delete n[d];d=s.data;var g=1*g/a,f=[],l,i,e,h,k,p;i=0;for(e=d.length;i<e;i++){p=d[i];l={x:[],y:[]};h=0;for(k=p.x.length;h<k;h++)l.x.push(p.x[h]*g),l.y.push(p.y[h]*g);f.push(l)}s.data=f;j[w](s)}}}(this.eventTokens,this.$parent,this.$parent.width(),1*this.canvas.width/this.canvas.height));this.resetCanvas(d.data);
-this.events.publish(e+".initialized");return this}var e="jSignature",y=function(b,j){var a;this.kick=function(){clearTimeout(a);a=setTimeout(j,b)};this.clear=function(){clearTimeout(a)};return this},v=function(b){this.topics={};this.context=b?b:this;this.publish=function(b,a,g,d){if(this.topics[b]){var c=this.topics[b],f=Array.prototype.slice.call(arguments,1),l=[],i,e,h,p;e=0;for(h=c.length;e<h;e++)p=c[e],i=p[0],p[1]&&(p[0]=function(){},l.push(e)),i.apply(this.context,f);e=0;for(h=l.length;e<h;e++)c.splice(l[e],
-1)}};this.subscribe=function(b,a,g){this.topics[b]?this.topics[b].push([a,g]):this.topics[b]=[[a,g]];return{topic:b,callback:a}};this.unsubscribe=function(b){if(this.topics[b.topic])for(var a=this.topics[b.topic],g=0,d=a.length;g<d;g++)a[g][0]===b.callback&&a.splice(g,1)}},z=function(b){var a=this.canvasContext,d=b.x[0],b=b.y[0],g=this.settings.lineWidth,c=a.fillStyle;a.fillStyle=a.strokeStyle;a.fillRect(d+g/-2,b+g/-2,g,g);a.fillStyle=c},u=function(b,a){var d=new k(b.x[a-1],b.y[a-1]),g=new k(b.x[a],
-b.y[a]),c=d.getVectorToPoint(g);if(1<a){var f=new k(b.x[a-2],b.y[a-2]),l=f.getVectorToPoint(d),i;if(l.getLength()>this.lineCurveThreshold){i=2<a?(new k(b.x[a-3],b.y[a-3])).getVectorToPoint(f):new m(0,0);var e=0.35*l.getLength(),h=l.angleTo(i.reverse()),p=c.angleTo(l.reverse());i=(new m(i.x+l.x,i.y+l.y)).resizeTo(Math.max(0.05,h)*e);var n=(new m(l.x+c.x,l.y+c.y)).reverse().resizeTo(Math.max(0.05,p)*e),l=this.canvasContext,e=f.x,p=f.y,h=d.x,w=d.y,A=f.x+i.x,f=f.y+i.y;i=d.x+n.x;n=d.y+n.y;l.beginPath();
-l.moveTo(e,p);l.bezierCurveTo(A,f,i,n,h,w);l.stroke()}}c.getLength()<=this.lineCurveThreshold&&(c=this.canvasContext,f=d.x,d=d.y,i=g.x,g=g.y,c.beginPath(),c.moveTo(f,d),c.lineTo(i,g),c.stroke())},x=function(b){var a=b.x.length-1;if(0<a){var d=new k(b.x[a],b.y[a]),g=new k(b.x[a-1],b.y[a-1]),c=g.getVectorToPoint(d);if(c.getLength()>this.lineCurveThreshold){if(1<a){var b=(new k(b.x[a-2],b.y[a-2])).getVectorToPoint(g),f=(new m(b.x+c.x,b.y+c.y)).resizeTo(c.getLength()/2),c=this.canvasContext,b=g.x,a=g.y,
-l=d.x,i=d.y,e=g.x+f.x,g=g.y+f.y,f=d.x,d=d.y;c.beginPath();c.moveTo(b,a);c.bezierCurveTo(e,g,f,d,l,i)}else c=this.canvasContext,b=g.x,g=g.y,a=d.x,d=d.y,c.beginPath(),c.moveTo(b,g),c.lineTo(a,d);c.stroke()}}};r.prototype.resetCanvas=function(b){var a=this.canvas,d=this.settings,c=this.canvasContext,f=this.isCanvasEmulator,l=a.width,i=a.height;c.clearRect(0,0,l+30,i+30);c.shadowColor=c.fillStyle=d["background-color"];f&&c.fillRect(0,0,l+30,i+30);c.lineWidth=Math.ceil(parseInt(d.lineWidth,10));c.lineCap=
-c.lineJoin="round";c.strokeStyle=d["decor-color"];c.shadowOffsetX=0;c.shadowOffsetY=0;var h=Math.round(i/5),p=1.5*h,k=i-h,l=l-1.5*h,i=i-h;c.beginPath();c.moveTo(p,k);c.lineTo(l,i);c.stroke();c.strokeStyle=d.color;f||(c.shadowColor=c.strokeStyle,c.shadowOffsetX=0.5*c.lineWidth,c.shadowOffsetY=-0.6*c.lineWidth,c.shadowBlur=0);b||(b=[]);c=this.dataEngine=new t(b,this,z,u,x);d.data=b;$(a).data(e+".data",b).data(e+".settings",d);var n=this.$parent,w=this.events,A=e;c.changed=function(){w.publish(A+".change");
-n.trigger("change")};c.changed();return!0};r.prototype.initializeCanvas=function(b){var a=document.createElement("canvas"),c=$(a);b.width===b.height&&"ratio"===b.height&&(b.width="100%");c.css("margin",0).css("padding",0).css("border","none").css("height","ratio"===b.height||!b.height?1:b.height.toString(10)).css("width","ratio"===b.width||!b.width?1:b.width.toString(10));c.appendTo(this.$parent);"ratio"===b.height?c.css("height",Math.round(c.width()/b.sizeRatio)):"ratio"===b.width&&c.css("width",
-Math.round(c.height()*b.sizeRatio));c.addClass(e);a.width=c.width();a.height=c.height();b=a;if(b.getContext)b=!1;else{var c=b.ownerDocument.parentWindow,d=c.FlashCanvas?b.ownerDocument.parentWindow.FlashCanvas:"undefined"===typeof FlashCanvas?void 0:FlashCanvas;if(d){b=d.initElement(b);d=1;c&&(c.screen&&c.screen.deviceXDPI&&c.screen.logicalXDPI)&&(d=1*c.screen.deviceXDPI/c.screen.logicalXDPI);if(1!==d)try{$(b).children("object").get(0).resize(Math.ceil(b.width*d),Math.ceil(b.height*d)),b.getContext("2d").scale(d,
-d)}catch(f){}b=!0}else throw Error("Canvas element does not support 2d context. jSignature cannot proceed.");}this.isCanvasEmulator=b;a.onselectstart=function(b){b&&b.preventDefault&&b.preventDefault();b&&b.stopPropagation&&b.stopPropagation();return!1};return a};var p=window,h=function(b,a){var c=new Image,d=this;c.onload=function(){d.getContext("2d").drawImage(c,0,0,c.width<d.width?c.width:d.width,c.height<d.height?c.height:d.height)};c.src="data:"+a+","+b},a=function(b){this.find("canvas."+e).add(this.filter("canvas."+
-e)).data(e+".this").resetCanvas(b);return this},f=function(b,a){if(void 0===a&&("string"===typeof b&&"data:"===b.substr(0,5))&&(a=b.slice(5).split(",")[0],b=b.slice(6+a.length),a===b))return;var c=this.find("canvas."+e).add(this.filter("canvas."+e));if(n.hasOwnProperty(a))0!==c.length&&n[a].call(c[0],b,a,function(b){return function(){return b.resetCanvas.apply(b,arguments)}}(c.data(e+".this")));else throw Error(e+" is unable to find import plugin with for format '"+String(a)+"'");return this},d=new v,
-c=e,l,i=function(){d.publish(c+".parentresized")};$(p).bind("resize."+c,function(){l&&clearTimeout(l);l=setTimeout(i,500)}).bind("mouseup."+c,function(){d.publish(c+".windowmouseup")});var w={},A={"default":function(){return this.toDataURL()},"native":function(b){return b},image:function(){var b=this.toDataURL();if("string"===typeof b&&4<b.length&&"data:"===b.slice(0,5)&&-1!==b.indexOf(",")){var a=b.indexOf(",");return[b.slice(5,a),b.substr(a+1)]}return[]}},n={"native":function(b,a,c){c(b)},image:h,
-"image/png;base64":h,"image/jpeg;base64":h,"image/jpg;base64":h},B={"export":A,"import":n,instance:w},C={init:function(b){return this.each(function(){var a,c=!1;for(a=this.parentNode;a&&!c;)c=a.body,a=a.parentNode;!c||new r(this,b,w)})},getSettings:function(){return this.find("canvas."+e).add(this.filter("canvas."+e)).data(e+".this").settings},clear:a,reset:a,addPlugin:function(b,a,c){B.hasOwnProperty(b)&&(B[b][a]=c);return this},listPlugins:function(b){var a=[];if(B.hasOwnProperty(b)){var b=B[b],
-c;for(c in b)b.hasOwnProperty(c)&&a.push(c)}return a},getData:function(b){var a=this.find("canvas."+e).add(this.filter("canvas."+e));void 0===b&&(b="default");if(0!==a.length&&A.hasOwnProperty(b))return A[b].call(a.get(0),a.data(e+".data"))},importData:f,setData:f,globalEvents:function(){return d},events:function(){return this.find("canvas."+e).add(this.filter("canvas."+e)).data(e+".this").events}};$.fn[e]=function(b){if(!b||"object"===typeof b)return C.init.apply(this,arguments);if("string"===typeof b&&
-C[b])return C[b].apply(this,Array.prototype.slice.call(arguments,1));$.error("Method "+String(b)+" does not exist on jQuery."+e)}})();
-(function(){$.fn.jSignature("addPlugin","instance","UndoButton",function(q){this.events.subscribe("jSignature.attachingEventHandlers",function(){if(this.settings[q]){var m=this.settings[q];"function"!==typeof m&&(m=function(){var e=$('<input type="button" value="Undo last stroke" style="position:absolute;display:none;margin:0 !important;top:auto" />').appendTo(this.$controlbarLower),k=e.width();e.css("left",Math.round((this.canvas.width-k)/2));k!==e.width()&&e.width(k);return e});var k=m.call(this),
-t=this;t.events.subscribe("jSignature.change",function(){t.dataEngine.data.length?k.show():k.hide()});var r=this,e=(this.events.topics.hasOwnProperty("jSignature.undo")?q:"jSignature")+".undo";k.bind("click",function(){r.events.publish(e)});r.events.subscribe(e,function(){var e=r.dataEngine.data;e.length&&(e.pop(),r.resetCanvas(e))})}})})})();
-(function(){for(var q={},m={},k="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWX".split(""),t=k.length/2,r=t-1;-1<r;r--)q[k[r]]=k[r+t],m[k[r+t]]=k[r];var e=function(e){for(var e=e.split(""),h=e.length,a=1;a<h;a++)e[a]=q[e[a]];return e.join("")},y=function(k){for(var h=[],a=0,f=1,d=k.length,c,l,i=0;i<d;i++)c=Math.round(k[i]),l=c-a,a=c,0>l&&0<f?(f=-1,h.push("Z")):0<l&&0>f&&(f=1,h.push("Y")),c=Math.abs(l),c>=t?h.push(e(c.toString(t))):h.push(c.toString(t));return h.join("")},v=function(e){for(var h=
-[],e=e.split(""),a=e.length,f,d=1,c=[],l=0,i=0;i<a;i++)f=e[i],f in q||"Z"===f||"Y"===f?(0!==c.length&&(c=parseInt(c.join(""),t)*d+l,h.push(c),l=c),"Z"===f?(d=-1,c=[]):"Y"===f?(d=1,c=[]):c=[f]):c.push(m[f]);h.push(parseInt(c.join(""),t)*d+l);return h},z=function(e){for(var h=[],a=e.length,f,d=0;d<a;d++)f=e[d],h.push(y(f.x)),h.push(y(f.y));return h.join("_")},u=function(e){for(var h=[],e=e.split("_"),a=e.length/2,f=0;f<a;f++)h.push({x:v(e[2*f]),y:v(e[2*f+1])});return h},k=function(e){return["image/jsignature;base30",
-z(e)]},r=function(e,h,a){"string"===typeof e&&("image/jsignature;base30"===e.substring(0,23).toLowerCase()&&(e=e.substring(24)),a(u(e)))};if(null==this.jQuery)throw Error("We need jQuery for some of the functionality. jQuery is not detected. Failing to initialize...");var x=this.jQuery.fn.jSignature;x("addPlugin","export","base30",k);x("addPlugin","export","image/jsignature;base30",k);x("addPlugin","import","base30",r);x("addPlugin","import","image/jsignature;base30",r);this.jSignatureDebug&&(this.jSignatureDebug.base30=
-{remapTailChars:e,compressstrokeleg:y,uncompressstrokeleg:v,compressstrokes:z,uncompressstrokes:u,charmap:q})}).call("undefined"!==typeof window?window:this);
-(function(){function q(a,f){this.x=a;this.y=f;this.reverse=function(){return new this.constructor(-1*this.x,-1*this.y)};this._length=null;this.getLength=function(){this._length||(this._length=Math.sqrt(Math.pow(this.x,2)+Math.pow(this.y,2)));return this._length};var d=function(a){return Math.round(a/Math.abs(a))};this.resizeTo=function(a){if(0===this.x&&0===this.y)this._length=0;else if(0===this.x)this._length=a,this.y=a*d(this.y);else if(0===this.y)this._length=a,this.x=a*d(this.x);else{var f=Math.abs(this.y/
-this.x),e=Math.sqrt(Math.pow(a,2)/(1+Math.pow(f,2))),f=f*e;this._length=a;this.x=e*d(this.x);this.y=f*d(this.y)}return this};this.angleTo=function(a){var d=this.getLength()*a.getLength();return 0===d?0:Math.acos(Math.min(Math.max((this.x*a.x+this.y*a.y)/d,-1),1))/Math.PI}}function m(a,f){this.x=a;this.y=f;this.getVectorToCoordinates=function(a,c){return new q(a-this.x,c-this.y)};this.getVectorFromCoordinates=function(a,c){return this.getVectorToCoordinates(a,c).reverse()};this.getVectorToPoint=function(a){return new q(a.x-
-this.x,a.y-this.y)};this.getVectorFromPoint=function(a){return this.getVectorToPoint(a).reverse()}}function k(a,f){var d=Math.pow(10,f);return Math.round(a*d)/d}function t(a,f,d){var f=f+1,c=new m(a.x[f-1],a.y[f-1]),e=new m(a.x[f],a.y[f]),e=c.getVectorToPoint(e),i=new m(a.x[f-2],a.y[f-2]),c=i.getVectorToPoint(c);return c.getLength()>d?(d=2<f?(new m(a.x[f-3],a.y[f-3])).getVectorToPoint(i):new q(0,0),a=0.35*c.getLength(),i=c.angleTo(d.reverse()),f=e.angleTo(c.reverse()),d=(new q(d.x+c.x,d.y+c.y)).resizeTo(Math.max(0.05,
-i)*a),e=(new q(c.x+e.x,c.y+e.y)).reverse().resizeTo(Math.max(0.05,f)*a),e=new q(c.x+e.x,c.y+e.y),["c",k(d.x,2),k(d.y,2),k(e.x,2),k(e.y,2),k(c.x,2),k(c.y,2)]):["l",k(c.x,2),k(c.y,2)]}function r(a,f){var d=a.x.length-1,c=new m(a.x[d],a.y[d]),e=new m(a.x[d-1],a.y[d-1]),c=e.getVectorToPoint(c);if(1<d&&c.getLength()>f){var d=(new m(a.x[d-2],a.y[d-2])).getVectorToPoint(e),e=c.angleTo(d.reverse()),i=0.35*c.getLength(),d=(new q(d.x+c.x,d.y+c.y)).resizeTo(Math.max(0.05,e)*i);return["c",k(d.x,2),k(d.y,2),k(c.x,
-2),k(c.y,2),k(c.x,2),k(c.y,2)]}return["l",k(c.x,2),k(c.y,2)]}function e(a,e,d){for(var e=["M",k(a.x[0]-e,2),k(a.y[0]-d,2)],d=1,c=a.x.length-1;d<c;d++)e.push.apply(e,t(a,d,1));0<c?e.push.apply(e,r(a,d,1)):0===c&&e.push.apply(e,["l",1,1]);return e.join(" ")}function y(a){var f=['<?xml version="1.0" encoding="UTF-8" standalone="no"?>','<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">'],d,c=a.length,l,i=[],h=[],k=l=d=0,n=0,p=[];if(0!==c){for(d=0;d<c;d++){k=
-a[d];n=[];l={x:[],y:[]};for(var m=void 0,b=void 0,m=0,b=k.x.length;m<b;m++)n.push({x:k.x[m],y:k.y[m]});n=simplify(n,0.7,!0);m=0;for(b=n.length;m<b;m++)l.x.push(n[m].x),l.y.push(n[m].y);p.push(l);i=i.concat(l.x);h=h.concat(l.y)}a=Math.min.apply(null,i)-1;c=Math.max.apply(null,i)+1;i=Math.min.apply(null,h)-1;h=Math.max.apply(null,h)+1;k=0>a?0:a;n=0>i?0:i;d=c-a;l=h-i}f.push('<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="'+d.toString()+'" height="'+l.toString()+'">');d=0;for(c=p.length;d<
-c;d++)l=p[d],f.push('<path fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="'+e(l,k,n)+'"/>');f.push("</svg>");return f.join("")}function v(a){return[p,y(a)]}function z(a){return[h,x(y(a))]}var u=window;"use strict";("undefined"!=typeof exports?exports:u).simplify=function(a,e,d){e=void 0!==e?e*e:1;if(!d){for(var c=a.length,l,i=a[0],h=[i],d=1;d<c;d++){l=a[d];var k=l.x-i.x,n=l.y-i.y;k*k+n*n>e&&(h.push(l),i=l)}a=(i!==l&&h.push(l),h)}l=a;var d=l.length,
-c=new ("undefined"!=typeof Uint8Array?Uint8Array:Array)(d),i=0,h=d-1,m,p,b=[],j=[],s=[];for(c[i]=c[h]=1;h;){n=0;for(k=i+1;k<h;k++){m=l[k];var g=l[i],r=l[h],q=g.x,t=g.y,g=r.x-q,u=r.y-t,v=void 0;if(0!==g||0!==u)v=((m.x-q)*g+(m.y-t)*u)/(g*g+u*u),1<v?(q=r.x,t=r.y):0<v&&(q+=g*v,t+=u*v);m=(g=m.x-q,u=m.y-t,g*g+u*u);m>n&&(p=k,n=m)}n>e&&(c[p]=1,b.push(i),j.push(p),b.push(p),j.push(h));i=b.pop();h=j.pop()}for(k=0;k<d;k++)c[k]&&s.push(l[k]);return a=s,a};if("function"!==typeof x)var x=function(a){var e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".split(""),
-d,c,h,i,k=0,m=0,n="",n=[];do d=a.charCodeAt(k++),c=a.charCodeAt(k++),h=a.charCodeAt(k++),i=d<<16|c<<8|h,d=i>>18&63,c=i>>12&63,h=i>>6&63,i&=63,n[m++]=e[d]+e[c]+e[h]+e[i];while(k<a.length);n=n.join("");a=a.length%3;return(a?n.slice(0,a-3):n)+"===".slice(a||3)};var p="image/svg+xml",h="image/svg+xml;base64";if("undefined"===typeof $)throw Error("We need jQuery for some of the functionality. jQuery is not detected. Failing to initialize...");u=$.fn.jSignature;u("addPlugin","export","svg",v);u("addPlugin",
-"export",p,v);u("addPlugin","export","svgbase64",z);u("addPlugin","export",h,z)})(); \ No newline at end of file
diff --git a/addons/website_quote/views/website_quotation.xml b/addons/website_quote/views/website_quotation.xml
index 5e22bf1e5ee..02e86be9bb4 100644
--- a/addons/website_quote/views/website_quotation.xml
+++ b/addons/website_quote/views/website_quotation.xml
@@ -4,8 +4,8 @@
<template id="assets_frontend" inherit_id="website.assets_frontend" name="Website Quote frontend assets">
<xpath expr="." position="inside">
+ <script type="text/javascript" src="/website/static/lib/jSignature/jSignatureCustom.js"></script>
<script type="text/javascript" src="/website_quote/static/src/js/website_quotation.js"></script>
- <script type="text/javascript" src="/website_quote/static/lib/jSignature/jSignature.min.js"></script>
<link rel='stylesheet' href='/website_quote/static/src/css/website_quotation.css'/>
</xpath>
</template>