Bug 1598156 - Part 6: Build path for basic shapes. r=emilio

Basically, the fuzzy happens because the direction angle in circle(),
ellipse(), and inset() can not be equal to the degree perfectly (due to
the arc drawing implementation and the anti-alias around the element),
so we need some tolerance.

And drop offset-path-shape(-ref).html because it has been removed from
the upstream repo.

Differential Revision: https://phabricator.services.mozilla.com/D179630
This commit is contained in:
Boris Chiou 2023-06-26 23:23:55 +00:00
parent 48208132f5
commit 99508a94f6
34 changed files with 177 additions and 108 deletions

View File

@ -509,8 +509,8 @@ AnimationStorageData AnimationHelper::ExtractAnimations(
RefPtr<gfx::PathBuilder> builder =
MotionPathUtils::GetCompositorPathBuilder();
storageData.mCachedMotionPath =
MotionPathUtils::BuildPath(offsetPath.AsSVGPathData(), builder);
storageData.mCachedMotionPath = MotionPathUtils::BuildSVGPath(
offsetPath.AsSVGPathData(), builder);
}
}

View File

@ -262,20 +262,18 @@ Maybe<ResolvedMotionPathData> MotionPathUtils::ResolveMotionPath(
double directionAngle = 0.0;
gfx::Point point;
if (aPath.IsShape()) {
const auto& path = aPath.AsShape();
if (!path.mGfxPath) {
// Empty gfx::Path means it is path('') (i.e. empty path string).
return Nothing();
}
const auto& data = aPath.AsShape();
RefPtr<gfx::Path> path = data.mGfxPath;
MOZ_ASSERT(path, "The empty path is not allowed");
// Per the spec, we have to convert offset distance to pixels, with 100%
// being converted to total length. So here |gfxPath| is built with CSS
// pixel, and we calculate |pathLength| and |computedDistance| with CSS
// pixel as well.
gfx::Float pathLength = path.mGfxPath->ComputeLength();
gfx::Float pathLength = path->ComputeLength();
gfx::Float usedDistance =
aDistance.ResolveToCSSPixels(CSSCoord(pathLength));
if (path.mIsClosedIntervals) {
if (data.mIsClosedIntervals) {
// Per the spec, let used offset distance be equal to offset distance
// modulus the total length of the path. If the total length of the path
// is 0, used offset distance is also 0.
@ -291,8 +289,14 @@ Maybe<ResolvedMotionPathData> MotionPathUtils::ResolveMotionPath(
usedDistance = clamped(usedDistance, 0.0f, pathLength);
}
gfx::Point tangent;
point = path.mGfxPath->ComputePointAtLength(usedDistance, &tangent);
directionAngle = (double)atan2(tangent.y, tangent.x); // In Radian.
point = path->ComputePointAtLength(usedDistance, &tangent);
// Basically, |point| should be a relative distance between the current
// position and the target position. The built |path| is in the coordinate
// system of its containing block. Therefore, we have to take the current
// position of this box into account to offset the translation so it's final
// position is not affected by other boxes in the same containing block.
point -= NSPointToPoint(data.mCurrentPosition, AppUnitsPerCSSPixel());
directionAngle = atan2((double)tangent.y, (double)tangent.x); // in Radian.
} else if (aPath.IsRay()) {
const auto& ray = aPath.AsRay();
MOZ_ASSERT(ray.mRay);
@ -360,6 +364,12 @@ Maybe<ResolvedMotionPathData> MotionPathUtils::ResolveMotionPath(
angle, shift});
}
static inline bool IsClosedPath(const StyleSVGPathData& aPathData) {
return !aPathData._0.AsSpan().empty() &&
aPathData._0.AsSpan().rbegin()->IsClosePath();
}
// Generate data for motion path on the main thread.
static OffsetPathData GenerateOffsetPathData(const nsIFrame* aFrame) {
const StyleOffsetPath& offsetPath = aFrame->StyleDisplay()->mOffsetPath;
if (offsetPath.IsNone()) {
@ -389,13 +399,36 @@ static OffsetPathData GenerateOffsetPathData(const nsIFrame* aFrame) {
aFrame->GetProperty(nsIFrame::OffsetPathCache());
MOZ_ASSERT(gfxPath || pathData._0.IsEmpty(),
"Should have a valid cached gfx::Path or an empty path string");
return OffsetPathData::Shape(pathData, gfxPath.forget());
// FIXME: Bug 1836847. Once we support "at <position>" for path(), we have
// to give it the current box position.
return OffsetPathData::Shape(gfxPath.forget(), {}, IsClosedPath(pathData));
}
// The rest part is to handle "<basic-shape> || <coord-box>".
MOZ_ASSERT(offsetPath.IsBasicShapeOrCoordBox());
// TODO: Implement this in the following patches.
return OffsetPathData::None();
nsRect coordBox;
const nsIFrame* containingFrame =
MotionPathUtils::GetOffsetPathReferenceBox(aFrame, coordBox);
if (!containingFrame || coordBox.IsEmpty()) {
return OffsetPathData::None();
}
const nsStyleDisplay* disp = aFrame->StyleDisplay();
nsPoint currentPosition = aFrame->GetOffsetTo(containingFrame);
RefPtr<gfx::PathBuilder> builder = MotionPathUtils::GetPathBuilder();
// TODO: If offset-path is coord-box only, create inset(0 round X), where X is
// the value of border-radius on the element that establishes the containing
// block for this element.
const StyleBasicShape& shape =
disp->mOffsetPath.AsOffsetPath().path->AsShape();
RefPtr<gfx::Path> path = MotionPathUtils::BuildPath(
shape, disp->mOffsetPosition, coordBox, currentPosition, builder);
if (!path) {
return OffsetPathData::None();
}
return OffsetPathData::Shape(path.forget(), std::move(currentPosition), true);
}
/* static*/
@ -417,6 +450,7 @@ Maybe<ResolvedMotionPathData> MotionPathUtils::ResolveMotionPath(
transformOrigin, aRefBox, ComputeAnchorPointAdjustment(*aFrame));
}
// Generate data for motion path on the compositor thread.
static OffsetPathData GenerateOffsetPathData(
const StyleOffsetPath& aOffsetPath,
const layers::MotionPathData& aMotionPathData,
@ -444,9 +478,11 @@ static OffsetPathData GenerateOffsetPathData(
if (!path) {
RefPtr<gfx::PathBuilder> builder =
MotionPathUtils::GetCompositorPathBuilder();
path = MotionPathUtils::BuildPath(pathData, builder);
path = MotionPathUtils::BuildSVGPath(pathData, builder);
}
return OffsetPathData::Shape(pathData, path.forget());
// FIXME: Bug 1836847. Once we support "at <position>" for path(), we have
// to give it the current box position.
return OffsetPathData::Shape(path.forget(), {}, IsClosedPath(pathData));
}
// The rest part is to handle "<basic-shape> || <coord-box>".
@ -482,7 +518,7 @@ Maybe<ResolvedMotionPathData> MotionPathUtils::ResolveMotionPath(
}
/* static */
already_AddRefed<gfx::Path> MotionPathUtils::BuildPath(
already_AddRefed<gfx::Path> MotionPathUtils::BuildSVGPath(
const StyleSVGPathData& aPath, gfx::PathBuilder* aPathBuilder) {
if (!aPathBuilder) {
return nullptr;
@ -493,6 +529,60 @@ already_AddRefed<gfx::Path> MotionPathUtils::BuildPath(
0.0);
}
/* static */
already_AddRefed<gfx::Path> MotionPathUtils::BuildPath(
const StyleBasicShape& aBasicShape,
const StyleOffsetPosition& aOffsetPosition, const nsRect& aCoordBox,
const nsPoint& aCurrentPosition, gfx::PathBuilder* aPathBuilder) {
if (!aPathBuilder) {
return nullptr;
}
switch (aBasicShape.tag) {
case StyleBasicShape::Tag::Circle: {
const nsPoint center =
ComputePosition(aBasicShape.AsCircle().position, aOffsetPosition,
aCoordBox, aCurrentPosition);
return ShapeUtils::BuildCirclePath(aBasicShape, aCoordBox, center,
AppUnitsPerCSSPixel(), aPathBuilder);
}
case StyleBasicShape::Tag::Ellipse: {
const nsPoint center =
ComputePosition(aBasicShape.AsEllipse().position, aOffsetPosition,
aCoordBox, aCurrentPosition);
return ShapeUtils::BuildEllipsePath(aBasicShape, aCoordBox, center,
AppUnitsPerCSSPixel(), aPathBuilder);
}
case StyleBasicShape::Tag::Inset:
return ShapeUtils::BuildInsetPath(aBasicShape, aCoordBox,
AppUnitsPerCSSPixel(), aPathBuilder);
case StyleBasicShape::Tag::Polygon:
return ShapeUtils::BuildPolygonPath(aBasicShape, aCoordBox,
AppUnitsPerCSSPixel(), aPathBuilder);
case StyleBasicShape::Tag::Path:
// FIXME: Bug 1836847. Once we support "at <position>" for path(), we have
// to also check its containing block as well. For now, we are still
// building its gfx::Path directly by its SVGPathData without other
// reference. https://github.com/w3c/fxtf-drafts/issues/504
return BuildSVGPath(aBasicShape.AsPath().path, aPathBuilder);
}
return nullptr;
}
/* static */
already_AddRefed<gfx::PathBuilder> MotionPathUtils::GetPathBuilder() {
// Here we only need to build a valid path for motion path, so
// using the default values of stroke-width, stoke-linecap, and fill-rule
// is fine for now because what we want is to get the point and its normal
// vector along the path, instead of rendering it.
RefPtr<gfx::PathBuilder> builder =
gfxPlatform::GetPlatform()
->ScreenReferenceDrawTarget()
->CreatePathBuilder(gfx::FillRule::FILL_WINDING);
return builder.forget();
}
/* static */
already_AddRefed<gfx::PathBuilder> MotionPathUtils::GetCompositorPathBuilder() {
// FIXME: Perhaps we need a PathBuilder which is independent on the backend.

View File

@ -45,6 +45,9 @@ struct OffsetPathData {
struct ShapeData {
RefPtr<gfx::Path> mGfxPath;
// The current position of this transfromed box in the coordinate system of
// its containing block.
nsPoint mCurrentPosition;
bool mIsClosedIntervals;
};
@ -66,11 +69,10 @@ struct OffsetPathData {
};
static OffsetPathData None() { return OffsetPathData(); }
static OffsetPathData Shape(const StyleSVGPathData& aPath,
already_AddRefed<gfx::Path>&& aGfxPath) {
const auto& path = aPath._0.AsSpan();
return OffsetPathData(std::move(aGfxPath),
!path.empty() && path.rbegin()->IsClosePath());
static OffsetPathData Shape(already_AddRefed<gfx::Path>&& aGfxPath,
nsPoint&& aCurrentPosition, bool aIsClosedPath) {
return OffsetPathData(std::move(aGfxPath), std::move(aCurrentPosition),
aIsClosedPath);
}
static OffsetPathData Ray(const StyleRayFunction& aRay, nsRect&& aCoordBox,
nsPoint&& aPosition,
@ -139,8 +141,10 @@ struct OffsetPathData {
private:
OffsetPathData() : mType(Type::None) {}
OffsetPathData(already_AddRefed<gfx::Path>&& aPath, bool aIsClosed)
: mType(Type::Shape), mShape{std::move(aPath), aIsClosed} {}
OffsetPathData(already_AddRefed<gfx::Path>&& aPath,
nsPoint&& aCurrentPosition, bool aIsClosed)
: mType(Type::Shape),
mShape{std::move(aPath), std::move(aCurrentPosition), aIsClosed} {}
OffsetPathData(const StyleRayFunction* aRay, nsRect&& aCoordBox,
nsPoint&& aPosition, CSSCoord&& aContainReferenceLength)
: mType(Type::Ray),
@ -217,11 +221,26 @@ class MotionPathUtils final {
TransformReferenceBox&, gfx::Path* aCachedMotionPath);
/**
* Build a gfx::Path from the computed svg path. We should give it a path
* builder. If |aPathBuilder| is nullptr, we return null path.
* */
static already_AddRefed<gfx::Path> BuildPath(const StyleSVGPathData& aPath,
gfx::PathBuilder* aPathBuilder);
* Build a gfx::Path from the svg path data. We should give it a path builder.
* If |aPathBuilder| is nullptr, we return null path.
* This can be used on the main thread or on the compositor thread.
*/
static already_AddRefed<gfx::Path> BuildSVGPath(
const StyleSVGPathData& aPath, gfx::PathBuilder* aPathBuilder);
/**
* Build a gfx::Path from the computed basic shape.
*/
static already_AddRefed<gfx::Path> BuildPath(const StyleBasicShape&,
const StyleOffsetPosition&,
const nsRect& aCoordBox,
const nsPoint& aCurrentPosition,
gfx::PathBuilder*);
/**
* Get a path builder for motion path on the main thread.
*/
static already_AddRefed<gfx::PathBuilder> GetPathBuilder();
/**
* Get a path builder for compositor.

View File

@ -1389,16 +1389,9 @@ void nsIFrame::DidSetComputedStyle(ComputedStyle* aOldComputedStyle) {
if (!oldPath || *oldPath != newPath) {
// FIXME: Bug 1837042. Cache all basic shapes.
if (newPath.IsPath()) {
// Here we only need to build a valid path for motion path, so
// using the default values of stroke-width, stoke-linecap, and fill-rule
// is fine for now because what we want is to get the point and its normal
// vector along the path, instead of rendering it.
RefPtr<gfx::PathBuilder> builder =
gfxPlatform::GetPlatform()
->ScreenReferenceDrawTarget()
->CreatePathBuilder(gfx::FillRule::FILL_WINDING);
RefPtr<gfx::PathBuilder> builder = MotionPathUtils::GetPathBuilder();
RefPtr<gfx::Path> path =
MotionPathUtils::BuildPath(newPath.AsSVGPathData(), builder);
MotionPathUtils::BuildSVGPath(newPath.AsSVGPathData(), builder);
if (path) {
// The newPath could be path('') (i.e. empty path), so its gfx path
// could be nullptr, and so we only set property for a non-empty path.

View File

@ -1,2 +0,0 @@
[offset-path-shape-circle-001.html]
expected: FAIL

View File

@ -1,2 +0,0 @@
[offset-path-shape-circle-002.html]
expected: FAIL

View File

@ -1,2 +0,0 @@
[offset-path-shape-circle-003.html]
expected: FAIL

View File

@ -1,2 +0,0 @@
[offset-path-shape-circle-004.html]
expected: FAIL

View File

@ -1,2 +0,0 @@
[offset-path-shape-circle-005.html]
expected: FAIL

View File

@ -1,2 +0,0 @@
[offset-path-shape-ellipse-001.html]
expected: FAIL

View File

@ -1,2 +0,0 @@
[offset-path-shape-ellipse-002.html]
expected: FAIL

View File

@ -1,2 +0,0 @@
[offset-path-shape-ellipse-003.html]
expected: FAIL

View File

@ -1,2 +0,0 @@
[offset-path-shape-ellipse-004.html]
expected: FAIL

View File

@ -1,2 +0,0 @@
[offset-path-shape-ellipse-005.html]
expected: FAIL

View File

@ -1,2 +0,0 @@
[offset-path-shape-inset-001.html]
expected: FAIL

View File

@ -1,2 +0,0 @@
[offset-path-shape-inset-002.html]
expected: FAIL

View File

@ -1,2 +0,0 @@
[offset-path-shape-polygon-001.html]
expected: FAIL

View File

@ -1,2 +0,0 @@
[offset-path-shape-polygon-002.html]
expected: FAIL

View File

@ -1,2 +0,0 @@
[offset-path-shape-polygon-003.html]
expected: FAIL

View File

@ -13,7 +13,8 @@
}
#box {
background-color: green;
transform: translate(250px, 350px);
transform: translate(250px, 350px) rotate(180deg);
border-radius: 50% 50% 0 0;
width: 100px;
height: 100px;
}

View File

@ -1,7 +1,7 @@
<!doctype html>
<meta charset="utf-8">
<title>CSS Motion Path test: &lt;basic-shape&gt; circle() path with explicit arguments</title>
<meta name=fuzzy content="0-1;0-200">
<meta name=fuzzy content="0-35;0-200">
<link rel="author" title="Daniil Sakhapov" href="sakhapov@google.com">
<link rel="match" href="offset-path-shape-circle-001-ref.html">
<link rel="help" href="https://drafts.fxtf.org/motion/#valdef-offset-path-basic-shape">
@ -19,6 +19,7 @@
position: relative;
offset-path: circle(closest-side at center);
offset-distance: 25%;
border-radius: 50% 50% 0 0;
width: 100px;
height: 100px;
}

View File

@ -15,6 +15,7 @@
background-color: green;
position: relative;
transform: translate(420.231px, 311.969px) rotate(154.813deg);
border-radius: 50% 50% 0 0;
width: 100px;
height: 100px;
}

View File

@ -1,7 +1,7 @@
<!doctype html>
<meta charset="utf-8">
<title>CSS Motion Path test: &lt;basic-shape&gt; circle() path with offset-distance</title>
<meta name=fuzzy content="0-10;0-20">
<meta name=fuzzy content="0-110;0-500">
<link rel="author" title="Daniil Sakhapov" href="sakhapov@google.com">
<link rel="match" href="offset-path-shape-circle-003-ref.html">
<link rel="help" href="https://drafts.fxtf.org/motion/#valdef-offset-path-basic-shape">
@ -19,6 +19,7 @@
position: relative;
offset-path: circle(farthest-side at top);
offset-distance: 18%;
border-radius: 50% 50% 0 0;
width: 100px;
height: 100px;
}

View File

@ -8,13 +8,14 @@
top: 100px;
left: 100px;
position: relative;
width: 600px;
width: 400px;
height: 400px;
}
#box {
background-color: green;
position: relative;
transform: translate(400px, 199px) translate(-50px, -50px);
transform: translate(200px, 160px);
border-radius: 50% 50% 0 0;
width: 100px;
height: 100px;
}

View File

@ -1,6 +1,7 @@
<!doctype html>
<meta charset="utf-8">
<title>CSS Motion Path test: &lt;basic-shape&gt; circle() path with offset-distance and offset-anchor</title>
<meta name=fuzzy content="0-80;0-310">
<link rel="author" title="Daniil Sakhapov" href="sakhapov@google.com">
<link rel="match" href="offset-path-shape-circle-004-ref.html">
<link rel="help" href="https://drafts.fxtf.org/motion/#valdef-offset-path-basic-shape">
@ -10,7 +11,7 @@
top: 100px;
left: 100px;
position: relative;
width: 600px;
width: 400px;
height: 400px;
}
#box {
@ -19,6 +20,7 @@
offset-path: circle(10% at bottom 25% right 25%);
offset-anchor: 100% 100%;
offset-distance: 75%;
border-radius: 50% 50% 0 0;
width: 100px;
height: 100px;
}

View File

@ -1,6 +1,7 @@
<!doctype html>
<meta charset="utf-8">
<title>CSS Motion Path test: &lt;basic-shape&gt; circle() path with offset-position</title>
<meta name=fuzzy content="0-18;0-400">
<link rel="author" title="Daniil Sakhapov" href="sakhapov@google.com">
<link rel="match" href="offset-path-shape-circle-005-ref.html">
<link rel="help" href="https://drafts.fxtf.org/motion/#valdef-offset-path-basic-shape">

View File

@ -13,7 +13,8 @@
}
#box {
background-color: green;
transform: translate(250px, 350px);
transform: translate(250px, 350px) rotate(180deg);
border-radius: 50% 50% 0 0;
width: 100px;
height: 100px;
}

View File

@ -1,7 +1,7 @@
<!doctype html>
<meta charset="utf-8">
<title>CSS Motion Path test: &lt;basic-shape&gt; ellipse() path with explicit arguments</title>
<meta name=fuzzy content="0-1;0-200">
<meta name=fuzzy content="0-24;0-200">
<link rel="author" title="Daniil Sakhapov" href="sakhapov@google.com">
<link rel="match" href="offset-path-shape-ellipse-001-ref.html">
<link rel="help" href="https://drafts.fxtf.org/motion/#valdef-offset-path-basic-shape">
@ -19,6 +19,7 @@
position: relative;
offset-path: ellipse(closest-side closest-side at center);
offset-distance: 25%;
border-radius: 50% 50% 0 0;
width: 100px;
height: 100px;
}

View File

@ -8,13 +8,14 @@
top: 100px;
left: 100px;
position: relative;
width: 600px;
height: 400px;
width: 400px;
height: 200px;
}
#box {
background-color: green;
position: relative;
transform: translate(393.93px, 300.959px) rotate(143.905deg);
transform: translate(8.6px, 91.4px) rotate(-135deg);
border-radius: 50% 50% 0 0;
width: 100px;
height: 100px;
}

View File

@ -1,6 +1,7 @@
<!doctype html>
<meta charset="utf-8">
<title>CSS Motion Path test: &lt;basic-shape&gt; ellipse() path with offset-distance</title>
<meta name=fuzzy content="0-25;0-450">
<link rel="author" title="Daniil Sakhapov" href="sakhapov@google.com">
<link rel="match" href="offset-path-shape-ellipse-003-ref.html">
<link rel="help" href="https://drafts.fxtf.org/motion/#valdef-offset-path-basic-shape">
@ -10,14 +11,15 @@
top: 100px;
left: 100px;
position: relative;
width: 600px;
height: 400px;
width: 400px;
height: 200px;
}
#box {
background-color: green;
position: relative;
offset-path: ellipse(farthest-side farthest-side at top);
offset-distance: 18%;
offset-distance: 37.5%;
border-radius: 50% 50% 0 0;
width: 100px;
height: 100px;
}

View File

@ -1,6 +1,7 @@
<!doctype html>
<meta charset="utf-8">
<title>CSS Motion Path test: &lt;basic-shape&gt; ellipse() path with offset-position</title>
<meta name=fuzzy content="0-18;0-400">
<link rel="author" title="Daniil Sakhapov" href="sakhapov@google.com">
<link rel="match" href="offset-path-shape-ellipse-005-ref.html">
<link rel="help" href="https://drafts.fxtf.org/motion/#valdef-offset-path-basic-shape">

View File

@ -8,12 +8,14 @@
top: 100px;
left: 100px;
position: relative;
width: 600px;
width: 400px;
height: 400px;
}
#box {
background-color: green;
transform: translate(512.786px, 283.444px) rotate(133.055deg) translate(50px, 50px);
transform: translate(306px, 94px) rotate(45deg);
transform-origin: 0 0;
border-radius: 50% 50% 0 0;
width: 100px;
height: 100px;
}

View File

@ -1,6 +1,7 @@
<!doctype html>
<meta charset="utf-8">
<title>CSS Motion Path test: &lt;basic-shape&gt; inset() path with explicit arguments and radius</title>
<meta name=fuzzy content="0-75;0-500">
<link rel="author" title="Daniil Sakhapov" href="sakhapov@google.com">
<link rel="match" href="offset-path-shape-inset-002-ref.html">
<link rel="help" href="https://drafts.fxtf.org/motion/#valdef-offset-path-basic-shape">
@ -10,15 +11,16 @@
top: 100px;
left: 100px;
position: relative;
width: 600px;
width: 400px;
height: 400px;
}
#box {
background-color: green;
position: relative;
offset-path: inset(10px 10px 10px 10px round 30%);
offset-path: inset(50px round 50%);
offset-anchor: 0 0;
offset-distance: 40%;
offset-distance: 12.5%;
border-radius: 50% 50% 0 0;
width: 100px;
height: 100px;
}

View File

@ -1,21 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>CSS Motion Path: path(basic-shape) paths</title>
<style>
#target {
position: absolute;
left: 300px;
top: 0px;
width: 300px;
height: 200px;
background-color: lime;
transform-origin: 0px 0px;
transform: translate(35px, 44px);
}
</style>
</head>
<body>
<div id="target"></div>
</body>
</html>