Refactor path methods and some other stuff
authorDmitry Baranovskiy <dbaranovskiy@Fresh-Air.sydney.atlassian.com>
Tue, 18 Aug 2009 00:19:50 +0000 (10:19 +1000)
committerDmitry Baranovskiy <dbaranovskiy@Fresh-Air.sydney.atlassian.com>
Tue, 18 Aug 2009 00:19:50 +0000 (10:19 +1000)
raphael.js

index c9ded94..5bd0c35 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Raphael 0.8.6 - JavaScript Vector Library
+ * Raphael 1.0 - JavaScript Vector Library
  *
  * Copyright (c) 2008 - 2009 Dmitry Baranovskiy (http://raphaeljs.com)
  * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
@@ -21,7 +21,7 @@ window.Raphael = (function () {
         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},
         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"},
         events = ["click", "dblclick", "mousedown", "mousemove", "mouseout", "mouseover", "mouseup"];
-    R.version = "0.8.6";
+    R.version = "1.0";
     R.type = (window.SVGAngle || document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1") ? "SVG" : "VML");
     R.svg = !(R.vml = R.type == "VML");
     R.idGenerator = 0;
@@ -216,26 +216,94 @@ window.Raphael = (function () {
         delete this.start;
     };
     // path utilities
+    var pathMethods = {
+        absolutely: function () {
+            this.isAbsolute = true;
+            return this;
+        },
+        relatively: function () {
+            this.isAbsolute = false;
+            return this;
+        },
+        moveTo: function (x, y) {
+            var d = this.isAbsolute ? "M" : "m";
+            d += +parseFloat(x).toFixed(3) + " " + (+parseFloat(y).toFixed(3)) + " ";
+            this.attr({path: this.attrs.path + d});
+            return this;
+        },
+        lineTo: function (x, y) {
+            var d = this.isAbsolute ? "L" : "l";
+            d += +parseFloat(x).toFixed(3) + " " + (+parseFloat(y).toFixed(3)) + " ";
+            this.attr({path: this.attrs.path + d});
+            return this;
+        },
+        arcTo: function (rx, ry, large_arc_flag, sweep_flag, x, y) {
+            var d = this.isAbsolute ? "A" : "a";
+            d += [+parseFloat(rx).toFixed(3), +parseFloat(ry).toFixed(3), 0, large_arc_flag, sweep_flag, +parseFloat(x).toFixed(3), +parseFloat(y).toFixed(3)].join(" ");
+            this.attr({path: this.attrs.path + d});
+            return this;
+        },
+        curveTo: function () {
+            var args = Array.prototype.splice.call(arguments, 0, arguments.length),
+                d = [0, 0, 0, 0, "s", 0, "c"][args.length] || "";
+            if (this.isAbsolute) {
+                d = d.toUpperCase();
+            }
+            this.attr({path: this.attrs.path + d + args});
+            return this;
+        },
+        qcurveTo: function () {
+            var d = [0, 1, "t", 3, "q"][arguments.length],
+                args = Array.prototype.splice.call(arguments, 0, arguments.length);
+            if (this.isAbsolute) {
+                d = d.toUpperCase();
+            }
+            this.attr({path: this.attrs.path + d + args});
+            return this;
+        },
+        addRoundedCorner: function (r, dir) {
+            var rollback = this.isAbsolute,
+                o = this;
+            if (rollback) {
+                this.relatively();
+                rollback = function () {
+                    o.absolutely();
+                };
+            } else {
+                rollback = function () {};
+            }
+            this.arcTo(r, r, 0, {"lu": 1, "rd": 1, "ur": 1, "dl": 1}[dir] || 0, r * (!!(dir.indexOf("r") + 1) * 2 - 1), r * (!!(dir.indexOf("d") + 1) * 2 - 1));
+            rollback();
+            return this;
+        },
+        andClose: function () {
+            this.attr({path: this.attrs.path + "z"});
+            return this;
+        },
+        getPathArray: function () {
+            return R.parsePathString(this.attrs.path);
+        }
+    };
     var pathcache = {},
+        path2string = function () {
+            var res = "";
+            for (var i = 0, ii = this.length; i < ii; i++) {
+                res += this[i][0] + this[i].join(",").substring(2);
+            }
+            return res.replace(/,(?=-)/g, "");
+        },
         pathcount = [];
     R.parsePathString = function (pathString) {
         if (pathString in pathcache) {
             return pathcache[pathString];
         }
         var paramCounts = {a: 7, c: 6, h: 1, l: 2, m: 2, q: 4, s: 4, t: 2, v: 1, z: 0},
-            data = [],
-            toString = function () {
-                var res = "";
-                for (var i = 0, ii = this.length; i < ii; i++) {
-                    res += this[i][0] + this[i].join(",").substring(2);
-                }
-                return res;
-            };
-        if (pathString.toString.toString() == toString.toString()) {
+            data = [];
+        if (pathString.toString == path2string) {
             data = pathString;
         }
         if (!data.length) {
-            pathString.replace(/([achlmqstvz])[\s,]*((-?\d*\.?\d*(?:e[-+]?\d+)?\s*,?\s*)+)/ig, function (a, b, c) {
+            (pathString + "").replace(/([achlmqstvz])[\s,]*((-?\d*\.?\d*(?:e[-+]?\d+)?\s*,?\s*)+)/ig, function (a, b, c) {
                 var params = [],
                     name = b.toLowerCase();
                 c.replace(/(-?\d*\.?\d*(?:e[-+]?\d+)?)\s*,?\s*/ig, function (a, b) {
@@ -248,7 +316,7 @@ window.Raphael = (function () {
                     };
                 }
             });
-            data.toString = toString;
+            data.toString = path2string;
         }
         if (pathcount.length > 20) {
             delete pathcache[pathcount.unshift()];
@@ -259,9 +327,6 @@ window.Raphael = (function () {
     };
     var pathDimensions = function (path) {
         var pathArray = path;
-        if (typeof path == "string") {
-            pathArray = R.parsePathString(path);
-        }
         pathArray = pathToAbsolute(pathArray);
         var x = [],
             y = [],
@@ -303,64 +368,6 @@ window.Raphael = (function () {
             };
         }
     },
-        addRoundedCorner = function (r, dir) {
-            var R = .5522 * r,
-                rollback = this.isAbsolute,
-                o = this;
-            if (rollback) {
-                this.relatively();
-                rollback = function () {
-                    o.absolutely();
-                };
-            } else {
-                rollback = function () {};
-            }
-            var actions = {
-                l: function () {
-                    return {
-                        u: function () {
-                            o.curveTo(-R, 0, -r, -(r - R), -r, -r);
-                        },
-                        d: function () {
-                            o.curveTo(-R, 0, -r, r - R, -r, r);
-                        }
-                    };
-                },
-                r: function () {
-                    return {
-                        u: function () {
-                            o.curveTo(R, 0, r, -(r - R), r, -r);
-                        },
-                        d: function () {
-                            o.curveTo(R, 0, r, r - R, r, r);
-                        }
-                    };
-                },
-                u: function () {
-                    return {
-                        r: function () {
-                            o.curveTo(0, -R, -(R - r), -r, r, -r);
-                        },
-                        l: function () {
-                            o.curveTo(0, -R, R - r, -r, -r, -r);
-                        }
-                    };
-                },
-                d: function () {
-                    return {
-                        r: function () {
-                            o.curveTo(0, R, -(R - r), r, r, r);
-                        },
-                        l: function () {
-                            o.curveTo(0, R, R - r, r, -r, r);
-                        }
-                    };
-                }
-            };
-            actions[dir.charAt(0)]()[dir.charAt(1)]();
-            rollback();
-            return o;
-        },
         pathToRelative = function (pathArray) {
             var res = [],
                 x = 0,
@@ -423,13 +430,13 @@ window.Raphael = (function () {
             return res;
         },
         pathToAbsolute = function (pathArray) {
-            var res = [];
+            var res = [],
+                x = 0,
+                y = 0,
+                start = 0;
             if (typeof pathArray == "string") {
                 pathArray = R.parsePathString(pathArray);
             }
-            var x = 0,
-                y = 0,
-                start = 0;
             if (pathArray[0][0] == "M") {
                 x = +pathArray[0][1];
                 y = +pathArray[0][2];
@@ -485,22 +492,121 @@ window.Raphael = (function () {
             res.toString = pathArray.toString;
             return res;
         },
-        pecache = {}, pecount = [],
-        pathEqualiser = function (path1, path2) {
-            if ((path1 + path2) in pecache) {
-                return pecache[path1 + path2];
+        l2c = function (x1, y1, x2, y2) {
+            return [x1, y1, x2, y2, x2, y2];
+        },
+        q2c = function (x1, y1, ax, ay, x2, y2) {
+            return [
+                    2 / 3 * x1 + 1 / 3 * ax,
+                    2 / 3 * y1 + 1 / 3 * ay,
+                    2 / 3 * x1 + 1 / 3 * x2,
+                    2 / 3 * y1 + 1 / 3 * y2,
+                    x2,
+                    y2
+                ];
+        },
+        a2c = function (x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, recursive) {
+            // for more information of where this math came from visit:
+            // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
+            var _120 = Math.PI * 120 / 180,
+                res = [];
+            if (!recursive) {
+                var x = (x1 - x2) / 2,
+                    y = (y1 - y2) / 2,
+                    rx2 = rx * rx,
+                    ry2 = ry * ry,
+                    k = (large_arc_flag == sweep_flag ? -1 : 1) *
+                        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),
+                    f2 = Math.asin((y2 - cy) / ry);
+
+                f1 = x1 < cx ? Math.PI - f1 : f1;
+                f2 = x2 < cx ? Math.PI - f2 : f2;
+                f1 < 0 && (f1 = Math.PI * 2 + f1);
+                f2 < 0 && (f2 = Math.PI * 2 + f2);
+                if (sweep_flag && f1 > f2) {
+                    f1 = f1 - Math.PI * 2;
+                }
+                if (!sweep_flag && f2 > f1) {
+                    f2 = f2 - Math.PI * 2;
+                }
+            } else {
+                f1 = recursive[0];
+                f2 = recursive[1];
+                cx = recursive[2];
+                cy = recursive[3];
+            }
+            var df = f2 - f1;
+            if (Math.abs(df) > _120) {
+                var f2old = f2,
+                    x2old = x2,
+                    y2old = y2;
+                f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1);
+                x2 = cx + rx * Math.cos(f2);
+                y2 = cy + ry * Math.sin(f2);
+                res = arguments.callee(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [f2, f2old, cx, cy]);
+            }
+            var c1 = Math.cos(f1),
+                s1 = Math.sin(f1),
+                c2 = Math.cos(f2),
+                s2 = Math.sin(f2),
+                df = f2 - f1,
+                t = Math.tan(df / 4),
+                hx = 4 / 3 * rx * t,
+                hy = 4 / 3 * ry * t,
+                m1 = [x1, y1],
+                m2 = [x1 + hx * s1, y1 - hy * c1],
+                m3 = [x2 + hx * s2, y2 - hy * c2],
+                m4 = [x2, y2];
+            m2[0] = 2 * m1[0] - m2[0];
+            m2[1] = 2 * m1[1] - m2[1];
+            if (recursive) {
+                return [m2, m3, m4].concat(res);
+            } else {
+                res = [m2, m3, m4].concat(res).join(",").split(",");
+                for (var i = res.length; i--;) {
+                    res[i] = +res[i];
+                }
+                return res;
             }
-            var data = [pathToAbsolute(R.parsePathString(path1)), pathToAbsolute(R.parsePathString(path2))],
-                attrs = [{x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0}, {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0}],
+        },
+        findDotAtSegment = function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
+            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,
+                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,
+                mx = p1x + 2 * t * (c1x - p1x) + t * t * (c2x - 2 * c1x + p1x),
+                my = p1y + 2 * t * (c1y - p1y) + t * t * (c2y - 2 * c1y + p1y),
+                nx = c1x + 2 * t * (c2x - c1x) + t * t * (p2x - 2 * c2x + c1x),
+                ny = c1y + 2 * t * (c2y - c1y) + t * t * (p2y - 2 * c2y + c1y),
+                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}};
+        },
+        path2curve = function (path, path2) {
+            path2curve.cache = path2curve.cache || {};
+            path2curve.count = path2curve.count || [];
+            if (path in path2curve.cache) {
+                return path2curve.cache[path + "&" + path2];
+            }
+            var p = pathToAbsolute(path),
+                p2 = path2 && pathToAbsolute(path2),
+                attrs = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0},
+                attrs2 = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0},
                 processPath = function (path, d) {
                     if (!path) {
-                        return ["U"];
+                        return ["C", d.x, d.y, d.x, d.y, d.x, d.y];
                     }
                     switch (path[0]) {
                         case "M":
                             d.X = path[1];
                             d.Y = path[2];
                             break;
+                        case "A":
+                            path = ["C"].concat(a2c(d.x, d.y, path[1], path[2], path[3], path[4], path[5], path[6], path[7]));
+                            break;
                         case "S":
                             var nx = d.x + (d.x - (d.bx || d.x)),
                                 ny = d.y + (d.y - (d.by || d.y));
@@ -509,83 +615,72 @@ window.Raphael = (function () {
                         case "T":
                             var nx = d.x + (d.x - (d.bx || d.x)),
                                 ny = d.y + (d.y - (d.by || d.y));
-                            path = ["Q", nx, ny, path[1], path[2]];
+                            path = ["C"].concat(q2c(d.x, d.y, nx, ny, path[1], path[2]));
+                            break;
+                        case "Q":
+                            path = ["C"].concat(q2c(d.x, d.y, path[1], path[2], path[3], path[4]));
+                            break;
+                        case "L":
+                            path = ["C"].concat(l2c(d.x, d.y, path[1], path[2]));
                             break;
                         case "H":
-                            path = ["L", path[1], d.y];
+                            path = ["C"].concat(l2c(d.x, d.y, path[1], d.y));
                             break;
                         case "V":
-                            path = ["L", d.x, path[1]];
+                            path = ["C"].concat(l2c(d.x, d.y, d.x, path[1]));
                             break;
                         case "Z":
-                            path = ["L", d.X, d.Y];
+                            path = ["C"].concat(l2c(d.x, d.y, d.X, d.Y));
                             break;
                     }
                     return path;
                 },
-                edgeCases = function (a, b, i) {
-                    if (data[a][i][0] == "M" && data[b][i][0] != "M") {
-                        data[b].splice(i, 0, ["M", attrs[b].x, attrs[b].y]);
-                        attrs[a].bx = data[a][i][data[a][i].length - 4] || 0;
-                        attrs[a].by = data[a][i][data[a][i].length - 3] || 0;
-                        attrs[a].x = data[a][i][data[a][i].length - 2];
-                        attrs[a].y = data[a][i][data[a][i].length - 1];
-                        return true;
-                    } else if (data[a][i][0] == "L" && data[b][i][0] == "C") {
-                        data[a][i] = ["C", attrs[a].x, attrs[a].y, data[a][i][1], data[a][i][2], data[a][i][1], data[a][i][2]];
-                    } else if (data[a][i][0] == "L" && data[b][i][0] == "Q") {
-                        data[a][i] = ["Q", data[a][i][1], data[a][i][2], data[a][i][1], data[a][i][2]];
-                    } else if (data[a][i][0] == "Q" && data[b][i][0] == "C") {
-                        var x = data[b][i][data[b][i].length - 2],
-                            y = data[b][i][data[b][i].length - 1];
-                        data[b].splice(i + 1, 0, ["Q", x, y, x, y]);
-                        data[a].splice(i, 0, ["C", attrs[a].x, attrs[a].y, attrs[a].x, attrs[a].y, attrs[a].x, attrs[a].y]);
-                        i++;
-                        attrs[b].bx = data[b][i][data[b][i].length - 4] || 0;
-                        attrs[b].by = data[b][i][data[b][i].length - 3] || 0;
-                        attrs[b].x = data[b][i][data[b][i].length - 2];
-                        attrs[b].y = data[b][i][data[b][i].length - 1];
-                        return true;
-                    } else if (data[a][i][0] == "A" && data[b][i][0] == "C") {
-                        var x = data[b][i][data[b][i].length - 2],
-                            y = data[b][i][data[b][i].length - 1];
-                        data[b].splice(i + 1, 0, ["A", 0, 0, data[a][i][3], data[a][i][4], data[a][i][5], x, y]);
-                        data[a].splice(i, 0, ["C", attrs[a].x, attrs[a].y, attrs[a].x, attrs[a].y, attrs[a].x, attrs[a].y]);
-                        i++;
-                        attrs[b].bx = data[b][i][data[b][i].length - 4] || 0;
-                        attrs[b].by = data[b][i][data[b][i].length - 3] || 0;
-                        attrs[b].x = data[b][i][data[b][i].length - 2];
-                        attrs[b].y = data[b][i][data[b][i].length - 1];
-                        return true;
-                    } else if (data[a][i][0] == "U") {
-                        data[a][i][0] = data[b][i][0];
-                        for (var j = 1, jj = data[b][i].length; j < jj; j++) {
-                            data[a][i][j] = (j % 2) ? attrs[a].x : attrs[a].y;
+                fixArc = function (pp, i) {
+                    if (pp[i].length > 7) {
+                        pp[i].shift();
+                        var pi = pp[i];
+                        while (pi.length) {
+                            pp.splice(i++, 0, ["C"].concat(pi.splice(0, 6)));
                         }
+                        pp.splice(i, 1);
+                        ii = Math.max(p.length, p2 && p2.length || 0);
+                    }
+                },
+                fixM = function (path1, path2, a1, a2, i) {
+                    if (path1 && path2 && path1[i][0] == "M" && path2[i][0] != "M") {
+                        path2.splice(i, 0, ["M", a2.x, a2.y]);
+                        a1.bx = 0;
+                        a1.by = 0;
+                        a1.x = path1[i][1];
+                        a1.y = path1[i][2];
+                        ii = Math.max(p.length, p2 && p2.length || 0);
                     }
-                    return false;
                 };
-            for (var i = 0; i < Math.max(data[0].length, data[1].length); i++) {
-                data[0][i] = processPath(data[0][i], attrs[0]);
-                data[1][i] = processPath(data[1][i], attrs[1]);
-                if (data[0][i][0] != data[1][i][0] && (edgeCases(0, 1, i) || edgeCases(1, 0, i))) {
-                    continue;
-                }
-                attrs[0].bx = data[0][i][data[0][i].length - 4] || 0;
-                attrs[0].by = data[0][i][data[0][i].length - 3] || 0;
-                attrs[0].x = data[0][i][data[0][i].length - 2];
-                attrs[0].y = data[0][i][data[0][i].length - 1];
-                attrs[1].bx = data[1][i][data[1][i].length - 4] || 0;
-                attrs[1].by = data[1][i][data[1][i].length - 3] || 0;
-                attrs[1].x = data[1][i][data[1][i].length - 2];
-                attrs[1].y = data[1][i][data[1][i].length - 1];
-            }
-            if (pecount.length > 20) {
-                delete pecache[pecount.unshift()];
-            }
-            pecount.push(path1 + path2);
-            pecache[path1 + path2] = data;
-            return data;
+            for (var i = 0, ii = Math.max(p.length, p2 && p2.length || 0); i < ii; i++) {
+                p[i] = processPath(p[i], attrs);
+                fixArc(p, i);
+                p2 && (p2[i] = processPath(p2[i], attrs2));
+                p2 && fixArc(p2, i);
+                fixM(p, p2, attrs, attrs2, i);
+                fixM(p2, p, attrs2, attrs, i);
+                var seg = p[i],
+                    seg2 = p2 && p2[i],
+                    seglen = seg.length,
+                    seg2len = p2 && seg2.length;
+                attrs.bx = seg[seglen - 4] || 0;
+                attrs.by = seg[seglen - 3] || 0;
+                attrs.x = seg[seglen - 2];
+                attrs.y = seg[seglen - 1];
+                attrs2.bx = p2 && (seg2[seg2len - 4] || 0);
+                attrs2.by = p2 && (seg2[seg2len - 3] || 0);
+                attrs2.x = p2 && seg2[seg2len - 2];
+                attrs2.y = p2 && seg2[seg2len - 1];
+            }
+            if (path2curve.count.length > 100) {
+                delete path2curve.cache[path2curve.count.unshift()];
+            }
+            path2curve.count.push(path + "&" + path2);
+            return (path2curve.cache[path + "&" + path2] = p2 ? [p, p2] : p);
         },
         toGradient = function (gradient) {
             if (typeof gradient == "string") {
@@ -701,117 +796,7 @@ window.Raphael = (function () {
         R.toString = function () {
             return  "Your browser supports SVG.\nYou are running Rapha\u00ebl " + this.version;
         };
-        var pathMethods = {
-            absolutely: function () {
-                this.isAbsolute = true;
-                return this;
-            },
-            relatively: function () {
-                this.isAbsolute = false;
-                return this;
-            },
-            moveTo: function (x, y) {
-                var d = this.isAbsolute ? "M" : "m";
-                d += parseFloat(x).toFixed(3) + " " + parseFloat(y).toFixed(3) + " ";
-                var oldD = this[0].getAttribute("d") || "";
-                (oldD == "M0,0") && (oldD = "");
-                this[0].setAttribute("d", oldD + d);
-                this.last.x = (this.isAbsolute ? 0 : this.last.x) + parseFloat(x);
-                this.last.y = (this.isAbsolute ? 0 : this.last.y) + parseFloat(y);
-                this.attrs.path = oldD + d;
-                return this;
-            },
-            lineTo: function (x, y) {
-                this.last.x = (!this.isAbsolute * this.last.x) + parseFloat(x);
-                this.last.y = (!this.isAbsolute * this.last.y) + parseFloat(y);
-                var d = this.isAbsolute ? "L" : "l";
-                d += parseFloat(x).toFixed(3) + " " + parseFloat(y).toFixed(3) + " ";
-                var oldD = this.node.getAttribute("d") || "";
-                this.node.setAttribute("d", oldD + d);
-                this.attrs.path = oldD + d;
-                return this;
-            },
-            arcTo: function (rx, ry, large_arc_flag, sweep_flag, x, y) {
-                var d = this.isAbsolute ? "A" : "a";
-                d += [parseFloat(rx).toFixed(3), parseFloat(ry).toFixed(3), 0, large_arc_flag, sweep_flag, parseFloat(x).toFixed(3), parseFloat(y).toFixed(3)].join(" ");
-                var oldD = this[0].getAttribute("d") || "";
-                this.node.setAttribute("d", oldD + d);
-                this.last.x = parseFloat(x);
-                this.last.y = parseFloat(y);
-                this.attrs.path = oldD + d;
-                return this;
-            },
-            cplineTo: function (x1, y1, w1) {
-                if (!w1) {
-                    return this.lineTo(x1, y1);
-                } else {
-                    var p = {},
-                        x = parseFloat(x1),
-                        y = parseFloat(y1),
-                        w = parseFloat(w1),
-                        d = this.isAbsolute ? "C" : "c",
-                        attr = [+this.last.x + w, +this.last.y, x - w, y, x, y];
-                    for (var i = 0, ii = attr.length; i < ii; i++) {
-                        d += attr[i] + " ";
-                    }
-                    this.last.x = (this.isAbsolute ? 0 : this.last.x) + attr[4];
-                    this.last.y = (this.isAbsolute ? 0 : this.last.y) + attr[5];
-                    this.last.bx = attr[2];
-                    this.last.by = attr[3];
-                    var oldD = this.node.getAttribute("d") || "";
-                    this.node.setAttribute("d", oldD + d);
-                    this.attrs.path = oldD + d;
-                    return this;
-                }
-            },
-            curveTo: function () {
-                var p = {},
-                    d = [0, 1, 2, 3, "s", 5, "c"][arguments.length];
-                if (this.isAbsolute) {
-                    d = d.toUpperCase();
-                }
-                for (var i = 0, ii = arguments.length; i < ii; i++) {
-                    d += parseFloat(arguments[i]).toFixed(3) + " ";
-                }
-                this.last.x = (this.isAbsolute ? 0 : this.last.x) + parseFloat(arguments[arguments.length - 2]);
-                this.last.y = (this.isAbsolute ? 0 : this.last.y) + parseFloat(arguments[arguments.length - 1]);
-                this.last.bx = parseFloat(arguments[arguments.length - 4]);
-                this.last.by = parseFloat(arguments[arguments.length - 3]);
-                var oldD = this.node.getAttribute("d") || "";
-                this.node.setAttribute("d", oldD + d);
-                this.attrs.path = oldD + d;
-                return this;
-            },
-            qcurveTo: function () {
-                var p = {},
-                    d = [0, 1, "t", 3, "q"][arguments.length];
-                if (this.isAbsolute) {
-                    d = d.toUpperCase();
-                }
-                for (var i = 0, ii = arguments.length; i < ii; i++) {
-                    d += parseFloat(arguments[i]).toFixed(3) + " ";
-                }
-                this.last.x = (this.isAbsolute ? 0 : this.last.x) + parseFloat(arguments[arguments.length - 2]);
-                this.last.y = (this.isAbsolute ? 0 : this.last.y) + parseFloat(arguments[arguments.length - 1]);
-                if (arguments.length != 2) {
-                    this.last.qx = parseFloat(arguments[arguments.length - 4]);
-                    this.last.qy = parseFloat(arguments[arguments.length - 3]);
-                }
-                var oldD = this.node.getAttribute("d") || "";
-                this.node.setAttribute("d", oldD + d);
-                this.attrs.path = oldD + d;
-                return this;
-            },
-            addRoundedCorner: addRoundedCorner,
-            andClose: function () {
-                var oldD = this[0].getAttribute("d") || "";
-                this[0].setAttribute("d", oldD + "Z ");
-                this.attrs.path = oldD + "Z ";
-                return this;
-            }
-        };
         var thePath = function (params, pathString, SVG) {
-            params = params || {};
             var el = doc.createElementNS(SVG.svgns, "path");
             if (SVG.canvas) {
                 SVG.canvas.appendChild(el);
@@ -822,12 +807,8 @@ window.Raphael = (function () {
                 p[method] = pathMethods[method];
             }
             p.type = "path";
-            p.last = {x: 0, y: 0, bx: 0, by: 0};
-            if (pathString) {
-                p.attrs.path = "" + pathString;
-                p.absolutely();
-                paper.pathfinder(p, p.attrs.path);
-            }
+            p.attrs.path = "" + (pathString || "");
+            pathString && p.node.setAttribute("d", R.parsePathString(pathString));
             if (params) {
                 !params.gradient && (params.fill = params.fill || "none");
                 params.stroke = params.stroke || "#000";
@@ -894,7 +875,7 @@ window.Raphael = (function () {
                 attrs = o.attrs,
                 rot = attrs.rotation,
                 addDashes = function (o, value) {
-                    value = dasharray[value.toString().toLowerCase()];
+                    value = dasharray[(value + "").toLowerCase()];
                     if (value) {
                         var width = o.attrs["stroke-width"] || "1",
                             butt = {round: width, square: width, butt: 0}[o.attrs["stroke-linecap"] || params["stroke-linecap"]] || 0,
@@ -929,8 +910,7 @@ window.Raphael = (function () {
                       break;
                     case "path":
                         if (o.type == "path") {
-                            node.setAttribute("d", "M0,0");
-                            paper.pathfinder(o, value);
+                            node.setAttribute("d", R.parsePathString(value));
                         }
                     case "width":
                         node.setAttribute(att, value);
@@ -1131,10 +1111,13 @@ window.Raphael = (function () {
         };
         Element.prototype.rotate = function (deg, cx, cy) {
             if (deg == null) {
+                if (this._.rt.cx) {
+                    return [this._.rt.deg, this._.rt.cx, this._.rt.cy].join(" ");
+                }
                 return this._.rt.deg;
             }
             var bbox = this.getBBox();
-            deg = deg.toString().split(separator);
+            deg = (deg + "").split(separator);
             if (deg.length - 1) {
                 cx = parseFloat(deg[1]);
                 cy = parseFloat(deg[2]);
@@ -1145,9 +1128,9 @@ window.Raphael = (function () {
             } else {
                 this._.rt.deg += deg;
             }
-            if (cy == null) {
-                cx = null;
-            }
+            (cy == null) && (cx = null);
+            this._.rt.cx = cx;
+            this._.rt.cy = cy;
             cx = cx == null ? bbox.x + bbox.width / 2 : cx;
             cy = cy == null ? bbox.y + bbox.height / 2 : cy;
             if (this._.rt.deg) {
@@ -1198,6 +1181,12 @@ window.Raphael = (function () {
                 if (arguments[0] == "translation") {
                     return this.translate();
                 }
+                if (arguments[0] == "rotation") {
+                    return this.rotate();
+                }
+                if (arguments[0] == "scale") {
+                    return this.scale();
+                }
                 return this.attrs[arguments[0]];
             }
             if (arguments.length == 1 && R.isArray(arguments[0])) {
@@ -1320,6 +1309,7 @@ window.Raphael = (function () {
             }
             var res = new Element(el, svg);
             res.attrs = res.attrs || {};
+            res.attrs.src = src;
             res.attrs.x = x;
             res.attrs.y = y;
             res.attrs.width = w;
@@ -1337,6 +1327,7 @@ window.Raphael = (function () {
             }
             var res = new Element(el, svg);
             res.attrs = res.attrs || {};
+            res.attrs.text = text;
             res.attrs.x = x;
             res.attrs.y = y;
             res.type = "text";
@@ -1398,7 +1389,7 @@ window.Raphael = (function () {
             return container;
         };
         paper.remove = function () {
-            this.canvas.parentNode.removeChild(this.canvas);
+            this.canvas.parentNode && this.canvas.parentNode.removeChild(this.canvas);
         };
         paper.svgns = "http://www.w3.org/2000/svg";
         paper.xlink = "http://www.w3.org/1999/xlink";
@@ -1412,160 +1403,19 @@ window.Raphael = (function () {
 
     // VML
     if (R.vml) {
-        R.toString = function () {
-            return  "Your browser doesn\u2019t support SVG. Assuming it is Internet Explorer and falling down to VML.\nYou are running Rapha\u00ebl " + this.version;
-        };
-        var pathMethods = {
-            absolutely: function () {
-                this.isAbsolute = true;
-                return this;
-            },
-            relatively: function () {
-                this.isAbsolute = false;
-                return this;
-            },
-            moveTo: function (x, y) {
-                var X = Math.round(parseFloat(x)) - 1,
-                    Y = Math.round(parseFloat(y)) - 1,
-                    d = this.isAbsolute ? "m" : "t";
-                d += X + " " + Y;
-                this.node.path = this.Path += d;
-                this.last.x = (this.isAbsolute ? 0 : this.last.x) + parseFloat(x);
-                this.last.y = (this.isAbsolute ? 0 : this.last.y) + parseFloat(y);
-                this.last.isAbsolute = this.isAbsolute;
-                this.attrs.path += (this.isAbsolute ? "M" : "m") + [x, y];
-                return this;
-            },
-            lineTo: function (x, y) {
-                var X = Math.round(parseFloat(x)) - 1,
-                    Y = Math.round(parseFloat(y)) - 1,
-                    d = this.isAbsolute ? "l" : "r";
-                d += X + " " + Y;
-                this.node.path = this.Path += d;
-                this.last.x = (this.isAbsolute ? 0 : this.last.x) + parseFloat(x);
-                this.last.y = (this.isAbsolute ? 0 : this.last.y) + parseFloat(y);
-                this.last.isAbsolute = this.isAbsolute;
-                this.attrs.path += (this.isAbsolute ? "L" : "l") + [x, y];
-                return this;
-            },
-            arcTo: function (rx, ry, large_arc_flag, sweep_flag, x2, y2) {
-                // for more information of where this math came from visit:
-                // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
-                var x22 = (this.isAbsolute ? 0 : this.last.x) + parseFloat(x2) - 1,
-                    y22 = (this.isAbsolute ? 0 : this.last.y) + parseFloat(y2) - 1,
-                    x1 = this.last.x - 1,
-                    y1 = this.last.y - 1,
-                    x = (x1 - x22) / 2,
-                    y = (y1 - y22) / 2,
-                    k = (large_arc_flag == sweep_flag ? -1 : 1) *
-                        Math.sqrt(Math.abs(rx * rx * ry * ry - rx * rx * y * y - ry * ry * x * x) / (rx * rx * y * y + ry * ry * x * x)),
-                    cx = k * rx * y / ry + (x1 + x22) / 2,
-                    cy = k * -ry * x / rx + (y1 + y22) / 2,
-                    d = sweep_flag ? (this.isAbsolute ? "wa" : "wr") : (this.isAbsolute ? "at" : "ar"),
-                    left = Math.round(cx - rx),
-                    top = Math.round(cy - ry);
-                d += [left, top, Math.round(left + rx * 2), Math.round(top + ry * 2), Math.round(x1), Math.round(y1), Math.round(x22), Math.round(y22)].join(", ");
-                this.node.path = this.Path += d;
-                this.last.x = (this.isAbsolute ? 0 : this.last.x) + x2;
-                this.last.y = (this.isAbsolute ? 0 : this.last.y) + y2;
-                this.last.isAbsolute = this.isAbsolute;
-                this.attrs.path += (this.isAbsolute ? "A" : "a") + [rx, ry, 0, large_arc_flag, sweep_flag, x2, y2];
-                return this;
-            },
-            cplineTo: function (x1, y1, w1) {
-                if (!w1) {
-                    return this.lineTo(x1, y1);
-                } else {
-                    var x = Math.round(parseFloat(x1)) - 1,
-                        y = Math.round(parseFloat(y1)) - 1,
-                        w = Math.round(parseFloat(w1)),
-                        d = this.isAbsolute ? "c" : "v",
-                        attr = [Math.round(this.last.x) - 1 + w, Math.round(this.last.y) - 1, x - w, y, x, y],
-                        svgattr = [this.last.x + w1, this.last.y, x1 - w1, y1, x1, y1];
-                    d += attr.join(" ") + " ";
-                    this.last.x = (this.isAbsolute ? 0 : this.last.x) + attr[4];
-                    this.last.y = (this.isAbsolute ? 0 : this.last.y) + attr[5];
-                    this.last.bx = attr[2];
-                    this.last.by = attr[3];
-                    this.node.path = this.Path += d;
-                    this.attrs.path += (this.isAbsolute ? "C" : "c") + svgattr;
-                    return this;
+        var path2vml = function (path) {
+            var pa = path2curve(path);
+            for (var i = 0, ii = pa.length; i < ii; i++) {
+                for (var j = 0, jj = pa[i].length; j < jj; j++) {
+                    pa[i][j] = isNaN(pa[i][j]) ? pa[i][j] : Math.round(pa[i][j]);
                 }
-            },
-            curveTo: function () {
-                var d = this.isAbsolute ? "c" : "v";
-                if (arguments.length == 6) {
-                    this.last.bx = (this.isAbsolute ? 0 : this.last.x) + parseFloat(arguments[2]);
-                    this.last.by = (this.isAbsolute ? 0 : this.last.y) + parseFloat(arguments[3]);
-                    this.last.x = (this.isAbsolute ? 0 : this.last.x) + parseFloat(arguments[4]);
-                    this.last.y = (this.isAbsolute ? 0 : this.last.y) + parseFloat(arguments[5]);
-                    d += [Math.round(parseFloat(arguments[0])) - 1,
-                         Math.round(parseFloat(arguments[1])) - 1,
-                         Math.round(parseFloat(arguments[2])) - 1,
-                         Math.round(parseFloat(arguments[3])) - 1,
-                         Math.round(parseFloat(arguments[4])) - 1,
-                         Math.round(parseFloat(arguments[5])) - 1].join(" ") + " ";
-                    this.last.isAbsolute = this.isAbsolute;
-                    this.attrs.path += (this.isAbsolute ? "C" : "c") + Array.prototype.splice.call(arguments, 0, arguments.length);
-                }
-                if (arguments.length == 4) {
-                    var bx = this.last.x * 2 - this.last.bx,
-                        by = this.last.y * 2 - this.last.by;
-                    this.last.bx = (this.isAbsolute ? 0 : this.last.x) + parseFloat(arguments[0]);
-                    this.last.by = (this.isAbsolute ? 0 : this.last.y) + parseFloat(arguments[1]);
-                    this.last.x = (this.isAbsolute ? 0 : this.last.x) + parseFloat(arguments[2]);
-                    this.last.y = (this.isAbsolute ? 0 : this.last.y) + parseFloat(arguments[3]);
-                    d += [Math.round(bx) - 1, Math.round(by) - 1,
-                         Math.round(parseFloat(arguments[0])) - 1,
-                         Math.round(parseFloat(arguments[1])) - 1,
-                         Math.round(parseFloat(arguments[2])) - 1,
-                         Math.round(parseFloat(arguments[3])) - 1].join(" ") + " ";
-                     this.attrs.path += (this.isAbsolute ? "S" : "s") + Array.prototype.splice.call(arguments, 0, arguments.length);
-                }
-                this.node.path = this.Path += d;
-                return this;
-            },
-            qcurveTo: function () {
-                var lx = Math.round(this.last.x) - 1,
-                    ly = Math.round(this.last.y) - 1,
-                    res = [];
-                if (arguments.length == 4) {
-                    this.last.qx = (!this.isAbsolute * this.last.x) + parseFloat(arguments[0]);
-                    this.last.qy = (!this.isAbsolute * this.last.y) + parseFloat(arguments[1]);
-                    this.last.x = (!this.isAbsolute * this.last.x) + parseFloat(arguments[2]);
-                    this.last.y = (!this.isAbsolute * this.last.y) + parseFloat(arguments[3]);
-                    res = [this.last.qx, this.last.qy, this.last.x, this.last.y];
-                    this.last.isAbsolute = this.isAbsolute;
-                    this.attrs.path += (this.isAbsolute ? "Q" : "q") + Array.prototype.splice.call(arguments, 0, arguments.length);
-                }
-                if (arguments.length == 2) {
-                    this.last.qx = this.last.x * 2 - this.last.qx;
-                    this.last.qy = this.last.y * 2 - this.last.qy;
-                    this.last.x = (!this.isAbsolute * this.last.x) + parseFloat(arguments[2]);
-                    this.last.y = (!this.isAbsolute * this.last.y) + parseFloat(arguments[3]);
-                    res = [this.last.qx, this.last.qy, this.last.x, this.last.y];
-                     this.attrs.path += (this.isAbsolute ? "T" : "t") + Array.prototype.splice.call(arguments, 0, arguments.length);
-                }
-                var d = "c" + [
-                        Math.round(2 / 3 * res[0] + 1 / 3 * lx) - 1,
-                        Math.round(2 / 3 * res[1] + 1 / 3 * ly) - 1,
-                        Math.round(2 / 3 * res[0] + 1 / 3 * res[2]) - 1,
-                        Math.round(2 / 3 * res[1] + 1 / 3 * res[3]) - 1,
-                        Math.round(res[2]) - 1,
-                        Math.round(res[3]) - 1
-                    ].join(" ") + " ";
-                this.node.path = this.Path += d;
-                return this;
-            },
-            addRoundedCorner: addRoundedCorner,
-            andClose: function () {
-                this.node.path = (this.Path += "x");
-                this.attrs.path += "z";
-                return this;
             }
+            return (pa + "").toLowerCase().replace(/z/g, "x");
+        };
+        R.toString = function () {
+            return  "Your browser doesn\u2019t support SVG. Assuming it is Internet Explorer and falling down to VML.\nYou are running Rapha\u00ebl " + this.version;
         };
         var thePath = function (params, pathString, VML) {
-            params = params || {};
             var g = createNode("group"), gl = g.style;
             gl.position = "absolute";
             gl.left = 0;
@@ -1594,17 +1444,16 @@ window.Raphael = (function () {
                 p[method] = pathMethods[method];
             }
             
-            if (pathString) {
-                p.absolutely();
-                p.attrs.path = "";
-                paper.pathfinder(p, "" + pathString);
-            }
             if (params) {
                 params.fill = params.fill || "none";
                 params.stroke = params.stroke || "#000";
             } else {
                 params = {fill: "none", stroke: "#000"};
             }
+            if (pathString) {
+                p.node.path = path2vml(pathString);
+                p.attrs.path = pathString;
+            }
             setFillAndStroke(p, params);
             if (params.gradient) {
                 addGradientFill(p, params.gradient);
@@ -1626,10 +1475,7 @@ window.Raphael = (function () {
             params.title && (node.title = params.title);
             params.target && (node.target = params.target);
             if (params.path && o.type == "path") {
-                o.Path = "";
-                o.path = [];
-                o.attrs.path = "";
-                paper.pathfinder(o, params.path);
+                o.node.path = path2vml(params.path);
             }
             if (params.rotation != null) {
                 o.rotate(params.rotation, true);
@@ -1722,7 +1568,8 @@ window.Raphael = (function () {
                 a["font-size"] && (s.fontSize = a["font-size"]);
                 a["font-weight"] && (s.fontWeight = a["font-weight"]);
                 a["font-style"] && (s.fontStyle = a["font-style"]);
-                paper.span.innerText = res.node.string;
+                paper.span.innerHTML = "";
+                paper.span.appendChild(doc.createTextNode(res.node.string));
                 res.W = a.w = paper.span.offsetWidth;
                 res.H = a.h = paper.span.offsetHeight;
                 res.X = a.x;
@@ -1824,6 +1671,9 @@ window.Raphael = (function () {
         };
         Element.prototype.rotate = function (deg, cx, cy) {
             if (deg == null) {
+                if (this._.rt.cx) {
+                    return [this._.rt.deg, this._.rt.cx, this._.rt.cy].join(" ");
+                }
                 return this._.rt.deg;
             }
             deg = (deg + "").split(separator);
@@ -1837,9 +1687,7 @@ window.Raphael = (function () {
             } else {
                 this._.rt.deg += deg;
             }
-            if (cy == null) {
-                cx = null;
-            }
+            (cy == null) && (cx = null);
             this._.rt.cx = cx;
             this._.rt.cy = cy;
             this.setBox(this.attrs, cx, cy);
@@ -1981,6 +1829,12 @@ window.Raphael = (function () {
                 if (arguments[0] == "translation") {
                     return this.translate();
                 }
+                if (arguments[0] == "rotation") {
+                    return this.rotate();
+                }
+                if (arguments[0] == "scale") {
+                    return this.scale();
+                }
                 return this.attrs[arguments[0]];
             }
             if (this.attrs && arguments.length == 1 && R.isArray(arguments[0])) {
@@ -2117,6 +1971,7 @@ window.Raphael = (function () {
             g.appendChild(o);
             var res = new Element(o, g, vml);
             res.type = "image";
+            res.attrs.src = src;
             res.attrs.x = x;
             res.attrs.y = y;
             res.attrs.w = w;
@@ -2158,6 +2013,7 @@ window.Raphael = (function () {
             res.shape = el;
             res.textpath = path;
             res.type = "text";
+            res.attrs.text = text;
             res.attrs.x = x;
             res.attrs.y = y;
             res.attrs.w = 1;
@@ -2335,6 +2191,11 @@ window.Raphael = (function () {
         return theEllipse(this, x, y, rx, ry);
     };
     paper.path = function (params, pathString) {
+        if (typeof params == "string" && !pathString) {
+            pathString = params;
+            params = {};
+        }
+        params = params || {};
         return thePath(params, pathString, this);
     };
     paper.image = function (src, x, y, w, h) {
@@ -2356,47 +2217,6 @@ window.Raphael = (function () {
         }
         return this.path({stroke: color, "stroke-width": 1}, path.join(","));
     };
-    paper.pathfinder = function (p, path) {
-        var commands = {
-            M: function (x, y) {
-                this.moveTo(x, y);
-            },
-            C: function (x1, y1, x2, y2, x3, y3) {
-                this.curveTo(x1, y1, x2, y2, x3, y3);
-            },
-            Q: function (x1, y1, x2, y2) {
-                this.qcurveTo(x1, y1, x2, y2);
-            },
-            T: function (x, y) {
-                this.qcurveTo(x, y);
-            },
-            S: function (x1, y1, x2, y2) {
-                p.curveTo(x1, y1, x2, y2);
-            },
-            L: function (x, y) {
-                p.lineTo(x, y);
-            },
-            H: function (x) {
-                this.lineTo(x, this.last.y);
-            },
-            V: function (y) {
-                this.lineTo(this.last.x, y);
-            },
-            A: function (rx, ry, xaxisrotation, largearcflag, sweepflag, x, y) {
-                this.arcTo(rx, ry, largearcflag, sweepflag, x, y);
-            },
-            Z: function () {
-                this.andClose();
-            }
-        };
-
-        path = pathToAbsolute(path);
-        for (var i = 0, ii = path.length; i < ii; i++) {
-            var b = path[i].shift();
-            commands[b].apply(p, path[i]);
-            path[i].unshift(b);
-        }
-    };
     paper.set = function (itemsArray) {
         return new Set(itemsArray);
     };
@@ -2406,7 +2226,7 @@ window.Raphael = (function () {
     };
     Element.prototype.scale = function (x, y, cx, cy) {
         if (x == null && y == null) {
-            return {x: this._.sx, y: this._.sy, toString: function () { return this.x.toFixed(3) + " " + this.y.toFixed(3); }};
+            return {x: this._.sx, y: this._.sy, toString: function () { return +this.x.toFixed(3) + " " + (+this.y.toFixed(3)); }};
         }
         y = y || x;
         !+y && (y = x);
@@ -2610,7 +2430,7 @@ window.Raphael = (function () {
                         };
                         break;
                     case "path":
-                        var pathes = pathEqualiser(from[attr], to[attr]);
+                        var pathes = path2curve(from[attr], to[attr]);
                         from[attr] = pathes[0];
                         to[attr] = pathes[1];
                         diff[attr] = [];
@@ -2668,7 +2488,7 @@ window.Raphael = (function () {
                             for (var i = 0, ii = from[attr].length; i < ii; i++) {
                                 now[i] = [from[attr][i][0]];
                                 for (var j = 1, jj = from[attr][i].length; j < jj; j++) {
-                                    now[i][j] = from[attr][i][j] + pos * ms * diff[attr][i][j];
+                                    now[i][j] = +from[attr][i][j] + pos * ms * diff[attr][i][j];
                                 }
                                 now[i] = now[i].join(" ");
                             }
@@ -2705,7 +2525,7 @@ window.Raphael = (function () {
                 (t.x || t.y) && that.translate(-t.x, -t.y);
                 that.attr(params);
                 clearTimeout(that.animation_in_progress);
-                paper.safari();
+                R.svg && paper.safari();
                 (typeof callback == "function") && callback.call(that);
             }
             prev = time;