Implement PathItem#pathData getter and setter for SVG style path data.

And use it fro SvgImport too.
This commit is contained in:
Jürg Lehni 2013-02-28 19:13:46 -08:00
parent 55cc668cc2
commit 78b3621cf6
5 changed files with 150 additions and 146 deletions

View file

@ -112,9 +112,10 @@ var CompoundPath = this.CompoundPath = PathItem.extend(/** @lends CompoundPath#
* @bean
*/
getCurves: function() {
var curves = [];
for (var i = 0, l = this._children.length; i < l; i++)
curves = curves.concat(this._children[i].getCurves());
var children = this._children,
curves = [];
for (var i = 0, l = children.length; i < l; i++)
curves = curves.concat(children[i].getCurves());
return curves;
},
@ -140,6 +141,14 @@ var CompoundPath = this.CompoundPath = PathItem.extend(/** @lends CompoundPath#
return last && last.getFirstCurve();
},
getPathData: function(/* precision */) {
var children = this._children,
paths = [];
for (var i = 0, l = children.length; i < l; i++)
paths.push(children[i].getPathData(arguments[0]));
return paths.join(' ');
},
/**
* A private method to help with both #contains() and #_hitTest().
*/

View file

@ -184,6 +184,53 @@ var Path = this.Path = PathItem.extend(/** @lends Path# */{
return curves[curves.length - 1];
},
/**
* The segments contained within the path, described as SVG style path data.
*
* @type String
* @bean
*/
getPathData: function(/* precision */) {
var segments = this._segments,
style = this._style,
format = Format.point,
precision = arguments[0],
parts = [];
// TODO: Add support for H/V and/or relative commands, where appropriate
// and resulting in shorter strings
function addCurve(seg1, seg2, skipLine) {
var point1 = seg1._point,
point2 = seg2._point,
handle1 = seg1._handleOut,
handle2 = seg2._handleIn;
if (handle1.isZero() && handle2.isZero()) {
if (!skipLine) {
// L = absolute lineto: moving to a point with drawing
parts.push('L' + format(point2));
}
} else {
// c = relative curveto: handle1, handle2 + end - start,
// end - start
var end = point2.subtract(point1);
parts.push('c' + format(handle1)
+ ' ' + format(end.add(handle2))
+ ' ' + format(end));
}
}
parts.push('M' + format(segments[0]._point));
for (i = 0, l = segments.length - 1; i < l; i++)
addCurve(segments[i], segments[i + 1], false);
// We only need to draw the connecting curve if it is not a line, and if
// the path is cosed and has a stroke color, or if it is filled.
if (this._closed && style._strokeColor || style._fillColor)
addCurve(segments[segments.length - 1], segments[0], true);
if (this._closed)
parts.push('z');
return parts.join('');
},
/**
* Specifies whether the path is closed. If it is closed, Paper.js connects
* the first and last segments.

View file

@ -49,7 +49,87 @@ var PathItem = this.PathItem = Item.extend(/** @lends PathItem# */{
Curve._addIntersections(values1, values2[j], curve, locations);
}
return locations;
},
setPathData: function(data) {
// This is a very compact SVG Path Data parser that works both for Path
// and CompoundPath.
var parts = data.match(/[a-z][^a-z]*/ig),
coords,
relative = false,
control,
current = new Point(); // the current position
function getCoord(index, coord, update) {
var val = parseFloat(coords[index]);
if (relative)
val += current[coord];
if (update)
current[coord] = val;
return val;
}
function getPoint(index, update) {
return new Point(
getCoord(index, 'x', update),
getCoord(index + 1, 'y', update)
);
}
for (var i = 0, l = parts.length; i < l; i++) {
var part = parts[i].trim();
cmd = part[0],
lower = cmd.toLowerCase();
// Split at white-space, commas but also before signs.
// Use positive ookahead to include signs.
coords = part.slice(1).split(/[\s,]+|(?=[+-])/);
relative = cmd === lower;
switch (lower) {
case 'm':
case 'l':
for (var j = 0; j < coords.length; j += 2)
this[j === 0 && lower === 'm' ? 'moveTo' : 'lineTo'](
getPoint(j, true));
break;
case 'h':
case 'v':
var coord = lower == 'h' ? 'x' : 'y';
for (var j = 0; j < coords.length; j++)
getCoord(j, coord, true);
this.lineTo(current);
break;
case 'c':
control = getPoint(2);
this.cubicCurveTo(getPoint(0), control, getPoint(4, true));
break;
case 's': // Shorthand cubic bezierCurveTo, absolute
// Calculate reflection of previous control points
var handle = current.multiply(2).subtract(control);
control = getPoint(0);
this.cubicCurveTo(handle, control, getPoint(2, true));
break;
case 'q':
control = getPoint(0);
this.quadraticCurveTo(control, getPoint(2, true));
break;
case 't':
for (var j = 0; j < coords.length; j += 2) {
// Calculate reflection of previous control points
control = current.multiply(2).subtract(control);
path.quadraticCurveTo(control, getPoint(j, true));
}
break;
case 'a':
// TODO: Implement Arcs!
break;
case 'z':
this.closePath();
break;
}
}
}
/**
* Smooth bezier curves without changing the amount of segments or their
* points, by only smoothing and adjusting their handle points, for both

View file

@ -84,42 +84,6 @@ new function() {
return attrs;
}
function getPath(path) {
var segments = path._segments,
style = path._style,
parts = [];
function addCurve(seg1, seg2, skipLine) {
var point1 = seg1._point,
point2 = seg2._point,
handle1 = seg1._handleOut,
handle2 = seg2._handleIn;
if (handle1.isZero() && handle2.isZero()) {
if (!skipLine) {
// L = lineto: moving to a point with drawing
parts.push('L' + formatPoint(point2));
}
} else {
// c = relative curveto: handle1, handle2 + end - start, end - start
var end = point2.subtract(point1);
parts.push('c' + formatPoint(handle1),
formatPoint(end.add(handle2)),
formatPoint(end));
}
}
parts.push('M' + formatPoint(segments[0]._point));
for (i = 0, l = segments.length - 1; i < l; i++)
addCurve(segments[i], segments[i + 1], false);
// We only need to draw the connecting curve if it is not a line, and if
// the path is cosed and has a stroke color, or if it is filled.
if (path._closed && style._strokeColor || style._fillColor)
addCurve(segments[segments.length - 1], segments[0], true);
if (path._closed)
parts.push('z');
return parts.join(' ');
}
function determineAngle(path, segments, type, center) {
// If the object is a circle, ellipse, rectangle, or rounded rectangle,
// see if it is placed at an angle, by figuring out its topCenter point
@ -253,7 +217,7 @@ new function() {
return null;
case 'path':
attrs = {
d: getPath(item)
d: item.getPathData()
};
break;
case 'polyline':
@ -342,12 +306,8 @@ new function() {
}
function exportCompoundPath(item) {
var attrs = getTransform(item, true),
children = item._children,
paths = [];
for (var i = 0, l = children.length; i < l; i++)
paths.push(children[i].getPathData());
attrs.d = paths.join(' ');
var attrs = getTransform(item, true);
attrs.d = item.getPathData();
return createElement('path', attrs);
}

View file

@ -121,106 +121,14 @@ new function() {
}
function importPath(node) {
var path = new Path(),
list = node.pathSegList,
compoundPath, lastPoint;
for (var i = 0, l = list.numberOfItems; i < l; i++) {
var segment = list.getItem(i),
segType = segment.pathSegType,
isRelative = segType % 2 == 1;
if (segType === /*#=*/ SVGPathSeg.PATHSEG_UNKNOWN)
continue;
if (!path.isEmpty())
lastPoint = path.getLastSegment().getPoint();
var relative = isRelative && !path.isEmpty()
? lastPoint
: Point.create(0, 0);
// Horizontal or vertical lineto commands, so fill in the
// missing x or y value:
var coord = (segType == /*#=*/ SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_ABS
|| segType == /*#=*/ SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_REL) && 'y'
|| (segType == /*#=*/ SVGPathSeg.PATHSEG_LINETO_VERTICAL_ABS
|| segType == /*#=*/ SVGPathSeg.PATHSEG_LINETO_VERTICAL_REL) && 'x';
if (coord)
segment[coord] = isRelative ? 0 : lastPoint[coord];
var point = Point.create(segment.x, segment.y).add(relative);
switch (segType) {
case /*#=*/ SVGPathSeg.PATHSEG_CLOSEPATH:
path.closePath();
break;
case /*#=*/ SVGPathSeg.PATHSEG_MOVETO_ABS:
case /*#=*/ SVGPathSeg.PATHSEG_MOVETO_REL:
if (!path.isEmpty() && !compoundPath) {
compoundPath = new CompoundPath([path]);
}
if (compoundPath) {
path = new Path();
compoundPath.addChild(path);
}
path.moveTo(point);
break;
case /*#=*/ SVGPathSeg.PATHSEG_LINETO_ABS:
case /*#=*/ SVGPathSeg.PATHSEG_LINETO_REL:
case /*#=*/ SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_ABS:
case /*#=*/ SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_REL:
case /*#=*/ SVGPathSeg.PATHSEG_LINETO_VERTICAL_ABS:
case /*#=*/ SVGPathSeg.PATHSEG_LINETO_VERTICAL_REL:
path.lineTo(point);
break;
case /*#=*/ SVGPathSeg.PATHSEG_CURVETO_CUBIC_ABS:
case /*#=*/ SVGPathSeg.PATHSEG_CURVETO_CUBIC_REL:
path.cubicCurveTo(
relative.add(segment.x1, segment.y1),
relative.add(segment.x2, segment.y2),
point
);
break;
case /*#=*/ SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_ABS:
case /*#=*/ SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_REL:
path.quadraticCurveTo(
relative.add(segment.x1, segment.y1),
point
);
break;
// TODO: Implement Arcs: ttp://www.w3.org/TR/SVG/implnote.html
// case /*#=*/ SVGPathSeg.PATHSEG_ARC_ABS:
// case /*#=*/ SVGPathSeg.PATHSEG_ARC_REL:
// break;
case /*#=*/ SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_ABS:
case /*#=*/ SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_REL:
var prev = list.getItem(i - 1),
control = lastPoint.add(lastPoint.subtract(
Point.create(prev.x2, prev.y2)
.subtract(prev.x, prev.y)
.add(lastPoint)));
path.cubicCurveTo(
control,
relative.add(segment.x2, segment.y2),
point);
break;
case /*#=*/ SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS:
case /*#=*/ SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL:
var control,
j = i;
for (; j >= 0; j--) {
var prev = list.getItem(j);
if (prev.pathSegType === /*#=*/ SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_ABS ||
prev.pathSegType === /*#=*/ SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_REL) {
control = Point.create(prev.x1, prev.y1)
.subtract(prev.x, prev.y)
.add(path._segments[j].getPoint());
break;
}
}
for (; j < i; ++j) {
var anchor = path._segments[j].getPoint();
control = anchor.add(anchor.subtract(control));
}
path.quadraticCurveTo(control, point);
break;
}
}
return compoundPath || path;
// 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;
}
function importGradient(node, type) {