mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2025-01-07 13:22:07 -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
|
* @bean
|
||||||
*/
|
*/
|
||||||
getCurves: function() {
|
getCurves: function() {
|
||||||
var curves = [];
|
var children = this._children,
|
||||||
for (var i = 0, l = this._children.length; i < l; i++)
|
curves = [];
|
||||||
curves = curves.concat(this._children[i].getCurves());
|
for (var i = 0, l = children.length; i < l; i++)
|
||||||
|
curves = curves.concat(children[i].getCurves());
|
||||||
return curves;
|
return curves;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -140,6 +141,14 @@ var CompoundPath = this.CompoundPath = PathItem.extend(/** @lends CompoundPath#
|
||||||
return last && last.getFirstCurve();
|
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().
|
* 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];
|
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
|
* Specifies whether the path is closed. If it is closed, Paper.js connects
|
||||||
* the first and last segments.
|
* the first and last segments.
|
||||||
|
|
|
@ -49,7 +49,87 @@ var PathItem = this.PathItem = Item.extend(/** @lends PathItem# */{
|
||||||
Curve._addIntersections(values1, values2[j], curve, locations);
|
Curve._addIntersections(values1, values2[j], curve, locations);
|
||||||
}
|
}
|
||||||
return 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
|
* Smooth bezier curves without changing the amount of segments or their
|
||||||
* points, by only smoothing and adjusting their handle points, for both
|
* points, by only smoothing and adjusting their handle points, for both
|
||||||
|
|
|
@ -84,42 +84,6 @@ new function() {
|
||||||
return attrs;
|
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) {
|
function determineAngle(path, segments, type, center) {
|
||||||
// If the object is a circle, ellipse, rectangle, or rounded rectangle,
|
// 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
|
// see if it is placed at an angle, by figuring out its topCenter point
|
||||||
|
@ -253,7 +217,7 @@ new function() {
|
||||||
return null;
|
return null;
|
||||||
case 'path':
|
case 'path':
|
||||||
attrs = {
|
attrs = {
|
||||||
d: getPath(item)
|
d: item.getPathData()
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
case 'polyline':
|
case 'polyline':
|
||||||
|
@ -342,12 +306,8 @@ new function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function exportCompoundPath(item) {
|
function exportCompoundPath(item) {
|
||||||
var attrs = getTransform(item, true),
|
var attrs = getTransform(item, true);
|
||||||
children = item._children,
|
attrs.d = item.getPathData();
|
||||||
paths = [];
|
|
||||||
for (var i = 0, l = children.length; i < l; i++)
|
|
||||||
paths.push(children[i].getPathData());
|
|
||||||
attrs.d = paths.join(' ');
|
|
||||||
return createElement('path', attrs);
|
return createElement('path', attrs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -121,106 +121,14 @@ new function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function importPath(node) {
|
function importPath(node) {
|
||||||
var path = new Path(),
|
// Get the path data, and determine wether it is a compound path or a
|
||||||
list = node.pathSegList,
|
// normal path based on the amount of moveTo commands inside it.
|
||||||
compoundPath, lastPoint;
|
var data = node.getAttribute('d'),
|
||||||
for (var i = 0, l = list.numberOfItems; i < l; i++) {
|
path = data.match(/m/gi).length > 1
|
||||||
var segment = list.getItem(i),
|
? new CompoundPath()
|
||||||
segType = segment.pathSegType,
|
: new Path();
|
||||||
isRelative = segType % 2 == 1;
|
path.setPathData(data);
|
||||||
if (segType === /*#=*/ SVGPathSeg.PATHSEG_UNKNOWN)
|
return path;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function importGradient(node, type) {
|
function importGradient(node, type) {
|
||||||
|
|
Loading…
Reference in a new issue