Add new actions delete and edit to Office Notes (#7813)
[openemr.git] / portal / sign / assets / signature_pad.umd.js
blobfbbf0ef090c31716b1c2b5b6fe3141b5376b5032
1 /*!
2  * Signature Pad v4.0.4 | https://github.com/szimek/signature_pad
3  * (c) 2022 Szymon Nowak | Released under the MIT license
4  */
6 (function (global, factory) {
7     typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
8     typeof define === 'function' && define.amd ? define(factory) :
9     (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.SignaturePad = factory());
10 })(this, (function () { 'use strict';
12     class Point {
13         constructor(x, y, pressure, time) {
14             if (isNaN(x) || isNaN(y)) {
15                 throw new Error(`Point is invalid: (${x}, ${y})`);
16             }
17             this.x = +x;
18             this.y = +y;
19             this.pressure = pressure || 0;
20             this.time = time || Date.now();
21         }
22         distanceTo(start) {
23             return Math.sqrt(Math.pow(this.x - start.x, 2) + Math.pow(this.y - start.y, 2));
24         }
25         equals(other) {
26             return (this.x === other.x &&
27                 this.y === other.y &&
28                 this.pressure === other.pressure &&
29                 this.time === other.time);
30         }
31         velocityFrom(start) {
32             return this.time !== start.time
33                 ? this.distanceTo(start) / (this.time - start.time)
34                 : 0;
35         }
36     }
38     class Bezier {
39         constructor(startPoint, control2, control1, endPoint, startWidth, endWidth) {
40             this.startPoint = startPoint;
41             this.control2 = control2;
42             this.control1 = control1;
43             this.endPoint = endPoint;
44             this.startWidth = startWidth;
45             this.endWidth = endWidth;
46         }
47         static fromPoints(points, widths) {
48             const c2 = this.calculateControlPoints(points[0], points[1], points[2]).c2;
49             const c3 = this.calculateControlPoints(points[1], points[2], points[3]).c1;
50             return new Bezier(points[1], c2, c3, points[2], widths.start, widths.end);
51         }
52         static calculateControlPoints(s1, s2, s3) {
53             const dx1 = s1.x - s2.x;
54             const dy1 = s1.y - s2.y;
55             const dx2 = s2.x - s3.x;
56             const dy2 = s2.y - s3.y;
57             const m1 = { x: (s1.x + s2.x) / 2.0, y: (s1.y + s2.y) / 2.0 };
58             const m2 = { x: (s2.x + s3.x) / 2.0, y: (s2.y + s3.y) / 2.0 };
59             const l1 = Math.sqrt(dx1 * dx1 + dy1 * dy1);
60             const l2 = Math.sqrt(dx2 * dx2 + dy2 * dy2);
61             const dxm = m1.x - m2.x;
62             const dym = m1.y - m2.y;
63             const k = l2 / (l1 + l2);
64             const cm = { x: m2.x + dxm * k, y: m2.y + dym * k };
65             const tx = s2.x - cm.x;
66             const ty = s2.y - cm.y;
67             return {
68                 c1: new Point(m1.x + tx, m1.y + ty),
69                 c2: new Point(m2.x + tx, m2.y + ty),
70             };
71         }
72         length() {
73             const steps = 10;
74             let length = 0;
75             let px;
76             let py;
77             for (let i = 0; i <= steps; i += 1) {
78                 const t = i / steps;
79                 const cx = this.point(t, this.startPoint.x, this.control1.x, this.control2.x, this.endPoint.x);
80                 const cy = this.point(t, this.startPoint.y, this.control1.y, this.control2.y, this.endPoint.y);
81                 if (i > 0) {
82                     const xdiff = cx - px;
83                     const ydiff = cy - py;
84                     length += Math.sqrt(xdiff * xdiff + ydiff * ydiff);
85                 }
86                 px = cx;
87                 py = cy;
88             }
89             return length;
90         }
91         point(t, start, c1, c2, end) {
92             return (start * (1.0 - t) * (1.0 - t) * (1.0 - t))
93                 + (3.0 * c1 * (1.0 - t) * (1.0 - t) * t)
94                 + (3.0 * c2 * (1.0 - t) * t * t)
95                 + (end * t * t * t);
96         }
97     }
99     class SignatureEventTarget {
100         constructor() {
101             try {
102                 this._et = new EventTarget();
103             }
104             catch (error) {
105                 this._et = document;
106             }
107         }
108         addEventListener(type, listener, options) {
109             this._et.addEventListener(type, listener, options);
110         }
111         dispatchEvent(event) {
112             return this._et.dispatchEvent(event);
113         }
114         removeEventListener(type, callback, options) {
115             this._et.removeEventListener(type, callback, options);
116         }
117     }
119     function throttle(fn, wait = 250) {
120         let previous = 0;
121         let timeout = null;
122         let result;
123         let storedContext;
124         let storedArgs;
125         const later = () => {
126             previous = Date.now();
127             timeout = null;
128             result = fn.apply(storedContext, storedArgs);
129             if (!timeout) {
130                 storedContext = null;
131                 storedArgs = [];
132             }
133         };
134         return function wrapper(...args) {
135             const now = Date.now();
136             const remaining = wait - (now - previous);
137             storedContext = this;
138             storedArgs = args;
139             if (remaining <= 0 || remaining > wait) {
140                 if (timeout) {
141                     clearTimeout(timeout);
142                     timeout = null;
143                 }
144                 previous = now;
145                 result = fn.apply(storedContext, storedArgs);
146                 if (!timeout) {
147                     storedContext = null;
148                     storedArgs = [];
149                 }
150             }
151             else if (!timeout) {
152                 timeout = window.setTimeout(later, remaining);
153             }
154             return result;
155         };
156     }
158     class SignaturePad extends SignatureEventTarget {
159         constructor(canvas, options = {}) {
160             super();
161             this.canvas = canvas;
162             this._handleMouseDown = (event) => {
163                 if (event.buttons === 1) {
164                     this._drawningStroke = true;
165                     this._strokeBegin(event);
166                 }
167             };
168             this._handleMouseMove = (event) => {
169                 if (this._drawningStroke) {
170                     this._strokeMoveUpdate(event);
171                 }
172             };
173             this._handleMouseUp = (event) => {
174                 if (event.buttons === 1 && this._drawningStroke) {
175                     this._drawningStroke = false;
176                     this._strokeEnd(event);
177                 }
178             };
179             this._handleTouchStart = (event) => {
180                 event.preventDefault();
181                 if (event.targetTouches.length === 1) {
182                     const touch = event.changedTouches[0];
183                     this._strokeBegin(touch);
184                 }
185             };
186             this._handleTouchMove = (event) => {
187                 event.preventDefault();
188                 const touch = event.targetTouches[0];
189                 this._strokeMoveUpdate(touch);
190             };
191             this._handleTouchEnd = (event) => {
192                 const wasCanvasTouched = event.target === this.canvas;
193                 if (wasCanvasTouched) {
194                     event.preventDefault();
195                     const touch = event.changedTouches[0];
196                     this._strokeEnd(touch);
197                 }
198             };
199             this._handlePointerStart = (event) => {
200                 this._drawningStroke = true;
201                 event.preventDefault();
202                 this._strokeBegin(event);
203             };
204             this._handlePointerMove = (event) => {
205                 if (this._drawningStroke) {
206                     event.preventDefault();
207                     this._strokeMoveUpdate(event);
208                 }
209             };
210             this._handlePointerEnd = (event) => {
211                 if (this._drawningStroke) {
212                     event.preventDefault();
213                     this._drawningStroke = false;
214                     this._strokeEnd(event);
215                 }
216             };
217             this.velocityFilterWeight = options.velocityFilterWeight || 0.7;
218             this.minWidth = options.minWidth || 0.5;
219             this.maxWidth = options.maxWidth || 2.5;
220             this.throttle = ('throttle' in options ? options.throttle : 16);
221             this.minDistance = ('minDistance' in options ? options.minDistance : 5);
222             this.dotSize = options.dotSize || 0;
223             this.penColor = options.penColor || 'black';
224             this.backgroundColor = options.backgroundColor || 'rgba(0,0,0,0)';
225             this._strokeMoveUpdate = this.throttle
226                 ? throttle(SignaturePad.prototype._strokeUpdate, this.throttle)
227                 : SignaturePad.prototype._strokeUpdate;
228             this._ctx = canvas.getContext('2d');
229             this.clear();
230             this.on();
231         }
232         clear() {
233             const { _ctx: ctx, canvas } = this;
234             ctx.fillStyle = this.backgroundColor;
235             ctx.clearRect(0, 0, canvas.width, canvas.height);
236             ctx.fillRect(0, 0, canvas.width, canvas.height);
237             this._data = [];
238             this._reset();
239             this._isEmpty = true;
240         }
241         fromDataURL(dataUrl, options = {}) {
242             return new Promise((resolve, reject) => {
243                 const image = new Image();
244                 const ratio = options.ratio || window.devicePixelRatio || 1;
245                 const width = options.width || this.canvas.width / ratio;
246                 const height = options.height || this.canvas.height / ratio;
247                 const xOffset = options.xOffset || 0;
248                 const yOffset = options.yOffset || 0;
249                 this._reset();
250                 image.onload = () => {
251                     this._ctx.drawImage(image, xOffset, yOffset, width, height);
252                     resolve();
253                 };
254                 image.onerror = (error) => {
255                     reject(error);
256                 };
257                 image.crossOrigin = 'anonymous';
258                 image.src = dataUrl;
259                 this._isEmpty = false;
260             });
261         }
262         toDataURL(type = 'image/png', encoderOptions) {
263             switch (type) {
264                 case 'image/svg+xml':
265                     return this._toSVG();
266                 default:
267                     return this.canvas.toDataURL(type, encoderOptions);
268             }
269         }
270         on() {
271             this.canvas.style.touchAction = 'none';
272             this.canvas.style.msTouchAction = 'none';
273             this.canvas.style.userSelect = 'none';
274             const isIOS = /Macintosh/.test(navigator.userAgent) && 'ontouchstart' in document;
275             if (window.PointerEvent && !isIOS) {
276                 this._handlePointerEvents();
277             }
278             else {
279                 this._handleMouseEvents();
280                 if ('ontouchstart' in window) {
281                     this._handleTouchEvents();
282                 }
283             }
284         }
285         off() {
286             this.canvas.style.touchAction = 'auto';
287             this.canvas.style.msTouchAction = 'auto';
288             this.canvas.style.userSelect = 'auto';
289             this.canvas.removeEventListener('pointerdown', this._handlePointerStart);
290             this.canvas.removeEventListener('pointermove', this._handlePointerMove);
291             document.removeEventListener('pointerup', this._handlePointerEnd);
292             this.canvas.removeEventListener('mousedown', this._handleMouseDown);
293             this.canvas.removeEventListener('mousemove', this._handleMouseMove);
294             document.removeEventListener('mouseup', this._handleMouseUp);
295             this.canvas.removeEventListener('touchstart', this._handleTouchStart);
296             this.canvas.removeEventListener('touchmove', this._handleTouchMove);
297             this.canvas.removeEventListener('touchend', this._handleTouchEnd);
298         }
299         isEmpty() {
300             return this._isEmpty;
301         }
302         fromData(pointGroups, { clear = true } = {}) {
303             if (clear) {
304                 this.clear();
305             }
306             this._fromData(pointGroups, this._drawCurve.bind(this), this._drawDot.bind(this));
307             this._data = this._data.concat(pointGroups);
308         }
309         toData() {
310             return this._data;
311         }
312         _strokeBegin(event) {
313             this.dispatchEvent(new CustomEvent('beginStroke', { detail: event }));
314             const newPointGroup = {
315                 dotSize: this.dotSize,
316                 minWidth: this.minWidth,
317                 maxWidth: this.maxWidth,
318                 penColor: this.penColor,
319                 points: [],
320             };
321             this._data.push(newPointGroup);
322             this._reset();
323             this._strokeUpdate(event);
324         }
325         _strokeUpdate(event) {
326             if (this._data.length === 0) {
327                 this._strokeBegin(event);
328                 return;
329             }
330             this.dispatchEvent(new CustomEvent('beforeUpdateStroke', { detail: event }));
331             const x = event.clientX;
332             const y = event.clientY;
333             const pressure = event.pressure !== undefined
334                 ? event.pressure
335                 : event.force !== undefined
336                     ? event.force
337                     : 0;
338             const point = this._createPoint(x, y, pressure);
339             const lastPointGroup = this._data[this._data.length - 1];
340             const lastPoints = lastPointGroup.points;
341             const lastPoint = lastPoints.length > 0 && lastPoints[lastPoints.length - 1];
342             const isLastPointTooClose = lastPoint
343                 ? point.distanceTo(lastPoint) <= this.minDistance
344                 : false;
345             const { penColor, dotSize, minWidth, maxWidth } = lastPointGroup;
346             if (!lastPoint || !(lastPoint && isLastPointTooClose)) {
347                 const curve = this._addPoint(point);
348                 if (!lastPoint) {
349                     this._drawDot(point, {
350                         penColor,
351                         dotSize,
352                         minWidth,
353                         maxWidth,
354                     });
355                 }
356                 else if (curve) {
357                     this._drawCurve(curve, {
358                         penColor,
359                         dotSize,
360                         minWidth,
361                         maxWidth,
362                     });
363                 }
364                 lastPoints.push({
365                     time: point.time,
366                     x: point.x,
367                     y: point.y,
368                     pressure: point.pressure,
369                 });
370             }
371             this.dispatchEvent(new CustomEvent('afterUpdateStroke', { detail: event }));
372         }
373         _strokeEnd(event) {
374             this._strokeUpdate(event);
375             this.dispatchEvent(new CustomEvent('endStroke', { detail: event }));
376         }
377         _handlePointerEvents() {
378             this._drawningStroke = false;
379             this.canvas.addEventListener('pointerdown', this._handlePointerStart);
380             this.canvas.addEventListener('pointermove', this._handlePointerMove);
381             document.addEventListener('pointerup', this._handlePointerEnd);
382         }
383         _handleMouseEvents() {
384             this._drawningStroke = false;
385             this.canvas.addEventListener('mousedown', this._handleMouseDown);
386             this.canvas.addEventListener('mousemove', this._handleMouseMove);
387             document.addEventListener('mouseup', this._handleMouseUp);
388         }
389         _handleTouchEvents() {
390             this.canvas.addEventListener('touchstart', this._handleTouchStart);
391             this.canvas.addEventListener('touchmove', this._handleTouchMove);
392             this.canvas.addEventListener('touchend', this._handleTouchEnd);
393         }
394         _reset() {
395             this._lastPoints = [];
396             this._lastVelocity = 0;
397             this._lastWidth = (this.minWidth + this.maxWidth) / 2;
398             this._ctx.fillStyle = this.penColor;
399         }
400         _createPoint(x, y, pressure) {
401             const rect = this.canvas.getBoundingClientRect();
402             return new Point(x - rect.left, y - rect.top, pressure, new Date().getTime());
403         }
404         _addPoint(point) {
405             const { _lastPoints } = this;
406             _lastPoints.push(point);
407             if (_lastPoints.length > 2) {
408                 if (_lastPoints.length === 3) {
409                     _lastPoints.unshift(_lastPoints[0]);
410                 }
411                 const widths = this._calculateCurveWidths(_lastPoints[1], _lastPoints[2]);
412                 const curve = Bezier.fromPoints(_lastPoints, widths);
413                 _lastPoints.shift();
414                 return curve;
415             }
416             return null;
417         }
418         _calculateCurveWidths(startPoint, endPoint) {
419             const velocity = this.velocityFilterWeight * endPoint.velocityFrom(startPoint) +
420                 (1 - this.velocityFilterWeight) * this._lastVelocity;
421             const newWidth = this._strokeWidth(velocity);
422             const widths = {
423                 end: newWidth,
424                 start: this._lastWidth,
425             };
426             this._lastVelocity = velocity;
427             this._lastWidth = newWidth;
428             return widths;
429         }
430         _strokeWidth(velocity) {
431             return Math.max(this.maxWidth / (velocity + 1), this.minWidth);
432         }
433         _drawCurveSegment(x, y, width) {
434             const ctx = this._ctx;
435             ctx.moveTo(x, y);
436             ctx.arc(x, y, width, 0, 2 * Math.PI, false);
437             this._isEmpty = false;
438         }
439         _drawCurve(curve, options) {
440             const ctx = this._ctx;
441             const widthDelta = curve.endWidth - curve.startWidth;
442             const drawSteps = Math.ceil(curve.length()) * 2;
443             ctx.beginPath();
444             ctx.fillStyle = options.penColor;
445             for (let i = 0; i < drawSteps; i += 1) {
446                 const t = i / drawSteps;
447                 const tt = t * t;
448                 const ttt = tt * t;
449                 const u = 1 - t;
450                 const uu = u * u;
451                 const uuu = uu * u;
452                 let x = uuu * curve.startPoint.x;
453                 x += 3 * uu * t * curve.control1.x;
454                 x += 3 * u * tt * curve.control2.x;
455                 x += ttt * curve.endPoint.x;
456                 let y = uuu * curve.startPoint.y;
457                 y += 3 * uu * t * curve.control1.y;
458                 y += 3 * u * tt * curve.control2.y;
459                 y += ttt * curve.endPoint.y;
460                 const width = Math.min(curve.startWidth + ttt * widthDelta, options.maxWidth);
461                 this._drawCurveSegment(x, y, width);
462             }
463             ctx.closePath();
464             ctx.fill();
465         }
466         _drawDot(point, options) {
467             const ctx = this._ctx;
468             const width = options.dotSize > 0
469                 ? options.dotSize
470                 : (options.minWidth + options.maxWidth) / 2;
471             ctx.beginPath();
472             this._drawCurveSegment(point.x, point.y, width);
473             ctx.closePath();
474             ctx.fillStyle = options.penColor;
475             ctx.fill();
476         }
477         _fromData(pointGroups, drawCurve, drawDot) {
478             for (const group of pointGroups) {
479                 const { penColor, dotSize, minWidth, maxWidth, points } = group;
480                 if (points.length > 1) {
481                     for (let j = 0; j < points.length; j += 1) {
482                         const basicPoint = points[j];
483                         const point = new Point(basicPoint.x, basicPoint.y, basicPoint.pressure, basicPoint.time);
484                         this.penColor = penColor;
485                         if (j === 0) {
486                             this._reset();
487                         }
488                         const curve = this._addPoint(point);
489                         if (curve) {
490                             drawCurve(curve, {
491                                 penColor,
492                                 dotSize,
493                                 minWidth,
494                                 maxWidth,
495                             });
496                         }
497                     }
498                 }
499                 else {
500                     this._reset();
501                     drawDot(points[0], {
502                         penColor,
503                         dotSize,
504                         minWidth,
505                         maxWidth,
506                     });
507                 }
508             }
509         }
510         _toSVG() {
511             const pointGroups = this._data;
512             const ratio = Math.max(window.devicePixelRatio || 1, 1);
513             const minX = 0;
514             const minY = 0;
515             const maxX = this.canvas.width / ratio;
516             const maxY = this.canvas.height / ratio;
517             const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
518             svg.setAttribute('width', this.canvas.width.toString());
519             svg.setAttribute('height', this.canvas.height.toString());
520             this._fromData(pointGroups, (curve, { penColor }) => {
521                 const path = document.createElement('path');
522                 if (!isNaN(curve.control1.x) &&
523                     !isNaN(curve.control1.y) &&
524                     !isNaN(curve.control2.x) &&
525                     !isNaN(curve.control2.y)) {
526                     const attr = `M ${curve.startPoint.x.toFixed(3)},${curve.startPoint.y.toFixed(3)} ` +
527                         `C ${curve.control1.x.toFixed(3)},${curve.control1.y.toFixed(3)} ` +
528                         `${curve.control2.x.toFixed(3)},${curve.control2.y.toFixed(3)} ` +
529                         `${curve.endPoint.x.toFixed(3)},${curve.endPoint.y.toFixed(3)}`;
530                     path.setAttribute('d', attr);
531                     path.setAttribute('stroke-width', (curve.endWidth * 2.25).toFixed(3));
532                     path.setAttribute('stroke', penColor);
533                     path.setAttribute('fill', 'none');
534                     path.setAttribute('stroke-linecap', 'round');
535                     svg.appendChild(path);
536                 }
537             }, (point, { penColor, dotSize, minWidth, maxWidth }) => {
538                 const circle = document.createElement('circle');
539                 const size = dotSize > 0 ? dotSize : (minWidth + maxWidth) / 2;
540                 circle.setAttribute('r', size.toString());
541                 circle.setAttribute('cx', point.x.toString());
542                 circle.setAttribute('cy', point.y.toString());
543                 circle.setAttribute('fill', penColor);
544                 svg.appendChild(circle);
545             });
546             const prefix = 'data:image/svg+xml;base64,';
547             const header = '<svg' +
548                 ' xmlns="http://www.w3.org/2000/svg"' +
549                 ' xmlns:xlink="http://www.w3.org/1999/xlink"' +
550                 ` viewBox="${minX} ${minY} ${this.canvas.width} ${this.canvas.height}"` +
551                 ` width="${maxX}"` +
552                 ` height="${maxY}"` +
553                 '>';
554             let body = svg.innerHTML;
555             if (body === undefined) {
556                 const dummy = document.createElement('dummy');
557                 const nodes = svg.childNodes;
558                 dummy.innerHTML = '';
559                 for (let i = 0; i < nodes.length; i += 1) {
560                     dummy.appendChild(nodes[i].cloneNode(true));
561                 }
562                 body = dummy.innerHTML;
563             }
564             const footer = '</svg>';
565             const data = header + body + footer;
566             return prefix + btoa(data);
567         }
568     }
570     return SignaturePad;
572 }));
573 //# sourceMappingURL=signature_pad.umd.js.map