mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2025-01-03 19:45:44 -05:00
Add options object to boolean operations and improve handling of open paths.
This closes #1036, closes #1072, closes #1089 and closes #1121
This commit is contained in:
parent
a29ada8f23
commit
e643338422
5 changed files with 182 additions and 99 deletions
|
@ -30,6 +30,9 @@ var CompoundPath = PathItem.extend(/** @lends CompoundPath# */{
|
||||||
_serializeFields: {
|
_serializeFields: {
|
||||||
children: []
|
children: []
|
||||||
},
|
},
|
||||||
|
// Enforce creation of beans, as bean getters have hidden parameters.
|
||||||
|
// See #getPathData() and #getArea below.
|
||||||
|
beans: true,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new compound path item and places it in the active layer.
|
* Creates a new compound path item and places it in the active layer.
|
||||||
|
@ -248,11 +251,11 @@ var CompoundPath = PathItem.extend(/** @lends CompoundPath# */{
|
||||||
* @bean
|
* @bean
|
||||||
* @type Number
|
* @type Number
|
||||||
*/
|
*/
|
||||||
getArea: function() {
|
getArea: function(_closed) {
|
||||||
var children = this._children,
|
var children = this._children,
|
||||||
area = 0;
|
area = 0;
|
||||||
for (var i = 0, l = children.length; i < l; i++)
|
for (var i = 0, l = children.length; i < l; i++)
|
||||||
area += children[i].getArea();
|
area += children[i].getArea(_closed);
|
||||||
return area;
|
return area;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -269,10 +272,7 @@ var CompoundPath = PathItem.extend(/** @lends CompoundPath# */{
|
||||||
for (var i = 0, l = children.length; i < l; i++)
|
for (var i = 0, l = children.length; i < l; i++)
|
||||||
length += children[i].getLength();
|
length += children[i].getLength();
|
||||||
return length;
|
return length;
|
||||||
}
|
},
|
||||||
}, /** @lends CompoundPath# */{
|
|
||||||
// Enforce bean creation for getPathData(), as it has hidden parameters.
|
|
||||||
beans: true,
|
|
||||||
|
|
||||||
getPathData: function(_matrix, _precision) {
|
getPathData: function(_matrix, _precision) {
|
||||||
// NOTE: #setPathData() is defined in PathItem.
|
// NOTE: #setPathData() is defined in PathItem.
|
||||||
|
@ -285,8 +285,8 @@ var CompoundPath = PathItem.extend(/** @lends CompoundPath# */{
|
||||||
? _matrix.appended(mx) : _matrix, _precision));
|
? _matrix.appended(mx) : _matrix, _precision));
|
||||||
}
|
}
|
||||||
return paths.join('');
|
return paths.join('');
|
||||||
}
|
},
|
||||||
}, /** @lends CompoundPath# */{
|
|
||||||
_hitTestChildren: function _hitTestChildren(point, options, viewMatrix) {
|
_hitTestChildren: function _hitTestChildren(point, options, viewMatrix) {
|
||||||
return _hitTestChildren.base.call(this, point,
|
return _hitTestChildren.base.call(this, point,
|
||||||
// If we're not specifically asked to returns paths through
|
// If we're not specifically asked to returns paths through
|
||||||
|
|
|
@ -295,8 +295,8 @@ var Path = PathItem.extend(/** @lends Path# */{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, /** @lends Path# */{
|
}, /** @lends Path# */{
|
||||||
// Enforce bean creation for getPathData() and getArea(), as they have
|
// Enforce creation of beans, as bean getters have hidden parameters.
|
||||||
// hidden parameters.
|
// See #getPathData() and #getArea below.
|
||||||
beans: true,
|
beans: true,
|
||||||
|
|
||||||
getPathData: function(_matrix, _precision) {
|
getPathData: function(_matrix, _precision) {
|
||||||
|
@ -829,24 +829,28 @@ var Path = PathItem.extend(/** @lends Path# */{
|
||||||
* @type Number
|
* @type Number
|
||||||
*/
|
*/
|
||||||
getArea: function(_closed) {
|
getArea: function(_closed) {
|
||||||
// If the call overrides the 'closed' state, do not cache the result.
|
// Cache the area for the open path, and the the final curve separately,
|
||||||
// This is used in tracePaths().
|
// so open and closed area can be returned at almost no additional cost.
|
||||||
var cached = _closed === undefined,
|
var closed = Base.pick(_closed, this._closed),
|
||||||
area = this._area;
|
cached = this._area;
|
||||||
if (!cached || area == null) {
|
if (cached == null) {
|
||||||
var segments = this._segments,
|
var segments = this._segments,
|
||||||
count = segments.length,
|
sum = 0,
|
||||||
closed = cached ? this._closed : _closed,
|
close = 0;
|
||||||
last = count - 1;
|
for (var i = 0, l = segments.length; i < l; i++) {
|
||||||
area = 0;
|
var next = i + 1,
|
||||||
for (var i = 0, l = closed ? count : last; i < l; i++) {
|
last = next >= l,
|
||||||
area += Curve.getArea(Curve.getValues(
|
area = Curve.getArea(Curve.getValues(
|
||||||
segments[i], segments[i < last ? i + 1 : 0]));
|
segments[i], segments[last ? 0 : i + 1]));
|
||||||
|
if (last) {
|
||||||
|
close = area;
|
||||||
|
} else {
|
||||||
|
sum += area;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (cached)
|
cached = this._area = [sum, close];
|
||||||
this._area = area;
|
|
||||||
}
|
}
|
||||||
return area;
|
return cached[0] + (closed ? cached[1] : 0);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -50,52 +50,52 @@ PathItem.inject(new function() {
|
||||||
* remove empty curves, #resolveCrossings() to resolve self-intersection
|
* remove empty curves, #resolveCrossings() to resolve self-intersection
|
||||||
* make sure all paths have correct winding direction.
|
* make sure all paths have correct winding direction.
|
||||||
*/
|
*/
|
||||||
function preparePath(path, closed) {
|
function preparePath(path, resolve) {
|
||||||
var res = path.clone(false).reduce({ simplify: true })
|
var res = path.clone(false).reduce({ simplify: true })
|
||||||
.transform(null, true, true);
|
.transform(null, true, true);
|
||||||
if (closed)
|
return resolve
|
||||||
res.setClosed(true);
|
|
||||||
return closed
|
|
||||||
? res.resolveCrossings().reorient(res.getFillRule() === 'nonzero')
|
? res.resolveCrossings().reorient(res.getFillRule() === 'nonzero')
|
||||||
: res;
|
: res;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createResult(ctor, paths, reduce, path1, path2) {
|
function createResult(ctor, paths, reduce, path1, path2, options) {
|
||||||
var result = new ctor(Item.NO_INSERT);
|
var result = new ctor(Item.NO_INSERT);
|
||||||
result.addChildren(paths, true);
|
result.addChildren(paths, true);
|
||||||
// See if the item can be reduced to just a simple Path.
|
// See if the item can be reduced to just a simple Path.
|
||||||
if (reduce)
|
if (reduce)
|
||||||
result = result.reduce({ simplify: true });
|
result = result.reduce({ simplify: true });
|
||||||
// Insert the resulting path above whichever of the two paths appear
|
if (!(options && options.insert === false)) {
|
||||||
// further up in the stack.
|
// Insert the resulting path above whichever of the two paths appear
|
||||||
result.insertAbove(path2 && path1.isSibling(path2)
|
// further up in the stack.
|
||||||
&& path1.getIndex() < path2.getIndex() ? path2 : path1);
|
result.insertAbove(path2 && path1.isSibling(path2)
|
||||||
|
&& path1.getIndex() < path2.getIndex() ? path2 : path1);
|
||||||
|
}
|
||||||
// Copy over the input path attributes, excluding matrix and we're done.
|
// Copy over the input path attributes, excluding matrix and we're done.
|
||||||
result.copyAttributes(path1, true);
|
result.copyAttributes(path1, true);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
function computeBoolean(path1, path2, operation) {
|
function computeBoolean(path1, path2, operation, options) {
|
||||||
// Retrieve the operator lookup table for winding numbers.
|
// Only support subtract and intersect operations when computing stroke
|
||||||
var operator = operators[operation];
|
// based boolean operations.
|
||||||
|
if (options && options.stroke &&
|
||||||
|
/^(subtract|intersect)$/.test(operation))
|
||||||
|
return computeStrokeBoolean(path1, path2, operation === 'subtract');
|
||||||
|
// We do not modify the operands themselves, but create copies instead,
|
||||||
|
// fas produced by the calls to preparePath().
|
||||||
|
// NOTE: The result paths might not belong to the same type i.e.
|
||||||
|
// subtract(A:Path, B:Path):CompoundPath etc.
|
||||||
|
var _path1 = preparePath(path1, true),
|
||||||
|
_path2 = path2 && path1 !== path2 && preparePath(path2, true),
|
||||||
|
// Retrieve the operator lookup table for winding numbers.
|
||||||
|
operator = operators[operation];
|
||||||
// Add a simple boolean property to check for a given operation,
|
// Add a simple boolean property to check for a given operation,
|
||||||
// e.g. `if (operator.unite)`
|
// e.g. `if (operator.unite)`
|
||||||
operator[operation] = true;
|
operator[operation] = true;
|
||||||
// If path1 is open, delegate to computeOpenBoolean().
|
|
||||||
// NOTE: Do not access private _closed property here, since path1 may
|
|
||||||
// be a CompoundPath.
|
|
||||||
if (!path1.isClosed())
|
|
||||||
return computeOpenBoolean(path1, path2, operator);
|
|
||||||
// We do not modify the operands themselves, but create copies instead,
|
|
||||||
// fas produced by the calls to preparePath().
|
|
||||||
// Note that the result paths might not belong to the same type
|
|
||||||
// i.e. subtraction(A:Path, B:Path):CompoundPath etc.
|
|
||||||
var _path1 = preparePath(path1, true),
|
|
||||||
_path2 = path2 && path1 !== path2 && preparePath(path2, true);
|
|
||||||
// Give both paths the same orientation except for subtraction
|
// Give both paths the same orientation except for subtraction
|
||||||
// and exclusion, where we need them at opposite orientation.
|
// and exclusion, where we need them at opposite orientation.
|
||||||
if (_path2 && (operator.subtract || operator.exclude)
|
if (_path2 && (operator.subtract || operator.exclude)
|
||||||
^ (_path2.isClockwise() ^ _path1.isClockwise()))
|
^ (_path2.isClockwise(true) ^ _path1.isClockwise(true)))
|
||||||
_path2.reverse();
|
_path2.reverse();
|
||||||
// Split curves at crossings on both paths. Note that for self-
|
// Split curves at crossings on both paths. Note that for self-
|
||||||
// intersection, path2 is null and getIntersections() handles it.
|
// intersection, path2 is null and getIntersections() handles it.
|
||||||
|
@ -183,26 +183,20 @@ PathItem.inject(new function() {
|
||||||
paths = tracePaths(segments, operator);
|
paths = tracePaths(segments, operator);
|
||||||
}
|
}
|
||||||
|
|
||||||
return createResult(CompoundPath, paths, true, path1, path2);
|
return createResult(CompoundPath, paths, true, path1, path2, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
function computeOpenBoolean(path1, path2, operator) {
|
function computeStrokeBoolean(path1, path2, subtract) {
|
||||||
// Only support subtract and intersect operations between an open
|
var _path1 = preparePath(path1),
|
||||||
// and a closed path.
|
_path2 = preparePath(path2),
|
||||||
if (!path2 || !operator.subtract && !operator.intersect) {
|
|
||||||
throw new Error('Boolean operations on open paths only support ' +
|
|
||||||
'subtraction and intersection with another path.');
|
|
||||||
}
|
|
||||||
var _path1 = preparePath(path1, false),
|
|
||||||
_path2 = preparePath(path2, false),
|
|
||||||
crossings = _path1.getCrossings(_path2),
|
crossings = _path1.getCrossings(_path2),
|
||||||
sub = operator.subtract,
|
|
||||||
paths = [];
|
paths = [];
|
||||||
|
|
||||||
function addPath(path) {
|
function addPath(path) {
|
||||||
// Simple see if the point halfway across the open path is inside
|
// Simple see if the point halfway across the open path is inside
|
||||||
// path2, and include / exclude the path based on the operator.
|
// path2, and include / exclude the path based on the operator.
|
||||||
if (_path2.contains(path.getPointAt(path.getLength() / 2)) ^ sub) {
|
if (_path2.contains(path.getPointAt(path.getLength() / 2))
|
||||||
|
^ subtract) {
|
||||||
paths.unshift(path);
|
paths.unshift(path);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -358,13 +352,17 @@ PathItem.inject(new function() {
|
||||||
* {@link CompoundPath#getCurves()}
|
* {@link CompoundPath#getCurves()}
|
||||||
* @param {Number} [dir=0] the direction in which to determine the
|
* @param {Number} [dir=0] the direction in which to determine the
|
||||||
* winding contribution, `0`: in x-direction, `1`: in y-direction
|
* winding contribution, `0`: in x-direction, `1`: in y-direction
|
||||||
|
* @param {Boolean} [closed=false] determines how areas should be closed
|
||||||
|
* when a curve is part of an open path, `false`: area is closed with a
|
||||||
|
* straight line, `true`: area is closed taking the handles of the first
|
||||||
|
* and last segment into account
|
||||||
* @param {Boolean} [dontFlip=false] controls whether the algorithm is
|
* @param {Boolean} [dontFlip=false] controls whether the algorithm is
|
||||||
* allowed to flip direction if it is deemed to produce better results
|
* allowed to flip direction if it is deemed to produce better results
|
||||||
* @return {Object} an object containing the calculated winding number, as
|
* @return {Object} an object containing the calculated winding number, as
|
||||||
* well as an indication whether the point was situated on the contour
|
* well as an indication whether the point was situated on the contour
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
function getWinding(point, curves, dir, dontFlip) {
|
function getWinding(point, curves, dir, closed, dontFlip) {
|
||||||
var epsilon = /*#=*/Numerical.WINDING_EPSILON,
|
var epsilon = /*#=*/Numerical.WINDING_EPSILON,
|
||||||
// Determine the index of the abscissa and ordinate values in the
|
// Determine the index of the abscissa and ordinate values in the
|
||||||
// curve values arrays, based on the direction:
|
// curve values arrays, based on the direction:
|
||||||
|
@ -465,7 +463,7 @@ PathItem.inject(new function() {
|
||||||
// again with flipped direction and return that result instead.
|
// again with flipped direction and return that result instead.
|
||||||
return !dontFlip && a > paL && a < paR
|
return !dontFlip && a > paL && a < paR
|
||||||
&& Curve.getTangent(v, t)[dir ? 'x' : 'y'] === 0
|
&& Curve.getTangent(v, t)[dir ? 'x' : 'y'] === 0
|
||||||
&& getWinding(point, curves, dir ? 0 : 1, true);
|
&& getWinding(point, curves, dir ? 0 : 1, closed, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCurve(v) {
|
function handleCurve(v) {
|
||||||
|
@ -505,14 +503,22 @@ PathItem.inject(new function() {
|
||||||
// We're on a new (sub-)path, so we need to determine values of
|
// We're on a new (sub-)path, so we need to determine values of
|
||||||
// the last non-horizontal curve on this path.
|
// the last non-horizontal curve on this path.
|
||||||
vPrev = null;
|
vPrev = null;
|
||||||
// If the path is not closed, connect the end points with a
|
// If the path is not closed, connect the first and last segment
|
||||||
// straight curve, just like how filling open paths works.
|
// based on the value of `closed`:
|
||||||
|
// - `false`: Connect with a straight curve, just like how
|
||||||
|
// filling open paths works.
|
||||||
|
// - `true`: Connect with a curve that takes the segment handles
|
||||||
|
// into account, just like how closed paths behave.
|
||||||
if (!path._closed) {
|
if (!path._closed) {
|
||||||
var p1 = path.getLastCurve().getPoint2(),
|
var s1 = path.getLastCurve().getSegment2(),
|
||||||
p2 = curve.getPoint1(),
|
s2 = curve.getSegment1(),
|
||||||
|
p1 = s1._point,
|
||||||
|
p2 = s2._point,
|
||||||
x1 = p1._x, y1 = p1._y,
|
x1 = p1._x, y1 = p1._y,
|
||||||
x2 = p2._x, y2 = p2._y;
|
x2 = p2._x, y2 = p2._y;
|
||||||
vClose = [x1, y1, x1, y1, x2, y2, x2, y2];
|
vClose = closed
|
||||||
|
? Curve.getValues(s1, s2)
|
||||||
|
: [x1, y1, x1, y1, x2, y2, x2, y2];
|
||||||
// This closing curve is a potential candidate for the last
|
// This closing curve is a potential candidate for the last
|
||||||
// non-horizontal curve.
|
// non-horizontal curve.
|
||||||
if (vClose[io] !== vClose[io + 6]) {
|
if (vClose[io] !== vClose[io + 6]) {
|
||||||
|
@ -555,7 +561,7 @@ PathItem.inject(new function() {
|
||||||
// for clockwise and [-1,+1] for counter-clockwise paths.
|
// for clockwise and [-1,+1] for counter-clockwise paths.
|
||||||
// If the ray is cast in y direction (dir == 1), the
|
// If the ray is cast in y direction (dir == 1), the
|
||||||
// windings always have opposite sign.
|
// windings always have opposite sign.
|
||||||
var add = path.isClockwise() ^ dir ? 1 : -1;
|
var add = path.isClockwise(closed) ^ dir ? 1 : -1;
|
||||||
windingL += add;
|
windingL += add;
|
||||||
windingR -= add;
|
windingR -= add;
|
||||||
onPathWinding += add;
|
onPathWinding += add;
|
||||||
|
@ -626,10 +632,12 @@ PathItem.inject(new function() {
|
||||||
// contributing to the second operand and is outside the
|
// contributing to the second operand and is outside the
|
||||||
// first operand.
|
// first operand.
|
||||||
winding = !(operator.subtract && path2 && (
|
winding = !(operator.subtract && path2 && (
|
||||||
path === path1 && path2._getWinding(pt, dir).winding ||
|
path === path1 &&
|
||||||
path === path2 && !path1._getWinding(pt, dir).winding))
|
path2._getWinding(pt, dir, true).winding ||
|
||||||
? getWinding(pt, curves, dir)
|
path === path2 &&
|
||||||
: { winding: 0 };
|
!path1._getWinding(pt, dir, true).winding))
|
||||||
|
? getWinding(pt, curves, dir, true)
|
||||||
|
: { winding: 0 };
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
length -= curveLength;
|
length -= curveLength;
|
||||||
|
@ -734,6 +742,7 @@ PathItem.inject(new function() {
|
||||||
for (var i = 0, l = segments.length; i < l; i++) {
|
for (var i = 0, l = segments.length; i < l; i++) {
|
||||||
var path = null,
|
var path = null,
|
||||||
finished = false,
|
finished = false,
|
||||||
|
closed = true,
|
||||||
seg = segments[i],
|
seg = segments[i],
|
||||||
inter = seg._intersection,
|
inter = seg._intersection,
|
||||||
handleIn;
|
handleIn;
|
||||||
|
@ -793,18 +802,21 @@ PathItem.inject(new function() {
|
||||||
seg = other;
|
seg = other;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Bail out if we're done, or if we encounter an already visited
|
if (finished) {
|
||||||
// next segment.
|
|
||||||
if (finished || seg._visited) {
|
|
||||||
// It doesn't hurt to set again to share some code.
|
|
||||||
seg._visited = true;
|
seg._visited = true;
|
||||||
|
// If we end up on the first or last segment of an operand,
|
||||||
|
// copy over its closed state, to support mixed open/closed
|
||||||
|
// scenarios as described in #1036
|
||||||
|
if (seg.isFirst() || seg.isLast())
|
||||||
|
closed = seg._path._closed;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// If there are only valid overlaps and we encounter and invalid
|
// If a visited or invalid segment is encountered, bail out
|
||||||
// segment, bail out immediately. Otherwise we need to be more
|
// immediately. But if there aren't only valid overlaps, be more
|
||||||
// tolerant due to complex situations of crossing,
|
// tolerant due to complex crossing situations.
|
||||||
// see findBestIntersection()
|
// See findBestIntersection()
|
||||||
if (seg._path._validOverlapsOnly && !isValid(seg))
|
if (seg._visited
|
||||||
|
|| seg._path._validOverlapsOnly && !isValid(seg))
|
||||||
break;
|
break;
|
||||||
if (!path) {
|
if (!path) {
|
||||||
path = new Path(Item.NO_INSERT);
|
path = new Path(Item.NO_INSERT);
|
||||||
|
@ -830,7 +842,7 @@ PathItem.inject(new function() {
|
||||||
// Finish with closing the paths, and carrying over the last
|
// Finish with closing the paths, and carrying over the last
|
||||||
// handleIn to the first segment.
|
// handleIn to the first segment.
|
||||||
path.firstSegment.setHandleIn(handleIn);
|
path.firstSegment.setHandleIn(handleIn);
|
||||||
path.setClosed(true);
|
path.setClosed(closed);
|
||||||
} else if (path) {
|
} else if (path) {
|
||||||
// Only complain about open paths if they would actually contain
|
// Only complain about open paths if they would actually contain
|
||||||
// an area when closed. Open paths that can silently discarded
|
// an area when closed. Open paths that can silently discarded
|
||||||
|
@ -873,39 +885,58 @@ PathItem.inject(new function() {
|
||||||
* winding contribution, `0`: in x-direction, `1`: in y-direction
|
* winding contribution, `0`: in x-direction, `1`: in y-direction
|
||||||
* @return {Number} the winding number
|
* @return {Number} the winding number
|
||||||
*/
|
*/
|
||||||
_getWinding: function(point, dir) {
|
_getWinding: function(point, dir, closed) {
|
||||||
return getWinding(point, this.getCurves(), dir);
|
return getWinding(point, this.getCurves(), dir, closed);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@grouptitle Boolean Path Operations}
|
* {@grouptitle Boolean Path Operations}
|
||||||
*
|
*
|
||||||
* Merges the geometry of the specified path with this path's geometry
|
* Unites the geometry of the specified path with this path's geometry
|
||||||
* and returns the result as a new path item.
|
* and returns the result as a new path item.
|
||||||
*
|
*
|
||||||
|
* @option [options.insert=true] {Boolean} whether the resulting item
|
||||||
|
* should be inserted back into the scene graph, above both paths
|
||||||
|
* involved in the operation
|
||||||
|
*
|
||||||
* @param {PathItem} path the path to unite with
|
* @param {PathItem} path the path to unite with
|
||||||
|
* @param {Object} [options] the boolean operation options
|
||||||
* @return {PathItem} the resulting path item
|
* @return {PathItem} the resulting path item
|
||||||
*/
|
*/
|
||||||
unite: function(path) {
|
unite: function(path, options) {
|
||||||
return computeBoolean(this, path, 'unite');
|
return computeBoolean(this, path, 'unite', options);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Intersects the geometry of the specified path with this path's
|
* Intersects the geometry of the specified path with this path's
|
||||||
* geometry and returns the result as a new path item.
|
* geometry and returns the result as a new path item.
|
||||||
*
|
*
|
||||||
|
* @option [options.insert=true] {Boolean} whether the resulting item
|
||||||
|
* should be inserted back into the scene graph, above both paths
|
||||||
|
* involved in the operation
|
||||||
|
* @option [options.stroke=false] {Boolean} whether the operation should
|
||||||
|
* be performed on the stroke or on the fill of the first path
|
||||||
|
*
|
||||||
* @param {PathItem} path the path to intersect with
|
* @param {PathItem} path the path to intersect with
|
||||||
|
* @param {Object} [options] the boolean operation options
|
||||||
* @return {PathItem} the resulting path item
|
* @return {PathItem} the resulting path item
|
||||||
*/
|
*/
|
||||||
intersect: function(path) {
|
intersect: function(path, options) {
|
||||||
return computeBoolean(this, path, 'intersect');
|
return computeBoolean(this, path, 'intersect', options);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subtracts the geometry of the specified path from this path's
|
* Subtracts the geometry of the specified path from this path's
|
||||||
* geometry and returns the result as a new path item.
|
* geometry and returns the result as a new path item.
|
||||||
*
|
*
|
||||||
|
* @option [options.insert=true] {Boolean} whether the resulting item
|
||||||
|
* should be inserted back into the scene graph, above both paths
|
||||||
|
* involved in the operation
|
||||||
|
* @option [options.stroke=false] {Boolean} whether the operation should
|
||||||
|
* be performed on the stroke or on the fill of the first path
|
||||||
|
*
|
||||||
* @param {PathItem} path the path to subtract
|
* @param {PathItem} path the path to subtract
|
||||||
|
* @param {Object} [options] the boolean operation options
|
||||||
* @return {PathItem} the resulting path item
|
* @return {PathItem} the resulting path item
|
||||||
*/
|
*/
|
||||||
subtract: function(path) {
|
subtract: function(path) {
|
||||||
|
@ -916,11 +947,16 @@ PathItem.inject(new function() {
|
||||||
* Excludes the intersection of the geometry of the specified path with
|
* Excludes the intersection of the geometry of the specified path with
|
||||||
* this path's geometry and returns the result as a new path item.
|
* this path's geometry and returns the result as a new path item.
|
||||||
*
|
*
|
||||||
|
* @option [options.insert=true] {Boolean} whether the resulting item
|
||||||
|
* should be inserted back into the scene graph, above both paths
|
||||||
|
* involved in the operation
|
||||||
|
*
|
||||||
* @param {PathItem} path the path to exclude the intersection of
|
* @param {PathItem} path the path to exclude the intersection of
|
||||||
|
* @param {Object} [options] the boolean operation options
|
||||||
* @return {PathItem} the resulting group item
|
* @return {PathItem} the resulting group item
|
||||||
*/
|
*/
|
||||||
exclude: function(path) {
|
exclude: function(path, options) {
|
||||||
return computeBoolean(this, path, 'exclude');
|
return computeBoolean(this, path, 'exclude', options);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -929,12 +965,21 @@ PathItem.inject(new function() {
|
||||||
* calling {@link #subtract(path)} and {@link #subtract(path)} and
|
* calling {@link #subtract(path)} and {@link #subtract(path)} and
|
||||||
* putting the results into a new group.
|
* putting the results into a new group.
|
||||||
*
|
*
|
||||||
|
* @option [options.insert=true] {Boolean} whether the resulting item
|
||||||
|
* should be inserted back into the scene graph, above both paths
|
||||||
|
* involved in the operation
|
||||||
|
* @option [options.stroke=false] {Boolean} whether the operation should
|
||||||
|
* be performed on the stroke or on the fill of the first path
|
||||||
|
*
|
||||||
* @param {PathItem} path the path to divide by
|
* @param {PathItem} path the path to divide by
|
||||||
|
* @param {Object} [options] the boolean operation options
|
||||||
* @return {Group} the resulting group item
|
* @return {Group} the resulting group item
|
||||||
*/
|
*/
|
||||||
divide: function(path) {
|
divide: function(path, options) {
|
||||||
return createResult(Group, [this.subtract(path),
|
return createResult(Group, [
|
||||||
this.intersect(path)], true, this, path);
|
this.subtract(path, options),
|
||||||
|
this.intersect(path, options)
|
||||||
|
], true, this, path, options);
|
||||||
},
|
},
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -23,6 +23,9 @@ var PathItem = Item.extend(/** @lends PathItem# */{
|
||||||
_class: 'PathItem',
|
_class: 'PathItem',
|
||||||
_selectBounds: false,
|
_selectBounds: false,
|
||||||
_canScaleStroke: true,
|
_canScaleStroke: true,
|
||||||
|
// Enforce creation of beans, as bean getters have hidden parameters.
|
||||||
|
// See #isClockwise() below.
|
||||||
|
beans: true,
|
||||||
|
|
||||||
initialize: function PathItem() {
|
initialize: function PathItem() {
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
|
@ -101,8 +104,8 @@ var PathItem = Item.extend(/** @lends PathItem# */{
|
||||||
* @see Path#getArea()
|
* @see Path#getArea()
|
||||||
* @see CompoundPath#getArea()
|
* @see CompoundPath#getArea()
|
||||||
*/
|
*/
|
||||||
isClockwise: function() {
|
isClockwise: function(_closed) {
|
||||||
return this.getArea() >= 0;
|
return this.getArea(_closed) >= 0;
|
||||||
},
|
},
|
||||||
|
|
||||||
setClockwise: function(clockwise) {
|
setClockwise: function(clockwise) {
|
||||||
|
|
|
@ -99,7 +99,7 @@ test('#719', function() {
|
||||||
compareBoolean(result, expected);
|
compareBoolean(result, expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('#757 (support for open paths)', function() {
|
test('#757 (path1.intersect(pat2, { stroke: true }))', function() {
|
||||||
var rect = new Path.Rectangle({
|
var rect = new Path.Rectangle({
|
||||||
from: [100, 250],
|
from: [100, 250],
|
||||||
to: [350, 350]
|
to: [350, 350]
|
||||||
|
@ -116,7 +116,7 @@ test('#757 (support for open paths)', function() {
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
var res = line.intersect(rect);
|
var res = line.intersect(rect, { stroke: true });
|
||||||
|
|
||||||
var children = res.removeChildren();
|
var children = res.removeChildren();
|
||||||
var first = children[0];
|
var first = children[0];
|
||||||
|
@ -540,6 +540,37 @@ test('#973', function() {
|
||||||
'children orientation after calling path.resolveCrossings()');
|
'children orientation after calling path.resolveCrossings()');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('#1036', function() {
|
||||||
|
var line1 = new Path([
|
||||||
|
[[305.10732,101.34786],[0,0],[62.9214,0]],
|
||||||
|
[[499.20274,169.42611],[-29.38716,-68.57004],[5.78922,13.50818]],
|
||||||
|
[[497.75426,221.57115],[2.90601,-13.5614],[-9.75434,45.52027]],
|
||||||
|
[[416.63976,331.65512],[31.9259,-30.40562],[-21.77284,20.73604]],
|
||||||
|
[[350.00999,391.04252],[23.92578,-18.22917],[-23.33885,17.78198]],
|
||||||
|
[[277.58633,431.59977],[27.45996,-10.67887],[-1.72805,0.67202]],
|
||||||
|
[[251.51381,437.39367],[0,5.7145],[0,0]]
|
||||||
|
]);
|
||||||
|
|
||||||
|
var line2 = new Path([
|
||||||
|
[[547.00236,88.31161],[0,0],[-1.36563,0]],
|
||||||
|
[[544.10541,85.41466],[1.29555,0.43185],[-9.83725,-3.27908]],
|
||||||
|
[[509.34205,82.51771],[10.32634,-1.29079],[-10.20055,1.27507]],
|
||||||
|
[[444.16075,97.00245],[7.10741,-4.06138],[-4.93514,2.82008]],
|
||||||
|
[[431.12449,105.69328],[4.27047,-2.13524],[-14.94798,7.47399]],
|
||||||
|
[[407.94892,175.22],[1.27008,-13.33587],[-3.16966,33.28138]],
|
||||||
|
[[399.25808,279.51008],[-5.61644,-33.69865],[1.73417,10.40499]],
|
||||||
|
[[415.19129,307.03107],[-5.98792,-8.16534],[2.74694,3.74583]],
|
||||||
|
[[432.57297,328.75817],[-3.89061,-3.29206],[2.9716,2.51443]],
|
||||||
|
[[442.71228,334.55206],[-3.01275,-2.46498],[2.39275,1.95771]],
|
||||||
|
[[448.50617,341.79443],[-2.37502,-1.97918],[39.75954,33.13295]],
|
||||||
|
[[578.86877,378.00626],[-51.65429,10.33086],[11.28627,-2.25725]],
|
||||||
|
[[612.18365,366.41848],[-10.6547,4.26188],[3.10697,-1.24279]],
|
||||||
|
[[617.97755,362.07306],[-3.19904,0],[0,0]]
|
||||||
|
]);
|
||||||
|
compareBoolean(function() { return line1.intersect(line2); },
|
||||||
|
'M424.54226,112.15158c32.89387,9.15202 61.28089,26.0555 74.66048,57.27453c5.78922,13.50818 1.45753,38.58364 -1.44848,52.14504c-8.75233,40.8442 -41.40003,72.54068 -71.07836,100.58668c-4.48065,-5.55963 -9.68976,-12.67924 -11.48461,-15.12676c-5.98792,-8.16534 -14.19904,-17.116 -15.93321,-27.52099c-5.61644,-33.69865 5.52118,-71.0087 8.69084,-104.29008c1.06648,-11.19805 6.14308,-47.34273 16.59334,-63.06842z');
|
||||||
|
});
|
||||||
|
|
||||||
test('#1054', function() {
|
test('#1054', function() {
|
||||||
var p1 = new Path({
|
var p1 = new Path({
|
||||||
segments: [
|
segments: [
|
||||||
|
|
Loading…
Reference in a new issue