mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2025-01-05 20:32:00 -05:00
Fix issue with Path#strokeBounds with #strokeJoin == 'miter' and clockwise orientation.
Closes #206.
This commit is contained in:
parent
77292892ca
commit
c70b9e7d7d
2 changed files with 72 additions and 40 deletions
102
src/path/Path.js
102
src/path/Path.js
|
@ -1079,33 +1079,7 @@ var Path = this.Path = PathItem.extend(/** @lends Path# */{
|
||||||
isClockwise: function() {
|
isClockwise: function() {
|
||||||
if (this._clockwise !== undefined)
|
if (this._clockwise !== undefined)
|
||||||
return this._clockwise;
|
return this._clockwise;
|
||||||
var sum = 0,
|
return Path.isClockwise(this._segments);
|
||||||
xPre, yPre;
|
|
||||||
function edge(x, y) {
|
|
||||||
if (xPre !== undefined)
|
|
||||||
sum += (xPre - x) * (y + yPre);
|
|
||||||
xPre = x;
|
|
||||||
yPre = y;
|
|
||||||
}
|
|
||||||
// Method derived from:
|
|
||||||
// http://stackoverflow.com/questions/1165647
|
|
||||||
// We treat the curve points and handles as the outline of a polygon of
|
|
||||||
// which we determine the orientation using the method of calculating
|
|
||||||
// the sum over the edges. This will work even with non-convex polygons,
|
|
||||||
// telling you whether it's mostly clockwise
|
|
||||||
for (var i = 0, l = this._segments.length; i < l; i++) {
|
|
||||||
var seg1 = this._segments[i],
|
|
||||||
seg2 = this._segments[i + 1 < l ? i + 1 : 0],
|
|
||||||
point1 = seg1._point,
|
|
||||||
handle1 = seg1._handleOut,
|
|
||||||
handle2 = seg2._handleIn,
|
|
||||||
point2 = seg2._point;
|
|
||||||
edge(point1._x, point1._y);
|
|
||||||
edge(point1._x + handle1._x, point1._y + handle1._y);
|
|
||||||
edge(point2._x + handle2._x, point2._y + handle2._y);
|
|
||||||
edge(point2._x, point2._y);
|
|
||||||
}
|
|
||||||
return sum > 0;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
setClockwise: function(clockwise) {
|
setClockwise: function(clockwise) {
|
||||||
|
@ -2213,8 +2187,47 @@ var Path = this.Path = PathItem.extend(/** @lends Path# */{
|
||||||
|
|
||||||
// Mess with indentation in order to get more line-space below...
|
// Mess with indentation in order to get more line-space below...
|
||||||
statics: {
|
statics: {
|
||||||
|
/**
|
||||||
|
* Determines wether the segments describe a path in clockwise or counter-
|
||||||
|
* clockwise orientation.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
isClockwise: function(segments) {
|
||||||
|
var sum = 0,
|
||||||
|
xPre, yPre;
|
||||||
|
function edge(x, y) {
|
||||||
|
if (xPre !== undefined)
|
||||||
|
sum += (xPre - x) * (y + yPre);
|
||||||
|
xPre = x;
|
||||||
|
yPre = y;
|
||||||
|
}
|
||||||
|
// Method derived from:
|
||||||
|
// http://stackoverflow.com/questions/1165647
|
||||||
|
// We treat the curve points and handles as the outline of a polygon of
|
||||||
|
// which we determine the orientation using the method of calculating
|
||||||
|
// the sum over the edges. This will work even with non-convex polygons,
|
||||||
|
// telling you whether it's mostly clockwise
|
||||||
|
// TODO: Check if this works correctly for all open paths.
|
||||||
|
for (var i = 0, l = segments.length; i < l; i++) {
|
||||||
|
var seg1 = segments[i],
|
||||||
|
seg2 = segments[i + 1 < l ? i + 1 : 0],
|
||||||
|
point1 = seg1._point,
|
||||||
|
handle1 = seg1._handleOut,
|
||||||
|
handle2 = seg2._handleIn,
|
||||||
|
point2 = seg2._point;
|
||||||
|
edge(point1._x, point1._y);
|
||||||
|
edge(point1._x + handle1._x, point1._y + handle1._y);
|
||||||
|
edge(point2._x + handle2._x, point2._y + handle2._y);
|
||||||
|
edge(point2._x, point2._y);
|
||||||
|
}
|
||||||
|
return sum > 0;
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the bounding rectangle of the item excluding stroke width.
|
* Returns the bounding rectangle of the item excluding stroke width.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
*/
|
*/
|
||||||
getBounds: function(segments, closed, style, matrix, strokePadding) {
|
getBounds: function(segments, closed, style, matrix, strokePadding) {
|
||||||
var first = segments[0];
|
var first = segments[0];
|
||||||
|
@ -2254,6 +2267,8 @@ statics: {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the bounding rectangle of the item including stroke width.
|
* Returns the bounding rectangle of the item including stroke width.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
*/
|
*/
|
||||||
getStrokeBounds: function(segments, closed, style, matrix) {
|
getStrokeBounds: function(segments, closed, style, matrix) {
|
||||||
/**
|
/**
|
||||||
|
@ -2302,14 +2317,22 @@ statics: {
|
||||||
// TODO: Find a way to reuse 'bounds' cache instead?
|
// TODO: Find a way to reuse 'bounds' cache instead?
|
||||||
if (!style.getStrokeColor() || !style.getStrokeWidth())
|
if (!style.getStrokeColor() || !style.getStrokeWidth())
|
||||||
return Path.getBounds(segments, closed, style, matrix);
|
return Path.getBounds(segments, closed, style, matrix);
|
||||||
var radius = style.getStrokeWidth() / 2,
|
var length = segments.length - (closed ? 0 : 1),
|
||||||
|
radius = style.getStrokeWidth() / 2,
|
||||||
padding = getPenPadding(radius, matrix),
|
padding = getPenPadding(radius, matrix),
|
||||||
bounds = Path.getBounds(segments, closed, style, matrix, padding),
|
bounds = Path.getBounds(segments, closed, style, matrix, padding),
|
||||||
join = style.getStrokeJoin(),
|
join = style.getStrokeJoin(),
|
||||||
cap = style.getStrokeCap(),
|
cap = style.getStrokeCap(),
|
||||||
|
miterLimit,
|
||||||
|
miterRadius;
|
||||||
|
if (join == 'miter' && length > 1) {
|
||||||
// miter is relative to stroke width. Divide it by 2 since we're
|
// miter is relative to stroke width. Divide it by 2 since we're
|
||||||
// measuring half the distance below
|
// measuring half the distance below
|
||||||
miter = style.getMiterLimit() * style.getStrokeWidth() / 2;
|
miterLimit = style.getMiterLimit() * style.getStrokeWidth() / 2;
|
||||||
|
// Depending on the path's orientation, mitters are created towards
|
||||||
|
// one side or the other
|
||||||
|
miterRadius = radius * (Path.isClockwise(segments) ? 1 : -1);
|
||||||
|
}
|
||||||
// Create a rectangle of padding size, used for union with bounds
|
// Create a rectangle of padding size, used for union with bounds
|
||||||
// further down
|
// further down
|
||||||
var joinBounds = new Rectangle(new Size(padding).multiply(2));
|
var joinBounds = new Rectangle(new Size(padding).multiply(2));
|
||||||
|
@ -2333,25 +2356,26 @@ statics: {
|
||||||
&& !segment._handleOut.isZero()) {
|
&& !segment._handleOut.isZero()) {
|
||||||
bounds = bounds.unite(joinBounds.setCenter(matrix
|
bounds = bounds.unite(joinBounds.setCenter(matrix
|
||||||
? matrix._transformPoint(segment._point) : segment._point));
|
? matrix._transformPoint(segment._point) : segment._point));
|
||||||
} else if (join == 'bevel') {
|
} else if (join === 'bevel') {
|
||||||
var curve = segment.getCurve();
|
var curve = segment.getCurve();
|
||||||
addBevelJoin(curve, 0);
|
addBevelJoin(curve, 0);
|
||||||
addBevelJoin(curve.getPrevious(), 1);
|
addBevelJoin(curve.getPrevious(), 1);
|
||||||
} else if (join == 'miter') {
|
} else if (join === 'miter') {
|
||||||
var curve2 = segment.getCurve(),
|
var curve2 = segment.getCurve(),
|
||||||
curve1 = curve2.getPrevious(),
|
curve1 = curve2.getPrevious(),
|
||||||
point = curve2.getPointAt(0, true),
|
point = curve2.getPointAt(0, true),
|
||||||
normal1 = curve1.getNormalAt(1, true).normalize(radius),
|
normal1 = curve1.getNormalAt(1, true).normalize(miterRadius),
|
||||||
normal2 = curve2.getNormalAt(0, true).normalize(radius),
|
normal2 = curve2.getNormalAt(0, true).normalize(miterRadius),
|
||||||
// Intersect the two lines
|
// Intersect the two lines
|
||||||
line1 = new Line(point.subtract(normal1),
|
line1 = new Line(point.add(normal1),
|
||||||
Point.create(-normal1.y, normal1.x)),
|
Point.create(-normal1.y, normal1.x)),
|
||||||
line2 = new Line(point.subtract(normal2),
|
line2 = new Line(point.add(normal2),
|
||||||
Point.create(-normal2.y, normal2.x)),
|
Point.create(-normal2.y, normal2.x)),
|
||||||
corner = line1.intersect(line2);
|
corner = line1.intersect(line2);
|
||||||
|
new Path.Circle(corner, 5).strokeColor = 'red';
|
||||||
// Now measure the distance from the segment to the
|
// Now measure the distance from the segment to the
|
||||||
// intersection, which his half of the miter distance
|
// intersection, which his half of the miter distance
|
||||||
if (!corner || point.getDistance(corner) > miter) {
|
if (!corner || point.getDistance(corner) > miterLimit) {
|
||||||
addJoin(segment, 'bevel');
|
addJoin(segment, 'bevel');
|
||||||
} else {
|
} else {
|
||||||
add(corner);
|
add(corner);
|
||||||
|
@ -2379,7 +2403,7 @@ statics: {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 1, l = segments.length - (closed ? 0 : 1); i < l; i++)
|
for (var i = 1; i < length; i++)
|
||||||
addJoin(segments[i], join);
|
addJoin(segments[i], join);
|
||||||
if (closed) {
|
if (closed) {
|
||||||
addJoin(segments[0], join);
|
addJoin(segments[0], join);
|
||||||
|
@ -2392,6 +2416,8 @@ statics: {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the bounding rectangle of the item including handles.
|
* Returns the bounding rectangle of the item including handles.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
*/
|
*/
|
||||||
getHandleBounds: function(segments, closed, style, matrix, strokePadding,
|
getHandleBounds: function(segments, closed, style, matrix, strokePadding,
|
||||||
joinPadding) {
|
joinPadding) {
|
||||||
|
@ -2426,6 +2452,8 @@ statics: {
|
||||||
/**
|
/**
|
||||||
* Returns the rough bounding rectangle of the item that is sure to include
|
* Returns the rough bounding rectangle of the item that is sure to include
|
||||||
* all of the drawing, including stroke width.
|
* all of the drawing, including stroke width.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
*/
|
*/
|
||||||
getRoughBounds: function(segments, closed, style, matrix) {
|
getRoughBounds: function(segments, closed, style, matrix) {
|
||||||
// Delegate to handleBounds, but pass on radius values for stroke and
|
// Delegate to handleBounds, but pass on radius values for stroke and
|
||||||
|
|
|
@ -190,9 +190,13 @@ test('path.bounds & path.strokeBounds with stroke styles', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('path.strokeBounds with rectangles', function() {
|
test('path.strokeBounds with rectangles', function() {
|
||||||
var path = new paper.Path.Rectangle([100, 100], [100, 100]);
|
var path = new paper.Path.Rectangle({
|
||||||
path.strokeWidth = 50;
|
point: [100, 100],
|
||||||
|
size: [100, 100],
|
||||||
|
strokeWidth: 50,
|
||||||
|
strokeColor: 'black'
|
||||||
|
});
|
||||||
compareRectangles(path.strokeBounds,
|
compareRectangles(path.strokeBounds,
|
||||||
{ x: 50, y: 50, width: 200, height: 200 },
|
{ x: 75, y: 75, width: 150, height: 150 },
|
||||||
'path.strokeBounds');
|
'path.strokeBounds');
|
||||||
});
|
});
|
Loading…
Reference in a new issue