master
parent
954dd83acf
commit
cbcc7f1f07
@ -0,0 +1,234 @@
|
||||
/** @preserve
|
||||
jSignature v2 jSignature's custom "base30" format export and import plugins.
|
||||
|
||||
*/
|
||||
/**
|
||||
Copyright (c) 2011 Willow Systems Corp http://willow-systems.com
|
||||
MIT License <http://www.opensource.org/licenses/mit-license.php>
|
||||
*/
|
||||
|
||||
;(function(){
|
||||
|
||||
var chunkSeparator = '_'
|
||||
, charmap = {} // {'1':'g','2':'h','3':'i','4':'j','5':'k','6':'l','7':'m','8':'n','9':'o','a':'p','b':'q','c':'r','d':'s','e':'t','f':'u','0':'v'}
|
||||
, charmap_reverse = {} // will be filled by 'uncompress*" function
|
||||
// need to split below for IE7 (possibly others), which does not understand string[position] it seems (returns undefined)
|
||||
, allchars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWX'.split('')
|
||||
, bitness = allchars.length / 2
|
||||
, minus = 'Z'
|
||||
, plus = 'Y'
|
||||
|
||||
for(var i = bitness-1; i > -1; i--){
|
||||
charmap[allchars[i]] = allchars[i+bitness]
|
||||
charmap_reverse[allchars[i+bitness]] = allchars[i]
|
||||
}
|
||||
var remapTailChars = function(number){
|
||||
// for any given number as string, returning string with trailing chars remapped something like so:
|
||||
// '345' -> '3de'
|
||||
var chars = number.split('')
|
||||
, l = chars.length
|
||||
// we are skipping first char. standard hex number char = delimiter
|
||||
for (var i = 1; i < l; i++ ){
|
||||
chars[i] = charmap[chars[i]]
|
||||
}
|
||||
return chars.join('')
|
||||
}
|
||||
, compressstrokeleg = function(data){
|
||||
// we convert half-stroke (only 'x' series or only 'y' series of numbers)
|
||||
// data is like this:
|
||||
// [517,516,514,513,513,513,514,516,519,524,529,537,541,543,544,544,539,536]
|
||||
// that is converted into this:
|
||||
// "5agm12100p1235584210m53"
|
||||
// each number in the chain is converted such:
|
||||
// - find diff from previous number
|
||||
// - first significant digit is kept as digit char. digit char = start of new number.
|
||||
// - consecutive numbers are mapped to letters, where 1 to 9 are A to I, 0 is O
|
||||
// Sign changes are denoted by "P" - plus, "M" for minus.
|
||||
var answer = []
|
||||
, lastwhole = 0
|
||||
, last = 0
|
||||
, lastpolarity = 1
|
||||
, l = data.length
|
||||
, nwhole, n, absn
|
||||
|
||||
for(var i = 0; i < l; i++){
|
||||
// we start with whole coordinates for each point
|
||||
// coords are converted into series of vectors:
|
||||
// [512, 514, 520]
|
||||
// [512, +2, +6]
|
||||
nwhole = Math.round(data[i])
|
||||
n = nwhole - lastwhole
|
||||
lastwhole = nwhole
|
||||
|
||||
// inserting sign change when needed.
|
||||
if (n < 0 && lastpolarity > 0) {
|
||||
lastpolarity = -1
|
||||
answer.push(minus)
|
||||
}
|
||||
else if (n > 0 && lastpolarity < 0) {
|
||||
lastpolarity = 1
|
||||
answer.push(plus)
|
||||
}
|
||||
|
||||
// since we have dealt with sign. let's absolute the value.
|
||||
absn = Math.abs(n)
|
||||
// adding number to list We convert these to Hex before storing on the string.
|
||||
if (absn >= bitness) {
|
||||
answer.push(remapTailChars(absn.toString(bitness)))
|
||||
} else {
|
||||
answer.push(absn.toString(bitness))
|
||||
}
|
||||
}
|
||||
return answer.join('')
|
||||
}
|
||||
, uncompressstrokeleg = function(datastring){
|
||||
// we convert half-stroke (only 'x' series or only 'y' series of numbers)
|
||||
// datastring like this:
|
||||
// "5agm12100p1235584210m53"
|
||||
// is converted into this:
|
||||
// [517,516,514,513,513,513,514,516,519,524,529,537,541,543,544,544,539,536]
|
||||
// each number in the chain is converted such:
|
||||
// - digit char = start of new whole number. Alpha chars except "p","m" are numbers in hiding.
|
||||
// These consecutive digist expressed as alphas mapped back to digit char.
|
||||
// resurrected number is the diff between this point and prior coord.
|
||||
// - running polaritiy is attached to the number.
|
||||
// - we undiff (signed number + prior coord) the number.
|
||||
// - if char 'm','p', flip running polarity
|
||||
var answer = []
|
||||
, chars = datastring.split('')
|
||||
, l = chars.length
|
||||
, ch
|
||||
, polarity = 1
|
||||
, partial = []
|
||||
, preprewhole = 0
|
||||
, prewhole
|
||||
for(var i = 0; i < l; i++){
|
||||
ch = chars[i]
|
||||
if (ch in charmap || ch === minus || ch === plus){
|
||||
// this is new number - start of a new whole number.
|
||||
// before we can deal with it, we need to flush out what we already
|
||||
// parsed out from string, but keep in limbo, waiting for this sign
|
||||
// that prior number is done.
|
||||
// we deal with 3 numbers here:
|
||||
// 1. start of this number - a diff from previous number to
|
||||
// whole, new number, which we cannot do anything with cause
|
||||
// we don't know its ending yet.
|
||||
// 2. number that we now realize have just finished parsing = prewhole
|
||||
// 3. number we keep around that came before prewhole = preprewhole
|
||||
|
||||
if (partial.length !== 0) {
|
||||
// yep, we have some number parts in there.
|
||||
prewhole = parseInt( partial.join(''), bitness) * polarity + preprewhole
|
||||
answer.push( prewhole )
|
||||
preprewhole = prewhole
|
||||
}
|
||||
|
||||
if (ch === minus){
|
||||
polarity = -1
|
||||
partial = []
|
||||
} else if (ch === plus){
|
||||
polarity = 1
|
||||
partial = []
|
||||
} else {
|
||||
// now, let's start collecting parts for the new number:
|
||||
partial = [ch]
|
||||
}
|
||||
} else /* alphas replacing digits */ {
|
||||
// more parts for the new number
|
||||
partial.push(charmap_reverse[ch])
|
||||
}
|
||||
}
|
||||
// we always will have something stuck in partial
|
||||
// because we don't have closing delimiter
|
||||
answer.push( parseInt( partial.join(''), bitness ) * polarity + preprewhole )
|
||||
|
||||
return answer
|
||||
}
|
||||
, compressstrokes = function(data){
|
||||
var answer = []
|
||||
, l = data.length
|
||||
, stroke
|
||||
for(var i = 0; i < l; i++){
|
||||
stroke = data[i]
|
||||
answer.push(compressstrokeleg(stroke.x))
|
||||
answer.push(compressstrokeleg(stroke.y))
|
||||
}
|
||||
return answer.join(chunkSeparator)
|
||||
}
|
||||
, uncompressstrokes = function(datastring){
|
||||
var data = []
|
||||
, chunks = datastring.split(chunkSeparator)
|
||||
, l = chunks.length / 2
|
||||
for (var i = 0; i < l; i++){
|
||||
data.push({
|
||||
'x':uncompressstrokeleg(chunks[i*2])
|
||||
, 'y':uncompressstrokeleg(chunks[i*2+1])
|
||||
})
|
||||
}
|
||||
return data
|
||||
}
|
||||
, acceptedformat = 'image/jsignature;base30'
|
||||
, pluginCompressor = function(data){
|
||||
return [acceptedformat , compressstrokes(data)]
|
||||
}
|
||||
, pluginDecompressor = function(data, formattype, importcallable){
|
||||
if (typeof data !== 'string') return
|
||||
if (data.substring(0, acceptedformat.length).toLowerCase() === acceptedformat) {
|
||||
data = data.substring(acceptedformat.length + 1) // chopping off "," there
|
||||
}
|
||||
importcallable( uncompressstrokes(data) )
|
||||
}
|
||||
, Initializer = function($){
|
||||
var mothership = $.fn['jSignature']
|
||||
mothership(
|
||||
'addPlugin'
|
||||
,'export'
|
||||
,'base30' // alias
|
||||
,pluginCompressor
|
||||
)
|
||||
mothership(
|
||||
'addPlugin'
|
||||
,'export'
|
||||
,acceptedformat // full name
|
||||
,pluginCompressor
|
||||
)
|
||||
mothership(
|
||||
'addPlugin'
|
||||
,'import'
|
||||
,'base30' // alias
|
||||
,pluginDecompressor
|
||||
)
|
||||
mothership(
|
||||
'addPlugin'
|
||||
,'import'
|
||||
,acceptedformat // full name
|
||||
,pluginDecompressor
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// //Because plugins are minified together with jSignature, multiple defines per (minified) file blow up and dont make sense
|
||||
// //Need to revisit this later.
|
||||
|
||||
// if ( typeof define === "function" && define.amd != null) {
|
||||
// // AMD-loader compatible resource declaration
|
||||
// // you need to call this one with jQuery as argument.
|
||||
// define(function(){return Initializer} )
|
||||
// } else {
|
||||
// global-polluting outcome.
|
||||
if(this.jQuery == null) {throw new Error("We need jQuery for some of the functionality. jQuery is not detected. Failing to initialize...")}
|
||||
Initializer(this.jQuery)
|
||||
// }
|
||||
|
||||
if (this.jSignatureDebug) {
|
||||
this.jSignatureDebug['base30'] = {
|
||||
'remapTailChars':remapTailChars
|
||||
, 'compressstrokeleg':compressstrokeleg
|
||||
, 'uncompressstrokeleg':uncompressstrokeleg
|
||||
, 'compressstrokes':compressstrokes
|
||||
, 'uncompressstrokes':uncompressstrokes
|
||||
, 'charmap': charmap
|
||||
}
|
||||
}
|
||||
|
||||
}).call(typeof window !== 'undefined'? window : this);
|
@ -0,0 +1,519 @@
|
||||
/** @license
|
||||
jSignature v2 SVG export plugin.
|
||||
|
||||
*/
|
||||
/**
|
||||
Copyright (c) 2012 Willow Systems Corp http://willow-systems.com
|
||||
MIT License <http://www.opensource.org/licenses/mit-license.php>
|
||||
*/
|
||||
|
||||
;(function(){
|
||||
'use strict'
|
||||
|
||||
/** @preserve
|
||||
Simplify.js BSD
|
||||
(c) 2012, Vladimir Agafonkin
|
||||
mourner.github.com/simplify-js
|
||||
|
||||
*/
|
||||
;(function(a,b){function c(a,b){var c=a.x-b.x,d=a.y-b.y;return c*c+d*d}function d(a,b,c){var d=b.x,e=b.y,f=c.x-d,g=c.y-e,h;if(f!==0||g!==0)h=((a.x-d)*f+(a.y-e)*g)/(f*f+g*g),h>1?(d=c.x,e=c.y):h>0&&(d+=f*h,e+=g*h);return f=a.x-d,g=a.y-e,f*f+g*g}function e(a,b){var d,e=a.length,f,g=a[0],h=[g];for(d=1;d<e;d++)f=a[d],c(f,g)>b&&(h.push(f),g=f);return g!==f&&h.push(f),h}function f(a,c){var e=a.length,f=typeof Uint8Array!=b+""?Uint8Array:Array,g=new f(e),h=0,i=e-1,j,k,l,m,n=[],o=[],p=[];g[h]=g[i]=1;while(i){k=0;for(j=h+1;j<i;j++)l=d(a[j],a[h],a[i]),l>k&&(m=j,k=l);k>c&&(g[m]=1,n.push(h),o.push(m),n.push(m),o.push(i)),h=n.pop(),i=o.pop()}for(j=0;j<e;j++)g[j]&&p.push(a[j]);return p}"use strict";var g=a;g.simplify=function(a,c,d){var g=c!==b?c*c:1;return d||(a=e(a,g)),a=f(a,g),a}})(window);
|
||||
|
||||
|
||||
/**
|
||||
Vector class. Allows us to simplify representation and manipulation of coordinate-pair
|
||||
representing shift against (0, 0)
|
||||
|
||||
@public
|
||||
@class
|
||||
@param
|
||||
@returns {Type}
|
||||
*/
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Allows one to round a number to arbitrary precision.
|
||||
Math.round() rounds to whole only.
|
||||
Number.toFixed(precision) returns a string.
|
||||
I need float to float, but with arbitrary precision, hence:
|
||||
|
||||
@public
|
||||
@function
|
||||
@param number {Number}
|
||||
@param position {Number} number of digits right of decimal point to keep. If negative, rounding to the left of decimal.
|
||||
@returns {Type}
|
||||
*/
|
||||
function round (number, position){
|
||||
var tmp = Math.pow(10, position)
|
||||
return Math.round( number * tmp ) / tmp
|
||||
}
|
||||
|
||||
// /**
|
||||
// * This is a simple, points-to-lines (not curves) renderer.
|
||||
// * Keeping it around so we can activate it from time to time and see
|
||||
// * if smoothing logic is off much.
|
||||
// * @public
|
||||
// * @function
|
||||
// * @returns {String} Like so "l 1 2 3 5' with stroke as long line chain.
|
||||
// */
|
||||
// function compressstroke(stroke, shiftx, shifty){
|
||||
// // we combine strokes data into string like this:
|
||||
// // 'M 53 7 l 1 2 3 4 -5 -6 5 -6'
|
||||
// // see SVG documentation for Path element's 'd' argument.
|
||||
// var lastx = stroke.x[0]
|
||||
// , lasty = stroke.y[0]
|
||||
// , i
|
||||
// , l = stroke.x.length
|
||||
// , answer = ['M', lastx - shiftx, lasty - shifty, 'l']
|
||||
//
|
||||
// if (l === 1){
|
||||
// // meaning this was just a DOT, not a stroke.
|
||||
// // instead of creating a circle, we just create a short line
|
||||
// answer.concat(1, -1)
|
||||
// } else {
|
||||
// for(i = 1; i < l; i++){
|
||||
// answer = answer.concat(stroke.x[i] - lastx, stroke.y[i] - lasty)
|
||||
// lastx = stroke.x[i]
|
||||
// lasty = stroke.y[i]
|
||||
// }
|
||||
// }
|
||||
// return answer.join(' ')
|
||||
// }
|
||||
|
||||
function segmentToCurve(stroke, positionInStroke, lineCurveThreshold){
|
||||
'use strict'
|
||||
// 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
|
||||
|
||||
// Again, we can only derive curve between points positionInStroke-1 and positionInStroke
|
||||
// Thus, since we can only draw a line if we know one point ahead of it, we need to shift our focus one point ahead.
|
||||
positionInStroke += 1
|
||||
// Let's hope the code that calls us knows we do that and does not call us with positionInStroke = index of last point.
|
||||
|
||||
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 only PREVIOUS line segment - BC
|
||||
|
||||
// So, let's start with BC curve.
|
||||
// if there is only 2 points in stroke array (C, D), we don't have "history" long enough to have point B, let alone point A.
|
||||
// so positionInStroke should start with 2, ie
|
||||
// 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
|
||||
, rounding = 2
|
||||
|
||||
if ( BCvector.getLength() > lineCurveThreshold ){
|
||||
// Yey! Pretty curves, here we come!
|
||||
if(positionInStroke > 2) {
|
||||
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())
|
||||
, BtoCP1vector = new Vector(ABvector.x + BCvector.x, ABvector.y + BCvector.y).resizeTo(
|
||||
Math.max(minlenfraction, ABCangle) * maxlen
|
||||
)
|
||||
, CtoCP2vector = (new Vector(BCvector.x + CDvector.x, BCvector.y + CDvector.y)).reverse().resizeTo(
|
||||
Math.max(minlenfraction, BCDangle) * maxlen
|
||||
)
|
||||
, BtoCP2vector = new Vector(BCvector.x + CtoCP2vector.x, BCvector.y + CtoCP2vector.y)
|
||||
|
||||
// returing curve for BC segment
|
||||
// all coords are vectors against Bpoint
|
||||
return [
|
||||
'c' // bezier curve
|
||||
, round( BtoCP1vector.x, rounding )
|
||||
, round( BtoCP1vector.y, rounding )
|
||||
, round( BtoCP2vector.x, rounding )
|
||||
, round( BtoCP2vector.y, rounding )
|
||||
, round( BCvector.x, rounding )
|
||||
, round( BCvector.y, rounding )
|
||||
]
|
||||
} else {
|
||||
return [
|
||||
'l' // line
|
||||
, round( BCvector.x, rounding )
|
||||
, round( BCvector.y, rounding )
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
function lastSegmentToCurve(stroke, lineCurveThreshold){
|
||||
'use strict'
|
||||
// Here we tidy up things left unfinished
|
||||
|
||||
// What's left unfinished there is the curve between the last points
|
||||
// in the stroke
|
||||
// We can also be called when there is only one point in the stroke (meaning, the
|
||||
// stroke was just a dot), in which case 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
|
||||
|
||||
// there must be at least 2 points in the stroke.for us to work. Hope calling code checks for that.
|
||||
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)
|
||||
, rounding = 2
|
||||
|
||||
if (positionInStroke > 1 && BCvector.getLength() > lineCurveThreshold){
|
||||
// we have at least 3 elems in stroke
|
||||
var ABvector = (new Point(stroke.x[positionInStroke-2], stroke.y[positionInStroke-2])).getVectorToPoint(Bpoint)
|
||||
, ABCangle = BCvector.angleTo(ABvector.reverse())
|
||||
, minlenfraction = 0.05
|
||||
, maxlen = BCvector.getLength() * 0.35
|
||||
, BtoCP1vector = new Vector(ABvector.x + BCvector.x, ABvector.y + BCvector.y).resizeTo(
|
||||
Math.max(minlenfraction, ABCangle) * maxlen
|
||||
)
|
||||
|
||||
return [
|
||||
'c' // bezier curve
|
||||
, round( BtoCP1vector.x, rounding )
|
||||
, round( BtoCP1vector.y, rounding )
|
||||
, round( BCvector.x, rounding ) // CP2 is same as Cpoint
|
||||
, round( BCvector.y, rounding ) // CP2 is same as Cpoint
|
||||
, round( BCvector.x, rounding )
|
||||
, round( BCvector.y, rounding )
|
||||
]
|
||||
} else {
|
||||
// Since there is no AB leg, there is no curve to draw. This is just line
|
||||
return [
|
||||
'l' // simple line
|
||||
, round( BCvector.x, rounding )
|
||||
, round( BCvector.y, rounding )
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
function addstroke(stroke, shiftx, shifty){
|
||||
'use strict'
|
||||
// we combine strokes data into string like this:
|
||||
// 'M 53 7 l 1 2 c 3 4 -5 -6 5 -6'
|
||||
// see SVG documentation for Path element's 'd' argument.
|
||||
var lines = [
|
||||
'M' // move to
|
||||
, round( (stroke.x[0] - shiftx), 2)
|
||||
, round( (stroke.y[0] - shifty), 2)
|
||||
]
|
||||
// processing all points but first and last.
|
||||
, i = 1 // index zero item in there is STARTING point. we already extracted it.
|
||||
, l = stroke.x.length - 1 // this is a trick. We are leaving last point coordinates for separate processing.
|
||||
, lineCurveThreshold = 1
|
||||
|
||||
for(; i < l; i++){
|
||||
lines.push.apply(lines, segmentToCurve(stroke, i, lineCurveThreshold))
|
||||
}
|
||||
if (l > 0 /* effectively more than 1, since we "-1" above */){
|
||||
lines.push.apply(lines, lastSegmentToCurve(stroke, i, lineCurveThreshold))
|
||||
} else if (l === 0){
|
||||
// meaning we only have ONE point in the stroke (and otherwise refer to the stroke as "dot")
|
||||
lines.push.apply(lines, ['l' , 1, 1])
|
||||
}
|
||||
return lines.join(' ')
|
||||
}
|
||||
|
||||
function simplifystroke(stroke){
|
||||
var d = []
|
||||
, newstroke = {'x':[], 'y':[]}
|
||||
, i, l
|
||||
|
||||
for (i = 0, l = stroke.x.length; i < l; i++){
|
||||
d.push({'x':stroke.x[i], 'y':stroke.y[i]})
|
||||
}
|
||||
d = simplify(d, 0.7, true)
|
||||
for (i = 0, l = d.length; i < l; i++){
|
||||
newstroke.x.push(d[i].x)
|
||||
newstroke.y.push(d[i].y)
|
||||
}
|
||||
return newstroke
|
||||
}
|
||||
|
||||
// generate SVG style from settings
|
||||
function styleFromSettings(settings){
|
||||
var styles = [];
|
||||
var meta = [
|
||||
// ["style attr", "key in settings", "default value"]
|
||||
["fill", undefined, "none"],
|
||||
["stroke", "color", "#000000"],
|
||||
["stroke-width", "lineWidth", 2],
|
||||
["stroke-linecap", undefined, "round"],
|
||||
["stroke-linejoin", undefined, "round"]
|
||||
];
|
||||
for (var i = meta.length - 1; i >= 0; i--){
|
||||
var attr = meta[i][0]
|
||||
, key = meta[i][1]
|
||||
, defaultVal = meta[i][2];
|
||||
styles.push(attr + '="' + (key in settings && settings[key] ? settings[key] : defaultVal) + '"');
|
||||
}
|
||||
return styles.join(' ');
|
||||
}
|
||||
|
||||
function compressstrokes(data, settings){
|
||||
'use strict'
|
||||
var answer = [
|
||||
'<?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">'
|
||||
]
|
||||
, i , l = data.length
|
||||
, stroke
|
||||
, xlimits = []
|
||||
, ylimits = []
|
||||
, sizex = 0
|
||||
, sizey = 0
|
||||
, shiftx = 0
|
||||
, shifty = 0
|
||||
, minx, maxx, miny, maxy, padding = 1
|
||||
, simplifieddata = []
|
||||
|
||||
if(l !== 0){
|
||||
for(i = 0; i < l; i++){
|
||||
stroke = simplifystroke( data[i] )
|
||||
simplifieddata.push(stroke)
|
||||
xlimits = xlimits.concat(stroke.x)
|
||||
ylimits = ylimits.concat(stroke.y)
|
||||
}
|
||||
|
||||
minx = Math.min.apply(null, xlimits) - padding
|
||||
maxx = Math.max.apply(null, xlimits) + padding
|
||||
miny = Math.min.apply(null, ylimits) - padding
|
||||
maxy = Math.max.apply(null, ylimits) + padding
|
||||
shiftx = minx < 0? 0 : minx
|
||||
shifty = miny < 0? 0 : miny
|
||||
sizex = maxx - minx
|
||||
sizey = maxy - miny
|
||||
}
|
||||
|
||||
answer.push(
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="'+
|
||||
sizex.toString() +
|
||||
'" height="'+
|
||||
sizey.toString() +
|
||||
'">'
|
||||
)
|
||||
|
||||
// // This is a nice idea: use style declaration on top, and mark the lines with 'class="f"'
|
||||
// // thus saving space in svg...
|
||||
// // alas, many SVG renderers don't understand "class" and render the strokes in default "fill = black, no stroke" style. Ugh!!!
|
||||
// // TODO: Rewrite ImageMagic / GraphicsMagic, InkScape, http://svg.codeplex.com/ to support style + class. until then, we hardcode the stroke style within the path.
|
||||
// answer.push(
|
||||
// '<style type="text/css"><![CDATA[.f {fill:none;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}]]></style>'
|
||||
// )
|
||||
|
||||
// // This set is accompaniment to "simple line renderer" - compressstroke
|
||||
// answer.push(
|
||||
// '<style type="text/css"><![CDATA[.t {fill:none;stroke:#FF0000;stroke-width:2}]]></style>'
|
||||
// )
|
||||
// for(i = 0; i < l; i++){
|
||||
// stroke = data[i]
|
||||
// // This one is accompaniment to "simple line renderer"
|
||||
// answer.push('<path class="t" d="'+ compressstroke(stroke, shiftx, shifty) +'"/>')
|
||||
// }
|
||||
|
||||
for(i = 0, l = simplifieddata.length; i < l; i++){
|
||||
stroke = simplifieddata[i]
|
||||
answer.push('<path ' + styleFromSettings(settings) + ' d="'+ addstroke(stroke, shiftx, shifty) + '"/>')
|
||||
}
|
||||
answer.push('</svg>')
|
||||
return answer.join('')
|
||||
}
|
||||
|
||||
if (typeof btoa !== 'function')
|
||||
{
|
||||
var btoa = function(data) {
|
||||
/** @preserve
|
||||
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)
|
||||
|
||||
*/
|
||||
var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
|
||||
, b64a = b64.split('')
|
||||
, o1, o2, o3, h1, h2, h3, h4, bits, i = 0,
|
||||
ac = 0,
|
||||
enc = "",
|
||||
tmp_arr = [];
|
||||
|
||||
do { // pack three octets into four hexets
|
||||
o1 = data.charCodeAt(i++);
|
||||
o2 = data.charCodeAt(i++);
|
||||
o3 = data.charCodeAt(i++);
|
||||
|
||||
bits = o1 << 16 | o2 << 8 | o3;
|
||||
|
||||
h1 = bits >> 18 & 0x3f;
|
||||
h2 = bits >> 12 & 0x3f;
|
||||
h3 = bits >> 6 & 0x3f;
|
||||
h4 = bits & 0x3f;
|
||||
|
||||
// use hexets to index into b64, and append result to encoded string
|
||||
tmp_arr[ac++] = b64a[h1] + b64a[h2] + b64a[h3] + b64a[h4];
|
||||
} while (i < data.length);
|
||||
|
||||
enc = tmp_arr.join('');
|
||||
var r = data.length % 3;
|
||||
return (r ? enc.slice(0, r - 3) : enc) + '==='.slice(r || 3);
|
||||
|
||||
// end of base64 encoder MIT, GPL
|
||||
}
|
||||
}
|
||||
|
||||
var unencodedmime = 'image/svg+xml'
|
||||
function getUnencodedSVG(data, settings){
|
||||
return [unencodedmime , compressstrokes(data, settings)];
|
||||
}
|
||||
|
||||
var base64encodedmime = 'image/svg+xml;base64'
|
||||
function getBase64encodedSVG(data, settings){
|
||||
|
||||
return [base64encodedmime , btoa( compressstrokes(data, settings) )];
|
||||
}
|
||||
|
||||
function Initializer($){
|
||||
var mothership = $.fn['jSignature']
|
||||
mothership(
|
||||
'addPlugin'
|
||||
,'export'
|
||||
,'svg' // alias
|
||||
,getUnencodedSVG
|
||||
)
|
||||
mothership(
|
||||
'addPlugin'
|
||||
,'export'
|
||||
,unencodedmime // full name
|
||||
,getUnencodedSVG
|
||||
)
|
||||
mothership(
|
||||
'addPlugin'
|
||||
,'export'
|
||||
,'svgbase64' // alias
|
||||
,getBase64encodedSVG
|
||||
)
|
||||
mothership(
|
||||
'addPlugin'
|
||||
,'export'
|
||||
,base64encodedmime // full name
|
||||
,getBase64encodedSVG
|
||||
)
|
||||
}
|
||||
|
||||
// //Because plugins are minified together with jSignature, multiple defines per (minified) file blow up and dont make sense
|
||||
// //Need to revisit this later.
|
||||
|
||||
if(typeof $ === 'undefined') {throw new Error("We need jQuery for some of the functionality. jQuery is not detected. Failing to initialize...")}
|
||||
Initializer($)
|
||||
|
||||
})();
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,165 @@
|
||||
/** @license
|
||||
jSignature v2 jSignature's Undo Button and undo functionality plugin
|
||||
|
||||
*/
|
||||
/**
|
||||
Copyright (c) 2011 Willow Systems Corp http://willow-systems.com
|
||||
MIT License <http://www.opensource.org/licenses/mit-license.php>
|
||||
*/
|
||||
|
||||
;(function(){
|
||||
|
||||
var apinamespace = 'jSignature'
|
||||
|
||||
function attachHandlers(buttonRenderer, apinamespace, extensionName) {
|
||||
var $undoButton = buttonRenderer.call(this)
|
||||
|
||||
;(function(jSignatureInstance, $undoButton, apinamespace) {
|
||||
jSignatureInstance.events.subscribe(
|
||||
apinamespace + '.change'
|
||||
, function(){
|
||||
if (jSignatureInstance.dataEngine.data.length) {
|
||||
$undoButton.show()
|
||||
} else {
|
||||
$undoButton.hide()
|
||||
}
|
||||
}
|
||||
)
|
||||
})( this, $undoButton, apinamespace )
|
||||
|
||||
;(function(jSignatureInstance, $undoButton, apinamespace) {
|
||||
|
||||
var eventName = apinamespace + '.undo'
|
||||
|
||||
$undoButton.bind('click', function(){
|
||||
jSignatureInstance.events.publish(eventName)
|
||||
})
|
||||
|
||||
// This one creates new "undo" event listener to jSignature instance
|
||||
// It handles the actual undo-ing.
|
||||
jSignatureInstance.events.subscribe(
|
||||
eventName
|
||||
, function(){
|
||||
var data = jSignatureInstance.dataEngine.data
|
||||
if (data.length) {
|
||||
data.pop()
|
||||
jSignatureInstance.resetCanvas(data)
|
||||
}
|
||||
}
|
||||
)
|
||||
})(
|
||||
this
|
||||
, $undoButton
|
||||
, this.events.topics.hasOwnProperty( apinamespace + '.undo' ) ?
|
||||
// oops, seems some other plugin or code has already claimed "jSignature.undo" event
|
||||
// we will use this extension's name for event name prefix
|
||||
extensionName :
|
||||
// Great! we will use 'jSignature' for event name prefix.
|
||||
apinamespace
|
||||
)
|
||||
}
|
||||
|
||||
function ExtensionInitializer(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
|
||||
|
||||
var apinamespace = 'jSignature'
|
||||
|
||||
this.events.subscribe(
|
||||
// name of the event
|
||||
apinamespace + '.attachingEventHandlers'
|
||||
// event handlers, can pass args too, but in majority of cases,
|
||||
// 'this' which is jSignatureClass object instance pointer is enough to get by.
|
||||
, function(){
|
||||
|
||||
// hooking up "undo" button to lower edge of Canvas.
|
||||
// but only when options passed to jSignature('init', options)
|
||||
// contain "undoButton":renderingFunction pair.
|
||||
// or "undoButton":true (in which case default, internal rendering fn is used)
|
||||
if (this.settings[extensionName]) {
|
||||
var oursettings = this.settings[extensionName]
|
||||
if (typeof oursettings !== 'function') {
|
||||
// we make it a function.
|
||||
|
||||
// we allow people to override the button rendering code,
|
||||
// but when developler is OK with default look (and just passes "truthy" value)
|
||||
// this defines default look for the button:
|
||||
// centered against canvas, hanging on its lower side.
|
||||
oursettings = function(){
|
||||
// this === jSignatureInstance
|
||||
var undoButtonSytle = 'position:absolute;display:none;margin:0 !important;top:auto'
|
||||
, $undoButton = $('<input type="button" value="Undo last stroke" style="'+undoButtonSytle+'" />')
|
||||
.appendTo(this.$controlbarLower)
|
||||
|
||||
// this centers the button against the canvas.
|
||||
var buttonWidth = $undoButton.width()
|
||||
$undoButton.css(
|
||||
'left'
|
||||
, Math.round(( this.canvas.width - buttonWidth ) / 2)
|
||||
)
|
||||
// IE 7 grows the button. Correcting for that.
|
||||
if ( buttonWidth !== $undoButton.width() ) {
|
||||
$undoButton.width(buttonWidth)
|
||||
}
|
||||
|
||||
return $undoButton
|
||||
}
|
||||
}
|
||||
|
||||
attachHandlers.call(
|
||||
this
|
||||
, oursettings
|
||||
, apinamespace
|
||||
, extensionName
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
var ExtensionAttacher = function(){
|
||||
$.fn[apinamespace](
|
||||
'addPlugin'
|
||||
,'instance' // type of plugin
|
||||
,'UndoButton' // extension name
|
||||
,ExtensionInitializer
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// //Because plugins are minified together with jSignature, multiple defines per (minified) file blow up and dont make sense
|
||||
// //Need to revisit this later.
|
||||
|
||||
// if ( typeof define === "function" && define.amd != null) {
|
||||
// // AMD-loader compatible resource declaration
|
||||
// // you need to call this one with jQuery as argument.
|
||||
// define(function(){return Initializer} )
|
||||
// } else {
|
||||
ExtensionAttacher()
|
||||
// }
|
||||
|
||||
})();
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@ -0,0 +1,4 @@
|
||||
/* Modernizr 2.5.2 (Custom Build) | MIT & BSD
|
||||
* Build: http://www.modernizr.com/download/#-borderradius-csscolumns-canvas-touch-mq-cssclasses-addtest-teststyles-testprop-testallprops-prefixes-domprefixes-fullscreen_api
|
||||
*/
|
||||
;window.Modernizr=function(a,b,c){function A(a){j.cssText=a}function B(a,b){return A(m.join(a+";")+(b||""))}function C(a,b){return typeof a===b}function D(a,b){return!!~(""+a).indexOf(b)}function E(a,b){for(var d in a)if(j[a[d]]!==c)return b=="pfx"?a[d]:!0;return!1}function F(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:C(f,"function")?f.bind(d||b):f}return!1}function G(a,b,c){var d=a.charAt(0).toUpperCase()+a.substr(1),e=(a+" "+o.join(d+" ")+d).split(" ");return C(b,"string")||C(b,"undefined")?E(e,b):(e=(a+" "+p.join(d+" ")+d).split(" "),F(e,b,c))}var d="2.5.2",e={},f=!0,g=b.documentElement,h="modernizr",i=b.createElement(h),j=i.style,k,l={}.toString,m=" -webkit- -moz- -o- -ms- ".split(" "),n="Webkit Moz O ms",o=n.split(" "),p=n.toLowerCase().split(" "),q={},r={},s={},t=[],u=t.slice,v,w=function(a,c,d,e){var f,i,j,k=b.createElement("div"),l=b.body,m=l?l:b.createElement("body");if(parseInt(d,10))while(d--)j=b.createElement("div"),j.id=e?e[d]:h+(d+1),k.appendChild(j);return f=["­","<style>",a,"</style>"].join(""),k.id=h,m.innerHTML+=f,m.appendChild(k),l||g.appendChild(m),i=c(k,a),l?k.parentNode.removeChild(k):m.parentNode.removeChild(m),!!i},x=function(b){var c=a.matchMedia||a.msMatchMedia;if(c)return c(b).matches;var d;return w("@media "+b+" { #"+h+" { position: absolute; } }",function(b){d=(a.getComputedStyle?getComputedStyle(b,null):b.currentStyle)["position"]=="absolute"}),d},y={}.hasOwnProperty,z;!C(y,"undefined")&&!C(y.call,"undefined")?z=function(a,b){return y.call(a,b)}:z=function(a,b){return b in a&&C(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=u.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(u.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(u.call(arguments)))};return e});var H=function(c,d){var f=c.join(""),g=d.length;w(f,function(c,d){var f=b.styleSheets[b.styleSheets.length-1],h=f?f.cssRules&&f.cssRules[0]?f.cssRules[0].cssText:f.cssText||"":"",i=c.childNodes,j={};while(g--)j[i[g].id]=i[g];e.touch="ontouchstart"in a||a.DocumentTouch&&b instanceof DocumentTouch||(j.touch&&j.touch.offsetTop)===9},g,d)}([,["@media (",m.join("touch-enabled),("),h,")","{#touch{top:9px;position:absolute}}"].join("")],[,"touch"]);q.canvas=function(){var a=b.createElement("canvas");return!!a.getContext&&!!a.getContext("2d")},q.touch=function(){return e.touch},q.borderradius=function(){return G("borderRadius")},q.csscolumns=function(){return G("columnCount")};for(var I in q)z(q,I)&&(v=I.toLowerCase(),e[v]=q[I](),t.push((e[v]?"":"no-")+v));return e.addTest=function(a,b){if(typeof a=="object")for(var d in a)z(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return e;b=typeof b=="function"?b():b,g.className+=" "+(b?"":"no-")+a,e[a]=b}return e},A(""),i=k=null,e._version=d,e._prefixes=m,e._domPrefixes=p,e._cssomPrefixes=o,e.mq=x,e.testProp=function(a){return E([a])},e.testAllProps=G,e.testStyles=w,g.className=g.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(f?" js "+t.join(" "):""),e}(this,this.document),Modernizr.addTest("fullscreen",function(){for(var a=0;a<Modernizr._domPrefixes.length;a++)if(document[Modernizr._domPrefixes[a].toLowerCase()+"CancelFullScreen"])return!0;return!!document.cancelFullScreen||!1});
|
@ -1,31 +0,0 @@
|
||||
function mSign_signMark_app(){
|
||||
|
||||
debugger;
|
||||
var wrapper = document.getElementById("mSign_signMark_signature_pad"),
|
||||
clearButton = document.getElementById("mSign_signMark_clear_out"),
|
||||
canvas = wrapper.querySelector("canvas"),
|
||||
sure = document.getElementsByClassName("mSign_signMark_footer_sure"),
|
||||
signaturePad;
|
||||
|
||||
function resizeCanvas() {
|
||||
|
||||
var ratio = Math.max(window.devicePixelRatio || 1, 1);
|
||||
canvas.width = canvas.offsetWidth * ratio;
|
||||
canvas.height = canvas.offsetHeight * ratio;
|
||||
canvas.getContext("2d").scale(ratio, ratio);
|
||||
}
|
||||
|
||||
window.onresize = resizeCanvas;
|
||||
resizeCanvas();
|
||||
|
||||
signaturePad = new SignaturePad(canvas,{
|
||||
minWidth: 1,
|
||||
maxWidth: 2,
|
||||
penColor: "rgb(0,0,0)"
|
||||
});
|
||||
|
||||
clearButton.addEventListener("click", function (event) {
|
||||
signaturePad.clear();
|
||||
});
|
||||
}
|
||||
|
@ -1,368 +0,0 @@
|
||||
(function (root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
|
||||
define([], function () {
|
||||
return (root['SignaturePad'] = factory());
|
||||
});
|
||||
} else if (typeof exports === 'object') {
|
||||
|
||||
module.exports = factory();
|
||||
} else {
|
||||
root['SignaturePad'] = factory();
|
||||
}
|
||||
}(this, function () {
|
||||
|
||||
var SignaturePad = (function (document) {
|
||||
"use strict";
|
||||
|
||||
var SignaturePad = function (canvas, options) {
|
||||
var self = this,
|
||||
opts = options || {};
|
||||
|
||||
this.velocityFilterWeight = opts.velocityFilterWeight || 0.7;
|
||||
this.minWidth = opts.minWidth || 0.5;
|
||||
this.maxWidth = opts.maxWidth || 2.5;
|
||||
this.dotSize = opts.dotSize || function () {
|
||||
return (this.minWidth + this.maxWidth) / 2;
|
||||
};
|
||||
this.penColor = opts.penColor || "black";
|
||||
this.backgroundColor = opts.backgroundColor || "rgba(0,0,0,0)";
|
||||
this.onEnd = opts.onEnd;
|
||||
this.onBegin = opts.onBegin;
|
||||
|
||||
this._canvas = canvas;
|
||||
this._ctx = canvas.getContext("2d");
|
||||
this.clear();
|
||||
|
||||
this._handleMouseDown = function (event) {
|
||||
if (event.which === 1) {
|
||||
self._mouseButtonDown = true;
|
||||
self._strokeBegin(event);
|
||||
}
|
||||
};
|
||||
|
||||
this._handleMouseMove = function (event) {
|
||||
if (self._mouseButtonDown) {
|
||||
self._strokeUpdate(event);
|
||||
}
|
||||
};
|
||||
|
||||
this._handleMouseUp = function (event) {
|
||||
if (event.which === 1 && self._mouseButtonDown) {
|
||||
self._mouseButtonDown = false;
|
||||
self._strokeEnd(event);
|
||||
}
|
||||
};
|
||||
|
||||
this._handleTouchStart = function (event) {
|
||||
if (event.targetTouches.length == 1) {
|
||||
var touch = event.changedTouches[0];
|
||||
self._strokeBegin(touch);
|
||||
}
|
||||
};
|
||||
|
||||
this._handleTouchMove = function (event) {
|
||||
// Prevent scrolling.
|
||||
event.preventDefault();
|
||||
|
||||
var touch = event.targetTouches[0];
|
||||
self._strokeUpdate(touch);
|
||||
};
|
||||
|
||||
this._handleTouchEnd = function (event) {
|
||||
var wasCanvasTouched = event.target === self._canvas;
|
||||
if (wasCanvasTouched) {
|
||||
event.preventDefault();
|
||||
self._strokeEnd(event);
|
||||
}
|
||||
};
|
||||
|
||||
this._handleMouseEvents();
|
||||
this._handleTouchEvents();
|
||||
};
|
||||
|
||||
SignaturePad.prototype.clear = function () {
|
||||
var ctx = this._ctx,
|
||||
canvas = this._canvas;
|
||||
|
||||
ctx.fillStyle = this.backgroundColor;
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
this._reset();
|
||||
};
|
||||
|
||||
SignaturePad.prototype.toDataURL = function (imageType, quality) {
|
||||
var canvas = this._canvas;
|
||||
return canvas.toDataURL.apply(canvas, arguments);
|
||||
};
|
||||
|
||||
SignaturePad.prototype.fromDataURL = function (dataUrl) {
|
||||
var self = this,
|
||||
image = new Image(),
|
||||
ratio = window.devicePixelRatio || 1,
|
||||
width = this._canvas.width / ratio,
|
||||
height = this._canvas.height / ratio;
|
||||
|
||||
this._reset();
|
||||
image.src = dataUrl;
|
||||
image.onload = function () {
|
||||
self._ctx.drawImage(image, 0, 0, width, height);
|
||||
};
|
||||
this._isEmpty = false;
|
||||
};
|
||||
|
||||
SignaturePad.prototype._strokeUpdate = function (event) {
|
||||
var point = this._createPoint(event);
|
||||
this._addPoint(point);
|
||||
};
|
||||
|
||||
SignaturePad.prototype._strokeBegin = function (event) {
|
||||
this._reset();
|
||||
this._strokeUpdate(event);
|
||||
if (typeof this.onBegin === 'function') {
|
||||
this.onBegin(event);
|
||||
}
|
||||
};
|
||||
|
||||
SignaturePad.prototype._strokeDraw = function (point) {
|
||||
var ctx = this._ctx,
|
||||
dotSize = typeof(this.dotSize) === 'function' ? this.dotSize() : this.dotSize;
|
||||
|
||||
ctx.beginPath();
|
||||
this._drawPoint(point.x, point.y, dotSize);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
};
|
||||
|
||||
SignaturePad.prototype._strokeEnd = function (event) {
|
||||
var canDrawCurve = this.points.length > 2,
|
||||
point = this.points[0];
|
||||
|
||||
if (!canDrawCurve && point) {
|
||||
this._strokeDraw(point);
|
||||
}
|
||||
if (typeof this.onEnd === 'function') {
|
||||
this.onEnd(event);
|
||||
}
|
||||
};
|
||||
|
||||
SignaturePad.prototype._handleMouseEvents = function () {
|
||||
this._mouseButtonDown = false;
|
||||
|
||||
this._canvas.addEventListener("mousedown", this._handleMouseDown);
|
||||
this._canvas.addEventListener("mousemove", this._handleMouseMove);
|
||||
document.addEventListener("mouseup", this._handleMouseUp);
|
||||
};
|
||||
|
||||
SignaturePad.prototype._handleTouchEvents = function () {
|
||||
// Pass touch events to canvas element on mobile IE11 and Edge.
|
||||
this._canvas.style.msTouchAction = 'none';
|
||||
this._canvas.style.touchAction = 'none';
|
||||
|
||||
this._canvas.addEventListener("touchstart", this._handleTouchStart);
|
||||
this._canvas.addEventListener("touchmove", this._handleTouchMove);
|
||||
this._canvas.addEventListener("touchend", this._handleTouchEnd);
|
||||
};
|
||||
|
||||
SignaturePad.prototype.on = function () {
|
||||
this._handleMouseEvents();
|
||||
this._handleTouchEvents();
|
||||
};
|
||||
|
||||
SignaturePad.prototype.off = function () {
|
||||
this._canvas.removeEventListener("mousedown", this._handleMouseDown);
|
||||
this._canvas.removeEventListener("mousemove", this._handleMouseMove);
|
||||
document.removeEventListener("mouseup", this._handleMouseUp);
|
||||
|
||||
this._canvas.removeEventListener("touchstart", this._handleTouchStart);
|
||||
this._canvas.removeEventListener("touchmove", this._handleTouchMove);
|
||||
this._canvas.removeEventListener("touchend", this._handleTouchEnd);
|
||||
};
|
||||
|
||||
SignaturePad.prototype.isEmpty = function () {
|
||||
return this._isEmpty;
|
||||
};
|
||||
|
||||
SignaturePad.prototype._reset = function () {
|
||||
this.points = [];
|
||||
this._lastVelocity = 0;
|
||||
this._lastWidth = (this.minWidth + this.maxWidth) / 2;
|
||||
this._isEmpty = true;
|
||||
this._ctx.fillStyle = this.penColor;
|
||||
};
|
||||
|
||||
SignaturePad.prototype._createPoint = function (event) {
|
||||
var rect = this._canvas.getBoundingClientRect();
|
||||
return new Point(
|
||||
event.clientX - rect.left,
|
||||
event.clientY - rect.top
|
||||
);
|
||||
};
|
||||
|
||||
SignaturePad.prototype._addPoint = function (point) {
|
||||
var points = this.points,
|
||||
c2, c3,
|
||||
curve, tmp;
|
||||
|
||||
points.push(point);
|
||||
|
||||
if (points.length > 2) {
|
||||
// To reduce the initial lag make it work with 3 points
|
||||
// by copying the first point to the beginning.
|
||||
if (points.length === 3) points.unshift(points[0]);
|
||||
|
||||
tmp = this._calculateCurveControlPoints(points[0], points[1], points[2]);
|
||||
c2 = tmp.c2;
|
||||
tmp = this._calculateCurveControlPoints(points[1], points[2], points[3]);
|
||||
c3 = tmp.c1;
|
||||
curve = new Bezier(points[1], c2, c3, points[2]);
|
||||
this._addCurve(curve);
|
||||
|
||||
// Remove the first element from the list,
|
||||
// so that we always have no more than 4 points in points array.
|
||||
points.shift();
|
||||
}
|
||||
};
|
||||
|
||||
SignaturePad.prototype._calculateCurveControlPoints = function (s1, s2, s3) {
|
||||
var dx1 = s1.x - s2.x, dy1 = s1.y - s2.y,
|
||||
dx2 = s2.x - s3.x, dy2 = s2.y - s3.y,
|
||||
|
||||
m1 = {x: (s1.x + s2.x) / 2.0, y: (s1.y + s2.y) / 2.0},
|
||||
m2 = {x: (s2.x + s3.x) / 2.0, y: (s2.y + s3.y) / 2.0},
|
||||
|
||||
l1 = Math.sqrt(dx1*dx1 + dy1*dy1),
|
||||
l2 = Math.sqrt(dx2*dx2 + dy2*dy2),
|
||||
|
||||
dxm = (m1.x - m2.x),
|
||||
dym = (m1.y - m2.y),
|
||||
|
||||
k = l2 / (l1 + l2),
|
||||
cm = {x: m2.x + dxm*k, y: m2.y + dym*k},
|
||||
|
||||
tx = s2.x - cm.x,
|
||||
ty = s2.y - cm.y;
|
||||
|
||||
return {
|
||||
c1: new Point(m1.x + tx, m1.y + ty),
|
||||
c2: new Point(m2.x + tx, m2.y + ty)
|
||||
};
|
||||
};
|
||||
|
||||
SignaturePad.prototype._addCurve = function (curve) {
|
||||
var startPoint = curve.startPoint,
|
||||
endPoint = curve.endPoint,
|
||||
velocity, newWidth;
|
||||
|
||||
velocity = endPoint.velocityFrom(startPoint);
|
||||
velocity = this.velocityFilterWeight * velocity
|
||||
+ (1 - this.velocityFilterWeight) * this._lastVelocity;
|
||||
|
||||
newWidth = this._strokeWidth(velocity);
|
||||
this._drawCurve(curve, this._lastWidth, newWidth);
|
||||
|
||||
this._lastVelocity = velocity;
|
||||
this._lastWidth = newWidth;
|
||||
};
|
||||
|
||||
SignaturePad.prototype._drawPoint = function (x, y, size) {
|
||||
var ctx = this._ctx;
|
||||
|
||||
ctx.moveTo(x, y);
|
||||
ctx.arc(x, y, size, 0, 2 * Math.PI, false);
|
||||
this._isEmpty = false;
|
||||
};
|
||||
|
||||
SignaturePad.prototype._drawCurve = function (curve, startWidth, endWidth) {
|
||||
var ctx = this._ctx,
|
||||
widthDelta = endWidth - startWidth,
|
||||
drawSteps, width, i, t, tt, ttt, u, uu, uuu, x, y;
|
||||
|
||||
drawSteps = Math.floor(curve.length());
|
||||
ctx.beginPath();
|
||||
for (i = 0; i < drawSteps; i++) {
|
||||
// Calculate the Bezier (x, y) coordinate for this step.
|
||||
t = i / drawSteps;
|
||||
tt = t * t;
|
||||
ttt = tt * t;
|
||||
u = 1 - t;
|
||||
uu = u * u;
|
||||
uuu = uu * u;
|
||||
|
||||
x = uuu * curve.startPoint.x;
|
||||
x += 3 * uu * t * curve.control1.x;
|
||||
x += 3 * u * tt * curve.control2.x;
|
||||
x += ttt * curve.endPoint.x;
|
||||
|
||||
y = uuu * curve.startPoint.y;
|
||||
y += 3 * uu * t * curve.control1.y;
|
||||
y += 3 * u * tt * curve.control2.y;
|
||||
y += ttt * curve.endPoint.y;
|
||||
|
||||
width = startWidth + ttt * widthDelta;
|
||||
this._drawPoint(x, y, width);
|
||||
}
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
};
|
||||
|
||||
SignaturePad.prototype._strokeWidth = function (velocity) {
|
||||
return Math.max(this.maxWidth / (velocity + 1), this.minWidth);
|
||||
};
|
||||
|
||||
|
||||
var Point = function (x, y, time) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.time = time || new Date().getTime();
|
||||
};
|
||||
|
||||
Point.prototype.velocityFrom = function (start) {
|
||||
return (this.time !== start.time) ? this.distanceTo(start) / (this.time - start.time) : 1;
|
||||
};
|
||||
|
||||
Point.prototype.distanceTo = function (start) {
|
||||
return Math.sqrt(Math.pow(this.x - start.x, 2) + Math.pow(this.y - start.y, 2));
|
||||
};
|
||||
|
||||
var Bezier = function (startPoint, control1, control2, endPoint) {
|
||||
this.startPoint = startPoint;
|
||||
this.control1 = control1;
|
||||
this.control2 = control2;
|
||||
this.endPoint = endPoint;
|
||||
};
|
||||
|
||||
// Returns approximated length.
|
||||
Bezier.prototype.length = function () {
|
||||
var steps = 10,
|
||||
length = 0,
|
||||
i, t, cx, cy, px, py, xdiff, ydiff;
|
||||
|
||||
for (i = 0; i <= steps; i++) {
|
||||
t = i / steps;
|
||||
cx = this._point(t, this.startPoint.x, this.control1.x, this.control2.x, this.endPoint.x);
|
||||
cy = this._point(t, this.startPoint.y, this.control1.y, this.control2.y, this.endPoint.y);
|
||||
if (i > 0) {
|
||||
xdiff = cx - px;
|
||||
ydiff = cy - py;
|
||||
length += Math.sqrt(xdiff * xdiff + ydiff * ydiff);
|
||||
}
|
||||
px = cx;
|
||||
py = cy;
|
||||
}
|
||||
return length;
|
||||
};
|
||||
|
||||
Bezier.prototype._point = function (t, start, c1, c2, end) {
|
||||
return start * (1.0 - t) * (1.0 - t) * (1.0 - t)
|
||||
+ 3.0 * c1 * (1.0 - t) * (1.0 - t) * t
|
||||
+ 3.0 * c2 * (1.0 - t) * t * t
|
||||
+ end * t * t * t;
|
||||
};
|
||||
|
||||
return SignaturePad;
|
||||
})(document);
|
||||
|
||||
return SignaturePad;
|
||||
|
||||
}));
|
Loading…
Reference in new issue