Bug 1823463 - Render clip-path:shape(). r=emilio

Update clip-path-shape-003.html and clip-path-shape-004.html because
1. Per SVG2 spec, we don't accept comma among commands, so I remove them.
2. Basically, these two tests want to test the result of `shape()`
   should be identical to the result of `path()`. However, I noticed the
   original tests which put a `clip-path:path()` with `position:absolutely`
   may have a fuzzy result if the path has some curves there. This may be
   caused by anti-alias together with absoultely positioned element
   (note: perhaps there are some floating point calculation in layout for
   this, so the final rendering coordinates may have some fractions).
   Therefore, I drop the absolutely positioned element, and just test
   that if the result of `shape()` is identical to the result of `path()`.

Also, add two more tests for different reference-boxes together with
the usage of `shape()` (to make sure we resolve percentage values properly).

Differential Revision: https://phabricator.services.mozilla.com/D202884
This commit is contained in:
Boris Chiou 2024-03-18 21:20:29 +00:00
Родитель 6605350dc6
Коммит 86adb0bd63
19 изменённых файлов: 175 добавлений и 87 удалений

Просмотреть файл

@ -556,11 +556,30 @@ already_AddRefed<Path> SVGPathData::BuildPathForMeasuring(
return BuildPath(aPath, builder, StyleStrokeLinecap::Butt, 0);
}
static inline StyleCSSFloat GetRotate(const StyleCSSFloat& aAngle) {
return aAngle;
}
static inline StyleCSSFloat GetRotate(const StyleAngle& aAngle) {
return aAngle.ToDegrees();
}
static inline StyleCSSFloat Resolve(const StyleCSSFloat& aValue,
CSSCoord aBasis) {
return aValue;
}
static inline StyleCSSFloat Resolve(const LengthPercentage& aValue,
CSSCoord aBasis) {
return aValue.ResolveToCSSPixels(aBasis);
}
template <typename Angle, typename LP>
static already_AddRefed<Path> BuildPathInternal(
Span<const StyleGenericShapeCommand<Angle, LP>> aPath,
PathBuilder* aBuilder, StyleStrokeLinecap aStrokeLineCap,
Float aStrokeWidth, const Point& aOffset, float aZoomFactor) {
Float aStrokeWidth, const CSSSize& aPercentageBasis, const Point& aOffset,
float aZoomFactor) {
using Command = StyleGenericShapeCommand<Angle, LP>;
if (aPath.IsEmpty() || !aPath[0].IsMove()) {
@ -609,14 +628,14 @@ static already_AddRefed<Path> BuildPathInternal(
break;
case Command::Tag::Move: {
maybeApproximateZeroLengthSubpathSquareCaps(prevSeg, seg);
const Point& p = cmd.move.point.ToGfxPoint();
const Point& p = cmd.move.point.ToGfxPoint(aPercentageBasis);
pathStart = segEnd = cmd.move.by_to == StyleByTo::To ? p : segStart + p;
aBuilder->MoveTo(scale(segEnd));
subpathHasLength = false;
break;
}
case Command::Tag::Line: {
const Point& p = cmd.line.point.ToGfxPoint();
const Point& p = cmd.line.point.ToGfxPoint(aPercentageBasis);
segEnd = cmd.line.by_to == StyleByTo::To ? p : segStart + p;
if (segEnd != segStart) {
subpathHasLength = true;
@ -625,9 +644,9 @@ static already_AddRefed<Path> BuildPathInternal(
break;
}
case Command::Tag::CubicCurve:
cp1 = cmd.cubic_curve.control1.ToGfxPoint();
cp2 = cmd.cubic_curve.control2.ToGfxPoint();
segEnd = cmd.cubic_curve.point.ToGfxPoint();
cp1 = cmd.cubic_curve.control1.ToGfxPoint(aPercentageBasis);
cp2 = cmd.cubic_curve.control2.ToGfxPoint(aPercentageBasis);
segEnd = cmd.cubic_curve.point.ToGfxPoint(aPercentageBasis);
if (cmd.cubic_curve.by_to == StyleByTo::By) {
cp1 += segStart;
@ -642,8 +661,8 @@ static already_AddRefed<Path> BuildPathInternal(
break;
case Command::Tag::QuadCurve:
cp1 = cmd.quad_curve.control1.ToGfxPoint();
segEnd = cmd.quad_curve.point.ToGfxPoint();
cp1 = cmd.quad_curve.control1.ToGfxPoint(aPercentageBasis);
segEnd = cmd.quad_curve.point.ToGfxPoint(aPercentageBasis);
if (cmd.quad_curve.by_to == StyleByTo::By) {
cp1 += segStart;
@ -662,8 +681,8 @@ static already_AddRefed<Path> BuildPathInternal(
case Command::Tag::Arc: {
const auto& arc = cmd.arc;
const Point& radii = arc.radii.ToGfxPoint();
segEnd = arc.point.ToGfxPoint();
const Point& radii = arc.radii.ToGfxPoint(aPercentageBasis);
segEnd = arc.point.ToGfxPoint(aPercentageBasis);
if (arc.by_to == StyleByTo::By) {
segEnd += segStart;
}
@ -674,8 +693,9 @@ static already_AddRefed<Path> BuildPathInternal(
} else {
const bool arc_is_large = arc.arc_size == StyleArcSize::Large;
const bool arc_is_cw = arc.arc_sweep == StyleArcSweep::Cw;
SVGArcConverter converter(segStart, segEnd, radii, arc.rotate,
arc_is_large, arc_is_cw);
SVGArcConverter converter(segStart, segEnd, radii,
GetRotate(arc.rotate), arc_is_large,
arc_is_cw);
while (converter.GetNextSegment(&cp1, &cp2, &segEnd)) {
aBuilder->BezierTo(scale(cp1), scale(cp2), scale(segEnd));
}
@ -683,11 +703,12 @@ static already_AddRefed<Path> BuildPathInternal(
}
break;
}
case Command::Tag::HLine:
case Command::Tag::HLine: {
const float x = Resolve(cmd.h_line.x, aPercentageBasis.width);
if (cmd.h_line.by_to == StyleByTo::To) {
segEnd = Point(cmd.h_line.x, segStart.y);
segEnd = Point(x, segStart.y);
} else {
segEnd = segStart + Point(cmd.h_line.x, 0.0f);
segEnd = segStart + Point(x, 0.0f);
}
if (segEnd != segStart) {
@ -695,12 +716,13 @@ static already_AddRefed<Path> BuildPathInternal(
aBuilder->LineTo(scale(segEnd));
}
break;
case Command::Tag::VLine:
}
case Command::Tag::VLine: {
const float y = Resolve(cmd.v_line.y, aPercentageBasis.height);
if (cmd.v_line.by_to == StyleByTo::To) {
segEnd = Point(segStart.x, cmd.v_line.y);
segEnd = Point(segStart.x, y);
} else {
segEnd = segStart + Point(0.0f, cmd.v_line.y);
segEnd = segStart + Point(0.0f, y);
}
if (segEnd != segStart) {
@ -708,11 +730,11 @@ static already_AddRefed<Path> BuildPathInternal(
aBuilder->LineTo(scale(segEnd));
}
break;
}
case Command::Tag::SmoothCubic:
cp1 = prevSeg && prevSeg->IsCubicType() ? segStart * 2 - cp2 : segStart;
cp2 = cmd.smooth_cubic.control2.ToGfxPoint();
segEnd = cmd.smooth_cubic.point.ToGfxPoint();
cp2 = cmd.smooth_cubic.control2.ToGfxPoint(aPercentageBasis);
segEnd = cmd.smooth_cubic.point.ToGfxPoint(aPercentageBasis);
if (cmd.smooth_cubic.by_to == StyleByTo::By) {
cp2 += segStart;
@ -731,7 +753,7 @@ static already_AddRefed<Path> BuildPathInternal(
// Convert quadratic curve to cubic curve:
tcp1 = segStart + (cp1 - segStart) * 2 / 3;
const Point& p = cmd.smooth_quad.point.ToGfxPoint();
const Point& p = cmd.smooth_quad.point.ToGfxPoint(aPercentageBasis);
// set before setting tcp2!
segEnd = cmd.smooth_quad.by_to == StyleByTo::To ? p : segStart + p;
tcp2 = cp1 + (segEnd - cp1) / 3;
@ -756,16 +778,22 @@ static already_AddRefed<Path> BuildPathInternal(
return aBuilder->Finish();
}
// We could simplify this function because this is only used by CSS motion path
// and clip-path, which don't render the SVG Path. i.e. The returned path is
// used as a reference.
/* static */
already_AddRefed<Path> SVGPathData::BuildPath(
Span<const StylePathCommand> aPath, PathBuilder* aBuilder,
StyleStrokeLinecap aStrokeLineCap, Float aStrokeWidth,
const gfx::Point& aOffset, float aZoomFactor) {
const CSSSize& aBasis, const gfx::Point& aOffset, float aZoomFactor) {
return BuildPathInternal(aPath, aBuilder, aStrokeLineCap, aStrokeWidth,
aOffset, aZoomFactor);
aBasis, aOffset, aZoomFactor);
}
/* static */
already_AddRefed<Path> SVGPathData::BuildPath(
Span<const StyleShapeCommand> aShape, PathBuilder* aBuilder,
StyleStrokeLinecap aStrokeLineCap, Float aStrokeWidth,
const CSSSize& aBasis, const gfx::Point& aOffset, float aZoomFactor) {
return BuildPathInternal(aShape, aBuilder, aStrokeLineCap, aStrokeWidth,
aBasis, aOffset, aZoomFactor);
}
static double AngleOfVector(const Point& aVector) {

Просмотреть файл

@ -190,14 +190,22 @@ class SVGPathData {
Span<const StylePathCommand> aPath);
/**
* This function tries to build the path from an array of StylePathCommand,
* This function tries to build the path from an array of GenericShapeCommand,
* which is generated by cbindgen from Rust (see ServoStyleConsts.h).
* Basically, this is a variant of the above BuildPath() functions.
* Note: |StylePathCommand| doesn't accept percentage values, so its |aBasis|
* is empty by default.
*/
static already_AddRefed<Path> BuildPath(
Span<const StylePathCommand> aPath, PathBuilder* aBuilder,
StyleStrokeLinecap aStrokeLineCap, Float aStrokeWidth,
const gfx::Point& aOffset = gfx::Point(), float aZoomFactor = 1.0);
const CSSSize& aBasis = {}, const gfx::Point& aOffset = gfx::Point(),
float aZoomFactor = 1.0);
static already_AddRefed<Path> BuildPath(
Span<const StyleShapeCommand> aShape, PathBuilder* aBuilder,
StyleStrokeLinecap aStrokeLineCap, Float aStrokeWidth,
const CSSSize& aBasis, const gfx::Point& aOffset = gfx::Point(),
float aZoomFactor = 1.0);
const_iterator begin() const { return mData.Elements(); }
const_iterator end() const { return mData.Elements() + mData.Length(); }

Просмотреть файл

@ -1202,10 +1202,19 @@ inline nsRect StyleZoom::Unzoom(const nsRect& aValue) const {
}
template <>
inline gfx::Point StyleCoordinatePair<StyleCSSFloat>::ToGfxPoint() const {
inline gfx::Point StyleCoordinatePair<StyleCSSFloat>::ToGfxPoint(
const CSSSize* aBasis) const {
return gfx::Point(x, y);
}
template <>
inline gfx::Point StyleCoordinatePair<LengthPercentage>::ToGfxPoint(
const CSSSize* aBasis) const {
MOZ_ASSERT(aBasis);
return gfx::Point(x.ResolveToCSSPixels(aBasis->Width()),
y.ResolveToCSSPixels(aBasis->Height()));
}
} // namespace mozilla
#endif

Просмотреть файл

@ -66,7 +66,7 @@ bool CSSClipPathInstance::HitTestBasicShapeOrPathClip(nsIFrame* aFrame,
RefPtr<Path> path = instance.CreateClipPath(
drawTarget, SVGUtils::GetCSSPxToDevPxMatrix(aFrame));
float pixelRatio = float(AppUnitsPerCSSPixel()) /
aFrame->PresContext()->AppUnitsPerDevPixel();
float(aFrame->PresContext()->AppUnitsPerDevPixel());
return path && path->ContainsPoint(ToPoint(aPoint) * pixelRatio, Matrix());
}
@ -124,8 +124,7 @@ already_AddRefed<Path> CSSClipPathInstance::CreateClipPath(
case StyleBasicShape::Tag::Path:
return CreateClipPathPath(aDrawTarget, r);
case StyleBasicShape::Tag::Shape:
// TODO: Support shape() in this patch series.
return nullptr;
return CreateClipPathShape(aDrawTarget, r);
default:
MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Unexpected shape type");
}
@ -183,13 +182,36 @@ already_AddRefed<Path> CSSClipPathInstance::CreateClipPathPath(
RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder(
path.fill == StyleFillRule::Nonzero ? FillRule::FILL_WINDING
: FillRule::FILL_EVEN_ODD);
nscoord appUnitsPerDevPixel =
const nscoord appUnitsPerDevPixel =
mTargetFrame->PresContext()->AppUnitsPerDevPixel();
float scale = float(AppUnitsPerCSSPixel()) / appUnitsPerDevPixel;
Point offset = Point(aRefBox.x, aRefBox.y) / appUnitsPerDevPixel;
const Point offset =
LayoutDevicePoint::FromAppUnits(aRefBox.TopLeft(), appUnitsPerDevPixel)
.ToUnknownPoint();
const float scale = float(AppUnitsPerCSSPixel()) / float(appUnitsPerDevPixel);
return SVGPathData::BuildPath(path.path._0.AsSpan(), builder,
StyleStrokeLinecap::Butt, 0.0, offset, scale);
StyleStrokeLinecap::Butt, 0.0, {}, offset,
scale);
}
already_AddRefed<Path> CSSClipPathInstance::CreateClipPathShape(
DrawTarget* aDrawTarget, const nsRect& aRefBox) {
const auto& shape = mClipPathStyle.AsShape()._0->AsShape();
RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder(
shape.fill == StyleFillRule::Nonzero ? FillRule::FILL_WINDING
: FillRule::FILL_EVEN_ODD);
const nscoord appUnitsPerDevPixel =
mTargetFrame->PresContext()->AppUnitsPerDevPixel();
const CSSSize basis = CSSSize::FromAppUnits(aRefBox.Size());
const Point offset =
LayoutDevicePoint::FromAppUnits(aRefBox.TopLeft(), appUnitsPerDevPixel)
.ToUnknownPoint();
const float scale = float(AppUnitsPerCSSPixel()) / float(appUnitsPerDevPixel);
return SVGPathData::BuildPath(shape.commands.AsSpan(), builder,
StyleStrokeLinecap::Butt, 0.0, basis, offset,
scale);
}
} // namespace mozilla

Просмотреть файл

@ -58,6 +58,9 @@ class MOZ_STACK_CLASS CSSClipPathInstance {
already_AddRefed<Path> CreateClipPathPath(DrawTarget* aDrawTarget,
const nsRect& aRefBox);
already_AddRefed<Path> CreateClipPathShape(DrawTarget* aDrawTarget,
const nsRect& aRefBox);
/**
* The frame for the element that is currently being clipped.
*/

Просмотреть файл

@ -214,6 +214,7 @@ include = [
"BasicShape",
"InsetRect",
"ShapeRadius",
"ShapeCommand",
"ArcSlice",
"ForgottenArcSlicePtr",
"HeaderWithLength",
@ -758,7 +759,10 @@ renaming_overrides_prefixing = true
"""
"CoordinatePair" = """
inline gfx::Point ToGfxPoint() const;
inline gfx::Point ToGfxPoint(const CSSSize* aBasis = nullptr) const;
gfx::Point ToGfxPoint(const CSSSize& aBasis) const {
return ToGfxPoint(&aBasis);
};
"""
"TextOverflow" = """

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

@ -8,15 +8,6 @@
'shape()' for clipping. Test curves.">
</head>
<style>
div {
width: 100px;
height: 100px;
}
#ref {
clip-path: path(nonzero, "M 10 10, Q 40 0 60 20, T 90 0, c 10 40 20 20 -20 60, s -10 70 -40 -10");
background-color: red;
position: absolute;
}
#rect {
width: 100px;
height: 100px;
@ -29,8 +20,6 @@
}
</style>
<body>
<p>You should see no red.</p>
<div id="ref"></div>
<div id="rect"></div>
</body>
</html>

Просмотреть файл

@ -8,15 +8,6 @@
'shape()' for clipping. Test arcs.">
</head>
<style>
div {
width: 100px;
height: 100px;
}
#ref {
clip-path: path(nonzero, "M 20 20 A 25 12 0 0 1 80 20, a 33 33 120 1 1 -40 50, A 20 25 0 0 0 20 20");
background-color: red;
position: absolute;
}
#rect {
width: 100px;
height: 100px;
@ -28,8 +19,6 @@
}
</style>
<body>
<p>You should see no red.</p>
<div id="ref"></div>
<div id="rect"></div>
</body>
</html>

Просмотреть файл

@ -0,0 +1,29 @@
<!DOCTYPE html>
<html>
<head>
<title>CSS Masking: Test clip-path property and shape function with padding-box</title>
<link rel="help" href="https://drafts.csswg.org/css-shapes-2/#funcdef-shape">
<link rel="match" href="reference/clip-path-path-001-ref.html">
<meta name="assert" content="The clip-path property takes the basic shape
'shape()' for clipping. Test the usage of the reference box. On pass you
should see a green square.">
</head>
<style>
#rect {
/* The size of the padding-box is 100x100. */
width: 120px;
height: 120px;
padding: 10px;
border: 10px solid red;
box-sizing: border-box;
background-color: green;
clip-path: shape(from 0px 0px,
hline by 80px, vline by 80%, hline by -80%, close)
padding-box;
}
</style>
<body>
<p>The test passes if there are a green filled rect.</p>
<div id="rect"></div>
</body>
</html>

Просмотреть файл

@ -0,0 +1,29 @@
<!DOCTYPE html>
<html>
<head>
<title>CSS Masking: Test clip-path property and shape function with content-box</title>
<link rel="help" href="https://drafts.csswg.org/css-shapes-2/#funcdef-shape">
<link rel="match" href="reference/clip-path-path-001-ref.html">
<meta name="assert" content="The clip-path property takes the basic shape
'shape()' for clipping. Test the usage of the reference box. On pass you
should see a green square.">
</head>
<style>
#rect {
width: 140px;
height: 140px;
padding: 10px;
border: 10px solid red;
box-sizing: border-box;
background-color: green;
/* The size of the content-box is 100x100. */
clip-path: shape(from -10px -10%,
hline by 80px, vline by 80%, hline by -80%, close)
content-box;
}
</style>
<body>
<p>The test passes if there are a green filled rect.</p>
<div id="rect"></div>
</body>
</html>

Просмотреть файл

@ -5,18 +5,14 @@
<link rel="help" href="https://drafts.csswg.org/css-shapes-2/#funcdef-shape">
</head>
<style>
div {
#ref {
width: 100px;
height: 100px;
}
#ref {
background-color: green;
clip-path: path(nonzero, "M 10 10, Q 40 0 60 20, T 90 0, c 10 40 20 20 -20 60, s -10 70 -40 -10");
position: absolute;
clip-path: path(nonzero, "M 10 10 Q 40 0 60 20 T 90 0 c 10 40 20 20 -20 60 s -10 70 -40 -10");
}
</style>
<body>
<p>You should see no red.</p>
<div id="ref"></div>
</body>
</html>

Просмотреть файл

@ -5,18 +5,14 @@
<link rel="help" href="https://drafts.csswg.org/css-shapes-2/#funcdef-shape">
</head>
<style>
div {
#ref {
width: 100px;
height: 100px;
}
#ref {
background-color: green;
clip-path: path(nonzero, "M 20 20 A 25 12 0 0 1 80 20, a 33 33 120 1 1 -40 50, A 20 25 0 0 0 20 20");
position: absolute;
clip-path: path(nonzero, "M 20 20 A 25 12 0 0 1 80 20 a 33 33 120 1 1 -40 50 A 20 25 0 0 0 20 20");
}
</style>
<body>
<p>You should see no red.</p>
<div id="ref"></div>
</body>
</html>