Added caching.
[raphael] / raphael.js
1 /*
2  * Raphael 1.0 - JavaScript Vector Library
3  *
4  * Copyright (c) 2008 - 2009 Dmitry Baranovskiy (http://raphaeljs.com)
5  * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
6  */
7
8
9 window.Raphael = (function () {
10     var separator = /[, ]+/,
11         doc = document,
12         win = window,
13         oldRaphael = {
14             was: "Raphael" in window,
15             is: window.Raphael
16         },
17         R = function () {
18             return create.apply(R, arguments);
19         },
20         paper = {},
21         availableAttrs = {cx: 0, cy: 0, fill: "#fff", "fill-opacity": 1, font: '10px "Arial"', "font-family": '"Arial"', "font-size": "10", "font-style": "normal", "font-weight": 400, gradient: 0, height: 0, href: "http://raphaeljs.com/", opacity: 1, path: "M0,0", r: 0, rotation: 0, rx: 0, ry: 0, scale: "1 1", src: "", stroke: "#000", "stroke-dasharray": "", "stroke-linecap": "butt", "stroke-linejoin": "butt", "stroke-miterlimit": 0, "stroke-opacity": 1, "stroke-width": 1, target: "_blank", "text-anchor": "middle", title: "Raphael", translation: "0 0", width: 0, x: 0, y: 0},
22         availableAnimAttrs = {cx: "number", cy: "number", fill: "colour", "fill-opacity": "number", "font-size": "number", height: "number", opacity: "number", path: "path", r: "number", rotation: "csv", rx: "number", ry: "number", scale: "csv", stroke: "colour", "stroke-opacity": "number", "stroke-width": "number", translation: "csv", width: "number", x: "number", y: "number"},
23         events = ["click", "dblclick", "mousedown", "mousemove", "mouseout", "mouseover", "mouseup"];
24     R.version = "1.0";
25     R.type = (window.SVGAngle || document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1") ? "SVG" : "VML");
26     R.svg = !(R.vml = R.type == "VML");
27     R.idGenerator = 0;
28     R.fn = {};
29     R.isArray = function (arr) {
30         return Object.prototype.toString.call(arr) == "[object Array]";
31     };
32     R.setWindow = function (newwin) {
33         win = newwin;
34         doc = win.document;
35     };
36     // colour utilities
37     R.hsb2rgb = cacher(function (hue, saturation, brightness) {
38         if (typeof hue == "object" && "h" in hue && "s" in hue && "b" in hue) {
39             brightness = hue.b;
40             saturation = hue.s;
41             hue = hue.h;
42         }
43         var red,
44             green,
45             blue;
46         if (brightness == 0) {
47             return {r: 0, g: 0, b: 0, hex: "#000"};
48         }
49         if (hue > 1 || saturation > 1 || brightness > 1) {
50             hue /= 255;
51             saturation /= 255;
52             brightness /= 255;
53         }
54         var i = Math.floor(hue * 6),
55             f = (hue * 6) - i,
56             p = brightness * (1 - saturation),
57             q = brightness * (1 - (saturation * f)),
58             t = brightness * (1 - (saturation * (1 - f)));
59         red = [brightness, q, p, p, t, brightness, brightness][i];
60         green = [t, brightness, brightness, q, p, p, t][i];
61         blue = [p, p, t, brightness, brightness, q, p][i];
62         red *= 255;
63         green *= 255;
64         blue *= 255;
65         var rgb = {r: red, g: green, b: blue},
66             r = Math.round(red).toString(16),
67             g = Math.round(green).toString(16),
68             b = Math.round(blue).toString(16);
69         if (r.length == 1) {
70             r = "0" + r;
71         }
72         if (g.length == 1) {
73             g = "0" + g;
74         }
75         if (b.length == 1) {
76             b = "0" + b;
77         }
78         rgb.hex = "#" + r + g + b;
79         return rgb;
80     }, R);
81     R.rgb2hsb = cacher(function (red, green, blue) {
82         if (typeof red == "object" && "r" in red && "g" in red && "b" in red) {
83             blue = red.b;
84             green = red.g;
85             red = red.r;
86         }
87         if (typeof red == "string") {
88             var clr = R.getRGB(red);
89             red = clr.r;
90             green = clr.g;
91             blue = clr.b;
92         }
93         if (red > 1 || green > 1 || blue > 1) {
94             red /= 255;
95             green /= 255;
96             blue /= 255;
97         }
98         var max = Math.max(red, green, blue),
99             min = Math.min(red, green, blue),
100             hue,
101             saturation,
102             brightness = max;
103         if (min == max) {
104             return {h: 0, s: 0, b: max};
105         } else {
106             var delta = (max - min);
107             saturation = delta / max;
108             if (red == max) {
109                 hue = (green - blue) / delta;
110             } else if (green == max) {
111                 hue = 2 + ((blue - red) / delta);
112             } else {
113                 hue = 4 + ((red - green) / delta);
114             }
115             hue /= 6;
116             if (hue < 0) {
117                 hue += 1;
118             }
119             if (hue > 1) {
120                 hue -= 1;
121             }
122         }
123         return {h: hue, s: saturation, b: brightness};
124     }, R);
125     R._path2string = function () {
126         var res = "",
127             item;
128         for (var i = 0, ii = this.length; i < ii; i++) {
129             for (var j = 0, jj = this[i].length; j < jj; j++) {
130                 res += this[i][j];
131                 j && j != jj - 1 && (res += ",");
132             }
133             i != ii - 1 && (res += "\n");
134         }
135         return res.replace(/,(?=-)/g, "");
136     };
137     function cacher(f, scope, postprocessor) {
138         function newf() {
139             var arg = Array.prototype.splice.call(arguments, 0, arguments.length),
140                 args = arg.join("\u25ba");
141             newf.cache = newf.cache || {};
142             newf.count = newf.count || [];
143             if (args in newf.cache) {
144                 return postprocessor ? postprocessor(newf.cache[args]) : newf.cache[args];
145             }
146             if (newf.count.length > 1000) {
147                 delete newf.cache[newf.count.unshift()];
148             }
149             newf.count.push(args);
150             newf.cache[args] = f.apply(scope, arg);
151             return postprocessor ? postprocessor(newf.cache[args]) : newf.cache[args];
152         }
153         return newf;
154     }
155
156     R.getRGB = cacher(function (colour) {
157         var htmlcolors = {aliceblue: "#f0f8ff", amethyst: "#96c", antiquewhite: "#faebd7", aqua: "#0ff", aquamarine: "#7fffd4", azure: "#f0ffff", beige: "#f5f5dc", bisque: "#ffe4c4", black: "#000", blanchedalmond: "#ffebcd", blue: "#00f", blueviolet: "#8a2be2", brown: "#a52a2a", burlywood: "#deb887", cadetblue: "#5f9ea0", chartreuse: "#7fff00", chocolate: "#d2691e", coral: "#ff7f50", cornflowerblue: "#6495ed", cornsilk: "#fff8dc", crimson: "#dc143c", cyan: "#0ff", darkblue: "#00008b", darkcyan: "#008b8b", darkgoldenrod: "#b8860b", darkgray: "#a9a9a9", darkgreen: "#006400", darkkhaki: "#bdb76b", darkmagenta: "#8b008b", darkolivegreen: "#556b2f", darkorange: "#ff8c00", darkorchid: "#9932cc", darkred: "#8b0000", darksalmon: "#e9967a", darkseagreen: "#8fbc8f", darkslateblue: "#483d8b", darkslategray: "#2f4f4f", darkturquoise: "#00ced1", darkviolet: "#9400d3", deeppink: "#ff1493", deepskyblue: "#00bfff", dimgray: "#696969", dodgerblue: "#1e90ff", firebrick: "#b22222", floralwhite: "#fffaf0", forestgreen: "#228b22", fuchsia: "#f0f", gainsboro: "#dcdcdc", ghostwhite: "#f8f8ff", gold: "#ffd700", goldenrod: "#daa520", gray: "#808080", green: "#008000", greenyellow: "#adff2f", honeydew: "#f0fff0", hotpink: "#ff69b4", indianred: "#cd5c5c", indigo: "#4b0082", ivory: "#fffff0", khaki: "#f0e68c", lavender: "#e6e6fa", lavenderblush: "#fff0f5", lawngreen: "#7cfc00", lemonchiffon: "#fffacd", lightblue: "#add8e6", lightcoral: "#f08080", lightcyan: "#e0ffff", lightgoldenrodyellow: "#fafad2", lightgreen: "#90ee90", lightgrey: "#d3d3d3", lightpink: "#ffb6c1", lightsalmon: "#ffa07a", lightsalmon: "#ffa07a", lightseagreen: "#20b2aa", lightskyblue: "#87cefa", lightslategray: "#789", lightsteelblue: "#b0c4de", lightyellow: "#ffffe0", lime: "#0f0", limegreen: "#32cd32", linen: "#faf0e6", magenta: "#f0f", maroon: "#800000", mediumaquamarine: "#66cdaa", mediumblue: "#0000cd", mediumorchid: "#ba55d3", mediumpurple: "#9370db", mediumseagreen: "#3cb371", mediumslateblue: "#7b68ee", mediumslateblue: "#7b68ee", mediumspringgreen: "#00fa9a", mediumturquoise: "#48d1cc", mediumvioletred: "#c71585", midnightblue: "#191970", mintcream: "#f5fffa", mistyrose: "#ffe4e1", moccasin: "#ffe4b5", navajowhite: "#ffdead", navy: "#000080", oldlace: "#fdf5e6", olive: "#808000", olivedrab: "#6b8e23", orange: "#ffa500", orangered: "#ff4500", orchid: "#da70d6", palegoldenrod: "#eee8aa", palegreen: "#98fb98", paleturquoise: "#afeeee", palevioletred: "#db7093", papayawhip: "#ffefd5", peachpuff: "#ffdab9", peru: "#cd853f", pink: "#ffc0cb", plum: "#dda0dd", powderblue: "#b0e0e6", purple: "#800080", red: "#f00", rosybrown: "#bc8f8f", royalblue: "#4169e1", saddlebrown: "#8b4513", salmon: "#fa8072", sandybrown: "#f4a460", seagreen: "#2e8b57", seashell: "#fff5ee", sienna: "#a0522d", silver: "#c0c0c0", skyblue: "#87ceeb", slateblue: "#6a5acd", slategray: "#708090", snow: "#fffafa", springgreen: "#00ff7f", steelblue: "#4682b4", tan: "#d2b48c", teal: "#008080", thistle: "#d8bfd8", tomato: "#ff6347", turquoise: "#40e0d0", violet: "#ee82ee", wheat: "#f5deb3", white: "#fff", whitesmoke: "#f5f5f5", yellow: "#ff0", yellowgreen: "#9acd32"},
158         res;
159         if ((colour + "").toLowerCase() in htmlcolors) {
160             colour = htmlcolors[colour.toString().toLowerCase()];
161         }
162         if (!colour) {
163             return {r: 0, g: 0, b: 0, hex: "#000"};
164         }
165         if (colour == "none") {
166             return {r: -1, g: -1, b: -1, hex: "none"};
167         }
168         var red,
169             green,
170             blue,
171             rgb = (colour + "").match(/^\s*((#[a-f\d]{6})|(#[a-f\d]{3})|rgb\(\s*([\d\.]+\s*,\s*[\d\.]+\s*,\s*[\d\.]+)\s*\)|rgb\(\s*([\d\.]+%\s*,\s*[\d\.]+%\s*,\s*[\d\.]+%)\s*\)|hsb\(\s*([\d\.]+\s*,\s*[\d\.]+\s*,\s*[\d\.]+)\s*\)|hsb\(\s*([\d\.]+%\s*,\s*[\d\.]+%\s*,\s*[\d\.]+%)\s*\))\s*$/i);
172         if (rgb) {
173             if (rgb[2]) {
174                 blue = parseInt(rgb[2].substring(5), 16);
175                 green = parseInt(rgb[2].substring(3, 5), 16);
176                 red = parseInt(rgb[2].substring(1, 3), 16);
177             }
178             if (rgb[3]) {
179                 blue = parseInt(rgb[3].substring(3) + rgb[3].substring(3), 16);
180                 green = parseInt(rgb[3].substring(2, 3) + rgb[3].substring(2, 3), 16);
181                 red = parseInt(rgb[3].substring(1, 2) + rgb[3].substring(1, 2), 16);
182             }
183             if (rgb[4]) {
184                 rgb = rgb[4].split(/\s*,\s*/);
185                 red = parseFloat(rgb[0]);
186                 green = parseFloat(rgb[1]);
187                 blue = parseFloat(rgb[2]);
188             }
189             if (rgb[5]) {
190                 rgb = rgb[5].split(/\s*,\s*/);
191                 red = parseFloat(rgb[0]) * 2.55;
192                 green = parseFloat(rgb[1]) * 2.55;
193                 blue = parseFloat(rgb[2]) * 2.55;
194             }
195             if (rgb[6]) {
196                 rgb = rgb[6].split(/\s*,\s*/);
197                 red = parseFloat(rgb[0]);
198                 green = parseFloat(rgb[1]);
199                 blue = parseFloat(rgb[2]);
200                 return R.hsb2rgb(red, green, blue);
201             }
202             if (rgb[7]) {
203                 rgb = rgb[7].split(/\s*,\s*/);
204                 red = parseFloat(rgb[0]) * 2.55;
205                 green = parseFloat(rgb[1]) * 2.55;
206                 blue = parseFloat(rgb[2]) * 2.55;
207                 return R.hsb2rgb(red, green, blue);
208             }
209             var rgb = {r: red, g: green, b: blue},
210                 r = Math.round(red).toString(16),
211                 g = Math.round(green).toString(16),
212                 b = Math.round(blue).toString(16);
213             (r.length == 1) && (r = "0" + r);
214             (g.length == 1) && (g = "0" + g);
215             (b.length == 1) && (b = "0" + b);
216             rgb.hex = "#" + r + g + b;
217             res = rgb;
218         } else {
219             res = {r: -1, g: -1, b: -1, hex: "none"};
220         }
221         return res;
222     }, R);
223     R.getColor = function (value) {
224         var start = this.getColor.start = this.getColor.start || {h: 0, s: 1, b: value || .75},
225             rgb = this.hsb2rgb(start.h, start.s, start.b);
226         start.h += .075;
227         if (start.h > 1) {
228             start.h = 0;
229             start.s -= .2;
230             if (start.s <= 0) {
231                 this.getColor.start = {h: 0, s: 1, b: start.b};
232             }
233         }
234         return rgb.hex;
235     };
236     R.getColor.reset = function () {
237         delete this.start;
238     };
239     // path utilities
240     R.parsePathString = cacher(function (pathString) {
241         if (!pathString) {
242             return null;
243         }
244         var paramCounts = {a: 7, c: 6, h: 1, l: 2, m: 2, q: 4, s: 4, t: 2, v: 1, z: 0},
245             data = [];
246         if (R.isArray(pathString) && R.isArray(pathString[0])) { // rough assumption
247             data = pathString;
248         }
249         if (!data.length) {
250             (pathString + "").replace(/([achlmqstvz])[\s,]*((-?\d*\.?\d*(?:e[-+]?\d+)?\s*,?\s*)+)/ig, function (a, b, c) {
251                 var params = [],
252                     name = b.toLowerCase();
253                 c.replace(/(-?\d*\.?\d*(?:e[-+]?\d+)?)\s*,?\s*/ig, function (a, b) {
254                     b && params.push(+b);
255                 });
256                 while (params.length >= paramCounts[name]) {
257                     data.push([b].concat(params.splice(0, paramCounts[name])));
258                     if (!paramCounts[name]) {
259                         break;
260                     };
261                 }
262             });
263         }
264         data.toString = R._path2string;
265         return data;
266     }, R);
267     var pathDimensions = cacher(function (path) {
268         path = path2curve(path);
269         var x = 0, 
270             y = 0,
271             X = [],
272             Y = [];
273         for (var i = 0, ii = path.length; i < ii; i++) {
274             if (path[i][0] == "M") {
275                 x = path[i][1];
276                 y = path[i][2];
277             } else {
278                 var dim = curveDim(x, y, path[i][1], path[i][2], path[i][3], path[i][4], path[i][5], path[i][6]);
279                 X = X.concat(dim.min.x, dim.max.x);
280                 Y = Y.concat(dim.min.y, dim.max.y);
281             }
282         }
283         var xmin = Math.min.apply(0, X),
284             ymin = Math.min.apply(0, Y);
285         return {
286             x: xmin,
287             y: ymin,
288             width: Math.max.apply(0, X) - xmin,
289             height: Math.max.apply(0, Y) - ymin
290         };
291     }),
292         pathToRelative = cacher(function (pathArray) {
293             if (!R.isArray(pathArray) || !R.isArray(pathArray && pathArray[0])) { // rough assumption
294                 pathArray = R.parsePathString(pathArray);
295             }
296             var res = [],
297                 x = 0,
298                 y = 0,
299                 mx = 0,
300                 my = 0,
301                 start = 0;
302             if (pathArray[0][0] == "M") {
303                 x = pathArray[0][1];
304                 y = pathArray[0][2];
305                 mx = x;
306                 my = y;
307                 start++;
308                 res.push(["M", x, y]);
309             }
310             for (var i = start, ii = pathArray.length; i < ii; i++) {
311                 var r = res[i] = [],
312                     pa = pathArray[i];
313                 if (pa[0] != pa[0].toLowerCase()) {
314                     r[0] = pa[0].toLowerCase();
315                     switch (r[0]) {
316                         case "a":
317                             r[1] = pa[1];
318                             r[2] = pa[2];
319                             r[3] = pa[3];
320                             r[4] = pa[4];
321                             r[5] = pa[5];
322                             r[6] = +(pa[6] - x).toFixed(3);
323                             r[7] = +(pa[7] - y).toFixed(3);
324                             break;
325                         case "v":
326                             r[1] = +(pa[1] - y).toFixed(3);
327                             break;
328                         case "m":
329                             mx = pa[1];
330                             my = pa[2];
331                         default:
332                             for (var j = 1, jj = pa.length; j < jj; j++) {
333                                 r[j] = +(pa[j] - ((j % 2) ? x : y)).toFixed(3);
334                             }
335                     }
336                 } else {
337                     r = res[i] = [];
338                     if (pa[0] == "m") {
339                         mx = pa[1] + x;
340                         my = pa[2] + y;
341                     }
342                     for (var k = 0, kk = pa.length; k < kk; k++) {
343                         res[i][k] = pa[k];
344                     }
345                 }
346                 var len = res[i].length;
347                 switch (res[i][0]) {
348                     case "z":
349                         x = mx;
350                         y = my;
351                         break;
352                     case "h":
353                         x += +res[i][len - 1];
354                         break;
355                     case "v":
356                         y += +res[i][len - 1];
357                         break;
358                     default:
359                         x += +res[i][len - 2];
360                         y += +res[i][len - 1];
361                 }
362             }
363             res.toString = R._path2string;
364             return res;
365         }, null, pathClone),
366         pathClone = function (pathArray) {
367             var res = [];
368             if (!R.isArray(pathArray) || !R.isArray(pathArray && pathArray[0])) { // rough assumption
369                 pathArray = R.parsePathString(pathArray);
370             }
371             for (var i = 0, ii = pathArray.length; i < ii; i++) {
372                 res[i] = [];
373                 for (var j = 0, jj = pathArray[i].length; j < jj; j++) {
374                     res[i][j] = pathArray[i][j];
375                 }
376             }
377             res.toString = R._path2string;
378             return res;
379         },
380         pathToAbsolute = cacher(function (pathArray) {
381             if (!R.isArray(pathArray) || !R.isArray(pathArray && pathArray[0])) { // rough assumption
382                 pathArray = R.parsePathString(pathArray);
383             }
384             var res = [],
385                 x = 0,
386                 y = 0,
387                 mx = 0,
388                 my = 0,
389                 start = 0;
390             if (pathArray[0][0] == "M") {
391                 x = +pathArray[0][1];
392                 y = +pathArray[0][2];
393                 mx = x;
394                 my = y;
395                 start++;
396                 res[0] = ["M", x, y];
397             }
398             for (var i = start, ii = pathArray.length; i < ii; i++) {
399                 var r = res[i] = [],
400                     pa = pathArray[i];
401                 if (pa[0] != (pa[0] + "").toUpperCase()) {
402                     r[0] = (pa[0] + "").toUpperCase();
403                     switch (r[0]) {
404                         case "A":
405                             r[1] = pa[1];
406                             r[2] = pa[2];
407                             r[3] = pa[3];
408                             r[4] = pa[4];
409                             r[5] = pa[5];
410                             r[6] = +(pa[6] + x);
411                             r[7] = +(pa[7] + y);
412                             break;
413                         case "V":
414                             r[1] = +pa[1] + y;
415                             break;
416                         case "H":
417                             r[1] = +pa[1] + x;
418                             break;
419                         case "M":
420                             mx = +pa[1] + x;
421                             my = +pa[2] + y;
422                         default:
423                             for (var j = 1, jj = pa.length; j < jj; j++) {
424                                 r[j] = +pa[j] + ((j % 2) ? x : y);
425                             }
426                     }
427                 } else {
428                     for (var k = 0, kk = pa.length; k < kk; k++) {
429                         res[i][k] = pa[k];
430                     }
431                 }
432                 switch (r[0]) {
433                     case "Z":
434                         x = mx;
435                         y = my;
436                         break;
437                     case "H":
438                         x = r[1];
439                         break;
440                     case "V":
441                         y = r[1];
442                         break;
443                     default:
444                         x = res[i][res[i].length - 2];
445                         y = res[i][res[i].length - 1];
446                 }
447             }
448             res.toString = R._path2string;
449             return res;
450         }, null, pathClone),
451         l2c = function (x1, y1, x2, y2) {
452             return [x1, y1, x2, y2, x2, y2];
453         },
454         q2c = function (x1, y1, ax, ay, x2, y2) {
455             return [
456                     2 / 3 * x1 + 1 / 3 * ax,
457                     2 / 3 * y1 + 1 / 3 * ay,
458                     2 / 3 * x1 + 1 / 3 * x2,
459                     2 / 3 * y1 + 1 / 3 * y2,
460                     x2,
461                     y2
462                 ];
463         },
464         a2c = function (x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, recursive) {
465             // for more information of where this math came from visit:
466             // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
467             var _120 = Math.PI * 120 / 180,
468                 res = [];
469             if (!recursive) {
470                 var x = (x1 - x2) / 2,
471                     y = (y1 - y2) / 2,
472                     rx2 = rx * rx,
473                     ry2 = ry * ry,
474                     k = (large_arc_flag == sweep_flag ? -1 : 1) *
475                         Math.sqrt(Math.abs(rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x)),
476                     cx = k * rx * y / ry + (x1 + x2) / 2,
477                     cy = k * -ry * x / rx + (y1 + y2) / 2,
478                     f1 = Math.asin((y1 - cy) / ry),
479                     f2 = Math.asin((y2 - cy) / ry);
480
481                 f1 = x1 < cx ? Math.PI - f1 : f1;
482                 f2 = x2 < cx ? Math.PI - f2 : f2;
483                 f1 < 0 && (f1 = Math.PI * 2 + f1);
484                 f2 < 0 && (f2 = Math.PI * 2 + f2);
485                 if (sweep_flag && f1 > f2) {
486                     f1 = f1 - Math.PI * 2;
487                 }
488                 if (!sweep_flag && f2 > f1) {
489                     f2 = f2 - Math.PI * 2;
490                 }
491             } else {
492                 f1 = recursive[0];
493                 f2 = recursive[1];
494                 cx = recursive[2];
495                 cy = recursive[3];
496             }
497             var df = f2 - f1;
498             if (Math.abs(df) > _120) {
499                 var f2old = f2,
500                     x2old = x2,
501                     y2old = y2;
502                 f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1);
503                 x2 = cx + rx * Math.cos(f2);
504                 y2 = cy + ry * Math.sin(f2);
505                 res = arguments.callee(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [f2, f2old, cx, cy]);
506             }
507             var c1 = Math.cos(f1),
508                 s1 = Math.sin(f1),
509                 c2 = Math.cos(f2),
510                 s2 = Math.sin(f2),
511                 df = f2 - f1,
512                 t = Math.tan(df / 4),
513                 hx = 4 / 3 * rx * t,
514                 hy = 4 / 3 * ry * t,
515                 m1 = [x1, y1],
516                 m2 = [x1 + hx * s1, y1 - hy * c1],
517                 m3 = [x2 + hx * s2, y2 - hy * c2],
518                 m4 = [x2, y2];
519             m2[0] = 2 * m1[0] - m2[0];
520             m2[1] = 2 * m1[1] - m2[1];
521             if (recursive) {
522                 return [m2, m3, m4].concat(res);
523             } else {
524                 res = [m2, m3, m4].concat(res).join(",").split(",");
525                 for (var i = res.length; i--;) {
526                     res[i] = +res[i];
527                 }
528                 return res;
529             }
530         },
531         findDotAtSegment = cacher(function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
532             var x = Math.pow(1 - t, 3) * p1x + Math.pow(1 - t, 2) * 3 * t * c1x + (1 - t) * 3 * t * t * c2x + Math.pow(t, 3) * p2x,
533                 y = Math.pow(1 - t, 3) * p1y + Math.pow(1 - t, 2) * 3 * t * c1y + (1 - t) * 3 * t * t * c2y + Math.pow(t, 3) * p2y,
534                 mx = p1x + 2 * t * (c1x - p1x) + t * t * (c2x - 2 * c1x + p1x),
535                 my = p1y + 2 * t * (c1y - p1y) + t * t * (c2y - 2 * c1y + p1y),
536                 nx = c1x + 2 * t * (c2x - c1x) + t * t * (p2x - 2 * c2x + c1x),
537                 ny = c1y + 2 * t * (c2y - c1y) + t * t * (p2y - 2 * c2y + c1y),
538                 ax = (1 - t) * p1x + t * c1x,
539                 ay = (1 - t) * p1y + t * c1y,
540                 cx = (1 - t) * c2x + t * p2x,
541                 cy = (1 - t) * c2y + t * p2y;
542             return {x: x, y: y, m: {x: mx, y: my}, n: {x: nx, y: ny}, start: {x: ax, y: ay}, end: {x: cx, y: cy}};
543         }),
544         curveDim = cacher(function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
545             var a = (c2x - 2 * c1x + p1x) - (p2x - 2 * c2x + c1x),
546                 b = 2 * (c1x - p1x) - 2 * (c2x - c1x),
547                 c = p1x - c1x,
548                 t1 = (-b + Math.sqrt(b * b - 4 * a * c)) / 2 / a,
549                 t2 = (-b - Math.sqrt(b * b - 4 * a * c)) / 2 / a,
550                 y = [p1y, p2y],
551                 x = [p1x, p2x],
552                 dot1 = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1 > 0 && t1 < 1 ? t1 : 0),
553                 dot2 = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2 > 0 && t2 < 1 ? t2 : 0);
554             x = x.concat(dot1.x, dot2.x);
555             y = y.concat(dot1.y, dot2.y);
556             a = (c2y - 2 * c1y + p1y) - (p2y - 2 * c2y + c1y);
557             b = 2 * (c1y - p1y) - 2 * (c2y - c1y);
558             c = p1y - c1y;
559             t1 = (-b + Math.sqrt(b * b - 4 * a * c)) / 2 / a;
560             t2 = (-b - Math.sqrt(b * b - 4 * a * c)) / 2 / a;
561             dot1 = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1 > 0 && t1 < 1 ? t1 : 0);
562             dot2 = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2 > 0 && t2 < 1 ? t2 : 0);
563             x = x.concat(dot1.x, dot2.x);
564             y = y.concat(dot1.y, dot2.y);
565             return {
566                 min: {x: Math.min.apply(Math, x), y: Math.min.apply(Math, y)},
567                 max: {x: Math.max.apply(Math, x), y: Math.max.apply(Math, y)}
568             };
569         }),
570         path2curve = cacher(function (path, path2) {
571             var p = pathToAbsolute(path),
572                 p2 = path2 && pathToAbsolute(path2),
573                 attrs = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0},
574                 attrs2 = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0},
575                 processPath = function (path, d) {
576                     if (!path) {
577                         return ["C", d.x, d.y, d.x, d.y, d.x, d.y];
578                     }
579                     switch (path[0]) {
580                         case "M":
581                             d.X = path[1];
582                             d.Y = path[2];
583                             break;
584                         case "A":
585                             path = ["C"].concat(a2c(d.x, d.y, path[1], path[2], path[3], path[4], path[5], path[6], path[7]));
586                             break;
587                         case "S":
588                             var nx = d.x + (d.x - (d.bx || d.x)),
589                                 ny = d.y + (d.y - (d.by || d.y));
590                             path = ["C", nx, ny, path[1], path[2], path[3], path[4]];
591                             break;
592                         case "T":
593                             var nx = d.x + (d.x - (d.bx || d.x)),
594                                 ny = d.y + (d.y - (d.by || d.y));
595                             path = ["C"].concat(q2c(d.x, d.y, nx, ny, path[1], path[2]));
596                             break;
597                         case "Q":
598                             path = ["C"].concat(q2c(d.x, d.y, path[1], path[2], path[3], path[4]));
599                             break;
600                         case "L":
601                             path = ["C"].concat(l2c(d.x, d.y, path[1], path[2]));
602                             break;
603                         case "H":
604                             path = ["C"].concat(l2c(d.x, d.y, path[1], d.y));
605                             break;
606                         case "V":
607                             path = ["C"].concat(l2c(d.x, d.y, d.x, path[1]));
608                             break;
609                         case "Z":
610                             path = ["C"].concat(l2c(d.x, d.y, d.X, d.Y));
611                             break;
612                     }
613                     return path;
614                 },
615                 fixArc = function (pp, i) {
616                     if (pp[i].length > 7) {
617                         pp[i].shift();
618                         var pi = pp[i];
619                         while (pi.length) {
620                             pp.splice(i++, 0, ["C"].concat(pi.splice(0, 6)));
621                         }
622                         pp.splice(i, 1);
623                         ii = Math.max(p.length, p2 && p2.length || 0);
624                     }
625                 },
626                 fixM = function (path1, path2, a1, a2, i) {
627                     if (path1 && path2 && path1[i][0] == "M" && path2[i][0] != "M") {
628                         path2.splice(i, 0, ["M", a2.x, a2.y]);
629                         a1.bx = 0;
630                         a1.by = 0;
631                         a1.x = path1[i][1];
632                         a1.y = path1[i][2];
633                         ii = Math.max(p.length, p2 && p2.length || 0);
634                     }
635                 };
636             for (var i = 0, ii = Math.max(p.length, p2 && p2.length || 0); i < ii; i++) {
637                 p[i] = processPath(p[i], attrs);
638                 fixArc(p, i);
639                 p2 && (p2[i] = processPath(p2[i], attrs2));
640                 p2 && fixArc(p2, i);
641                 fixM(p, p2, attrs, attrs2, i);
642                 fixM(p2, p, attrs2, attrs, i);
643                 var seg = p[i],
644                     seg2 = p2 && p2[i],
645                     seglen = seg.length,
646                     seg2len = p2 && seg2.length;
647                 attrs.bx = seg[seglen - 4] || 0;
648                 attrs.by = seg[seglen - 3] || 0;
649                 attrs.x = seg[seglen - 2];
650                 attrs.y = seg[seglen - 1];
651                 attrs2.bx = p2 && (seg2[seg2len - 4] || 0);
652                 attrs2.by = p2 && (seg2[seg2len - 3] || 0);
653                 attrs2.x = p2 && seg2[seg2len - 2];
654                 attrs2.y = p2 && seg2[seg2len - 1];
655             }
656             return p2 ? [p, p2] : p;
657         }),
658         toGradient = cacher(function (gradient) {
659             if (typeof gradient == "string") {
660                 gradient = gradient.split(/\s*\-\s*/);
661                 var angle = gradient.shift();
662                 if (angle.toLowerCase() == "v") {
663                     angle = 90;
664                 } else if (angle.toLowerCase() == "h") {
665                     angle = 0;
666                 } else {
667                     angle = parseFloat(angle);
668                 }
669                 angle = -angle;
670                 var grobj = {angle: angle, type: "linear", dots: [], vector: [0, 0, Math.cos(angle * Math.PI / 180).toFixed(3), Math.sin(angle * Math.PI / 180).toFixed(3)]},
671                     max = 1 / (Math.max(Math.abs(grobj.vector[2]), Math.abs(grobj.vector[3])) || 1);
672                 grobj.vector[2] *= max;
673                 grobj.vector[3] *= max;
674                 if (grobj.vector[2] < 0) {
675                     grobj.vector[0] = -grobj.vector[2];
676                     grobj.vector[2] = 0;
677                 }
678                 if (grobj.vector[3] < 0) {
679                     grobj.vector[1] = -grobj.vector[3];
680                     grobj.vector[3] = 0;
681                 }
682                 grobj.vector[0] = grobj.vector[0].toFixed(3);
683                 grobj.vector[1] = grobj.vector[1].toFixed(3);
684                 grobj.vector[2] = grobj.vector[2].toFixed(3);
685                 grobj.vector[3] = grobj.vector[3].toFixed(3);
686                 for (var i = 0, ii = gradient.length; i < ii; i++) {
687                     var dot = {},
688                         par = gradient[i].match(/^([^:]*):?([\d\.]*)/);
689                     dot.color = R.getRGB(par[1]).hex;
690                     par[2] && (dot.offset = par[2] + "%");
691                     grobj.dots.push(dot);
692                 }
693                 for (var i = 1, ii = grobj.dots.length - 1; i < ii; i++) {
694                     if (!grobj.dots[i].offset) {
695                         var start = parseFloat(grobj.dots[i - 1].offset || 0),
696                             end = false;
697                         for (var j = i + 1; j < ii; j++) {
698                             if (grobj.dots[j].offset) {
699                                 end = grobj.dots[j].offset;
700                                 break;
701                             }
702                         }
703                         if (!end) {
704                             end = 100;
705                             j = ii;
706                         }
707                         end = parseFloat(end);
708                         var d = (end - start) / (j - i + 1);
709                         for (; i < j; i++) {
710                             start += d;
711                             grobj.dots[i].offset = start + "%";
712                         }
713                     }
714                 }
715                 return grobj;
716             } else {
717                 return gradient;
718             }
719         }),
720         getContainer = function () {
721             var container,
722                 x,
723                 y,
724                 width,
725                 height;
726             if (typeof arguments[0] == "string" || typeof arguments[0] == "object") {
727                 if (typeof arguments[0] == "string") {
728                     container = doc.getElementById(arguments[0]);
729                 } else {
730                     container = arguments[0];
731                 }
732                 if (container.tagName) {
733                     if (arguments[1] == null) {
734                         return {
735                             container: container,
736                             width: container.style.pixelWidth || container.offsetWidth,
737                             height: container.style.pixelHeight || container.offsetHeight
738                         };
739                     } else {
740                         return {container: container, width: arguments[1], height: arguments[2]};
741                     }
742                 }
743             } else if (typeof arguments[0] == "number" && arguments.length > 3) {
744                 return {container: 1, x: arguments[0], y: arguments[1], width: arguments[2], height: arguments[3]};
745             }
746         },
747         plugins = function (con, add) {
748             var that = this;
749             for (var prop in add) if (add.hasOwnProperty(prop) && !(prop in con)) {
750                 switch (typeof add[prop]) {
751                     case "function":
752                         (function (f) {
753                             con[prop] = con === that ? f : function () { return f.apply(that, arguments); };
754                         })(add[prop]);
755                     break;
756                     case "object":
757                         con[prop] = con[prop] || {};
758                         plugins.call(this, con[prop], add[prop]);
759                     break;
760                     default:
761                         con[prop] = add[prop];
762                     break;
763                 }
764             }
765         };
766
767     // SVG
768     if (R.svg) {
769         var round = function (num) {
770             return +num + (Math.floor(num) == num) * .5;
771         };
772         var roundPath = function (path) {
773             for (var i = 0, ii = path.length; i < ii; i++) {
774                 if (path[i][0].toLowerCase() != "a") {
775                     for (var j = 1, jj = path[i].length; j < jj; j++) {
776                         path[i][j] = round(path[i][j]);
777                     }
778                 } else {
779                     path[i][6] = round(path[i][6]);
780                     path[i][7] = round(path[i][7]);
781                 }
782             }
783             return path;
784         };
785         R.toString = function () {
786             return  "Your browser supports SVG.\nYou are running Rapha\u00ebl " + this.version;
787         };
788         var thePath = function (pathString, SVG) {
789             var el = doc.createElementNS(SVG.svgns, "path");
790             if (SVG.canvas) {
791                 SVG.canvas.appendChild(el);
792             }
793             var p = new Element(el, SVG);
794             p.type = "path";
795             pathString && (p.attrs.path = roundPath(pathToAbsolute(pathString))) && p.node.setAttribute("d", p.attrs.path);
796             setFillAndStroke(p, {fill: "none", stroke: "#000"});
797             return p;
798         };
799         var addGradientFill = function (o, gradient, SVG) {
800             gradient = toGradient(gradient);
801             var el = doc.createElementNS(SVG.svgns, (gradient.type || "linear") + "Gradient");
802             el.id = "r" + (R.idGenerator++).toString(36);
803             if (gradient.vector && gradient.vector.length) {
804                 el.setAttribute("x1", gradient.vector[0]);
805                 el.setAttribute("y1", gradient.vector[1]);
806                 el.setAttribute("x2", gradient.vector[2]);
807                 el.setAttribute("y2", gradient.vector[3]);
808             }
809             SVG.defs.appendChild(el);
810             var isopacity = true;
811             for (var i = 0, ii = gradient.dots.length; i < ii; i++) {
812                 var stop = doc.createElementNS(SVG.svgns, "stop");
813                 if (gradient.dots[i].offset) {
814                     isopacity = false;
815                 }
816                 stop.setAttribute("offset", gradient.dots[i].offset ? gradient.dots[i].offset : (i == 0) ? "0%" : "100%");
817                 stop.setAttribute("stop-color", R.getRGB(gradient.dots[i].color).hex || "#fff");
818                 // ignoring opacity for internal points, because VML doesn't support it
819                 el.appendChild(stop);
820             };
821             if (isopacity && typeof gradient.dots[ii - 1].opacity != "undefined") {
822                 stop.setAttribute("stop-opacity", gradient.dots[ii - 1].opacity);
823             }
824             o.setAttribute("fill", "url(#" + el.id + ")");
825             o.style.fill = "";
826             o.style.opacity = 1;
827             o.style.fillOpacity = 1;
828             o.setAttribute("opacity", 1);
829             o.setAttribute("fill-opacity", 1);
830         };
831         var updatePosition = function (o) {
832             var bbox = o.getBBox();
833             o.pattern.setAttribute("patternTransform", "translate(".concat(bbox.x, ",", bbox.y, ")"));
834         };
835         var setFillAndStroke = function (o, params) {
836             var dasharray = {
837                     "": [0],
838                     "none": [0],
839                     "-": [3, 1],
840                     ".": [1, 1],
841                     "-.": [3, 1, 1, 1],
842                     "-..": [3, 1, 1, 1, 1, 1],
843                     ". ": [1, 3],
844                     "- ": [4, 3],
845                     "--": [8, 3],
846                     "- .": [4, 3, 1, 3],
847                     "--.": [8, 3, 1, 3],
848                     "--..": [8, 3, 1, 3, 1, 3]
849                 },
850                 node = o.node,
851                 attrs = o.attrs,
852                 rot = o.attr("rotation"),
853                 addDashes = function (o, value) {
854                     value = dasharray[(value + "").toLowerCase()];
855                     if (value) {
856                         var width = o.attrs["stroke-width"] || "1",
857                             butt = {round: width, square: width, butt: 0}[o.attrs["stroke-linecap"] || params["stroke-linecap"]] || 0,
858                             dashes = [];
859                         for (var i = 0, ii = value.length; i < ii; i++) {
860                             dashes.push(value[i] * width + ((i % 2) ? 1 : -1) * butt);
861                         }
862                         value = dashes.join(",");
863                         node.setAttribute("stroke-dasharray", value);
864                     }
865                 };
866             parseInt(rot, 10) && o.rotate(0, true);
867             for (var att in params) {
868                 if (!(att in availableAttrs)) {
869                     continue;
870                 }
871                 var value = params[att];
872                 attrs[att] = value;
873                 switch (att) {
874                     // Hyperlink
875                     case "href":
876                     case "title":
877                     case "target":
878                         var pn = node.parentNode;
879                         if (pn.tagName.toLowerCase() != "a") {
880                             var hl = doc.createElementNS(o.paper.svgns, "a");
881                             pn.insertBefore(hl, node);
882                             hl.appendChild(node);
883                             pn = hl;
884                         }
885                         pn.setAttributeNS(o.paper.xlink, att, value);
886                       break;
887                     case "path":
888                         if (o.type == "path") {
889                             attrs.path = roundPath(pathToAbsolute(value));
890                             node.setAttribute("d", attrs.path);
891                         }
892                     case "width":
893                         node.setAttribute(att, value);
894                         if (attrs.fx) {
895                             att = "x";
896                             value = attrs.x;
897                         } else {
898                             break;
899                         }
900                     case "x":
901                         if (attrs.fx) {
902                             value = -attrs.x - (attrs.width || 0);
903                         }
904                     case "rx":
905                     case "cx":
906                         node.setAttribute(att, value);
907                         o.pattern && updatePosition(o);
908                         break;
909                     case "height":
910                         node.setAttribute(att, value);
911                         if (attrs.fy) {
912                             att = "y";
913                             value = attrs.y;
914                         } else {
915                             break;
916                         }
917                     case "y":
918                         if (attrs.fy) {
919                             value = -attrs.y - (attrs.height || 0);
920                         }
921                     case "ry":
922                     case "cy":
923                         node.setAttribute(att, value);
924                         o.pattern && updatePosition(o);
925                         break;
926                     case "r":
927                         if (o.type == "rect") {
928                             node.setAttribute("rx", value);
929                             node.setAttribute("ry", value);
930                         } else {
931                             node.setAttribute(att, value);
932                         }
933                         break;
934                     case "src":
935                         if (o.type == "image") {
936                             node.setAttributeNS(o.paper.xlink, "href", value);
937                         }
938                         break;
939                     case "stroke-width":
940                         node.style.strokeWidth = value;
941                         // Need following line for Firefox
942                         node.setAttribute(att, value);
943                         if (attrs["stroke-dasharray"]) {
944                             addDashes(o, attrs["stroke-dasharray"]);
945                         }
946                         break;
947                     case "stroke-dasharray":
948                         addDashes(o, value);
949                         break;
950                     case "rotation":
951                         rot = value;
952                         o.rotate(value, true);
953                         break;
954                     case "translation":
955                         var xy = (value + "").split(separator);
956                         o.translate((+xy[0] + 1 || 2) - 1, (+xy[1] + 1 || 2) - 1);
957                         break;
958                     case "scale":
959                         var xy = (value + "").split(separator);
960                         o.scale(+xy[0] || 1, +xy[1] || +xy[0] || 1, +xy[2] || null, +xy[3] || null);
961                         break;
962                     case "fill":
963                         var isURL = (value + "").match(/^url\(['"]?([^\)]+)['"]?\)$/i);
964                         if (isURL) {
965                             var el = doc.createElementNS(o.paper.svgns, "pattern"),
966                                 ig = doc.createElementNS(o.paper.svgns, "image");
967                             el.id = "r" + (R.idGenerator++).toString(36);
968                             el.setAttribute("x", 0);
969                             el.setAttribute("y", 0);
970                             el.setAttribute("patternUnits", "userSpaceOnUse");
971                             ig.setAttribute("x", 0);
972                             ig.setAttribute("y", 0);
973                             ig.setAttributeNS(o.paper.xlink, "href", isURL[1]);
974                             el.appendChild(ig);
975
976                             var img = doc.createElement("img");
977                             img.style.position = "absolute";
978                             img.style.top = "-9999em";
979                             img.style.left = "-9999em";
980                             img.onload = function () {
981                                 el.setAttribute("width", this.offsetWidth);
982                                 el.setAttribute("height", this.offsetHeight);
983                                 ig.setAttribute("width", this.offsetWidth);
984                                 ig.setAttribute("height", this.offsetHeight);
985                                 doc.body.removeChild(this);
986                                 paper.safari();
987                             };
988                             doc.body.appendChild(img);
989                             img.src = isURL[1];
990                             o.paper.defs.appendChild(el);
991                             node.style.fill = "url(#" + el.id + ")";
992                             node.setAttribute("fill", "url(#" + el.id + ")");
993                             o.pattern = el;
994                             o.pattern && updatePosition(o);
995                             break;
996                         }
997                         delete params.gradient;
998                         delete attrs.gradient;
999                         if (typeof attrs.opacity != "undefined" && typeof params.opacity == "undefined" ) {
1000                             node.style.opacity = attrs.opacity;
1001                             // Need following line for Firefox
1002                             node.setAttribute("opacity", attrs.opacity);
1003                         }
1004                         if (typeof attrs["fill-opacity"] != "undefined" && typeof params["fill-opacity"] == "undefined" ) {
1005                             node.style.fillOpacity = attrs["fill-opacity"];
1006                             // Need following line for Firefox
1007                             node.setAttribute("fill-opacity", attrs["fill-opacity"]);
1008                         }
1009                     case "stroke":
1010                         node.style[att] = R.getRGB(value).hex;
1011                         // Need following line for Firefox
1012                         node.setAttribute(att, R.getRGB(value).hex);
1013                         break;
1014                     case "gradient":
1015                         addGradientFill(node, value, o.paper);
1016                         break;
1017                     case "opacity":
1018                     case "fill-opacity":
1019                         if (attrs.gradient) {
1020                             var gradient = doc.getElementById(node.getAttribute("fill").replace(/^url\(#|\)$/g, ""));
1021                             if (gradient) {
1022                                 var stops = gradient.getElementsByTagName("stop");
1023                                 stops[stops.length - 1].setAttribute("stop-opacity", value);
1024                             }
1025                             break;
1026                         }
1027                     default:
1028                         att == "font-size" && (value = parseInt(value, 10) + "px");
1029                         var cssrule = att.replace(/(\-.)/g, function (w) {
1030                             return w.substring(1).toUpperCase();
1031                         });
1032                         node.style[cssrule] = value;
1033                         // Need following line for Firefox
1034                         node.setAttribute(att, value);
1035                         break;
1036                 }
1037             }
1038             
1039             tuneText(o, params);
1040             parseInt(rot, 10) && o.rotate(rot, true);
1041         };
1042         var leading = 1.2;
1043         var tuneText = function (el, params) {
1044             if (el.type != "text" || !("text" in params || "font" in params || "font-size" in params || "x" in params || "y" in params)) {
1045                 return;
1046             }
1047             var a = el.attrs,
1048                 node = el.node,
1049                 fontSize = node.firstChild ? parseInt(doc.defaultView.getComputedStyle(node.firstChild, "").getPropertyValue("font-size"), 10) : 10;
1050
1051             if ("text" in params) {
1052                 while (node.firstChild) {
1053                     node.removeChild(node.firstChild);
1054                 }
1055                 var texts = (params.text + "").split("\n");
1056                 for (var i = 0, ii = texts.length; i < ii; i++) {
1057                     var tspan = doc.createElementNS(el.paper.svgns, "tspan");
1058                     i && tspan.setAttribute("dy", fontSize * leading);
1059                     i && tspan.setAttribute("x", a.x);
1060                     tspan.appendChild(doc.createTextNode(texts[i]));
1061                     node.appendChild(tspan);
1062                 }
1063             } else {
1064                 var texts = node.getElementsByTagName("tspan");
1065                 for (var i = 0, ii = texts.length; i < ii; i++) {
1066                     i && texts[i].setAttribute("dy", fontSize * leading);
1067                     i && texts[i].setAttribute("x", a.x);
1068                 }
1069             }
1070             node.setAttribute("y", a.y);
1071             var bb = el.getBBox(),
1072                 dif = a.y - (bb.y + bb.height / 2);
1073             dif && node.setAttribute("y", a.y + dif);
1074         };
1075         var Element = function (node, svg) {
1076             var X = 0,
1077                 Y = 0;
1078             this[0] = node;
1079             this.node = node;
1080             this.paper = svg;
1081             this.attrs = this.attrs || {};
1082             this.transformations = []; // rotate, translate, scale
1083             this._ = {
1084                 tx: 0,
1085                 ty: 0,
1086                 rt: {deg: 0, cx: 0, cy: 0},
1087                 sx: 1,
1088                 sy: 1
1089             };
1090         };
1091         Element.prototype.rotate = function (deg, cx, cy) {
1092             if (deg == null) {
1093                 if (this._.rt.cx) {
1094                     return [this._.rt.deg, this._.rt.cx, this._.rt.cy].join(" ");
1095                 }
1096                 return this._.rt.deg;
1097             }
1098             var bbox = this.getBBox();
1099             deg = (deg + "").split(separator);
1100             if (deg.length - 1) {
1101                 cx = parseFloat(deg[1]);
1102                 cy = parseFloat(deg[2]);
1103             }
1104             deg = parseFloat(deg[0]);
1105             if (cx != null) {
1106                 this._.rt.deg = deg;
1107             } else {
1108                 this._.rt.deg += deg;
1109             }
1110             (cy == null) && (cx = null);
1111             this._.rt.cx = cx;
1112             this._.rt.cy = cy;
1113             cx = cx == null ? bbox.x + bbox.width / 2 : cx;
1114             cy = cy == null ? bbox.y + bbox.height / 2 : cy;
1115             if (this._.rt.deg) {
1116                 this.transformations[0] = "rotate(".concat(this._.rt.deg, " ", cx, " ", cy, ")");
1117             } else {
1118                 this.transformations[0] = "";
1119             }
1120             this.node.setAttribute("transform", this.transformations.join(" "));
1121             return this;
1122         };
1123         Element.prototype.hide = function () {
1124             this.node.style.display = "none";
1125             return this;
1126         };
1127         Element.prototype.show = function () {
1128             this.node.style.display = "block";
1129             return this;
1130         };
1131         Element.prototype.remove = function () {
1132             this.node.parentNode.removeChild(this.node);
1133         };
1134         Element.prototype.getBBox = function () {
1135             if (this.node.style.display == "none") {
1136                 this.show();
1137                 var hide = true;
1138             }
1139             var bbox = {};
1140             try {
1141                 bbox = this.node.getBBox();
1142             } catch(e) {
1143                 // Firefox 3.0.x plays badly here
1144             } finally {
1145                 bbox = bbox || {};
1146             }
1147             if (this.type == "text") {
1148                 bbox = {x: bbox.x, y: Infinity, width: bbox.width, height: 0};
1149                 for (var i = 0, ii = this.node.getNumberOfChars(); i < ii; i++) {
1150                     var bb = this.node.getExtentOfChar(i);
1151                     (bb.y < bbox.y) && (bbox.y = bb.y);
1152                     (bb.y + bb.height - bbox.y > bbox.height) && (bbox.height = bb.y + bb.height - bbox.y);
1153                 }
1154             }
1155             hide && this.hide();
1156             return bbox;
1157         };
1158         Element.prototype.attr = function () {
1159             if (arguments.length == 1 && typeof arguments[0] == "string") {
1160                 if (arguments[0] == "translation") {
1161                     return this.translate();
1162                 }
1163                 if (arguments[0] == "rotation") {
1164                     return this.rotate();
1165                 }
1166                 if (arguments[0] == "scale") {
1167                     return this.scale();
1168                 }
1169                 return this.attrs[arguments[0]];
1170             }
1171             if (arguments.length == 1 && R.isArray(arguments[0])) {
1172                 var values = {};
1173                 for (var j in arguments[0]) {
1174                     values[arguments[0][j]] = this.attrs[arguments[0][j]];
1175                 }
1176                 return values;
1177             }
1178             if (arguments.length == 2) {
1179                 var params = {};
1180                 params[arguments[0]] = arguments[1];
1181                 setFillAndStroke(this, params);
1182             } else if (arguments.length == 1 && typeof arguments[0] == "object") {
1183                 setFillAndStroke(this, arguments[0]);
1184             }
1185             return this;
1186         };
1187         Element.prototype.toFront = function () {
1188             this.node.parentNode.appendChild(this.node);
1189             return this;
1190         };
1191         Element.prototype.toBack = function () {
1192             if (this.node.parentNode.firstChild != this.node) {
1193                 this.node.parentNode.insertBefore(this.node, this.node.parentNode.firstChild);
1194             }
1195             return this;
1196         };
1197         Element.prototype.insertAfter = function (element) {
1198             if (element.node.nextSibling) {
1199                 element.node.parentNode.insertBefore(this.node, element.node.nextSibling);
1200             } else {
1201                 element.node.parentNode.appendChild(this.node);
1202             }
1203             return this;
1204         };
1205         Element.prototype.insertBefore = function (element) {
1206             var node = element.node;
1207             node.parentNode.insertBefore(this.node, node);
1208             return this;
1209         };
1210         var theCircle = function (svg, x, y, r) {
1211             x = round(x);
1212             y = round(y);
1213             var el = doc.createElementNS(svg.svgns, "circle");
1214             el.setAttribute("cx", x);
1215             el.setAttribute("cy", y);
1216             el.setAttribute("r", r);
1217             el.setAttribute("fill", "none");
1218             el.setAttribute("stroke", "#000");
1219             if (svg.canvas) {
1220                 svg.canvas.appendChild(el);
1221             }
1222             var res = new Element(el, svg);
1223             res.attrs = res.attrs || {};
1224             res.attrs.cx = x;
1225             res.attrs.cy = y;
1226             res.attrs.r = r;
1227             res.attrs.stroke = "#000";
1228             res.type = "circle";
1229             return res;
1230         };
1231         var theRect = function (svg, x, y, w, h, r) {
1232             x = round(x);
1233             y = round(y);
1234             var el = doc.createElementNS(svg.svgns, "rect");
1235             el.setAttribute("x", x);
1236             el.setAttribute("y", y);
1237             el.setAttribute("width", w);
1238             el.setAttribute("height", h);
1239             if (r) {
1240                 el.setAttribute("rx", r);
1241                 el.setAttribute("ry", r);
1242             }
1243             el.setAttribute("fill", "none");
1244             el.setAttribute("stroke", "#000");
1245             if (svg.canvas) {
1246                 svg.canvas.appendChild(el);
1247             }
1248             var res = new Element(el, svg);
1249             res.attrs = res.attrs || {};
1250             res.attrs.x = x;
1251             res.attrs.y = y;
1252             res.attrs.width = w;
1253             res.attrs.height = h;
1254             res.attrs.stroke = "#000";
1255             if (r) {
1256                 res.attrs.rx = res.attrs.ry = r;
1257             }
1258             res.type = "rect";
1259             return res;
1260         };
1261         var theEllipse = function (svg, x, y, rx, ry) {
1262             x = round(x);
1263             y = round(y);
1264             var el = doc.createElementNS(svg.svgns, "ellipse");
1265             el.setAttribute("cx", x);
1266             el.setAttribute("cy", y);
1267             el.setAttribute("rx", rx);
1268             el.setAttribute("ry", ry);
1269             el.setAttribute("fill", "none");
1270             el.setAttribute("stroke", "#000");
1271             if (svg.canvas) {
1272                 svg.canvas.appendChild(el);
1273             }
1274             var res = new Element(el, svg);
1275             res.attrs = res.attrs || {};
1276             res.attrs.cx = x;
1277             res.attrs.cy = y;
1278             res.attrs.rx = rx;
1279             res.attrs.ry = ry;
1280             res.attrs.stroke = "#000";
1281             res.type = "ellipse";
1282             return res;
1283         };
1284         var theImage = function (svg, src, x, y, w, h) {
1285             var el = doc.createElementNS(svg.svgns, "image");
1286             el.setAttribute("x", x);
1287             el.setAttribute("y", y);
1288             el.setAttribute("width", w);
1289             el.setAttribute("height", h);
1290             el.setAttribute("preserveAspectRatio", "none");
1291             el.setAttributeNS(svg.xlink, "href", src);
1292             if (svg.canvas) {
1293                 svg.canvas.appendChild(el);
1294             }
1295             var res = new Element(el, svg);
1296             res.attrs = res.attrs || {};
1297             res.attrs.src = src;
1298             res.attrs.x = x;
1299             res.attrs.y = y;
1300             res.attrs.width = w;
1301             res.attrs.height = h;
1302             res.type = "image";
1303             return res;
1304         };
1305         var theText = function (svg, x, y, text) {
1306             var el = doc.createElementNS(svg.svgns, "text");
1307             el.setAttribute("x", x);
1308             el.setAttribute("y", y);
1309             el.setAttribute("text-anchor", "middle");
1310             if (svg.canvas) {
1311                 svg.canvas.appendChild(el);
1312             }
1313             var res = new Element(el, svg);
1314             res.attrs = res.attrs || {};
1315             res.attrs.text = text;
1316             res.attrs.x = x;
1317             res.attrs.y = y;
1318             res.type = "text";
1319             setFillAndStroke(res, {font: availableAttrs.font, stroke: "none", fill: "#000", text: text});
1320             return res;
1321         };
1322         var setSize = function (width, height) {
1323             this.width = width || this.width;
1324             this.height = height || this.height;
1325             this.canvas.setAttribute("width", this.width);
1326             this.canvas.setAttribute("height", this.height);
1327             return this;
1328         };
1329         var create = function () {
1330             var con = getContainer.apply(null, arguments),
1331                 container = con.container,
1332                 x = con.x,
1333                 y = con.y,
1334                 width = con.width,
1335                 height = con.height;
1336             if (!container) {
1337                 throw new Error("SVG container not found.");
1338             }
1339             paper.canvas = doc.createElementNS(paper.svgns, "svg");
1340             paper.canvas.setAttribute("width", width || 512);
1341             paper.width = width || 512;
1342             paper.canvas.setAttribute("height", height || 342);
1343             paper.height = height || 342;
1344             if (container == 1) {
1345                 doc.body.appendChild(paper.canvas);
1346                 paper.canvas.style.position = "absolute";
1347                 paper.canvas.style.left = x + "px";
1348                 paper.canvas.style.top = y + "px";
1349             } else {
1350                 if (container.firstChild) {
1351                     container.insertBefore(paper.canvas, container.firstChild);
1352                 } else {
1353                     container.appendChild(paper.canvas);
1354                 }
1355             }
1356             container = {
1357                 canvas: paper.canvas,
1358                 clear: function () {
1359                     while (this.canvas.firstChild) {
1360                         this.canvas.removeChild(this.canvas.firstChild);
1361                     }
1362                     this.defs = doc.createElementNS(paper.svgns, "defs");
1363                     this.canvas.appendChild(this.defs);
1364                 }
1365             };
1366             for (var prop in paper) {
1367                 if (prop != "create") {
1368                     container[prop] = paper[prop];
1369                 }
1370             }
1371             plugins.call(container, container, R.fn);
1372             container.clear();
1373             container.raphael = R;
1374             return container;
1375         };
1376         paper.remove = function () {
1377             this.canvas.parentNode && this.canvas.parentNode.removeChild(this.canvas);
1378         };
1379         paper.svgns = "http://www.w3.org/2000/svg";
1380         paper.xlink = "http://www.w3.org/1999/xlink";
1381         paper.safari = function () {
1382             if ({"Apple Computer, Inc.": 1, "Google Inc.": 1}[navigator.vendor]) {
1383                 var rect = this.rect(-this.width, -this.height, this.width * 3, this.height * 3).attr({stroke: "none"});
1384                 setTimeout(function () {rect.remove();});
1385             }
1386         };
1387     }
1388
1389     // VML
1390     if (R.vml) {
1391         var path2vml = function (path) {
1392             var pa = path2curve(path);
1393             for (var i = 0, ii = pa.length; i < ii; i++) {
1394                 pa[i][0] = (pa[i][0] + "").toLowerCase();
1395                 pa[i][0] == "z" && (pa[i][0] = "x");
1396                 for (var j = 1, jj = pa[i].length; j < jj; j++) {
1397                     pa[i][j] = Math.round(pa[i][j]);
1398                 }
1399             }
1400             return (pa + "");
1401         };
1402         R.toString = function () {
1403             return  "Your browser doesn\u2019t support SVG. Assuming it is Internet Explorer and falling down to VML.\nYou are running Rapha\u00ebl " + this.version;
1404         };
1405         var thePath = function (pathString, VML) {
1406             var g = createNode("group"), gl = g.style;
1407             gl.position = "absolute";
1408             gl.left = 0;
1409             gl.top = 0;
1410             gl.width = VML.width + "px";
1411             gl.height = VML.height + "px";
1412             g.coordsize = VML.coordsize;
1413             g.coordorigin = VML.coordorigin;
1414             var el = createNode("shape"), ol = el.style;
1415             ol.width = VML.width + "px";
1416             ol.height = VML.height + "px";
1417             el.path = "";
1418             el.coordsize = this.coordsize;
1419             el.coordorigin = this.coordorigin;
1420             g.appendChild(el);
1421             var p = new Element(el, g, VML);
1422             p.isAbsolute = true;
1423             p.type = "path";
1424             p.path = [];
1425             // p.last = {x: 0, y: 0, bx: 0, by: 0, isAbsolute: true};
1426             p.Path = "";
1427             if (pathString) {
1428                 p.attrs.path = R.parsePathString(pathString);
1429                 p.node.path = path2vml(p.attrs.path);
1430             }
1431             setFillAndStroke(p, {fill: "none", stroke: "#000"});
1432             p.setBox();
1433             VML.canvas.appendChild(g);
1434             return p;
1435         };
1436         var setFillAndStroke = function (o, params) {
1437             o.attrs = o.attrs || {};
1438             var node = o.node,
1439                 a = o.attrs,
1440                 s = node.style,
1441                 xy,
1442                 res = o;
1443             for (var par in params) {
1444                 a[par] = params[par];
1445             }
1446             params.href && (node.href = params.href);
1447             params.title && (node.title = params.title);
1448             params.target && (node.target = params.target);
1449             if (params.path && o.type == "path") {
1450                 a.path = R.parsePathString(params.path);
1451                 node.path = path2vml(a.path);
1452             }
1453             if (params.rotation != null) {
1454                 o.rotate(params.rotation, true);
1455             }
1456             if (params.translation) {
1457                 xy = (params.translation + "").split(separator);
1458                 o.translate(xy[0], xy[1]);
1459             }
1460             if (params.scale) {
1461                 xy = (params.scale + "").split(separator);
1462                 o.scale(+xy[0] || 1, +xy[1] || +xy[0] || 1, +xy[2] || null, +xy[3] || null);
1463             }
1464             if (o.type == "image" && params.src) {
1465                 node.src = params.src;
1466             }
1467             if (o.type == "image" && params.opacity) {
1468                 node.filterOpacity = " progid:DXImageTransform.Microsoft.Alpha(opacity=" + (params.opacity * 100) + ")";
1469                 s.filter = (node.filterMatrix || "") + (node.filterOpacity || "");
1470             }
1471             params.font && (s.font = params.font);
1472             params["font-family"] && (s.fontFamily = '"' + params["font-family"].split(",")[0].replace(/^['"]+|['"]+$/g, "") + '"');
1473             params["font-size"] && (s.fontSize = params["font-size"]);
1474             params["font-weight"] && (s.fontWeight = params["font-weight"]);
1475             params["font-style"] && (s.fontStyle = params["font-style"]);
1476             if (params.opacity != null || 
1477                 params["stroke-width"] != null ||
1478                 params.fill != null ||
1479                 params.stroke != null ||
1480                 params["stroke-width"] != null ||
1481                 params["stroke-opacity"] != null ||
1482                 params["fill-opacity"] != null ||
1483                 params["stroke-dasharray"] != null ||
1484                 params["stroke-miterlimit"] != null ||
1485                 params["stroke-linejoin"] != null ||
1486                 params["stroke-linecap"] != null) {
1487                 node = o.shape || node;
1488                 var fill = (node.getElementsByTagName("fill") && node.getElementsByTagName("fill")[0]),
1489                     newfill = false;
1490                 !fill && (newfill = fill = createNode("fill"));
1491                 if ("fill-opacity" in params || "opacity" in params) {
1492                     var opacity = ((+a["fill-opacity"] + 1 || 2) - 1) * ((+a.opacity + 1 || 2) - 1);
1493                     opacity < 0 && (opacity = 0);
1494                     opacity > 1 && (opacity = 1);
1495                     fill.opacity = opacity;
1496                 }
1497                 params.fill && (fill.on = true);
1498                 if (fill.on == null || params.fill == "none") {
1499                     fill.on = false;
1500                 }
1501                 if (fill.on && params.fill) {
1502                     var isURL = params.fill.match(/^url\(([^\)]+)\)$/i);
1503                     if (isURL) {
1504                         fill.src = isURL[1];
1505                         fill.type = "tile";
1506                     } else {
1507                         fill.color = R.getRGB(params.fill).hex;
1508                         fill.src = "";
1509                         fill.type = "solid";
1510                     }
1511                 }
1512                 newfill && node.appendChild(fill);
1513                 var stroke = (node.getElementsByTagName("stroke") && node.getElementsByTagName("stroke")[0]),
1514                 newstroke = false;
1515                 !stroke && (newstroke = stroke = createNode("stroke"));
1516                 if ((params.stroke && params.stroke != "none") ||
1517                     params["stroke-width"] ||
1518                     params["stroke-opacity"] != null ||
1519                     params["stroke-dasharray"] ||
1520                     params["stroke-miterlimit"] ||
1521                     params["stroke-linejoin"] ||
1522                     params["stroke-linecap"]) {
1523                     stroke.on = true;
1524                 }
1525                 (params.stroke == "none" || stroke.on == null || params.stroke == 0 || params["stroke-width"] == 0) && (stroke.on = false);
1526                 stroke.on && params.stroke && (stroke.color = R.getRGB(params.stroke).hex);
1527                 var opacity = ((+a["stroke-opacity"] + 1 || 2) - 1) * ((+a.opacity + 1 || 2) - 1);
1528                 opacity < 0 && (opacity = 0);
1529                 opacity > 1 && (opacity = 1);
1530                 stroke.opacity = opacity;
1531                 params["stroke-linejoin"] && (stroke.joinstyle = params["stroke-linejoin"] || "miter");
1532                 stroke.miterlimit = params["stroke-miterlimit"] || 8;
1533                 params["stroke-linecap"] && (stroke.endcap = {butt: "flat", square: "square", round: "round"}[params["stroke-linecap"]] || "miter");
1534                 params["stroke-width"] && (stroke.weight = (parseFloat(params["stroke-width"]) || 1) * 12 / 16);
1535                 if (params["stroke-dasharray"]) {
1536                     var dasharray = {
1537                         "-": "shortdash",
1538                         ".": "shortdot",
1539                         "-.": "shortdashdot",
1540                         "-..": "shortdashdotdot",
1541                         ". ": "dot",
1542                         "- ": "dash",
1543                         "--": "longdash",
1544                         "- .": "dashdot",
1545                         "--.": "longdashdot",
1546                         "--..": "longdashdotdot"
1547                     };
1548                     stroke.dashstyle = dasharray[params["stroke-dasharray"]] || "";
1549                 }
1550                 newstroke && node.appendChild(stroke);
1551             }
1552             if (res.type == "text") {
1553                 var s = paper.span.style;
1554                 a.font && (s.font = a.font);
1555                 a["font-family"] && (s.fontFamily = a["font-family"]);
1556                 a["font-size"] && (s.fontSize = a["font-size"]);
1557                 a["font-weight"] && (s.fontWeight = a["font-weight"]);
1558                 a["font-style"] && (s.fontStyle = a["font-style"]);
1559                 paper.span.innerHTML = "";
1560                 paper.span.appendChild(doc.createTextNode(res.node.string));
1561                 res.W = a.w = paper.span.offsetWidth;
1562                 res.H = a.h = paper.span.offsetHeight;
1563                 res.X = a.x;
1564                 res.Y = a.y + Math.round(res.H / 2);
1565
1566                 // text-anchor emulation
1567                 switch (a["text-anchor"]) {
1568                     case "start":
1569                         res.node.style["v-text-align"] = "left";
1570                         res.bbx = Math.round(res.W / 2);
1571                     break;
1572                     case "end":
1573                         res.node.style["v-text-align"] = "right";
1574                         res.bbx = -Math.round(res.W / 2);
1575                     break;
1576                     default:
1577                         res.node.style["v-text-align"] = "center";
1578                     break;
1579                 }
1580             }
1581         };
1582         var getAngle = function (a, b, c, d) {
1583             var angle = Math.round(Math.atan((parseFloat(c) - parseFloat(a)) / (parseFloat(d) - parseFloat(b))) * 57.29) || 0;
1584             if (!angle && parseFloat(a) < parseFloat(b)) {
1585                 angle = 180;
1586             }
1587             angle -= 180;
1588             if (angle < 0) {
1589                 angle += 360;
1590             }
1591             return angle;
1592         };
1593         var addGradientFill = function (o, gradient) {
1594             gradient = toGradient(gradient);
1595             o.attrs = o.attrs || {};
1596             var attrs = o.attrs,
1597                 fill = o.node.getElementsByTagName("fill");
1598             o.attrs.gradient = gradient;
1599             o = o.shape || o.node;
1600             if (fill.length) {
1601                 fill = fill[0];
1602             } else {
1603                 fill = createNode("fill");
1604             }
1605             if (gradient.dots.length) {
1606                 fill.on = true;
1607                 fill.method = "none";
1608                 fill.type = ((gradient.type + "").toLowerCase() == "radial") ? "gradientTitle" : "gradient";
1609                 if (typeof gradient.dots[0].color != "undefined") {
1610                     fill.color = R.getRGB(gradient.dots[0].color).hex;
1611                 }
1612                 if (typeof gradient.dots[gradient.dots.length - 1].color != "undefined") {
1613                     fill.color2 = R.getRGB(gradient.dots[gradient.dots.length - 1].color).hex;
1614                 }
1615                 var clrs = [];
1616                 for (var i = 0, ii = gradient.dots.length; i < ii; i++) {
1617                     if (gradient.dots[i].offset) {
1618                         clrs.push(gradient.dots[i].offset + " " + R.getRGB(gradient.dots[i].color).hex);
1619                     }
1620                 };
1621                 var fillOpacity = typeof gradient.dots[gradient.dots.length - 1].opacity == "undefined" ? (typeof attrs.opacity == "undefined" ? 1 : attrs.opacity) : gradient.dots[gradient.dots.length - 1].opacity;
1622                 if (clrs.length) {
1623                     fill.colors.value = clrs.join(",");
1624                     fillOpacity = typeof attrs.opacity == "undefined" ? 1 : attrs.opacity;
1625                 } else {
1626                     fill.colors && (fill.colors.value = "0% " + fill.color);
1627                 }
1628                 fill.opacity = fillOpacity;
1629                 if (typeof gradient.angle != "undefined") {
1630                     fill.angle = (-gradient.angle + 270) % 360;
1631                 } else if (gradient.vector) {
1632                     fill.angle = getAngle.apply(null, gradient.vector);
1633                 }
1634                 if ((gradient.type + "").toLowerCase() == "radial") {
1635                     fill.focus = "100%";
1636                     fill.focusposition = "0.5 0.5";
1637                 }
1638             }
1639         };
1640         var Element = function (node, group, vml) {
1641             var Rotation = 0,
1642                 RotX = 0,
1643                 RotY = 0,
1644                 Scale = 1;
1645             this[0] = node;
1646             this.node = node;
1647             this.X = 0;
1648             this.Y = 0;
1649             this.attrs = {};
1650             this.Group = group;
1651             this.paper = vml;
1652             this._ = {
1653                 tx: 0,
1654                 ty: 0,
1655                 rt: {deg:0},
1656                 sx: 1,
1657                 sy: 1
1658             };
1659         };
1660         Element.prototype.rotate = function (deg, cx, cy) {
1661             if (deg == null) {
1662                 if (this._.rt.cx) {
1663                     return [this._.rt.deg, this._.rt.cx, this._.rt.cy].join(" ");
1664                 }
1665                 return this._.rt.deg;
1666             }
1667             deg = (deg + "").split(separator);
1668             if (deg.length - 1) {
1669                 cx = parseFloat(deg[1]);
1670                 cy = parseFloat(deg[2]);
1671             }
1672             deg = parseFloat(deg[0]);
1673             if (cx != null) {
1674                 this._.rt.deg = deg;
1675             } else {
1676                 this._.rt.deg += deg;
1677             }
1678             (cy == null) && (cx = null);
1679             this._.rt.cx = cx;
1680             this._.rt.cy = cy;
1681             this.setBox(this.attrs, cx, cy);
1682             this.Group.style.rotation = this._.rt.deg;
1683             // gradient fix for rotation. TODO
1684             // var fill = (this.shape || this.node).getElementsByTagName("fill");
1685             // fill = fill[0] || {};
1686             // var b = ((360 - this._.rt.deg) - 270) % 360;
1687             // typeof fill.angle != "undefined" && (fill.angle = b);
1688             return this;
1689         };
1690         Element.prototype.setBox = function (params, cx, cy) {
1691             var gs = this.Group.style,
1692                 os = (this.shape && this.shape.style) || this.node.style;
1693             params = params || {};
1694             for (var i in params) {
1695                 this.attrs[i] = params[i];
1696             }
1697             cx = cx || this._.rt.cx;
1698             cy = cy || this._.rt.cy;
1699             var attr = this.attrs,
1700                 x,
1701                 y,
1702                 w,
1703                 h;
1704             switch (this.type) {
1705                 case "circle":
1706                     x = attr.cx - attr.r;
1707                     y = attr.cy - attr.r;
1708                     w = h = attr.r * 2;
1709                     break;
1710                 case "ellipse":
1711                     x = attr.cx - attr.rx;
1712                     y = attr.cy - attr.ry;
1713                     w = attr.rx * 2;
1714                     h = attr.ry * 2;
1715                     break;
1716                 case "rect":
1717                 case "image":
1718                     x = attr.x;
1719                     y = attr.y;
1720                     w = attr.width || 0;
1721                     h = attr.height || 0;
1722                     break;
1723                 case "text":
1724                     this.textpath.v = ["m", Math.round(attr.x), ", ", Math.round(attr.y - 2), "l", Math.round(attr.x) + 1, ", ", Math.round(attr.y - 2)].join("");
1725                     x = attr.x - Math.round(this.W / 2);
1726                     y = attr.y - this.H / 2;
1727                     w = this.W;
1728                     h = this.H;
1729                     break;
1730                 case "path":
1731                     if (!this.attrs.path) {
1732                         x = 0;
1733                         y = 0;
1734                         w = this.paper.width;
1735                         h = this.paper.height;
1736                     } else {
1737                         var dim = pathDimensions(this.attrs.path),
1738                         x = dim.x;
1739                         y = dim.y;
1740                         w = dim.width;
1741                         h = dim.height;
1742                     }
1743                     break;
1744                 default:
1745                     x = 0;
1746                     y = 0;
1747                     w = this.paper.width;
1748                     h = this.paper.height;
1749                     break;
1750             }
1751             cx = (cx == null) ? x + w / 2 : cx;
1752             cy = (cy == null) ? y + h / 2 : cy;
1753             var left = cx - this.paper.width / 2,
1754                 top = cy - this.paper.height / 2;
1755             if (this.type == "path" || this.type == "text") {
1756                 (gs.left != left + "px") && (gs.left = left + "px");
1757                 (gs.top != top + "px") && (gs.top = top + "px");
1758                 this.X = this.type == "text" ? x : -left;
1759                 this.Y = this.type == "text" ? y : -top;
1760                 this.W = w;
1761                 this.H = h;
1762                 (os.left != -left + "px") && (os.left = -left + "px");
1763                 (os.top != -top + "px") && (os.top = -top + "px");
1764             } else {
1765                 (gs.left != left + "px") && (gs.left = left + "px");
1766                 (gs.top != top + "px") && (gs.top = top + "px");
1767                 this.X = x;
1768                 this.Y = y;
1769                 this.W = w;
1770                 this.H = h;
1771                 (gs.width != this.paper.width + "px") && (gs.width = this.paper.width + "px");
1772                 (gs.height != this.paper.height + "px") && (gs.height = this.paper.height + "px");
1773                 (os.left != x - left + "px") && (os.left = x - left + "px");
1774                 (os.top != y - top + "px") && (os.top = y - top + "px");
1775                 (os.width != w + "px") && (os.width = w + "px");
1776                 (os.height != h + "px") && (os.height = h + "px");
1777                 var arcsize = (+params.r || 0) / (Math.min(w, h));
1778                 if (this.type == "rect" && this.arcsize != arcsize && (arcsize || this.arcsize)) {
1779                     // We should replace element with the new one
1780                     var o = createNode(arcsize ? "roundrect" : "rect");
1781                     o.arcsize = arcsize;
1782                     this.Group.appendChild(o);
1783                     this.node.parentNode.removeChild(this.node);
1784                     this.node = o;
1785                     this.arcsize = arcsize;
1786                     setFillAndStroke(this, this.attrs);
1787                     this.setBox(this.attrs);
1788                 }
1789             }
1790         };
1791         Element.prototype.hide = function () {
1792             this.Group.style.display = "none";
1793             return this;
1794         };
1795         Element.prototype.show = function () {
1796             this.Group.style.display = "block";
1797             return this;
1798         };
1799         Element.prototype.getBBox = function () {
1800             if (this.type == "path") {
1801                 return pathDimensions(this.attrs.path);
1802             }
1803             return {
1804                 x: this.X + (this.bbx || 0),
1805                 y: this.Y,
1806                 width: this.W,
1807                 height: this.H
1808             };
1809         };
1810         Element.prototype.remove = function () {
1811             this[0].parentNode.removeChild(this[0]);
1812             this.Group.parentNode.removeChild(this.Group);
1813             this.shape && this.shape.parentNode.removeChild(this.shape);
1814         };
1815         Element.prototype.attr = function () {
1816             if (arguments.length == 1 && typeof arguments[0] == "string") {
1817                 if (arguments[0] == "translation") {
1818                     return this.translate();
1819                 }
1820                 if (arguments[0] == "rotation") {
1821                     return this.rotate();
1822                 }
1823                 if (arguments[0] == "scale") {
1824                     return this.scale();
1825                 }
1826                 return this.attrs[arguments[0]];
1827             }
1828             if (this.attrs && arguments.length == 1 && R.isArray(arguments[0])) {
1829                 var values = {};
1830                 for (var i = 0, ii = arguments[0].length; i < ii; i++) {
1831                     values[arguments[0][i]] = this.attrs[arguments[0][i]];
1832                 };
1833                 return values;
1834             }
1835             var params;
1836             if (arguments.length == 2) {
1837                 params = {};
1838                 params[arguments[0]] = arguments[1];
1839             }
1840             if (arguments.length == 1 && typeof arguments[0] == "object") {
1841                 params = arguments[0];
1842             }
1843             if (params) {
1844                 if (params.gradient) {
1845                     addGradientFill(this, params.gradient);
1846                 }
1847                 if (params.text && this.type == "text") {
1848                     this.node.string = params.text;
1849                 }
1850                 setFillAndStroke(this, params);
1851                 this.setBox(this.attrs);
1852             }
1853             return this;
1854         };
1855         Element.prototype.toFront = function () {
1856             this.Group.parentNode.appendChild(this.Group);
1857             return this;
1858         };
1859         Element.prototype.toBack = function () {
1860             if (this.Group.parentNode.firstChild != this.Group) {
1861                 this.Group.parentNode.insertBefore(this.Group, this.Group.parentNode.firstChild);
1862             }
1863             return this;
1864         };
1865         Element.prototype.insertAfter = function (element) {
1866             if (element.Group.nextSibling) {
1867                 element.Group.parentNode.insertBefore(this.Group, element.Group.nextSibling);
1868             } else {
1869                 element.Group.parentNode.appendChild(this.Group);
1870             }
1871             return this;
1872         };
1873         Element.prototype.insertBefore = function (element) {
1874             element.Group.parentNode.insertBefore(this.Group, element.Group);
1875             return this;
1876         };
1877         var theCircle = function (vml, x, y, r) {
1878             var g = createNode("group"),
1879                 gl = g.style,
1880                 o = createNode("oval"),
1881                 ol = o.style;
1882             gl.position = "absolute";
1883             gl.left = 0;
1884             gl.top = 0;
1885             gl.width = vml.width + "px";
1886             gl.height = vml.height + "px";
1887             g.coordsize = vml.coordsize;
1888             g.coordorigin = vml.coordorigin;
1889             g.appendChild(o);
1890             var res = new Element(o, g, vml);
1891             res.type = "circle";
1892             setFillAndStroke(res, {stroke: "#000", fill: "none"});
1893             res.attrs.cx = x;
1894             res.attrs.cy = y;
1895             res.attrs.r = r;
1896             res.setBox({x: x - r, y: y - r, width: r * 2, height: r * 2});
1897             vml.canvas.appendChild(g);
1898             return res;
1899         };
1900         var theRect = function (vml, x, y, w, h, r) {
1901             var g = createNode("group"),
1902                 gl = g.style,
1903                 o = createNode(r ? "roundrect" : "rect"),
1904                 arcsize = (+r || 0) / (Math.min(w, h));
1905             o.arcsize = arcsize;
1906             gl.position = "absolute";
1907             gl.left = 0;
1908             gl.top = 0;
1909             gl.width = vml.width + "px";
1910             gl.height = vml.height + "px";
1911             g.coordsize = vml.coordsize;
1912             g.coordorigin = vml.coordorigin;
1913             g.appendChild(o);
1914             var res = new Element(o, g, vml);
1915             res.type = "rect";
1916             setFillAndStroke(res, {stroke: "#000"});
1917             res.arcsize = arcsize;
1918             res.setBox({x: x, y: y, width: w, height: h, r: +r});
1919             vml.canvas.appendChild(g);
1920             return res;
1921         };
1922         var theEllipse = function (vml, x, y, rx, ry) {
1923             var g = createNode("group"),
1924                 gl = g.style,
1925                 o = createNode("oval"),
1926                 ol = o.style;
1927             gl.position = "absolute";
1928             gl.left = 0;
1929             gl.top = 0;
1930             gl.width = vml.width + "px";
1931             gl.height = vml.height + "px";
1932             g.coordsize = vml.coordsize;
1933             g.coordorigin = vml.coordorigin;
1934             g.appendChild(o);
1935             var res = new Element(o, g, vml);
1936             res.type = "ellipse";
1937             setFillAndStroke(res, {stroke: "#000"});
1938             res.attrs.cx = x;
1939             res.attrs.cy = y;
1940             res.attrs.rx = rx;
1941             res.attrs.ry = ry;
1942             res.setBox({x: x - rx, y: y - ry, width: rx * 2, height: ry * 2});
1943             vml.canvas.appendChild(g);
1944             return res;
1945         };
1946         var theImage = function (vml, src, x, y, w, h) {
1947             var g = createNode("group"),
1948                 gl = g.style,
1949                 o = createNode("image"),
1950                 ol = o.style;
1951             gl.position = "absolute";
1952             gl.left = 0;
1953             gl.top = 0;
1954             gl.width = vml.width + "px";
1955             gl.height = vml.height + "px";
1956             g.coordsize = vml.coordsize;
1957             g.coordorigin = vml.coordorigin;
1958             o.src = src;
1959             g.appendChild(o);
1960             var res = new Element(o, g, vml);
1961             res.type = "image";
1962             res.attrs.src = src;
1963             res.attrs.x = x;
1964             res.attrs.y = y;
1965             res.attrs.w = w;
1966             res.attrs.h = h;
1967             res.setBox({x: x, y: y, width: w, height: h});
1968             vml.canvas.appendChild(g);
1969             return res;
1970         };
1971         var theText = function (vml, x, y, text) {
1972             var g = createNode("group"),
1973                 gs = g.style,
1974                 el = createNode("shape"),
1975                 ol = el.style,
1976                 path = createNode("path"),
1977                 ps = path.style,
1978                 o = createNode("textpath");
1979             gs.position = "absolute";
1980             gs.left = 0;
1981             gs.top = 0;
1982             gs.width = vml.width + "px";
1983             gs.height = vml.height + "px";
1984             g.coordsize = vml.coordsize;
1985             g.coordorigin = vml.coordorigin;
1986             path.v = ["m", Math.round(x), ", ", Math.round(y), "l", Math.round(x) + 1, ", ", Math.round(y)].join("");
1987             path.textpathok = true;
1988             ol.width = vml.width;
1989             ol.height = vml.height;
1990             gs.position = "absolute";
1991             gs.left = 0;
1992             gs.top = 0;
1993             gs.width = vml.width;
1994             gs.height = vml.height;
1995             o.string = text;
1996             o.on = true;
1997             el.appendChild(o);
1998             el.appendChild(path);
1999             g.appendChild(el);
2000             var res = new Element(o, g, vml);
2001             res.shape = el;
2002             res.textpath = path;
2003             res.type = "text";
2004             res.attrs.text = text;
2005             res.attrs.x = x;
2006             res.attrs.y = y;
2007             res.attrs.w = 1;
2008             res.attrs.h = 1;
2009             setFillAndStroke(res, {font: availableAttrs.font, stroke: "none", fill: "#000"});
2010             res.setBox();
2011             vml.canvas.appendChild(g);
2012             return res;
2013         };
2014         var setSize = function (width, height) {
2015             var cs = this.canvas.style;
2016             this.width = width || this.width;
2017             this.height = height || this.height;
2018             cs.width = this.width + "px";
2019             cs.height = this.height + "px";
2020             cs.clip = "rect(0 " + this.width + "px " + this.height + "px 0)";
2021             this.canvas.coordsize = this.width + " " + this.height;
2022             return this;
2023         };
2024         doc.createStyleSheet().addRule(".rvml", "behavior:url(#default#VML)");
2025         try {
2026             if (!doc.namespaces.rvml) {
2027                 doc.namespaces.add("rvml", "urn:schemas-microsoft-com:vml");
2028             }
2029             var createNode = function (tagName) {
2030                 return doc.createElement('<rvml:' + tagName + ' class="rvml">');
2031             };
2032         } catch (e) {
2033             var createNode = function (tagName) {
2034                 return doc.createElement('<' + tagName + ' xmlns="urn:schemas-microsoft.com:vml" class="rvml">');
2035             };
2036         }
2037         var create = function () {
2038             var con = getContainer.apply(null, arguments),
2039                 container = con.container,
2040                 x = con.x,
2041                 y = con.y,
2042                 width = con.width,
2043                 s,
2044                 height = con.height;
2045             if (!container) {
2046                 throw new Error("VML container not found.");
2047             }
2048             var c = paper.canvas = doc.createElement("div"),
2049                 cs = c.style;
2050             width = parseFloat(width) || "512px";
2051             height = parseFloat(height) || "342px";
2052             paper.width = width;
2053             paper.height = height;
2054             paper.coordsize = width + " " + height;
2055             paper.coordorigin = "0 0";
2056             paper.span = doc.createElement("span");
2057             s = paper.span.style;
2058             c.appendChild(paper.span);
2059             s.position = "absolute";
2060             s.left = "-99999px";
2061             s.top = "-99999px";
2062             s.padding = 0;
2063             s.margin = 0;
2064             s.lineHeight = 1;
2065             s.display = "inline";
2066             cs.width  = width + "px";
2067             cs.height = height + "px";
2068             cs.position = "absolute";
2069             cs.clip = "rect(0 " + width + "px " + height + "px 0)";
2070             if (container == 1) {
2071                 doc.body.appendChild(c);
2072                 cs.left = x + "px";
2073                 cs.top = y + "px";
2074                 container = {
2075                     style: {
2076                         width: width,
2077                         height: height
2078                     }
2079                 };
2080             } else {
2081                 container.style.width = width;
2082                 container.style.height = height;
2083                 if (container.firstChild) {
2084                     container.insertBefore(c, container.firstChild);
2085                 } else {
2086                     container.appendChild(c);
2087                 }
2088             }
2089             for (var prop in paper) {
2090                 container[prop] = paper[prop];
2091             }
2092             plugins.call(container, container, R.fn);
2093             container.clear = function () {
2094                 while (c.firstChild) {
2095                     c.removeChild(c.firstChild);
2096                 }
2097             };
2098             container.raphael = R;
2099             return container;
2100         };
2101         paper.remove = function () {
2102             this.canvas.parentNode.removeChild(this.canvas);
2103         };
2104         paper.safari = function () {};
2105     }
2106
2107     // rest
2108
2109     // Events
2110     var addEvent = (function () {
2111         if (doc.addEventListener) {
2112             return function (obj, type, fn, element) {
2113                 var f = function (e) {
2114                     return fn.call(element, e);
2115                 };
2116                 obj.addEventListener(type, f, false);
2117                 return function () {
2118                     obj.removeEventListener(type, f, false);
2119                     return true;
2120                 };
2121             };
2122         } else if (doc.attachEvent) {
2123             return function (obj, type, fn, element) {
2124                 var f = function (e) {
2125                     return fn.call(element, e || win.event);
2126                 };
2127                 obj.attachEvent("on" + type, f);
2128                 var detacher = function () {
2129                     obj.detachEvent("on" + type, f);
2130                     return true;
2131                 };
2132                 if (type == "mouseover") {
2133                     obj.attachEvent("onmouseenter", f);
2134                     return function () {
2135                         obj.detachEvent("onmouseenter", f);
2136                         return detacher();
2137                     };
2138                 } else if (type == "mouseout") {
2139                     obj.attachEvent("onmouseleave", f);
2140                     return function () {
2141                         obj.detachEvent("onmouseleave", f);
2142                         return detacher();
2143                     };
2144                 }
2145                 return detacher;
2146             };
2147         }
2148     })();
2149     for (var i = events.length; i--;) {
2150         (function (eventName) {
2151             Element.prototype[eventName] = function (fn) {
2152                 if (typeof fn == "function") {
2153                     this.events = this.events || {};
2154                     this.events[eventName] = this.events[eventName] || {};
2155                     this.events[eventName][fn] = this.events[eventName][fn] || [];
2156                     this.events[eventName][fn].push(addEvent(this.shape || this.node, eventName, fn, this));
2157                 }
2158                 return this;
2159             };
2160             Element.prototype["un" + eventName] = function (fn) {
2161                 this.events &&
2162                 this.events[eventName] &&
2163                 this.events[eventName][fn] &&
2164                 this.events[eventName][fn].length &&
2165                 this.events[eventName][fn].shift()() &&
2166                 !this.events[eventName][fn].length &&
2167                 delete this.events[eventName][fn];
2168             };
2169
2170         })(events[i]);
2171     }
2172     paper.circle = function (x, y, r) {
2173         return theCircle(this, x, y, r);
2174     };
2175     paper.rect = function (x, y, w, h, r) {
2176         return theRect(this, x, y, w, h, r);
2177     };
2178     paper.ellipse = function (x, y, rx, ry) {
2179         return theEllipse(this, x, y, rx, ry);
2180     };
2181     paper.path = function (pathString) {
2182         return thePath(pathString, this);
2183     };
2184     paper.image = function (src, x, y, w, h) {
2185         return theImage(this, src, x, y, w, h);
2186     };
2187     paper.text = function (x, y, text) {
2188         return theText(this, x, y, text);
2189     };
2190     paper.set = function (itemsArray) {
2191         arguments.length > 1 && (itemsArray = Array.prototype.splice.call(arguments, 0, arguments.length));
2192         return new Set(itemsArray);
2193     };
2194     paper.setSize = setSize;
2195     Element.prototype.stop = function () {
2196         clearTimeout(this.animation_in_progress);
2197         return this;
2198     };
2199     Element.prototype.scale = function (x, y, cx, cy) {
2200         if (x == null && y == null) {
2201             return {x: this._.sx, y: this._.sy, toString: function () { return +this.x.toFixed(3) + " " + (+this.y.toFixed(3)); }};
2202         }
2203         y = y || x;
2204         !+y && (y = x);
2205         var dx,
2206             dy,
2207             dcx,
2208             dcy,
2209             a = this.attrs;
2210         if (x != 0) {
2211             var bb = this.type == "path" ? pathDimensions(a.path) : this.getBBox(),
2212                 rcx = bb.x + bb.width / 2,
2213                 rcy = bb.y + bb.height / 2,
2214                 kx = x / this._.sx,
2215                 ky = y / this._.sy;
2216             cx = (+cx || cx == 0) ? cx : rcx;
2217             cy = (+cy || cy == 0) ? cy : rcy;
2218             var dirx = Math.round(x / Math.abs(x)),
2219                 diry = Math.round(y / Math.abs(y)),
2220                 s = this.node.style,
2221                 ncx = cx + (rcx - cx) * dirx * kx,
2222                 ncy = cy + (rcy - cy) * diry * ky;
2223             switch (this.type) {
2224                 case "rect":
2225                 case "image":
2226                     var neww = a.width * dirx * kx,
2227                         newh = a.height * diry * ky,
2228                         newx = ncx - neww / 2,
2229                         newy = ncy - newh / 2;
2230                     this.attr({
2231                         width: neww,
2232                         height: newh,
2233                         x: newx,
2234                         y: newy
2235                     });
2236                     break;
2237                 case "circle":
2238                 case "ellipse":
2239                     this.attr({
2240                         rx: a.rx * kx,
2241                         ry: a.ry * ky,
2242                         r: a.r * kx,
2243                         cx: ncx,
2244                         cy: ncy
2245                     });
2246                     break;
2247                 case "path":
2248                     var path = pathToRelative(a.path),
2249                         skip = true;
2250                     for (var i = 0, ii = path.length; i < ii; i++) {
2251                         var p = path[i];
2252                         if (p[0].toUpperCase() == "M" && skip) {
2253                             continue;
2254                         } else {
2255                             skip = false;
2256                         }
2257                         if (R.svg && p[0].toUpperCase() == "A") {
2258                             p[path[i].length - 2] *= kx;
2259                             p[path[i].length - 1] *= ky;
2260                             p[1] *= kx;
2261                             p[2] *= ky;
2262                             p[5] = +(dirx + diry ? !!+p[5] : !+p[5]);
2263                         } else {
2264                             for (var j = 1, jj = p.length; j < jj; j++) {
2265                                 p[j] *= (j % 2) ? kx : ky;
2266                             }
2267                         }
2268                     }
2269                     var dim2 = pathDimensions(path),
2270                         dx = ncx - dim2.x - dim2.width / 2,
2271                         dy = ncy - dim2.y - dim2.height / 2;
2272                     path = pathToRelative(path);
2273                     path[0][1] += dx;
2274                     path[0][2] += dy;
2275                     
2276                     this.attr({path: path.join(" ")});
2277                 break;
2278             }
2279             if (this.type in {text: 1, image:1} && (dirx != 1 || diry != 1)) {
2280                 if (this.transformations) {
2281                     this.transformations[2] = "scale(".concat(dirx, ",", diry, ")");
2282                     this.node.setAttribute("transform", this.transformations.join(" "));
2283                     dx = (dirx == -1) ? -a.x - (neww || 0) : a.x;
2284                     dy = (diry == -1) ? -a.y - (newh || 0) : a.y;
2285                     this.attr({x: dx, y: dy});
2286                     a.fx = dirx - 1;
2287                     a.fy = diry - 1;
2288                 } else {
2289                     this.node.filterMatrix = " progid:DXImageTransform.Microsoft.Matrix(M11=".concat(dirx,
2290                         ", M12=0, M21=0, M22=", diry,
2291                         ", Dx=0, Dy=0, sizingmethod='auto expand', filtertype='bilinear')");
2292                     s.filter = (this.node.filterMatrix || "") + (this.node.filterOpacity || "");
2293                 }
2294             } else {
2295                 if (this.transformations) {
2296                     this.transformations[2] = "";
2297                     this.node.setAttribute("transform", this.transformations.join(" "));
2298                     a.fx = 0;
2299                     a.fy = 0;
2300                 } else {
2301                     this.node.filterMatrix = "";
2302                     s.filter = (this.node.filterMatrix || "") + (this.node.filterOpacity || "");
2303                 }
2304             }
2305             a.scale = [x, y, cx, cy].join(" ");
2306             this._.sx = x;
2307             this._.sy = y;
2308         }
2309         return this;
2310     };
2311
2312     R.easing_formulas = {
2313         linear: function (n) {
2314             return n;
2315         },
2316         "<": function (n) {
2317             return Math.pow(n, 3);
2318         },
2319         ">": function (n) {
2320             return Math.pow(n - 1, 3) + 1;
2321         },
2322         "<>": function (n) {
2323             n = n * 2;
2324             if (n < 1) {
2325                 return Math.pow(n, 3) / 2;
2326             }
2327             n -= 2;
2328             return (Math.pow(n, 3) + 2) / 2;
2329         },
2330         backIn: function (n) {
2331             var s = 1.70158;
2332             return Math.pow(n, 2) * ((s + 1) * n - s);
2333         },
2334         backOut: function (n) {
2335             n = n - 1;
2336             var s = 1.70158;
2337             return Math.pow(n, 2) * ((s + 1) * n + s) + 1;
2338         },
2339         elastic: function (n) {
2340             if (n == 0 || n == 1) {
2341                 return n;
2342             }
2343             var p = .3,
2344                 s = p / 4;
2345             return Math.pow(2, -10 * n) * Math.sin((n - s) * (2 * Math.PI) / p) + 1;
2346         },
2347         bounce: function (n) {
2348             var s = 7.5625,
2349                 p = 2.75,
2350                 l;
2351             if (n < (1 / p)) {
2352                 l = s * Math.pow(n, 2);
2353             } else {
2354                 if (n < (2 / p)) {
2355                     n -= (1.5 / p);
2356                     l = s * Math.pow(n, 2) + .75;
2357                 } else {
2358                     if (n < (2.5 / p)) {
2359                         n -= (2.25 / p);
2360                         l = s * Math.pow(n, 2) + .9375;
2361                     } else {
2362                         n -= (2.625 / p);
2363                         l = s * Math.pow(n, 2) + .984375;
2364                     }
2365                 }
2366             }
2367             return l;
2368         }
2369     };
2370
2371     // animation easing formulas
2372     R.easing = function(easing, n) {
2373         return R.easing_formulas[easing] ? R.easing_formulas[easing](n) : n;
2374     };
2375
2376     Element.prototype.animate = function (params, ms, easing, callback) {
2377         clearTimeout(this.animation_in_progress);
2378         if (typeof easing == "function" || !easing) {
2379             callback = easing || null;
2380         }
2381         var from = {},
2382             to = {},
2383             diff = {},
2384             t = {x: 0, y: 0};
2385         for (var attr in params) {
2386             if (attr in availableAnimAttrs) {
2387                 from[attr] = this.attr(attr);
2388                 (typeof from[attr] == "undefined") && (from[attr] = availableAttrs[attr]);
2389                 to[attr] = params[attr];
2390                 switch (availableAnimAttrs[attr]) {
2391                     case "number":
2392                         diff[attr] = (to[attr] - from[attr]) / ms;
2393                         break;
2394                     case "colour":
2395                         from[attr] = R.getRGB(from[attr]);
2396                         var toColour = R.getRGB(to[attr]);
2397                         diff[attr] = {
2398                             r: (toColour.r - from[attr].r) / ms,
2399                             g: (toColour.g - from[attr].g) / ms,
2400                             b: (toColour.b - from[attr].b) / ms
2401                         };
2402                         break;
2403                     case "path":
2404                         var pathes = path2curve(from[attr], to[attr]);
2405                         from[attr] = pathes[0];
2406                         to[attr] = pathes[1];
2407                         diff[attr] = [];
2408                         for (var i = 0, ii = from[attr].length; i < ii; i++) {
2409                             diff[attr][i] = [0];
2410                             for (var j = 1, jj = from[attr][i].length; j < jj; j++) {
2411                                 diff[attr][i][j] = (to[attr][i][j] - from[attr][i][j]) / ms;
2412                             }
2413                         }
2414                         break;
2415                     case "csv":
2416                         var values = (params[attr] + "").split(separator),
2417                             from2 = (from[attr] + "").split(separator);
2418                         switch (attr) {
2419                             case "translation":
2420                                 from[attr] = [0, 0];
2421                                 diff[attr] = [values[0] / ms, values[1] / ms];
2422                             break;
2423                             case "rotation":
2424                                 from[attr] = (from2[1] == values[1] && from2[2] == values[2]) ? from2 : [0, values[1], values[2]];
2425                                 diff[attr] = [(values[0] - from[attr][0]) / ms, 0, 0];
2426                             break;
2427                             case "scale":
2428                                 params[attr] = values;
2429                                 from[attr] = (from[attr] + "").split(separator);
2430                                 diff[attr] = [(values[0] - from[attr][0]) / ms, (values[1] - from[attr][1]) / ms, 0, 0];
2431                         }
2432                         to[attr] = values;
2433                 }
2434             }
2435         }
2436         var start = +new Date,
2437             prev = 0,
2438             upto255 = function (color) {
2439                 return +color > 255 ? 255 : +color;
2440             },
2441             that = this;
2442         (function tick() {
2443             var time = new Date - start,
2444                 set = {},
2445                 now;
2446             if (time < ms) {
2447                 var pos = R.easing(easing, time / ms);
2448                 for (var attr in from) {
2449                     switch (availableAnimAttrs[attr]) {
2450                         case "number":
2451                             now = +from[attr] + pos * ms * diff[attr];
2452                             break;
2453                         case "colour":
2454                             now = "rgb(" + [
2455                                 upto255(Math.round(from[attr].r + pos * ms * diff[attr].r)),
2456                                 upto255(Math.round(from[attr].g + pos * ms * diff[attr].g)),
2457                                 upto255(Math.round(from[attr].b + pos * ms * diff[attr].b))
2458                             ].join(",") + ")";
2459                             break;
2460                         case "path":
2461                             now = [];
2462                             for (var i = 0, ii = from[attr].length; i < ii; i++) {
2463                                 now[i] = [from[attr][i][0]];
2464                                 for (var j = 1, jj = from[attr][i].length; j < jj; j++) {
2465                                     now[i][j] = +from[attr][i][j] + pos * ms * diff[attr][i][j];
2466                                 }
2467                                 now[i] = now[i].join(" ");
2468                             }
2469                             now = now.join(" ");
2470                             break;
2471                         case "csv":
2472                             switch (attr) {
2473                                 case "translation":
2474                                     var x = diff[attr][0] * (time - prev),
2475                                         y = diff[attr][1] * (time - prev);
2476                                     t.x += x;
2477                                     t.y += y;
2478                                     now = [x, y].join(" ");
2479                                 break;
2480                                 case "rotation":
2481                                     now = +from[attr][0] + pos * ms * diff[attr][0];
2482                                     from[attr][1] && (now += "," + from[attr][1] + "," + from[attr][2]);
2483                                 break;
2484                                 case "scale":
2485                                     now = [+from[attr][0] + pos * ms * diff[attr][0], +from[attr][1] + pos * ms * diff[attr][1], (2 in params[attr] ? params[attr][2] : ""), (3 in params[attr] ? params[attr][3] : "")].join(" ");
2486                             }
2487                             break;
2488                     }
2489                     set[attr] = now;
2490                 }
2491                 that.attr(set);
2492                 that.animation_in_progress = setTimeout(tick);
2493                 R.svg && paper.safari();
2494             } else {
2495                 (t.x || t.y) && that.translate(-t.x, -t.y);
2496                 that.attr(params);
2497                 clearTimeout(that.animation_in_progress);
2498                 R.svg && paper.safari();
2499                 (typeof callback == "function") && callback.call(that);
2500             }
2501             prev = time;
2502         })();
2503         return this;
2504     };
2505     Element.prototype.translate = function (x, y) {
2506         if (x == null) {
2507             return {x: this._.tx, y: this._.ty};
2508         }
2509         this._.tx += +x;
2510         this._.ty += +y;
2511         switch (this.type) {
2512             case "circle":
2513             case "ellipse":
2514                 this.attr({cx: +x + this.attrs.cx, cy: +y + this.attrs.cy});
2515                 break;
2516             case "rect":
2517             case "image":
2518             case "text":
2519                 this.attr({x: +x + this.attrs.x, y: +y + this.attrs.y});
2520                 break;
2521             case "path":
2522                 var path = pathToRelative(this.attrs.path);
2523                 path[0][1] += +x;
2524                 path[0][2] += +y;
2525                 this.attr({path: path});
2526             break;
2527         }
2528         return this;
2529     };
2530     
2531     // Set
2532     var Set = function (items) {
2533         this.items = [];
2534         this.length = 0;
2535         if (items) {
2536             for (var i = 0, ii = items.length; i < ii; i++) {
2537                 if (items[i] && (items[i].constructor == Element || items[i].constructor == Set)) {
2538                     this[this.items.length] = this.items[this.items.length] = items[i];
2539                     this.length++;
2540                 }
2541             }
2542         }
2543     };
2544     Set.prototype.push = function () {
2545         var item,
2546             len;
2547         for (var i = 0, ii = arguments.length; i < ii; i++) {
2548             item = arguments[i];
2549             if (item && (item.constructor == Element || item.constructor == Set)) {
2550                 len = this.items.length;
2551                 this[len] = this.items[len] = item;
2552                 this.length++;
2553             }
2554         }
2555         return this;
2556     };
2557     Set.prototype.pop = function (id) {
2558         var res = this.items.splice(id, 1)[0];
2559         for (var j = id, jj = this.items.length; j < jj; j++) {
2560             this[j] = this[j + 1];
2561         }
2562         delete this[jj + 1];
2563         this.length--;
2564         return res;
2565     };
2566     for (var method in Element.prototype) {
2567         Set.prototype[method] = (function (methodname) {
2568             return function () {
2569                 for (var i = 0, ii = this.items.length; i < ii; i++) {
2570                     this.items[i][methodname].apply(this.items[i], arguments);
2571                 }
2572                 return this;
2573             };
2574         })(method);
2575     }
2576     Set.prototype.attr = function (name, value) {
2577         if (name && R.isArray(name) && typeof name[0] == "object") {
2578             for (var j = 0, jj = name.length; j < jj; j++) {
2579                 this.items[j].attr(name[j]);
2580             }
2581         } else {
2582             for (var i = 0, ii = this.items.length; i < ii; i++) {
2583                 this.items[i].attr.apply(this.items[i], arguments);
2584             }
2585         }
2586         return this;
2587     };
2588     
2589     Set.prototype.getBBox = function () {
2590         var x = [],
2591             y = [],
2592             w = [],
2593             h = [];
2594         for (var i = this.items.length; i--;) {
2595             var box = this.items[i].getBBox();
2596             x.push(box.x);
2597             y.push(box.y);
2598             w.push(box.x + box.width);
2599             h.push(box.y + box.height);
2600         }
2601         x = Math.min.apply(Math, x);
2602         y = Math.min.apply(Math, y);
2603         return {
2604             x: x,
2605             y: y,
2606             width: Math.max.apply(Math, w) - x,
2607             height: Math.max.apply(Math, h) - y
2608         };
2609     };
2610
2611     R.registerFont = function (font) {
2612         if (!font.face) {
2613             return font;
2614         }
2615         this.fonts = this.fonts || {};
2616         var fontcopy = {
2617                 w: font.w,
2618                 face: {},
2619                 glyphs: {}
2620             },
2621             family = font.face["font-family"];
2622         for (var prop in font.face) {
2623             fontcopy.face[prop] = font.face[prop];
2624         }
2625         if (this.fonts[family]) {
2626             this.fonts[family].push(fontcopy);
2627         } else {
2628             this.fonts[family] = [fontcopy];
2629         }
2630         if (!font.svg) {
2631             fontcopy.face["units-per-em"] = parseInt(font.face["units-per-em"], 10);
2632             for (var glyph in font.glyphs) {
2633                 var path = font.glyphs[glyph];
2634                 fontcopy.glyphs[glyph] = {
2635                     w: path.w,
2636                     k: {},
2637                     d: path.d && "M" + path.d.replace(/[mlcxtrv]/g, function (command) {
2638                             return {l: "L", c: "C", x: "z", t: "m", r: "l", v: "c"}[command] || "M";
2639                         }) + "z"
2640                 };
2641                 if (path.k) {
2642                     for (var k in path.k) {
2643                         fontcopy.glyphs[glyph].k[k] = path.k[k];
2644                     }
2645                 }
2646             }
2647         }
2648         return font;
2649     };
2650     paper.getFont = function (family, weight, style, stretch) {
2651         stretch = stretch || "normal";
2652         style = style || "normal";
2653         weight = +weight || {normal: 400, bold: 700, lighter: 300, bolder: 800}[weight] || 400;
2654         var font = R.fonts[family];
2655         if (!font) {
2656             var name = new RegExp("(^|\\s)" + family.replace(/[^\w\d\s+!~.:_-]/g, "") + "(\\s|$)", "i");
2657             for (var fontName in R.fonts) {
2658                 if (name.test(fontName)) {
2659                     font = R.fonts[fontName];
2660                     break;
2661                 }
2662             }
2663         }
2664         var thefont;
2665         if (font) {
2666             for (var i = 0, ii = font.length; i < ii; i++) {
2667                 thefont = font[i];
2668                 if (thefont.face["font-weight"] == weight && (thefont.face["font-style"] == style || !thefont.face["font-style"]) && thefont.face["font-stretch"] == stretch) {
2669                     break;
2670                 }
2671             }
2672         }
2673         return thefont;
2674     };
2675     paper.print = function (x, y, string, font, size) {
2676         var out = this.set(),
2677             letters = (string + "").split(""),
2678             shift = 0,
2679             path = "",
2680             scale;
2681         if (font) {
2682             scale = (size || 16) / font.face["units-per-em"];
2683             for (var i = 0, ii = letters.length; i < ii; i++) {
2684                 var prev = i && font.glyphs[letters[i - 1]] || {},
2685                     curr = font.glyphs[letters[i]];
2686                 shift += i ? (prev.w || font.w) + (prev.k && prev.k[letters[i]] || 0) : 0;
2687                 curr && curr.d && out.push(this.path({fill: "#000", stroke: "none"}, curr.d).translate(shift, 0));
2688             }
2689             out.scale(scale, scale, 0, y).translate(x, (size || 16) / 2);
2690         }
2691         return out;
2692     };
2693
2694     R.ninja = function () {
2695         var r = window.Raphael;
2696         if (oldRaphael.was) {
2697             window.Raphael = oldRaphael.is;
2698         } else {
2699             try {
2700                 delete window.Raphael;
2701             } catch (e) {
2702                 window.Raphael = void(0);
2703             }
2704         }
2705         return r;
2706     };
2707     R.el = Element.prototype;
2708     return R;
2709 })();