1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
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';
|
11 |
|
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 | }
|
37 |
|
38 | class Bezier {
|
39 | static fromPoints(points, widths) {
|
40 | const c2 = this.calculateControlPoints(points[0], points[1], points[2]).c2;
|
41 | const c3 = this.calculateControlPoints(points[1], points[2], points[3]).c1;
|
42 | return new Bezier(points[1], c2, c3, points[2], widths.start, widths.end);
|
43 | }
|
44 | static calculateControlPoints(s1, s2, s3) {
|
45 | const dx1 = s1.x - s2.x;
|
46 | const dy1 = s1.y - s2.y;
|
47 | const dx2 = s2.x - s3.x;
|
48 | const dy2 = s2.y - s3.y;
|
49 | const m1 = { x: (s1.x + s2.x) / 2.0, y: (s1.y + s2.y) / 2.0 };
|
50 | const m2 = { x: (s2.x + s3.x) / 2.0, y: (s2.y + s3.y) / 2.0 };
|
51 | const l1 = Math.sqrt(dx1 * dx1 + dy1 * dy1);
|
52 | const l2 = Math.sqrt(dx2 * dx2 + dy2 * dy2);
|
53 | const dxm = m1.x - m2.x;
|
54 | const dym = m1.y - m2.y;
|
55 | const k = l2 / (l1 + l2);
|
56 | const cm = { x: m2.x + dxm * k, y: m2.y + dym * k };
|
57 | const tx = s2.x - cm.x;
|
58 | const ty = s2.y - cm.y;
|
59 | return {
|
60 | c1: new Point(m1.x + tx, m1.y + ty),
|
61 | c2: new Point(m2.x + tx, m2.y + ty),
|
62 | };
|
63 | }
|
64 | constructor(startPoint, control2, control1, endPoint, startWidth, endWidth) {
|
65 | this.startPoint = startPoint;
|
66 | this.control2 = control2;
|
67 | this.control1 = control1;
|
68 | this.endPoint = endPoint;
|
69 | this.startWidth = startWidth;
|
70 | this.endWidth = endWidth;
|
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 | }
|
98 |
|
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 | }
|
118 |
|
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 | }
|
157 |
|
158 | class SignaturePad extends SignatureEventTarget {
|
159 | constructor(canvas, options = {}) {
|
160 | var _a, _b, _c;
|
161 | super();
|
162 | this.canvas = canvas;
|
163 | this._drawingStroke = false;
|
164 | this._isEmpty = true;
|
165 | this._lastPoints = [];
|
166 | this._data = [];
|
167 | this._lastVelocity = 0;
|
168 | this._lastWidth = 0;
|
169 | this._handleMouseDown = (event) => {
|
170 | if (!this._isLeftButtonPressed(event, true) || this._drawingStroke) {
|
171 | return;
|
172 | }
|
173 | this._strokeBegin(this._pointerEventToSignatureEvent(event));
|
174 | };
|
175 | this._handleMouseMove = (event) => {
|
176 | if (!this._isLeftButtonPressed(event, true) || !this._drawingStroke) {
|
177 | this._strokeEnd(this._pointerEventToSignatureEvent(event), false);
|
178 | return;
|
179 | }
|
180 | this._strokeMoveUpdate(this._pointerEventToSignatureEvent(event));
|
181 | };
|
182 | this._handleMouseUp = (event) => {
|
183 | if (this._isLeftButtonPressed(event)) {
|
184 | return;
|
185 | }
|
186 | this._strokeEnd(this._pointerEventToSignatureEvent(event));
|
187 | };
|
188 | this._handleTouchStart = (event) => {
|
189 | if (event.targetTouches.length !== 1 || this._drawingStroke) {
|
190 | return;
|
191 | }
|
192 | if (event.cancelable) {
|
193 | event.preventDefault();
|
194 | }
|
195 | this._strokeBegin(this._touchEventToSignatureEvent(event));
|
196 | };
|
197 | this._handleTouchMove = (event) => {
|
198 | if (event.targetTouches.length !== 1) {
|
199 | return;
|
200 | }
|
201 | if (event.cancelable) {
|
202 | event.preventDefault();
|
203 | }
|
204 | if (!this._drawingStroke) {
|
205 | this._strokeEnd(this._touchEventToSignatureEvent(event), false);
|
206 | return;
|
207 | }
|
208 | this._strokeMoveUpdate(this._touchEventToSignatureEvent(event));
|
209 | };
|
210 | this._handleTouchEnd = (event) => {
|
211 | if (event.targetTouches.length !== 0) {
|
212 | return;
|
213 | }
|
214 | if (event.cancelable) {
|
215 | event.preventDefault();
|
216 | }
|
217 | this.canvas.removeEventListener('touchmove', this._handleTouchMove);
|
218 | this._strokeEnd(this._touchEventToSignatureEvent(event));
|
219 | };
|
220 | this._handlePointerDown = (event) => {
|
221 | if (!this._isLeftButtonPressed(event) || this._drawingStroke) {
|
222 | return;
|
223 | }
|
224 | event.preventDefault();
|
225 | this._strokeBegin(this._pointerEventToSignatureEvent(event));
|
226 | };
|
227 | this._handlePointerMove = (event) => {
|
228 | if (!this._isLeftButtonPressed(event, true) || !this._drawingStroke) {
|
229 | this._strokeEnd(this._pointerEventToSignatureEvent(event), false);
|
230 | return;
|
231 | }
|
232 | event.preventDefault();
|
233 | this._strokeMoveUpdate(this._pointerEventToSignatureEvent(event));
|
234 | };
|
235 | this._handlePointerUp = (event) => {
|
236 | if (this._isLeftButtonPressed(event)) {
|
237 | return;
|
238 | }
|
239 | event.preventDefault();
|
240 | this._strokeEnd(this._pointerEventToSignatureEvent(event));
|
241 | };
|
242 | this.velocityFilterWeight = options.velocityFilterWeight || 0.7;
|
243 | this.minWidth = options.minWidth || 0.5;
|
244 | this.maxWidth = options.maxWidth || 2.5;
|
245 | this.throttle = (_a = options.throttle) !== null && _a !== void 0 ? _a : 16;
|
246 | this.minDistance = (_b = options.minDistance) !== null && _b !== void 0 ? _b : 5;
|
247 | this.dotSize = options.dotSize || 0;
|
248 | this.penColor = options.penColor || 'black';
|
249 | this.backgroundColor = options.backgroundColor || 'rgba(0,0,0,0)';
|
250 | this.compositeOperation = options.compositeOperation || 'source-over';
|
251 | this.canvasContextOptions = (_c = options.canvasContextOptions) !== null && _c !== void 0 ? _c : {};
|
252 | this._strokeMoveUpdate = this.throttle
|
253 | ? throttle(SignaturePad.prototype._strokeUpdate, this.throttle)
|
254 | : SignaturePad.prototype._strokeUpdate;
|
255 | this._ctx = canvas.getContext('2d', this.canvasContextOptions);
|
256 | this.clear();
|
257 | this.on();
|
258 | }
|
259 | clear() {
|
260 | const { _ctx: ctx, canvas } = this;
|
261 | ctx.fillStyle = this.backgroundColor;
|
262 | ctx.clearRect(0, 0, canvas.width, canvas.height);
|
263 | ctx.fillRect(0, 0, canvas.width, canvas.height);
|
264 | this._data = [];
|
265 | this._reset(this._getPointGroupOptions());
|
266 | this._isEmpty = true;
|
267 | }
|
268 | fromDataURL(dataUrl, options = {}) {
|
269 | return new Promise((resolve, reject) => {
|
270 | const image = new Image();
|
271 | const ratio = options.ratio || window.devicePixelRatio || 1;
|
272 | const width = options.width || this.canvas.width / ratio;
|
273 | const height = options.height || this.canvas.height / ratio;
|
274 | const xOffset = options.xOffset || 0;
|
275 | const yOffset = options.yOffset || 0;
|
276 | this._reset(this._getPointGroupOptions());
|
277 | image.onload = () => {
|
278 | this._ctx.drawImage(image, xOffset, yOffset, width, height);
|
279 | resolve();
|
280 | };
|
281 | image.onerror = (error) => {
|
282 | reject(error);
|
283 | };
|
284 | image.crossOrigin = 'anonymous';
|
285 | image.src = dataUrl;
|
286 | this._isEmpty = false;
|
287 | });
|
288 | }
|
289 | toDataURL(type = 'image/png', encoderOptions) {
|
290 | switch (type) {
|
291 | case 'image/svg+xml':
|
292 | if (typeof encoderOptions !== 'object') {
|
293 | encoderOptions = undefined;
|
294 | }
|
295 | return `data:image/svg+xml;base64,${btoa(this.toSVG(encoderOptions))}`;
|
296 | default:
|
297 | if (typeof encoderOptions !== 'number') {
|
298 | encoderOptions = undefined;
|
299 | }
|
300 | return this.canvas.toDataURL(type, encoderOptions);
|
301 | }
|
302 | }
|
303 | on() {
|
304 | this.canvas.style.touchAction = 'none';
|
305 | this.canvas.style.msTouchAction = 'none';
|
306 | this.canvas.style.userSelect = 'none';
|
307 | const isIOS = /Macintosh/.test(navigator.userAgent) && 'ontouchstart' in document;
|
308 | if (window.PointerEvent && !isIOS) {
|
309 | this._handlePointerEvents();
|
310 | }
|
311 | else {
|
312 | this._handleMouseEvents();
|
313 | if ('ontouchstart' in window) {
|
314 | this._handleTouchEvents();
|
315 | }
|
316 | }
|
317 | }
|
318 | off() {
|
319 | this.canvas.style.touchAction = 'auto';
|
320 | this.canvas.style.msTouchAction = 'auto';
|
321 | this.canvas.style.userSelect = 'auto';
|
322 | this.canvas.removeEventListener('pointerdown', this._handlePointerDown);
|
323 | this.canvas.removeEventListener('mousedown', this._handleMouseDown);
|
324 | this.canvas.removeEventListener('touchstart', this._handleTouchStart);
|
325 | this._removeMoveUpEventListeners();
|
326 | }
|
327 | _getListenerFunctions() {
|
328 | var _a;
|
329 | const canvasWindow = window.document === this.canvas.ownerDocument
|
330 | ? window
|
331 | : (_a = this.canvas.ownerDocument.defaultView) !== null && _a !== void 0 ? _a : this.canvas.ownerDocument;
|
332 | return {
|
333 | addEventListener: canvasWindow.addEventListener.bind(canvasWindow),
|
334 | removeEventListener: canvasWindow.removeEventListener.bind(canvasWindow),
|
335 | };
|
336 | }
|
337 | _removeMoveUpEventListeners() {
|
338 | const { removeEventListener } = this._getListenerFunctions();
|
339 | removeEventListener('pointermove', this._handlePointerMove);
|
340 | removeEventListener('pointerup', this._handlePointerUp);
|
341 | removeEventListener('mousemove', this._handleMouseMove);
|
342 | removeEventListener('mouseup', this._handleMouseUp);
|
343 | removeEventListener('touchmove', this._handleTouchMove);
|
344 | removeEventListener('touchend', this._handleTouchEnd);
|
345 | }
|
346 | isEmpty() {
|
347 | return this._isEmpty;
|
348 | }
|
349 | fromData(pointGroups, { clear = true } = {}) {
|
350 | if (clear) {
|
351 | this.clear();
|
352 | }
|
353 | this._fromData(pointGroups, this._drawCurve.bind(this), this._drawDot.bind(this));
|
354 | this._data = this._data.concat(pointGroups);
|
355 | }
|
356 | toData() {
|
357 | return this._data;
|
358 | }
|
359 | _isLeftButtonPressed(event, only) {
|
360 | if (only) {
|
361 | return event.buttons === 1;
|
362 | }
|
363 | return (event.buttons & 1) === 1;
|
364 | }
|
365 | _pointerEventToSignatureEvent(event) {
|
366 | return {
|
367 | event: event,
|
368 | type: event.type,
|
369 | x: event.clientX,
|
370 | y: event.clientY,
|
371 | pressure: 'pressure' in event ? event.pressure : 0,
|
372 | };
|
373 | }
|
374 | _touchEventToSignatureEvent(event) {
|
375 | const touch = event.changedTouches[0];
|
376 | return {
|
377 | event: event,
|
378 | type: event.type,
|
379 | x: touch.clientX,
|
380 | y: touch.clientY,
|
381 | pressure: touch.force,
|
382 | };
|
383 | }
|
384 | _getPointGroupOptions(group) {
|
385 | return {
|
386 | penColor: group && 'penColor' in group ? group.penColor : this.penColor,
|
387 | dotSize: group && 'dotSize' in group ? group.dotSize : this.dotSize,
|
388 | minWidth: group && 'minWidth' in group ? group.minWidth : this.minWidth,
|
389 | maxWidth: group && 'maxWidth' in group ? group.maxWidth : this.maxWidth,
|
390 | velocityFilterWeight: group && 'velocityFilterWeight' in group
|
391 | ? group.velocityFilterWeight
|
392 | : this.velocityFilterWeight,
|
393 | compositeOperation: group && 'compositeOperation' in group
|
394 | ? group.compositeOperation
|
395 | : this.compositeOperation,
|
396 | };
|
397 | }
|
398 | _strokeBegin(event) {
|
399 | const cancelled = !this.dispatchEvent(new CustomEvent('beginStroke', { detail: event, cancelable: true }));
|
400 | if (cancelled) {
|
401 | return;
|
402 | }
|
403 | const { addEventListener } = this._getListenerFunctions();
|
404 | switch (event.event.type) {
|
405 | case 'mousedown':
|
406 | addEventListener('mousemove', this._handleMouseMove);
|
407 | addEventListener('mouseup', this._handleMouseUp);
|
408 | break;
|
409 | case 'touchstart':
|
410 | addEventListener('touchmove', this._handleTouchMove);
|
411 | addEventListener('touchend', this._handleTouchEnd);
|
412 | break;
|
413 | case 'pointerdown':
|
414 | addEventListener('pointermove', this._handlePointerMove);
|
415 | addEventListener('pointerup', this._handlePointerUp);
|
416 | break;
|
417 | }
|
418 | this._drawingStroke = true;
|
419 | const pointGroupOptions = this._getPointGroupOptions();
|
420 | const newPointGroup = Object.assign(Object.assign({}, pointGroupOptions), { points: [] });
|
421 | this._data.push(newPointGroup);
|
422 | this._reset(pointGroupOptions);
|
423 | this._strokeUpdate(event);
|
424 | }
|
425 | _strokeUpdate(event) {
|
426 | if (!this._drawingStroke) {
|
427 | return;
|
428 | }
|
429 | if (this._data.length === 0) {
|
430 | this._strokeBegin(event);
|
431 | return;
|
432 | }
|
433 | this.dispatchEvent(new CustomEvent('beforeUpdateStroke', { detail: event }));
|
434 | const point = this._createPoint(event.x, event.y, event.pressure);
|
435 | const lastPointGroup = this._data[this._data.length - 1];
|
436 | const lastPoints = lastPointGroup.points;
|
437 | const lastPoint = lastPoints.length > 0 && lastPoints[lastPoints.length - 1];
|
438 | const isLastPointTooClose = lastPoint
|
439 | ? point.distanceTo(lastPoint) <= this.minDistance
|
440 | : false;
|
441 | const pointGroupOptions = this._getPointGroupOptions(lastPointGroup);
|
442 | if (!lastPoint || !(lastPoint && isLastPointTooClose)) {
|
443 | const curve = this._addPoint(point, pointGroupOptions);
|
444 | if (!lastPoint) {
|
445 | this._drawDot(point, pointGroupOptions);
|
446 | }
|
447 | else if (curve) {
|
448 | this._drawCurve(curve, pointGroupOptions);
|
449 | }
|
450 | lastPoints.push({
|
451 | time: point.time,
|
452 | x: point.x,
|
453 | y: point.y,
|
454 | pressure: point.pressure,
|
455 | });
|
456 | }
|
457 | this.dispatchEvent(new CustomEvent('afterUpdateStroke', { detail: event }));
|
458 | }
|
459 | _strokeEnd(event, shouldUpdate = true) {
|
460 | this._removeMoveUpEventListeners();
|
461 | if (!this._drawingStroke) {
|
462 | return;
|
463 | }
|
464 | if (shouldUpdate) {
|
465 | this._strokeUpdate(event);
|
466 | }
|
467 | this._drawingStroke = false;
|
468 | this.dispatchEvent(new CustomEvent('endStroke', { detail: event }));
|
469 | }
|
470 | _handlePointerEvents() {
|
471 | this._drawingStroke = false;
|
472 | this.canvas.addEventListener('pointerdown', this._handlePointerDown);
|
473 | }
|
474 | _handleMouseEvents() {
|
475 | this._drawingStroke = false;
|
476 | this.canvas.addEventListener('mousedown', this._handleMouseDown);
|
477 | }
|
478 | _handleTouchEvents() {
|
479 | this.canvas.addEventListener('touchstart', this._handleTouchStart);
|
480 | }
|
481 | _reset(options) {
|
482 | this._lastPoints = [];
|
483 | this._lastVelocity = 0;
|
484 | this._lastWidth = (options.minWidth + options.maxWidth) / 2;
|
485 | this._ctx.fillStyle = options.penColor;
|
486 | this._ctx.globalCompositeOperation = options.compositeOperation;
|
487 | }
|
488 | _createPoint(x, y, pressure) {
|
489 | const rect = this.canvas.getBoundingClientRect();
|
490 | return new Point(x - rect.left, y - rect.top, pressure, new Date().getTime());
|
491 | }
|
492 | _addPoint(point, options) {
|
493 | const { _lastPoints } = this;
|
494 | _lastPoints.push(point);
|
495 | if (_lastPoints.length > 2) {
|
496 | if (_lastPoints.length === 3) {
|
497 | _lastPoints.unshift(_lastPoints[0]);
|
498 | }
|
499 | const widths = this._calculateCurveWidths(_lastPoints[1], _lastPoints[2], options);
|
500 | const curve = Bezier.fromPoints(_lastPoints, widths);
|
501 | _lastPoints.shift();
|
502 | return curve;
|
503 | }
|
504 | return null;
|
505 | }
|
506 | _calculateCurveWidths(startPoint, endPoint, options) {
|
507 | const velocity = options.velocityFilterWeight * endPoint.velocityFrom(startPoint) +
|
508 | (1 - options.velocityFilterWeight) * this._lastVelocity;
|
509 | const newWidth = this._strokeWidth(velocity, options);
|
510 | const widths = {
|
511 | end: newWidth,
|
512 | start: this._lastWidth,
|
513 | };
|
514 | this._lastVelocity = velocity;
|
515 | this._lastWidth = newWidth;
|
516 | return widths;
|
517 | }
|
518 | _strokeWidth(velocity, options) {
|
519 | return Math.max(options.maxWidth / (velocity + 1), options.minWidth);
|
520 | }
|
521 | _drawCurveSegment(x, y, width) {
|
522 | const ctx = this._ctx;
|
523 | ctx.moveTo(x, y);
|
524 | ctx.arc(x, y, width, 0, 2 * Math.PI, false);
|
525 | this._isEmpty = false;
|
526 | }
|
527 | _drawCurve(curve, options) {
|
528 | const ctx = this._ctx;
|
529 | const widthDelta = curve.endWidth - curve.startWidth;
|
530 | const drawSteps = Math.ceil(curve.length()) * 2;
|
531 | ctx.beginPath();
|
532 | ctx.fillStyle = options.penColor;
|
533 | for (let i = 0; i < drawSteps; i += 1) {
|
534 | const t = i / drawSteps;
|
535 | const tt = t * t;
|
536 | const ttt = tt * t;
|
537 | const u = 1 - t;
|
538 | const uu = u * u;
|
539 | const uuu = uu * u;
|
540 | let x = uuu * curve.startPoint.x;
|
541 | x += 3 * uu * t * curve.control1.x;
|
542 | x += 3 * u * tt * curve.control2.x;
|
543 | x += ttt * curve.endPoint.x;
|
544 | let y = uuu * curve.startPoint.y;
|
545 | y += 3 * uu * t * curve.control1.y;
|
546 | y += 3 * u * tt * curve.control2.y;
|
547 | y += ttt * curve.endPoint.y;
|
548 | const width = Math.min(curve.startWidth + ttt * widthDelta, options.maxWidth);
|
549 | this._drawCurveSegment(x, y, width);
|
550 | }
|
551 | ctx.closePath();
|
552 | ctx.fill();
|
553 | }
|
554 | _drawDot(point, options) {
|
555 | const ctx = this._ctx;
|
556 | const width = options.dotSize > 0
|
557 | ? options.dotSize
|
558 | : (options.minWidth + options.maxWidth) / 2;
|
559 | ctx.beginPath();
|
560 | this._drawCurveSegment(point.x, point.y, width);
|
561 | ctx.closePath();
|
562 | ctx.fillStyle = options.penColor;
|
563 | ctx.fill();
|
564 | }
|
565 | _fromData(pointGroups, drawCurve, drawDot) {
|
566 | for (const group of pointGroups) {
|
567 | const { points } = group;
|
568 | const pointGroupOptions = this._getPointGroupOptions(group);
|
569 | if (points.length > 1) {
|
570 | for (let j = 0; j < points.length; j += 1) {
|
571 | const basicPoint = points[j];
|
572 | const point = new Point(basicPoint.x, basicPoint.y, basicPoint.pressure, basicPoint.time);
|
573 | if (j === 0) {
|
574 | this._reset(pointGroupOptions);
|
575 | }
|
576 | const curve = this._addPoint(point, pointGroupOptions);
|
577 | if (curve) {
|
578 | drawCurve(curve, pointGroupOptions);
|
579 | }
|
580 | }
|
581 | }
|
582 | else {
|
583 | this._reset(pointGroupOptions);
|
584 | drawDot(points[0], pointGroupOptions);
|
585 | }
|
586 | }
|
587 | }
|
588 | toSVG({ includeBackgroundColor = false } = {}) {
|
589 | const pointGroups = this._data;
|
590 | const ratio = Math.max(window.devicePixelRatio || 1, 1);
|
591 | const minX = 0;
|
592 | const minY = 0;
|
593 | const maxX = this.canvas.width / ratio;
|
594 | const maxY = this.canvas.height / ratio;
|
595 | const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
596 | svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
|
597 | svg.setAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink');
|
598 | svg.setAttribute('viewBox', `${minX} ${minY} ${maxX} ${maxY}`);
|
599 | svg.setAttribute('width', maxX.toString());
|
600 | svg.setAttribute('height', maxY.toString());
|
601 | if (includeBackgroundColor && this.backgroundColor) {
|
602 | const rect = document.createElement('rect');
|
603 | rect.setAttribute('width', '100%');
|
604 | rect.setAttribute('height', '100%');
|
605 | rect.setAttribute('fill', this.backgroundColor);
|
606 | svg.appendChild(rect);
|
607 | }
|
608 | this._fromData(pointGroups, (curve, { penColor }) => {
|
609 | const path = document.createElement('path');
|
610 | if (!isNaN(curve.control1.x) &&
|
611 | !isNaN(curve.control1.y) &&
|
612 | !isNaN(curve.control2.x) &&
|
613 | !isNaN(curve.control2.y)) {
|
614 | const attr = `M ${curve.startPoint.x.toFixed(3)},${curve.startPoint.y.toFixed(3)} ` +
|
615 | `C ${curve.control1.x.toFixed(3)},${curve.control1.y.toFixed(3)} ` +
|
616 | `${curve.control2.x.toFixed(3)},${curve.control2.y.toFixed(3)} ` +
|
617 | `${curve.endPoint.x.toFixed(3)},${curve.endPoint.y.toFixed(3)}`;
|
618 | path.setAttribute('d', attr);
|
619 | path.setAttribute('stroke-width', (curve.endWidth * 2.25).toFixed(3));
|
620 | path.setAttribute('stroke', penColor);
|
621 | path.setAttribute('fill', 'none');
|
622 | path.setAttribute('stroke-linecap', 'round');
|
623 | svg.appendChild(path);
|
624 | }
|
625 | }, (point, { penColor, dotSize, minWidth, maxWidth }) => {
|
626 | const circle = document.createElement('circle');
|
627 | const size = dotSize > 0 ? dotSize : (minWidth + maxWidth) / 2;
|
628 | circle.setAttribute('r', size.toString());
|
629 | circle.setAttribute('cx', point.x.toString());
|
630 | circle.setAttribute('cy', point.y.toString());
|
631 | circle.setAttribute('fill', penColor);
|
632 | svg.appendChild(circle);
|
633 | });
|
634 | return svg.outerHTML;
|
635 | }
|
636 | }
|
637 |
|
638 | return SignaturePad;
|
639 |
|
640 | }));
|
641 |
|