Merge branch 'cuttingedge' of git@github.com:DmitryBaranovskiy/raphael into cuttingedge
[raphael] / raphael.js
index 748185f..cf41ab0 100644 (file)
@@ -1,18 +1,19 @@
 /*!
- * Raphael 1.3.0dev - JavaScript Vector Library
+ * Raphael 1.3.2 - JavaScript Vector Library
  *
- * Copyright (c) 2008 - 2009 Dmitry Baranovskiy (http://raphaeljs.com)
+ * Copyright (c) 2009 Dmitry Baranovskiy (http://raphaeljs.com)
  * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
  */
-
-
-window.Raphael = (function () {
+Raphael = (function () {
     var separator = /[, ]+/,
         elements = /^(circle|rect|path|ellipse|text|image)$/,
+        has = "hasOwnProperty",
         doc = document,
         win = window,
         oldRaphael = {
-            was: "Raphael" in win,
+            was: win[has]("Raphael"),
             is: win.Raphael
         },
         R = function () {
@@ -36,7 +37,6 @@ window.Raphael = (function () {
         S = " ",
         split = "split",
         events = "click dblclick mousedown mousemove mouseout mouseover mouseup"[split](S),
-        has = "hasOwnProperty",
         join = "join",
         length = "length",
         proto = "prototype",
@@ -44,7 +44,6 @@ window.Raphael = (function () {
         math = Math,
         mmax = math.max,
         mmin = math.min,
-        abs = math.abs,
         nu = "number",
         toString = "toString",
         objectToString = Object[proto][toString],
@@ -59,10 +58,10 @@ window.Raphael = (function () {
         toFloat = parseFloat,
         toInt = parseInt,
         upperCase = String[proto].toUpperCase,
-        availableAttrs = {"clip-rect": "0 0 10e9 10e9", cursor: "default", 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},
-        availableAnimAttrs = {"clip-rect": "csv", cx: nu, cy: nu, fill: "colour", "fill-opacity": nu, "font-size": nu, height: nu, opacity: nu, path: "path", r: nu, rotation: "csv", rx: nu, ry: nu, scale: "csv", stroke: "colour", "stroke-opacity": nu, "stroke-width": nu, translation: "csv", width: nu, x: nu, y: nu},
+        availableAttrs = {"clip-rect": "0 0 1e9 1e9", cursor: "default", 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},
+        availableAnimAttrs = {along: "along", "clip-rect": "csv", cx: nu, cy: nu, fill: "colour", "fill-opacity": nu, "font-size": nu, height: nu, opacity: nu, path: "path", r: nu, rotation: "csv", rx: nu, ry: nu, scale: "csv", stroke: "colour", "stroke-opacity": nu, "stroke-width": nu, translation: "csv", width: nu, x: nu, y: nu},
         rp = "replace";
-    R.version = "1.3.0dev";
+    R.version = "1.3.2";
     R.type = (win.SVGAngle || doc.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1") ? "SVG" : "VML");
     if (R.type == "VML") {
         var d = document.createElement("div");
@@ -220,7 +219,7 @@ window.Raphael = (function () {
         }
         return newf;
     }
-
     R.getRGB = cacher(function (colour) {
         if (!colour || !!((colour = colour + E).indexOf("-") + 1)) {
             return {r: -1, g: -1, b: -1, hex: "none", error: 1};
@@ -299,6 +298,8 @@ window.Raphael = (function () {
         delete this.start;
     };
     // path utilities
+    var pathCommand = /([achlmqstvz])[\s,]*((-?\d*\.?\d*(?:e[-+]?\d+)?\s*,?\s*)+)/ig,
+        pathValues = /(-?\d*\.?\d*(?:e[-+]?\d+)?)\s*,?\s*/ig;
     R.parsePathString = cacher(function (pathString) {
         if (!pathString) {
             return null;
@@ -309,12 +310,17 @@ window.Raphael = (function () {
             data = pathClone(pathString);
         }
         if (!data[length]) {
-            (pathString + E)[rp](/([achlmqstvz])[\s,]*((-?\d*\.?\d*(?:e[-+]?\d+)?\s*,?\s*)+)/ig, function (a, b, c) {
+            (pathString + E)[rp](pathCommand, function (a, b, c) {
                 var params = [],
                     name = lowerCase.call(b);
-                c[rp](/(-?\d*\.?\d*(?:e[-+]?\d+)?)\s*,?\s*/ig, function (a, b) {
+                c[rp](pathValues, function (a, b) {
                     b && params[push](+b);
                 });
+                if (name == "m" && params[length] > 2) {
+                    data[push]([b][concat](params.splice(0, 2)));
+                    name = "l";
+                    b = b == "m" ? "l" : "L";
+                }
                 while (params[length] >= paramCounts[name]) {
                     data[push]([b][concat](params.splice(0, paramCounts[name])));
                     if (!paramCounts[name]) {
@@ -326,7 +332,7 @@ window.Raphael = (function () {
         data[toString] = R._path2string;
         return data;
     });
-    R.findDotsAtSegment = cacher(function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
+    R.findDotsAtSegment = function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
         var t1 = 1 - t,
             x = pow(t1, 3) * p1x + pow(t1, 2) * 3 * t * c1x + t1 * 3 * t * t * c2x + pow(t, 3) * p2x,
             y = pow(t1, 3) * p1y + pow(t1, 2) * 3 * t * c1y + t1 * 3 * t * t * c2y + pow(t, 3) * p2y,
@@ -337,9 +343,11 @@ window.Raphael = (function () {
             ax = (1 - t) * p1x + t * c1x,
             ay = (1 - t) * p1y + t * c1y,
             cx = (1 - t) * c2x + t * p2x,
-            cy = (1 - t) * c2y + t * p2y;
-        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}};
-    });
+            cy = (1 - t) * c2y + t * p2y,
+            alpha = (90 - math.atan((mx - nx) / (my - ny)) * 180 / math.PI);
+        (mx > nx || my < ny) && (alpha += 180);
+        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}, alpha: alpha};
+    };
     var pathDimensions = cacher(function (path) {
         if (!path) {
             return {x: 0, y: 0, width: 0, height: 0};
@@ -572,12 +580,18 @@ window.Raphael = (function () {
                     sin = math.sin(PI / 180 * angle),
                     x = (x1 - x2) / 2,
                     y = (y1 - y2) / 2;
-                rx = mmax(rx, abs(x));
-                ry = mmax(ry, abs(y));
+                // rx = mmax(rx, math.abs(x));
+                // ry = mmax(ry, math.abs(y));
+                var h = (x * x) / (rx * rx) + (y * y) / (ry * ry);
+                if (h > 1) {
+                    h = math.sqrt(h);
+                    rx = h * rx;
+                    ry = h * ry;
+                }
                 var rx2 = rx * rx,
                     ry2 = ry * ry,
                     k = (large_arc_flag == sweep_flag ? -1 : 1) *
-                        math.sqrt(abs((rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x))),
+                        math.sqrt(math.abs((rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x))),
                     cx = k * rx * y / ry + (x1 + x2) / 2,
                     cy = k * -ry * x / rx + (y1 + y2) / 2,
                     f1 = math.asin(((y1 - cy) / ry).toFixed(7)),
@@ -600,7 +614,7 @@ window.Raphael = (function () {
                 cy = recursive[3];
             }
             var df = f2 - f1;
-            if (abs(df) > _120) {
+            if (math.abs(df) > _120) {
                 var f2old = f2,
                     x2old = x2,
                     y2old = y2;
@@ -631,6 +645,7 @@ window.Raphael = (function () {
                 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;
                 }
+                // alert(newres);
                 return newres;
             }
         },
@@ -650,8 +665,8 @@ window.Raphael = (function () {
                 y = [p1y, p2y],
                 x = [p1x, p2x],
                 dot;
-            abs(t1) > 1e12 && (t1 = .5);
-            abs(t2) > 1e12 && (t2 = .5);
+            math.abs(t1) > 1e12 && (t1 = .5);
+            math.abs(t2) > 1e12 && (t2 = .5);
             if (t1 > 0 && t1 < 1) {
                 dot = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1);
                 x[push](dot.x);
@@ -667,8 +682,8 @@ window.Raphael = (function () {
             c = p1y - c1y;
             t1 = (-b + math.sqrt(b * b - 4 * a * c)) / 2 / a;
             t2 = (-b - math.sqrt(b * b - 4 * a * c)) / 2 / a;
-            abs(t1) > 1e12 && (t1 = .5);
-            abs(t2) > 1e12 && (t2 = .5);
+            math.abs(t1) > 1e12 && (t1 = .5);
+            math.abs(t2) > 1e12 && (t2 = .5);
             if (t1 > 0 && t1 < 1) {
                 dot = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1);
                 x[push](dot.x);
@@ -907,7 +922,7 @@ window.Raphael = (function () {
             };
         },
         radial_gradient = /^r(?:\(([^,]+?)\s*,\s*([^\)]+?)\))?/;
-
     // SVG
     if (R.svg) {
         Paper[proto].svgns = "http://www.w3.org/2000/svg";
@@ -973,7 +988,7 @@ window.Raphael = (function () {
                     return null;
                 }
                 var vector = [0, 0, math.cos(angle * math.PI / 180), math.sin(angle * math.PI / 180)],
-                    max = 1 / (mmax(abs(vector[2]), abs(vector[3])) || 1);
+                    max = 1 / (mmax(math.abs(vector[2]), math.abs(vector[3])) || 1);
                 vector[2] *= max;
                 vector[3] *= max;
                 if (vector[2] < 0) {
@@ -1076,7 +1091,7 @@ window.Raphael = (function () {
                             hl[appendChild](node);
                             pn = hl;
                         }
-                        pn.setAttributeNS(o.Paper[proto].xlink, att, value);
+                        pn.setAttributeNS(o.paper.xlink, att, value);
                         break;
                     case "cursor":
                         node.style.cursor = value;
@@ -1107,9 +1122,8 @@ window.Raphael = (function () {
                         }
                     break;
                     case "path":
-                        if (value && o.type == "path") {
-                            attrs.path = roundPath(pathToAbsolute(value));
-                            $(node, {d: attrs.path});
+                        if (o.type == "path") {
+                            $(node, {d: value ? attrs.path = roundPath(pathToAbsolute(value)) : "M0,0"});
                         }
                         break;
                     case "width":
@@ -1201,7 +1215,7 @@ window.Raphael = (function () {
                             $(ig, {x: 0, y: 0});
                             ig.setAttributeNS(o.paper.xlink, "href", isURL[1]);
                             el[appendChild](ig);
-
                             var img = doc.createElement("img");
                             img.style.cssText = "position:absolute;left:-9999em;top-9999em";
                             img.onload = function () {
@@ -1276,7 +1290,7 @@ window.Raphael = (function () {
             var a = el.attrs,
                 node = el.node,
                 fontSize = node.firstChild ? toInt(doc.defaultView.getComputedStyle(node.firstChild, E).getPropertyValue("font-size"), 10) : 10;
-
             if (params[has]("text")) {
                 a.text = params.text;
                 while (node.firstChild) {
@@ -1479,8 +1493,7 @@ window.Raphael = (function () {
             if (this.removed) {
                 return this;
             }
-            var svg = this.paper,
-                node = element.node;
+            var node = element.node;
             if (node.nextSibling) {
                 node.parentNode.insertBefore(this.node, node.nextSibling);
             } else {
@@ -1498,19 +1511,6 @@ window.Raphael = (function () {
             insertbefore(this, element, this.paper);
             return this;
         };
-        Element[proto].getTotalLength = function () {
-            if (this.type != "path") {
-                return -1;
-            }
-            return this.node.getTotalLength();
-        };
-        Element[proto].getPointAtLength = function (length) {
-            if (this.type != "path") {
-                return -1;
-            }
-            var point = this.node.getPointAtLength(length);
-            return {x: point.x, y: point.y};
-        };
         var theCircle = function (svg, x, y, r) {
             x = round(x);
             y = round(y);
@@ -1625,49 +1625,45 @@ window.Raphael = (function () {
             }
         };
     }
-
     // VML
     if (R.vml) {
-        var path2vml = function (path) {
-            var total =  /[ahqtv]/ig,
-                command = pathToAbsolute;
-            (path + E).match(total) && (command = path2curve);
-            total =  /[clmz]/g;
-            if (command == pathToAbsolute && !(path + E).match(total)) {
-                var map = {M: "m", L: "l", C: "c", Z: "x", m: "t", l: "r", c: "v", z: "x"},
-                    bites = /([clmz]),?([^clmz]*)/gi,
-                    val = /-?[^,\s-]+/g;
-                var res = (path + E)[rp](bites, function (all, command, args) {
-                    var vals = [];
-                    args[rp](val, function (value) {
-                        vals[push](round(value));
+        var map = {M: "m", L: "l", C: "c", Z: "x", m: "t", l: "r", c: "v", z: "x"},
+            bites = /([clmz]),?([^clmz]*)/gi,
+            val = /-?[^,\s-]+/g,
+            path2vml = function (path) {
+                var total =  /[ahqstv]/ig,
+                    command = pathToAbsolute;
+                (path + E).match(total) && (command = path2curve);
+                total = /[clmz]/g;
+                if (command == pathToAbsolute && !(path + E).match(total)) {
+                    var res = (path + E)[rp](bites, function (all, command, args) {
+                        var vals = [],
+                            isMove = lowerCase.call(command) == "m",
+                            res = map[command];
+                        args[rp](val, function (value) {
+                            if (isMove && vals[length] == 2) {
+                                res += vals + map[command == "m" ? "l" : "L"];
+                                vals = [];
+                            }
+                            vals[push](round(value));
+                        });
+                        return res + vals;
                     });
-                    return map[command] + vals;
-                });
-                return res;
-            }
-            var pa = command(path), p, res = [], r;
-            for (var i = 0, ii = pa[length]; i < ii; i++) {
-                p = pa[i];
-                r = lowerCase.call(pa[i][0]);
-                r == "z" && (r = "x");
-                for (var j = 1, jj = p[length]; j < jj; j++) {
-                    r += round(p[j]) + (j != jj - 1 ? "," : E);
+                    return res;
+                }
+                var pa = command(path), p, res = [], r;
+                for (var i = 0, ii = pa[length]; i < ii; i++) {
+                    p = pa[i];
+                    r = lowerCase.call(pa[i][0]);
+                    r == "z" && (r = "x");
+                    for (var j = 1, jj = p[length]; j < jj; j++) {
+                        r += round(p[j]) + (j != jj - 1 ? "," : E);
+                    }
+                    res[push](r);
                 }
-                res[push](r);
-            }
-            return res[join](S);
-        },
-        segmentLength = function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
-            var old = {x: 0, y: 0},
-                len = 0;
-            for (var i = 0; i < 1.01; i+=.01) {
-                var dot = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, i);
-                i && (len += math.sqrt(pow(old.x - dot.x, 2) + pow(old.y - dot.y, 2)));
-                old = dot;
-            }
-            return len;
-        };
+                return res[join](S);
+            };
         
         R[toString] = function () {
             return  "Your browser doesn\u2019t support SVG. Falling down to VML.\nYou are running Rapha\xebl " + this.version;
@@ -1683,12 +1679,14 @@ window.Raphael = (function () {
             el.coordsize = this.coordsize;
             el.coordorigin = this.coordorigin;
             g[appendChild](el);
-            var p = new Element(el, g, VML);
+            var p = new Element(el, g, VML),
+                attr = {fill: "none", stroke: "#000"};
+            pathString && (attr.path = pathString);
             p.isAbsolute = true;
             p.type = "path";
             p.path = [];
             p.Path = E;
-            pathString && setFillAndStroke(p, {fill: "none", stroke: "#000", path: pathString});
+            setFillAndStroke(p, attr);
             VML.canvas[appendChild](g);
             return p;
         };
@@ -1858,7 +1856,7 @@ window.Raphael = (function () {
                 res.H = a.h = res.paper.span.offsetHeight;
                 res.X = a.x;
                 res.Y = a.y + round(res.H / 2);
-
                 // text-anchor emulationm
                 switch (a["text-anchor"]) {
                     case "start":
@@ -2225,34 +2223,7 @@ window.Raphael = (function () {
             insertbefore(this, element, this.paper);
             return this;
         };
-        var getLengthFactory = function (istotal) {
-            return function (length) {
-                if (this.type != "path") {
-                    return -1;
-                }
-                var path = path2curve(this.attrs.path), x, y, p, l,
-                    len = 0;
-                for (var i = 0, ii = path.length; i < ii; i++) {
-                    p = path[i];
-                    if (p[0] == "M") {
-                        x = +p[1];
-                        y = +p[2];
-                    } else {
-                        l = segmentLength(x, y, p[1], p[2], p[3], p[4], p[5], p[6]);
-                        if (!istotal && len + l > length) {
-                            return findDotAtSegment(x, y, p[1], p[2], p[3], p[4], p[5], p[6], (length - len) / l);
-                        }
-                        len += l;
-                        x = +p[5];
-                        y = +p[6];
-                    }
-                }
-                return istotal ? len : {x: x, y: y};
-            };
-        };
-        Element[proto].getTotalLength = getLengthFactory(1);
-        Element[proto].getPointAtLength = getLengthFactory();
-
         var theCircle = function (vml, x, y, r) {
             var g = createNode("group"),
                 o = createNode("oval"),
@@ -2404,7 +2375,7 @@ window.Raphael = (function () {
             res.coordsize = "1000 1000";
             res.coordorigin = "0 0";
             res.span = doc.createElement("span");
-            res.span.style.cssText = "position:absolute;left:-9999px;top:-9999px;padding:0;margin:0;line-height:1;display:inline;";
+            res.span.style.cssText = "position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;display:inline;";
             c[appendChild](res.span);
             cs.cssText = R.format("width:{0};height:{1};position:absolute;clip:rect(0 {0} {1} 0);overflow:hidden", width, height);
             if (container == 1) {
@@ -2426,7 +2397,7 @@ window.Raphael = (function () {
         Paper[proto].clear = function () {
             this.canvas.innerHTML = E;
             this.span = doc.createElement("span");
-            this.span.style.cssText = "position:absolute;left:-9999px;top:-9999px;padding:0;margin:0;line-height:1;display:inline;";
+            this.span.style.cssText = "position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;display:inline;";
             this.canvas[appendChild](this.span);
             this.bottom = this.top = null;
         };
@@ -2437,7 +2408,7 @@ window.Raphael = (function () {
             }
         };
     }
-
     // rest
     // Safari or Chrome (WebKit) rendering bug workaround method
     if ((/^Apple|^Google/).test(navigator.vendor) && !(navigator.userAgent.indexOf("Version/4.0") + 1)) {
@@ -2448,7 +2419,7 @@ window.Raphael = (function () {
     } else {
         Paper[proto].safari = function () {};
     }
-
     // Events
     var addEvent = (function () {
         if (doc.addEventListener) {
@@ -2552,25 +2523,23 @@ window.Raphael = (function () {
             var bb = this.getBBox(),
                 rcx = bb.x + bb.width / 2,
                 rcy = bb.y + bb.height / 2,
-                kx = abs(x / this._.sx),
-                ky = abs(y / this._.sy);
+                kx = x / this._.sx,
+                ky = y / this._.sy;
             cx = (+cx || cx == 0) ? cx : rcx;
             cy = (+cy || cy == 0) ? cy : rcy;
-            var dirx = ~~(x / abs(x)),
-                diry = ~~(y / abs(y)),
-                dkx = kx * dirx,
-                dky = ky * diry,
+            var dirx = ~~(x / math.abs(x)),
+                diry = ~~(y / math.abs(y)),
                 s = this.node.style,
-                ncx = cx - abs(rcx - cx) * dkx,
-                ncy = cy - abs(rcy - cy) * dky;
+                ncx = cx + (rcx - cx) * kx,
+                ncy = cy + (rcy - cy) * ky;
             switch (this.type) {
                 case "rect":
                 case "image":
-                    var neww = a.width * kx,
-                        newh = a.height * ky;
+                    var neww = a.width * dirx * kx,
+                        newh = a.height * diry * ky;
                     this.attr({
                         height: newh,
-                        r: a.r * mmin(kx, ky),
+                        r: a.r * mmin(dirx * kx, diry * ky),
                         width: neww,
                         x: ncx - neww / 2,
                         y: ncy - newh / 2
@@ -2579,9 +2548,9 @@ window.Raphael = (function () {
                 case "circle":
                 case "ellipse":
                     this.attr({
-                        rx: a.rx * kx,
-                        ry: a.ry * ky,
-                        r: a.r * mmin(kx, ky),
+                        rx: a.rx * dirx * kx,
+                        ry: a.ry * diry * ky,
+                        r: a.r * mmin(dirx * kx, diry * ky),
                         cx: ncx,
                         cy: ncy
                     });
@@ -2599,22 +2568,22 @@ window.Raphael = (function () {
                             skip = false;
                         }
                         if (P0 == "A") {
-                            p[path[i][length] - 2] = abs(p[path[i][length] - 2]) * dkx;
-                            p[path[i][length] - 1] = abs(p[path[i][length] - 1]) * dky;
-                            p[1] *= kx;
-                            p[2] *= ky;
-                            p[5] = +(dirx + diry ? !+p[4] : !!+p[4]);
+                            p[path[i][length] - 2] *= kx;
+                            p[path[i][length] - 1] *= ky;
+                            p[1] *= dirx * kx;
+                            p[2] *= diry * ky;
+                            p[5] = +(dirx + diry ? !!+p[5] : !+p[5]);
                         } else if (P0 == "H") {
                             for (j = 1, jj = p[length]; j < jj; j++) {
-                                p[j] = abs(p[j]) * dkx;
+                                p[j] *= kx;
                             }
                         } else if (P0 == "V") {
                             for (j = 1, jj = p[length]; j < jj; j++) {
-                                p[j] = abs(p[j]) * dky;
+                                p[j] *= ky;
                             }
                          } else {
                             for (j = 1, jj = p[length]; j < jj; j++) {
-                                p[j] = abs(p[j]) * ( (j % 2) ? dkx : dky );
+                                p[j] *= (j % 2) ? kx : ky;
                             }
                         }
                     }
@@ -2664,6 +2633,76 @@ window.Raphael = (function () {
         delete attr.translation;
         return this.paper[this.type]().attr(attr);
     };
+    var getLengthFactory = function (istotal, subpath) {
+        return function (path, length, onlystart) {
+            path = path2curve(path);
+            var x, y, p, l, sp = "", subpaths = {}, point,
+                len = 0;
+            for (var i = 0, ii = path.length; i < ii; i++) {
+                p = path[i];
+                if (p[0] == "M") {
+                    x = +p[1];
+                    y = +p[2];
+                } else {
+                    l = segmentLength(x, y, p[1], p[2], p[3], p[4], p[5], p[6]);
+                    if (len + l > length) {
+                        if (subpath && !subpaths.start) {
+                            point = R.findDotsAtSegment(x, y, p[1], p[2], p[3], p[4], p[5], p[6], (length - len) / l);
+                            sp += ["C", point.start.x, point.start.y, point.m.x, point.m.y, point.x, point.y];
+                            if (onlystart) return sp;
+                            subpaths.start = sp;
+                            sp = ["M", point.x, point.y + "C", point.n.x, point.n.y, point.end.x, point.end.y, p[5], p[6]][join]();
+                            len += l;
+                            x = +p[5];
+                            y = +p[6];
+                            continue;
+                        }
+                        if (!istotal && !subpath) {
+                            point = R.findDotsAtSegment(x, y, p[1], p[2], p[3], p[4], p[5], p[6], (length - len) / l);
+                            return {x: point.x, y: point.y, alpha: point.alpha};
+                        }
+                    }
+                    len += l;
+                    x = +p[5];
+                    y = +p[6];
+                }
+                sp += 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.alpha && (point = {x: point.x, y: point.y, alpha: point.alpha});
+            return point;
+        };
+    },
+    segmentLength = cacher(function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
+        var old = {x: 0, y: 0},
+            len = 0;
+        for (var i = 0; i < 1.01; i+=.01) {
+            var dot = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, i);
+            i && (len += math.sqrt(pow(old.x - dot.x, 2) + pow(old.y - dot.y, 2)));
+            old = dot;
+        }
+        return len;
+    });
+    var getTotalLength = getLengthFactory(1),
+        getPointAtLength = getLengthFactory(),
+        getSubpathsAtLength = getLengthFactory(0, 1);
+    Element[proto].getTotalLength = function () {
+        if (this.type != "path") return;
+        return getTotalLength(this.attrs.path);
+    };
+    Element[proto].getPointAtLength = function (length) {
+        if (this.type != "path") return;
+        return getPointAtLength(this.attrs.path, length);
+    };
+    Element[proto].getSubpath = function (from, to) {
+        if (this.type != "path") return;
+        if (math.abs(this.getTotalLength() - to) < 1e-6) {
+            return getSubpathsAtLength(this.attrs.path, from).end;
+        }
+        var a = getSubpathsAtLength(this.attrs.path, to, 1);
+        return from ? getSubpathsAtLength(a, from).end : a;
+    };
 
     // animation easing formulas
     R.easing_formulas = {
@@ -2724,13 +2763,13 @@ window.Raphael = (function () {
             return l;
         }
     };
-
     var animationElements = {length : 0},
         animation = function () {
             var Now = +new Date;
             for (var l in animationElements) if (l != "length" && animationElements[has](l)) {
                 var e = animationElements[l];
-                if (e.stop) {
+                if (e.stop || e.el.removed) {
                     delete animationElements[l];
                     animationElements[length]--;
                     continue;
@@ -2751,6 +2790,16 @@ window.Raphael = (function () {
                     var pos = R.easing_formulas[easing] ? R.easing_formulas[easing](time / ms) : time / ms;
                     for (var attr in from) if (from[has](attr)) {
                         switch (availableAnimAttrs[attr]) {
+                            case "along":
+                                now = pos * ms * diff[attr];
+                                to.back && (now = to.len - now);
+                                var point = getPointAtLength(to[attr], now);
+                                that.translate(diff.sx - diff.x || 0, diff.sy - diff.y || 0);
+                                diff.x = point.x;
+                                diff.y = point.y;
+                                that.translate(point.x - diff.sx, point.y - diff.sy);
+                                to.rot && that.rotate(diff.r + point.alpha, point.x, point.y);
+                                break;
                             case "number":
                                 now = +from[attr] + pos * ms * diff[attr];
                                 break;
@@ -2803,6 +2852,11 @@ window.Raphael = (function () {
                     that.attr(set);
                     that._run && that._run.call(that);
                 } else {
+                    if (to.along) {
+                        var point = getPointAtLength(to.along, to.len * !to.back);
+                        that.translate(diff.sx - (diff.x || 0) + point.x - diff.sx, diff.sy - (diff.y || 0) + point.y - diff.sy);
+                        to.rot && that.rotate(diff.r + point.alpha, point.x, point.y);
+                    }
                     (t.x || t.y) && that.translate(-t.x, -t.y);
                     to.scale && (to.scale = to.scale + E);
                     that.attr(to);
@@ -2813,7 +2867,7 @@ window.Raphael = (function () {
                 }
                 e.prev = time;
             }
-            R.svg && that.paper.safari();
+            R.svg && that && that.paper.safari();
             animationElements[length] && setTimeout(animation);
         },
         upto255 = function (color) {
@@ -2848,8 +2902,19 @@ window.Raphael = (function () {
         animationElements[element.id] && (params.start = animationElements[element.id].start);
         return this.animate(params, ms, easing, callback);
     };
+    Element[proto].animateAlong = along();
+    Element[proto].animateAlongBack = along(1);
+    function along(isBack) {
+        return function (path, ms, rotate, callback) {
+            var params = {back: isBack};
+            R.is(rotate, "function") ? (callback = rotate) : (params.rot = rotate);
+            path && path.constructor == Element && (path = path.attrs.path);
+            path && (params.along = path);
+            return this.animate(params, ms, callback);
+        };
+    }
     Element[proto].onAnimation = function (f) {
-        this._run = f || null;
+        this._run = f || 0;
         return this;
     };
     Element[proto].animate = function (params, ms, easing, callback) {
@@ -2865,6 +2930,20 @@ window.Raphael = (function () {
                 (from[attr] == null) && (from[attr] = availableAttrs[attr]);
                 to[attr] = params[attr];
                 switch (availableAnimAttrs[attr]) {
+                    case "along":
+                        var len = getTotalLength(params[attr]),
+                            point = getPointAtLength(params[attr], len * !!params.back),
+                            bb = this.getBBox();
+                        diff[attr] = len / ms;
+                        diff.tx = bb.x;
+                        diff.ty = bb.y;
+                        diff.sx = point.x;
+                        diff.sy = point.y;
+                        to.rot = params.rot;
+                        to.back = params.back;
+                        to.len = len;
+                        params.rot && (diff.r = toFloat(this.rotate()) || 0);
+                        break;
                     case "number":
                         diff[attr] = (to[attr] - from[attr]) / ms;
                         break;
@@ -2947,7 +3026,7 @@ window.Raphael = (function () {
         return "Rapha\xebl\u2019s object";
     };
     R.ae = animationElements;
-
     // Set
     var Set = function (items) {
         this.items = [];
@@ -3043,6 +3122,13 @@ window.Raphael = (function () {
             height: mmax[apply](0, h) - y
         };
     };
+    Set[proto].clone = function (s) {
+        s = new Set;
+        for (var i = 0, ii = this.items[length]; i < ii; i++) {
+            s[push](this.items[i].clone());
+        }
+        return s;
+    };
 
     R.registerFont = function (font) {
         if (!font.face) {
@@ -3131,7 +3217,7 @@ window.Raphael = (function () {
         }
         return out;
     };
-
     R.format = function (token) {
         var args = R.is(arguments[1], "array") ? [0][concat](arguments[1]) : arguments,
             rg = /\{(\d+)\}/g;
@@ -3141,17 +3227,8 @@ window.Raphael = (function () {
         return token || E;
     };
     R.ninja = function () {
-        var r = win.Raphael, u;
-        if (oldRaphael.was) {
-            win.Raphael = oldRaphael.is;
-        } else {
-            try {
-                delete win.Raphael;
-            } catch (e) {
-                win.Raphael = u;
-            }
-        }
-        return r;
+        oldRaphael.was ? (Raphael = oldRaphael.is) : delete Raphael;
+        return R;
     };
     R.el = Element[proto];
     return R;