2012-11-02 20:47:14 -04:00
|
|
|
/*
|
2012-09-30 17:51:50 -04:00
|
|
|
* Paper.js
|
|
|
|
*
|
|
|
|
* This file is part of Paper.js, a JavaScript Vector Graphics Library,
|
|
|
|
* based on Scriptographer.org and designed to be largely API compatible.
|
|
|
|
* http://paperjs.org/
|
|
|
|
* http://scriptographer.org/
|
|
|
|
*
|
|
|
|
* Copyright (c) 2011, Juerg Lehni & Jonathan Puckey
|
|
|
|
* http://lehni.org/ & http://jonathanpuckey.com/
|
|
|
|
*
|
|
|
|
* Distributed under the MIT license. See LICENSE file for details.
|
|
|
|
*
|
|
|
|
* All rights reserved.
|
|
|
|
*
|
2012-11-02 21:14:20 -04:00
|
|
|
* The base for this code was donated by Stetson-Team-Alpha.
|
2012-09-13 20:45:27 -04:00
|
|
|
*/
|
|
|
|
|
2012-11-04 02:04:15 -05:00
|
|
|
/**
|
|
|
|
* @name SvgExporter
|
|
|
|
*
|
|
|
|
* @class The SvgExporter object holds all the functionality to convert a
|
|
|
|
* Paper.js DOM to a SVG DOM.
|
|
|
|
*/
|
2012-09-30 17:51:50 -04:00
|
|
|
|
2012-11-04 01:43:45 -05:00
|
|
|
var SvgExporter = this.SvgExporter = new function() {
|
2012-10-24 23:21:59 -04:00
|
|
|
|
2012-11-04 12:01:11 -05:00
|
|
|
function setAttributes(svg, attrs) {
|
2012-11-05 22:03:42 -05:00
|
|
|
for (var key in attrs)
|
2012-11-04 12:01:11 -05:00
|
|
|
svg.setAttribute(key, attrs[key]);
|
2012-11-05 23:31:45 -05:00
|
|
|
return svg;
|
|
|
|
}
|
|
|
|
|
|
|
|
function createElement(tag, attrs) {
|
|
|
|
return setAttributes(
|
|
|
|
document.createElementNS('http://www.w3.org/2000/svg', tag), attrs);
|
2012-11-04 12:01:11 -05:00
|
|
|
}
|
|
|
|
|
2012-11-05 23:50:31 -05:00
|
|
|
function getDistance(segments, index1, index2) {
|
|
|
|
return segments[index1]._point.getDistance(segments[index2]._point);
|
|
|
|
}
|
|
|
|
|
2012-11-04 01:43:45 -05:00
|
|
|
function exportGroup(group) {
|
|
|
|
var svg = createElement('g'),
|
2012-11-02 19:51:42 -04:00
|
|
|
children = group._children;
|
2012-11-04 01:43:45 -05:00
|
|
|
for (var i = 0, l = children.length; i < l; i++)
|
|
|
|
svg.appendChild(SvgExporter.exportItem(children[i]));
|
2012-11-05 22:03:42 -05:00
|
|
|
// Override default SVG style on groups, then apply style.
|
2012-11-05 23:50:31 -05:00
|
|
|
return setAttributes(svg, {
|
2012-11-05 22:03:42 -05:00
|
|
|
fill: 'none'
|
|
|
|
});
|
2012-11-04 01:43:45 -05:00
|
|
|
}
|
|
|
|
|
2012-11-05 23:50:31 -05:00
|
|
|
function exportText(item) {
|
|
|
|
var point = item.getPoint(),
|
|
|
|
style = item._style,
|
|
|
|
attrs = {
|
|
|
|
x: point._x,
|
|
|
|
y: point._y
|
|
|
|
};
|
|
|
|
if (style._font != null)
|
|
|
|
attrs['font-family'] = style._font;
|
|
|
|
if (style._fontSize != null)
|
|
|
|
attrs['font-size'] = style._fontSize;
|
|
|
|
var svg = createElement('text', attrs);
|
|
|
|
svg.textContent = item.getContent();
|
|
|
|
svg.setAttribute('transform','rotate(' + item.matrix.getRotation() + ',' + item.getPoint()._x + ',' + item.getPoint()._y +')');
|
|
|
|
return svg;
|
2012-11-05 23:10:31 -05:00
|
|
|
}
|
|
|
|
|
2012-11-05 23:50:31 -05:00
|
|
|
function exportPath(path) {
|
|
|
|
var segments = path._segments,
|
2012-11-05 22:44:23 -05:00
|
|
|
type = determineType(path, segments);
|
2012-11-06 00:19:53 -05:00
|
|
|
// If the object is a circle, ellipse, rectangle, or rounded rectangle,
|
|
|
|
// see if they are placed at an angle.
|
|
|
|
if (/^(circle|ellipse|rect|roundrect)$/.test(type)) {
|
|
|
|
var angle = determineIfTransformed(path, segments, type) + 90;
|
|
|
|
if (angle !== 0) {
|
|
|
|
// TODO: Need to implement exported transforms for circle, ellipse,
|
|
|
|
// and rectangles instead of making them paths
|
|
|
|
return pathSetup(path, segments);
|
|
|
|
}
|
|
|
|
}
|
2012-09-30 17:51:50 -04:00
|
|
|
switch (type) {
|
2012-10-22 19:31:08 -04:00
|
|
|
case 'rect':
|
2012-11-05 23:31:45 -05:00
|
|
|
var width = getDistance(segments, 0, 3),
|
|
|
|
height = getDistance(segments, 0, 1),
|
|
|
|
point = path.getBounds().getTopLeft();
|
2012-11-06 00:19:53 -05:00
|
|
|
return createElement('rect', {
|
2012-11-05 23:31:45 -05:00
|
|
|
x: point._x,
|
|
|
|
y: point._y,
|
|
|
|
width: width,
|
|
|
|
height: height
|
|
|
|
});
|
2012-11-02 21:23:23 -04:00
|
|
|
case 'roundrect':
|
2012-11-05 23:50:31 -05:00
|
|
|
// d-variables and point are used to determine the rounded corners
|
|
|
|
// for the rounded rectangle
|
2012-11-06 00:16:36 -05:00
|
|
|
var width = getDistance(segments, 1, 6),
|
|
|
|
height = getDistance(segments, 0, 3),
|
|
|
|
point = path.getBounds().getTopLeft(),
|
|
|
|
dx2 = getDistance(segments, 0, 7),
|
|
|
|
dy2 = getDistance(segments, 1, 2),
|
|
|
|
dx3 = (width - dx2) / 2,
|
|
|
|
dy3 = (height - dy2) / 2,
|
|
|
|
point = new Point((segments[3]._point._x - dx3), (segments[2]._point._y - dy3)),
|
|
|
|
rx = segments[3]._point._x - point.x,
|
|
|
|
ry = segments[2]._point._y - point.y;
|
2012-11-06 00:19:53 -05:00
|
|
|
return createElement('rect', {
|
2012-11-06 00:16:36 -05:00
|
|
|
x: point._x,
|
|
|
|
y: point._y,
|
2012-11-05 23:31:45 -05:00
|
|
|
width: width,
|
2012-11-06 00:16:36 -05:00
|
|
|
height: height,
|
|
|
|
rx: rx,
|
|
|
|
ry: ry
|
2012-11-05 23:31:45 -05:00
|
|
|
});
|
2012-10-22 19:31:08 -04:00
|
|
|
case'line':
|
2012-11-05 23:31:45 -05:00
|
|
|
var first = segments[0]._point,
|
|
|
|
last = segments[segments.length - 1]._point;
|
2012-11-06 00:19:53 -05:00
|
|
|
return createElement('line', {
|
2012-11-05 23:31:45 -05:00
|
|
|
x1: first._x,
|
|
|
|
y1: first._y,
|
|
|
|
x2: last._x,
|
|
|
|
y2: last._y
|
|
|
|
});
|
2012-10-22 19:31:08 -04:00
|
|
|
case 'circle':
|
2012-11-05 23:31:45 -05:00
|
|
|
var radius = getDistance(segments, 0, 2) / 2,
|
|
|
|
center = path.getPosition();
|
2012-11-06 00:19:53 -05:00
|
|
|
return createElement('circle', {
|
2012-11-05 23:31:45 -05:00
|
|
|
cx: center._x,
|
|
|
|
cy: center._y,
|
|
|
|
r: radius
|
|
|
|
});
|
2012-10-22 19:31:08 -04:00
|
|
|
case 'ellipse':
|
2012-11-06 00:19:53 -05:00
|
|
|
var rx = getDistance(segments, 2, 0) / 2,
|
|
|
|
ry = getDistance(segments, 3, 1) / 2,
|
2012-11-05 23:31:45 -05:00
|
|
|
center = path.getPosition();
|
2012-11-06 00:19:53 -05:00
|
|
|
return createElement('ellipse', {
|
2012-11-05 23:31:45 -05:00
|
|
|
cx: center._x,
|
|
|
|
cy: center._y,
|
2012-11-06 00:19:53 -05:00
|
|
|
rx: rx,
|
|
|
|
ry: ry
|
2012-11-05 23:31:45 -05:00
|
|
|
});
|
2012-10-22 19:31:08 -04:00
|
|
|
case 'polyline':
|
|
|
|
case 'polygon':
|
2012-11-05 22:56:49 -05:00
|
|
|
var parts = [];
|
2012-11-05 22:44:23 -05:00
|
|
|
for(i = 0; i < segments.length; i++) {
|
2012-11-05 23:31:45 -05:00
|
|
|
var point = segments[i]._point;
|
|
|
|
parts.push(point._x + ',' + point._y);
|
2012-10-22 19:31:08 -04:00
|
|
|
}
|
2012-11-06 00:19:53 -05:00
|
|
|
return createElement(type, {
|
2012-11-05 23:31:45 -05:00
|
|
|
points: parts.join(' ')
|
|
|
|
});
|
2012-09-30 17:51:50 -04:00
|
|
|
}
|
2012-11-06 00:19:53 -05:00
|
|
|
return pathSetup(path, segments);
|
2012-11-04 01:43:45 -05:00
|
|
|
}
|
2012-09-13 20:45:27 -04:00
|
|
|
|
2012-11-05 22:44:23 -05:00
|
|
|
function pathSetup(path, segments) {
|
2012-11-05 23:50:31 -05:00
|
|
|
var svg = createElement('path');
|
2012-11-05 22:56:49 -05:00
|
|
|
var parts = [];
|
|
|
|
parts.push('M' + segments[0]._point._x + ',' + segments[0]._point._y);
|
2012-11-05 23:06:54 -05:00
|
|
|
function drawCurve(seg1, seg2, skipLine) {
|
2012-11-05 23:03:03 -05:00
|
|
|
var point1 = seg1._point,
|
|
|
|
point2 = seg2._point,
|
|
|
|
x1 = point1._x,
|
|
|
|
y1 = point1._y,
|
|
|
|
x2 = point2._x,
|
|
|
|
y2 = point2._y,
|
|
|
|
handle1 = seg1._handleOut,
|
|
|
|
handle2 = seg2._handleIn;
|
|
|
|
if (handle1.isZero() && handle2.isZero()) {
|
2012-11-05 23:06:54 -05:00
|
|
|
if (!skipLine) {
|
2012-11-02 21:23:37 -04:00
|
|
|
// L is lineto, moving to a point with drawing
|
2012-11-05 22:56:49 -05:00
|
|
|
parts.push('L' + x2 + ',' + y2 + ' ');
|
2012-11-05 23:06:54 -05:00
|
|
|
}
|
2012-09-30 17:51:50 -04:00
|
|
|
} else {
|
2012-11-05 23:03:03 -05:00
|
|
|
// c is curveto, relative: handle1, handle2 + end - start, end - start
|
|
|
|
x2 -= x1;
|
|
|
|
y2 -= y1;
|
|
|
|
parts.push(
|
|
|
|
'c' + handle1._x + ',' + handle1._y,
|
|
|
|
(x2 + handle2._x) + ',' + (y2 + handle2._y),
|
|
|
|
x2 + ',' + y2
|
|
|
|
);
|
2012-09-30 17:51:50 -04:00
|
|
|
}
|
|
|
|
}
|
2012-11-05 22:56:49 -05:00
|
|
|
for (i = 0; i < segments.length - 1; i++)
|
2012-11-05 23:06:54 -05:00
|
|
|
drawCurve(segments[i], segments[i + 1], false);
|
2012-11-05 23:03:03 -05:00
|
|
|
// We only need to draw the connecting curve if the path is cosed and
|
|
|
|
// has a stroke color, or if it's filled.
|
2012-11-05 23:06:54 -05:00
|
|
|
if (path._closed && path._style._strokeColor || path._style._fillColor)
|
|
|
|
drawCurve(segments[segments.length - 1], segments[0], true);
|
2012-11-05 23:03:03 -05:00
|
|
|
if (path._closed)
|
2012-11-05 22:56:49 -05:00
|
|
|
parts.push('z');
|
2012-11-05 23:50:31 -05:00
|
|
|
svg.setAttribute('d', parts.join(' '));
|
|
|
|
return svg;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Determines whether the object has been transformed or not through finding the angle
|
|
|
|
function determineIfTransformed(path, segments, type) {
|
|
|
|
var centerPoint = path.getPosition();
|
|
|
|
var topMidPath = centerPoint;
|
|
|
|
switch (type) {
|
|
|
|
case 'rect':
|
|
|
|
topMidPath = segments[1]._point.add(segments[2]._point).divide(2);
|
|
|
|
break;
|
|
|
|
case 'roundrect':
|
|
|
|
topMidPath = segments[3]._point.add(segments[4]._point).divide(2);
|
|
|
|
break;
|
|
|
|
case 'circle':
|
|
|
|
case 'ellipse':
|
|
|
|
topMidPath = segments[1]._point;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return topMidPath.subtract(centerPoint).getAngle();
|
2012-11-04 01:43:45 -05:00
|
|
|
}
|
2012-09-30 17:51:50 -04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Checks the type SVG object created by converting from Paper.js
|
|
|
|
*/
|
2012-11-05 22:44:23 -05:00
|
|
|
function determineType(path, segments) {
|
2012-11-02 21:22:01 -04:00
|
|
|
// See if actually have any curves in the path. Differentiate
|
|
|
|
// between straight objects (line, polyline, rect, and polygon) and
|
|
|
|
// objects with curves(circle, ellipse, roundedRectangle).
|
2012-11-04 02:04:15 -05:00
|
|
|
if (path.isPolygon()) {
|
2012-11-06 00:16:36 -05:00
|
|
|
// If the distance between (point0 and point1) and (point2 and
|
|
|
|
// point3) are equal, then it is a rectangle
|
|
|
|
return segments.length == 4 && Numerical.isZero(
|
|
|
|
getDistance(segments, 0, 1) - getDistance(segments, 3, 2))
|
|
|
|
? 'rect'
|
|
|
|
: segments.length >= 3
|
|
|
|
? path._closed ? 'polygon' : 'polyline'
|
|
|
|
: 'line';
|
2012-11-02 21:22:01 -04:00
|
|
|
} else {
|
2012-11-05 22:44:23 -05:00
|
|
|
if (segments.length == 8) {
|
2012-11-02 21:22:01 -04:00
|
|
|
// If the distance between (point0 and point3) and (point7 and
|
|
|
|
// point4) are equal then it is a roundedRectangle
|
2012-11-06 00:16:36 -05:00
|
|
|
if (Numerical.isZero(
|
|
|
|
getDistance(segments, 0, 3) - getDistance(segments, 7, 5)))
|
|
|
|
return 'roundrect';
|
2012-11-05 22:44:23 -05:00
|
|
|
} else if (segments.length == 4) {
|
2012-11-02 21:22:01 -04:00
|
|
|
// Check if the values of the point have values similar to
|
|
|
|
// circles and ellipses.
|
2012-09-30 17:51:50 -04:00
|
|
|
var checkPointValues = true;
|
2012-11-06 00:16:36 -05:00
|
|
|
for (var i = 0; i < segments.length && checkPointValues; i++) {
|
|
|
|
var handleIn = segments[i]._handleIn,
|
|
|
|
handleOut = segments[i]._handleOut;
|
|
|
|
checkPointValues = !handleIn.isZero()
|
|
|
|
&& Numerical.isZero(Math.abs(handleIn._x) - Math.abs(handleOut._x))
|
|
|
|
&& Numerical.isZero(Math.abs(handleIn._y) - Math.abs(handleOut._y));
|
2012-09-30 17:51:50 -04:00
|
|
|
}
|
2012-11-02 21:22:01 -04:00
|
|
|
if (checkPointValues) {
|
|
|
|
// If the distance between (point0 and point2) and (point1
|
|
|
|
// and point3) are equal, then it is a circle
|
2012-11-06 00:16:36 -05:00
|
|
|
return Numerical.isZero(getDistance(segments, 0, 2)
|
|
|
|
- getDistance(segments, 1, 3))
|
|
|
|
? 'circle'
|
|
|
|
: 'ellipse';
|
2012-09-30 17:51:50 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2012-11-04 01:43:45 -05:00
|
|
|
|
2012-11-04 12:01:11 -05:00
|
|
|
function applyStyle(item, svg) {
|
|
|
|
var attrs = {},
|
|
|
|
style = item._style,
|
2012-11-05 12:05:32 -05:00
|
|
|
parent = item.getParent(),
|
2012-11-05 21:58:16 -05:00
|
|
|
parentStyle = parent && parent._style;
|
2012-11-04 12:01:11 -05:00
|
|
|
|
|
|
|
if (item._name != null)
|
2012-11-05 22:26:54 -05:00
|
|
|
attrs.id = item._name;
|
2012-11-04 12:01:11 -05:00
|
|
|
|
2012-11-05 21:58:16 -05:00
|
|
|
Base.each(SvgStyles.properties, function(entry) {
|
2012-11-04 12:01:11 -05:00
|
|
|
// Get a given style only if it differs from the value on the parent
|
|
|
|
// (A layer or group which can have style values in SVG).
|
2012-11-05 22:03:42 -05:00
|
|
|
var value = style[entry.get]();
|
2012-11-05 12:05:32 -05:00
|
|
|
if (value != null && (!parentStyle
|
2012-11-05 22:03:42 -05:00
|
|
|
|| !Base.equals(parentStyle[entry.get](), value))) {
|
2012-11-05 21:58:16 -05:00
|
|
|
attrs[entry.attribute] = entry.type === 'color'
|
2012-11-04 12:01:11 -05:00
|
|
|
? value.toCssString()
|
2012-11-05 21:58:16 -05:00
|
|
|
: entry.type === 'array'
|
2012-11-04 12:01:11 -05:00
|
|
|
? value.join(',')
|
|
|
|
: value;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
if (item._opacity != null)
|
|
|
|
attrs.opacity = item._opacity;
|
|
|
|
|
|
|
|
if (item._visibility != null)
|
|
|
|
attrs._visibility = item._visibility ? 'visible' : 'hidden';
|
|
|
|
|
2012-11-05 23:50:31 -05:00
|
|
|
return setAttributes(svg, attrs);
|
2012-11-04 12:01:11 -05:00
|
|
|
}
|
|
|
|
|
2012-11-05 23:50:31 -05:00
|
|
|
var exporters = {
|
|
|
|
group: exportGroup,
|
|
|
|
layer: exportGroup,
|
|
|
|
path: exportPath,
|
|
|
|
pointtext: exportText
|
|
|
|
// TODO:
|
|
|
|
// raster:
|
|
|
|
// placedsymbol:
|
|
|
|
// compoundpath:
|
|
|
|
};
|
|
|
|
|
2012-11-04 01:43:45 -05:00
|
|
|
return /** @Lends SvgExporter */{
|
|
|
|
/**
|
|
|
|
* Takes the selected Paper.js project and parses all of its layers and
|
|
|
|
* groups to be placed into SVG groups, converting the project into one
|
|
|
|
* SVG group.
|
|
|
|
*
|
|
|
|
* @function
|
|
|
|
* @param {Project} project a Paper.js project
|
|
|
|
* @return {SVGSVGElement} the imported project converted to an SVG project
|
|
|
|
*/
|
|
|
|
// TODO: Implement symbols and Gradients
|
|
|
|
exportProject: function(project) {
|
|
|
|
var svg = createElement('svg'),
|
|
|
|
layers = project.layers;
|
|
|
|
for (var i = 0, l = layers.length; i < l; i++) {
|
|
|
|
svg.appendChild(this.exportItem(layers[i]));
|
|
|
|
}
|
|
|
|
return svg;
|
|
|
|
},
|
|
|
|
|
|
|
|
exportItem: function(item) {
|
|
|
|
var exporter = exporters[item._type];
|
|
|
|
// TODO: exporter == null: Not supported yet.
|
2012-11-05 23:50:31 -05:00
|
|
|
var svg = exporter && exporter(item, item._type);
|
|
|
|
return svg && applyStyle(item, svg);
|
2012-11-04 01:43:45 -05:00
|
|
|
}
|
|
|
|
};
|
2012-11-02 19:19:45 -04:00
|
|
|
};
|