diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 00000000..82eecf05
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,8 @@
+language: node_js
+
+node_js:
+ - "8"
+ - "9"
+
+cache:
+ yarn: true
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5471a672..710effb5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,14 @@
## Changelog
+### master
+#### Bug fixes
+- Fix error in `touchend` event handler.
+- Make both params in `#toDataURL` optional to match `Canvas#toDataURL`.
+
+#### Features
+- Add optional callback param to `#fromDataURL`.
+- Add basic unit tests for SignaturePad class.
+
### 3.0.0-beta.1
#### Breaking changes
- Rewrite library using TypeScript. TypeScript declaration files are now provided by the library. Hopefully, it should be a bit easier to refactor now...
diff --git a/docs/js/signature_pad.umd.js b/docs/js/signature_pad.umd.js
index 71d18f7d..a04ec991 100644
--- a/docs/js/signature_pad.umd.js
+++ b/docs/js/signature_pad.umd.js
@@ -4,493 +4,504 @@
*/
(function (global, factory) {
- typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
- typeof define === 'function' && define.amd ? define(factory) :
- (global.SignaturePad = factory());
+ typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
+ typeof define === 'function' && define.amd ? define(factory) :
+ (global.SignaturePad = factory());
}(this, (function () { 'use strict';
-var Point = (function () {
- function Point(x, y, time) {
- this.x = x;
- this.y = y;
- this.time = time || Date.now();
- }
- Point.prototype.distanceTo = function (start) {
- return Math.sqrt(Math.pow(this.x - start.x, 2) + Math.pow(this.y - start.y, 2));
- };
- Point.prototype.equals = function (other) {
- return this.x === other.x && this.y === other.y && this.time === other.time;
- };
- Point.prototype.velocityFrom = function (start) {
- return (this.time !== start.time) ? this.distanceTo(start) / (this.time - start.time) : 0;
- };
- return Point;
-}());
+ var Point = (function () {
+ function Point(x, y, time) {
+ this.x = x;
+ this.y = y;
+ this.time = time || Date.now();
+ }
+ Point.prototype.distanceTo = function (start) {
+ return Math.sqrt(Math.pow(this.x - start.x, 2) + Math.pow(this.y - start.y, 2));
+ };
+ Point.prototype.equals = function (other) {
+ return this.x === other.x && this.y === other.y && this.time === other.time;
+ };
+ Point.prototype.velocityFrom = function (start) {
+ return (this.time !== start.time) ? this.distanceTo(start) / (this.time - start.time) : 0;
+ };
+ return Point;
+ }());
-var Bezier = (function () {
- function Bezier(startPoint, control2, control1, endPoint, startWidth, endWidth) {
- this.startPoint = startPoint;
- this.control2 = control2;
- this.control1 = control1;
- this.endPoint = endPoint;
- this.startWidth = startWidth;
- this.endWidth = endWidth;
- }
- Bezier.fromPoints = function (points, widths) {
- var c2 = this.calculateControlPoints(points[0], points[1], points[2]).c2;
- var c3 = this.calculateControlPoints(points[1], points[2], points[3]).c1;
- return new Bezier(points[1], c2, c3, points[2], widths.start, widths.end);
- };
- Bezier.calculateControlPoints = 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)
- };
- };
- Bezier.prototype.length = function () {
- var steps = 10;
- var length = 0;
- var px;
- var py;
- 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;
- };
- 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 Bezier;
-}());
+ var Bezier = (function () {
+ function Bezier(startPoint, control2, control1, endPoint, startWidth, endWidth) {
+ this.startPoint = startPoint;
+ this.control2 = control2;
+ this.control1 = control1;
+ this.endPoint = endPoint;
+ this.startWidth = startWidth;
+ this.endWidth = endWidth;
+ }
+ Bezier.fromPoints = function (points, widths) {
+ var c2 = this.calculateControlPoints(points[0], points[1], points[2]).c2;
+ var c3 = this.calculateControlPoints(points[1], points[2], points[3]).c1;
+ return new Bezier(points[1], c2, c3, points[2], widths.start, widths.end);
+ };
+ Bezier.calculateControlPoints = 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)
+ };
+ };
+ Bezier.prototype.length = function () {
+ var steps = 10;
+ var length = 0;
+ var px;
+ var py;
+ 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;
+ };
+ 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 Bezier;
+ }());
-function throttle(fn, wait) {
- if (wait === void 0) { wait = 250; }
- var previous = 0;
- var timeout = null;
- var result;
- var storedContext;
- var storedArgs;
- var later = function () {
- previous = Date.now();
- timeout = null;
- result = fn.apply(storedContext, storedArgs);
- if (!timeout) {
- storedContext = null;
- storedArgs = [];
- }
- };
- return function () {
- var args = [];
- for (var _i = 0; _i < arguments.length; _i++) {
- args[_i] = arguments[_i];
- }
- var now = Date.now();
- var remaining = wait - (now - previous);
- storedContext = this;
- storedArgs = args;
- if (remaining <= 0 || remaining > wait) {
- if (timeout) {
- clearTimeout(timeout);
- timeout = null;
- }
- previous = now;
- result = fn.apply(storedContext, storedArgs);
- if (!timeout) {
- storedContext = null;
- storedArgs = [];
- }
- }
- else if (!timeout) {
- timeout = setTimeout(later, remaining);
- }
- return result;
- };
-}
+ function throttle(fn, wait) {
+ if (wait === void 0) { wait = 250; }
+ var previous = 0;
+ var timeout = null;
+ var result;
+ var storedContext;
+ var storedArgs;
+ var later = function () {
+ previous = Date.now();
+ timeout = null;
+ result = fn.apply(storedContext, storedArgs);
+ if (!timeout) {
+ storedContext = null;
+ storedArgs = [];
+ }
+ };
+ return function () {
+ var args = [];
+ for (var _i = 0; _i < arguments.length; _i++) {
+ args[_i] = arguments[_i];
+ }
+ var now = Date.now();
+ var remaining = wait - (now - previous);
+ storedContext = this;
+ storedArgs = args;
+ if (remaining <= 0 || remaining > wait) {
+ if (timeout) {
+ clearTimeout(timeout);
+ timeout = null;
+ }
+ previous = now;
+ result = fn.apply(storedContext, storedArgs);
+ if (!timeout) {
+ storedContext = null;
+ storedArgs = [];
+ }
+ }
+ else if (!timeout) {
+ timeout = setTimeout(later, remaining);
+ }
+ return result;
+ };
+ }
-var SignaturePad = (function () {
- function SignaturePad(canvas, options) {
- if (options === void 0) { options = {}; }
- var _this = this;
- this.canvas = canvas;
- this.options = options;
- this.velocityFilterWeight = options.velocityFilterWeight || 0.7;
- this.minWidth = options.minWidth || 0.5;
- this.maxWidth = options.maxWidth || 2.5;
- this.throttle = ("throttle" in options ? options.throttle : 16);
- this.minDistance = ("minDistance" in options ? options.minDistance : 5);
- if (this.throttle) {
- this._strokeMoveUpdate = throttle(SignaturePad.prototype._strokeUpdate, this.throttle);
- }
- else {
- this._strokeMoveUpdate = SignaturePad.prototype._strokeUpdate;
- }
- this.dotSize = options.dotSize || function () {
- return (this.minWidth + this.maxWidth) / 2;
- };
- this.penColor = options.penColor || "black";
- this.backgroundColor = options.backgroundColor || "rgba(0,0,0,0)";
- this.onBegin = options.onBegin;
- this.onEnd = options.onEnd;
- this._ctx = canvas.getContext("2d");
- this.clear();
- this._handleMouseDown = function (event) {
- if (event.which === 1) {
- _this._mouseButtonDown = true;
- _this._strokeBegin(event);
- }
- };
- this._handleMouseMove = function (event) {
- if (_this._mouseButtonDown) {
- _this._strokeMoveUpdate(event);
- }
- };
- this._handleMouseUp = function (event) {
- if (event.which === 1 && _this._mouseButtonDown) {
- _this._mouseButtonDown = false;
- _this._strokeEnd(event);
- }
- };
- this._handleTouchStart = function (event) {
- event.preventDefault();
- if (event.targetTouches.length === 1) {
- var touch = event.changedTouches[0];
- _this._strokeBegin(touch);
- }
- };
- this._handleTouchMove = function (event) {
- event.preventDefault();
- var touch = event.targetTouches[0];
- _this._strokeMoveUpdate(touch);
- };
- this._handleTouchEnd = function (event) {
- var wasCanvasTouched = event.target === _this.canvas;
- if (wasCanvasTouched) {
- event.preventDefault();
- var touch = event.changedTouches[0];
- _this._strokeEnd(touch);
- }
- };
- this.on();
- }
- 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.fromDataURL = function (dataUrl, options) {
- var _this = this;
- if (options === void 0) { options = {}; }
- var image = new Image();
- var ratio = options.ratio || window.devicePixelRatio || 1;
- var width = options.width || (this.canvas.width / ratio);
- var height = options.height || (this.canvas.height / ratio);
- this._reset();
- image.src = dataUrl;
- image.onload = function () {
- _this._ctx.drawImage(image, 0, 0, width, height);
- };
- this._isEmpty = false;
- };
- SignaturePad.prototype.toDataURL = function (type, encoderOptions) {
- switch (type) {
- case "image/svg+xml":
- return this._toSVG();
- default:
- return this.canvas.toDataURL(type, encoderOptions);
- }
- };
- SignaturePad.prototype.on = function () {
- this._handleMouseEvents();
- this._handleTouchEvents();
- };
- SignaturePad.prototype.off = function () {
- this.canvas.style.msTouchAction = "auto";
- this.canvas.style.touchAction = "auto";
- 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.fromData = function (pointGroups) {
- var _this = this;
- this.clear();
- this._fromData(pointGroups, function (_a) {
- var color = _a.color, curve = _a.curve;
- return _this._drawCurve({ color: color, curve: curve });
- }, function (_a) {
- var color = _a.color, point = _a.point;
- return _this._drawDot({ color: color, point: point });
- });
- this._data = pointGroups;
- };
- SignaturePad.prototype.toData = function () {
- return this._data;
- };
- SignaturePad.prototype._strokeBegin = function (event) {
- var newPointGroup = {
- color: this.penColor,
- points: []
- };
- this._data.push(newPointGroup);
- this._reset();
- this._strokeUpdate(event);
- if (typeof this.onBegin === "function") {
- this.onBegin(event);
- }
- };
- SignaturePad.prototype._strokeUpdate = function (event) {
- var x = event.clientX;
- var y = event.clientY;
- var point = this._createPoint(x, y);
- var lastPointGroup = this._data[this._data.length - 1];
- var lastPoints = lastPointGroup.points;
- var lastPoint = lastPoints.length > 0 && lastPoints[lastPoints.length - 1];
- var isLastPointTooClose = lastPoint ? point.distanceTo(lastPoint) <= this.minDistance : false;
- var color = lastPointGroup.color;
- if (!lastPoint || !(lastPoint && isLastPointTooClose)) {
- var curve = this._addPoint(point);
- if (!lastPoint) {
- this._drawDot({ color: color, point: point });
- }
- else if (curve) {
- this._drawCurve({ color: color, curve: curve });
- }
- lastPoints.push({
- time: point.time,
- x: point.x,
- y: point.y
- });
- }
- };
- SignaturePad.prototype._strokeEnd = function (event) {
- this._strokeUpdate(event);
- 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 () {
- 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._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) {
- var rect = this.canvas.getBoundingClientRect();
- return new Point(x - rect.left, y - rect.top, new Date().getTime());
- };
- SignaturePad.prototype._addPoint = function (point) {
- var _points = this._points;
- _points.push(point);
- if (_points.length > 2) {
- if (_points.length === 3) {
- _points.unshift(_points[0]);
- }
- var widths = this._calculateCurveWidths(_points[1], _points[2]);
- var curve = Bezier.fromPoints(_points, widths);
- _points.shift();
- return curve;
- }
- return null;
- };
- SignaturePad.prototype._calculateCurveWidths = function (startPoint, endPoint) {
- var velocity = (this.velocityFilterWeight * endPoint.velocityFrom(startPoint))
- + ((1 - this.velocityFilterWeight) * this._lastVelocity);
- var newWidth = this._strokeWidth(velocity);
- var widths = {
- end: newWidth,
- start: this._lastWidth
- };
- this._lastVelocity = velocity;
- this._lastWidth = newWidth;
- return widths;
- };
- SignaturePad.prototype._strokeWidth = function (velocity) {
- return Math.max(this.maxWidth / (velocity + 1), this.minWidth);
- };
- SignaturePad.prototype._drawCurveSegment = function (x, y, width) {
- var ctx = this._ctx;
- ctx.moveTo(x, y);
- ctx.arc(x, y, width, 0, 2 * Math.PI, false);
- this._isEmpty = false;
- };
- SignaturePad.prototype._drawCurve = function (_a) {
- var color = _a.color, curve = _a.curve;
- var ctx = this._ctx;
- var widthDelta = curve.endWidth - curve.startWidth;
- var drawSteps = Math.floor(curve.length()) * 2;
- ctx.beginPath();
- ctx.fillStyle = color;
- for (var i = 0; i < drawSteps; i += 1) {
- 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 = curve.startWidth + (ttt * widthDelta);
- this._drawCurveSegment(x, y, width);
- }
- ctx.closePath();
- ctx.fill();
- };
- SignaturePad.prototype._drawDot = function (_a) {
- var color = _a.color, point = _a.point;
- var ctx = this._ctx;
- var width = typeof this.dotSize === "function" ? this.dotSize() : this.dotSize;
- ctx.beginPath();
- this._drawCurveSegment(point.x, point.y, width);
- ctx.closePath();
- ctx.fillStyle = color;
- ctx.fill();
- };
- SignaturePad.prototype._fromData = function (pointGroups, drawCurve, drawDot) {
- for (var _i = 0, pointGroups_1 = pointGroups; _i < pointGroups_1.length; _i++) {
- var group = pointGroups_1[_i];
- var color = group.color, points = group.points;
- if (points.length > 1) {
- for (var j = 0; j < points.length; j += 1) {
- var basicPoint = points[j];
- var point = new Point(basicPoint.x, basicPoint.y, basicPoint.time);
- this.penColor = color;
- if (j === 0) {
- this._reset();
- }
- var curve = this._addPoint(point);
- if (curve) {
- drawCurve({ color: color, curve: curve });
- }
- }
- }
- else {
- this._reset();
- drawDot({
- color: color,
- point: points[0]
- });
- }
- }
- };
- SignaturePad.prototype._toSVG = function () {
- var _this = this;
- var pointGroups = this._data;
- var ratio = Math.max(window.devicePixelRatio || 1, 1);
- var minX = 0;
- var minY = 0;
- var maxX = this.canvas.width / ratio;
- var maxY = this.canvas.height / ratio;
- var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
- svg.setAttribute("width", this.canvas.width.toString());
- svg.setAttribute("height", this.canvas.height.toString());
- this._fromData(pointGroups, function (_a) {
- var color = _a.color, curve = _a.curve;
- var path = document.createElement("path");
- if (!isNaN(curve.control1.x) &&
- !isNaN(curve.control1.y) &&
- !isNaN(curve.control2.x) &&
- !isNaN(curve.control2.y)) {
- var attr = "M " + curve.startPoint.x.toFixed(3) + "," + curve.startPoint.y.toFixed(3) + " "
- + ("C " + curve.control1.x.toFixed(3) + "," + curve.control1.y.toFixed(3) + " ")
- + (curve.control2.x.toFixed(3) + "," + curve.control2.y.toFixed(3) + " ")
- + (curve.endPoint.x.toFixed(3) + "," + curve.endPoint.y.toFixed(3));
- path.setAttribute("d", attr);
- path.setAttribute("stroke-width", (curve.endWidth * 2.25).toFixed(3));
- path.setAttribute("stroke", color);
- path.setAttribute("fill", "none");
- path.setAttribute("stroke-linecap", "round");
- svg.appendChild(path);
- }
- }, function (_a) {
- var color = _a.color, point = _a.point;
- var circle = document.createElement("circle");
- var dotSize = typeof _this.dotSize === "function" ? _this.dotSize() : _this.dotSize;
- circle.setAttribute("r", dotSize.toString());
- circle.setAttribute("cx", point.x.toString());
- circle.setAttribute("cy", point.y.toString());
- circle.setAttribute("fill", color);
- svg.appendChild(circle);
- });
- var prefix = "data:image/svg+xml;base64,";
- var header = "";
- var data = header + body + footer;
- return prefix + btoa(data);
- };
- return SignaturePad;
-}());
+ var SignaturePad = (function () {
+ function SignaturePad(canvas, options) {
+ if (options === void 0) { options = {}; }
+ var _this = this;
+ this.canvas = canvas;
+ this.options = options;
+ this._handleMouseDown = function (event) {
+ if (event.which === 1) {
+ _this._mouseButtonDown = true;
+ _this._strokeBegin(event);
+ }
+ };
+ this._handleMouseMove = function (event) {
+ if (_this._mouseButtonDown) {
+ _this._strokeMoveUpdate(event);
+ }
+ };
+ this._handleMouseUp = function (event) {
+ if (event.which === 1 && _this._mouseButtonDown) {
+ _this._mouseButtonDown = false;
+ _this._strokeEnd(event);
+ }
+ };
+ this._handleTouchStart = function (event) {
+ event.preventDefault();
+ if (event.targetTouches.length === 1) {
+ var touch = event.changedTouches[0];
+ _this._strokeBegin(touch);
+ }
+ };
+ this._handleTouchMove = function (event) {
+ event.preventDefault();
+ var touch = event.targetTouches[0];
+ _this._strokeMoveUpdate(touch);
+ };
+ this._handleTouchEnd = function (event) {
+ var wasCanvasTouched = event.target === _this.canvas;
+ if (wasCanvasTouched) {
+ event.preventDefault();
+ var touch = event.changedTouches[0];
+ _this._strokeEnd(touch);
+ }
+ };
+ this.velocityFilterWeight = options.velocityFilterWeight || 0.7;
+ this.minWidth = options.minWidth || 0.5;
+ this.maxWidth = options.maxWidth || 2.5;
+ this.throttle = ("throttle" in options ? options.throttle : 16);
+ this.minDistance = ("minDistance" in options ? options.minDistance : 5);
+ if (this.throttle) {
+ this._strokeMoveUpdate = throttle(SignaturePad.prototype._strokeUpdate, this.throttle);
+ }
+ else {
+ this._strokeMoveUpdate = SignaturePad.prototype._strokeUpdate;
+ }
+ this.dotSize = options.dotSize || function () {
+ return (this.minWidth + this.maxWidth) / 2;
+ };
+ this.penColor = options.penColor || "black";
+ this.backgroundColor = options.backgroundColor || "rgba(0,0,0,0)";
+ this.onBegin = options.onBegin;
+ this.onEnd = options.onEnd;
+ this._ctx = canvas.getContext("2d");
+ this.clear();
+ this.on();
+ }
+ 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.fromDataURL = function (dataUrl, options, callback) {
+ var _this = this;
+ if (options === void 0) { options = {}; }
+ var image = new Image();
+ var ratio = options.ratio || window.devicePixelRatio || 1;
+ var width = options.width || (this.canvas.width / ratio);
+ var height = options.height || (this.canvas.height / ratio);
+ this._reset();
+ image.onload = function () {
+ _this._ctx.drawImage(image, 0, 0, width, height);
+ if (callback) {
+ callback();
+ }
+ };
+ image.onerror = function (error) {
+ if (callback) {
+ callback(error);
+ }
+ };
+ image.src = dataUrl;
+ this._isEmpty = false;
+ };
+ SignaturePad.prototype.toDataURL = function (type, encoderOptions) {
+ if (type === void 0) { type = "image/png"; }
+ switch (type) {
+ case "image/svg+xml":
+ return this._toSVG();
+ default:
+ return this.canvas.toDataURL(type, encoderOptions);
+ }
+ };
+ SignaturePad.prototype.on = function () {
+ this._handleMouseEvents();
+ if ("ontouchstart" in window) {
+ this._handleTouchEvents();
+ }
+ };
+ SignaturePad.prototype.off = function () {
+ this.canvas.style.msTouchAction = "auto";
+ this.canvas.style.touchAction = "auto";
+ 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.fromData = function (pointGroups) {
+ var _this = this;
+ this.clear();
+ this._fromData(pointGroups, function (_a) {
+ var color = _a.color, curve = _a.curve;
+ return _this._drawCurve({ color: color, curve: curve });
+ }, function (_a) {
+ var color = _a.color, point = _a.point;
+ return _this._drawDot({ color: color, point: point });
+ });
+ this._data = pointGroups;
+ };
+ SignaturePad.prototype.toData = function () {
+ return this._data;
+ };
+ SignaturePad.prototype._strokeBegin = function (event) {
+ var newPointGroup = {
+ color: this.penColor,
+ points: []
+ };
+ this._data.push(newPointGroup);
+ this._reset();
+ this._strokeUpdate(event);
+ if (typeof this.onBegin === "function") {
+ this.onBegin(event);
+ }
+ };
+ SignaturePad.prototype._strokeUpdate = function (event) {
+ var x = event.clientX;
+ var y = event.clientY;
+ var point = this._createPoint(x, y);
+ var lastPointGroup = this._data[this._data.length - 1];
+ var lastPoints = lastPointGroup.points;
+ var lastPoint = lastPoints.length > 0 && lastPoints[lastPoints.length - 1];
+ var isLastPointTooClose = lastPoint ? point.distanceTo(lastPoint) <= this.minDistance : false;
+ var color = lastPointGroup.color;
+ if (!lastPoint || !(lastPoint && isLastPointTooClose)) {
+ var curve = this._addPoint(point);
+ if (!lastPoint) {
+ this._drawDot({ color: color, point: point });
+ }
+ else if (curve) {
+ this._drawCurve({ color: color, curve: curve });
+ }
+ lastPoints.push({
+ time: point.time,
+ x: point.x,
+ y: point.y
+ });
+ }
+ };
+ SignaturePad.prototype._strokeEnd = function (event) {
+ this._strokeUpdate(event);
+ 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 () {
+ 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._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) {
+ var rect = this.canvas.getBoundingClientRect();
+ return new Point(x - rect.left, y - rect.top, new Date().getTime());
+ };
+ SignaturePad.prototype._addPoint = function (point) {
+ var _points = this._points;
+ _points.push(point);
+ if (_points.length > 2) {
+ if (_points.length === 3) {
+ _points.unshift(_points[0]);
+ }
+ var widths = this._calculateCurveWidths(_points[1], _points[2]);
+ var curve = Bezier.fromPoints(_points, widths);
+ _points.shift();
+ return curve;
+ }
+ return null;
+ };
+ SignaturePad.prototype._calculateCurveWidths = function (startPoint, endPoint) {
+ var velocity = (this.velocityFilterWeight * endPoint.velocityFrom(startPoint))
+ + ((1 - this.velocityFilterWeight) * this._lastVelocity);
+ var newWidth = this._strokeWidth(velocity);
+ var widths = {
+ end: newWidth,
+ start: this._lastWidth
+ };
+ this._lastVelocity = velocity;
+ this._lastWidth = newWidth;
+ return widths;
+ };
+ SignaturePad.prototype._strokeWidth = function (velocity) {
+ return Math.max(this.maxWidth / (velocity + 1), this.minWidth);
+ };
+ SignaturePad.prototype._drawCurveSegment = function (x, y, width) {
+ var ctx = this._ctx;
+ ctx.moveTo(x, y);
+ ctx.arc(x, y, width, 0, 2 * Math.PI, false);
+ this._isEmpty = false;
+ };
+ SignaturePad.prototype._drawCurve = function (_a) {
+ var color = _a.color, curve = _a.curve;
+ var ctx = this._ctx;
+ var widthDelta = curve.endWidth - curve.startWidth;
+ var drawSteps = Math.floor(curve.length()) * 2;
+ ctx.beginPath();
+ ctx.fillStyle = color;
+ for (var i = 0; i < drawSteps; i += 1) {
+ 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 = curve.startWidth + (ttt * widthDelta);
+ this._drawCurveSegment(x, y, width);
+ }
+ ctx.closePath();
+ ctx.fill();
+ };
+ SignaturePad.prototype._drawDot = function (_a) {
+ var color = _a.color, point = _a.point;
+ var ctx = this._ctx;
+ var width = typeof this.dotSize === "function" ? this.dotSize() : this.dotSize;
+ ctx.beginPath();
+ this._drawCurveSegment(point.x, point.y, width);
+ ctx.closePath();
+ ctx.fillStyle = color;
+ ctx.fill();
+ };
+ SignaturePad.prototype._fromData = function (pointGroups, drawCurve, drawDot) {
+ for (var _i = 0, pointGroups_1 = pointGroups; _i < pointGroups_1.length; _i++) {
+ var group = pointGroups_1[_i];
+ var color = group.color, points = group.points;
+ if (points.length > 1) {
+ for (var j = 0; j < points.length; j += 1) {
+ var basicPoint = points[j];
+ var point = new Point(basicPoint.x, basicPoint.y, basicPoint.time);
+ this.penColor = color;
+ if (j === 0) {
+ this._reset();
+ }
+ var curve = this._addPoint(point);
+ if (curve) {
+ drawCurve({ color: color, curve: curve });
+ }
+ }
+ }
+ else {
+ this._reset();
+ drawDot({
+ color: color,
+ point: points[0]
+ });
+ }
+ }
+ };
+ SignaturePad.prototype._toSVG = function () {
+ var _this = this;
+ var pointGroups = this._data;
+ var ratio = Math.max(window.devicePixelRatio || 1, 1);
+ var minX = 0;
+ var minY = 0;
+ var maxX = this.canvas.width / ratio;
+ var maxY = this.canvas.height / ratio;
+ var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
+ svg.setAttribute("width", this.canvas.width.toString());
+ svg.setAttribute("height", this.canvas.height.toString());
+ this._fromData(pointGroups, function (_a) {
+ var color = _a.color, curve = _a.curve;
+ var path = document.createElement("path");
+ if (!isNaN(curve.control1.x) &&
+ !isNaN(curve.control1.y) &&
+ !isNaN(curve.control2.x) &&
+ !isNaN(curve.control2.y)) {
+ var attr = "M " + curve.startPoint.x.toFixed(3) + "," + curve.startPoint.y.toFixed(3) + " "
+ + ("C " + curve.control1.x.toFixed(3) + "," + curve.control1.y.toFixed(3) + " ")
+ + (curve.control2.x.toFixed(3) + "," + curve.control2.y.toFixed(3) + " ")
+ + (curve.endPoint.x.toFixed(3) + "," + curve.endPoint.y.toFixed(3));
+ path.setAttribute("d", attr);
+ path.setAttribute("stroke-width", (curve.endWidth * 2.25).toFixed(3));
+ path.setAttribute("stroke", color);
+ path.setAttribute("fill", "none");
+ path.setAttribute("stroke-linecap", "round");
+ svg.appendChild(path);
+ }
+ }, function (_a) {
+ var color = _a.color, point = _a.point;
+ var circle = document.createElement("circle");
+ var dotSize = typeof _this.dotSize === "function" ? _this.dotSize() : _this.dotSize;
+ circle.setAttribute("r", dotSize.toString());
+ circle.setAttribute("cx", point.x.toString());
+ circle.setAttribute("cy", point.y.toString());
+ circle.setAttribute("fill", color);
+ svg.appendChild(circle);
+ });
+ var prefix = "data:image/svg+xml;base64,";
+ var header = "";
+ var data = header + body + footer;
+ return prefix + btoa(data);
+ };
+ return SignaturePad;
+ }());
-return SignaturePad;
+ return SignaturePad;
})));
diff --git a/package.json b/package.json
index 85b2d764..612e29ba 100644
--- a/package.json
+++ b/package.json
@@ -30,27 +30,31 @@
"docs"
],
"devDependencies": {
+ "@types/jest": "^22.2.3",
+ "canvas-prebuilt": "^1.6.5-prerelease.1",
"del": "^3.0.0",
"del-cli": "^1.1.0",
+ "jest": "^22.4.3",
"rollup": "^0.57.1",
"rollup-plugin-tslint": "^0.1.34",
"rollup-plugin-typescript2": "^0.13.0",
"rollup-plugin-uglify": "^3.0.0",
- "typescript": "^2.8.1",
- "@types/jest": "^22.2.3",
- "jest": "^22.4.3",
- "ts-jest": "^22.4.2"
+ "ts-jest": "^22.4.2",
+ "typescript": "^2.8.1"
},
"jest": {
- "transform": {
- "^.+\\.tsx?$": "ts-jest"
- },
- "testMatch": [
- "/tests/**/*.ts"
- ],
"moduleFileExtensions": [
"ts",
"js"
- ]
+ ],
+ "testEnvironmentOptions": {
+ "resources": "usable"
+ },
+ "testMatch": [
+ "/tests/**/*.test.ts"
+ ],
+ "transform": {
+ "^.+\\.tsx?$": "ts-jest"
+ }
}
}
diff --git a/src/signature_pad.ts b/src/signature_pad.ts
index 6fdf6167..ae17c0e2 100644
--- a/src/signature_pad.ts
+++ b/src/signature_pad.ts
@@ -54,12 +54,6 @@ export default class SignaturePad {
private _lastVelocity: number;
private _lastWidth: number;
private _strokeMoveUpdate: (event: MouseEvent | Touch) => void;
- private _handleMouseDown: (event: MouseEvent) => void;
- private _handleMouseMove: (event: MouseEvent) => void;
- private _handleMouseUp: (event: MouseEvent) => void;
- private _handleTouchStart: (event: TouchEvent) => void;
- private _handleTouchMove: (event: TouchEvent) => void;
- private _handleTouchEnd: (event: TouchEvent) => void;
/* tslint:enable: variable-name */
constructor(private canvas: HTMLCanvasElement, private options: IOptions = {}) {
@@ -86,56 +80,6 @@ export default class SignaturePad {
this._ctx = canvas.getContext("2d") as CanvasRenderingContext2D;
this.clear();
- // We need add these inline so they are available to unbind while still having
- // access to 'this'.
- this._handleMouseDown = (event: MouseEvent) => {
- if (event.which === 1) {
- this._mouseButtonDown = true;
- this._strokeBegin(event);
- }
- };
-
- this._handleMouseMove = (event) => {
- if (this._mouseButtonDown) {
- this._strokeMoveUpdate(event);
- }
- };
-
- this._handleMouseUp = (event) => {
- if (event.which === 1 && this._mouseButtonDown) {
- this._mouseButtonDown = false;
- this._strokeEnd(event);
- }
- };
-
- this._handleTouchStart = (event) => {
- // Prevent scrolling.
- event.preventDefault();
-
- if (event.targetTouches.length === 1) {
- const touch = event.changedTouches[0];
- this._strokeBegin(touch);
- }
- };
-
- this._handleTouchMove = (event) => {
- // Prevent scrolling.
- event.preventDefault();
-
- const touch = event.targetTouches[0];
- this._strokeMoveUpdate(touch);
- };
-
- this._handleTouchEnd = (event) => {
- const wasCanvasTouched = event.target === this.canvas;
- if (wasCanvasTouched) {
- event.preventDefault();
-
- const touch = event.changedTouches[0];
- this._strokeEnd(touch);
- }
- };
-
// Enable mouse and touch event handlers
this.on();
}
@@ -154,21 +98,31 @@ export default class SignaturePad {
this._isEmpty = true;
}
- public fromDataURL(dataUrl: string, options: { ratio?: number, width?: number, height?: number } = {}): void {
+ public fromDataURL(
+ dataUrl: string,
+ options: { ratio?: number, width?: number, height?: number } = {},
+ callback?: (error?: ErrorEvent) => void,
+ ): void {
const image = new Image();
const ratio = options.ratio || window.devicePixelRatio || 1;
const width = options.width || (this.canvas.width / ratio);
const height = options.height || (this.canvas.height / ratio);
this._reset();
- image.src = dataUrl;
+
image.onload = () => {
this._ctx.drawImage(image, 0, 0, width, height);
+ if (callback) { callback(); }
+ };
+ image.onerror = (error) => {
+ if (callback) { callback(error); }
};
+ image.src = dataUrl;
+
this._isEmpty = false;
}
- public toDataURL(type?: string, encoderOptions?: number) {
+ public toDataURL(type = "image/png", encoderOptions?: number) {
switch (type) {
case "image/svg+xml":
return this._toSVG();
@@ -179,7 +133,10 @@ export default class SignaturePad {
public on(): void {
this._handleMouseEvents();
- this._handleTouchEvents();
+
+ if ("ontouchstart" in window) {
+ this._handleTouchEvents();
+ }
}
public off(): void {
@@ -191,8 +148,12 @@ export default class SignaturePad {
this.canvas.removeEventListener("mousemove", this._handleMouseMove);
document.removeEventListener("mouseup", this._handleMouseUp);
+ // TS 2.8.1 has incorrect type definition for touch event handlers
+ // @ts-ignore
this.canvas.removeEventListener("touchstart", this._handleTouchStart);
+ // @ts-ignore
this.canvas.removeEventListener("touchmove", this._handleTouchMove);
+ // @ts-ignore
this.canvas.removeEventListener("touchend", this._handleTouchEnd);
}
@@ -216,6 +177,55 @@ export default class SignaturePad {
return this._data;
}
+ // Event handlers
+ private _handleMouseDown = (event: MouseEvent): void => {
+ if (event.which === 1) {
+ this._mouseButtonDown = true;
+ this._strokeBegin(event);
+ }
+ }
+
+ private _handleMouseMove = (event: MouseEvent): void => {
+ if (this._mouseButtonDown) {
+ this._strokeMoveUpdate(event);
+ }
+ }
+
+ private _handleMouseUp = (event: MouseEvent): void => {
+ if (event.which === 1 && this._mouseButtonDown) {
+ this._mouseButtonDown = false;
+ this._strokeEnd(event);
+ }
+ }
+
+ private _handleTouchStart = (event: TouchEvent): void => {
+ // Prevent scrolling.
+ event.preventDefault();
+
+ if (event.targetTouches.length === 1) {
+ const touch = event.changedTouches[0];
+ this._strokeBegin(touch);
+ }
+ }
+
+ private _handleTouchMove = (event: TouchEvent): void => {
+ // Prevent scrolling.
+ event.preventDefault();
+
+ const touch = event.targetTouches[0];
+ this._strokeMoveUpdate(touch);
+ }
+
+ private _handleTouchEnd = (event: TouchEvent): void => {
+ const wasCanvasTouched = event.target === this.canvas;
+ if (wasCanvasTouched) {
+ event.preventDefault();
+
+ const touch = event.changedTouches[0];
+ this._strokeEnd(touch);
+ }
+ }
+
// Private methods
private _strokeBegin(event: MouseEvent | Touch): void {
const newPointGroup = {
@@ -282,8 +292,12 @@ export default class SignaturePad {
this.canvas.style.msTouchAction = "none";
this.canvas.style.touchAction = "none";
+ // TS 2.8.1 has incorrect type definition for touch event handlers
+ // @ts-ignore
this.canvas.addEventListener("touchstart", this._handleTouchStart);
+ // @ts-ignore
this.canvas.addEventListener("touchmove", this._handleTouchMove);
+ // @ts-ignore
this.canvas.addEventListener("touchend", this._handleTouchEnd);
}
diff --git a/tests/fixtures/face.ts b/tests/fixtures/face.ts
new file mode 100644
index 00000000..c596d49e
--- /dev/null
+++ b/tests/fixtures/face.ts
@@ -0,0 +1,106 @@
+export const dataURL = {
+ png: "data:image/png;base64,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",
+ svg: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgMzAwIDE1MCIgd2lkdGg9IjMwMCIgaGVpZ2h0PSIxNTAiPjxjaXJjbGUgcj0iMS41IiBjeD0iMTI1IiBjeT0iNTQiIGZpbGw9ImJsYWNrIj48L2NpcmNsZT48Y2lyY2xlIHI9IjEuNSIgY3g9IjE3NSIgY3k9IjU0IiBmaWxsPSJibGFjayI+PC9jaXJjbGU+PHBhdGggZD0iTSA4My4wMDAsNTcuMDAwIEMgODMuMTc0LDYwLjUxMCA4NC4wMDAsNjAuMDAwIDg1LjAwMCw2My4wMDAiIHN0cm9rZS13aWR0aD0iNS41MDgiIHN0cm9rZT0iYmxhY2siIGZpbGw9Im5vbmUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCI+PC9wYXRoPjxwYXRoIGQ9Ik0gODUuMDAwLDYzLjAwMCBDIDg5LjA2MCw2OC4wNDggODguNjc0LDY4LjAxMCA5NC4wMDAsNzIuMDAwIiBzdHJva2Utd2lkdGg9IjQuNDA3IiBzdHJva2U9ImJsYWNrIiBmaWxsPSJub25lIiBzdHJva2UtbGluZWNhcD0icm91bmQiPjwvcGF0aD48cGF0aCBkPSJNIDk0LjAwMCw3Mi4wMDAgQyA5OS40ODgsNzUuNTE5IDk5LjA2MCw3Ni4wNDggMTA1LjAwMCw3OS4wMDAiIHN0cm9rZS13aWR0aD0iMy40MDIiIHN0cm9rZT0iYmxhY2siIGZpbGw9Im5vbmUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCI+PC9wYXRoPjxwYXRoIGQ9Ik0gMTA1LjAwMCw3OS4wMDAgQyAxMDguNzAwLDgyLjE1NyAxMDguOTg4LDgxLjUxOSAxMTMuMDAwLDg0LjAwMCIgc3Ryb2tlLXdpZHRoPSIzLjQ5NyIgc3Ryb2tlPSJibGFjayIgZmlsbD0ibm9uZSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIj48L3BhdGg+PHBhdGggZD0iTSAxMTMuMDAwLDg0LjAwMCBDIDExOS41MDAsODYuMDAwIDExOS4yMDAsODYuNjU3IDEyNi4wMDAsODguMDAwIiBzdHJva2Utd2lkdGg9IjMuMjI4IiBzdHJva2U9ImJsYWNrIiBmaWxsPSJub25lIiBzdHJva2UtbGluZWNhcD0icm91bmQiPjwvcGF0aD48cGF0aCBkPSJNIDEyNi4wMDAsODguMDAwIEMgMTMyLjQwMiw5MC40MTYgMTMyLjUwMCw5MC4wMDAgMTM5LjAwMCw5Mi4wMDAiIHN0cm9rZS13aWR0aD0iMy4yMTEiIHN0cm9rZT0iYmxhY2siIGZpbGw9Im5vbmUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCI+PC9wYXRoPjxwYXRoIGQ9Ik0gMTM5LjAwMCw5Mi4wMDAgQyAxNDQuOTYxLDkzLjQ3NSAxNDQuOTAyLDkzLjQxNiAxNTEuMDAwLDk0LjAwMCIgc3Ryb2tlLXdpZHRoPSIzLjI1OCIgc3Ryb2tlPSJibGFjayIgZmlsbD0ibm9uZSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIj48L3BhdGg+PHBhdGggZD0iTSAxNTEuMDAwLDk0LjAwMCBDIDE1Ni41NjksOTQuNjUyIDE1Ni40NjEsOTQuNDc1IDE2Mi4wMDAsOTQuMDAwIiBzdHJva2Utd2lkdGg9IjMuMzY2IiBzdHJva2U9ImJsYWNrIiBmaWxsPSJub25lIiBzdHJva2UtbGluZWNhcD0icm91bmQiPjwvcGF0aD48cGF0aCBkPSJNIDE2Mi4wMDAsOTQuMDAwIEMgMTY4Ljk5Miw5Mi40NjAgMTY5LjA2OSw5My4xNTIgMTc2LjAwMCw5MS4wMDAiIHN0cm9rZS13aWR0aD0iMy4wNzgiIHN0cm9rZT0iYmxhY2siIGZpbGw9Im5vbmUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCI+PC9wYXRoPjxwYXRoIGQ9Ik0gMTc2LjAwMCw5MS4wMDAgQyAxODEuMDcyLDkwLjI4NSAxODAuOTkyLDg5Ljk2MCAxODYuMDAwLDg5LjAwMCIgc3Ryb2tlLXdpZHRoPSIzLjM3MiIgc3Ryb2tlPSJibGFjayIgZmlsbD0ibm9uZSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIj48L3BhdGg+PHBhdGggZD0iTSAxODYuMDAwLDg5LjAwMCBDIDE5Mi41ODIsODcuMjMyIDE5Mi41NzIsODcuMjg1IDE5OS4wMDAsODUuMDAwIiBzdHJva2Utd2lkdGg9IjMuMTMzIiBzdHJva2U9ImJsYWNrIiBmaWxsPSJub25lIiBzdHJva2UtbGluZWNhcD0icm91bmQiPjwvcGF0aD48cGF0aCBkPSJNIDE5OS4wMDAsODUuMDAwIEMgMjA0LjI5Nyw4My41NjUgMjA0LjA4Miw4My4yMzIgMjA5LjAwMCw4MS4wMDAiIHN0cm9rZS13aWR0aD0iMy4yOTAiIHN0cm9rZT0iYmxhY2siIGZpbGw9Im5vbmUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCI+PC9wYXRoPjxwYXRoIGQ9Ik0gMjA5LjAwMCw4MS4wMDAgQyAyMTUuMzM0LDc3LjQwNyAyMTUuMjk3LDc3LjU2NSAyMjEuMDAwLDczLjAwMCIgc3Ryb2tlLXdpZHRoPSIzLjExMyIgc3Ryb2tlPSJibGFjayIgZmlsbD0ibm9uZSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIj48L3BhdGg+PHBhdGggZD0iTSAyMjEuMDAwLDczLjAwMCBDIDIyNC4xMTUsNzAuMTA3IDIyNC4zMzQsNzAuNDA3IDIyNy4wMDAsNjcuMDAwIiBzdHJva2Utd2lkdGg9IjMuNTM1IiBzdHJva2U9ImJsYWNrIiBmaWxsPSJub25lIiBzdHJva2UtbGluZWNhcD0icm91bmQiPjwvcGF0aD48L3N2Zz4=",
+};
+
+export const json = [{
+ "color": "black",
+ "points": [{
+ "time": 1523730547109,
+ "x": 125,
+ "y": 54
+ }]
+ },
+ {
+ "color": "black",
+ "points": [{
+ "time": 1523730547775,
+ "x": 175,
+ "y": 54
+ }]
+ },
+ {
+ "color": "black",
+ "points": [{
+ "time": 1523730548448,
+ "x": 83,
+ "y": 57
+ },
+ {
+ "time": 1523730548657,
+ "x": 85,
+ "y": 63
+ },
+ {
+ "time": 1523730548690,
+ "x": 94,
+ "y": 72
+ },
+ {
+ "time": 1523730548706,
+ "x": 105,
+ "y": 79
+ },
+ {
+ "time": 1523730548722,
+ "x": 113,
+ "y": 84
+ },
+ {
+ "time": 1523730548739,
+ "x": 126,
+ "y": 88
+ },
+ {
+ "time": 1523730548757,
+ "x": 139,
+ "y": 92
+ },
+ {
+ "time": 1523730548774,
+ "x": 151,
+ "y": 94
+ },
+ {
+ "time": 1523730548791,
+ "x": 162,
+ "y": 94
+ },
+ {
+ "time": 1523730548807,
+ "x": 176,
+ "y": 91
+ },
+ {
+ "time": 1523730548824,
+ "x": 186,
+ "y": 89
+ },
+ {
+ "time": 1523730548840,
+ "x": 199,
+ "y": 85
+ },
+ {
+ "time": 1523730548856,
+ "x": 209,
+ "y": 81
+ },
+ {
+ "time": 1523730548873,
+ "x": 221,
+ "y": 73
+ },
+ {
+ "time": 1523730548890,
+ "x": 227,
+ "y": 67
+ },
+ {
+ "time": 1523730548924,
+ "x": 234,
+ "y": 59
+ }
+ ]
+ }
+];
diff --git a/tests/signature_pad.test.ts b/tests/signature_pad.test.ts
new file mode 100644
index 00000000..248cc41d
--- /dev/null
+++ b/tests/signature_pad.test.ts
@@ -0,0 +1,89 @@
+import SignaturePad from "../src/signature_pad";
+import { json, dataURL } from "./fixtures/face";
+
+let canvas: HTMLCanvasElement;
+
+beforeAll(() => {
+ canvas = document.createElement('canvas');
+ canvas.setAttribute("width", "300");
+ canvas.setAttribute("height", "150");
+})
+
+describe("#constructor", () => {
+ it("returns an instance of SignaturePad", () => {
+ const pad = new SignaturePad(canvas);
+
+ expect(pad).toBeInstanceOf(SignaturePad);
+ });
+
+ it("allows to set 'throttle' to 0", () => {
+ const pad = new SignaturePad(canvas, { throttle: 0 });
+
+ expect(pad.throttle).toBe(0);
+ });
+
+ it("allows to set 'minDistance' to 0", () => {
+ const pad = new SignaturePad(canvas, { minDistance: 0 });
+
+ expect(pad.minDistance).toBe(0);
+ });
+});
+
+describe("#clear", () => {
+ it.skip("clears canvas", () => {});
+
+ it("clears data structures", () => {
+ const pad = new SignaturePad(canvas);
+
+ pad.fromData(json);
+ expect(pad.isEmpty()).toBe(false);
+
+ pad.clear();
+
+ expect(pad.isEmpty()).toBe(true);
+ expect(pad.toData()).toEqual([]);
+ });
+});
+
+describe("#isEmpty", () => {
+ it("returns true if pad is empty", () => {
+ const pad = new SignaturePad(canvas);
+
+ expect(pad.isEmpty()).toBe(true);
+ });
+
+ it("returns false if pad is not empty", () => {
+ const pad = new SignaturePad(canvas);
+ pad.fromData(json);
+
+ expect(pad.isEmpty()).toBe(false);
+ });
+});
+
+describe("#fromData", () => {});
+
+describe("#toData", () => {
+ it("returns JSON with point groups", () => {
+ const pad = new SignaturePad(canvas);
+ pad.fromData(json);
+
+ expect(pad.toData()).toEqual(json);
+ });
+});
+
+describe("#fromDataURL", () => {});
+
+describe("#toDataURL", () => {
+ // Unfortunately, results of Canvas#toDataURL depend on a platform :/
+ it.skip("returns PNG image in data URL format");
+
+ // Synchronous Canvas#toDataURL for JPEG images is not supported by 'canvas' library :/
+ it.skip("returns JPG image in data URL format");
+
+ it("returns SVG image in data URL format", () => {
+ const pad = new SignaturePad(canvas);
+ pad.fromData(json);
+
+ expect(pad.toDataURL('image/svg+xml')).toEqual(dataURL.svg);
+ });
+});
diff --git a/tslint.json b/tslint.json
index 2c2e962c..41c44eac 100644
--- a/tslint.json
+++ b/tslint.json
@@ -10,6 +10,15 @@
"method": "never",
"named": "never"
}
+ },
+
+ "variable-name": {
+ "options": [
+ "ban-keywords",
+ "check-format",
+ "allow-pascal-case",
+ "allow-leading-underscore"
+ ]
}
}
}
diff --git a/yarn.lock b/yarn.lock
index 10aa96fe..c7f4e9be 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -527,6 +527,14 @@ camelcase@^4.0.0, camelcase@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd"
+canvas-prebuilt@^1.6.5-prerelease.1:
+ version "1.6.5-prerelease.1"
+ resolved "https://registry.yarnpkg.com/canvas-prebuilt/-/canvas-prebuilt-1.6.5-prerelease.1.tgz#6814b20b9c80835dcc24bfd6199147288630521c"
+ dependencies:
+ node-pre-gyp "^0.6.29"
+ parse-css-font "^2.0.2"
+ units-css "^0.4.0"
+
capture-stack-trace@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz#4a6fa07399c26bba47f0b2496b4d0fb408c5550d"
@@ -720,6 +728,36 @@ crypto-random-string@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e"
+css-font-size-keywords@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/css-font-size-keywords/-/css-font-size-keywords-1.0.0.tgz#854875ace9aca6a8d2ee0d345a44aae9bb6db6cb"
+
+css-font-stretch-keywords@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/css-font-stretch-keywords/-/css-font-stretch-keywords-1.0.1.tgz#50cee9b9ba031fb5c952d4723139f1e107b54b10"
+
+css-font-style-keywords@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/css-font-style-keywords/-/css-font-style-keywords-1.0.1.tgz#5c3532813f63b4a1de954d13cea86ab4333409e4"
+
+css-font-weight-keywords@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/css-font-weight-keywords/-/css-font-weight-keywords-1.0.0.tgz#9bc04671ac85bc724b574ef5d3ac96b0d604fd97"
+
+css-global-keywords@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/css-global-keywords/-/css-global-keywords-1.0.1.tgz#72a9aea72796d019b1d2a3252de4e5aaa37e4a69"
+
+css-list-helpers@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/css-list-helpers/-/css-list-helpers-1.0.1.tgz#fff57192202db83240c41686f919e449a7024f7d"
+ dependencies:
+ tcomb "^2.5.0"
+
+css-system-font-keywords@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/css-system-font-keywords/-/css-system-font-keywords-1.0.0.tgz#85c6f086aba4eb32c571a3086affc434b84823ed"
+
cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0":
version "0.3.2"
resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.2.tgz#b8036170c79f07a90ff2f16e22284027a243848b"
@@ -1574,6 +1612,10 @@ isexe@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
+isnumeric@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/isnumeric/-/isnumeric-0.2.0.tgz#a2347ba360de19e33d0ffd590fddf7755cbf2e64"
+
isobject@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89"
@@ -2351,7 +2393,7 @@ node-notifier@^5.2.1:
shellwords "^0.1.1"
which "^1.3.0"
-node-pre-gyp@^0.6.39:
+node-pre-gyp@^0.6.29, node-pre-gyp@^0.6.39:
version "0.6.39"
resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.39.tgz#c00e96860b23c0e1420ac7befc5044e1d78d8649"
dependencies:
@@ -2518,6 +2560,20 @@ package-json@^4.0.0:
registry-url "^3.0.3"
semver "^5.1.0"
+parse-css-font@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/parse-css-font/-/parse-css-font-2.0.2.tgz#7b60b060705a25a9b90b7f0ed493e5823248a652"
+ dependencies:
+ css-font-size-keywords "^1.0.0"
+ css-font-stretch-keywords "^1.0.1"
+ css-font-style-keywords "^1.0.1"
+ css-font-weight-keywords "^1.0.0"
+ css-global-keywords "^1.0.1"
+ css-list-helpers "^1.0.1"
+ css-system-font-keywords "^1.0.0"
+ tcomb "^2.5.0"
+ unquote "^1.1.0"
+
parse-glob@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c"
@@ -3222,6 +3278,10 @@ tar@^2.2.1:
fstream "^1.0.2"
inherits "2"
+tcomb@^2.5.0:
+ version "2.7.0"
+ resolved "https://registry.yarnpkg.com/tcomb/-/tcomb-2.7.0.tgz#10d62958041669a5d53567b9a4ee8cde22b1c2b0"
+
term-size@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/term-size/-/term-size-1.2.0.tgz#458b83887f288fc56d6fffbfad262e26638efa69"
@@ -3373,10 +3433,21 @@ unique-string@^1.0.0:
dependencies:
crypto-random-string "^1.0.0"
+units-css@^0.4.0:
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/units-css/-/units-css-0.4.0.tgz#d6228653a51983d7c16ff28f8b9dc3b1ffed3a07"
+ dependencies:
+ isnumeric "^0.2.0"
+ viewport-dimensions "^0.2.0"
+
universalify@^0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.1.tgz#fa71badd4437af4c148841e3b3b165f9e9e590b7"
+unquote@^1.1.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/unquote/-/unquote-1.1.1.tgz#8fded7324ec6e88a0ff8b905e7c098cdc086d544"
+
unzip-response@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-2.0.1.tgz#d2f0f737d16b0615e72a6935ed04214572d56f97"
@@ -3431,6 +3502,10 @@ verror@1.10.0:
core-util-is "1.0.2"
extsprintf "^1.2.0"
+viewport-dimensions@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/viewport-dimensions/-/viewport-dimensions-0.2.0.tgz#de740747db5387fd1725f5175e91bac76afdf36c"
+
w3c-hr-time@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz#82ac2bff63d950ea9e3189a58a65625fedf19045"