| @@ -0,0 +1,390 @@ | |||
| /*! | |||
| * Signature Pad v2.2.0 | |||
| * https://github.com/szimek/signature_pad | |||
| * | |||
| * Copyright 2017 Szymon Nowak | |||
| * Released under the MIT license | |||
| * | |||
| * The main idea and some parts of the code (e.g. drawing variable width Bézier curve) are taken from: | |||
| * http://corner.squareup.com/2012/07/smoother-signatures.html | |||
| * | |||
| * Implementation of interpolation using cubic Bézier curves is taken from: | |||
| * http://benknowscode.wordpress.com/2012/09/14/path-interpolation-using-cubic-bezier-and-control-point-estimation-in-javascript | |||
| * | |||
| * Algorithm for approximated length of a Bézier curve is taken from: | |||
| * http://www.lemoda.net/maths/bezier-length/index.html | |||
| * | |||
| */ | |||
| (function(global, factory) { | |||
| typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : | |||
| typeof define === 'function' && define.amd ? define(factory) : | |||
| (global.SignaturePad = factory()); | |||
| }(this, (function() { | |||
| 'use strict'; | |||
| function Point(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)); | |||
| }; | |||
| function Bezier(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; | |||
| var length = 0; | |||
| var px = void 0; | |||
| var py = void 0; | |||
| for (var i = 0; i <= steps; i += 1) { | |||
| var t = i / steps; | |||
| var cx = this._point(t, this.startPoint.x, this.control1.x, this.control2.x, this.endPoint.x); | |||
| var cy = this._point(t, this.startPoint.y, this.control1.y, this.control2.y, this.endPoint.y); | |||
| if (i > 0) { | |||
| var xdiff = cx - px; | |||
| var ydiff = cy - py; | |||
| length += Math.sqrt(xdiff * xdiff + ydiff * ydiff); | |||
| } | |||
| px = cx; | |||
| py = cy; | |||
| } | |||
| return length; | |||
| }; | |||
| /* eslint-disable no-multi-spaces, space-in-parens */ | |||
| 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; | |||
| }; | |||
| /* eslint-disable */ | |||
| // http://stackoverflow.com/a/27078401/815507 | |||
| function throttle(func, wait, options) { | |||
| var context, args, result; | |||
| var timeout = null; | |||
| var previous = 0; | |||
| if (!options) options = {}; | |||
| var later = function later() { | |||
| previous = options.leading === false ? 0 : Date.now(); | |||
| timeout = null; | |||
| result = func.apply(context, args); | |||
| if (!timeout) context = args = null; | |||
| }; | |||
| return function() { | |||
| var now = Date.now(); | |||
| if (!previous && options.leading === false) previous = now; | |||
| var remaining = wait - (now - previous); | |||
| context = this; | |||
| args = arguments; | |||
| if (remaining <= 0 || remaining > wait) { | |||
| if (timeout) { | |||
| clearTimeout(timeout); | |||
| timeout = null; | |||
| } | |||
| previous = now; | |||
| result = func.apply(context, args); | |||
| if (!timeout) context = args = null; | |||
| } else if (!timeout && options.trailing !== false) { | |||
| timeout = setTimeout(later, remaining); | |||
| } | |||
| return result; | |||
| }; | |||
| } | |||
| function SignaturePad(canvas, options) { | |||
| var self = this; | |||
| var opts = options || {}; | |||
| this.velocityFilterWeight = opts.velocityFilterWeight || 0.7; | |||
| this.minWidth = opts.minWidth || 0.5; | |||
| this.maxWidth = opts.maxWidth || 2.5; | |||
| this.throttle = 'throttle' in opts ? opts.throttle : 16; // in miliseconds | |||
| if (this.throttle) { | |||
| this._strokeMoveUpdate = throttle(SignaturePad.prototype._strokeUpdate, this.throttle); | |||
| } else { | |||
| this._strokeMoveUpdate = SignaturePad.prototype._strokeUpdate; | |||
| } | |||
| 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.onBegin = opts.onBegin; | |||
| this.onEnd = opts.onEnd; | |||
| this.devicePixelRatio = opts.devicePixelRatio || 1; | |||
| this.lineWidth = opts.lineWidth || 1; //签字板的粗细 | |||
| this._canvas = canvas; | |||
| this._ctx = canvas; | |||
| this.clear(); | |||
| // We need add these inline so they are available to unbind while still having | |||
| // access to 'self' we could use _.bind but it's not worth adding a dependency. | |||
| this._handleTouchStart = function(event, data) { | |||
| self.penColor = data.penColor || self.penColor; | |||
| self.lineWidth = data.lineWidth || self.lineWidth; | |||
| if (event.touches.length === 1) { | |||
| var touch = event.changedTouches[0]; | |||
| self._strokeBegin(touch); | |||
| } | |||
| }; | |||
| this._handleTouchMove = function(event) { | |||
| // Prevent scrolling. | |||
| var touch = event.touches[0]; | |||
| self._strokeMoveUpdate(touch); | |||
| }; | |||
| this._handleTouchEnd = function(event) { | |||
| self._strokeEnd(event); | |||
| }; | |||
| this.clear = function() { | |||
| var ctx = this._ctx; | |||
| var canvas = this._canvas; | |||
| ctx.fillStyle = this.backgroundColor; | |||
| ctx.clearRect(0, 0, canvas.width, canvas.height); | |||
| ctx.fillRect(0, 0, canvas.width, canvas.height); | |||
| ctx.draw() | |||
| this._data = []; | |||
| this._reset(); | |||
| this._isEmpty = true; | |||
| }; | |||
| } | |||
| // Public methods | |||
| SignaturePad.prototype.clear = function() { | |||
| var ctx = this._ctx; | |||
| var canvas = this._canvas; | |||
| ctx.fillStyle = this.backgroundColor; | |||
| ctx.clearRect(0, 0, canvas.width, canvas.height); | |||
| ctx.fillRect(0, 0, canvas.width, canvas.height); | |||
| this._data = []; | |||
| this._reset(); | |||
| this._isEmpty = true; | |||
| }; | |||
| SignaturePad.prototype.isEmpty = function() { | |||
| return this._isEmpty; | |||
| }; | |||
| // Private methods | |||
| SignaturePad.prototype._strokeBegin = function(event) { | |||
| this._data.push([]); | |||
| this._reset(); | |||
| this._strokeUpdate(event); | |||
| if (typeof this.onBegin === 'function') { | |||
| this.onBegin(event); | |||
| } | |||
| }; | |||
| SignaturePad.prototype._strokeUpdate = function(event) { | |||
| var x = event.x; | |||
| var y = event.y; | |||
| var point = this._createPoint(x, y); | |||
| var _addPoint = this._addPoint(point), | |||
| curve = _addPoint.curve, | |||
| widths = _addPoint.widths; | |||
| if (curve && widths) { | |||
| this._drawCurve(curve, widths.start, widths.end); | |||
| } | |||
| this._data[this._data.length - 1].push({ | |||
| x: point.x, | |||
| y: point.y, | |||
| time: point.time, | |||
| color: this.penColor | |||
| }); | |||
| }; | |||
| SignaturePad.prototype._strokeEnd = function(event) { | |||
| var canDrawCurve = this.points.length > 2; | |||
| var point = this.points[0]; | |||
| if (!canDrawCurve && point) { | |||
| this._drawDot(point); | |||
| } | |||
| if (typeof this.onEnd === 'function') { | |||
| this.onEnd(event); | |||
| } | |||
| }; | |||
| SignaturePad.prototype._reset = function() { | |||
| this.points = []; | |||
| this._lastVelocity = 0; | |||
| this._lastWidth = (this.minWidth + this.maxWidth) / 2; | |||
| this._ctx.fillStyle = this.penColor; | |||
| }; | |||
| SignaturePad.prototype._createPoint = function(x, y, time) { | |||
| var rect = { | |||
| left: 10, | |||
| top: 10 | |||
| }; | |||
| return new Point(x - rect.left, y - rect.top, time || new Date().getTime()); | |||
| }; | |||
| SignaturePad.prototype._addPoint = function(point) { | |||
| var points = this.points; | |||
| var tmp = void 0; | |||
| 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]); | |||
| var c2 = tmp.c2; | |||
| tmp = this._calculateCurveControlPoints(points[1], points[2], points[3]); | |||
| var c3 = tmp.c1; | |||
| var curve = new Bezier(points[1], c2, c3, points[2]); | |||
| var widths = this._calculateCurveWidths(curve); | |||
| // Remove the first element from the list, | |||
| // so that we always have no more than 4 points in points array. | |||
| points.shift(); | |||
| return { curve: curve, widths: widths }; | |||
| } | |||
| return {}; | |||
| }; | |||
| SignaturePad.prototype._calculateCurveControlPoints = function(s1, s2, s3) { | |||
| var dx1 = s1.x - s2.x; | |||
| var dy1 = s1.y - s2.y; | |||
| var dx2 = s2.x - s3.x; | |||
| var dy2 = s2.y - s3.y; | |||
| var m1 = { x: (s1.x + s2.x) / 2.0, y: (s1.y + s2.y) / 2.0 }; | |||
| var m2 = { x: (s2.x + s3.x) / 2.0, y: (s2.y + s3.y) / 2.0 }; | |||
| var l1 = Math.sqrt(dx1 * dx1 + dy1 * dy1); | |||
| var l2 = Math.sqrt(dx2 * dx2 + dy2 * dy2); | |||
| var dxm = m1.x - m2.x; | |||
| var dym = m1.y - m2.y; | |||
| var k = l2 / (l1 + l2); | |||
| var cm = { x: m2.x + dxm * k, y: m2.y + dym * k }; | |||
| var tx = s2.x - cm.x; | |||
| var 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._calculateCurveWidths = function(curve) { | |||
| var startPoint = curve.startPoint; | |||
| var endPoint = curve.endPoint; | |||
| var widths = { start: null, end: null }; | |||
| var velocity = this.velocityFilterWeight * endPoint.velocityFrom(startPoint) + (1 - this.velocityFilterWeight) * this._lastVelocity; | |||
| var newWidth = this._strokeWidth(velocity); | |||
| widths.start = this._lastWidth; | |||
| widths.end = newWidth; | |||
| this._lastVelocity = velocity; | |||
| this._lastWidth = newWidth; | |||
| return widths; | |||
| }; | |||
| SignaturePad.prototype._strokeWidth = function(velocity) { | |||
| return Math.max(this.maxWidth / (velocity + 1), this.minWidth); | |||
| }; | |||
| SignaturePad.prototype._drawPoint = function(x, y, size) { | |||
| var ctx = this._ctx; | |||
| var lineWidth = this.lineWidth; | |||
| ctx.moveTo(x, y); | |||
| ctx.arc(x, y, size * lineWidth, 0, 2 * Math.PI, false); | |||
| this._isEmpty = false; | |||
| }; | |||
| SignaturePad.prototype._drawCurve = function(curve, startWidth, endWidth) { | |||
| var ctx = this._ctx; | |||
| var widthDelta = endWidth - startWidth; | |||
| var drawSteps = Math.floor(curve.length()); | |||
| ctx.beginPath(); | |||
| for (var i = 0; i < drawSteps; i += 1) { | |||
| // Calculate the Bezier (x, y) coordinate for this step. | |||
| var t = i / drawSteps; | |||
| var tt = t * t; | |||
| var ttt = tt * t; | |||
| var u = 1 - t; | |||
| var uu = u * u; | |||
| var uuu = uu * u; | |||
| var x = uuu * curve.startPoint.x; | |||
| x += 3 * uu * t * curve.control1.x; | |||
| x += 3 * u * tt * curve.control2.x; | |||
| x += ttt * curve.endPoint.x; | |||
| var y = uuu * curve.startPoint.y; | |||
| y += 3 * uu * t * curve.control1.y; | |||
| y += 3 * u * tt * curve.control2.y; | |||
| y += ttt * curve.endPoint.y; | |||
| var width = startWidth + ttt * widthDelta; | |||
| this._drawPoint(x, y, width); | |||
| } | |||
| var penColor = this.penColor; | |||
| ctx.closePath(); | |||
| ctx.setStrokeStyle(penColor); | |||
| ctx.setFillStyle(penColor); | |||
| ctx.fill(); | |||
| ctx.stroke(); | |||
| ctx.draw(true) | |||
| }; | |||
| SignaturePad.prototype._drawDot = function(point) { | |||
| var ctx = this._ctx; | |||
| var width = typeof this.dotSize === 'function' ? this.dotSize() : this.dotSize; | |||
| var penColor = this.penColor; | |||
| ctx.beginPath(); | |||
| this._drawPoint(point.x, point.y, width); | |||
| ctx.closePath(); | |||
| ctx.setStrokeStyle(penColor); | |||
| ctx.setFillStyle(penColor); | |||
| ctx.fill(); | |||
| ctx.stroke(); | |||
| ctx.draw(true) | |||
| }; | |||
| SignaturePad.prototype.toData = function() { | |||
| return this._data; | |||
| }; | |||
| return SignaturePad; | |||
| }))); | |||