b15c07bf490446b185bb0581645fb5bdfb99ce7c
[roojs1] / Roo / bootstrap / BezierSignature.js
1 /**
2 *    This script refer to:
3 *    Title: Signature Pad
4 *    Author: szimek
5 *    Availability: https://github.com/szimek/signature_pad
6 **/
7
8 /**
9  * @class Roo.bootstrap.BezierSignature
10  * @extends Roo.bootstrap.Component
11  * Bootstrap BezierSignature class
12  * 
13  * @constructor
14  * Create a new BezierSignature
15  * @param {Object} config The config object
16  */
17
18 Roo.bootstrap.BezierSignature = function(config){
19     Roo.bootstrap.BezierSignature.superclass.constructor.call(this, config);
20     this.addEvents({
21         "resize" : true
22     });
23 };
24
25 Roo.extend(Roo.bootstrap.BezierSignature, Roo.bootstrap.Component,  {
26     
27     _data: [],
28     
29     _isEmpty: true,
30     
31     _mouseButtonDown: true,
32     
33     /**
34      * @cfg(int) canvas height
35      */
36     canvas_height: '200px',
37     
38     /**
39      * @cfg(float or function) Radius of a single dot.
40      */ 
41     dotSize: false,
42     
43     /**
44      * @cfg(float) Minimum width of a line. Defaults to 0.5.
45      */
46     minWidth: 0.5,
47     
48     /**
49      * @cfg(float) Maximum width of a line. Defaults to 2.5.
50      */
51     maxWidth: 2.5,
52     
53     /**
54      * @cfg(integer) Draw the next point at most once per every x milliseconds. Set it to 0 to turn off throttling. Defaults to 16.
55      */
56     throttle: 16,
57     
58     /**
59      * @cfg(integer) Add the next point only if the previous one is farther than x pixels. Defaults to 5.
60      */
61     minDistance: 5,
62     
63     /**
64      * @cfg(string) Color used to clear the background. Can be any color format accepted by context.fillStyle. Defaults to "rgba(0,0,0,0)" (transparent black). Use a non-transparent color e.g. "rgb(255,255,255)" (opaque white) if you'd like to save signatures as JPEG images.
65      */
66     backgroundColor: 'rgba(0, 0, 0, 0)',
67     
68     /**
69      * @cfg(string) Color used to draw the lines. Can be any color format accepted by context.fillStyle. Defaults to "black".
70      */
71     penColor: 'black',
72     
73     /**
74      * @cfg(float) Weight used to modify new velocity based on the previous velocity. Defaults to 0.7.
75      */
76     velocityFilterWeight: 0.7,
77     
78     /**
79      * @cfg(function) Callback when stroke begin.
80      */
81     onBegin: false,
82     
83     /**
84      * @cfg(function) Callback when stroke end.
85      */
86     onEnd: false,
87     
88     getAutoCreate : function()
89     {
90         var cls = 'roo-signature column';
91         
92         if(this.cls){
93             cls += ' ' + this.cls;
94         }
95         
96         var col_sizes = [
97             'lg',
98             'md',
99             'sm',
100             'xs'
101         ];
102         
103         for(var i = 0; i < col_sizes.length; i++) {
104             if(this[col_sizes[i]]) {
105                 cls += " col-"+col_sizes[i]+"-"+this[col_sizes[i]];
106             }
107         }
108         
109         var cfg = {
110             tag: 'div',
111             cls: cls,
112             cn: [
113                 {
114                     tag: 'div',
115                     cls: 'roo-signature-body',
116                     cn: [
117                         {
118                             tag: 'canvas',
119                             cls: 'roo-signature-body-canvas',
120                             height: this.canvas_height,
121                             width: this.canvas_width
122                         }
123                     ]
124                 }
125             ]
126         };
127         
128         return cfg;
129     },
130     
131     initEvents: function() 
132     {
133         Roo.bootstrap.BezierSignature.superclass.initEvents.call(this);
134         
135         var canvas = this.canvasEl();
136         
137         // mouse && touch event swapping...
138         canvas.dom.style.touchAction = 'none';
139         canvas.dom.style.msTouchAction = 'none';
140         
141         this._mouseButtonDown = false;
142         canvas.on('mousedown', this._handleMouseDown, this);
143         canvas.on('mousemove', this._handleMouseMove, this);
144         Roo.select('html').first().on('mouseup', this._handleMouseUp, this);
145         
146         if (window.ontouchstart) {
147             canvas.on('touchstart', this._handleTouchStart, this);
148             canvas.on('touchmove', this._handleTouchMove, this);
149             canvas.on('touchend', this._handleTouchEnd, this);
150         }
151         
152         if(this.resize_to_parent_width) {
153             Roo.EventManager.onWindowResize(this.resize, this, true);
154         }
155         
156         this.clear();
157         
158         this.resize();
159     },
160     
161     resize: function(){
162         this.canvasEl().dom.width = this.el.dom.offsetWidth;
163     },
164     
165     _handleMouseDown: function(e)
166     {
167         if (e.browserEvent.which === 1) {
168             this._mouseButtonDown = true;
169             this.strokeBegin(e);
170         }
171     },
172     
173     _handleMouseMove: function (e)
174     {
175         if (this._mouseButtonDown) {
176             this.strokeMoveUpdate(e);
177         }
178     },
179     
180     _handleMouseUp: function (e)
181     {
182         if (e.browserEvent.which === 1 && this._mouseButtonDown) {
183             this._mouseButtonDown = false;
184             this.strokeEnd(e);
185         }
186     },
187     
188     _handleTouchStart: function (e) {
189         e.preventDefault();
190         if (e.browserEvent.targetTouches.length === 1) {
191             // var touch = e.browserEvent.changedTouches[0];
192             // this.strokeBegin(touch);
193             
194              this.strokeBegin(e); // assume e catching the correct xy...
195         }
196     },
197     
198     _handleTouchMove: function (e) {
199         e.preventDefault();
200         // var touch = event.targetTouches[0];
201         // _this._strokeMoveUpdate(touch);
202         this._strokeMoveUpdate(e);
203     },
204     
205     _handleTouchEnd: function (e) {
206         var wasCanvasTouched = e.target === this.canvasEl().dom;
207         if (wasCanvasTouched) {
208             e.preventDefault();
209             // var touch = event.changedTouches[0];
210             // _this._strokeEnd(touch);
211             this.strokeEnd(e);
212         }
213     },
214     
215     reset: function () {
216         this._lastPoints = [];
217         this._lastVelocity = 0;
218         this._lastWidth = (this.minWidth + this.maxWidth) / 2;
219         this.canvasElCtx().fillStyle = this.penColor;
220     },
221     
222     strokeMoveUpdate: function(e)
223     {
224         this.strokeUpdate(e);
225         
226         if (this.throttle) {
227             this.throttle(this.strokeUpdate, this.throttle);
228         }
229         else {
230             this.strokeUpdate(e);
231         }
232     },
233     
234     strokeBegin: function(e)
235     {
236         var newPointGroup = {
237             color: this.penColor,
238             points: []
239         };
240         
241         if (typeof this.onBegin === 'function') {
242             this.onBegin(e);
243         }
244         
245         this._data.push(newPointGroup);
246         this.reset();
247         this.strokeUpdate(e);
248     },
249     
250     strokeUpdate: function(e)
251     {
252         var rect = this.canvasEl().dom.getBoundingClientRect();
253         var point = new this.Point(e.xy[0] - rect.left, e.xy[1] - rect.top, new Date().getTime());
254         var lastPointGroup = this._data[this._data.length - 1];
255         var lastPoints = lastPointGroup.points;
256         var lastPoint = lastPoints.length > 0 && lastPoints[lastPoints.length - 1];
257         var isLastPointTooClose = lastPoint
258             ? point.distanceTo(lastPoint) <= this.minDistance
259             : false;
260         var color = lastPointGroup.color;
261         if (!lastPoint || !(lastPoint && isLastPointTooClose)) {
262             var curve = this.addPoint(point);
263             if (!lastPoint) {
264                 this.drawDot({color: color, point: point});
265             }
266             else if (curve) {
267                 this.drawCurve({color: color, curve: curve});
268             }
269             lastPoints.push({
270                 time: point.time,
271                 x: point.x,
272                 y: point.y
273             });
274         }
275     },
276     
277     strokeEnd: function(e)
278     {
279         this.strokeUpdate(e);
280         if (typeof this.onEnd === 'function') {
281             this.onEnd(e);
282         }
283     },
284     
285     addPoint:  function (point) {
286         var _lastPoints = this._lastPoints;
287         _lastPoints.push(point);
288         if (_lastPoints.length > 2) {
289             if (_lastPoints.length === 3) {
290                 _lastPoints.unshift(_lastPoints[0]);
291             }
292             var widths = this.calculateCurveWidths(_lastPoints[1], _lastPoints[2]);
293             var curve = this.Bezier.fromPoints(_lastPoints, widths, this);
294             _lastPoints.shift();
295             return curve;
296         }
297         return null;
298     },
299     
300     calculateCurveWidths: function (startPoint, endPoint) {
301         var velocity = this.velocityFilterWeight * endPoint.velocityFrom(startPoint) +
302             (1 - this.velocityFilterWeight) * this._lastVelocity;
303
304         var newWidth = Math.max(this.maxWidth / (velocity + 1), this.minWidth);
305         var widths = {
306             end: newWidth,
307             start: this._lastWidth
308         };
309         
310         this._lastVelocity = velocity;
311         this._lastWidth = newWidth;
312         return widths;
313     },
314     
315     drawDot: function (_a) {
316         var color = _a.color, point = _a.point;
317         var ctx = this.canvasElCtx();
318         var width = typeof this.dotSize === 'function' ? this.dotSize() : this.dotSize;
319         ctx.beginPath();
320         this.drawCurveSegment(point.x, point.y, width);
321         ctx.closePath();
322         ctx.fillStyle = color;
323         ctx.fill();
324     },
325     
326     drawCurve: function (_a) {
327         var color = _a.color, curve = _a.curve;
328         var ctx = this.canvasElCtx();
329         var widthDelta = curve.endWidth - curve.startWidth;
330         var drawSteps = Math.floor(curve.length()) * 2;
331         ctx.beginPath();
332         ctx.fillStyle = color;
333         for (var i = 0; i < drawSteps; i += 1) {
334         var t = i / drawSteps;
335         var tt = t * t;
336         var ttt = tt * t;
337         var u = 1 - t;
338         var uu = u * u;
339         var uuu = uu * u;
340         var x = uuu * curve.startPoint.x;
341         x += 3 * uu * t * curve.control1.x;
342         x += 3 * u * tt * curve.control2.x;
343         x += ttt * curve.endPoint.x;
344         var y = uuu * curve.startPoint.y;
345         y += 3 * uu * t * curve.control1.y;
346         y += 3 * u * tt * curve.control2.y;
347         y += ttt * curve.endPoint.y;
348         var width = curve.startWidth + ttt * widthDelta;
349         this.drawCurveSegment(x, y, width);
350         }
351         ctx.closePath();
352         ctx.fill();
353     },
354     
355     drawCurveSegment: function (x, y, width) {
356         var ctx = this.canvasElCtx();
357         ctx.moveTo(x, y);
358         ctx.arc(x, y, width, 0, 2 * Math.PI, false);
359         this._isEmpty = false;
360     },
361     
362     clear: function()
363     {
364         var ctx = this.canvasElCtx();
365         var canvas = this.canvasEl().dom;
366         ctx.fillStyle = this.backgroundColor;
367         ctx.clearRect(0, 0, canvas.width, canvas.height);
368         ctx.fillRect(0, 0, canvas.width, canvas.height);
369         this._data = [];
370         this.reset();
371         this._isEmpty = true;
372     },
373     
374     canvasEl: function()
375     {
376         return this.el.select('canvas',true).first();
377     },
378     
379     canvasElCtx: function()
380     {
381         return this.el.select('canvas',true).first().dom.getContext('2d');
382     },
383     
384     getImage: function(type)
385     {
386         if(this._isEmpty) {
387             return false;
388         }
389         
390         // encryption ?
391         return this.canvasEl().dom.toDataURL('image/'+type, false);
392     },
393     
394     drawFromImage: function(img_src)
395     {
396         var img = new Image();
397         
398         img.src = img_src;
399         
400         this.canvasElCtx().drawImage(img, 0, 0);
401     },
402     
403     // Bezier Point Constructor
404     Point: (function () {
405         function Point(x, y, time) {
406             this.x = x;
407             this.y = y;
408             this.time = time || Date.now();
409         }
410         Point.prototype.distanceTo = function (start) {
411             return Math.sqrt(Math.pow(this.x - start.x, 2) + Math.pow(this.y - start.y, 2));
412         };
413         Point.prototype.equals = function (other) {
414             return this.x === other.x && this.y === other.y && this.time === other.time;
415         };
416         Point.prototype.velocityFrom = function (start) {
417             return this.time !== start.time
418             ? this.distanceTo(start) / (this.time - start.time)
419             : 0;
420         };
421         return Point;
422     }()),
423     
424     
425     // Bezier Constructor
426     Bezier: (function () {
427         function Bezier(startPoint, control2, control1, endPoint, startWidth, endWidth) {
428             this.startPoint = startPoint;
429             this.control2 = control2;
430             this.control1 = control1;
431             this.endPoint = endPoint;
432             this.startWidth = startWidth;
433             this.endWidth = endWidth;
434         }
435         Bezier.fromPoints = function (points, widths, scope) {
436             var c2 = this.calculateControlPoints(points[0], points[1], points[2], scope).c2;
437             var c3 = this.calculateControlPoints(points[1], points[2], points[3], scope).c1;
438             return new Bezier(points[1], c2, c3, points[2], widths.start, widths.end);
439         };
440         Bezier.calculateControlPoints = function (s1, s2, s3, scope) {
441             var dx1 = s1.x - s2.x;
442             var dy1 = s1.y - s2.y;
443             var dx2 = s2.x - s3.x;
444             var dy2 = s2.y - s3.y;
445             var m1 = { x: (s1.x + s2.x) / 2.0, y: (s1.y + s2.y) / 2.0 };
446             var m2 = { x: (s2.x + s3.x) / 2.0, y: (s2.y + s3.y) / 2.0 };
447             var l1 = Math.sqrt(dx1 * dx1 + dy1 * dy1);
448             var l2 = Math.sqrt(dx2 * dx2 + dy2 * dy2);
449             var dxm = m1.x - m2.x;
450             var dym = m1.y - m2.y;
451             var k = l2 / (l1 + l2);
452             var cm = { x: m2.x + dxm * k, y: m2.y + dym * k };
453             var tx = s2.x - cm.x;
454             var ty = s2.y - cm.y;
455             return {
456                 c1: new scope.Point(m1.x + tx, m1.y + ty),
457                 c2: new scope.Point(m2.x + tx, m2.y + ty)
458             };
459         };
460         Bezier.prototype.length = function () {
461             var steps = 10;
462             var length = 0;
463             var px;
464             var py;
465             for (var i = 0; i <= steps; i += 1) {
466                 var t = i / steps;
467                 var cx = this.point(t, this.startPoint.x, this.control1.x, this.control2.x, this.endPoint.x);
468                 var cy = this.point(t, this.startPoint.y, this.control1.y, this.control2.y, this.endPoint.y);
469                 if (i > 0) {
470                     var xdiff = cx - px;
471                     var ydiff = cy - py;
472                     length += Math.sqrt(xdiff * xdiff + ydiff * ydiff);
473                 }
474                 px = cx;
475                 py = cy;
476             }
477             return length;
478         };
479         Bezier.prototype.point = function (t, start, c1, c2, end) {
480             return (start * (1.0 - t) * (1.0 - t) * (1.0 - t))
481             + (3.0 * c1 * (1.0 - t) * (1.0 - t) * t)
482             + (3.0 * c2 * (1.0 - t) * t * t)
483             + (end * t * t * t);
484         };
485         return Bezier;
486     }()),
487     
488     throttle: function(fn, wait) {
489       if (wait === void 0) { wait = 250; }
490       var previous = 0;
491       var timeout = null;
492       var result;
493       var storedContext;
494       var storedArgs;
495       var later = function () {
496           previous = Date.now();
497           timeout = null;
498           result = fn.apply(storedContext, storedArgs);
499           if (!timeout) {
500               storedContext = null;
501               storedArgs = [];
502           }
503       };
504       return function wrapper() {
505           var args = [];
506           for (var _i = 0; _i < arguments.length; _i++) {
507               args[_i] = arguments[_i];
508           }
509           var now = Date.now();
510           var remaining = wait - (now - previous);
511           storedContext = this;
512           storedArgs = args;
513           if (remaining <= 0 || remaining > wait) {
514               if (timeout) {
515                   clearTimeout(timeout);
516                   timeout = null;
517               }
518               previous = now;
519               result = fn.apply(storedContext, storedArgs);
520               if (!timeout) {
521                   storedContext = null;
522                   storedArgs = [];
523               }
524           }
525           else if (!timeout) {
526               timeout = window.setTimeout(later, remaining);
527           }
528           return result;
529       };
530   }
531   
532 });
533
534  
535
536