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