Upgrade to ExtJS 4.0.0 - Released 04/26/2011
[extjs.git] / src / draw / Draw.js
1 /*
2  * @class Ext.draw.Draw
3  * Base Drawing class.  Provides base drawing functions.
4  */
5
6 Ext.define('Ext.draw.Draw', {
7     /* Begin Definitions */
8
9     singleton: true,
10
11     requires: ['Ext.draw.Color'],
12
13     /* End Definitions */
14
15     pathToStringRE: /,?([achlmqrstvxz]),?/gi,
16     pathCommandRE: /([achlmqstvz])[\s,]*((-?\d*\.?\d*(?:e[-+]?\d+)?\s*,?\s*)+)/ig,
17     pathValuesRE: /(-?\d*\.?\d*(?:e[-+]?\d+)?)\s*,?\s*/ig,
18     stopsRE: /^(\d+%?)$/,
19     radian: Math.PI / 180,
20
21     availableAnimAttrs: {
22         along: "along",
23         blur: null,
24         "clip-rect": "csv",
25         cx: null,
26         cy: null,
27         fill: "color",
28         "fill-opacity": null,
29         "font-size": null,
30         height: null,
31         opacity: null,
32         path: "path",
33         r: null,
34         rotation: "csv",
35         rx: null,
36         ry: null,
37         scale: "csv",
38         stroke: "color",
39         "stroke-opacity": null,
40         "stroke-width": null,
41         translation: "csv",
42         width: null,
43         x: null,
44         y: null
45     },
46
47     is: function(o, type) {
48         type = String(type).toLowerCase();
49         return (type == "object" && o === Object(o)) ||
50             (type == "undefined" && typeof o == type) ||
51             (type == "null" && o === null) ||
52             (type == "array" && Array.isArray && Array.isArray(o)) ||
53             (Object.prototype.toString.call(o).toLowerCase().slice(8, -1)) == type;
54     },
55
56     ellipsePath: function(sprite) {
57         var attr = sprite.attr;
58         return Ext.String.format("M{0},{1}A{2},{3},0,1,1,{0},{4}A{2},{3},0,1,1,{0},{1}z", attr.x, attr.y - attr.ry, attr.rx, attr.ry, attr.y + attr.ry);
59     },
60
61     rectPath: function(sprite) {
62         var attr = sprite.attr;
63         if (attr.radius) {
64             return Ext.String.format("M{0},{1}l{2},0a{3},{3},0,0,1,{3},{3}l0,{5}a{3},{3},0,0,1,{4},{3}l{6},0a{3},{3},0,0,1,{4},{4}l0,{7}a{3},{3},0,0,1,{3},{4}z", attr.x + attr.radius, attr.y, attr.width - attr.radius * 2, attr.radius, -attr.radius, attr.height - attr.radius * 2, attr.radius * 2 - attr.width, attr.radius * 2 - attr.height);
65         }
66         else {
67             return Ext.String.format("M{0},{1}l{2},0,0,{3},{4},0z", attr.x, attr.y, attr.width, attr.height, -attr.width);
68         }
69     },
70
71     path2string: function () {
72         return this.join(",").replace(Ext.draw.Draw.pathToStringRE, "$1");
73     },
74
75     parsePathString: function (pathString) {
76         if (!pathString) {
77             return null;
78         }
79         var paramCounts = {a: 7, c: 6, h: 1, l: 2, m: 2, q: 4, s: 4, t: 2, v: 1, z: 0},
80             data = [],
81             me = this;
82         if (me.is(pathString, "array") && me.is(pathString[0], "array")) { // rough assumption
83             data = me.pathClone(pathString);
84         }
85         if (!data.length) {
86             String(pathString).replace(me.pathCommandRE, function (a, b, c) {
87                 var params = [],
88                     name = b.toLowerCase();
89                 c.replace(me.pathValuesRE, function (a, b) {
90                     b && params.push(+b);
91                 });
92                 if (name == "m" && params.length > 2) {
93                     data.push([b].concat(params.splice(0, 2)));
94                     name = "l";
95                     b = (b == "m") ? "l" : "L";
96                 }
97                 while (params.length >= paramCounts[name]) {
98                     data.push([b].concat(params.splice(0, paramCounts[name])));
99                     if (!paramCounts[name]) {
100                         break;
101                     }
102                 }
103             });
104         }
105         data.toString = me.path2string;
106         return data;
107     },
108
109     mapPath: function (path, matrix) {
110         if (!matrix) {
111             return path;
112         }
113         var x, y, i, ii, j, jj, pathi;
114         path = this.path2curve(path);
115         for (i = 0, ii = path.length; i < ii; i++) {
116             pathi = path[i];
117             for (j = 1, jj = pathi.length; j < jj-1; j += 2) {
118                 x = matrix.x(pathi[j], pathi[j + 1]);
119                 y = matrix.y(pathi[j], pathi[j + 1]);
120                 pathi[j] = x;
121                 pathi[j + 1] = y;
122             }
123         }
124         return path;
125     },
126
127     pathClone: function(pathArray) {
128         var res = [],
129             j,
130             jj,
131             i,
132             ii;
133         if (!this.is(pathArray, "array") || !this.is(pathArray && pathArray[0], "array")) { // rough assumption
134             pathArray = this.parsePathString(pathArray);
135         }
136         for (i = 0, ii = pathArray.length; i < ii; i++) {
137             res[i] = [];
138             for (j = 0, jj = pathArray[i].length; j < jj; j++) {
139                 res[i][j] = pathArray[i][j];
140             }
141         }
142         res.toString = this.path2string;
143         return res;
144     },
145
146     pathToAbsolute: function (pathArray) {
147         if (!this.is(pathArray, "array") || !this.is(pathArray && pathArray[0], "array")) { // rough assumption
148             pathArray = this.parsePathString(pathArray);
149         }
150         var res = [],
151             x = 0,
152             y = 0,
153             mx = 0,
154             my = 0,
155             start = 0,
156             i,
157             ii,
158             r,
159             pa,
160             j,
161             jj,
162             k,
163             kk;
164         if (pathArray[0][0] == "M") {
165             x = +pathArray[0][1];
166             y = +pathArray[0][2];
167             mx = x;
168             my = y;
169             start++;
170             res[0] = ["M", x, y];
171         }
172         for (i = start, ii = pathArray.length; i < ii; i++) {
173             r = res[i] = [];
174             pa = pathArray[i];
175             if (pa[0] != pa[0].toUpperCase()) {
176                 r[0] = pa[0].toUpperCase();
177                 switch (r[0]) {
178                     case "A":
179                         r[1] = pa[1];
180                         r[2] = pa[2];
181                         r[3] = pa[3];
182                         r[4] = pa[4];
183                         r[5] = pa[5];
184                         r[6] = +(pa[6] + x);
185                         r[7] = +(pa[7] + y);
186                         break;
187                     case "V":
188                         r[1] = +pa[1] + y;
189                         break;
190                     case "H":
191                         r[1] = +pa[1] + x;
192                         break;
193                     case "M":
194                         mx = +pa[1] + x;
195                         my = +pa[2] + y;
196                     default:
197                         for (j = 1, jj = pa.length; j < jj; j++) {
198                             r[j] = +pa[j] + ((j % 2) ? x : y);
199                         }
200                 }
201             } else {
202                 for (k = 0, kk = pa.length; k < kk; k++) {
203                     res[i][k] = pa[k];
204                 }
205             }
206             switch (r[0]) {
207                 case "Z":
208                     x = mx;
209                     y = my;
210                     break;
211                 case "H":
212                     x = r[1];
213                     break;
214                 case "V":
215                     y = r[1];
216                     break;
217                 case "M":
218                     mx = res[i][res[i].length - 2];
219                     my = res[i][res[i].length - 1];
220                 default:
221                     x = res[i][res[i].length - 2];
222                     y = res[i][res[i].length - 1];
223             }
224         }
225         res.toString = this.path2string;
226         return res;
227     },
228
229     pathToRelative: function (pathArray) {
230         if (!this.is(pathArray, "array") || !this.is(pathArray && pathArray[0], "array")) {
231             pathArray = this.parsePathString(pathArray);
232         }
233         var res = [],
234             x = 0,
235             y = 0,
236             mx = 0,
237             my = 0,
238             start = 0;
239         if (pathArray[0][0] == "M") {
240             x = pathArray[0][1];
241             y = pathArray[0][2];
242             mx = x;
243             my = y;
244             start++;
245             res.push(["M", x, y]);
246         }
247         for (var i = start, ii = pathArray.length; i < ii; i++) {
248             var r = res[i] = [],
249                 pa = pathArray[i];
250             if (pa[0] != pa[0].toLowerCase()) {
251                 r[0] = pa[0].toLowerCase();
252                 switch (r[0]) {
253                     case "a":
254                         r[1] = pa[1];
255                         r[2] = pa[2];
256                         r[3] = pa[3];
257                         r[4] = pa[4];
258                         r[5] = pa[5];
259                         r[6] = +(pa[6] - x).toFixed(3);
260                         r[7] = +(pa[7] - y).toFixed(3);
261                         break;
262                     case "v":
263                         r[1] = +(pa[1] - y).toFixed(3);
264                         break;
265                     case "m":
266                         mx = pa[1];
267                         my = pa[2];
268                     default:
269                         for (var j = 1, jj = pa.length; j < jj; j++) {
270                             r[j] = +(pa[j] - ((j % 2) ? x : y)).toFixed(3);
271                         }
272                 }
273             } else {
274                 r = res[i] = [];
275                 if (pa[0] == "m") {
276                     mx = pa[1] + x;
277                     my = pa[2] + y;
278                 }
279                 for (var k = 0, kk = pa.length; k < kk; k++) {
280                     res[i][k] = pa[k];
281                 }
282             }
283             var len = res[i].length;
284             switch (res[i][0]) {
285                 case "z":
286                     x = mx;
287                     y = my;
288                     break;
289                 case "h":
290                     x += +res[i][len - 1];
291                     break;
292                 case "v":
293                     y += +res[i][len - 1];
294                     break;
295                 default:
296                     x += +res[i][len - 2];
297                     y += +res[i][len - 1];
298             }
299         }
300         res.toString = this.path2string;
301         return res;
302     },
303
304     //Returns a path converted to a set of curveto commands
305     path2curve: function (path) {
306         var me = this,
307             points = me.pathToAbsolute(path),
308             ln = points.length,
309             attrs = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
310             i, seg, segLn, point;
311             
312         for (i = 0; i < ln; i++) {
313             points[i] = me.command2curve(points[i], attrs);
314             if (points[i].length > 7) {
315                     points[i].shift();
316                     point = points[i];
317                     while (point.length) {
318                         points.splice(i++, 0, ["C"].concat(point.splice(0, 6)));
319                     }
320                     points.splice(i, 1);
321                     ln = points.length;
322                 }
323             seg = points[i];
324             segLn = seg.length;
325             attrs.x = seg[segLn - 2];
326             attrs.y = seg[segLn - 1];
327             attrs.bx = parseFloat(seg[segLn - 4]) || attrs.x;
328             attrs.by = parseFloat(seg[segLn - 3]) || attrs.y;
329         }
330         return points;
331     },
332     
333     interpolatePaths: function (path, path2) {
334         var me = this,
335             p = me.pathToAbsolute(path),
336             p2 = me.pathToAbsolute(path2),
337             attrs = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
338             attrs2 = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
339             fixArc = function (pp, i) {
340                 if (pp[i].length > 7) {
341                     pp[i].shift();
342                     var pi = pp[i];
343                     while (pi.length) {
344                         pp.splice(i++, 0, ["C"].concat(pi.splice(0, 6)));
345                     }
346                     pp.splice(i, 1);
347                     ii = Math.max(p.length, p2.length || 0);
348                 }
349             },
350             fixM = function (path1, path2, a1, a2, i) {
351                 if (path1 && path2 && path1[i][0] == "M" && path2[i][0] != "M") {
352                     path2.splice(i, 0, ["M", a2.x, a2.y]);
353                     a1.bx = 0;
354                     a1.by = 0;
355                     a1.x = path1[i][1];
356                     a1.y = path1[i][2];
357                     ii = Math.max(p.length, p2.length || 0);
358                 }
359             };
360         for (var i = 0, ii = Math.max(p.length, p2.length || 0); i < ii; i++) {
361             p[i] = me.command2curve(p[i], attrs);
362             fixArc(p, i);
363             (p2[i] = me.command2curve(p2[i], attrs2));
364             fixArc(p2, i);
365             fixM(p, p2, attrs, attrs2, i);
366             fixM(p2, p, attrs2, attrs, i);
367             var seg = p[i],
368                 seg2 = p2[i],
369                 seglen = seg.length,
370                 seg2len = seg2.length;
371             attrs.x = seg[seglen - 2];
372             attrs.y = seg[seglen - 1];
373             attrs.bx = parseFloat(seg[seglen - 4]) || attrs.x;
374             attrs.by = parseFloat(seg[seglen - 3]) || attrs.y;
375             attrs2.bx = (parseFloat(seg2[seg2len - 4]) || attrs2.x);
376             attrs2.by = (parseFloat(seg2[seg2len - 3]) || attrs2.y);
377             attrs2.x = seg2[seg2len - 2];
378             attrs2.y = seg2[seg2len - 1];
379         }
380         return [p, p2];
381     },
382     
383     //Returns any path command as a curveto command based on the attrs passed
384     command2curve: function (pathCommand, d) {
385         var me = this;
386         if (!pathCommand) {
387             return ["C", d.x, d.y, d.x, d.y, d.x, d.y];
388         }
389         if (pathCommand[0] != "T" && pathCommand[0] != "Q") {
390             d.qx = d.qy = null;
391         }
392         switch (pathCommand[0]) {
393             case "M":
394                 d.X = pathCommand[1];
395                 d.Y = pathCommand[2];
396                 break;
397             case "A":
398                 pathCommand = ["C"].concat(me.arc2curve.apply(me, [d.x, d.y].concat(pathCommand.slice(1))));
399                 break;
400             case "S":
401                 pathCommand = ["C", d.x + (d.x - (d.bx || d.x)), d.y + (d.y - (d.by || d.y))].concat(pathCommand.slice(1));
402                 break;
403             case "T":
404                 d.qx = d.x + (d.x - (d.qx || d.x));
405                 d.qy = d.y + (d.y - (d.qy || d.y));
406                 pathCommand = ["C"].concat(me.quadratic2curve(d.x, d.y, d.qx, d.qy, pathCommand[1], pathCommand[2]));
407                 break;
408             case "Q":
409                 d.qx = pathCommand[1];
410                 d.qy = pathCommand[2];
411                 pathCommand = ["C"].concat(me.quadratic2curve(d.x, d.y, pathCommand[1], pathCommand[2], pathCommand[3], pathCommand[4]));
412                 break;
413             case "L":
414                 pathCommand = ["C"].concat(d.x, d.y, pathCommand[1], pathCommand[2], pathCommand[1], pathCommand[2]);
415                 break;
416             case "H":
417                 pathCommand = ["C"].concat(d.x, d.y, pathCommand[1], d.y, pathCommand[1], d.y);
418                 break;
419             case "V":
420                 pathCommand = ["C"].concat(d.x, d.y, d.x, pathCommand[1], d.x, pathCommand[1]);
421                 break;
422             case "Z":
423                 pathCommand = ["C"].concat(d.x, d.y, d.X, d.Y, d.X, d.Y);
424                 break;
425         }
426         return pathCommand;
427     },
428
429     quadratic2curve: function (x1, y1, ax, ay, x2, y2) {
430         var _13 = 1 / 3,
431             _23 = 2 / 3;
432         return [
433                 _13 * x1 + _23 * ax,
434                 _13 * y1 + _23 * ay,
435                 _13 * x2 + _23 * ax,
436                 _13 * y2 + _23 * ay,
437                 x2,
438                 y2
439             ];
440     },
441     
442     rotate: function (x, y, rad) {
443         var cos = Math.cos(rad),
444             sin = Math.sin(rad),
445             X = x * cos - y * sin,
446             Y = x * sin + y * cos;
447         return {x: X, y: Y};
448     },
449
450     arc2curve: function (x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, recursive) {
451         // for more information of where this Math came from visit:
452         // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
453         var me = this,
454             PI = Math.PI,
455             radian = me.radian,
456             _120 = PI * 120 / 180,
457             rad = radian * (+angle || 0),
458             res = [],
459             math = Math,
460             mcos = math.cos,
461             msin = math.sin,
462             msqrt = math.sqrt,
463             mabs = math.abs,
464             masin = math.asin,
465             xy, cos, sin, x, y, h, rx2, ry2, k, cx, cy, f1, f2, df, c1, s1, c2, s2,
466             t, hx, hy, m1, m2, m3, m4, newres, i, ln, f2old, x2old, y2old;
467         if (!recursive) {
468             xy = me.rotate(x1, y1, -rad);
469             x1 = xy.x;
470             y1 = xy.y;
471             xy = me.rotate(x2, y2, -rad);
472             x2 = xy.x;
473             y2 = xy.y;
474             cos = mcos(radian * angle);
475             sin = msin(radian * angle);
476             x = (x1 - x2) / 2;
477             y = (y1 - y2) / 2;
478             h = (x * x) / (rx * rx) + (y * y) / (ry * ry);
479             if (h > 1) {
480                 h = msqrt(h);
481                 rx = h * rx;
482                 ry = h * ry;
483             }
484             rx2 = rx * rx;
485             ry2 = ry * ry;
486             k = (large_arc_flag == sweep_flag ? -1 : 1) *
487                     msqrt(mabs((rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x)));
488             cx = k * rx * y / ry + (x1 + x2) / 2;
489             cy = k * -ry * x / rx + (y1 + y2) / 2;
490             f1 = masin(((y1 - cy) / ry).toFixed(7));
491             f2 = masin(((y2 - cy) / ry).toFixed(7));
492
493             f1 = x1 < cx ? PI - f1 : f1;
494             f2 = x2 < cx ? PI - f2 : f2;
495             if (f1 < 0) {
496                 f1 = PI * 2 + f1;
497             }
498             if (f2 < 0) {
499                 f2 = PI * 2 + f2;
500             }
501             if (sweep_flag && f1 > f2) {
502                 f1 = f1 - PI * 2;
503             }
504             if (!sweep_flag && f2 > f1) {
505                 f2 = f2 - PI * 2;
506             }
507         }
508         else {
509             f1 = recursive[0];
510             f2 = recursive[1];
511             cx = recursive[2];
512             cy = recursive[3];
513         }
514         df = f2 - f1;
515         if (mabs(df) > _120) {
516             f2old = f2;
517             x2old = x2;
518             y2old = y2;
519             f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1);
520             x2 = cx + rx * mcos(f2);
521             y2 = cy + ry * msin(f2);
522             res = me.arc2curve(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [f2, f2old, cx, cy]);
523         }
524         df = f2 - f1;
525         c1 = mcos(f1);
526         s1 = msin(f1);
527         c2 = mcos(f2);
528         s2 = msin(f2);
529         t = math.tan(df / 4);
530         hx = 4 / 3 * rx * t;
531         hy = 4 / 3 * ry * t;
532         m1 = [x1, y1];
533         m2 = [x1 + hx * s1, y1 - hy * c1];
534         m3 = [x2 + hx * s2, y2 - hy * c2];
535         m4 = [x2, y2];
536         m2[0] = 2 * m1[0] - m2[0];
537         m2[1] = 2 * m1[1] - m2[1];
538         if (recursive) {
539             return [m2, m3, m4].concat(res);
540         }
541         else {
542             res = [m2, m3, m4].concat(res).join().split(",");
543             newres = [];
544             ln = res.length;
545             for (i = 0;  i < ln; i++) {
546                 newres[i] = i % 2 ? me.rotate(res[i - 1], res[i], rad).y : me.rotate(res[i], res[i + 1], rad).x;
547             }
548             return newres;
549         }
550     },
551     
552     rotatePoint: function (x, y, alpha, cx, cy) {
553         if (!alpha) {
554             return {
555                 x: x,
556                 y: y
557             };
558         }
559         cx = cx || 0;
560         cy = cy || 0;
561         x = x - cx;
562         y = y - cy;
563         alpha = alpha * this.radian;
564         var cos = Math.cos(alpha),
565             sin = Math.sin(alpha);
566         return {
567             x: x * cos - y * sin + cx,
568             y: x * sin + y * cos + cy
569         };
570     },
571
572     rotateAndTranslatePath: function (sprite) {
573         var alpha = sprite.rotation.degrees,
574             cx = sprite.rotation.x,
575             cy = sprite.rotation.y,
576             dx = sprite.translation.x,
577             dy = sprite.translation.y,
578             path,
579             i,
580             p,
581             xy,
582             j,
583             res = [];
584         if (!alpha && !dx && !dy) {
585             return this.pathToAbsolute(sprite.attr.path);
586         }
587         dx = dx || 0;
588         dy = dy || 0;
589         path = this.pathToAbsolute(sprite.attr.path);
590         for (i = path.length; i--;) {
591             p = res[i] = path[i].slice();
592             if (p[0] == "A") {
593                 xy = this.rotatePoint(p[6], p[7], alpha, cx, cy);
594                 p[6] = xy.x + dx;
595                 p[7] = xy.y + dy;
596             } else {
597                 j = 1;
598                 while (p[j + 1] != null) {
599                     xy = this.rotatePoint(p[j], p[j + 1], alpha, cx, cy);
600                     p[j] = xy.x + dx;
601                     p[j + 1] = xy.y + dy;
602                     j += 2;
603                 }
604             }
605         }
606         return res;
607     },
608     
609     pathDimensions: function (path) {
610         if (!path || !(path + "")) {
611             return {x: 0, y: 0, width: 0, height: 0};
612         }
613         path = this.path2curve(path);
614         var x = 0, 
615             y = 0,
616             X = [],
617             Y = [],
618             p,
619             i,
620             ii,
621             xmin,
622             ymin,
623             dim;
624         for (i = 0, ii = path.length; i < ii; i++) {
625             p = path[i];
626             if (p[0] == "M") {
627                 x = p[1];
628                 y = p[2];
629                 X.push(x);
630                 Y.push(y);
631             }
632             else {
633                 dim = this.curveDim(x, y, p[1], p[2], p[3], p[4], p[5], p[6]);
634                 X = X.concat(dim.min.x, dim.max.x);
635                 Y = Y.concat(dim.min.y, dim.max.y);
636                 x = p[5];
637                 y = p[6];
638             }
639         }
640         xmin = Math.min.apply(0, X);
641         ymin = Math.min.apply(0, Y);
642         return {
643             x: xmin,
644             y: ymin,
645             path: path,
646             width: Math.max.apply(0, X) - xmin,
647             height: Math.max.apply(0, Y) - ymin
648         };
649     },
650     
651     intersect: function(subjectPolygon, clipPolygon) {
652         var cp1, cp2, s, e, point;
653         var inside = function(p) {
654             return (cp2[0]-cp1[0]) * (p[1]-cp1[1]) > (cp2[1]-cp1[1]) * (p[0]-cp1[0]);
655         };
656         var intersection = function() {
657             var p = [];
658             var dcx = cp1[0]-cp2[0],
659                 dcy = cp1[1]-cp2[1],
660                 dpx = s[0]-e[0],
661                 dpy = s[1]-e[1],
662                 n1 = cp1[0]*cp2[1] - cp1[1]*cp2[0],
663                 n2 = s[0]*e[1] - s[1]*e[0],
664                 n3 = 1 / (dcx*dpy - dcy*dpx);
665
666             p[0] = (n1*dpx - n2*dcx) * n3;
667             p[1] = (n1*dpy - n2*dcy) * n3;
668             return p;
669         };
670         var outputList = subjectPolygon;
671         cp1 = clipPolygon[clipPolygon.length -1];
672         for (var i = 0, l = clipPolygon.length; i < l; ++i) {
673             cp2 = clipPolygon[i];
674             var inputList = outputList;
675             outputList = [];
676             s = inputList[inputList.length -1];
677             for (var j = 0, ln = inputList.length; j < ln; j++) {
678                 e = inputList[j];
679                 if (inside(e)) {
680                     if (!inside(s)) {
681                         outputList.push(intersection());
682                     }
683                     outputList.push(e);
684                 } else if (inside(s)) {
685                     outputList.push(intersection());
686                 }
687                 s = e;
688             }
689             cp1 = cp2;
690         }
691         return outputList;
692     },
693     
694     curveDim: function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
695         var a = (c2x - 2 * c1x + p1x) - (p2x - 2 * c2x + c1x),
696             b = 2 * (c1x - p1x) - 2 * (c2x - c1x),
697             c = p1x - c1x,
698             t1 = (-b + Math.sqrt(b * b - 4 * a * c)) / 2 / a,
699             t2 = (-b - Math.sqrt(b * b - 4 * a * c)) / 2 / a,
700             y = [p1y, p2y],
701             x = [p1x, p2x],
702             dot;
703         if (Math.abs(t1) > 1e12) {
704             t1 = 0.5;
705         }
706         if (Math.abs(t2) > 1e12) {
707             t2 = 0.5;
708         }
709         if (t1 > 0 && t1 < 1) {
710             dot = this.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1);
711             x.push(dot.x);
712             y.push(dot.y);
713         }
714         if (t2 > 0 && t2 < 1) {
715             dot = this.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2);
716             x.push(dot.x);
717             y.push(dot.y);
718         }
719         a = (c2y - 2 * c1y + p1y) - (p2y - 2 * c2y + c1y);
720         b = 2 * (c1y - p1y) - 2 * (c2y - c1y);
721         c = p1y - c1y;
722         t1 = (-b + Math.sqrt(b * b - 4 * a * c)) / 2 / a;
723         t2 = (-b - Math.sqrt(b * b - 4 * a * c)) / 2 / a;
724         if (Math.abs(t1) > 1e12) {
725             t1 = 0.5;
726         }
727         if (Math.abs(t2) > 1e12) {
728             t2 = 0.5;
729         }
730         if (t1 > 0 && t1 < 1) {
731             dot = this.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1);
732             x.push(dot.x);
733             y.push(dot.y);
734         }
735         if (t2 > 0 && t2 < 1) {
736             dot = this.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2);
737             x.push(dot.x);
738             y.push(dot.y);
739         }
740         return {
741             min: {x: Math.min.apply(0, x), y: Math.min.apply(0, y)},
742             max: {x: Math.max.apply(0, x), y: Math.max.apply(0, y)}
743         };
744     },
745
746     getAnchors: function (p1x, p1y, p2x, p2y, p3x, p3y, value) {
747         value = value || 4;
748         var l = Math.min(Math.sqrt(Math.pow(p1x - p2x, 2) + Math.pow(p1y - p2y, 2)) / value, Math.sqrt(Math.pow(p3x - p2x, 2) + Math.pow(p3y - p2y, 2)) / value),
749             a = Math.atan((p2x - p1x) / Math.abs(p2y - p1y)),
750             b = Math.atan((p3x - p2x) / Math.abs(p2y - p3y)),
751             pi = Math.PI;
752         a = p1y < p2y ? pi - a : a;
753         b = p3y < p2y ? pi - b : b;
754         var alpha = pi / 2 - ((a + b) % (pi * 2)) / 2;
755         alpha > pi / 2 && (alpha -= pi);
756         var dx1 = l * Math.sin(alpha + a),
757             dy1 = l * Math.cos(alpha + a),
758             dx2 = l * Math.sin(alpha + b),
759             dy2 = l * Math.cos(alpha + b),
760             out = {
761                 x1: p2x - dx1,
762                 y1: p2y + dy1,
763                 x2: p2x + dx2,
764                 y2: p2y + dy2
765             };
766         return out;
767     },
768
769     /* Smoothing function for a path.  Converts a path into cubic beziers.  Value defines the divider of the distance between points.
770      * Defaults to a value of 4.
771      */
772     smooth: function (originalPath, value) {
773         var path = this.path2curve(originalPath),
774             newp = [path[0]],
775             x = path[0][1],
776             y = path[0][2],
777             j,
778             points,
779             i = 1,
780             ii = path.length,
781             beg = 1,
782             mx = x,
783             my = y,
784             cx = 0,
785             cy = 0;
786         for (; i < ii; i++) {
787             var pathi = path[i],
788                 pathil = pathi.length,
789                 pathim = path[i - 1],
790                 pathiml = pathim.length,
791                 pathip = path[i + 1],
792                 pathipl = pathip && pathip.length;
793             if (pathi[0] == "M") {
794                 mx = pathi[1];
795                 my = pathi[2];
796                 j = i + 1;
797                 while (path[j][0] != "C") {
798                     j++;
799                 }
800                 cx = path[j][5];
801                 cy = path[j][6];
802                 newp.push(["M", mx, my]);
803                 beg = newp.length;
804                 x = mx;
805                 y = my;
806                 continue;
807             }
808             if (pathi[pathil - 2] == mx && pathi[pathil - 1] == my && (!pathip || pathip[0] == "M")) {
809                 var begl = newp[beg].length;
810                 points = this.getAnchors(pathim[pathiml - 2], pathim[pathiml - 1], mx, my, newp[beg][begl - 2], newp[beg][begl - 1], value);
811                 newp[beg][1] = points.x2;
812                 newp[beg][2] = points.y2;
813             }
814             else if (!pathip || pathip[0] == "M") {
815                 points = {
816                     x1: pathi[pathil - 2],
817                     y1: pathi[pathil - 1]
818                 };
819             } else {
820                 points = this.getAnchors(pathim[pathiml - 2], pathim[pathiml - 1], pathi[pathil - 2], pathi[pathil - 1], pathip[pathipl - 2], pathip[pathipl - 1], value);
821             }
822             newp.push(["C", x, y, points.x1, points.y1, pathi[pathil - 2], pathi[pathil - 1]]);
823             x = points.x2;
824             y = points.y2;
825         }
826         return newp;
827     },
828
829     findDotAtSegment: function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
830         var t1 = 1 - t;
831         return {
832             x: Math.pow(t1, 3) * p1x + Math.pow(t1, 2) * 3 * t * c1x + t1 * 3 * t * t * c2x + Math.pow(t, 3) * p2x,
833             y: Math.pow(t1, 3) * p1y + Math.pow(t1, 2) * 3 * t * c1y + t1 * 3 * t * t * c2y + Math.pow(t, 3) * p2y
834         };
835     },
836
837     snapEnds: function (from, to, stepsMax) {
838         var step = (to - from) / stepsMax,
839             level = Math.floor(Math.log(step) / Math.LN10) + 1,
840             m = Math.pow(10, level),
841             cur,
842             modulo = Math.round((step % m) * Math.pow(10, 2 - level)),
843             interval = [[0, 15], [20, 4], [30, 2], [40, 4], [50, 9], [60, 4], [70, 2], [80, 4], [100, 15]],
844             stepCount = 0,
845             value,
846             weight,
847             i,
848             topValue,
849             topWeight = 1e9,
850             ln = interval.length;
851         cur = from = Math.floor(from / m) * m;
852         for (i = 0; i < ln; i++) {
853             value = interval[i][0];
854             weight = (value - modulo) < 0 ? 1e6 : (value - modulo) / interval[i][1];
855             if (weight < topWeight) {
856                 topValue = value;
857                 topWeight = weight;
858             }
859         }
860         step = Math.floor(step * Math.pow(10, -level)) * Math.pow(10, level) + topValue * Math.pow(10, level - 2);
861         while (cur < to) {
862             cur += step;
863             stepCount++;
864         }
865         to = +cur.toFixed(10);
866         return {
867             from: from,
868             to: to,
869             power: level,
870             step: step,
871             steps: stepCount
872         };
873     },
874
875     sorter: function (a, b) {
876         return a.offset - b.offset;
877     },
878
879     rad: function(degrees) {
880         return degrees % 360 * Math.PI / 180;
881     },
882
883     degrees: function(radian) {
884         return radian * 180 / Math.PI % 360;
885     },
886
887     withinBox: function(x, y, bbox) {
888         bbox = bbox || {};
889         return (x >= bbox.x && x <= (bbox.x + bbox.width) && y >= bbox.y && y <= (bbox.y + bbox.height));
890     },
891
892     parseGradient: function(gradient) {
893         var me = this,
894             type = gradient.type || 'linear',
895             angle = gradient.angle || 0,
896             radian = me.radian,
897             stops = gradient.stops,
898             stopsArr = [],
899             stop,
900             vector,
901             max,
902             stopObj;
903
904         if (type == 'linear') {
905             vector = [0, 0, Math.cos(angle * radian), Math.sin(angle * radian)];
906             max = 1 / (Math.max(Math.abs(vector[2]), Math.abs(vector[3])) || 1);
907             vector[2] *= max;
908             vector[3] *= max;
909             if (vector[2] < 0) {
910                 vector[0] = -vector[2];
911                 vector[2] = 0;
912             }
913             if (vector[3] < 0) {
914                 vector[1] = -vector[3];
915                 vector[3] = 0;
916             }
917         }
918
919         for (stop in stops) {
920             if (stops.hasOwnProperty(stop) && me.stopsRE.test(stop)) {
921                 stopObj = {
922                     offset: parseInt(stop, 10),
923                     color: Ext.draw.Color.toHex(stops[stop].color) || '#ffffff',
924                     opacity: stops[stop].opacity || 1
925                 };
926                 stopsArr.push(stopObj);
927             }
928         }
929         // Sort by pct property
930         Ext.Array.sort(stopsArr, me.sorter);
931         if (type == 'linear') {
932             return {
933                 id: gradient.id,
934                 type: type,
935                 vector: vector,
936                 stops: stopsArr
937             };
938         }
939         else {
940             return {
941                 id: gradient.id,
942                 type: type,
943                 centerX: gradient.centerX,
944                 centerY: gradient.centerY,
945                 focalX: gradient.focalX,
946                 focalY: gradient.focalY,
947                 radius: gradient.radius,
948                 vector: vector,
949                 stops: stopsArr
950             };
951         }
952     }
953 });