2012-09-30 17:51:50 -04:00
|
|
|
/*
|
2013-01-28 21:03:27 -05:00
|
|
|
* Paper.js - The Swiss Army Knife of Vector Graphics Scripting.
|
2012-11-02 20:47:14 -04:00
|
|
|
* http://paperjs.org/
|
|
|
|
*
|
2013-01-28 21:03:27 -05:00
|
|
|
* Copyright (c) 2011 - 2013, Juerg Lehni & Jonathan Puckey
|
2012-11-02 20:47:14 -04:00
|
|
|
* http://lehni.org/ & http://jonathanpuckey.com/
|
|
|
|
*
|
|
|
|
* Distributed under the MIT license. See LICENSE file for details.
|
|
|
|
*
|
|
|
|
* All rights reserved.
|
|
|
|
*/
|
2012-09-30 17:51:50 -04:00
|
|
|
|
2012-09-13 20:45:27 -04:00
|
|
|
/**
|
2012-11-06 16:07:18 -05:00
|
|
|
* A function scope holding all the functionality needed to convert a SVG DOM
|
|
|
|
* to a Paper.js DOM.
|
2012-11-02 20:47:14 -04:00
|
|
|
*/
|
2012-11-06 16:07:18 -05:00
|
|
|
new function() {
|
2012-11-02 22:16:23 -04:00
|
|
|
// Define a couple of helper functions to easily read values from SVG
|
|
|
|
// objects, dealing with baseVal, and item lists.
|
|
|
|
// index is option, and if passed, causes a lookup in a list.
|
|
|
|
|
2013-05-09 19:08:38 -04:00
|
|
|
function getValue(node, name, isString, allowNull) {
|
|
|
|
var namespace = SVGNamespaces[name],
|
|
|
|
value = namespace
|
|
|
|
? node.getAttributeNS(namespace, name)
|
|
|
|
: node.getAttribute(name);
|
|
|
|
return isString
|
|
|
|
? value
|
|
|
|
// Interpret value as number. Never return NaN, but 0 instead.
|
|
|
|
// If the value is a sequence of numbers, parseFloat will
|
|
|
|
// return the first occuring number, which is enough for now.
|
|
|
|
: value == 'null' ? 0 : parseFloat(value);
|
2012-11-02 22:16:23 -04:00
|
|
|
}
|
|
|
|
|
2013-05-09 19:08:38 -04:00
|
|
|
function getPoint(node, x, y, allowNull) {
|
|
|
|
x = getValue(node, x, false, allowNull);
|
|
|
|
y = getValue(node, y, false, allowNull);
|
2012-11-10 14:30:34 -05:00
|
|
|
return allowNull && x == null && y == null ? null
|
|
|
|
: Point.create(x || 0, y || 0);
|
2012-11-02 22:16:23 -04:00
|
|
|
}
|
|
|
|
|
2013-05-09 19:08:38 -04:00
|
|
|
function getSize(node, w, h, allowNull) {
|
|
|
|
w = getValue(node, w, false, allowNull);
|
|
|
|
h = getValue(node, h, false, allowNull);
|
2012-11-10 14:30:34 -05:00
|
|
|
return allowNull && w == null && h == null ? null
|
|
|
|
: Size.create(w || 0, h || 0);
|
2012-11-02 22:16:23 -04:00
|
|
|
}
|
|
|
|
|
2012-11-08 12:38:42 -05:00
|
|
|
// Converts a string attribute value to the specified type
|
|
|
|
function convertValue(value, type) {
|
|
|
|
return value === 'none'
|
2012-11-08 11:31:23 -05:00
|
|
|
? null
|
|
|
|
: type === 'number'
|
2013-02-28 17:32:34 -05:00
|
|
|
? parseFloat(value)
|
2012-11-08 11:31:23 -05:00
|
|
|
: type === 'array'
|
2013-02-15 00:09:44 -05:00
|
|
|
? value ? value.split(/[\s,]+/g).map(parseFloat) : []
|
2013-03-01 15:54:27 -05:00
|
|
|
: type === 'color' && getDefinition(value)
|
2012-11-08 12:38:42 -05:00
|
|
|
|| value;
|
2012-11-08 08:32:09 -05:00
|
|
|
}
|
|
|
|
|
2013-02-28 20:22:57 -05:00
|
|
|
// Importer functions for various SVG node types
|
2012-11-02 22:16:23 -04:00
|
|
|
|
2013-02-10 22:37:19 -05:00
|
|
|
function importGroup(node, type) {
|
|
|
|
var nodes = node.childNodes,
|
2013-05-09 04:45:38 -04:00
|
|
|
clip = type === 'clippath',
|
2013-03-01 18:30:57 -05:00
|
|
|
item = clip ? new CompoundPath() : new Group(),
|
|
|
|
project = item._project,
|
2013-02-15 00:42:50 -05:00
|
|
|
currentStyle = project._currentStyle;
|
2013-03-01 18:30:57 -05:00
|
|
|
// Style on items needs to be handled differently than all other items:
|
|
|
|
// We first apply the style to the item, then use it as the project's
|
2013-02-15 00:42:50 -05:00
|
|
|
// currentStyle, so it is used as a default for the creation of all
|
2013-04-23 10:19:08 -04:00
|
|
|
// nested items. importSVG then needs to check for items and avoid
|
2013-02-15 00:42:50 -05:00
|
|
|
// calling applyAttributes() again.
|
2013-03-01 15:32:24 -05:00
|
|
|
// Set the default color to black, since that's how SVG handles fills.
|
2013-03-01 18:30:57 -05:00
|
|
|
item.setFillColor('black');
|
|
|
|
if (!clip) {
|
|
|
|
item = applyAttributes(item, node);
|
|
|
|
project._currentStyle = item._style.clone();
|
|
|
|
}
|
2012-11-06 13:29:14 -05:00
|
|
|
for (var i = 0, l = nodes.length; i < l; i++) {
|
2013-03-01 18:30:57 -05:00
|
|
|
var childNode = nodes[i],
|
|
|
|
child;
|
2013-04-23 10:19:08 -04:00
|
|
|
if (childNode.nodeType == 1 && (child = importSVG(childNode))) {
|
2012-11-06 23:28:20 -05:00
|
|
|
// If adding CompoundPaths to other CompoundPaths,
|
2012-11-06 21:38:09 -05:00
|
|
|
// we need to "unbox" them first:
|
2013-04-19 22:27:02 -04:00
|
|
|
if (clip && child._type === 'compound-path') {
|
2013-03-01 18:30:57 -05:00
|
|
|
item.addChildren(child.removeChildren());
|
|
|
|
child.remove();
|
|
|
|
} else if (!(child instanceof Symbol)) {
|
|
|
|
item.addChild(child);
|
2012-11-06 12:14:17 -05:00
|
|
|
}
|
2012-11-02 19:51:42 -04:00
|
|
|
}
|
2012-09-30 17:51:50 -04:00
|
|
|
}
|
2013-03-01 18:30:57 -05:00
|
|
|
// clip paths are reduced (unboxed) and their attributes applied at the
|
|
|
|
// end.
|
|
|
|
if (clip)
|
|
|
|
item = applyAttributes(item.reduce(), node);
|
2013-02-15 00:42:50 -05:00
|
|
|
// Restore currentStyle
|
|
|
|
project._currentStyle = currentStyle;
|
2013-03-01 18:30:57 -05:00
|
|
|
if (clip || type === 'defs') {
|
|
|
|
// We don't want the defs in the DOM. But we might want to use
|
|
|
|
// Symbols for them to save memory?
|
|
|
|
item.remove();
|
|
|
|
item = null;
|
2012-11-07 11:21:02 -05:00
|
|
|
}
|
2013-03-01 18:30:57 -05:00
|
|
|
return item;
|
2012-11-02 21:40:41 -04:00
|
|
|
}
|
2012-09-30 17:51:50 -04:00
|
|
|
|
2013-02-10 22:37:19 -05:00
|
|
|
function importPoly(node, type) {
|
2012-11-03 01:53:33 -04:00
|
|
|
var path = new Path(),
|
2013-02-10 22:37:19 -05:00
|
|
|
points = node.points;
|
2012-11-06 14:28:50 -05:00
|
|
|
path.moveTo(points.getItem(0));
|
2012-11-02 22:10:58 -04:00
|
|
|
for (var i = 1, l = points.numberOfItems; i < l; i++)
|
2012-11-03 01:53:33 -04:00
|
|
|
path.lineTo(points.getItem(i));
|
2012-11-05 21:27:13 -05:00
|
|
|
if (type === 'polygon')
|
2012-11-03 01:53:33 -04:00
|
|
|
path.closePath();
|
|
|
|
return path;
|
2012-11-02 21:40:41 -04:00
|
|
|
}
|
2012-09-30 17:51:50 -04:00
|
|
|
|
2013-02-10 22:37:19 -05:00
|
|
|
function importPath(node) {
|
2013-02-28 22:13:46 -05:00
|
|
|
// Get the path data, and determine wether it is a compound path or a
|
|
|
|
// normal path based on the amount of moveTo commands inside it.
|
|
|
|
var data = node.getAttribute('d'),
|
|
|
|
path = data.match(/m/gi).length > 1
|
|
|
|
? new CompoundPath()
|
|
|
|
: new Path();
|
|
|
|
path.setPathData(data);
|
|
|
|
return path;
|
2012-11-06 13:28:34 -05:00
|
|
|
}
|
|
|
|
|
2013-02-10 22:37:19 -05:00
|
|
|
function importGradient(node, type) {
|
|
|
|
var nodes = node.childNodes,
|
2012-11-07 13:31:36 -05:00
|
|
|
stops = [];
|
|
|
|
for (var i = 0, l = nodes.length; i < l; i++) {
|
2013-02-11 17:36:44 -05:00
|
|
|
var child = nodes[i];
|
|
|
|
if (child.nodeType == 1)
|
|
|
|
stops.push(applyAttributes(new GradientStop(), child));
|
2012-11-07 13:31:36 -05:00
|
|
|
}
|
2013-05-09 04:45:38 -04:00
|
|
|
var isRadial = type === 'radialgradient',
|
2013-04-08 23:52:21 -04:00
|
|
|
gradient = new Gradient(stops, isRadial),
|
2012-11-07 13:31:36 -05:00
|
|
|
origin, destination, highlight;
|
|
|
|
if (isRadial) {
|
2013-02-10 22:37:19 -05:00
|
|
|
origin = getPoint(node, 'cx', 'cy');
|
|
|
|
destination = origin.add(getValue(node, 'r'), 0);
|
|
|
|
highlight = getPoint(node, 'fx', 'fy', true);
|
2012-11-07 13:31:36 -05:00
|
|
|
} else {
|
2013-02-10 22:37:19 -05:00
|
|
|
origin = getPoint(node, 'x1', 'y1');
|
|
|
|
destination = getPoint(node, 'x2', 'y2');
|
2012-11-07 13:31:36 -05:00
|
|
|
}
|
2012-11-10 18:48:15 -05:00
|
|
|
applyAttributes(
|
2013-04-09 04:21:36 -04:00
|
|
|
new Color(gradient, origin, destination, highlight), node);
|
2013-04-09 11:36:17 -04:00
|
|
|
// We don't return the gradient, since we only need a reference to it in
|
|
|
|
// definitions, which is created in applyAttributes()
|
2012-11-10 18:48:15 -05:00
|
|
|
return null;
|
2012-11-07 13:31:36 -05:00
|
|
|
}
|
|
|
|
|
2013-05-09 04:45:38 -04:00
|
|
|
// NOTE: All importers are lowercase, since jsdom is using uppercase
|
|
|
|
// nodeNames still.
|
2012-11-02 21:40:41 -04:00
|
|
|
var importers = {
|
2012-11-06 12:11:54 -05:00
|
|
|
// http://www.w3.org/TR/SVG/struct.html#Groups
|
2012-11-02 21:40:41 -04:00
|
|
|
g: importGroup,
|
2012-11-06 12:11:54 -05:00
|
|
|
// http://www.w3.org/TR/SVG/struct.html#NewDocument
|
2012-11-02 21:40:41 -04:00
|
|
|
svg: importGroup,
|
2013-05-09 04:45:38 -04:00
|
|
|
clippath: importGroup,
|
2012-11-06 13:28:34 -05:00
|
|
|
// http://www.w3.org/TR/SVG/shapes.html#PolygonElement
|
|
|
|
polygon: importPoly,
|
|
|
|
// http://www.w3.org/TR/SVG/shapes.html#PolylineElement
|
|
|
|
polyline: importPoly,
|
|
|
|
// http://www.w3.org/TR/SVG/paths.html
|
|
|
|
path: importPath,
|
2012-11-08 18:01:13 -05:00
|
|
|
// http://www.w3.org/TR/SVG/pservers.html#LinearGradients
|
2013-05-09 04:45:38 -04:00
|
|
|
lineargradient: importGradient,
|
2012-11-08 18:01:13 -05:00
|
|
|
// http://www.w3.org/TR/SVG/pservers.html#RadialGradients
|
2013-05-09 04:45:38 -04:00
|
|
|
radialgradient: importGradient,
|
2012-11-06 12:14:17 -05:00
|
|
|
|
2012-12-02 13:41:29 -05:00
|
|
|
// http://www.w3.org/TR/SVG/struct.html#ImageElement
|
2013-02-10 22:37:19 -05:00
|
|
|
image: function (node) {
|
2013-05-09 19:08:38 -04:00
|
|
|
var raster = new Raster(getValue(node, 'href', true));
|
2012-12-03 00:08:57 -05:00
|
|
|
raster.attach('load', function() {
|
2013-02-10 22:37:19 -05:00
|
|
|
var size = getSize(node, 'width', 'height');
|
2012-12-02 13:41:29 -05:00
|
|
|
this.setSize(size);
|
|
|
|
// Since x and y start from the top left of an image, add
|
|
|
|
// half of its size:
|
2013-02-10 22:37:19 -05:00
|
|
|
this.translate(getPoint(node, 'x', 'y').add(size.divide(2)));
|
2012-12-03 00:08:57 -05:00
|
|
|
});
|
2012-12-02 13:41:29 -05:00
|
|
|
return raster;
|
|
|
|
},
|
|
|
|
|
2012-11-06 12:11:54 -05:00
|
|
|
// http://www.w3.org/TR/SVG/struct.html#SymbolElement
|
2013-02-10 22:37:19 -05:00
|
|
|
symbol: function(node, type) {
|
2013-03-01 14:19:47 -05:00
|
|
|
// Pass true for dontCenter:
|
|
|
|
return new Symbol(importGroup(node, type), true);
|
2012-11-06 12:11:54 -05:00
|
|
|
},
|
2012-11-06 12:14:17 -05:00
|
|
|
|
|
|
|
// http://www.w3.org/TR/SVG/struct.html#DefsElement
|
2012-11-07 11:21:02 -05:00
|
|
|
defs: importGroup,
|
2012-11-07 11:21:35 -05:00
|
|
|
|
|
|
|
// http://www.w3.org/TR/SVG/struct.html#UseElement
|
2013-02-10 22:37:19 -05:00
|
|
|
use: function(node, type) {
|
2012-11-10 14:19:06 -05:00
|
|
|
// Note the namespaced xlink:href attribute is just called href
|
2013-02-10 22:37:19 -05:00
|
|
|
// as a property on node.
|
2013-03-01 14:19:47 -05:00
|
|
|
// TODO: Support overflow and width, height, in combination with
|
|
|
|
// overflow: hidden. Paper.js currently does not suport PlacedSymbol
|
|
|
|
// clipping, but perhaps it should?
|
2013-05-09 19:08:38 -04:00
|
|
|
var id = (getValue(node, 'href', true) || '').substring(1),
|
2013-03-01 17:52:20 -05:00
|
|
|
definition = definitions[id],
|
|
|
|
point = getPoint(node, 'x', 'y');
|
2012-11-10 09:56:56 -05:00
|
|
|
// Use place if we're dealing with a symbol:
|
2012-11-10 14:19:06 -05:00
|
|
|
return definition
|
|
|
|
? definition instanceof Symbol
|
2013-03-01 15:14:21 -05:00
|
|
|
// When placing symbols, we nee to take both point and
|
|
|
|
// matrix into account. This just does the right thing:
|
2013-03-01 17:52:20 -05:00
|
|
|
? definition.place(point)
|
|
|
|
: definition.clone().translate(point)
|
2012-11-10 14:19:06 -05:00
|
|
|
: null;
|
2012-11-06 12:14:17 -05:00
|
|
|
},
|
|
|
|
|
2012-11-06 12:11:54 -05:00
|
|
|
// http://www.w3.org/TR/SVG/shapes.html#InterfaceSVGCircleElement
|
2013-02-10 22:37:19 -05:00
|
|
|
circle: function(node) {
|
|
|
|
return new Path.Circle(getPoint(node, 'cx', 'cy'),
|
|
|
|
getValue(node, 'r'));
|
2012-11-02 21:40:41 -04:00
|
|
|
},
|
2012-09-30 17:51:50 -04:00
|
|
|
|
2012-11-06 12:11:54 -05:00
|
|
|
// http://www.w3.org/TR/SVG/shapes.html#InterfaceSVGEllipseElement
|
2013-02-10 22:37:19 -05:00
|
|
|
ellipse: function(node) {
|
|
|
|
var center = getPoint(node, 'cx', 'cy'),
|
|
|
|
radius = getSize(node, 'rx', 'ry');
|
2012-11-06 14:37:00 -05:00
|
|
|
return new Path.Ellipse(new Rectangle(center.subtract(radius),
|
2012-11-02 22:04:29 -04:00
|
|
|
center.add(radius)));
|
2012-11-02 21:40:41 -04:00
|
|
|
},
|
2012-09-30 17:51:50 -04:00
|
|
|
|
2012-11-06 12:11:54 -05:00
|
|
|
// http://www.w3.org/TR/SVG/shapes.html#RectElement
|
2013-02-10 22:37:19 -05:00
|
|
|
rect: function(node) {
|
|
|
|
var point = getPoint(node, 'x', 'y'),
|
|
|
|
size = getSize(node, 'width', 'height'),
|
|
|
|
radius = getSize(node, 'rx', 'ry');
|
2013-04-19 15:36:49 -04:00
|
|
|
return new Path.Rectangle(new Rectangle(point, size), radius);
|
2012-11-02 21:40:41 -04:00
|
|
|
},
|
2012-09-30 17:51:50 -04:00
|
|
|
|
2012-11-06 12:11:54 -05:00
|
|
|
// http://www.w3.org/TR/SVG/shapes.html#LineElement
|
2013-02-10 22:37:19 -05:00
|
|
|
line: function(node) {
|
|
|
|
return new Path.Line(getPoint(node, 'x1', 'y1'),
|
|
|
|
getPoint(node, 'x2', 'y2'));
|
2012-11-02 21:40:41 -04:00
|
|
|
},
|
2012-09-30 17:51:50 -04:00
|
|
|
|
2013-02-10 22:37:19 -05:00
|
|
|
text: function(node) {
|
2012-11-02 21:40:41 -04:00
|
|
|
// Not supported by Paper.js
|
|
|
|
// x: multiple values for x
|
|
|
|
// y: multiple values for y
|
|
|
|
// dx: multiple values for x
|
|
|
|
// dy: multiple values for y
|
2012-11-06 13:37:03 -05:00
|
|
|
// TODO: Support for these is missing in Paper.js right now
|
2012-11-02 21:40:41 -04:00
|
|
|
// rotate: character rotation
|
|
|
|
// lengthAdjust:
|
2013-05-09 19:08:38 -04:00
|
|
|
var text = new PointText(getPoint(node, 'x', 'y', false)
|
|
|
|
.add(getPoint(node, 'dx', 'dy', false)));
|
2013-03-01 16:29:48 -05:00
|
|
|
text.setContent(node.textContent.trim() || '');
|
2012-11-02 21:40:41 -04:00
|
|
|
return text;
|
2012-11-08 18:01:13 -05:00
|
|
|
}
|
2012-11-02 21:40:41 -04:00
|
|
|
};
|
2012-09-30 17:51:50 -04:00
|
|
|
|
2013-02-28 20:22:57 -05:00
|
|
|
// Attributes and Styles
|
2013-02-28 18:40:05 -05:00
|
|
|
|
2013-02-28 20:22:57 -05:00
|
|
|
// NOTE: Parmeter sequence for all apply*() functions is:
|
|
|
|
// (item, value, name, node) rather than (item, node, name, value),
|
|
|
|
// so we can ommit the less likely parameters from right to left.
|
|
|
|
|
|
|
|
function applyTransform(item, value, name, node) {
|
2013-02-28 18:40:05 -05:00
|
|
|
// http://www.w3.org/TR/SVG/types.html#DataTypeTransformList
|
2013-05-09 05:31:10 -04:00
|
|
|
// Parse SVG transform string. First we split at /)\s*/, to separate
|
|
|
|
// commands
|
|
|
|
var transforms = (node.getAttribute(name) || '').split(/\)\s*/g),
|
2013-02-28 18:40:05 -05:00
|
|
|
matrix = new Matrix();
|
2013-05-09 05:31:10 -04:00
|
|
|
for (var i = 0, l = transforms.length; i < l; i++) {
|
|
|
|
var transform = transforms[i];
|
|
|
|
if (!transform)
|
|
|
|
break;
|
|
|
|
// Command come before the '(', values after
|
|
|
|
var parts = transform.split('('),
|
|
|
|
command = parts[0],
|
|
|
|
v = parts[1].split(/[\s,]+/g);
|
|
|
|
// Convert values to floats
|
|
|
|
for (var j = 0, m = v.length; j < m; j++)
|
|
|
|
v[j] = parseFloat(v[j]);
|
|
|
|
switch (command) {
|
|
|
|
case 'matrix':
|
|
|
|
matrix.concatenate(
|
|
|
|
new Matrix(v[0], v[2], v[1], v[3], v[4], v[5]));
|
|
|
|
break;
|
|
|
|
case 'rotate':
|
|
|
|
matrix.rotate(v[0], v[1], v[2]);
|
|
|
|
break;
|
|
|
|
case 'translate':
|
|
|
|
matrix.translate(v[0], v[1]);
|
|
|
|
break;
|
|
|
|
case 'scale':
|
|
|
|
matrix.scale(v);
|
|
|
|
break;
|
|
|
|
case 'skewX':
|
|
|
|
case 'skewY':
|
|
|
|
var value = Math.tan(v[0] * Math.PI / 180),
|
|
|
|
isX = command == 'skewX';
|
|
|
|
matrix.shear(isX ? value : 0, isX ? 0 : value);
|
|
|
|
break;
|
|
|
|
}
|
2013-02-28 18:40:05 -05:00
|
|
|
}
|
|
|
|
item.transform(matrix);
|
|
|
|
}
|
|
|
|
|
2013-02-28 20:22:57 -05:00
|
|
|
function applyOpacity(item, value, name) {
|
2013-02-28 18:40:05 -05:00
|
|
|
// http://www.w3.org/TR/SVG/painting.html#FillOpacityProperty
|
|
|
|
// http://www.w3.org/TR/SVG/painting.html#StrokeOpacityProperty
|
2013-03-01 15:32:24 -05:00
|
|
|
var color = item._style[name === 'fill-opacity' ? 'getFillColor'
|
2013-02-28 18:40:05 -05:00
|
|
|
: 'getStrokeColor']();
|
|
|
|
if (color)
|
|
|
|
color.setAlpha(parseFloat(value));
|
|
|
|
}
|
|
|
|
|
2013-05-09 05:37:48 -04:00
|
|
|
// Create apply-functions for attributes, and merge in those for SVGStlyes.
|
|
|
|
// We need to define style attributes first, and merge in all others after,
|
|
|
|
// since transform needs to be applied after fill color, as transformations
|
|
|
|
// can affect gradient fills.
|
|
|
|
var attributes = Base.merge(Base.each(SVGStyles, function(entry) {
|
2013-02-28 20:22:57 -05:00
|
|
|
this[entry.attribute] = function(item, value, name, node) {
|
2013-02-28 18:57:47 -05:00
|
|
|
item._style[entry.set](convertValue(value, entry.type));
|
|
|
|
};
|
2013-05-09 05:37:48 -04:00
|
|
|
}, {}), {
|
2013-02-28 20:22:57 -05:00
|
|
|
id: function(item, value) {
|
2013-02-28 18:40:05 -05:00
|
|
|
definitions[value] = item;
|
|
|
|
if (item.setName)
|
|
|
|
item.setName(value);
|
|
|
|
},
|
|
|
|
|
2013-02-28 20:22:57 -05:00
|
|
|
'clip-path': function(item, value) {
|
2013-02-28 18:40:05 -05:00
|
|
|
// http://www.w3.org/TR/SVG/masking.html#ClipPathProperty
|
2013-03-01 15:54:27 -05:00
|
|
|
var clip = getDefinition(value);
|
|
|
|
if (clip) {
|
2013-03-01 18:30:57 -05:00
|
|
|
clip = clip.clone();
|
2013-03-01 15:54:27 -05:00
|
|
|
clip.setClipMask(true);
|
|
|
|
return new Group(clip, item);
|
|
|
|
}
|
2013-02-28 18:40:05 -05:00
|
|
|
},
|
|
|
|
|
|
|
|
gradientTransform: applyTransform,
|
|
|
|
transform: applyTransform,
|
|
|
|
|
2013-02-28 20:22:57 -05:00
|
|
|
opacity: function(item, value) {
|
2013-02-28 18:40:05 -05:00
|
|
|
// http://www.w3.org/TR/SVG/masking.html#OpacityProperty
|
|
|
|
item.setOpacity(parseFloat(value));
|
|
|
|
},
|
|
|
|
|
|
|
|
'fill-opacity': applyOpacity,
|
|
|
|
'stroke-opacity': applyOpacity,
|
|
|
|
|
2013-03-01 13:27:19 -05:00
|
|
|
'font-family': function(item, value) {
|
2013-04-09 19:46:20 -04:00
|
|
|
item.setFont(value.split(',')[0].replace(/^\s+|\s+$/g, ''));
|
2013-03-01 13:27:19 -05:00
|
|
|
},
|
|
|
|
|
|
|
|
'font-size': function(item, value) {
|
2013-04-09 19:46:20 -04:00
|
|
|
item.setFontSize(parseFloat(value));
|
2013-03-01 13:27:19 -05:00
|
|
|
},
|
|
|
|
|
|
|
|
'text-anchor': function(item, value) {
|
2013-04-09 19:46:20 -04:00
|
|
|
// http://www.w3.org/TR/SVG/text.html#TextAnchorProperty
|
|
|
|
item.setJustification({
|
|
|
|
start: 'left',
|
|
|
|
middle: 'center',
|
|
|
|
end: 'right'
|
|
|
|
}[value]);
|
2013-03-01 13:27:19 -05:00
|
|
|
},
|
2013-02-28 18:40:05 -05:00
|
|
|
|
2013-02-28 20:22:57 -05:00
|
|
|
visibility: function(item, value) {
|
2013-02-28 18:40:05 -05:00
|
|
|
item.setVisible(value === 'visible');
|
|
|
|
},
|
|
|
|
|
2013-02-28 20:22:57 -05:00
|
|
|
'stop-color': function(item, value) {
|
2013-02-28 18:40:05 -05:00
|
|
|
// http://www.w3.org/TR/SVG/pservers.html#StopColorProperty
|
|
|
|
item.setColor(value);
|
|
|
|
},
|
|
|
|
|
2013-02-28 20:22:57 -05:00
|
|
|
'stop-opacity': function(item, value) {
|
2013-02-28 20:15:03 -05:00
|
|
|
// http://www.w3.org/TR/SVG/pservers.html#StopOpacityProperty
|
|
|
|
// NOTE: It is important that this is applied after stop-color!
|
|
|
|
if (item._color)
|
|
|
|
item._color.setAlpha(parseFloat(value));
|
|
|
|
},
|
|
|
|
|
2013-02-28 20:22:57 -05:00
|
|
|
offset: function(item, value) {
|
2013-02-28 18:40:05 -05:00
|
|
|
// http://www.w3.org/TR/SVG/pservers.html#StopElementOffsetAttribute
|
|
|
|
var percentage = value.match(/(.*)%$/);
|
2013-05-09 04:57:07 -04:00
|
|
|
item.setRampPoint(percentage
|
|
|
|
? percentage[1] / 100
|
|
|
|
: parseFloat(value));
|
2013-02-28 18:40:05 -05:00
|
|
|
},
|
|
|
|
|
2013-03-01 14:19:47 -05:00
|
|
|
viewBox: function(item, value, name, node, styles) {
|
2013-02-28 18:40:05 -05:00
|
|
|
// http://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute
|
|
|
|
// TODO: implement preserveAspectRatio attribute
|
2013-03-01 15:14:21 -05:00
|
|
|
// viewBox will be applied both to the group that's created for the
|
|
|
|
// content in Symbol.definition, and the Symbol itself.
|
2013-03-01 16:10:05 -05:00
|
|
|
var rect = Rectangle.create.apply(this, convertValue(value, 'array')),
|
|
|
|
size = getSize(node, 'width', 'height', true);
|
|
|
|
if (item instanceof Group) {
|
2013-03-01 16:29:48 -05:00
|
|
|
// This is either a top-level svg node, or the container for a
|
2013-03-01 16:10:05 -05:00
|
|
|
// symbol.
|
|
|
|
var scale = size ? rect.getSize().divide(size) : 1,
|
|
|
|
matrix = new Matrix().translate(rect.getPoint()).scale(scale);
|
|
|
|
item.transform(matrix.inverted());
|
|
|
|
} else if (item instanceof Symbol) {
|
|
|
|
// The symbol is wrapping a group. Note that viewBox was already
|
|
|
|
// applied to the group, and above code was executed for it.
|
|
|
|
// All that is left to handle here on the Symbol level is
|
|
|
|
// clipping. We can't do it at group level because
|
|
|
|
// applyAttributes() gets called for groups before their
|
|
|
|
// children are added, for styling reasons. See importGroup()
|
|
|
|
if (size)
|
|
|
|
rect.setSize(size);
|
|
|
|
var clip = getAttribute(node, 'overflow', styles) != 'visible',
|
|
|
|
group = item._definition;
|
|
|
|
if (clip && !rect.contains(group.getBounds())) {
|
|
|
|
// Add a clip path at the top of this symbol's group
|
|
|
|
clip = new Path.Rectangle(rect).transform(group._matrix);
|
|
|
|
clip.setClipMask(true);
|
|
|
|
group.addChild(clip);
|
|
|
|
}
|
2013-03-01 13:08:17 -05:00
|
|
|
}
|
2013-02-28 18:40:05 -05:00
|
|
|
}
|
2013-02-28 18:57:47 -05:00
|
|
|
});
|
2013-02-28 18:40:05 -05:00
|
|
|
|
2013-03-01 14:18:50 -05:00
|
|
|
function getAttribute(node, name, styles) {
|
|
|
|
// First see if the given attribute is defined.
|
|
|
|
var attr = node.attributes[name],
|
|
|
|
value = attr && attr.value;
|
|
|
|
if (!value) {
|
|
|
|
// Fallback to using styles. See if there is a style, either set
|
|
|
|
// directly on the object or applied to it through CSS rules.
|
|
|
|
// We also need to filter out inheritance from their parents.
|
|
|
|
var style = Base.camelize(name);
|
|
|
|
value = node.style[style];
|
|
|
|
if (!value && styles.node[style] !== styles.parent[style])
|
|
|
|
value = styles.node[style];
|
|
|
|
}
|
2013-03-01 15:32:24 -05:00
|
|
|
// Return undefined if attribute is not defined, but null if it's
|
|
|
|
// defined as not set (e.g. fill / stroke).
|
|
|
|
return !value
|
|
|
|
? undefined
|
|
|
|
: value === 'none'
|
|
|
|
? null
|
|
|
|
: value;
|
2013-03-01 14:18:50 -05:00
|
|
|
}
|
|
|
|
|
2012-09-15 23:58:39 -04:00
|
|
|
/**
|
2012-09-30 17:51:50 -04:00
|
|
|
* Converts various SVG styles and attributes into Paper.js styles and
|
2012-11-02 21:14:20 -04:00
|
|
|
* attributes and applies them to the passed item.
|
2012-09-30 17:51:50 -04:00
|
|
|
*
|
2013-02-10 22:37:19 -05:00
|
|
|
* @param {SVGSVGElement} node an SVG node to read style and attributes from.
|
2012-11-02 21:14:20 -04:00
|
|
|
* @param {Item} item the item to apply the style and attributes to.
|
2012-09-15 23:58:39 -04:00
|
|
|
*/
|
2013-02-10 22:37:19 -05:00
|
|
|
function applyAttributes(item, node) {
|
2012-11-05 21:26:29 -05:00
|
|
|
// SVG attributes can be set both as styles and direct node attributes,
|
2013-02-28 20:22:57 -05:00
|
|
|
// so we need to handle both.
|
2013-03-01 14:18:50 -05:00
|
|
|
var styles = {
|
2013-03-01 17:17:31 -05:00
|
|
|
node: DomElement.getStyles(node) || {},
|
|
|
|
parent: DomElement.getStyles(node.parentNode) || {}
|
2013-03-01 14:18:50 -05:00
|
|
|
};
|
|
|
|
Base.each(attributes, function(apply, name) {
|
|
|
|
var value = getAttribute(node, name, styles);
|
2013-03-01 15:32:24 -05:00
|
|
|
if (value !== undefined)
|
2013-03-01 14:18:50 -05:00
|
|
|
item = Base.pick(apply(item, value, name, node, styles), item);
|
2013-02-28 20:15:03 -05:00
|
|
|
});
|
2012-11-06 23:13:29 -05:00
|
|
|
return item;
|
2012-11-02 21:40:41 -04:00
|
|
|
}
|
2012-09-30 17:51:50 -04:00
|
|
|
|
2013-02-10 13:23:49 -05:00
|
|
|
var definitions = {};
|
|
|
|
function getDefinition(value) {
|
2013-02-28 20:15:03 -05:00
|
|
|
// When url() comes from a style property, '#'' seems to be missing on
|
|
|
|
// WebKit, so let's make it optional here:
|
2013-03-01 15:54:27 -05:00
|
|
|
var match = value && value.match(/\((?:#|)([^)']+)/);
|
2013-02-10 13:23:49 -05:00
|
|
|
return match && definitions[match[1]];
|
|
|
|
}
|
|
|
|
|
2013-04-23 10:19:08 -04:00
|
|
|
function importSVG(node, clearDefs) {
|
2013-05-09 03:22:42 -04:00
|
|
|
if (typeof node === 'string')
|
|
|
|
node = new DOMParser().parseFromString(node, 'image/svg+xml');
|
2013-05-08 21:18:47 -04:00
|
|
|
// jsdom in Node.js uses uppercase values for nodeName...
|
|
|
|
var type = node.nodeName.toLowerCase(),
|
2012-11-07 13:31:36 -05:00
|
|
|
importer = importers[type],
|
2013-04-09 20:53:26 -04:00
|
|
|
item = importer && importer(node, type),
|
|
|
|
data = node.getAttribute('data-paper-data');
|
2013-02-15 00:42:50 -05:00
|
|
|
// See importGroup() for an explanation of this filtering:
|
2013-04-06 12:07:30 -04:00
|
|
|
if (item && item._type !== 'group')
|
2013-02-15 00:42:50 -05:00
|
|
|
item = applyAttributes(item, node);
|
2013-04-09 20:53:26 -04:00
|
|
|
if (item && data)
|
|
|
|
item._data = JSON.parse(data);
|
2013-02-09 16:59:04 -05:00
|
|
|
// Clear definitions at the end of import?
|
|
|
|
if (clearDefs)
|
|
|
|
definitions = {};
|
|
|
|
return item;
|
2012-11-06 16:07:18 -05:00
|
|
|
}
|
|
|
|
|
2012-11-06 16:34:46 -05:00
|
|
|
Item.inject(/** @lends Item# */{
|
2012-11-02 21:40:41 -04:00
|
|
|
/**
|
2013-02-10 22:37:19 -05:00
|
|
|
* Converts the passed node node into a Paper.js item and adds it to the
|
2012-11-06 16:07:18 -05:00
|
|
|
* children of this item.
|
2012-11-02 21:40:41 -04:00
|
|
|
*
|
2013-02-10 22:37:19 -05:00
|
|
|
* @param {SVGSVGElement} node the SVG DOM node to convert
|
2012-11-02 21:40:41 -04:00
|
|
|
* @return {Item} the converted Paper.js item
|
|
|
|
*/
|
2013-04-23 10:19:08 -04:00
|
|
|
importSVG: function(node) {
|
|
|
|
return this.addChild(importSVG(node, true));
|
2012-11-02 21:40:41 -04:00
|
|
|
}
|
2012-11-06 16:07:18 -05:00
|
|
|
});
|
2012-11-06 15:49:12 -05:00
|
|
|
|
2012-11-06 16:34:46 -05:00
|
|
|
Project.inject(/** @lends Project# */{
|
2012-11-06 16:07:18 -05:00
|
|
|
/**
|
2013-02-10 22:37:19 -05:00
|
|
|
* Converts the passed node node into a Paper.js item and adds it to the
|
2012-11-06 16:07:18 -05:00
|
|
|
* active layer of this project.
|
|
|
|
*
|
2013-02-10 22:37:19 -05:00
|
|
|
* @param {SVGSVGElement} node the SVG DOM node to convert
|
2012-11-06 16:07:18 -05:00
|
|
|
* @return {Item} the converted Paper.js item
|
|
|
|
*/
|
2013-04-23 10:19:08 -04:00
|
|
|
importSVG: function(node) {
|
2012-11-06 16:07:18 -05:00
|
|
|
this.activate();
|
2013-04-23 10:19:08 -04:00
|
|
|
return importSVG(node, true);
|
2012-11-06 16:07:18 -05:00
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|