-
-
Notifications
You must be signed in to change notification settings - Fork 3.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(Path): bbox stroke projection #8040
Changes from 46 commits
7bb3bbd
c8741c8
35a4050
8ef5be7
f37ee08
60fb86f
e33d4b2
f63381b
dd0174f
f90c896
0184144
c9696b9
8ec9d35
790d392
f8807b4
d6d1276
6dba673
aad27ad
ba6fb30
016e61f
c85b99a
e51fa03
6ea773d
869320b
22d2a19
8f62ded
e758365
0d03b0d
6b952c5
1de68eb
fa4f46b
49c037a
15771b1
ad70555
9a3119d
48e13ad
1a6b957
dc058a9
94ac1a7
a203254
d41b643
10aa26a
18f026b
82e892c
df3c0c5
1555e8d
44723c3
e133fa0
cb476a0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -64,7 +64,7 @@ | |
Array.isArray(path) ? path : fabric.util.parsePath(path) | ||
); | ||
|
||
fabric.Polyline.prototype._setPositionDimensions.call(this, options || {}); | ||
this._setPositionDimensions(options); | ||
}, | ||
|
||
/** | ||
|
@@ -237,6 +237,37 @@ | |
return this.path.length; | ||
}, | ||
|
||
/** | ||
* @private | ||
*/ | ||
_projectStrokeOnSegmentBBox: function (point, pointBefore, pointAfter, bboxPoints) { | ||
var v1 = pointBefore.subtract(point); | ||
var v2 = pointAfter.subtract(point); | ||
var angle = fabric.util.calcAngleBetweenVectors(v1, v2); | ||
if (Math.abs(angle) < Math.PI / 2) { | ||
var projectionVector = fabric.util.calcStrokeProjection( | ||
pointBefore, | ||
point, | ||
pointAfter, | ||
this | ||
); | ||
var projectedPoints = [ | ||
point.add(projectionVector), | ||
point.subtract(projectionVector), | ||
]; | ||
Array.isArray(bboxPoints) && bboxPoints.forEach(function (point) { | ||
projectedPoints.push( | ||
point.add(projectionVector), | ||
point.subtract(projectionVector) | ||
); | ||
}); | ||
return projectedPoints; | ||
} | ||
else { | ||
return []; | ||
} | ||
}, | ||
|
||
/** | ||
* @private | ||
*/ | ||
|
@@ -245,86 +276,163 @@ | |
var aX = [], | ||
aY = [], | ||
current, // current instruction | ||
subpathStartX = 0, | ||
subpathStartY = 0, | ||
x = 0, // current x | ||
y = 0, // current y | ||
bounds; | ||
subpathStart = new fabric.Point(0, 0), | ||
subpathSecond = new fabric.Point(0, 0), | ||
point = new fabric.Point(0, 0), | ||
prev = new fabric.Point(0, 0), | ||
beforePrev = new fabric.Point(0, 0), | ||
opening = false, | ||
second = false, | ||
closing = false, | ||
projectedPoints = [], | ||
prevBounds, | ||
bounds = []; | ||
|
||
for (var i = 0, len = this.path.length; i < len; ++i) { | ||
|
||
current = this.path[i]; | ||
if (opening) { | ||
second = true; | ||
} | ||
opening = closing = false; | ||
beforePrev.setFromPoint(prev); | ||
prev.setFromPoint(point); | ||
prevBounds = bounds; | ||
bounds = []; | ||
|
||
switch (current[0]) { // first letter | ||
|
||
case 'L': // lineto, absolute | ||
x = current[1]; | ||
y = current[2]; | ||
point.setXY(current[1], current[2]); | ||
bounds = []; | ||
break; | ||
|
||
case 'M': // moveTo, absolute | ||
x = current[1]; | ||
y = current[2]; | ||
subpathStartX = x; | ||
subpathStartY = y; | ||
point.setXY(current[1], current[2]); | ||
subpathStart.setFromPoint(point); | ||
prev.setFromPoint(point); | ||
beforePrev.setFromPoint(point); | ||
opening = true; | ||
bounds = []; | ||
break; | ||
|
||
case 'C': // bezierCurveTo, absolute | ||
bounds = fabric.util.getBoundsOfCurve(x, y, | ||
bounds = fabric.util.getBoundsOfCurve(point.x, point.y, | ||
current[1], | ||
current[2], | ||
current[3], | ||
current[4], | ||
current[5], | ||
current[6] | ||
); | ||
x = current[5]; | ||
y = current[6]; | ||
point.setXY(current[5], current[6]); | ||
break; | ||
|
||
case 'Q': // quadraticCurveTo, absolute | ||
bounds = fabric.util.getBoundsOfCurve(x, y, | ||
bounds = fabric.util.getBoundsOfCurve(point.x, point.y, | ||
current[1], | ||
current[2], | ||
current[1], | ||
current[2], | ||
current[3], | ||
current[4] | ||
); | ||
x = current[3]; | ||
y = current[4]; | ||
point.setXY(current[3], current[4]); | ||
break; | ||
|
||
case 'z': | ||
case 'Z': | ||
x = subpathStartX; | ||
y = subpathStartY; | ||
point.setFromPoint(subpathStart); | ||
closing = true; | ||
break; | ||
} | ||
|
||
if (this.strokeLineJoin === 'miter') { | ||
if (!opening && !second) { | ||
projectedPoints.push.apply( | ||
projectedPoints, | ||
this._projectStrokeOnSegmentBBox(prev, beforePrev, point, prevBounds) | ||
); | ||
} | ||
if (closing) { | ||
// project stroke on sub path start | ||
projectedPoints.push.apply( | ||
projectedPoints, | ||
this._projectStrokeOnSegmentBBox(subpathStart, prev, subpathSecond, bounds) | ||
); | ||
} | ||
} | ||
|
||
aX.push(point.x); | ||
aY.push(point.y); | ||
|
||
bounds.forEach(function (point) { | ||
aX.push(point.x); | ||
aY.push(point.y); | ||
}); | ||
aX.push(x); | ||
aY.push(y); | ||
|
||
if (second) { | ||
subpathSecond.setFromPoint(point); | ||
second = false; | ||
} | ||
|
||
} | ||
|
||
var minX = min(aX) || 0, | ||
minY = min(aY) || 0, | ||
maxX = max(aX) || 0, | ||
maxY = max(aY) || 0, | ||
deltaX = maxX - minX, | ||
deltaY = maxY - minY; | ||
projectedPoints.forEach(function (point) { | ||
aX.push(point.x); | ||
aY.push(point.y); | ||
}); | ||
|
||
var minPoint = new fabric.Point(min(aX) || 0, min(aY) || 0), | ||
maxPoint = new fabric.Point(max(aX) || 0, max(aY) || 0), | ||
delta = maxPoint.subtract(minPoint); | ||
|
||
return { | ||
left: minX, | ||
top: minY, | ||
width: deltaX, | ||
height: deltaY | ||
left: minPoint.x, | ||
top: minPoint.y, | ||
width: delta.x, | ||
height: delta.y | ||
}; | ||
} | ||
}, | ||
|
||
_setPositionDimensions: function (options) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. a wretched function |
||
options || (options = {}); | ||
var calcDim = this._calcDimensions(options), origin; | ||
this.width = calcDim.width; | ||
this.height = calcDim.height; | ||
if (!options.fromSVG) { | ||
origin = this.translateToGivenOrigin( | ||
// this looks bad, but is one way to keep it optional for now. | ||
new fabric.Point( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't like this. Because it doesn't take into account the translation caused by stroke width There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. resolved I believe |
||
calcDim.left, | ||
calcDim.top | ||
), | ||
'left', | ||
'top', | ||
this.originX, | ||
this.originY | ||
); | ||
} | ||
if (typeof options.left === 'undefined') { | ||
this.left = options.fromSVG ? calcDim.left : origin.x; | ||
} | ||
if (typeof options.top === 'undefined') { | ||
this.top = options.fromSVG ? calcDim.top : origin.y; | ||
} | ||
this.pathOffset = new fabric.Point( | ||
calcDim.left + this.width / 2, | ||
calcDim.top + this.height / 2 | ||
); | ||
}, | ||
|
||
/** | ||
* @override stroke is already accounted for in size | ||
* @returns {fabric.Point} dimensions | ||
*/ | ||
_getNonTransformedDimensions: function () { | ||
return new fabric.Point(this.width, this.height); | ||
}, | ||
|
||
}); | ||
|
||
/** | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -180,17 +180,18 @@ | |
}, | ||
|
||
/** | ||
* Let A, B, C be vertexes of a triangle, calculate the bisector of A | ||
* @static | ||
* @memberOf fabric.util | ||
* @param {Point} A | ||
* @param {Point} B | ||
* @param {Point} C | ||
* @param {Point} A the vertex of the bisector | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. wrong desc |
||
* @param {Point} B a vertex that defines a side of the angle | ||
* @param {Point} C a second vertex that defines the other side of the angle | ||
* @returns {{ vector: Point, angle: number }} vector representing the bisector of A and A's angle | ||
*/ | ||
getBisector: function (A, B, C) { | ||
var AB = fabric.util.createVector(A, B), AC = fabric.util.createVector(A, C); | ||
var alpha = fabric.util.calcAngleBetweenVectors(AB, AC); | ||
// check if alpha is relative to AB->BC | ||
// check if alpha is relative to AB->AC | ||
var ro = fabric.util.calcAngleBetweenVectors(fabric.util.rotateVector(AB, alpha), AC); | ||
var phi = alpha * (ro === 0 ? 1 : -1) / 2; | ||
return { | ||
|
@@ -199,6 +200,44 @@ | |
}; | ||
}, | ||
|
||
/** | ||
* Calculates the stroke projection vector to apply to `point` | ||
* @static | ||
* @memberOf fabric.util | ||
* @param {Point} point the point to project | ||
* @param {Point} pointBefore a vertex that defines one side of the angle | ||
* @param {Point} pointAfter a vertex that defines a second side of the angle | ||
* @param {Object} options | ||
* @param {number} options.strokeWidth | ||
* @param {'miter'|'bevel'|'round'} options.strokeLineJoin | ||
* @param {number} options.strokeMiterLimit https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-miterlimit | ||
* @param {boolean} options.strokeUniform | ||
* @param {number} options.scaleX | ||
* @param {number} options.scaleY | ||
* @returns {fabric.Point} a vector representing the stroke projection (direction sign can't be determined) | ||
*/ | ||
calcStrokeProjection: function (point, pointBefore, pointAfter, options) { | ||
var s = options.strokeWidth / 2, | ||
strokeUniformScalar = options.strokeUniform ? | ||
new fabric.Point(1 / options.scaleX, 1 / options.scaleY) : | ||
new fabric.Point(1, 1), | ||
bisector = fabric.util.getBisector(point, pointBefore, pointAfter), | ||
bisectorVector = bisector.vector.multiply(strokeUniformScalar), | ||
alpha = bisector.angle, | ||
scalar = -s / Math.sin(alpha / 2), | ||
miterVector = bisectorVector.scalarMultiply(scalar); | ||
if (options.strokeLineJoin === 'miter' && | ||
Math.hypot(miterVector.x, miterVector.y) / s <= options.strokeMiterLimit) { | ||
return miterVector; | ||
} | ||
else { | ||
// calculate bevel, round projections | ||
// incorrect approximation | ||
scalar = -s * Math.SQRT2; | ||
return bisectorVector.scalarMultiply(scalar); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. doesn't cover bevel/round cases properly |
||
} | ||
}, | ||
|
||
/** | ||
* Project stroke width on points returning 2 projections for each point as follows: | ||
* - `miter`: 2 points corresponding to the outer boundary and the inner boundary of stroke. | ||
|
@@ -241,28 +280,7 @@ | |
B = points[index - 1]; | ||
C = points[index + 1]; | ||
} | ||
var bisector = fabric.util.getBisector(A, B, C), | ||
bisectorVector = bisector.vector, | ||
alpha = bisector.angle, | ||
scalar, | ||
miterVector; | ||
if (options.strokeLineJoin === 'miter') { | ||
scalar = -s / Math.sin(alpha / 2); | ||
miterVector = new fabric.Point( | ||
bisectorVector.x * scalar * strokeUniformScalar.x, | ||
bisectorVector.y * scalar * strokeUniformScalar.y | ||
); | ||
if (Math.hypot(miterVector.x, miterVector.y) / s <= options.strokeMiterLimit) { | ||
coords.push(A.add(miterVector)); | ||
coords.push(A.subtract(miterVector)); | ||
return; | ||
} | ||
} | ||
scalar = -s * Math.SQRT2; | ||
miterVector = new fabric.Point( | ||
bisectorVector.x * scalar * strokeUniformScalar.x, | ||
bisectorVector.y * scalar * strokeUniformScalar.y | ||
); | ||
var miterVector = fabric.util.calcStrokeProjection(A, B, C, options); | ||
coords.push(A.add(miterVector)); | ||
coords.push(A.subtract(miterVector)); | ||
}); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we project all angles some bboxes of visual tests fix while other visual tests break
clipping13
pathWithGradientSvg
arc3