2.0.1
[raphael] / raphael.js
index c49bbc5..bf6e741 100644 (file)
@@ -1,5 +1,5 @@
 // ┌─────────────────────────────────────────────────────────────────────┐ \\
-// │ Raphaël 2.0 - JavaScript Vector Library                             │ \\
+// │ Raphaël 2.0.1 - JavaScript Vector Library                           │ \\
 // ├─────────────────────────────────────────────────────────────────────┤ \\
 // │ Copyright (c) 2008-2011 Dmitry Baranovskiy (http://raphaeljs.com)   │ \\
 // │ Copyright (c) 2008-2011 Sencha Labs (http://sencha.com)             │ \\
@@ -7,14 +7,14 @@
 // └─────────────────────────────────────────────────────────────────────┘ \\
 
 // ┌──────────────────────────────────────────────────────────────────────────────────────┐ \\
-// │ Eve 0.3.0 - JavaScript Events Library                                                │ \\
+// │ Eve 0.4.0 - JavaScript Events Library                                                │ \\
 // ├──────────────────────────────────────────────────────────────────────────────────────┤ \\
 // │ Copyright (c) 2008-2011 Dmitry Baranovskiy (http://dmitry.baranovskiy.com/)          │ \\
 // │ Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license. │ \\
 // └──────────────────────────────────────────────────────────────────────────────────────┘ \\
 
 (function (glob) {
-    var version = "0.3.0",
+    var version = "0.4.0",
         has = "hasOwnProperty",
         separator = /[\.\/]/,
         wildcard = "*",
@@ -28,6 +28,7 @@
     
         eve = function (name, scope) {
             var e = events,
+                oldstop = stop,
                 args = Array.prototype.slice.call(arguments, 2),
                 listeners = eve.listeners(name),
                 z = 0,
@@ -50,6 +51,7 @@
                 l = queue[indexed[z++]];
                 out.push(l.apply(scope, args));
                 if (stop) {
+                    stop = oldstop;
                     return out;
                 }
             }
@@ -59,6 +61,7 @@
                     if (l.zIndex == indexed[z]) {
                         out.push(l.apply(scope, args));
                         if (stop) {
+                            stop = oldstop;
                             return out;
                         }
                         do {
@@ -66,6 +69,7 @@
                             l = queue[indexed[z]];
                             l && out.push(l.apply(scope, args));
                             if (stop) {
+                                stop = oldstop;
                                 return out;
                             }
                         } while (l)
                 } else {
                     out.push(l.apply(scope, args));
                     if (stop) {
+                        stop = oldstop;
                         return out;
                     }
                 }
             }
+            stop = oldstop;
             return out.length ? out : null;
         };
     
             e,
             key,
             splice,
+            i, ii, j, jj,
             cur = [events];
-        for (var i = 0, ii = names.length; i < ii; i++) {
-            for (var j = 0; j < cur.length; j += splice.length - 2) {
+        for (i = 0, ii = names.length; i < ii; i++) {
+            for (j = 0; j < cur.length; j += splice.length - 2) {
                 splice = [j, 1];
                 e = cur[j].n;
                 if (names[i] != wildcard) {
             while (e.n) {
                 if (f) {
                     if (e.f) {
-                        for (i = 0, ii = e.f.length; i < ii; i++) if (e.f[i] == f) {
-                            e.f.splice(i, 1);
+                        for (j = 0, jj = e.f.length; j < jj; j++) if (e.f[j] == f) {
+                            e.f.splice(j, 1);
                             break;
                         }
                         !e.f.length && delete e.f;
                     }
                     for (key in e.n) if (e.n[has](key) && e.n[key].f) {
                         var funcs = e.n[key].f;
-                        for (i = 0, ii = funcs.length; i < ii; i++) if (funcs[i] == f) {
-                            funcs.splice(i, 1);
+                        for (j = 0, jj = funcs.length; j < jj; j++) if (funcs[j] == f) {
+                            funcs.splice(j, 1);
                             break;
                         }
                         !funcs.length && delete e.n[key].f;
         }
     };
     
+    eve.once = function (name, f) {
+        var f2 = function () {
+            f.apply(this, arguments);
+            eve.unbind(name, f2);
+        };
+        return eve.on(name, f2);
+    };
+    
     eve.version = version;
     eve.toString = function () {
         return "You are running Eve " + version;
 })(this);
 
 // ┌─────────────────────────────────────────────────────────────────────┐ \\
-// │ "Raphaël 2.0" - JavaScript Vector Library                           │ \\
+// │ "Raphaël 2.0.1" - JavaScript Vector Library                         │ \\
 // ├─────────────────────────────────────────────────────────────────────┤ \\
 // │ Copyright (c) 2008-2011 Dmitry Baranovskiy (http://raphaeljs.com)   │ \\
 // │ Copyright (c) 2008-2011 Sencha Labs (http://sencha.com)             │ \\
         if (R.is(first, "function")) {
             return loaded ? first() : eve.on("DOMload", first);
         } else if (R.is(first, array)) {
-            var a = first,
-                cnv = R._engine.create[apply](R, a.splice(0, 3 + R.is(a[0], nu))),
-                res = cnv.set(),
-                i = 0,
-                ii = a.length,
-                j;
-            for (; i < ii; i++) {
-                j = a[i] || {};
-                elements[has](j.type) && res.push(cnv[j.type]().attr(j));
-            }
-            return res;
+            return R._engine.create[apply](R, first.splice(0, 3 + R.is(first[0], nu))).add(first);
         } else {
             var args = Array.prototype.slice.call(arguments, 0);
             if (R.is(args[args.length - 1], "function")) {
             }
         }
     }
-    R.version = "2.0.0";
+    R.version = "2.0.1";
     R.eve = eve;
     var loaded,
         separator = /[, ]+/,
             was: Object.prototype[has].call(g.win, "Raphael"),
             is: g.win.Raphael
         },
-        Paper = function () {},
+        Paper = function () {
+            
+            
+            this.ca = this.customAttributes = {};
+        },
         paperproto,
         appendChild = "appendChild",
         apply = "apply",
         S = " ",
         Str = String,
         split = "split",
-        events = "click dblclick mousedown mousemove mouseout mouseover mouseup touchstart touchmove touchend touchcancel".split(S),
+        events = "click dblclick mousedown mousemove mouseout mouseover mouseup touchstart touchmove touchend touchcancel"[split](S),
         touchMap = {
             mousedown: "touchstart",
             mousemove: "touchmove",
         paper = {},
         push = "push",
         ISURL = R._ISURL = /^url\(['"]?([^\)]+?)['"]?\)$/i,
-        colourRegExp = /^\s*((#[a-f\d]{6})|(#[a-f\d]{3})|rgba?\(\s*([\d\.]+%?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?)%?\s*\)|hsba?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?)%?\s*\)|hsla?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?)%?\s*\))\s*$/i,
+        colourRegExp = /^\s*((#[a-f\d]{6})|(#[a-f\d]{3})|rgba?\(\s*([\d\.]+%?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+%?(?:\s*,\s*[\d\.]+%?)?)\s*\)|hsba?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?)%?\s*\)|hsla?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?)%?\s*\))\s*$/i,
         isnan = {"NaN": 1, "Infinity": 1, "-Infinity": 1},
         bezierrg = /^(?:cubic-)?bezier\(([^,]+),([^,]+),([^,]+),([^\)]+)\)/,
         round = math.round,
         setAttribute = "setAttribute",
         toFloat = parseFloat,
         toInt = parseInt,
-        ms = " progid:DXImageTransform.Microsoft",
         upperCase = Str.prototype.toUpperCase,
         availableAttrs = R._availableAttrs = {
             "arrow-end": "none",
             gradient: 0,
             height: 0,
             href: "http://raphaeljs.com/",
+            "letter-spacing": 0,
             opacity: 1,
             path: "M0,0",
             r: 0,
             return a.key - b.key;
         },
         sortByNumber = function (a, b) {
-            return a - b;
+            return toFloat(a) - toFloat(b);
         },
         fun = function () {},
         pipe = function (x) {
                 return rectPath(bbox.x, bbox.y, bbox.width, bbox.height);
             }
         },
-        mapPath = function (path, matrix) {
+        mapPath = R.mapPath = function (path, matrix) {
             if (!matrix) {
                 return path;
             }
-            var x, y, i, j, pathi;
+            var x, y, i, j, ii, jj, pathi;
             path = path2curve(path);
             for (i = 0, ii = path.length; i < ii; i++) {
                 pathi = path[i];
     R._Paper = Paper;
     
     R.fn = paperproto = Paper.prototype = R.prototype;
-    
-    paperproto.customAttributes = {};
     R._id = 0;
     R._oid = 0;
     
             return o instanceof Array;
         }
         return  (type == "null" && o === null) ||
-                (type == typeof o) ||
+                (type == typeof o && o !== null) ||
                 (type == "object" && o === Object(o)) ||
                 (type == "array" && Array.isArray && Array.isArray(o)) ||
                 objectToString.call(o).slice(8, -1).toLowerCase() == type;
         return value;
     };
     
-    var createUUID = R._createUUID = (function (uuidRegEx, uuidReplacer) {
+    
+    var createUUID = R.createUUID = (function (uuidRegEx, uuidReplacer) {
         return function () {
             return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(uuidRegEx, uuidReplacer).toUpperCase();
         };
         eve("setWindow", R, g.win, newwin);
         g.win = newwin;
         g.doc = g.win.document;
-        if (initWin) {
-            initWin(g.win);
+        if (R._engine.initWin) {
+            R._engine.initWin(g.win);
         }
     };
     var toHex = function (color) {
                 clr.v = rgb.b;
             } else {
                 clr = {hex: "none"};
-                crl.r = clr.g = clr.b = clr.h = clr.s = clr.v = clr.l = -1;
+                clr.r = clr.g = clr.b = clr.h = clr.s = clr.v = clr.l = -1;
             }
         }
         clr.toString = rgbtoString;
 
     var preload = R._preload = function (src, f) {
         var img = g.doc.createElement("img");
-        img.style.cssText = "position:absolute;left:-9999em;top-9999em";
+        img.style.cssText = "position:absolute;left:-9999em;top:-9999em";
         img.onload = function () {
             f.call(this);
             this.onload = null;
                 red = toInt((t = rgb[3].charAt(1)) + t, 16);
             }
             if (rgb[4]) {
-                values = rgb[4].split(commaSpaces);
+                values = rgb[4][split](commaSpaces);
                 red = toFloat(values[0]);
                 values[0].slice(-1) == "%" && (red *= 2.55);
                 green = toFloat(values[1]);
                 values[3] && values[3].slice(-1) == "%" && (opacity /= 100);
             }
             if (rgb[5]) {
-                values = rgb[5].split(commaSpaces);
+                values = rgb[5][split](commaSpaces);
                 red = toFloat(values[0]);
                 values[0].slice(-1) == "%" && (red *= 2.55);
                 green = toFloat(values[1]);
                 return R.hsb2rgb(red, green, blue, opacity);
             }
             if (rgb[6]) {
-                values = rgb[6].split(commaSpaces);
+                values = rgb[6][split](commaSpaces);
                 red = toFloat(values[0]);
                 values[0].slice(-1) == "%" && (red *= 2.55);
                 green = toFloat(values[1]);
         delete this.start;
     };
 
+    // http://schepers.cc/getting-to-the-point
+    function catmullRom2bezier(crp) {
+        var d = [];
+        for (var i = 0, iLen = crp.length; iLen - 2 > i; i += 2) {
+            var p = [{x: +crp[i],     y: +crp[i + 1]},
+                     {x: +crp[i],     y: +crp[i + 1]},
+                     {x: +crp[i + 2], y: +crp[i + 3]},
+                     {x: +crp[i + 4], y: +crp[i + 5]}];
+            if (iLen - 4 == i) {
+                p[0] = {x: +crp[i - 2], y: +crp[i - 1]};
+                p[3] = p[2];
+            } else if (i) {
+                p[0] = {x: +crp[i - 2], y: +crp[i - 1]};
+            }
+            d.push(["C",
+                (-p[0].x + 6 * p[1].x + p[2].x) / 6,
+                (-p[0].y + 6 * p[1].y + p[2].y) / 6,
+                (p[1].x + 6 * p[2].x - p[3].x) / 6,
+                (p[1].y + 6*p[2].y - p[3].y) / 6,
+                p[2].x,
+                p[2].y
+            ]);
+        }
+
+        return d;
+    }
     
     R.parsePathString = cacher(function (pathString) {
         if (!pathString) {
         if (!data.length) {
             Str(pathString).replace(pathCommand, function (a, b, c) {
                 var params = [],
-                    name = lowerCase.call(b);
+                    name = b.toLowerCase();
                 c.replace(pathValues, function (a, b) {
                     b && params.push(+b);
                 });
                     name = "l";
                     b = b == "m" ? "l" : "L";
                 }
-                while (params.length >= paramCounts[name]) {
+                if (name == "r") {
+                    data.push([b][concat](params));
+                } else while (params.length >= paramCounts[name]) {
                     data.push([b][concat](params.splice(0, paramCounts[name])));
                     if (!paramCounts[name]) {
                         break;
                 c.replace(pathValues, function (a, b) {
                     b && params.push(+b);
                 });
-                data.push([name][concat](params));
+                data.push([b][concat](params));
             });
         }
         data.toString = R._path2string;
             alpha: alpha
         };
     };
+    R._removedFactory = function (methodname) {
+        return function () {
+            throw new Error("Rapha\xebl: you are calling to method \u201c" + methodname + "\u201d of removed object");
+        };
+    };
     var pathDimensions = cacher(function (path) {
         if (!path) {
             return {x: 0, y: 0, width: 0, height: 0};
                 start++;
                 res[0] = ["M", x, y];
             }
-            for (var i = start, ii = pathArray.length; i < ii; i++) {
-                var r = res[i] = [],
-                    pa = pathArray[i];
+            for (var r, pa, i = start, ii = pathArray.length; i < ii; i++) {
+                res.push(r = []);
+                pa = pathArray[i];
                 if (pa[0] != upperCase.call(pa[0])) {
                     r[0] = upperCase.call(pa[0]);
                     switch (r[0]) {
                         case "H":
                             r[1] = +pa[1] + x;
                             break;
+                        case "R":
+                            var dots = [x, y][concat](pa.slice(1));
+                            for (var j = 2, jj = dots.length; j < jj; j++) {
+                                dots[j] = +dots[j] + x;
+                                dots[++j] = +dots[j] + y;
+                            }
+                            res.pop();
+                            res = res[concat](catmullRom2bezier(dots));
+                            break;
                         case "M":
                             mx = +pa[1] + x;
                             my = +pa[2] + y;
                         default:
-                            for (var j = 1, jj = pa.length; j < jj; j++) {
+                            for (j = 1, jj = pa.length; j < jj; j++) {
                                 r[j] = +pa[j] + ((j % 2) ? x : y);
                             }
                     }
+                } else if (pa[0] == "R") {
+                    dots = [x, y][concat](pa.slice(1));
+                    res.pop();
+                    res = res[concat](catmullRom2bezier(dots));
+                    r = ["R"][concat](pa.slice(-2));
                 } else {
                     for (var k = 0, kk = pa.length; k < kk; k++) {
-                        res[i][k] = pa[k];
+                        r[k] = pa[k];
                     }
                 }
                 switch (r[0]) {
                         y = r[1];
                         break;
                     case "M":
-                        mx = res[i][res[i].length - 2];
-                        my = res[i][res[i].length - 1];
+                        mx = r[r.length - 2];
+                        my = r[r.length - 1];
                     default:
-                        x = res[i][res[i].length - 2];
-                        y = res[i][res[i].length - 1];
+                        x = r[r.length - 2];
+                        y = r[r.length - 1];
                 }
             }
             res.toString = R._path2string;
             if (recursive) {
                 return [m2, m3, m4][concat](res);
             } else {
-                res = [m2, m3, m4][concat](res).join().split(",");
+                res = [m2, m3, m4][concat](res).join()[split](",");
                 var newres = [];
                 for (var i = 0, ii = res.length; i < ii; i++) {
                     newres[i] = i % 2 ? rotate(res[i - 1], res[i], rad).y : rotate(res[i], res[i + 1], rad).x;
             el2.prev = el;
             el.next = el2;
         },
-        removed = function (methodname) {
-            return function () {
-                throw new Error("Rapha\xebl: you are calling to method \u201c" + methodname + "\u201d of removed object");
-            };
-        },
         extractTransform = R._extractTransform = function (el, tstr) {
             if (tstr == null) {
                 return el._.transform;
                 for (var i = 0, ii = tdata.length; i < ii; i++) {
                     var t = tdata[i],
                         tlen = t.length,
+                        command = Str(t[0]).toLowerCase(),
+                        absolute = t[0] != command,
+                        inver = absolute ? m.invert() : 0,
+                        x1,
+                        y1,
+                        x2,
+                        y2,
                         bb;
-                    t[0] = Str(t[0]).toLowerCase();
-                    if (t[0] == "t" && tlen == 3) {
-                        m.translate(t[1], t[2]);
-                    } else if (t[0] == "r") {
+                    if (command == "t" && tlen == 3) {
+                        if (absolute) {
+                            x1 = inver.x(0, 0);
+                            y1 = inver.y(0, 0);
+                            x2 = inver.x(t[1], t[2]);
+                            y2 = inver.y(t[1], t[2]);
+                            m.translate(x2 - x1, y2 - y1);
+                        } else {
+                            m.translate(t[1], t[2]);
+                        }
+                    } else if (command == "r") {
                         if (tlen == 2) {
                             bb = bb || el.getBBox(1);
                             m.rotate(t[1], bb.x + bb.width / 2, bb.y + bb.height / 2);
                             deg += t[1];
                         } else if (tlen == 4) {
-                            m.rotate(t[1], t[2], t[3]);
+                            if (absolute) {
+                                x2 = inver.x(t[2], t[3]);
+                                y2 = inver.y(t[2], t[3]);
+                                m.rotate(t[1], x2, y2);
+                            } else {
+                                m.rotate(t[1], t[2], t[3]);
+                            }
                             deg += t[1];
                         }
-                    } else if (t[0] == "s") {
+                    } else if (command == "s") {
                         if (tlen == 2 || tlen == 3) {
                             bb = bb || el.getBBox(1);
                             m.scale(t[1], t[tlen - 1], bb.x + bb.width / 2, bb.y + bb.height / 2);
                             sx *= t[1];
                             sy *= t[tlen - 1];
                         } else if (tlen == 5) {
-                            m.scale(t[1], t[2], t[3], t[4]);
+                            if (absolute) {
+                                x2 = inver.x(t[3], t[4]);
+                                y2 = inver.y(t[3], t[4]);
+                                m.scale(t[1], t[2], x2, y2);
+                            } else {
+                                m.scale(t[1], t[2], t[3], t[4]);
+                            }
                             sx *= t[1];
                             sy *= t[2];
                         }
-                    } else if (t[0] == "m" && tlen == 7) {
+                    } else if (command == "m" && tlen == 7) {
                         m.add(t[1], t[2], t[3], t[4], t[5], t[6]);
                     }
                     _.dirtyT = 1;
             }
         },
         getEmpty = function (item) {
-            switch (item[0]) {
-                case "t": return ["t", 0, 0];
-                case "m": return ["m", 1, 0, 0, 1, 0, 0];
+            var l = item[0];
+            switch (l.toLowerCase()) {
+                case "t": return [l, 0, 0];
+                case "m": return [l, 1, 0, 0, 1, 0, 0];
                 case "r": if (item.length == 4) {
-                    return ["r", 0, item[2], item[3]];
+                    return [l, 0, item[2], item[3]];
                 } else {
-                    return ["r", 0];
+                    return [l, 0];
                 }
                 case "s": if (item.length == 5) {
-                    return ["s", 1, 1, item[3], item[4]];
+                    return [l, 1, 1, item[3], item[4]];
                 } else if (item.length == 3) {
-                    return ["s", 1, 1];
+                    return [l, 1, 1];
                 } else {
-                    return ["s", 1];
+                    return [l, 1];
                 }
             }
         },
                 tt1 = t1[i] || getEmpty(t2[i]);
                 tt2 = t2[i] || getEmpty(tt1);
                 if ((tt1[0] != tt2[0]) ||
-                    (tt1[0] == "r" && (tt1[2] != tt2[2] || tt1[3] != tt2[3])) ||
-                    (tt1[0] == "s" && (tt1[3] != tt2[3] || tt1[4] != tt2[4]))
+                    (tt1[0].toLowerCase() == "r" && (tt1[2] != tt2[2] || tt1[3] != tt2[3])) ||
+                    (tt1[0].toLowerCase() == "s" && (tt1[3] != tt2[3] || tt1[4] != tt2[4]))
                     ) {
                     return;
                 }
             return out;
         };
         
-        matrixproto.toTransformString = function () {
-            var s = this.split();
+        matrixproto.toTransformString = function (shorter) {
+            var s = shorter || this[split]();
             if (s.isSimple) {
-                return "t" + [s.dx, s.dy] + "s" + [s.scalex, s.scaley, 0, 0] + "r" + [s.rotate, 0, 0];
+                s.scalex = +s.scalex.toFixed(4);
+                s.scaley = +s.scaley.toFixed(4);
+                s.rotate = +s.rotate.toFixed(4);
+                return  (s.dx && s.dy ? "t" + [s.dx, s.dy] : E) + 
+                        (s.scalex != 1 || s.scaley != 1 ? "s" + [s.scalex, s.scaley, 0, 0] : E) +
+                        (s.rotate ? "r" + [s.rotate, 0, 0] : E);
             } else {
                 return "m" + [this.get(0), this.get(1), this.get(2), this.get(3), this.get(4), this.get(5)];
             }
     elproto.unhover = function (f_in, f_out) {
         return this.unmouseover(f_in).unmouseout(f_out);
     };
+    var draggable = [];
     
     elproto.drag = function (onmove, onstart, onend, move_scope, start_scope, end_scope) {
         function start(e) {
             eve("drag.start." + this.id, start_scope || move_scope || this, e.clientX + scrollX, e.clientY + scrollY, e);
         }
         this._drag = {};
+        draggable.push({el: this, start: start});
         this.mousedown(start);
         return this;
     };
     };
     
     elproto.undrag = function () {
-        var i = drag.length;
-        while (i--) if (drag[i].el == this) {
-            R.unmousedown(drag[i].start);
-            drag.splice(i++, 1);
+        var i = draggable.length;
+        while (i--) if (draggable[i].el == this) {
+            this.unmousedown(draggable[i].start);
+            draggable.splice(i, 1);
             eve.unbind("drag.*." + this.id);
         }
-        !drag.length && R.unmousemove(dragMove).unmouseup(dragUp);
+        !draggable.length && R.unmousemove(dragMove).unmouseup(dragUp);
     };
     
     paperproto.circle = function (x, y, r) {
         if (this.removed) {
             return null;
         }
-        return this.paper[this.type]().attr(this.attr());
+        var out = this.paper[this.type]().attr(this.attr());
+        this.__set__ && this.__set__.push(out);
+        return out;
     };
     
     elproto.glow = function (glow) {
                 sp += p.shift() + p;
             }
             subpaths.end = sp;
-            point = istotal ? len : subpath ? subpaths : R.findDotsAtSegment(x, y, p[1], p[2], p[3], p[4], p[5], p[6], 1);
+            point = istotal ? len : subpath ? subpaths : R.findDotsAtSegment(x, y, p[0], p[1], p[2], p[3], p[4], p[5], 1);
             point.alpha && (point = {x: point.x, y: point.y, alpha: point.alpha});
             return point;
         };
     R.getPointAtLength = getPointAtLength;
     
     R.getSubpath = function (path, from, to) {
-        if (abs(this.getTotalLength(path) - to) < 1e-6) {
+        if (this.getTotalLength(path) - to < 1e-6) {
             return getSubpathsAtLength(path, from).end;
         }
         var a = getSubpathsAtLength(path, to, 1);
                     t = e.t,
                     that = e.el,
                     set = {},
-                    now;
+                    now,
+                    init = {},
+                    key;
                 if (e.initstatus) {
                     time = (e.initstatus * e.anim.top - e.prev) / (e.percent - e.prev) * ms;
                     e.status = e.initstatus;
                                 }
                                 break;
                             default:
-                                var from2 = [].concat(from[attr]);
+                                var from2 = [][concat](from[attr]);
                                 now = [];
                                 i = that.paper.customAttributes[attr].length;
                                 while (i--) {
                     that.attr(to);
                     animationElements.splice(l--, 1);
                     if (e.repeat > 1 && !e.next) {
+                        for (key in to) if (to[has](key)) {
+                            init[key] = e.totalOrigin[key];
+                        }
+                        e.el.attr(init);
                         runAnimation(e.anim, e.el, e.anim.percents[0], null, e.totalOrigin, e.repeat - 1);
                     }
                     if (e.next && !e.stop) {
         };
     
     elproto.animateWith = function (element, anim, params, ms, easing, callback) {
-        var a = params ? R.animation(params, ms, easing, callback) : anim;
+        var a = params ? R.animation(params, ms, easing, callback) : anim,
             status = element.status(anim);
         return this.animate(a).status(a, status * anim.ms / a.ms);
     };
         return this;
     };
     function Animation(anim, ms) {
-        var percents = [];
-        this.anim = anim;
+        var percents = [],
+            newAnim = {};
         this.ms = ms;
         this.times = 1;
-        if (this.anim) {
-            for (var attr in this.anim) if (this.anim[has](attr)) {
-                percents.push(+attr);
+        if (anim) {
+            for (var attr in anim) if (anim[has](attr)) {
+                newAnim[toFloat(attr)] = anim[attr];
+                percents.push(toFloat(attr));
             }
             percents.sort(sortByNumber);
         }
+        this.anim = newAnim;
         this.top = percents[percents.length - 1];
         this.percents = percents;
     }
             return;
         }
         if (!isInAnim) {
-            for (attr in params) if (params[has](attr)) {
+            for (var attr in params) if (params[has](attr)) {
                 if (availableAnimAttrs[has](attr) || element.paper.customAttributes[has](attr)) {
                     from[attr] = element.attr(attr);
                     (from[attr] == null) && (from[attr] = availableAttrs[attr]);
                             }
                             break;
                         case "csv":
-                            var values = Str(params[attr]).split(separator),
-                                from2 = Str(from[attr]).split(separator);
+                            var values = Str(params[attr])[split](separator),
+                                from2 = Str(from[attr])[split](separator);
                             if (attr == "clip-rect") {
                                 from[attr] = from2;
                                 diff[attr] = [];
                             to[attr] = values;
                             break;
                         default:
-                            values = [].concat(params[attr]);
-                            from2 = [].concat(from[attr]);
+                            values = [][concat](params[attr]);
+                            from2 = [][concat](from[attr]);
                             diff[attr] = [];
                             i = element.paper.customAttributes[attr].length;
                             while (i--) {
         var p = {},
             json,
             attr;
-        for (attr in params) if (params[has](attr) && toFloat(attr) != attr) {
+        for (attr in params) if (params[has](attr) && toFloat(attr) != attr && toFloat(attr) + "%" != attr) {
             json = true;
             p[attr] = params[attr];
         }
     
     setproto.forEach = function (callback, thisArg) {
         for (var i = 0, ii = this.items.length; i < ii; i++) {
-            if (callback.call(thisArg, this.items[i]) === false) {
+            if (callback.call(thisArg, this.items[i], i) === false) {
                 return this;
             }
         }
     };
     
     setproto.exclude = function (el) {
-        for (var i = 0, ii = this.length, found; i < ii; i++) if (found || this[i] == el) {
-            this[i] = this[i + 1];
-            found = 1;
-        }
-        if (found) {
-            this.length--;
-            delete this[i];
+        for (var i = 0, ii = this.length; i < ii; i++) if (this[i] == el) {
+            this.splice(i, 1);
             return true;
         }
     };
         origin = origin || "middle"; // baseline|middle
         letter_spacing = mmax(mmin(letter_spacing || 0, 1), -1);
         var out = this.set(),
-            letters = Str(string).split(E),
+            letters = Str(string)[split](E),
             shift = 0,
             path = E,
             scale;
         R.is(font, string) && (font = this.getFont(font));
         if (font) {
             scale = (size || 16) / font.face["units-per-em"];
-            var bb = font.face.bbox.split(separator),
+            var bb = font.face.bbox[split](separator),
                 top = +bb[0],
                 height = +bb[1] + (origin == "baseline" ? bb[3] - bb[1] + (+font.face.descent) : (bb[3] - bb[1]) / 2);
             for (var i = 0, ii = letters.length; i < ii; i++) {
     };
 
     
+    paperproto.add = function (json) {
+        if (R.is(json, "array")) {
+            var res = this.set(),
+                i = 0,
+                ii = json.length,
+                j;
+            for (; i < ii; i++) {
+                j = json[i] || {};
+                elements[has](j.type) && res.push(this[j.type]().attr(j));
+            }
+        }
+        return res;
+    };
+
+    
     R.format = function (token, params) {
         var args = R.is(params, array) ? [0][concat](params) : arguments;
         token && R.is(token, string) && args.length - 1 && (token = token.replace(formatrg, function (str, i) {
     });
 })();
 
+
 // ┌─────────────────────────────────────────────────────────────────────┐ \\
-// │ Raphaël 2 - JavaScript Vector Library                               │ \\
+// │ Raphaël - JavaScript Vector Library                                 │ \\
 // ├─────────────────────────────────────────────────────────────────────┤ \\
 // │ SVG Module                                                          │ \\
 // ├─────────────────────────────────────────────────────────────────────┤ \\
@@ -3342,16 +3447,6 @@ window.Raphael.svg && function (R) {
         }
         return el;
     },
-    gradients = {},
-    rgGrad = /^url\(#(.*)\)$/,
-    removeGradientFill = function (node, paper) {
-        var oid = node.getAttribute("fill");
-        oid = oid && oid.match(rgGrad);
-        if (oid && !--gradients[oid[1]]) {
-            delete gradients[oid[1]];
-            paper.defs.removeChild(R._g.doc.getElementById(oid[1]));
-        }
-    },
     addGradientFill = function (element, gradient) {
         var type = "linear",
             id = element.id + gradient,
@@ -3398,30 +3493,33 @@ window.Raphael.svg && function (R) {
             if (!dots) {
                 return null;
             }
-            if (element.gradient) {
+            id = id.replace(/[\(\)\s,\xb0#]/g, "_");
+            
+            if (element.gradient && id != element.gradient.id) {
                 SVG.defs.removeChild(element.gradient);
                 delete element.gradient;
             }
 
-            id = id.replace(/[\(\)\s,\xb0#]/g, "-");
-            el = $(type + "Gradient", {id: id});
-            element.gradient = el;
-            $(el, type == "radial" ? {
-                fx: fx,
-                fy: fy
-            } : {
-                x1: vector[0],
-                y1: vector[1],
-                x2: vector[2],
-                y2: vector[3],
-                gradientTransform: element.matrix.invert()
-            });
-            SVG.defs.appendChild(el);
-            for (var i = 0, ii = dots.length; i < ii; i++) {
-                el.appendChild($("stop", {
-                    offset: dots[i].offset ? dots[i].offset : i ? "100%" : "0%",
-                    "stop-color": dots[i].color || "#fff"
-                }));
+            if (!element.gradient) {
+                el = $(type + "Gradient", {id: id});
+                element.gradient = el;
+                $(el, type == "radial" ? {
+                    fx: fx,
+                    fy: fy
+                } : {
+                    x1: vector[0],
+                    y1: vector[1],
+                    x2: vector[2],
+                    y2: vector[3],
+                    gradientTransform: element.matrix.invert()
+                });
+                SVG.defs.appendChild(el);
+                for (var i = 0, ii = dots.length; i < ii; i++) {
+                    el.appendChild($("stop", {
+                        offset: dots[i].offset ? dots[i].offset : i ? "100%" : "0%",
+                        "stop-color": dots[i].color || "#fff"
+                    }));
+                }
             }
         }
         $(o, {
@@ -3654,7 +3752,7 @@ window.Raphael.svg && function (R) {
                             o.clip && o.clip.parentNode.parentNode.removeChild(o.clip.parentNode);
                             var el = $("clipPath"),
                                 rc = $("rect");
-                            el.id = R._createUUID();
+                            el.id = R.createUUID();
                             $(rc, {
                                 x: rect[0],
                                 y: rect[1],
@@ -3667,10 +3765,13 @@ window.Raphael.svg && function (R) {
                             o.clip = rc;
                         }
                         if (!value) {
-                            var clip = R._g.doc.getElementById(node.getAttribute("clip-path").replace(/(^url\(#|\)$)/g, E));
-                            clip && clip.parentNode.removeChild(clip);
-                            $(node, {"clip-path": E});
-                            delete o.clip;
+                            var path = node.getAttribute("clip-path");
+                            if (path) {
+                                var clip = R._g.doc.getElementById(path.replace(/(^url\(#|\)$)/g, E));
+                                clip && clip.parentNode.removeChild(clip);
+                                $(node, {"clip-path": E});
+                                delete o.clip;
+                            }
                         }
                     break;
                     case "path":
@@ -3764,7 +3865,7 @@ window.Raphael.svg && function (R) {
                         if (isURL) {
                             el = $("pattern");
                             var ig = $("image");
-                            el.id = R._createUUID();
+                            el.id = R.createUUID();
                             $(el, {x: 0, y: 0, patternUnits: "userSpaceOnUse", height: 1, width: 1});
                             $(ig, {x: 0, y: 0, "xlink:href": isURL[1]});
                             el.appendChild(ig);
@@ -4027,11 +4128,16 @@ window.Raphael.svg && function (R) {
         if (this.removed) {
             return;
         }
+        var paper = this.paper;
+        paper.__set__ && paper.__set__.exclude(this);
         eve.unbind("*.*." + this.id);
-        R._tear(this, this.paper);
+        if (this.gradient) {
+            paper.defs.removeChild(this.gradient);
+        }
+        R._tear(this, paper);
         this.node.parentNode.removeChild(this.node);
         for (var i in this) {
-            delete this[i];
+            this[i] = typeof this[i] == "function" ? R._removedFactory(i) : null;
         }
         this.removed = true;
     };
@@ -4099,7 +4205,10 @@ window.Raphael.svg && function (R) {
         } else if (name != null && R.is(name, "object")) {
             params = name;
         }
-        for (var key in this.paper.customAttributes) if (this.paper.customAttributes[has](key) && params[has](key) && R.is(this.paper.customAttributes[key], "function")) {
+        for (var key in params) {
+            eve("attr." + key + "." + this.id, this, params[key]);
+        }
+        for (key in this.paper.customAttributes) if (this.paper.customAttributes[has](key) && params[has](key) && R.is(this.paper.customAttributes[key], "function")) {
             var par = this.paper.customAttributes[key].apply(this, [].concat(params[key]));
             this.attrs[key] = params[key];
             for (var subkey in par) if (par[has](subkey)) {
@@ -4114,7 +4223,11 @@ window.Raphael.svg && function (R) {
         if (this.removed) {
             return this;
         }
-        this.node.parentNode.appendChild(this.node);
+        if (this.node.parentNode.tagName.toLowerCase() == "a") {
+            this.node.parentNode.parentNode.appendChild(this.node.parentNode);
+        } else {
+            this.node.parentNode.appendChild(this.node);
+        }
         var svg = this.paper;
         svg.top != this && R._tofront(this, svg);
         return this;
@@ -4124,11 +4237,14 @@ window.Raphael.svg && function (R) {
         if (this.removed) {
             return this;
         }
-        if (this.node.parentNode.firstChild != this.node) {
-            this.node.parentNode.insertBefore(this.node, this.node.parentNode.firstChild);
-            R._toback(this, this.paper);
-            var svg = this.paper;
+        var parent = this.node.parentNode;
+        if (parent.tagName.toLowerCase() == "a") {
+            parent.parentNode.insertBefore(this.node.parentNode, this.node.parentNode.parentNode.firstChild); 
+        } else if (parent.firstChild != this.node) {
+            parent.insertBefore(this.node, this.node.parentNode.firstChild);
         }
+        R._toback(this, this.paper);
+        var svg = this.paper;
         return this;
     };
     
@@ -4162,7 +4278,7 @@ window.Raphael.svg && function (R) {
             var fltr = $("filter"),
                 blur = $("feGaussianBlur");
             t.attrs.blur = size;
-            fltr.id = R._createUUID();
+            fltr.id = R.createUUID();
             $(blur, {stdDeviation: +size || 1.5});
             fltr.appendChild(blur);
             t.paper.defs.appendChild(fltr);
@@ -4323,7 +4439,7 @@ window.Raphael.svg && function (R) {
     R.prototype.renderfix = function () {
         var cnvs = this.canvas,
             s = cnvs.style,
-            pos = cnvs.getScreenCTM(),
+            pos = cnvs.getScreenCTM() || cnvs.createSVGMatrix(),
             left = -pos.e % 1,
             top = -pos.f % 1;
         if (left || top) {
@@ -4354,7 +4470,7 @@ window.Raphael.svg && function (R) {
         eve("remove", this);
         this.canvas.parentNode && this.canvas.parentNode.removeChild(this.canvas);
         for (var i in this) {
-            this[i] = removed(i);
+            this[i] = typeof this[i] == "function" ? R._removedFactory(i) : null;
         }
     };
     var setproto = R.st;
@@ -4371,7 +4487,7 @@ window.Raphael.svg && function (R) {
 }(window.Raphael);
 
 // ┌─────────────────────────────────────────────────────────────────────┐ \\
-// │ Raphaël 2 - JavaScript Vector Library                               │ \\
+// │ Raphaël - JavaScript Vector Library                                 │ \\
 // ├─────────────────────────────────────────────────────────────────────┤ \\
 // │ VML Module                                                          │ \\
 // ├─────────────────────────────────────────────────────────────────────┤ \\
@@ -4391,16 +4507,16 @@ window.Raphael.vml && function (R) {
         fillString = "fill",
         separator = /[, ]+/,
         eve = R.eve,
+        ms = " progid:DXImageTransform.Microsoft",
         S = " ",
-        E = "";
-    // VML
-    var map = {M: "m", L: "l", C: "c", Z: "x", m: "t", l: "r", c: "v", z: "x"},
+        E = "",
+        map = {M: "m", L: "l", C: "c", Z: "x", m: "t", l: "r", c: "v", z: "x"},
         bites = /([clmz]),?([^clmz]*)/gi,
         blurregexp = / progid:\S+Blur\([^\)]+\)/g,
         val = /-?[^,\s-]+/g,
         cssDot = "position:absolute;left:0;top:0;width:1px;height:1px",
         zoom = 21600,
-        pathTypes = {path: 1, rect: 1},
+        pathTypes = {path: 1, rect: 1, image: 1},
         ovalTypes = {circle: 1, ellipse: 1},
         path2vml = function (path) {
             var total =  /[ahqstv]/ig,
@@ -4488,7 +4604,7 @@ window.Raphael.vml && function (R) {
     R.toString = function () {
         return  "Your browser doesn\u2019t support SVG. Falling down to VML.\nYou are running Rapha\xebl " + this.version;
     };
-    addArrow = function (o, value, isEnd) {
+    var addArrow = function (o, value, isEnd) {
         var values = Str(value).toLowerCase().split("-"),
             se = isEnd ? "end" : "start",
             i = values.length,
@@ -4515,7 +4631,7 @@ window.Raphael.vml && function (R) {
         stroke[se + "arrow"] = type;
         stroke[se + "arrowlength"] = w;
         stroke[se + "arrowwidth"] = h;
-    };
+    },
     setFillAndStroke = function (o, params) {
         // o.paper.canvas.style.display = "none";
         o.attrs = o.attrs || {};
@@ -4540,10 +4656,15 @@ window.Raphael.vml && function (R) {
         params.target && (node.target = params.target);
         params.cursor && (s.cursor = params.cursor);
         "blur" in params && o.blur(params.blur);
-        "transform" in params && o.transform(params.transform);
         if (params.path && o.type == "path" || newpath) {
-            node.path = path2vml(a.path);
+            node.path = path2vml(~Str(a.path).toLowerCase().indexOf("r") ? R._pathToAbsolute(a.path) : a.path);
+            if (o.type == "image") {
+                o._.fillpos = [a.x, a.y];
+                o._.fillsize = [a.width, a.height];
+                setCoords(o, 1, 1, 0, 0, 0);
+            }
         }
+        "transform" in params && o.transform(params.transform);
         if (isOval) {
             var cx = +a.cx,
                 cy = +a.cy,
@@ -4557,8 +4678,7 @@ window.Raphael.vml && function (R) {
                 rect[2] = +rect[2] + (+rect[0]);
                 rect[3] = +rect[3] + (+rect[1]);
                 var div = node.clipRect || R._g.doc.createElement("div"),
-                    dstyle = div.style,
-                    group = node.parentNode;
+                    dstyle = div.style;
                 dstyle.clip = R.format("rect({1}px {2}px {3}px {0}px)", rect);
                 if (!node.clipRect) {
                     dstyle.position = "absolute";
@@ -4566,13 +4686,13 @@ window.Raphael.vml && function (R) {
                     dstyle.left = 0;
                     dstyle.width = o.paper.width + "px";
                     dstyle.height = o.paper.height + "px";
-                    group.parentNode.insertBefore(div, group);
-                    div.appendChild(group);
+                    node.parentNode.insertBefore(div, node);
+                    div.appendChild(node);
                     node.clipRect = div;
                 }
             }
             if (!params["clip-rect"]) {
-                node.clipRect && (node.clipRect.style.clip = E);
+                node.clipRect && (node.clipRect.style.clip = "auto");
             }
         }
         if (o.textpath) {
@@ -4699,7 +4819,7 @@ window.Raphael.vml && function (R) {
             a["font-family"] && (s.fontFamily = a["font-family"]);
             a["font-weight"] && (s.fontWeight = a["font-weight"]);
             a["font-style"] && (s.fontStyle = a["font-style"]);
-            fontSize = toFloat(fontSize ? fontSize[0] : a["font-size"]);
+            fontSize = toFloat(a["font-size"] || fontSize && fontSize[0]) || 10;
             s.fontSize = fontSize * m + "px";
             res.textpath.string && (span.innerHTML = Str(res.textpath.string).replace(/</g, "&#60;").replace(/&/g, "&#38;").replace(/\n/g, "<br>"));
             var brect = span.getBoundingClientRect();
@@ -4734,10 +4854,11 @@ window.Raphael.vml && function (R) {
             res.textpath.style["v-text-kern"] = true;
         }
         // res.paper.canvas.style.display = E;
-    };
+    },
     addGradientFill = function (o, gradient, fill) {
         o.attrs = o.attrs || {};
         var attrs = o.attrs,
+            pow = Math.pow,
             opacity,
             oindex,
             type = "linear",
@@ -4791,7 +4912,7 @@ window.Raphael.vml && function (R) {
             o.appendChild(fill);
         }
         return 1;
-    };
+    },
     Element = function (node, vml) {
         this[0] = this.node = node;
         node.raphael = true;
@@ -4941,27 +5062,24 @@ window.Raphael.vml && function (R) {
         if (this.removed) {
             return {};
         }
-        if (this.type == "text") {
-            return {
-                x: this.X + (this.bbx || 0) - this.W / 2,
-                y: this.Y - this.H,
-                width: this.W,
-                height: this.H
-            };
-        } else {
-            return pathDimensions(this.attrs.path);
-        }
+        return {
+            x: this.X + (this.bbx || 0) - this.W / 2,
+            y: this.Y - this.H,
+            width: this.W,
+            height: this.H
+        };
     };
     elproto.remove = function () {
         if (this.removed) {
             return;
         }
+        this.paper.__set__ && this.paper.__set__.exclude(this);
         R.eve.unbind("*.*." + this.id);
         R._tear(this, this.paper);
         this.node.parentNode.removeChild(this.node);
         this.shape && this.shape.parentNode.removeChild(this.shape);
         for (var i in this) {
-            delete this[i];
+            this[i] = typeof this[i] == "function" ? R._removedFactory(i) : null;
         }
         this.removed = true;
     };
@@ -5010,11 +5128,11 @@ window.Raphael.vml && function (R) {
         }
         value == null && R.is(name, "object") && (params = name);
         for (var key in params) {
-            R.eve("attr." + key + "." + this.id, this, params[key]);
+            eve("attr." + key + "." + this.id, this, params[key]);
         }
         if (params) {
             for (key in this.paper.customAttributes) if (this.paper.customAttributes[has](key) && params[has](key) && R.is(this.paper.customAttributes[key], "function")) {
-                var par = this.paper.customAttributes[key].apply(this, [][concat](params[key]));
+                var par = this.paper.customAttributes[key].apply(this, [].concat(params[key]));
                 this.attrs[key] = params[key];
                 for (var subkey in par) if (par[has](subkey)) {
                     params[subkey] = par[subkey];
@@ -5182,7 +5300,7 @@ window.Raphael.vml && function (R) {
         path.textpathok = true;
         o.string = Str(text);
         o.on = true;
-        el.style.cssText = "position:absolute;left:0;top:0;width:1px;height:1px";
+        el.style.cssText = cssDot;
         el.coordsize = zoom + S + zoom;
         el.coordorigin = "0 0";
         var p = new Element(el, vml),
@@ -5222,7 +5340,7 @@ window.Raphael.vml && function (R) {
         cs.height = height;
         cs.clip = "rect(0 " + width + " " + height + " 0)";
         if (this._viewBox) {
-            setViewBox.apply(this, this._viewBox);
+            R._engine.setViewBox.apply(this, this._viewBox);
         }
         return this;
     };
@@ -5253,8 +5371,8 @@ window.Raphael.vml && function (R) {
         });
         return this;
     };
-    var createNode,
-        initWin = function (win) {
+    var createNode;
+    R._engine.initWin = function (win) {
             var doc = win.document;
             doc.createStyleSheet().addRule(".rvml", "behavior:url(#default#VML)");
             try {
@@ -5268,7 +5386,7 @@ window.Raphael.vml && function (R) {
                 };
             }
         };
-    initWin(R._g.win);
+    R._engine.initWin(R._g.win);
     R._engine.create = function () {
         var con = R._getContainer.apply(0, arguments),
             container = con.container,
@@ -5325,8 +5443,20 @@ window.Raphael.vml && function (R) {
         R.eve("remove", this);
         this.canvas.parentNode.removeChild(this.canvas);
         for (var i in this) {
-            this[i] = removed(i);
+            this[i] = typeof this[i] == "function" ? R._removedFactory(i) : null;
         }
         return true;
     };
+
+    var setproto = R.st;
+    for (var method in elproto) if (elproto[has](method) && !setproto[has](method)) {
+        setproto[method] = (function (methodname) {
+            return function () {
+                var arg = arguments;
+                return this.forEach(function (el) {
+                    el[methodname].apply(el, arg);
+                });
+            };
+        })(method);
+    }
 }(window.Raphael);
\ No newline at end of file