Decouple all the things and bug fixes

• extract matrix, attar & paper.js
• fix for transform for sag element
• add Fragment to plugin call
master
Dmitry Baranovskiy 2014-05-07 18:37:05 +10:00
parent 83100d6f71
commit 88f214e918
13 changed files with 3126 additions and 5524 deletions

2
.gitignore vendored
View File

@ -2,3 +2,5 @@ TAGS
*~
_*
.DS_Store
node_modules
playground

View File

@ -31,12 +31,15 @@ module.exports = function(grunt) {
"./src/amd-banner.js",
"./src/mina.js",
"./src/svg.js",
"./src/matrix.js",
"./src/attr.js",
"./src/paper.js",
"./src/path.js",
"./src/set.js",
"./src/equal.js",
"./src/mouse.js",
"./src/filter.js",
"./src/amd-footer.js",
"./src/amd-footer.js"
]
}
},

File diff suppressed because one or more lines are too long

2291
dist/snap.svg.js vendored

File diff suppressed because it is too large Load Diff

View File

@ -38,6 +38,10 @@ h5:hover a.dr-sourceline {
.dr-type {
float: left;
}
.dr-title {
float: left;
margin: 0 8px 0 0;
}
.dr-type em,
.dr-returns em,
.dr-property em {

File diff suppressed because it is too large Load Diff

396
src/attr.js Normal file
View File

@ -0,0 +1,396 @@
// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
Snap.plugin(function (Snap, Element, Paper, glob, Fragment) {
var has = "hasOwnProperty",
make = Snap._.make,
wrap = Snap._.wrap,
is = Snap.is,
getSomeDefs = Snap._.getSomeDefs,
reURLValue = /^url\(#?([^)]+)\)$/,
$ = Snap._.$,
URL = Snap.url,
Str = String,
separator = Snap._.separator,
E = "";
// Attributes event handlers
eve.on("snap.util.attr.mask", function (value) {
if (value instanceof Element || value instanceof Fragment) {
eve.stop();
if (value instanceof Fragment && value.node.childNodes.length == 1) {
value = value.node.firstChild;
getSomeDefs(this).appendChild(value);
value = wrap(value);
}
if (value.type == "mask") {
var mask = value;
} else {
mask = make("mask", getSomeDefs(this));
mask.node.appendChild(value.node);
!mask.node.id && $(mask.node, {
id: mask.id
});
}
$(this.node, {
mask: URL(mask.id)
});
}
});
(function (clipIt) {
eve.on("snap.util.attr.clip", clipIt);
eve.on("snap.util.attr.clip-path", clipIt);
eve.on("snap.util.attr.clipPath", clipIt);
}(function (value) {
if (value instanceof Element || value instanceof Fragment) {
eve.stop();
if (value.type == "clipPath") {
var clip = value;
} else {
clip = make("clipPath", getSomeDefs(this));
clip.node.appendChild(value.node);
!clip.node.id && $(clip.node, {
id: clip.id
});
}
$(this.node, {
"clip-path": URL(clip.id)
});
}
}));
function fillStroke(name) {
return function (value) {
eve.stop();
if (value instanceof Fragment && value.node.childNodes.length == 1 &&
(value.node.firstChild.tagName == "radialGradient" ||
value.node.firstChild.tagName == "linearGradient" ||
value.node.firstChild.tagName == "pattern")) {
value = value.node.firstChild;
getSomeDefs(this).appendChild(value);
value = wrap(value);
}
if (value instanceof Element) {
if (value.type == "radialGradient" || value.type == "linearGradient"
|| value.type == "pattern") {
if (!value.node.id) {
$(value.node, {
id: value.id
});
}
var fill = URL(value.node.id);
} else {
fill = value.attr(name);
}
} else {
fill = Snap.color(value);
if (fill.error) {
var grad = gradient(getSomeDefs(this), value);
if (grad) {
if (!grad.node.id) {
$(grad.node, {
id: grad.id
});
}
fill = URL(grad.node.id);
} else {
fill = value;
}
} else {
fill = Str(fill);
}
}
var attrs = {};
attrs[name] = fill;
$(this.node, attrs);
this.node.style[name] = E;
};
}
eve.on("snap.util.attr.fill", fillStroke("fill"));
eve.on("snap.util.attr.stroke", fillStroke("stroke"));
var gradrg = /^([lr])(?:\(([^)]*)\))?(.*)$/i;
eve.on("snap.util.grad.parse", function parseGrad(string) {
string = Str(string);
var tokens = string.match(gradrg);
if (!tokens) {
return null;
}
var type = tokens[1],
params = tokens[2],
stops = tokens[3];
params = params.split(/\s*,\s*/).map(function (el) {
return +el == el ? +el : el;
});
if (params.length == 1 && params[0] == 0) {
params = [];
}
stops = stops.split("-");
stops = stops.map(function (el) {
el = el.split(":");
var out = {
color: el[0]
};
if (el[1]) {
out.offset = parseFloat(el[1]);
}
return out;
});
return {
type: type,
params: params,
stops: stops
};
});
eve.on("snap.util.attr.d", function (value) {
eve.stop();
if (is(value, "array") && is(value[0], "array")) {
value = Snap.path.toString.call(value);
}
value = Str(value);
if (value.match(/[ruo]/i)) {
value = Snap.path.toAbsolute(value);
}
$(this.node, {d: value});
})(-1);
eve.on("snap.util.attr.#text", function (value) {
eve.stop();
value = Str(value);
var txt = glob.doc.createTextNode(value);
while (this.node.firstChild) {
this.node.removeChild(this.node.firstChild);
}
this.node.appendChild(txt);
})(-1);
eve.on("snap.util.attr.path", function (value) {
eve.stop();
this.attr({d: value});
})(-1);
eve.on("snap.util.attr.class", function (value) {
eve.stop();
this.node.className.baseVal = value;
})(-1);
eve.on("snap.util.attr.viewBox", function (value) {
var vb;
if (is(value, "object") && "x" in value) {
vb = [value.x, value.y, value.width, value.height].join(" ");
} else if (is(value, "array")) {
vb = value.join(" ");
} else {
vb = value;
}
$(this.node, {
viewBox: vb
});
eve.stop();
})(-1);
eve.on("snap.util.attr.transform", function (value) {
this.transform(value);
eve.stop();
})(-1);
eve.on("snap.util.attr.r", function (value) {
if (this.type == "rect") {
eve.stop();
$(this.node, {
rx: value,
ry: value
});
}
})(-1);
eve.on("snap.util.attr.textpath", function (value) {
eve.stop();
if (this.type == "text") {
var id, tp, node;
if (!value && this.textPath) {
tp = this.textPath;
while (tp.node.firstChild) {
this.node.appendChild(tp.node.firstChild);
}
tp.remove();
delete this.textPath;
return;
}
if (is(value, "string")) {
var defs = getSomeDefs(this),
path = wrap(defs.parentNode).path(value);
defs.appendChild(path.node);
id = path.id;
path.attr({id: id});
} else {
value = wrap(value);
if (value instanceof Element) {
id = value.attr("id");
if (!id) {
id = value.id;
value.attr({id: id});
}
}
}
if (id) {
tp = this.textPath;
node = this.node;
if (tp) {
tp.attr({"xlink:href": "#" + id});
} else {
tp = $("textPath", {
"xlink:href": "#" + id
});
while (node.firstChild) {
tp.appendChild(node.firstChild);
}
node.appendChild(tp);
this.textPath = wrap(tp);
}
}
}
})(-1);
eve.on("snap.util.attr.text", function (value) {
if (this.type == "text") {
var i = 0,
node = this.node,
tuner = function (chunk) {
var out = $("tspan");
if (is(chunk, "array")) {
for (var i = 0; i < chunk.length; i++) {
out.appendChild(tuner(chunk[i]));
}
} else {
out.appendChild(glob.doc.createTextNode(chunk));
}
out.normalize && out.normalize();
return out;
};
while (node.firstChild) {
node.removeChild(node.firstChild);
}
var tuned = tuner(value);
while (tuned.firstChild) {
node.appendChild(tuned.firstChild);
}
}
eve.stop();
})(-1);
eve.on("snap.util.getattr.transform", function () {
eve.stop();
return this.transform();
})(-1);
eve.on("snap.util.getattr.textpath", function () {
eve.stop();
return this.textPath;
})(-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 Snap(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("snap.util.getattr.marker-end", getter("end"))(-1);
eve.on("snap.util.getattr.markerEnd", getter("end"))(-1);
eve.on("snap.util.getattr.marker-start", getter("start"))(-1);
eve.on("snap.util.getattr.markerStart", getter("start"))(-1);
eve.on("snap.util.getattr.marker-mid", getter("mid"))(-1);
eve.on("snap.util.getattr.markerMid", getter("mid"))(-1);
eve.on("snap.util.attr.marker-end", setter("end"))(-1);
eve.on("snap.util.attr.markerEnd", setter("end"))(-1);
eve.on("snap.util.attr.marker-start", setter("start"))(-1);
eve.on("snap.util.attr.markerStart", setter("start"))(-1);
eve.on("snap.util.attr.marker-mid", setter("mid"))(-1);
eve.on("snap.util.attr.markerMid", setter("mid"))(-1);
}());
eve.on("snap.util.getattr.r", function () {
if (this.type == "rect" && $(this.node, "rx") == $(this.node, "ry")) {
eve.stop();
return $(this.node, "rx");
}
})(-1);
function textExtract(node) {
var out = [];
var children = node.childNodes;
for (var i = 0, ii = children.length; i < ii; i++) {
var chi = children[i];
if (chi.nodeType == 3) {
out.push(chi.nodeValue);
}
if (chi.tagName == "tspan") {
if (chi.childNodes.length == 1 && chi.firstChild.nodeType == 3) {
out.push(chi.firstChild.nodeValue);
} else {
out.push(textExtract(chi));
}
}
}
return out;
}
eve.on("snap.util.getattr.text", function () {
if (this.type == "text" || this.type == "tspan") {
eve.stop();
var out = textExtract(this.node);
return out.length == 1 ? out[0] : out;
}
})(-1);
eve.on("snap.util.getattr.#text", function () {
return this.node.textContent;
})(-1);
eve.on("snap.util.getattr.viewBox", function () {
eve.stop();
var vb = $(this.node, "viewBox");
if (vb) {
vb = vb.split(separator);
return Snap._.box(+vb[0], +vb[1], +vb[2], +vb[3]);
} else {
return;
}
})(-1);
eve.on("snap.util.getattr.points", function () {
var p = $(this.node, "points");
eve.stop();
return p.split(separator);
});
eve.on("snap.util.getattr.path", function () {
var p = $(this.node, "d");
eve.stop();
return p;
});
eve.on("snap.util.getattr.class", function () {
return this.node.className.baseVal;
});
function getFontSize() {
eve.stop();
return this.node.style.fontSize;
}
eve.on("snap.util.getattr.fontSize", getFontSize)(-1);
eve.on("snap.util.getattr.font-size", getFontSize)(-1);
});

299
src/matrix.js Normal file
View File

@ -0,0 +1,299 @@
// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
Snap.plugin(function (Snap, Element, Paper, glob, Fragment) {
var objectToString = Object.prototype.toString,
Str = String,
math = Math,
E = "";
function Matrix(a, b, c, d, e, f) {
if (b == null && objectToString.call(a) == "[object SVGMatrix]") {
this.a = a.a;
this.b = a.b;
this.c = a.c;
this.d = a.d;
this.e = a.e;
this.f = a.f;
return;
}
if (a != null) {
this.a = +a;
this.b = +b;
this.c = +c;
this.d = +d;
this.e = +e;
this.f = +f;
} else {
this.a = 1;
this.b = 0;
this.c = 0;
this.d = 1;
this.e = 0;
this.f = 0;
}
}
(function (matrixproto) {
/*\
* Matrix.add
[ method ]
**
* Adds the given matrix to existing one
- a (number)
- b (number)
- c (number)
- d (number)
- e (number)
- f (number)
* or
- matrix (object) @Matrix
\*/
matrixproto.add = function (a, b, c, d, e, f) {
var out = [[], [], []],
m = [[this.a, this.c, this.e], [this.b, this.d, this.f], [0, 0, 1]],
matrix = [[a, c, e], [b, d, f], [0, 0, 1]],
x, y, z, res;
if (a && a instanceof Matrix) {
matrix = [[a.a, a.c, a.e], [a.b, a.d, a.f], [0, 0, 1]];
}
for (x = 0; x < 3; x++) {
for (y = 0; y < 3; y++) {
res = 0;
for (z = 0; z < 3; z++) {
res += m[x][z] * matrix[z][y];
}
out[x][y] = res;
}
}
this.a = out[0][0];
this.b = out[1][0];
this.c = out[0][1];
this.d = out[1][1];
this.e = out[0][2];
this.f = out[1][2];
return this;
};
/*\
* Matrix.invert
[ method ]
**
* Returns an inverted version of the matrix
= (object) @Matrix
\*/
matrixproto.invert = function () {
var me = this,
x = me.a * me.d - me.b * me.c;
return new Matrix(me.d / x, -me.b / x, -me.c / x, me.a / x, (me.c * me.f - me.d * me.e) / x, (me.b * me.e - me.a * me.f) / x);
};
/*\
* Matrix.clone
[ method ]
**
* Returns a copy of the matrix
= (object) @Matrix
\*/
matrixproto.clone = function () {
return new Matrix(this.a, this.b, this.c, this.d, this.e, this.f);
};
/*\
* Matrix.translate
[ method ]
**
* Translate the matrix
- x (number) horizontal offset distance
- y (number) vertical offset distance
\*/
matrixproto.translate = function (x, y) {
return this.add(1, 0, 0, 1, x, y);
};
/*\
* Matrix.scale
[ method ]
**
* Scales the matrix
- x (number) amount to be scaled, with `1` resulting in no change
- y (number) #optional amount to scale along the vertical axis. (Otherwise `x` applies to both axes.)
- cx (number) #optional horizontal origin point from which to scale
- cy (number) #optional vertical origin point from which to scale
* Default cx, cy is the middle point of the element.
\*/
matrixproto.scale = function (x, y, cx, cy) {
y == null && (y = x);
(cx || cy) && this.add(1, 0, 0, 1, cx, cy);
this.add(x, 0, 0, y, 0, 0);
(cx || cy) && this.add(1, 0, 0, 1, -cx, -cy);
return this;
};
/*\
* Matrix.rotate
[ method ]
**
* Rotates the matrix
- a (number) angle of rotation, in degrees
- x (number) horizontal origin point from which to rotate
- y (number) vertical origin point from which to rotate
\*/
matrixproto.rotate = function (a, x, y) {
a = Snap.rad(a);
x = x || 0;
y = y || 0;
var cos = +math.cos(a).toFixed(9),
sin = +math.sin(a).toFixed(9);
this.add(cos, sin, -sin, cos, x, y);
return this.add(1, 0, 0, 1, -x, -y);
};
/*\
* Matrix.x
[ method ]
**
* Returns x coordinate for given point after transformation described by the matrix. See also @Matrix.y
- x (number)
- y (number)
= (number) x
\*/
matrixproto.x = function (x, y) {
return x * this.a + y * this.c + this.e;
};
/*\
* Matrix.y
[ method ]
**
* Returns y coordinate for given point after transformation described by the matrix. See also @Matrix.x
- x (number)
- y (number)
= (number) y
\*/
matrixproto.y = function (x, y) {
return x * this.b + y * this.d + this.f;
};
matrixproto.get = function (i) {
return +this[Str.fromCharCode(97 + i)].toFixed(4);
};
matrixproto.toString = function () {
return "matrix(" + [this.get(0), this.get(1), this.get(2), this.get(3), this.get(4), this.get(5)].join() + ")";
};
matrixproto.offset = function () {
return [this.e.toFixed(4), this.f.toFixed(4)];
};
function norm(a) {
return a[0] * a[0] + a[1] * a[1];
}
function normalize(a) {
var mag = math.sqrt(norm(a));
a[0] && (a[0] /= mag);
a[1] && (a[1] /= mag);
}
/*\
* Matrix.determinant
[ method ]
**
* Finds determinant of the given matrix.
= (number) determinant
\*/
matrixproto.determinant = function () {
return this.a * this.d - this.b * this.c;
};
/*\
* Matrix.split
[ method ]
**
* Splits matrix into primitive transformations
= (object) in format:
o dx (number) translation by x
o dy (number) translation by y
o scalex (number) scale by x
o scaley (number) scale by y
o shear (number) shear
o rotate (number) rotation in deg
o isSimple (boolean) could it be represented via simple transformations
\*/
matrixproto.split = function () {
var out = {};
// translation
out.dx = this.e;
out.dy = this.f;
// scale and shear
var row = [[this.a, this.c], [this.b, this.d]];
out.scalex = math.sqrt(norm(row[0]));
normalize(row[0]);
out.shear = row[0][0] * row[1][0] + row[0][1] * row[1][1];
row[1] = [row[1][0] - row[0][0] * out.shear, row[1][1] - row[0][1] * out.shear];
out.scaley = math.sqrt(norm(row[1]));
normalize(row[1]);
out.shear /= out.scaley;
if (this.determinant() < 0) {
out.scalex = -out.scalex;
}
// rotation
var sin = -row[0][1],
cos = row[1][1];
if (cos < 0) {
out.rotate = Snap.deg(math.acos(cos));
if (sin < 0) {
out.rotate = 360 - out.rotate;
}
} else {
out.rotate = Snap.deg(math.asin(sin));
}
out.isSimple = !+out.shear.toFixed(9) && (out.scalex.toFixed(9) == out.scaley.toFixed(9) || !out.rotate);
out.isSuperSimple = !+out.shear.toFixed(9) && out.scalex.toFixed(9) == out.scaley.toFixed(9) && !out.rotate;
out.noRotation = !+out.shear.toFixed(9) && !out.rotate;
return out;
};
/*\
* Matrix.toTransformString
[ method ]
**
* Returns transform string that represents given matrix
= (string) transform string
\*/
matrixproto.toTransformString = function (shorter) {
var s = shorter || this.split();
if (!+s.shear.toFixed(9)) {
s.scalex = +s.scalex.toFixed(4);
s.scaley = +s.scaley.toFixed(4);
s.rotate = +s.rotate.toFixed(4);
return (s.dx || s.dy ? "t" + [+s.dx.toFixed(4), +s.dy.toFixed(4)] : E) +
(s.scalex != 1 || s.scaley != 1 ? "s" + [s.scalex, s.scaley, 0, 0] : E) +
(s.rotate ? "r" + [+s.rotate.toFixed(4), 0, 0] : E);
} else {
return "m" + [this.get(0), this.get(1), this.get(2), this.get(3), this.get(4), this.get(5)];
}
};
})(Matrix.prototype);
/*\
* Snap.Matrix
[ method ]
**
* Utility method
**
* Returns a matrix based on the given parameters
- a (number)
- b (number)
- c (number)
- d (number)
- e (number)
- f (number)
* or
- svgMatrix (SVGMatrix)
= (object) @Matrix
\*/
Snap.Matrix = Matrix;
});

695
src/paper.js Normal file
View File

@ -0,0 +1,695 @@
// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
Snap.plugin(function (Snap, Element, Paper, glob, Fragment) {
var proto = Paper.prototype,
is = Snap.is;
/*\
* Paper.rect
[ method ]
*
* Draws a rectangle
**
- x (number) x coordinate of the top left corner
- y (number) y coordinate of the top left corner
- width (number) width
- height (number) height
- rx (number) #optional horizontal radius for rounded corners, default is 0
- ry (number) #optional vertical radius for rounded corners, default is rx or 0
= (object) the `rect` element
**
> Usage
| // regular rectangle
| var c = paper.rect(10, 10, 50, 50);
| // rectangle with rounded corners
| var c = paper.rect(40, 40, 50, 50, 10);
\*/
proto.rect = function (x, y, w, h, rx, ry) {
var attr;
if (ry == null) {
ry = rx;
}
if (is(x, "object") && x == "[object Object]") {
attr = x;
} else if (x != null) {
attr = {
x: x,
y: y,
width: w,
height: h
};
if (rx != null) {
attr.rx = rx;
attr.ry = ry;
}
}
return this.el("rect", attr);
};
/*\
* Paper.circle
[ method ]
**
* Draws a circle
**
- x (number) x coordinate of the centre
- y (number) y coordinate of the centre
- r (number) radius
= (object) the `circle` element
**
> Usage
| var c = paper.circle(50, 50, 40);
\*/
proto.circle = function (cx, cy, r) {
var attr;
if (is(cx, "object") && cx == "[object Object]") {
attr = cx;
} else if (cx != null) {
attr = {
cx: cx,
cy: cy,
r: r
};
}
return this.el("circle", attr);
};
var preload = (function () {
function onerror() {
this.parentNode.removeChild(this);
}
return function (src, f) {
var img = glob.doc.createElement("img"),
body = glob.doc.body;
img.style.cssText = "position:absolute;left:-9999em;top:-9999em";
img.onload = function () {
f.call(img);
img.onload = img.onerror = null;
body.removeChild(img);
};
img.onerror = onerror;
body.appendChild(img);
img.src = src;
};
}());
/*\
* Paper.image
[ method ]
**
* Places an image on the surface
**
- src (string) URI of the source image
- x (number) x offset position
- y (number) y offset position
- width (number) width of the image
- height (number) height of the image
= (object) the `image` element
* or
= (object) Snap element object with type `image`
**
> Usage
| var c = paper.image("apple.png", 10, 10, 80, 80);
\*/
proto.image = function (src, x, y, width, height) {
var el = this.el("image");
if (is(src, "object") && "src" in src) {
el.attr(src);
} else if (src != null) {
var set = {
"xlink:href": src,
preserveAspectRatio: "none"
};
if (x != null && y != null) {
set.x = x;
set.y = y;
}
if (width != null && height != null) {
set.width = width;
set.height = height;
} else {
preload(src, function () {
Snap._.$(el.node, {
width: this.offsetWidth,
height: this.offsetHeight
});
});
}
Snap._.$(el.node, set);
}
return el;
};
/*\
* Paper.ellipse
[ method ]
**
* Draws an ellipse
**
- x (number) x coordinate of the centre
- y (number) y coordinate of the centre
- rx (number) horizontal radius
- ry (number) vertical radius
= (object) the `ellipse` element
**
> Usage
| var c = paper.ellipse(50, 50, 40, 20);
\*/
proto.ellipse = function (cx, cy, rx, ry) {
var attr;
if (is(cx, "object") && cx == "[object Object]") {
attr = cx;
} else if (cx != null) {
attr ={
cx: cx,
cy: cy,
rx: rx,
ry: ry
};
}
return this.el("ellipse", attr);
};
// SIERRA Paper.path(): Unclear from the link what a Catmull-Rom curveto is, and why it would make life any easier.
/*\
* Paper.path
[ method ]
**
* Creates a `<path>` element using the given string as the path's definition
- pathString (string) #optional path string in SVG format
* Path string consists of one-letter commands, followed by comma seprarated arguments in numerical form. Example:
| "M10,20L30,40"
* This example features two commands: `M`, with arguments `(10, 20)` and `L` with arguments `(30, 40)`. Uppercase letter commands express coordinates in absolute terms, while lowercase commands express them in relative terms from the most recently declared coordinates.
*
# <p>Here is short list of commands available, for more details see <a href="http://www.w3.org/TR/SVG/paths.html#PathData" title="Details of a path's data attribute's format are described in the SVG specification.">SVG path string format</a> or <a href="https://developer.mozilla.org/en/SVG/Tutorial/Paths">article about path strings at MDN</a>.</p>
# <table><thead><tr><th>Command</th><th>Name</th><th>Parameters</th></tr></thead><tbody>
# <tr><td>M</td><td>moveto</td><td>(x y)+</td></tr>
# <tr><td>Z</td><td>closepath</td><td>(none)</td></tr>
# <tr><td>L</td><td>lineto</td><td>(x y)+</td></tr>
# <tr><td>H</td><td>horizontal lineto</td><td>x+</td></tr>
# <tr><td>V</td><td>vertical lineto</td><td>y+</td></tr>
# <tr><td>C</td><td>curveto</td><td>(x1 y1 x2 y2 x y)+</td></tr>
# <tr><td>S</td><td>smooth curveto</td><td>(x2 y2 x y)+</td></tr>
# <tr><td>Q</td><td>quadratic Bézier curveto</td><td>(x1 y1 x y)+</td></tr>
# <tr><td>T</td><td>smooth quadratic Bézier curveto</td><td>(x y)+</td></tr>
# <tr><td>A</td><td>elliptical arc</td><td>(rx ry x-axis-rotation large-arc-flag sweep-flag x y)+</td></tr>
# <tr><td>R</td><td><a href="http://en.wikipedia.org/wiki/CatmullRom_spline#Catmull.E2.80.93Rom_spline">Catmull-Rom curveto</a>*</td><td>x1 y1 (x y)+</td></tr></tbody></table>
* * _Catmull-Rom curveto_ is a not standard SVG command and added to make life easier.
* Note: there is a special case when a path consists of only three commands: `M10,10R…z`. In this case the path connects back to its starting point.
> Usage
| var c = paper.path("M10 10L90 90");
| // draw a diagonal line:
| // move to 10,10, line to 90,90
\*/
proto.path = function (d) {
var attr;
if (is(d, "object") && !is(d, "array")) {
attr = d;
} else if (d) {
attr = {d: d};
}
return this.el("path", attr);
};
/*\
* Paper.g
[ method ]
**
* Creates a group element
**
- varargs () #optional elements to nest within the group
= (object) the `g` element
**
> Usage
| var c1 = paper.circle(),
| c2 = paper.rect(),
| g = paper.g(c2, c1); // note that the order of elements is different
* or
| var c1 = paper.circle(),
| c2 = paper.rect(),
| g = paper.g();
| g.add(c2, c1);
\*/
/*\
* Paper.group
[ method ]
**
* See @Paper.g
\*/
proto.group = proto.g = function (first) {
var attr,
el = this.el("g");
if (arguments.length == 1 && first && !first.type) {
el.attr(first);
} else if (arguments.length) {
el.add(Array.prototype.slice.call(arguments, 0));
}
return el;
};
/*\
* Paper.svg
[ method ]
**
* Creates a nested SVG element.
- x (number) @optional X of the element
- y (number) @optional Y of the element
- width (number) @optional width of the element
- height (number) @optional height of the element
- vbx (number) @optional viewbox X
- vby (number) @optional viewbox Y
- vbw (number) @optional viewbox width
- vbh (number) @optional viewbox height
**
= (object) the `svg` element
**
\*/
proto.svg = function (x, y, width, height, vbx, vby, vbw, vbh) {
var attrs = {};
if (is(x, "object") && y == null) {
attrs = x;
} else {
if (x != null) {
attrs.x = x;
}
if (y != null) {
attrs.y = y;
}
if (width != null) {
attrs.width = width;
}
if (height != null) {
attrs.height = height;
}
if (vbx != null && vby != null && vbw != null && vbh != null) {
attrs.viewBox = [vbx, vby, vbw, vbh];
}
}
return this.el("svg", attrs);
};
/*\
* Paper.mask
[ method ]
**
* Equivalent in behaviour to @Paper.g, except its a mask.
**
= (object) the `mask` element
**
\*/
proto.mask = function (first) {
var attr,
el = this.el("mask");
if (arguments.length == 1 && first && !first.type) {
el.attr(first);
} else if (arguments.length) {
el.add(Array.prototype.slice.call(arguments, 0));
}
return el;
};
/*\
* Paper.ptrn
[ method ]
**
* Equivalent in behaviour to @Paper.g, except its a mask.
- x (number) @optional X of the element
- y (number) @optional Y of the element
- width (number) @optional width of the element
- height (number) @optional height of the element
- vbx (number) @optional viewbox X
- vby (number) @optional viewbox Y
- vbw (number) @optional viewbox width
- vbh (number) @optional viewbox height
**
= (object) the `mask` element
**
\*/
proto.ptrn = function (x, y, width, height, vx, vy, vw, vh) {
if (is(x, "object")) {
var attr = x;
} else if (!arguments.length) {
attr = {patternUnits: "userSpaceOnUse"};
} else {
attr = {};
if (x != null) {
attr.x = x;
}
if (y != null) {
attr.y = y;
}
if (width != null) {
attr.width = width;
}
if (height != null) {
attr.height = height;
}
if (vbx != null && vby != null && vbw != null && vbh != null) {
attr.viewBox = [vbx, vby, vbw, vbh];
}
}
return this.el("pattern", attr);
};
/*\
* Paper.use
[ method ]
**
* Creates a <use> element.
- id (string) @optional id of element to link
* or
- id (Element) @optional element to link
**
= (object) the `use` element
**
\*/
proto.use = function (id) {
if (id != null) {
var el = make("use", this.node);
if (id instanceof Element) {
if (!id.attr("id")) {
id.attr({id: ID()});
}
id = id.attr("id");
}
return this.el("use", {"xlink:href": id});
} else {
return Element.prototype.use.call(this);
}
};
/*\
* Paper.text
[ method ]
**
* Draws a text string
**
- x (number) x coordinate position
- y (number) y coordinate position
- text (string|array) The text string to draw or array of strings to nest within separate `<tspan>` elements
= (object) the `text` element
**
> Usage
| var t1 = paper.text(50, 50, "Snap");
| var t2 = paper.text(50, 50, ["S","n","a","p"]);
| // Text path usage
| t1.attr({textpath: "M10,10L100,100"});
| // or
| var pth = paper.path("M10,10L100,100");
| t1.attr({textpath: pth});
\*/
proto.text = function (x, y, text) {
var attr = {};
if (is(x, "object")) {
attr = x;
} else if (x != null) {
attr = {
x: x,
y: y,
text: text || ""
};
}
return this.el("text", attr);
};
/*\
* Paper.line
[ method ]
**
* Draws a line
**
- x1 (number) x coordinate position of the start
- y1 (number) y coordinate position of the start
- x2 (number) x coordinate position of the end
- y2 (number) y coordinate position of the end
= (object) the `line` element
**
> Usage
| var t1 = paper.line(50, 50, 100, 100);
\*/
proto.line = function (x1, y1, x2, y2) {
var attr = {};
if (is(x1, "object")) {
attr = x1;
} else if (x1 != null) {
attr = {
x1: x1,
x2: x2,
y1: y1,
y2: y2
};
}
return this.el("line", attr);
};
/*\
* Paper.polyline
[ method ]
**
* Draws a polyline
**
- points (array) array of points
* or
- varargs () points
= (object) the `polyline` element
**
> Usage
| var p1 = paper.polyline([10, 10, 100, 100]);
| var p2 = paper.polyline(10, 10, 100, 100);
\*/
proto.polyline = function (points) {
if (arguments.length > 1) {
points = Array.prototype.slice.call(arguments, 0);
}
var attr = {};
if (is(points, "object") && !is(points, "array")) {
attr = points;
} else if (points != null) {
attr = {points: points};
}
return this.el("polyline", attr);
};
/*\
* Paper.polygon
[ method ]
**
* Draws a polygon. See @Paper.polyline
\*/
proto.polygon = function (points) {
if (arguments.length > 1) {
points = Array.prototype.slice.call(arguments, 0);
}
var attr = {};
if (is(points, "object") && !is(points, "array")) {
attr = points;
} else if (points != null) {
attr = {points: points};
}
return this.el("polygon", attr);
};
// gradients
(function () {
var $ = Snap._.$;
// gradients' helpers
function Gstops() {
return this.selectAll("stop");
}
function GaddStop(color, offset) {
var stop = $("stop"),
attr = {
offset: +offset + "%"
};
color = Snap.color(color);
attr["stop-color"] = color.hex;
if (color.opacity < 1) {
attr["stop-opacity"] = color.opacity;
}
$(stop, attr);
this.node.appendChild(stop);
return this;
}
function GgetBBox() {
if (this.type == "linearGradient") {
var x1 = $(this.node, "x1") || 0,
x2 = $(this.node, "x2") || 1,
y1 = $(this.node, "y1") || 0,
y2 = $(this.node, "y2") || 0;
return Snap._.box(x1, y1, math.abs(x2 - x1), math.abs(y2 - y1));
} else {
var cx = this.node.cx || .5,
cy = this.node.cy || .5,
r = this.node.r || 0;
return Snap._.box(cx - r, cy - r, r * 2, r * 2);
}
}
function arrayFirstValue(arr) {
var res;
for (var i = 0, ii = arr.length; i < ii; i++) {
res = res || arr[i];
if (res) {
return res;
}
}
}
function gradient(defs, str) {
var grad = arrayFirstValue(eve("snap.util.grad.parse", null, str)),
el;
if (!grad) {
return null;
}
grad.params.unshift(defs);
if (grad.type.toLowerCase() == "l") {
el = gradientLinear.apply(0, grad.params);
} else {
el = gradientRadial.apply(0, grad.params);
}
if (grad.type != grad.type.toLowerCase()) {
$(el.node, {
gradientUnits: "userSpaceOnUse"
});
}
var stops = grad.stops,
len = stops.length,
start = 0,
j = 0;
function seed(i, end) {
var step = (end - start) / (i - j);
for (var k = j; k < i; k++) {
stops[k].offset = +(+start + step * (k - j)).toFixed(2);
}
j = i;
start = end;
}
len--;
for (var i = 0; i < len; i++) if ("offset" in stops[i]) {
seed(i, stops[i].offset);
}
stops[len].offset = stops[len].offset || 100;
seed(len, stops[len].offset);
for (i = 0; i <= len; i++) {
var stop = stops[i];
el.addStop(stop.color, stop.offset);
}
return el;
}
function gradientLinear(defs, x1, y1, x2, y2) {
var el = Snap._.make("linearGradient", defs);
el.stops = Gstops;
el.addStop = GaddStop;
el.getBBox = GgetBBox;
if (x1 != null) {
$(el.node, {
x1: x1,
y1: y1,
x2: x2,
y2: y2
});
}
return el;
}
function gradientRadial(defs, cx, cy, r, fx, fy) {
var el = Snap._.make("radialGradient", defs);
el.stops = Gstops;
el.addStop = GaddStop;
el.getBBox = GgetBBox;
if (cx != null) {
$(el.node, {
cx: cx,
cy: cy,
r: r
});
}
if (fx != null && fy != null) {
$(el.node, {
fx: fx,
fy: fy
});
}
return el;
}
/*\
* Paper.gradient
[ method ]
**
* Creates a gradient element
**
- gradient (string) gradient descriptor
> Gradient Descriptor
* The gradient descriptor is an expression formatted as
* follows: `<type>(<coords>)<colors>`. The `<type>` can be
* either linear or radial. The uppercase `L` or `R` letters
* indicate absolute coordinates offset from the SVG surface.
* Lowercase `l` or `r` letters indicate coordinates
* calculated relative to the element to which the gradient is
* applied. Coordinates specify a linear gradient vector as
* `x1`, `y1`, `x2`, `y2`, or a radial gradient as `cx`, `cy`,
* `r` and optional `fx`, `fy` specifying a focal point away
* from the center of the circle. Specify `<colors>` as a list
* of dash-separated CSS color values. Each color may be
* followed by a custom offset value, separated with a colon
* character.
> Examples
* Linear gradient, relative from top-left corner to bottom-right
* corner, from black through red to white:
| var g = paper.gradient("l(0, 0, 1, 1)#000-#f00-#fff");
* Linear gradient, absolute from (0, 0) to (100, 100), from black
* through red at 25% to white:
| var g = paper.gradient("L(0, 0, 100, 100)#000-#f00:25-#fff");
* Radial gradient, relative from the center of the element with radius
* half the width, from black to white:
| var g = paper.gradient("r(0.5, 0.5, 0.5)#000-#fff");
* To apply the gradient:
| paper.circle(50, 50, 40).attr({
| fill: g
| });
= (object) the `gradient` element
\*/
proto.gradient = function (str) {
return gradient(this.defs, str);
};
proto.gradientLinear = function (x1, y1, x2, y2) {
return gradientLinear(this.defs, x1, y1, x2, y2);
};
proto.gradientRadial = function (cx, cy, r, fx, fy) {
return gradientRadial(this.defs, cx, cy, r, fx, fy);
};
/*\
* Paper.toString
[ method ]
**
* Returns SVG code for the @Paper
= (string) SVG code for the @Paper
\*/
proto.toString = function () {
var doc = this.node.ownerDocument,
f = doc.createDocumentFragment(),
d = doc.createElement("div"),
svg = this.node.cloneNode(true),
res;
f.appendChild(d);
d.appendChild(svg);
Snap._.$(svg, {xmlns: "http://www.w3.org/2000/svg"});
res = d.innerHTML;
f.removeChild(f.firstChild);
return res;
};
/*\
* Paper.clear
[ method ]
**
* Removes all child nodes of the paper, except <defs>.
\*/
proto.clear = function () {
var node = this.node.firstChild,
next;
while (node) {
next = node.nextSibling;
if (node.tagName != "defs") {
node.parentNode.removeChild(node);
} else {
proto.clear.call({node: node});
}
node = next;
}
};
}());
});

1523
src/svg.js

File diff suppressed because it is too large Load Diff

View File

@ -65,15 +65,17 @@ describe("Paper methods", function () {
it("Paper.svg(x, y)", function() {
var c = paper.svg(100, 200);
expect(c.node.nodeName).to.be("svg");
expect(c.node.width.baseVal.value).to.be(100);
expect(c.node.height.baseVal.value).to.be(200);
expect(c.node.x.baseVal.value).to.be(100);
expect(c.node.y.baseVal.value).to.be(200);
expect(c.node.parentNode).to.be(paper.node);
});
it("Paper.svg(x, y, viewbox)", function() {
var c = paper.svg(100, 200, 10, 20, 30, 40);
it("Paper.svg(x, y, w, h, viewbox)", function() {
var c = paper.svg(100, 200, 300, 400, 10, 20, 30, 40);
expect(c.node.nodeName).to.be("svg");
expect(c.node.width.baseVal.value).to.be(100);
expect(c.node.height.baseVal.value).to.be(200);
expect(c.node.x.baseVal.value).to.be(100);
expect(c.node.y.baseVal.value).to.be(200);
expect(c.node.width.baseVal.value).to.be(300);
expect(c.node.height.baseVal.value).to.be(400);
expect(c.node.getAttribute("viewBox")).to.be("10 20 30 40");
expect(c.node.parentNode).to.be(paper.node);
});
@ -125,6 +127,7 @@ describe("Paper methods", function () {
});
it("Paper.getBBox", function() {
paper.circle(50, 50, 30);
console.log(paper.getBBox)
var bb = paper.getBBox();
expect(bb.x).to.be(20);
expect(bb.y).to.be(20);

View File

@ -109,4 +109,33 @@ describe("Primitives creation", function () {
expect(C.getAttribute("x")).to.be("10");
expect(C.textContent).to.be("test");
});
it("creates a mask", function () {
var c = s.mask();
var C = document.querySelector("mask");
expect(C).to.not.be(null);
expect(C).to.be(c.node);
});
it("creates a pattern", function () {
var c = s.ptrn();
var C = document.querySelector("pattern");
expect(C).to.not.be(null);
expect(C).to.be(c.node);
});
it("creates a pattern(x, y)", function() {
var c = s.ptrn(100, 200);
expect(c.node.nodeName).to.be("pattern");
expect(c.node.x.baseVal.value).to.be(100);
expect(c.node.y.baseVal.value).to.be(200);
expect(c.node.parentNode).to.be(s.node);
});
it("creates a pattern(x, y, w, h, viewbox)", function() {
var c = s.ptrn(100, 200, 300, 400, 10, 20, 30, 40);
expect(c.node.nodeName).to.be("pattern");
expect(c.node.x.baseVal.value).to.be(100);
expect(c.node.y.baseVal.value).to.be(200);
expect(c.node.width.baseVal.value).to.be(300);
expect(c.node.height.baseVal.value).to.be(400);
expect(c.node.getAttribute("viewBox")).to.be("10 20 30 40");
expect(c.node.parentNode).to.be(s.node);
});
});

View File

@ -28,7 +28,7 @@
</head>
<body>
<div id="mocha"></div>
<script src="../node_modules/expect.js/expect.js"></script>
<script src="../node_modules/expect.js/index.js"></script>
<script src="../node_modules/mocha/mocha.js"></script>
<script>mocha.setup("bdd");</script>
<script src="system.js"></script>