PluginProbe
VikBooking Hotel Booking Engine & PMS / 1.8.13
VikBooking Hotel Booking Engine & PMS v1.8.13
1.8.16 1.8.15 1.8.14 1.8.13 1.8.12 1.8.11 1.8.10 1.8.9 1.8.6 1.8.7 1.8.8 trunk 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.7 1.6.8 1.6.9 1.7.0 1.7.1 1.7.2 All 37 releases
vikbooking / site / resources / signature_pad.js

signature_pad.js in VikBooking Hotel Booking Engine & PMS 1.8.13, at site/resources/signature_pad.js

609 lines 17.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /*
2 * Signature Pad v2.3.2
3 * https://github.com/szimek/signature_pad
4 *
5 * Copyright 2017 Szymon Nowak
6 * Released under the MIT license
7 *
8 * The main idea and some parts of the code (e.g. drawing variable width Bézier curve) are taken from:
9 * http://corner.squareup.com/2012/07/smoother-signatures.html
10 *
11 * Implementation of interpolation using cubic Bézier curves is taken from:
12 * http://benknowscode.wordpress.com/2012/09/14/path-interpolation-using-cubic-bezier-and-control-point-estimation-in-javascript
13 *
14 * Algorithm for approximated length of a Bézier curve is taken from:
15 * http://www.lemoda.net/maths/bezier-length/index.html
16 *
17 * Source Code adjusted to fit into the Vik Booking framework.
18 *
19 */
20
21 (function (global, factory) {
22 typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
23 typeof define === 'function' && define.amd ? define(factory) :
24 (global.SignaturePad = factory());
25 }(this, (function () { 'use strict';
26
27 function Point(x, y, time) {
28 this.x = x;
29 this.y = y;
30 this.time = time || new Date().getTime();
31 }
32
33 Point.prototype.velocityFrom = function (start) {
34 return this.time !== start.time ? this.distanceTo(start) / (this.time - start.time) : 1;
35 };
36
37 Point.prototype.distanceTo = function (start) {
38 return Math.sqrt(Math.pow(this.x - start.x, 2) + Math.pow(this.y - start.y, 2));
39 };
40
41 Point.prototype.equals = function (other) {
42 return this.x === other.x && this.y === other.y && this.time === other.time;
43 };
44
45 function Bezier(startPoint, control1, control2, endPoint) {
46 this.startPoint = startPoint;
47 this.control1 = control1;
48 this.control2 = control2;
49 this.endPoint = endPoint;
50 }
51
52 // Returns approximated length.
53 Bezier.prototype.length = function () {
54 var steps = 10;
55 var length = 0;
56 var px = void 0;
57 var py = void 0;
58
59 for (var i = 0; i <= steps; i += 1) {
60 var t = i / steps;
61 var cx = this._point(t, this.startPoint.x, this.control1.x, this.control2.x, this.endPoint.x);
62 var cy = this._point(t, this.startPoint.y, this.control1.y, this.control2.y, this.endPoint.y);
63 if (i > 0) {
64 var xdiff = cx - px;
65 var ydiff = cy - py;
66 length += Math.sqrt(xdiff * xdiff + ydiff * ydiff);
67 }
68 px = cx;
69 py = cy;
70 }
71
72 return length;
73 };
74
75 /* eslint-disable no-multi-spaces, space-in-parens */
76 Bezier.prototype._point = function (t, start, c1, c2, end) {
77 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;
78 };
79 /* eslint-disable */
80
81 function throttle(func, wait, options) {
82 var context, args, result;
83 var timeout = null;
84 var previous = 0;
85 if (!options) options = {};
86 var later = function later() {
87 previous = options.leading === false ? 0 : Date.now();
88 timeout = null;
89 result = func.apply(context, args);
90 if (!timeout) context = args = null;
91 };
92 return function () {
93 var now = Date.now();
94 if (!previous && options.leading === false) previous = now;
95 var remaining = wait - (now - previous);
96 context = this;
97 args = arguments;
98 if (remaining <= 0 || remaining > wait) {
99 if (timeout) {
100 clearTimeout(timeout);
101 timeout = null;
102 }
103 previous = now;
104 result = func.apply(context, args);
105 if (!timeout) context = args = null;
106 } else if (!timeout && options.trailing !== false) {
107 timeout = setTimeout(later, remaining);
108 }
109 return result;
110 };
111 }
112
113 function SignaturePad(canvas, options) {
114 var self = this;
115 var opts = options || {};
116
117 this.velocityFilterWeight = opts.velocityFilterWeight || 0.7;
118 this.minWidth = opts.minWidth || 0.5;
119 this.maxWidth = opts.maxWidth || 2.5;
120 this.throttle = 'throttle' in opts ? opts.throttle : 16; // in miliseconds
121 this.minDistance = 'minDistance' in opts ? opts.minDistance : 5;
122
123 if (this.throttle) {
124 this._strokeMoveUpdate = throttle(SignaturePad.prototype._strokeUpdate, this.throttle);
125 } else {
126 this._strokeMoveUpdate = SignaturePad.prototype._strokeUpdate;
127 }
128
129 this.dotSize = opts.dotSize || function () {
130 return (this.minWidth + this.maxWidth) / 2;
131 };
132 this.penColor = opts.penColor || 'black';
133 this.backgroundColor = opts.backgroundColor || 'rgba(0,0,0,0)';
134 this.onBegin = opts.onBegin;
135 this.onEnd = opts.onEnd;
136
137 this._canvas = canvas;
138 this._ctx = canvas.getContext('2d');
139 this.clear();
140
141 // We need add these inline so they are available to unbind while still having
142 // access to 'self' we could use _.bind but it's not worth adding a dependency.
143 this._handleMouseDown = function (event) {
144 if (event.which === 1) {
145 self._mouseButtonDown = true;
146 self._strokeBegin(event);
147 }
148 };
149
150 this._handleMouseMove = function (event) {
151 if (self._mouseButtonDown) {
152 self._strokeMoveUpdate(event);
153 }
154 };
155
156 this._handleMouseUp = function (event) {
157 if (event.which === 1 && self._mouseButtonDown) {
158 self._mouseButtonDown = false;
159 self._strokeEnd(event);
160 }
161 };
162
163 this._handleTouchStart = function (event) {
164 if (event.targetTouches.length === 1) {
165 var touch = event.changedTouches[0];
166 self._strokeBegin(touch);
167 }
168 };
169
170 this._handleTouchMove = function (event) {
171 // Prevent scrolling.
172 event.preventDefault();
173
174 var touch = event.targetTouches[0];
175 self._strokeMoveUpdate(touch);
176 };
177
178 this._handleTouchEnd = function (event) {
179 var wasCanvasTouched = event.target === self._canvas;
180 if (wasCanvasTouched) {
181 event.preventDefault();
182 self._strokeEnd(event);
183 }
184 };
185
186 // Enable mouse and touch event handlers
187 this.on();
188 }
189
190 // Public methods
191 SignaturePad.prototype.clear = function () {
192 var ctx = this._ctx;
193 var canvas = this._canvas;
194
195 ctx.fillStyle = this.backgroundColor;
196 ctx.clearRect(0, 0, canvas.width, canvas.height);
197 ctx.fillRect(0, 0, canvas.width, canvas.height);
198
199 this._data = [];
200 this._reset();
201 this._isEmpty = true;
202 };
203
204 SignaturePad.prototype.fromDataURL = function (dataUrl) {
205 var _this = this;
206
207 var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
208
209 var image = new Image();
210 var ratio = options.ratio || window.devicePixelRatio || 1;
211 var width = options.width || this._canvas.width / ratio;
212 var height = options.height || this._canvas.height / ratio;
213
214 this._reset();
215 image.src = dataUrl;
216 image.onload = function () {
217 _this._ctx.drawImage(image, 0, 0, width, height);
218 };
219 this._isEmpty = false;
220 };
221
222 SignaturePad.prototype.toDataURL = function (type) {
223 var _canvas;
224
225 switch (type) {
226 case 'image/svg+xml':
227 return this._toSVG();
228 default:
229 for (var _len = arguments.length, options = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
230 options[_key - 1] = arguments[_key];
231 }
232 return (_canvas = this._canvas).toDataURL.apply(_canvas, [type].concat(options));
233 }
234 };
235
236 SignaturePad.prototype.on = function () {
237 this._handleMouseEvents();
238 this._handleTouchEvents();
239 };
240
241 SignaturePad.prototype.off = function () {
242 this._canvas.removeEventListener('mousedown', this._handleMouseDown);
243 this._canvas.removeEventListener('mousemove', this._handleMouseMove);
244 document.removeEventListener('mouseup', this._handleMouseUp);
245
246 this._canvas.removeEventListener('touchstart', this._handleTouchStart);
247 this._canvas.removeEventListener('touchmove', this._handleTouchMove);
248 this._canvas.removeEventListener('touchend', this._handleTouchEnd);
249 };
250
251 SignaturePad.prototype.isEmpty = function () {
252 return this._isEmpty;
253 };
254
255 // Private methods
256 SignaturePad.prototype._strokeBegin = function (event) {
257 this._data.push([]);
258 this._reset();
259 this._strokeUpdate(event);
260
261 if (typeof this.onBegin === 'function') {
262 this.onBegin(event);
263 }
264 };
265
266 SignaturePad.prototype._strokeUpdate = function (event) {
267 var x = event.clientX;
268 var y = event.clientY;
269
270 var point = this._createPoint(x, y);
271 var lastPointGroup = this._data[this._data.length - 1];
272 var lastPoint = lastPointGroup && lastPointGroup[lastPointGroup.length - 1];
273 var isLastPointTooClose = lastPoint && point.distanceTo(lastPoint) < this.minDistance;
274
275 // Skip this point if it's too close to the previous one
276 if (!(lastPoint && isLastPointTooClose)) {
277 var _addPoint = this._addPoint(point),
278 curve = _addPoint.curve,
279 widths = _addPoint.widths;
280
281 if (curve && widths) {
282 this._drawCurve(curve, widths.start, widths.end);
283 }
284
285 this._data[this._data.length - 1].push({
286 x: point.x,
287 y: point.y,
288 time: point.time,
289 color: this.penColor
290 });
291 }
292 };
293
294 SignaturePad.prototype._strokeEnd = function (event) {
295 var canDrawCurve = this.points.length > 2;
296 var point = this.points[0]; // Point instance
297
298 if (!canDrawCurve && point) {
299 this._drawDot(point);
300 }
301
302 if (point) {
303 var lastPointGroup = this._data[this._data.length - 1];
304 var lastPoint = lastPointGroup[lastPointGroup.length - 1]; // plain object
305
306 // When drawing a dot, there's only one point in a group, so without this check
307 // such group would end up with exactly the same 2 points.
308 if (!point.equals(lastPoint)) {
309 lastPointGroup.push({
310 x: point.x,
311 y: point.y,
312 time: point.time,
313 color: this.penColor
314 });
315 }
316 }
317
318 if (typeof this.onEnd === 'function') {
319 this.onEnd(event);
320 }
321 };
322
323 SignaturePad.prototype._handleMouseEvents = function () {
324 this._mouseButtonDown = false;
325
326 this._canvas.addEventListener('mousedown', this._handleMouseDown);
327 this._canvas.addEventListener('mousemove', this._handleMouseMove);
328 document.addEventListener('mouseup', this._handleMouseUp);
329 };
330
331 SignaturePad.prototype._handleTouchEvents = function () {
332 // Pass touch events to canvas element on mobile IE11 and Edge.
333 this._canvas.style.msTouchAction = 'none';
334 this._canvas.style.touchAction = 'none';
335
336 this._canvas.addEventListener('touchstart', this._handleTouchStart);
337 this._canvas.addEventListener('touchmove', this._handleTouchMove);
338 this._canvas.addEventListener('touchend', this._handleTouchEnd);
339 };
340
341 SignaturePad.prototype._reset = function () {
342 this.points = [];
343 this._lastVelocity = 0;
344 this._lastWidth = (this.minWidth + this.maxWidth) / 2;
345 this._ctx.fillStyle = this.penColor;
346 };
347
348 SignaturePad.prototype._createPoint = function (x, y, time) {
349 var rect = this._canvas.getBoundingClientRect();
350
351 return new Point(x - rect.left, y - rect.top, time || new Date().getTime());
352 };
353
354 SignaturePad.prototype._addPoint = function (point) {
355 var points = this.points;
356 var tmp = void 0;
357
358 points.push(point);
359
360 if (points.length > 2) {
361 // To reduce the initial lag make it work with 3 points
362 // by copying the first point to the beginning.
363 if (points.length === 3) points.unshift(points[0]);
364
365 tmp = this._calculateCurveControlPoints(points[0], points[1], points[2]);
366 var c2 = tmp.c2;
367 tmp = this._calculateCurveControlPoints(points[1], points[2], points[3]);
368 var c3 = tmp.c1;
369 var curve = new Bezier(points[1], c2, c3, points[2]);
370 var widths = this._calculateCurveWidths(curve);
371
372 // Remove the first element from the list,
373 // so that we always have no more than 4 points in points array.
374 points.shift();
375
376 return { curve: curve, widths: widths };
377 }
378
379 return {};
380 };
381
382 SignaturePad.prototype._calculateCurveControlPoints = function (s1, s2, s3) {
383 var dx1 = s1.x - s2.x;
384 var dy1 = s1.y - s2.y;
385 var dx2 = s2.x - s3.x;
386 var dy2 = s2.y - s3.y;
387
388 var m1 = { x: (s1.x + s2.x) / 2.0, y: (s1.y + s2.y) / 2.0 };
389 var m2 = { x: (s2.x + s3.x) / 2.0, y: (s2.y + s3.y) / 2.0 };
390
391 var l1 = Math.sqrt(dx1 * dx1 + dy1 * dy1);
392 var l2 = Math.sqrt(dx2 * dx2 + dy2 * dy2);
393
394 var dxm = m1.x - m2.x;
395 var dym = m1.y - m2.y;
396
397 var k = l2 / (l1 + l2);
398 var cm = { x: m2.x + dxm * k, y: m2.y + dym * k };
399
400 var tx = s2.x - cm.x;
401 var ty = s2.y - cm.y;
402
403 return {
404 c1: new Point(m1.x + tx, m1.y + ty),
405 c2: new Point(m2.x + tx, m2.y + ty)
406 };
407 };
408
409 SignaturePad.prototype._calculateCurveWidths = function (curve) {
410 var startPoint = curve.startPoint;
411 var endPoint = curve.endPoint;
412 var widths = { start: null, end: null };
413
414 var velocity = this.velocityFilterWeight * endPoint.velocityFrom(startPoint) + (1 - this.velocityFilterWeight) * this._lastVelocity;
415
416 var newWidth = this._strokeWidth(velocity);
417
418 widths.start = this._lastWidth;
419 widths.end = newWidth;
420
421 this._lastVelocity = velocity;
422 this._lastWidth = newWidth;
423
424 return widths;
425 };
426
427 SignaturePad.prototype._strokeWidth = function (velocity) {
428 return Math.max(this.maxWidth / (velocity + 1), this.minWidth);
429 };
430
431 SignaturePad.prototype._drawPoint = function (x, y, size) {
432 var ctx = this._ctx;
433
434 ctx.moveTo(x, y);
435 ctx.arc(x, y, size, 0, 2 * Math.PI, false);
436 this._isEmpty = false;
437 };
438
439 SignaturePad.prototype._drawCurve = function (curve, startWidth, endWidth) {
440 var ctx = this._ctx;
441 var widthDelta = endWidth - startWidth;
442 var drawSteps = Math.floor(curve.length());
443
444 ctx.beginPath();
445
446 for (var i = 0; i < drawSteps; i += 1) {
447 // Calculate the Bezier (x, y) coordinate for this step.
448 var t = i / drawSteps;
449 var tt = t * t;
450 var ttt = tt * t;
451 var u = 1 - t;
452 var uu = u * u;
453 var uuu = uu * u;
454
455 var x = uuu * curve.startPoint.x;
456 x += 3 * uu * t * curve.control1.x;
457 x += 3 * u * tt * curve.control2.x;
458 x += ttt * curve.endPoint.x;
459
460 var y = uuu * curve.startPoint.y;
461 y += 3 * uu * t * curve.control1.y;
462 y += 3 * u * tt * curve.control2.y;
463 y += ttt * curve.endPoint.y;
464
465 var width = startWidth + ttt * widthDelta;
466 this._drawPoint(x, y, width);
467 }
468
469 ctx.closePath();
470 ctx.fill();
471 };
472
473 SignaturePad.prototype._drawDot = function (point) {
474 var ctx = this._ctx;
475 var width = typeof this.dotSize === 'function' ? this.dotSize() : this.dotSize;
476
477 ctx.beginPath();
478 this._drawPoint(point.x, point.y, width);
479 ctx.closePath();
480 ctx.fill();
481 };
482
483 SignaturePad.prototype._fromData = function (pointGroups, drawCurve, drawDot) {
484 for (var i = 0; i < pointGroups.length; i += 1) {
485 var group = pointGroups[i];
486
487 if (group.length > 1) {
488 for (var j = 0; j < group.length; j += 1) {
489 var rawPoint = group[j];
490 var point = new Point(rawPoint.x, rawPoint.y, rawPoint.time);
491 var color = rawPoint.color;
492
493 if (j === 0) {
494 // First point in a group. Nothing to draw yet.
495
496 // All points in the group have the same color, so it's enough to set
497 // penColor just at the beginning.
498 this.penColor = color;
499 this._reset();
500
501 this._addPoint(point);
502 } else if (j !== group.length - 1) {
503 // Middle point in a group.
504 var _addPoint2 = this._addPoint(point),
505 curve = _addPoint2.curve,
506 widths = _addPoint2.widths;
507
508 if (curve && widths) {
509 drawCurve(curve, widths, color);
510 }
511 } else {
512 // Last point in a group. Do nothing.
513 }
514 }
515 } else {
516 this._reset();
517 var _rawPoint = group[0];
518 drawDot(_rawPoint);
519 }
520 }
521 };
522
523 SignaturePad.prototype._toSVG = function () {
524 var _this2 = this;
525
526 var pointGroups = this._data;
527 var canvas = this._canvas;
528 var ratio = Math.max(window.devicePixelRatio || 1, 1);
529 var minX = 0;
530 var minY = 0;
531 var maxX = canvas.width / ratio;
532 var maxY = canvas.height / ratio;
533 var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
534
535 svg.setAttributeNS(null, 'width', canvas.width);
536 svg.setAttributeNS(null, 'height', canvas.height);
537
538 this._fromData(pointGroups, function (curve, widths, color) {
539 var path = document.createElement('path');
540
541 // Need to check curve for NaN values, these pop up when drawing
542 // lines on the canvas that are not continuous. E.g. Sharp corners
543 // or stopping mid-stroke and than continuing without lifting mouse.
544 if (!isNaN(curve.control1.x) && !isNaN(curve.control1.y) && !isNaN(curve.control2.x) && !isNaN(curve.control2.y)) {
545 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));
546
547 path.setAttribute('d', attr);
548 path.setAttribute('stroke-width', (widths.end * 2.25).toFixed(3));
549 path.setAttribute('stroke', color);
550 path.setAttribute('fill', 'none');
551 path.setAttribute('stroke-linecap', 'round');
552
553 svg.appendChild(path);
554 }
555 }, function (rawPoint) {
556 var circle = document.createElement('circle');
557 var dotSize = typeof _this2.dotSize === 'function' ? _this2.dotSize() : _this2.dotSize;
558 circle.setAttribute('r', dotSize);
559 circle.setAttribute('cx', rawPoint.x);
560 circle.setAttribute('cy', rawPoint.y);
561 circle.setAttribute('fill', rawPoint.color);
562
563 svg.appendChild(circle);
564 });
565
566 var prefix = 'data:image/svg+xml;base64,';
567 var header = '<svg' + ' xmlns="http://www.w3.org/2000/svg"' + ' xmlns:xlink="http://www.w3.org/1999/xlink"' + (' viewBox="' + minX + ' ' + minY + ' ' + maxX + ' ' + maxY + '"') + (' width="' + maxX + '"') + (' height="' + maxY + '"') + '>';
568 var body = svg.innerHTML;
569
570 // IE hack for missing innerHTML property on SVGElement
571 if (body === undefined) {
572 var dummy = document.createElement('dummy');
573 var nodes = svg.childNodes;
574 dummy.innerHTML = '';
575
576 for (var i = 0; i < nodes.length; i += 1) {
577 dummy.appendChild(nodes[i].cloneNode(true));
578 }
579
580 body = dummy.innerHTML;
581 }
582
583 var footer = '</svg>';
584 var data = header + body + footer;
585
586 return prefix + btoa(data);
587 };
588
589 SignaturePad.prototype.fromData = function (pointGroups) {
590 var _this3 = this;
591
592 this.clear();
593
594 this._fromData(pointGroups, function (curve, widths) {
595 return _this3._drawCurve(curve, widths.start, widths.end);
596 }, function (rawPoint) {
597 return _this3._drawDot(rawPoint);
598 });
599
600 this._data = pointGroups;
601 };
602
603 SignaturePad.prototype.toData = function () {
604 return this._data;
605 };
606
607 return SignaturePad;
608
609 })));