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