mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2025-01-07 13:22:07 -05:00
Correctly take strokePadding and transformation matrices into account in roughBounds, through new Path._getPenPadding()
This commit is contained in:
parent
ea4dead72b
commit
d65ede7df8
1 changed files with 57 additions and 58 deletions
115
src/path/Path.js
115
src/path/Path.js
|
@ -2532,55 +2532,12 @@ statics: {
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
getStrokeBounds: function(segments, closed, style, matrix) {
|
getStrokeBounds: function(segments, closed, style, matrix) {
|
||||||
/**
|
|
||||||
* Returns the horizontal and vertical padding that a transformed round
|
|
||||||
* stroke adds to the bounding box, by calculating the dimensions of a
|
|
||||||
* rotated ellipse.
|
|
||||||
*/
|
|
||||||
function getPenPadding(radius, matrix) {
|
|
||||||
if (!matrix)
|
|
||||||
return [radius, radius];
|
|
||||||
// If a matrix is provided, we need to rotate the stroke circle
|
|
||||||
// and calculate the bounding box of the resulting rotated elipse:
|
|
||||||
// Get rotated hor and ver vectors, and determine rotation angle
|
|
||||||
// and elipse values from them:
|
|
||||||
var mx = matrix.shiftless(),
|
|
||||||
hor = mx.transform(new Point(radius, 0)),
|
|
||||||
ver = mx.transform(new Point(0, radius)),
|
|
||||||
phi = hor.getAngleInRadians(),
|
|
||||||
a = hor.getLength(),
|
|
||||||
b = ver.getLength();
|
|
||||||
// Formula for rotated ellipses:
|
|
||||||
// x = cx + a*cos(t)*cos(phi) - b*sin(t)*sin(phi)
|
|
||||||
// y = cy + b*sin(t)*cos(phi) + a*cos(t)*sin(phi)
|
|
||||||
// Derivates (by Wolfram Alpha):
|
|
||||||
// derivative of x = cx + a*cos(t)*cos(phi) - b*sin(t)*sin(phi)
|
|
||||||
// dx/dt = a sin(t) cos(phi) + b cos(t) sin(phi) = 0
|
|
||||||
// derivative of y = cy + b*sin(t)*cos(phi) + a*cos(t)*sin(phi)
|
|
||||||
// dy/dt = b cos(t) cos(phi) - a sin(t) sin(phi) = 0
|
|
||||||
// This can be simplified to:
|
|
||||||
// tan(t) = -b * tan(phi) / a // x
|
|
||||||
// tan(t) = b * cot(phi) / a // y
|
|
||||||
// Solving for t gives:
|
|
||||||
// t = pi * n - arctan(b * tan(phi) / a) // x
|
|
||||||
// t = pi * n + arctan(b * cot(phi) / a)
|
|
||||||
// = pi * n + arctan(b / tan(phi) / a) // y
|
|
||||||
var sin = Math.sin(phi),
|
|
||||||
cos = Math.cos(phi),
|
|
||||||
tan = Math.tan(phi),
|
|
||||||
tx = -Math.atan(b * tan / a),
|
|
||||||
ty = Math.atan(b / (tan * a));
|
|
||||||
// Due to symetry, we don't need to cycle through pi * n solutions:
|
|
||||||
return [Math.abs(a * Math.cos(tx) * cos - b * Math.sin(tx) * sin),
|
|
||||||
Math.abs(b * Math.sin(ty) * cos + a * Math.cos(ty) * sin)];
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Find a way to reuse 'bounds' cache instead?
|
// TODO: Find a way to reuse 'bounds' cache instead?
|
||||||
if (!style.hasStroke())
|
if (!style.hasStroke())
|
||||||
return Path.getBounds(segments, closed, style, matrix);
|
return Path.getBounds(segments, closed, style, matrix);
|
||||||
var length = segments.length - (closed ? 0 : 1),
|
var length = segments.length - (closed ? 0 : 1),
|
||||||
radius = style.getStrokeWidth() / 2,
|
radius = style.getStrokeWidth() / 2,
|
||||||
padding = getPenPadding(radius, matrix),
|
padding = Path._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(),
|
||||||
|
@ -2625,6 +2582,49 @@ statics: {
|
||||||
return bounds;
|
return bounds;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the horizontal and vertical padding that a transformed round
|
||||||
|
* stroke adds to the bounding box, by calculating the dimensions of a
|
||||||
|
* rotated ellipse.
|
||||||
|
*/
|
||||||
|
_getPenPadding: function(radius, matrix) {
|
||||||
|
if (!matrix)
|
||||||
|
return [radius, radius];
|
||||||
|
// If a matrix is provided, we need to rotate the stroke circle
|
||||||
|
// and calculate the bounding box of the resulting rotated elipse:
|
||||||
|
// Get rotated hor and ver vectors, and determine rotation angle
|
||||||
|
// and elipse values from them:
|
||||||
|
var mx = matrix.shiftless(),
|
||||||
|
hor = mx.transform(new Point(radius, 0)),
|
||||||
|
ver = mx.transform(new Point(0, radius)),
|
||||||
|
phi = hor.getAngleInRadians(),
|
||||||
|
a = hor.getLength(),
|
||||||
|
b = ver.getLength();
|
||||||
|
// Formula for rotated ellipses:
|
||||||
|
// x = cx + a*cos(t)*cos(phi) - b*sin(t)*sin(phi)
|
||||||
|
// y = cy + b*sin(t)*cos(phi) + a*cos(t)*sin(phi)
|
||||||
|
// Derivates (by Wolfram Alpha):
|
||||||
|
// derivative of x = cx + a*cos(t)*cos(phi) - b*sin(t)*sin(phi)
|
||||||
|
// dx/dt = a sin(t) cos(phi) + b cos(t) sin(phi) = 0
|
||||||
|
// derivative of y = cy + b*sin(t)*cos(phi) + a*cos(t)*sin(phi)
|
||||||
|
// dy/dt = b cos(t) cos(phi) - a sin(t) sin(phi) = 0
|
||||||
|
// This can be simplified to:
|
||||||
|
// tan(t) = -b * tan(phi) / a // x
|
||||||
|
// tan(t) = b * cot(phi) / a // y
|
||||||
|
// Solving for t gives:
|
||||||
|
// t = pi * n - arctan(b * tan(phi) / a) // x
|
||||||
|
// t = pi * n + arctan(b * cot(phi) / a)
|
||||||
|
// = pi * n + arctan(b / tan(phi) / a) // y
|
||||||
|
var sin = Math.sin(phi),
|
||||||
|
cos = Math.cos(phi),
|
||||||
|
tan = Math.tan(phi),
|
||||||
|
tx = -Math.atan(b * tan / a),
|
||||||
|
ty = Math.atan(b / (tan * a));
|
||||||
|
// Due to symetry, we don't need to cycle through pi * n solutions:
|
||||||
|
return [Math.abs(a * Math.cos(tx) * cos - b * Math.sin(tx) * sin),
|
||||||
|
Math.abs(b * Math.sin(ty) * cos + a * Math.cos(ty) * sin)];
|
||||||
|
},
|
||||||
|
|
||||||
_addSquareJoin: function(segment, join, radius, miterLimit, addPoint, area) {
|
_addSquareJoin: function(segment, join, radius, miterLimit, addPoint, area) {
|
||||||
// Treat bevel and miter in one go, since they share a lot of code.
|
// Treat bevel and miter in one go, since they share a lot of code.
|
||||||
var curve2 = segment.getCurve(),
|
var curve2 = segment.getCurve(),
|
||||||
|
@ -2694,20 +2694,20 @@ statics: {
|
||||||
x2 = -x1,
|
x2 = -x1,
|
||||||
y1 = x1,
|
y1 = x1,
|
||||||
y2 = x2;
|
y2 = x2;
|
||||||
strokePadding = strokePadding / 2 || 0;
|
|
||||||
joinPadding = joinPadding / 2 || 0;
|
|
||||||
for (var i = 0, l = segments.length; i < l; i++) {
|
for (var i = 0, l = segments.length; i < l; i++) {
|
||||||
var segment = segments[i];
|
var segment = segments[i];
|
||||||
segment._transformCoordinates(matrix, coords, false);
|
segment._transformCoordinates(matrix, coords, false);
|
||||||
for (var j = 0; j < 6; j += 2) {
|
for (var j = 0; j < 6; j += 2) {
|
||||||
// Use different padding for points or handles
|
// Use different padding for points or handles
|
||||||
var padding = j == 0 ? joinPadding : strokePadding,
|
var padding = j == 0 ? joinPadding : strokePadding,
|
||||||
|
paddingX = padding ? padding[0] : 0,
|
||||||
|
paddingY = padding ? padding[1] : 0,
|
||||||
x = coords[j],
|
x = coords[j],
|
||||||
y = coords[j + 1],
|
y = coords[j + 1],
|
||||||
xn = x - padding,
|
xn = x - paddingX,
|
||||||
xx = x + padding,
|
xx = x + paddingX,
|
||||||
yn = y - padding,
|
yn = y - paddingY,
|
||||||
yx = y + padding;
|
yx = y + paddingY;
|
||||||
if (xn < x1) x1 = xn;
|
if (xn < x1) x1 = xn;
|
||||||
if (xx > x2) x2 = xx;
|
if (xx > x2) x2 = xx;
|
||||||
if (yn < y1) y1 = yn;
|
if (yn < y1) y1 = yn;
|
||||||
|
@ -2727,17 +2727,16 @@ statics: {
|
||||||
// Delegate to handleBounds, but pass on radius values for stroke and
|
// Delegate to handleBounds, but pass on radius values for stroke and
|
||||||
// joins. Hanlde miter joins specially, by passing the largets radius
|
// joins. Hanlde miter joins specially, by passing the largets radius
|
||||||
// possible.
|
// possible.
|
||||||
var strokeWidth = style.getStrokeColor() ? style.getStrokeWidth() : 0,
|
var strokeRadius = style.hasStroke() ? style.getStrokeWidth() / 2 : 0,
|
||||||
joinWidth = strokeWidth;
|
joinRadius = strokeRadius;
|
||||||
if (strokeWidth === 0) {
|
if (strokeRadius > 0) {
|
||||||
strokeWidth = /*#=*/ Numerical.TOLERANCE;
|
|
||||||
} else {
|
|
||||||
if (style.getStrokeJoin() === 'miter')
|
if (style.getStrokeJoin() === 'miter')
|
||||||
joinWidth = strokeWidth * style.getMiterLimit();
|
joinRadius = strokeRadius * style.getMiterLimit();
|
||||||
if (style.getStrokeCap() === 'square')
|
if (style.getStrokeCap() === 'square')
|
||||||
joinWidth = Math.max(joinWidth, strokeWidth * Math.sqrt(2));
|
joinRadius = Math.max(joinRadius, strokeRadius * Math.sqrt(2));
|
||||||
}
|
}
|
||||||
return Path.getHandleBounds(segments, closed, style, matrix,
|
return Path.getHandleBounds(segments, closed, style, matrix,
|
||||||
strokeWidth, joinWidth);
|
Path._getPenPadding(strokeRadius, matrix),
|
||||||
|
Path._getPenPadding(joinRadius, matrix));
|
||||||
}
|
}
|
||||||
}});
|
}});
|
||||||
|
|
Loading…
Reference in a new issue