diff --git a/demojs.html b/demojs.html
index 65dee12..22483a2 100644
--- a/demojs.html
+++ b/demojs.html
@@ -13,6 +13,8 @@
+
+
diff --git a/mina.js b/mina.js
index 7a716f7..b8c9b4c 100644
--- a/mina.js
+++ b/mina.js
@@ -21,7 +21,18 @@ window.mina = (function () {
function (callback) {
setTimeout(callback, 16);
},
+ isArray = Array.isArray || function (a) {
+ return a instanceof Array ||
+ Object.prototype.toString.call(a) == "[object Array]";
+ },
diff = function (a, b, A, B) {
+ if (isArray(a)) {
+ res = [];
+ for (var i = 0, ii = a.length; i < ii; i++) {
+ res[i] = diff(a[i], b, A[i], B);
+ }
+ return res;
+ }
var dif = (A - a) / (B - b);
return function (bb) {
return a + dif * (bb - b);
@@ -31,12 +42,24 @@ window.mina = (function () {
return +new Date;
},
frame = function () {
+ var value, one;
for (var i = 0; i < animations.length; i++) {
var a = animations[i],
- gen = a.b + (a.gen() - a.b) * a["*"] + a["+"],
- value = a.dif(gen),
+ gen = a.b + (a.gen() - a.b) * a["*"] + a["+"];
+ if (isArray(a.a)) {
+ value = [];
+ for (var j = 0, jj = a.a.length; j < jj; j++) {
+ value[j] = a.dif[j](gen);
+ one = a.A[j] - a.a[j];
+ value[j] = one ?
+ a.a[j] + a.easing((value[j] - a.a[j]) / one) * one :
+ a.a[j];
+ }
+ } else {
+ value = a.dif(gen);
one = a.A - a.a;
- value = a.a + a.easing((value - a.a) / one) * one;
+ value = a.a + a.easing((value - a.a) / one) * one;
+ }
try {
if (a.stopper(gen)) {
if (--a.iterations) {
diff --git a/savage.equal.js b/savage.equal.js
new file mode 100644
index 0000000..a04ac0b
--- /dev/null
+++ b/savage.equal.js
@@ -0,0 +1,135 @@
+Savage.plugin(function (Savage, Element, Paper, glob) {
+ var names = {},
+ reUnit = /[a-z]+$/i,
+ Str = String;
+ names.stroke = names.fill = "colour";
+ function getEmpty(item) {
+ 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 [l, 0, item[2], item[3]];
+ } else {
+ return [l, 0];
+ }
+ case "s": if (item.length == 5) {
+ return [l, 1, 1, item[3], item[4]];
+ } else if (item.length == 3) {
+ return [l, 1, 1];
+ } else {
+ return [l, 1];
+ }
+ }
+ }
+ function equaliseTransform(t1, t2) {
+ t2 = Str(t2).replace(/\.{3}|\u2026/g, t1);
+ t1 = Savage.parseTransformString(t1) || [];
+ t2 = Savage.parseTransformString(t2) || [];
+ var maxlength = Math.max(t1.length, t2.length),
+ from = [],
+ to = [],
+ i = 0, j, jj,
+ tt1, tt2;
+ for (; i < maxlength; i++) {
+ tt1 = t1[i] || getEmpty(t2[i]);
+ tt2 = t2[i] || getEmpty(tt1);
+ if ((tt1[0] != tt2[0]) ||
+ (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;
+ }
+ from[i] = [];
+ to[i] = [];
+ for (j = 0, jj = Math.max(tt1.length, tt2.length); j < jj; j++) {
+ j in tt1 && (from[i][j] = tt1[j]);
+ j in tt2 && (to[i][j] = tt2[j]);
+ }
+ }
+ return {
+ from: path2array(from),
+ to: path2array(to),
+ f: getPath(from)
+ };
+ }
+ function getNumber(val) {
+ return val;
+ }
+ function getUnit(unit) {
+ return function (val) {
+ return +val.toFixed(3) + unit;
+ };
+ }
+ function getColour(clr) {
+ return Savage.rgb(clr[0], clr[1], clr[2]);
+ }
+ function getPath(path) {
+ var k = 0, i, ii, j, jj, out, a, b = [];
+ for (i = 0, ii = path.length; i < ii; i++) {
+ out = "[";
+ a = ['"' + path[i][0] + '"'];
+ for (j = 1, jj = path[i].length; j < jj; j++) {
+ a[j] = "val[" + (k++) + "]";
+ }
+ out += a + "]";
+ b[i] = out;
+ }
+ return Function("val", "return Savage.path2string.call([" + b + "]);");
+ }
+ function path2array(path) {
+ var out = [];
+ for (var i = 0, ii = path.length; i < ii; i++) {
+ for (var j = 1, jj = path[i].length; j < jj; j++) {
+ out.push(path[i][j]);
+ }
+ }
+ return out;
+ }
+ Element.prototype.equal = function (name, b) {
+ var A, B, a = Str(this.attr(name) || "");
+ if (a == +a && b == +b) {
+ return {
+ from: +a,
+ to: +b,
+ f: getNumber
+ };
+ }
+ if (names[name] == "colour") {
+ A = Savage.color(a);
+ B = Savage.color(b);
+ return {
+ from: [A.r, A.g, A.b],
+ to: [B.r, B.g, B.b],
+ f: getColour
+ };
+ }
+ if (name == "transform" || name == "gradientTransform" || name == "patternTransform") {
+ // TODO: b could be an SVG transform string or matrix
+ return equaliseTransform(a.local, b);
+ }
+ if (name == "d" || name == "path") {
+ A = Savage.path2curve(a, b);
+ return {
+ from: path2array(A[0]),
+ to: path2array(A[1]),
+ f: getPath(A[0])
+ };
+ }
+ var aUnit = a.match(reUnit),
+ bUnit = b.match(reUnit);
+ if (aUnit && aUnit == bUnit) {
+ return {
+ from: parseFloat(a),
+ to: parseFloat(b),
+ f: getUnit(aUnit)
+ };
+ } else {
+ return {
+ from: this.asPX(name),
+ to: this.asPX(name, b),
+ f: getNumber
+ };
+ }
+ };
+});
\ No newline at end of file
diff --git a/savage.mouse.js b/savage.mouse.js
new file mode 100644
index 0000000..b4153b9
--- /dev/null
+++ b/savage.mouse.js
@@ -0,0 +1,540 @@
+Savage.plugin(function (Savage, Element, Paper, glob) {
+ var elproto = Element.prototype,
+ has = "hasOwnProperty",
+ supportsTouch = "createTouch" in glob.doc,
+ events = [
+ "click", "dblclick", "mousedown", "mousemove", "mouseout",
+ "mouseover", "mouseup", "touchstart", "touchmove", "touchend",
+ "touchcancel"
+ ],
+ touchMap = {
+ mousedown: "touchstart",
+ mousemove: "touchmove",
+ mouseup: "touchend"
+ },
+ getScroll = function (xy) {
+ var name = xy == "y" ? "scrollTop" : "scrollLeft";
+ return glob.doc.documentElement[name] || glob.doc.body[name];
+ },
+ preventDefault = function () {
+ this.returnValue = false;
+ },
+ preventTouch = function () {
+ return this.originalEvent.preventDefault();
+ },
+ stopPropagation = function () {
+ this.cancelBubble = true;
+ },
+ stopTouch = function () {
+ return this.originalEvent.stopPropagation();
+ },
+ addEvent = (function () {
+ if (glob.doc.addEventListener) {
+ return function (obj, type, fn, element) {
+ var realName = supportsTouch && touchMap[type] ? touchMap[type] : type,
+ f = function (e) {
+ var scrollY = getScroll("y"),
+ scrollX = getScroll("x"),
+ x = e.clientX + scrollX,
+ y = e.clientY + scrollY;
+ if (supportsTouch && touchMap[has](type)) {
+ for (var i = 0, ii = e.targetTouches && e.targetTouches.length; i < ii; i++) {
+ if (e.targetTouches[i].target == obj) {
+ var olde = e;
+ e = e.targetTouches[i];
+ e.originalEvent = olde;
+ e.preventDefault = preventTouch;
+ e.stopPropagation = stopTouch;
+ break;
+ }
+ }
+ }
+ return fn.call(element, e, x, y);
+ };
+ obj.addEventListener(realName, f, false);
+ return function () {
+ obj.removeEventListener(realName, f, false);
+ return true;
+ };
+ };
+ } else if (glob.doc.attachEvent) {
+ return function (obj, type, fn, element) {
+ var f = function (e) {
+ e = e || glob.win.event;
+ var scrollY = getScroll("y"),
+ scrollX = getScroll("x"),
+ x = e.clientX + scrollX,
+ y = e.clientY + scrollY;
+ e.preventDefault = e.preventDefault || preventDefault;
+ e.stopPropagation = e.stopPropagation || stopPropagation;
+ return fn.call(element, e, x, y);
+ };
+ obj.attachEvent("on" + type, f);
+ var detacher = function () {
+ obj.detachEvent("on" + type, f);
+ return true;
+ };
+ return detacher;
+ };
+ }
+ })(),
+ drag = [],
+ dragMove = function (e) {
+ var x = e.clientX,
+ y = e.clientY,
+ scrollY = getScroll("y"),
+ scrollX = getScroll("x"),
+ dragi,
+ j = drag.length;
+ while (j--) {
+ dragi = drag[j];
+ if (supportsTouch) {
+ var i = e.touches.length,
+ touch;
+ while (i--) {
+ touch = e.touches[i];
+ if (touch.identifier == dragi.el._drag.id) {
+ x = touch.clientX;
+ y = touch.clientY;
+ (e.originalEvent ? e.originalEvent : e).preventDefault();
+ break;
+ }
+ }
+ } else {
+ e.preventDefault();
+ }
+ var node = dragi.el.node,
+ o,
+ next = node.nextSibling,
+ parent = node.parentNode,
+ display = node.style.display;
+ // glob.win.opera && parent.removeChild(node);
+ // node.style.display = "none";
+ // o = dragi.el.paper.getElementByPoint(x, y);
+ // node.style.display = display;
+ // glob.win.opera && (next ? parent.insertBefore(node, next) : parent.appendChild(node));
+ // o && eve("savage.drag.over." + dragi.el.id, dragi.el, o);
+ x += scrollX;
+ y += scrollY;
+ eve("savage.drag.move." + dragi.el.id, dragi.move_scope || dragi.el, x - dragi.el._drag.x, y - dragi.el._drag.y, x, y, e);
+ }
+ },
+ dragUp = function (e) {
+ Savage.unmousemove(dragMove).unmouseup(dragUp);
+ var i = drag.length,
+ dragi;
+ while (i--) {
+ dragi = drag[i];
+ dragi.el._drag = {};
+ eve("savage.drag.end." + dragi.el.id, dragi.end_scope || dragi.start_scope || dragi.move_scope || dragi.el, e);
+ }
+ drag = [];
+ };
+ /*\
+ * Element.click
+ [ method ]
+ **
+ * Adds event handler for click for the element.
+ > Parameters
+ - handler (function) handler for the event
+ = (object) @Element
+ \*/
+ /*\
+ * Element.unclick
+ [ method ]
+ **
+ * Removes event handler for click for the element.
+ > Parameters
+ - handler (function) handler for the event
+ = (object) @Element
+ \*/
+
+ /*\
+ * Element.dblclick
+ [ method ]
+ **
+ * Adds event handler for double click for the element.
+ > Parameters
+ - handler (function) handler for the event
+ = (object) @Element
+ \*/
+ /*\
+ * Element.undblclick
+ [ method ]
+ **
+ * Removes event handler for double click for the element.
+ > Parameters
+ - handler (function) handler for the event
+ = (object) @Element
+ \*/
+
+ /*\
+ * Element.mousedown
+ [ method ]
+ **
+ * Adds event handler for mousedown for the element.
+ > Parameters
+ - handler (function) handler for the event
+ = (object) @Element
+ \*/
+ /*\
+ * Element.unmousedown
+ [ method ]
+ **
+ * Removes event handler for mousedown for the element.
+ > Parameters
+ - handler (function) handler for the event
+ = (object) @Element
+ \*/
+
+ /*\
+ * Element.mousemove
+ [ method ]
+ **
+ * Adds event handler for mousemove for the element.
+ > Parameters
+ - handler (function) handler for the event
+ = (object) @Element
+ \*/
+ /*\
+ * Element.unmousemove
+ [ method ]
+ **
+ * Removes event handler for mousemove for the element.
+ > Parameters
+ - handler (function) handler for the event
+ = (object) @Element
+ \*/
+
+ /*\
+ * Element.mouseout
+ [ method ]
+ **
+ * Adds event handler for mouseout for the element.
+ > Parameters
+ - handler (function) handler for the event
+ = (object) @Element
+ \*/
+ /*\
+ * Element.unmouseout
+ [ method ]
+ **
+ * Removes event handler for mouseout for the element.
+ > Parameters
+ - handler (function) handler for the event
+ = (object) @Element
+ \*/
+
+ /*\
+ * Element.mouseover
+ [ method ]
+ **
+ * Adds event handler for mouseover for the element.
+ > Parameters
+ - handler (function) handler for the event
+ = (object) @Element
+ \*/
+ /*\
+ * Element.unmouseover
+ [ method ]
+ **
+ * Removes event handler for mouseover for the element.
+ > Parameters
+ - handler (function) handler for the event
+ = (object) @Element
+ \*/
+
+ /*\
+ * Element.mouseup
+ [ method ]
+ **
+ * Adds event handler for mouseup for the element.
+ > Parameters
+ - handler (function) handler for the event
+ = (object) @Element
+ \*/
+ /*\
+ * Element.unmouseup
+ [ method ]
+ **
+ * Removes event handler for mouseup for the element.
+ > Parameters
+ - handler (function) handler for the event
+ = (object) @Element
+ \*/
+
+ /*\
+ * Element.touchstart
+ [ method ]
+ **
+ * Adds event handler for touchstart for the element.
+ > Parameters
+ - handler (function) handler for the event
+ = (object) @Element
+ \*/
+ /*\
+ * Element.untouchstart
+ [ method ]
+ **
+ * Removes event handler for touchstart for the element.
+ > Parameters
+ - handler (function) handler for the event
+ = (object) @Element
+ \*/
+
+ /*\
+ * Element.touchmove
+ [ method ]
+ **
+ * Adds event handler for touchmove for the element.
+ > Parameters
+ - handler (function) handler for the event
+ = (object) @Element
+ \*/
+ /*\
+ * Element.untouchmove
+ [ method ]
+ **
+ * Removes event handler for touchmove for the element.
+ > Parameters
+ - handler (function) handler for the event
+ = (object) @Element
+ \*/
+
+ /*\
+ * Element.touchend
+ [ method ]
+ **
+ * Adds event handler for touchend for the element.
+ > Parameters
+ - handler (function) handler for the event
+ = (object) @Element
+ \*/
+ /*\
+ * Element.untouchend
+ [ method ]
+ **
+ * Removes event handler for touchend for the element.
+ > Parameters
+ - handler (function) handler for the event
+ = (object) @Element
+ \*/
+
+ /*\
+ * Element.touchcancel
+ [ method ]
+ **
+ * Adds event handler for touchcancel for the element.
+ > Parameters
+ - handler (function) handler for the event
+ = (object) @Element
+ \*/
+ /*\
+ * Element.untouchcancel
+ [ method ]
+ **
+ * Removes event handler for touchcancel for the element.
+ > Parameters
+ - handler (function) handler for the event
+ = (object) @Element
+ \*/
+ for (var i = events.length; i--;) {
+ (function (eventName) {
+ Savage[eventName] = elproto[eventName] = function (fn, scope) {
+ if (Savage.is(fn, "function")) {
+ this.events = this.events || [];
+ this.events.push({
+ name: eventName,
+ f: fn,
+ unbind: addEvent(this.shape || this.node || glob.doc, eventName, fn, scope || this)
+ });
+ }
+ return this;
+ };
+ Savage["un" + eventName] = elproto["un" + eventName] = function (fn) {
+ var events = this.events || [],
+ l = events.length;
+ while (l--) if (events[l].name == eventName && events[l].f == fn) {
+ events[l].unbind();
+ events.splice(l, 1);
+ !events.length && delete this.events;
+ return this;
+ }
+ return this;
+ };
+ })(events[i]);
+ }
+
+ /*\
+ * Element.data
+ [ method ]
+ **
+ * Adds or retrieves given value asociated with given key.
+ **
+ * See also @Element.removeData
+ > Parameters
+ - key (string) key to store data
+ - value (any) #optional value to store
+ = (object) @Element
+ * or, if value is not specified:
+ = (any) value
+ > Usage
+ | for (var i = 0, i < 5, i++) {
+ | paper.circle(10 + 15 * i, 10, 10)
+ | .attr({fill: "#000"})
+ | .data("i", i)
+ | .click(function () {
+ | alert(this.data("i"));
+ | });
+ | }
+ \*/
+ elproto.data = function (key, value) {
+ var data = eldata[this.id] = eldata[this.id] || {};
+ if (arguments.length == 1) {
+ if (Savage.is(key, "object")) {
+ for (var i in key) if (key[has](i)) {
+ this.data(i, key[i]);
+ }
+ return this;
+ }
+ eve("savage.data.get." + this.id, this, data[key], key);
+ return data[key];
+ }
+ data[key] = value;
+ eve("savage.data.set." + this.id, this, value, key);
+ return this;
+ };
+ /*\
+ * Element.removeData
+ [ method ]
+ **
+ * Removes value associated with an element by given key.
+ * If key is not provided, removes all the data of the element.
+ > Parameters
+ - key (string) #optional key
+ = (object) @Element
+ \*/
+ elproto.removeData = function (key) {
+ if (key == null) {
+ eldata[this.id] = {};
+ } else {
+ eldata[this.id] && delete eldata[this.id][key];
+ }
+ return this;
+ };
+ /*\
+ * Element.hover
+ [ method ]
+ **
+ * Adds event handlers for hover for the element.
+ > Parameters
+ - f_in (function) handler for hover in
+ - f_out (function) handler for hover out
+ - icontext (object) #optional context for hover in handler
+ - ocontext (object) #optional context for hover out handler
+ = (object) @Element
+ \*/
+ elproto.hover = function (f_in, f_out, scope_in, scope_out) {
+ return this.mouseover(f_in, scope_in).mouseout(f_out, scope_out || scope_in);
+ };
+ /*\
+ * Element.unhover
+ [ method ]
+ **
+ * Removes event handlers for hover for the element.
+ > Parameters
+ - f_in (function) handler for hover in
+ - f_out (function) handler for hover out
+ = (object) @Element
+ \*/
+ elproto.unhover = function (f_in, f_out) {
+ return this.unmouseover(f_in).unmouseout(f_out);
+ };
+ var draggable = [];
+ /*\
+ * Element.drag
+ [ method ]
+ **
+ * Adds event handlers for drag of the element.
+ > Parameters
+ - onmove (function) handler for moving
+ - onstart (function) handler for drag start
+ - onend (function) handler for drag end
+ - mcontext (object) #optional context for moving handler
+ - scontext (object) #optional context for drag start handler
+ - econtext (object) #optional context for drag end handler
+ * Additionaly following `drag` events will be triggered: `drag.start.` on start,
+ * `drag.end.` on end and `drag.move.` on every move. When element will be dragged over another element
+ * `drag.over.` will be fired as well.
+ *
+ * Start event and start handler will be called in specified context or in context of the element with following parameters:
+ o x (number) x position of the mouse
+ o y (number) y position of the mouse
+ o event (object) DOM event object
+ * Move event and move handler will be called in specified context or in context of the element with following parameters:
+ o dx (number) shift by x from the start point
+ o dy (number) shift by y from the start point
+ o x (number) x position of the mouse
+ o y (number) y position of the mouse
+ o event (object) DOM event object
+ * End event and end handler will be called in specified context or in context of the element with following parameters:
+ o event (object) DOM event object
+ = (object) @Element
+ \*/
+ elproto.drag = function (onmove, onstart, onend, move_scope, start_scope, end_scope) {
+ if (!arguments.length) {
+ var origTransform;
+ return this.drag(function (dx, dy) {
+ this.attr({
+ transform: origTransform + (origTransform ? "T" : "t") + [dx, dy]
+ });
+ }, function () {
+ origTransform = this.transform().local;
+ });
+ }
+ function start(e) {
+ (e.originalEvent || e).preventDefault();
+ var scrollY = getScroll("y"),
+ scrollX = getScroll("x");
+ this._drag.x = e.clientX + scrollX;
+ this._drag.y = e.clientY + scrollY;
+ this._drag.id = e.identifier;
+ !drag.length && Savage.mousemove(dragMove).mouseup(dragUp);
+ drag.push({el: this, move_scope: move_scope, start_scope: start_scope, end_scope: end_scope});
+ onstart && eve.on("savage.drag.start." + this.id, onstart);
+ onmove && eve.on("savage.drag.move." + this.id, onmove);
+ onend && eve.on("savage.drag.end." + this.id, onend);
+ eve("savage.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;
+ };
+ /*\
+ * Element.onDragOver
+ [ method ]
+ **
+ * Shortcut for assigning event handler for `drag.over.` event, where id is id of the element (see @Element.id).
+ > Parameters
+ - f (function) handler for event, first argument would be the element you are dragging over
+ \*/
+ elproto.onDragOver = function (f) {
+ f ? eve.on("savage.drag.over." + this.id, f) : eve.unbind("savage.drag.over." + this.id);
+ };
+ /*\
+ * Element.undrag
+ [ method ]
+ **
+ * Removes all drag event handlers from given element.
+ \*/
+ elproto.undrag = function () {
+ var i = draggable.length;
+ while (i--) if (draggable[i].el == this) {
+ this.unmousedown(draggable[i].start);
+ draggable.splice(i, 1);
+ eve.unbind("savage.drag.*." + this.id);
+ }
+ !draggable.length && Savage.unmousemove(dragMove).unmouseup(dragUp);
+ };
+});
\ No newline at end of file
diff --git a/svg.js b/svg.js
index 045aba3..c32befd 100644
--- a/svg.js
+++ b/svg.js
@@ -37,6 +37,7 @@ var has = "hasOwnProperty",
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\(([^,]+),([^,]+),([^,]+),([^\)]+)\)/,
+ reURLValue = /^url\(#?([^)]+)\)$/,
spaces = "\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029",
separator = new RegExp("[," + spaces + "]+"),
whitespace = new RegExp("[" + spaces + "]", "g"),
@@ -860,15 +861,105 @@ function box(x, y, width, height) {
};
}
// Transformations
-function path2string() {
+var path2string = Savage.path2string = function () {
return this.join(",").replace(p2s, "$1");
-}
+};
function pathClone(pathArray) {
var res = clone(pathArray);
res.toString = path2string;
return res;
}
-function parseTransformString(TString) {
+
+// http://schepers.cc/getting-to-the-point
+function catmullRom2bezier(crp, z) {
+ var d = [];
+ for (var i = 0, iLen = crp.length; iLen - 2 * !z > i; i += 2) {
+ var p = [
+ {x: +crp[i - 2], 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 (z) {
+ if (!i) {
+ p[0] = {x: +crp[iLen - 2], y: +crp[iLen - 1]};
+ } else if (iLen - 4 == i) {
+ p[3] = {x: +crp[0], y: +crp[1]};
+ } else if (iLen - 2 == i) {
+ p[2] = {x: +crp[0], y: +crp[1]};
+ p[3] = {x: +crp[2], y: +crp[3]};
+ }
+ } else {
+ if (iLen - 4 == i) {
+ p[3] = p[2];
+ } else if (!i) {
+ p[0] = {x: +crp[i], 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;
+}
+/*\
+ * Savage.parsePathString
+ [ method ]
+ **
+ * Utility method
+ **
+ * Parses given path string into an array of arrays of path segments.
+ > Parameters
+ - pathString (string|array) path string or array of segments (in the last case it will be returned straight away)
+ = (array) array of segments.
+\*/
+Savage.parsePathString = function (pathString) {
+ if (!pathString) {
+ return null;
+ }
+ var pth = paths(pathString);
+ if (pth.arr) {
+ return pathClone(pth.arr);
+ }
+
+ var paramCounts = {a: 7, c: 6, h: 1, l: 2, m: 2, r: 4, q: 4, s: 4, t: 2, v: 1, z: 0},
+ data = [];
+ if (is(pathString, "array") && is(pathString[0], "array")) { // rough assumption
+ data = pathClone(pathString);
+ }
+ if (!data.length) {
+ Str(pathString).replace(pathCommand, function (a, b, c) {
+ var params = [],
+ name = b.toLowerCase();
+ c.replace(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";
+ }
+ 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;
+ }
+ }
+ });
+ }
+ data.toString = path2string;
+ pth.arr = pathClone(data);
+ return data;
+};
+var parseTransformString = Savage.parseTransformString = function (TString) {
if (!TString) {
return null;
}
@@ -889,7 +980,7 @@ function parseTransformString(TString) {
}
data.toString = path2string;
return data;
-}
+};
function svgTransform2string(tstr) {
var res = [];
tstr = tstr.replace(/(?:^|\s)(\w+)\(([^)]+)\)/g, function (all, name, params) {
@@ -1086,7 +1177,7 @@ function ellipsePath(x, y, rx, ry, a) {
res.toString = path2string;
return res;
}
-function unit2px(el) {
+function unit2px(el, name, value) {
var defs = el.paper.defs,
out = {},
mgr = el.paper.measurer;
@@ -1115,28 +1206,57 @@ function unit2px(el) {
$(mgr, {height: val});
return mgr.getBBox().height;
}
+ function set(nam, f) {
+ if (name == null) {
+ out[nam] = f(el.attr(nam));
+ } else if (nam == name) {
+ out = f(value == null ? el.attr(nam) : value);
+ }
+ }
switch (el.type) {
case "rect":
- out.rx = getW(el.attr("rx"));
- out.ry = getH(el.attr("ry"));
+ set("rx", getW);
+ set("ry", getH);
case "image":
- out.width = getW(el.attr("width"));
- out.height = getH(el.attr("height"));
+ set("width", getW);
+ set("height", getH);
case "text":
- out.x = getW(el.attr("x"));
- out.y = getH(el.attr("y"));
+ set("x", getW);
+ set("y", getH);
break;
case "circle":
- out.cx = getW(el.attr("cx"));
- out.cy = getH(el.attr("cy"));
- out.r = getW(el.attr("r"));
+ set("cx", getW);
+ set("cy", getH);
+ set("r", getW);
break;
case "ellipse":
- out.cx = getW(el.attr("cx"));
- out.cy = getH(el.attr("cy"));
- out.rx = getW(el.attr("rx"));
- out.ry = getH(el.attr("ry"));
+ set("cx", getW);
+ set("cy", getH);
+ set("rx", getW);
+ set("ry", getH);
break;
+ case "line":
+ set("x1", getW);
+ set("x2", getW);
+ set("y1", getH);
+ set("y2", getH);
+ break;
+ case "marker":
+ set("refX", getW);
+ set("markerWidth", getW);
+ set("refY", getH);
+ set("markerHeight", getH);
+ break;
+ case "radialGradient":
+ set("fx", getW);
+ set("fy", getH);
+ break;
+ case "tspan":
+ set("dx", getW);
+ set("dy", getH);
+ break;
+ default:
+ out = null;
}
return out;
}
@@ -1223,7 +1343,7 @@ var pathDimensions = function (path) {
return pathClone(pth.rel);
}
if (!is(pathArray, array) || !is(pathArray && pathArray[0], array)) { // rough assumption
- pathArray = parsePathString(pathArray);
+ pathArray = Savage.parsePathString(pathArray);
}
var res = [],
x = 0,
@@ -1302,7 +1422,7 @@ var pathDimensions = function (path) {
return pathClone(pth.abs);
}
if (!is(pathArray, "array") || !is(pathArray && pathArray[0], "array")) { // rough assumption
- pathArray = parsePathString(pathArray);
+ pathArray = Savage.parsePathString(pathArray);
}
if (!pathArray || !pathArray.length) {
return [["M", 0, 0]];
@@ -1565,7 +1685,7 @@ var pathDimensions = function (path) {
max: {x: mmax.apply(0, x), y: mmax.apply(0, y)}
};
}),
- path2curve = cacher(function (path, path2) {
+ path2curve = Savage.path2curve = cacher(function (path, path2) {
var pth = !path2 && paths(path);
if (!path2 && pth.curve) {
return pathClone(pth.curve);
@@ -1764,17 +1884,9 @@ function arrayFirstValue(arr) {
}
return box(_.bbox);
};
- function prop(name) {
- return function () {
- return this[name];
- };
- }
- function always(x) {
- return function () {
- return x;
- };
- }
- var propString = prop("string");
+ var propString = function () {
+ return this.local;
+ };
elproto.transform = function (tstr) {
var _ = this._;
if (tstr == null) {
@@ -1864,6 +1976,9 @@ function arrayFirstValue(arr) {
}
return set;
};
+ elproto.asPX = function (attr, value) {
+ return unit2px(this, attr, value);
+ };
elproto.use = function () {
var use,
id = this.node.id;
@@ -1939,17 +2054,22 @@ function arrayFirstValue(arr) {
return p;
};
// animation
- function applyAttr(el, key) {
+ function applyAttr(el, key, f) {
var at = {};
return function (value) {
- at[key] = value;
+ at[key] = f ? f(value) : value;
el.attr(at);
};
}
elproto.animate = function (attrs, ms, callback) {
- var anims = [];
+ var anims = [], eq;
for (var key in attrs) if (attrs[has](key)) {
- anims.push(mina(+this.attr(key), +attrs[key], ms, applyAttr(this, key)));
+ if (this.equal) {
+ eq = this.equal(key, Str(attrs[key]));
+ anims.push(mina(eq.from, eq.to, ms, applyAttr(this, key, eq.f)));
+ } else {
+ anims.push(mina(+this.attr(key), +attrs[key], ms, applyAttr(this, key)));
+ }
}
};
}(Element.prototype));
@@ -2436,6 +2556,17 @@ eve.on("savage.util.grad.parse", function parseGrad(string) {
};
});
+eve.on("savage.util.attr.d", function (value) {
+ if (is(value, "array") && is(value[0], "array")) {
+ value = path2string.call(value);
+ }
+ value = Str(value);
+ if (value.match(/[ruo]/i)) {
+ value = pathToAbsolute(value);
+ }
+ $(this.node, {d: value});
+ eve.stop();
+});
eve.on("savage.util.attr.path", function (value) {
this.attr({d: value});
eve.stop();
@@ -2528,6 +2659,50 @@ eve.on("savage.util.getattr.transform", function () {
eve.stop();
return this.transform();
})(-1);
+// Markers
+(function () {
+ function getter(end) {
+ return function () {
+ eve.stop();
+ var style = glob.doc.defaultView.getComputedStyle(this.node, null).getPropertyValue("marker-" + end);
+ if (style == "none") {
+ return style;
+ } else {
+ return Savage(glob.doc.getElementById(style.match(reURLValue)[1]));
+ }
+ };
+ }
+ function setter(end) {
+ return function (value) {
+ eve.stop();
+ var name = "marker" + end.charAt(0).toUpperCase() + end.substring(1);
+ if (value == "" || !value) {
+ this.node.style[name] = "none";
+ return;
+ }
+ if (value.type == "marker") {
+ var id = value.node.id;
+ if (!id) {
+ $(value.node, {id: value.id});
+ }
+ this.node.style[name] = "url(#" + id + ")";
+ return;
+ }
+ };
+ }
+ eve.on("savage.util.getattr.marker-end", getter("end"))(-1);
+ eve.on("savage.util.getattr.markerEnd", getter("end"))(-1);
+ eve.on("savage.util.getattr.marker-start", getter("start"))(-1);
+ eve.on("savage.util.getattr.markerStart", getter("start"))(-1);
+ eve.on("savage.util.getattr.marker-mid", getter("mid"))(-1);
+ eve.on("savage.util.getattr.markerMid", getter("mid"))(-1);
+ eve.on("savage.util.attr.marker-end", setter("end"))(-1);
+ eve.on("savage.util.attr.markerEnd", setter("end"))(-1);
+ eve.on("savage.util.attr.marker-start", setter("start"))(-1);
+ eve.on("savage.util.attr.markerStart", setter("start"))(-1);
+ eve.on("savage.util.attr.marker-mid", setter("mid"))(-1);
+ eve.on("savage.util.attr.markerMid", setter("mid"))(-1);
+}());
eve.on("savage.util.getattr.r", function () {
if (this.type == "rect" && $(this.node, "rx") == $(this.node, "ry")) {
eve.stop();