Bug 1846079 - Ensure pruned point begins first sub-path if necessary. r=aosmond
The Canvas2D specification says that if a path has no active sub-paths, and a primitive is drawn, that the first point of that primitive becomes the start of the newly created sub-path that will be created for it. So if we prune a point when a path has no active sub-paths, and then a new primitive comes in that does not start with that same point, we risk not installing the pruned point as the start of that new sub-path. To solve this, we need to detect if a path has no active sub-paths while we are building it. This adds PathBuilder::IsActive() to help with that. Then before we go to add a primitive, we check if there is a pruned point on a path that is not active yet, and if so, install the correct start point with a MoveTo. This also makes IsActive and IsEmpty required so to ensure all our path implementations behave consistently rather than having any surprising unimplemented behavior. Differential Revision: https://phabricator.services.mozilla.com/D184891
This commit is contained in:
@@ -88,6 +88,7 @@ class CanvasPath final : public nsWrapperCache {
|
|||||||
|
|
||||||
void EnsurePathBuilder() const;
|
void EnsurePathBuilder() const;
|
||||||
void EnsureCapped() const;
|
void EnsureCapped() const;
|
||||||
|
void EnsureActive() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace dom
|
} // namespace dom
|
||||||
|
|||||||
@@ -3385,6 +3385,8 @@ void CanvasRenderingContext2D::Arc(double aX, double aY, double aR,
|
|||||||
|
|
||||||
EnsureWritablePath();
|
EnsureWritablePath();
|
||||||
|
|
||||||
|
EnsureActivePath();
|
||||||
|
|
||||||
mPathBuilder->Arc(Point(aX, aY), aR, aStartAngle, aEndAngle, aAnticlockwise);
|
mPathBuilder->Arc(Point(aX, aY), aR, aStartAngle, aEndAngle, aAnticlockwise);
|
||||||
mPathPruned = false;
|
mPathPruned = false;
|
||||||
}
|
}
|
||||||
@@ -6369,6 +6371,16 @@ inline void CanvasPath::EnsureCapped() const {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline void CanvasPath::EnsureActive() const {
|
||||||
|
// If the path is not active, then adding an op to the path may cause the path
|
||||||
|
// to add the first point of the op as the initial point instead of the actual
|
||||||
|
// current point.
|
||||||
|
if (mPruned && !mPathBuilder->IsActive()) {
|
||||||
|
mPathBuilder->MoveTo(mPathBuilder->CurrentPoint());
|
||||||
|
mPruned = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void CanvasPath::MoveTo(double aX, double aY) {
|
void CanvasPath::MoveTo(double aX, double aY) {
|
||||||
EnsurePathBuilder();
|
EnsurePathBuilder();
|
||||||
|
|
||||||
@@ -6399,6 +6411,8 @@ void CanvasPath::QuadraticCurveTo(double aCpx, double aCpy, double aX,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EnsureActive();
|
||||||
|
|
||||||
mPathBuilder->QuadraticBezierTo(cp1, cp2);
|
mPathBuilder->QuadraticBezierTo(cp1, cp2);
|
||||||
mPruned = false;
|
mPruned = false;
|
||||||
}
|
}
|
||||||
@@ -6518,6 +6532,8 @@ void CanvasPath::Arc(double aX, double aY, double aRadius, double aStartAngle,
|
|||||||
|
|
||||||
EnsurePathBuilder();
|
EnsurePathBuilder();
|
||||||
|
|
||||||
|
EnsureActive();
|
||||||
|
|
||||||
mPathBuilder->Arc(Point(aX, aY), aRadius, aStartAngle, aEndAngle,
|
mPathBuilder->Arc(Point(aX, aY), aRadius, aStartAngle, aEndAngle,
|
||||||
aAnticlockwise);
|
aAnticlockwise);
|
||||||
mPruned = false;
|
mPruned = false;
|
||||||
@@ -6552,6 +6568,8 @@ void CanvasPath::LineTo(const gfx::Point& aPoint) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EnsureActive();
|
||||||
|
|
||||||
mPathBuilder->LineTo(aPoint);
|
mPathBuilder->LineTo(aPoint);
|
||||||
mPruned = false;
|
mPruned = false;
|
||||||
}
|
}
|
||||||
@@ -6568,6 +6586,8 @@ void CanvasPath::BezierTo(const gfx::Point& aCP1, const gfx::Point& aCP2,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EnsureActive();
|
||||||
|
|
||||||
mPathBuilder->BezierTo(aCP1, aCP2, aCP3);
|
mPathBuilder->BezierTo(aCP1, aCP2, aCP3);
|
||||||
mPruned = false;
|
mPruned = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -352,6 +352,13 @@ class CanvasRenderingContext2D : public nsICanvasRenderingContextInternal,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EnsureActivePath() {
|
||||||
|
if (mPathPruned && !mPathBuilder->IsActive()) {
|
||||||
|
mPathBuilder->MoveTo(mPathBuilder->CurrentPoint());
|
||||||
|
mPathPruned = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void ClosePath() {
|
void ClosePath() {
|
||||||
EnsureWritablePath();
|
EnsureWritablePath();
|
||||||
|
|
||||||
@@ -390,6 +397,7 @@ class CanvasRenderingContext2D : public nsICanvasRenderingContextInternal,
|
|||||||
mPathPruned = true;
|
mPathPruned = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
EnsureActivePath();
|
||||||
mPathBuilder->QuadraticBezierTo(cp1, cp2);
|
mPathBuilder->QuadraticBezierTo(cp1, cp2);
|
||||||
mPathPruned = false;
|
mPathPruned = false;
|
||||||
}
|
}
|
||||||
@@ -530,6 +538,7 @@ class CanvasRenderingContext2D : public nsICanvasRenderingContextInternal,
|
|||||||
mPathPruned = true;
|
mPathPruned = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
EnsureActivePath();
|
||||||
mPathBuilder->LineTo(aPoint);
|
mPathBuilder->LineTo(aPoint);
|
||||||
mPathPruned = false;
|
mPathPruned = false;
|
||||||
}
|
}
|
||||||
@@ -545,6 +554,7 @@ class CanvasRenderingContext2D : public nsICanvasRenderingContextInternal,
|
|||||||
mPathPruned = true;
|
mPathPruned = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
EnsureActivePath();
|
||||||
mPathBuilder->BezierTo(aCP1, aCP2, aCP3);
|
mPathBuilder->BezierTo(aCP1, aCP2, aCP3);
|
||||||
mPathPruned = false;
|
mPathPruned = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1007,7 +1007,7 @@ class Path : public external::AtomicRefCounted<Path> {
|
|||||||
|
|
||||||
virtual Point ComputePointAtLength(Float aLength, Point* aTangent = nullptr);
|
virtual Point ComputePointAtLength(Float aLength, Point* aTangent = nullptr);
|
||||||
|
|
||||||
virtual bool IsEmpty() const { return false; }
|
virtual bool IsEmpty() const = 0;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Path();
|
Path();
|
||||||
@@ -1028,6 +1028,8 @@ class PathBuilder : public PathSink {
|
|||||||
virtual already_AddRefed<Path> Finish() = 0;
|
virtual already_AddRefed<Path> Finish() = 0;
|
||||||
|
|
||||||
virtual BackendType GetBackendType() const = 0;
|
virtual BackendType GetBackendType() const = 0;
|
||||||
|
|
||||||
|
virtual bool IsActive() const = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Glyph {
|
struct Glyph {
|
||||||
|
|||||||
@@ -239,6 +239,20 @@ void PathCairo::StreamToSink(PathSink* aSink) const {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool PathCairo::IsEmpty() const {
|
||||||
|
for (size_t i = 0; i < mPathData.size(); i++) {
|
||||||
|
switch (mPathData[i].header.type) {
|
||||||
|
case CAIRO_PATH_MOVE_TO:
|
||||||
|
break;
|
||||||
|
case CAIRO_PATH_CLOSE_PATH:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void PathCairo::EnsureContainingContext(const Matrix& aTransform) const {
|
void PathCairo::EnsureContainingContext(const Matrix& aTransform) const {
|
||||||
if (mContainingContext) {
|
if (mContainingContext) {
|
||||||
if (mContainingTransform.ExactlyEquals(aTransform)) {
|
if (mContainingTransform.ExactlyEquals(aTransform)) {
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ class PathBuilderCairo : public PathBuilder {
|
|||||||
|
|
||||||
BackendType GetBackendType() const override { return BackendType::CAIRO; }
|
BackendType GetBackendType() const override { return BackendType::CAIRO; }
|
||||||
|
|
||||||
|
bool IsActive() const override { return !mPathData.empty(); }
|
||||||
|
|
||||||
static already_AddRefed<PathBuilder> Create(FillRule aFillRule);
|
static already_AddRefed<PathBuilder> Create(FillRule aFillRule);
|
||||||
|
|
||||||
private: // data
|
private: // data
|
||||||
@@ -80,6 +82,8 @@ class PathCairo : public Path {
|
|||||||
void AppendPathToBuilder(PathBuilderCairo* aBuilder,
|
void AppendPathToBuilder(PathBuilderCairo* aBuilder,
|
||||||
const Matrix* aTransform = nullptr) const;
|
const Matrix* aTransform = nullptr) const;
|
||||||
|
|
||||||
|
bool IsEmpty() const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void EnsureContainingContext(const Matrix& aTransform) const;
|
void EnsureContainingContext(const Matrix& aTransform) const;
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ class PathBuilderD2D : public PathBuilder {
|
|||||||
|
|
||||||
bool IsFigureActive() const { return mFigureActive; }
|
bool IsFigureActive() const { return mFigureActive; }
|
||||||
|
|
||||||
|
virtual bool IsActive() const { return IsFigureActive(); }
|
||||||
|
|
||||||
static already_AddRefed<PathBuilder> Create(FillRule aFillRule);
|
static already_AddRefed<PathBuilder> Create(FillRule aFillRule);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -179,6 +179,25 @@ size_t PathOps::NumberOfOps() const {
|
|||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool PathOps::IsEmpty() const {
|
||||||
|
const uint8_t* nextByte = mPathData.data();
|
||||||
|
const uint8_t* end = nextByte + mPathData.size();
|
||||||
|
while (nextByte < end) {
|
||||||
|
const OpType opType = *reinterpret_cast<const OpType*>(nextByte);
|
||||||
|
nextByte += sizeof(OpType);
|
||||||
|
switch (opType) {
|
||||||
|
case OpType::OP_MOVETO:
|
||||||
|
nextByte += sizeof(Point);
|
||||||
|
break;
|
||||||
|
case OpType::OP_CLOSE:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void PathBuilderRecording::MoveTo(const Point& aPoint) {
|
void PathBuilderRecording::MoveTo(const Point& aPoint) {
|
||||||
mPathOps.MoveTo(aPoint);
|
mPathOps.MoveTo(aPoint);
|
||||||
mBeginPoint = aPoint;
|
mBeginPoint = aPoint;
|
||||||
|
|||||||
@@ -71,6 +71,10 @@ class PathOps {
|
|||||||
|
|
||||||
Maybe<Circle> AsCircle() const;
|
Maybe<Circle> AsCircle() const;
|
||||||
|
|
||||||
|
bool IsActive() const { return !mPathData.empty(); }
|
||||||
|
|
||||||
|
bool IsEmpty() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum class OpType : uint32_t {
|
enum class OpType : uint32_t {
|
||||||
OP_MOVETO = 0,
|
OP_MOVETO = 0,
|
||||||
@@ -167,6 +171,8 @@ class PathBuilderRecording final : public PathBuilder {
|
|||||||
|
|
||||||
BackendType GetBackendType() const final { return BackendType::RECORDING; }
|
BackendType GetBackendType() const final { return BackendType::RECORDING; }
|
||||||
|
|
||||||
|
bool IsActive() const final { return mPathOps.IsActive(); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
BackendType mBackendType;
|
BackendType mBackendType;
|
||||||
FillRule mFillRule;
|
FillRule mFillRule;
|
||||||
@@ -222,6 +228,8 @@ class PathRecording final : public Path {
|
|||||||
|
|
||||||
FillRule GetFillRule() const final { return mFillRule; }
|
FillRule GetFillRule() const final { return mFillRule; }
|
||||||
|
|
||||||
|
bool IsEmpty() const final { return mPathOps.IsEmpty(); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend class DrawTargetWrapAndRecord;
|
friend class DrawTargetWrapAndRecord;
|
||||||
friend class DrawTargetRecording;
|
friend class DrawTargetRecording;
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ class PathBuilderSkia : public PathBuilder {
|
|||||||
|
|
||||||
BackendType GetBackendType() const override { return BackendType::SKIA; }
|
BackendType GetBackendType() const override { return BackendType::SKIA; }
|
||||||
|
|
||||||
|
bool IsActive() const override { return mPath.countPoints() > 0; }
|
||||||
|
|
||||||
static already_AddRefed<PathBuilder> Create(FillRule aFillRule);
|
static already_AddRefed<PathBuilder> Create(FillRule aFillRule);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
8
layout/reftests/canvas/1846079-1-ref.html
Normal file
8
layout/reftests/canvas/1846079-1-ref.html
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<canvas id="c" width="50" height="50"></canvas>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
|
||||||
22
layout/reftests/canvas/1846079-1.html
Normal file
22
layout/reftests/canvas/1846079-1.html
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<canvas id="c" width="50" height="50"></canvas>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const c = document.getElementById("c");
|
||||||
|
const ctx = c.getContext("2d");
|
||||||
|
|
||||||
|
ctx.translate(20, 20);
|
||||||
|
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.lineTo(0, 0);
|
||||||
|
ctx.lineTo(0, 20);
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
|
||||||
@@ -119,3 +119,5 @@ fuzzy-if(winWidget,0-94,0-1575) fuzzy-if(cocoaWidget,0-1,0-34) == 1304353-text-g
|
|||||||
|
|
||||||
== 1817455-1.html 1817455-1-ref.html
|
== 1817455-1.html 1817455-1-ref.html
|
||||||
!= 1817873-1.html 1817873-1-ref.html
|
!= 1817873-1.html 1817873-1-ref.html
|
||||||
|
|
||||||
|
!= 1846079-1.html 1846079-1-ref.html
|
||||||
|
|||||||
Reference in New Issue
Block a user