mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2025-01-05 20:32:00 -05:00
Implement PathItem#pathData getter and setter for SVG style path data.
And use it fro SvgImport too.
This commit is contained in:
parent
55cc668cc2
commit
78b3621cf6
5 changed files with 150 additions and 146 deletions
|
@ -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().
|
||||
*/
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in a new issue