Skip to content

Commit

Permalink
FloatRoundedRect::OutsetForShapeMargin()
Browse files Browse the repository at this point in the history
It's different from Outset() in that it can make sharp corners
(radius=0) rounded (radius=shape_margin). This conforms to the
spec [1]:

This defines a new shape that is the smallest contour (in the
shrink-wrap sense) that includes all the points that are the
shape-margin distance outward in the perpendicular direction from
a point on the underlying shape.

[1] https://drafts.csswg.org/css-shapes/#shape-margin-property

Bug: 1305037
Change-Id: Iaafae94209a5413d0718076919c570a135f66ac1
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3516733
Reviewed-by: David Baron <dbaron@chromium.org>
Commit-Queue: Xianzhu Wang <wangxianzhu@chromium.org>
Cr-Commit-Position: refs/heads/main@{#984242}
  • Loading branch information
wangxianzhu authored and Chromium LUCI CQ committed Mar 23, 2022
1 parent ee2175f commit 50f523f
Show file tree
Hide file tree
Showing 11 changed files with 313 additions and 26 deletions.
6 changes: 1 addition & 5 deletions third_party/blink/renderer/core/layout/shapes/box_shape.cc
Expand Up @@ -42,12 +42,8 @@ LayoutRect BoxShape::ShapeMarginLogicalBoundingBox() const {

FloatRoundedRect BoxShape::ShapeMarginBounds() const {
FloatRoundedRect margin_bounds = bounds_;
// TODO(crbug.com/1305037): This is different from other callers of Outset(),
// because it always wants expansion by radial distance (always produces
// rounding) rather than the rules used for other cases of rounded rect
// expansion (with sharp corner reservation / cubic reduction of the radius).
if (ShapeMargin() > 0)
margin_bounds.Outset(ShapeMargin());
margin_bounds.OutsetForShapeMargin(ShapeMargin());
return margin_bounds;
}

Expand Down
48 changes: 31 additions & 17 deletions third_party/blink/renderer/core/layout/shapes/box_shape_test.cc
Expand Up @@ -54,8 +54,8 @@ namespace {
LineSegment segment = shapePtr->GetExcludedInterval(lineTop, lineHeight); \
EXPECT_TRUE(segment.is_valid); \
if (segment.is_valid) { \
EXPECT_FLOAT_EQ(expectedLeft, segment.logical_left); \
EXPECT_FLOAT_EQ(expectedRight, segment.logical_right); \
EXPECT_EQ(expectedLeft, segment.logical_left); \
EXPECT_EQ(expectedRight, segment.logical_right); \
} \
}

Expand All @@ -66,13 +66,14 @@ namespace {
}

/* The BoxShape is based on a 100x50 rectangle at 0,0. The shape-margin value is
* 10, so the shapeMarginBoundingBox rectangle is 120x70 at -10,-10:
* 10, so the shape is a rectangle (120x70 at -10,-10) with rounded corners
* (radius=10):
*
* -10,-10 110,-10
* +--------+
* (--------)
* | |
* +--------+
* -10,60 60,60
* (--------)
* -10,60 110,60
*/
TEST_F(BoxShapeTest, zeroRadii) {
std::unique_ptr<Shape> shape =
Expand Down Expand Up @@ -104,11 +105,18 @@ TEST_F(BoxShapeTest, zeroRadii) {
EXPECT_FALSE(
shape->LineOverlapsShapeMarginBounds(LayoutUnit(100), LayoutUnit(200)));

TEST_EXCLUDED_INTERVAL(shape, LayoutUnit(-9), LayoutUnit(1), -10, 110);
TEST_EXCLUDED_INTERVAL(shape, LayoutUnit(-10), LayoutUnit(), -10, 110);
TEST_EXCLUDED_INTERVAL(shape, LayoutUnit(-10), LayoutUnit(200), -10, 110);
TEST_EXCLUDED_INTERVAL(shape, LayoutUnit(5), LayoutUnit(10), -10, 110);
TEST_EXCLUDED_INTERVAL(shape, LayoutUnit(59), LayoutUnit(1), -10, 110);
TEST_EXCLUDED_INTERVAL(shape, LayoutUnit(-9), LayoutUnit(1), LayoutUnit(-6),
LayoutUnit(106));
TEST_EXCLUDED_INTERVAL(shape, LayoutUnit(-10), LayoutUnit(), LayoutUnit(0),
LayoutUnit(100));
TEST_EXCLUDED_INTERVAL(shape, LayoutUnit(-10), LayoutUnit(200),
LayoutUnit(-10), LayoutUnit(110));
TEST_EXCLUDED_INTERVAL(shape, LayoutUnit(5), LayoutUnit(10), LayoutUnit(-10),
LayoutUnit(110));
// 4.34375 is the LayoutUnit value of -sqrt(19).
// 104.34375 is the LayoutUnit value of 100 + sqrt(19).
TEST_EXCLUDED_INTERVAL(shape, LayoutUnit(59), LayoutUnit(1),
LayoutUnit(-4.34375), LayoutUnit(104.34375));

TEST_NO_EXCLUDED_INTERVAL(shape, LayoutUnit(-12), LayoutUnit(2));
TEST_NO_EXCLUDED_INTERVAL(shape, LayoutUnit(60), LayoutUnit(1));
Expand Down Expand Up @@ -138,12 +146,18 @@ TEST_F(BoxShapeTest, getIntervals) {

EXPECT_EQ(LayoutRect(0, 0, 100, 100), shape->ShapeMarginLogicalBoundingBox());

TEST_EXCLUDED_INTERVAL(shape, LayoutUnit(10), LayoutUnit(95), 0, 100);
TEST_EXCLUDED_INTERVAL(shape, LayoutUnit(5), LayoutUnit(25), 0, 100);
TEST_EXCLUDED_INTERVAL(shape, LayoutUnit(15), LayoutUnit(6), 0, 100);
TEST_EXCLUDED_INTERVAL(shape, LayoutUnit(20), LayoutUnit(50), 0, 100);
TEST_EXCLUDED_INTERVAL(shape, LayoutUnit(69), LayoutUnit(5), 0, 100);
TEST_EXCLUDED_INTERVAL(shape, LayoutUnit(85), LayoutUnit(10), 0, 97.3125f);
TEST_EXCLUDED_INTERVAL(shape, LayoutUnit(10), LayoutUnit(95), LayoutUnit(0),
LayoutUnit(100));
TEST_EXCLUDED_INTERVAL(shape, LayoutUnit(5), LayoutUnit(25), LayoutUnit(0),
LayoutUnit(100));
TEST_EXCLUDED_INTERVAL(shape, LayoutUnit(15), LayoutUnit(6), LayoutUnit(0),
LayoutUnit(100));
TEST_EXCLUDED_INTERVAL(shape, LayoutUnit(20), LayoutUnit(50), LayoutUnit(0),
LayoutUnit(100));
TEST_EXCLUDED_INTERVAL(shape, LayoutUnit(69), LayoutUnit(5), LayoutUnit(0),
LayoutUnit(100));
TEST_EXCLUDED_INTERVAL(shape, LayoutUnit(85), LayoutUnit(10), LayoutUnit(0),
LayoutUnit(97.3125f));
}

} // anonymous namespace
Expand Down
18 changes: 18 additions & 0 deletions third_party/blink/renderer/platform/geometry/float_rounded_rect.cc
Expand Up @@ -146,6 +146,16 @@ void FloatRoundedRect::Radii::OutsetForMarginOrShadow(float outset) {
OutsetCornerForMarginOrShadow(bottom_right_, outset);
}

void FloatRoundedRect::Radii::OutsetForShapeMargin(float outset) {
// We're not sure the following is fully correct for non-circular corners,
// but it's definitely close.
gfx::SizeF outset_size(outset, outset);
top_left_ += outset_size;
top_right_ += outset_size;
bottom_left_ += outset_size;
bottom_right_ += outset_size;
}

static inline float CornerRectIntercept(float y,
const gfx::RectF& corner_rect) {
DCHECK_GT(corner_rect.height(), 0);
Expand Down Expand Up @@ -225,6 +235,14 @@ void FloatRoundedRect::OutsetForMarginOrShadow(float size) {
radii_.OutsetForMarginOrShadow(size);
}

void FloatRoundedRect::OutsetForShapeMargin(float outset) {
DCHECK_GE(outset, 0);
if (outset == 0.f)
return;
rect_.Outset(outset);
radii_.OutsetForShapeMargin(outset);
}

bool FloatRoundedRect::IntersectsQuad(const gfx::QuadF& quad) const {
if (!quad.IntersectsRect(rect_))
return false;
Expand Down
Expand Up @@ -104,10 +104,8 @@ class PLATFORM_EXPORT FloatRoundedRect {
friend class FloatRoundedRect;
void Scale(float factor);
void Outset(const gfx::OutsetsF& outsets);

// Inflates the corners based on the algorithm in
// https://drafts.csswg.org/css-backgrounds-3/#shadow-shape.
void OutsetForMarginOrShadow(float outset);
void OutsetForShapeMargin(float outset);

gfx::SizeF top_left_;
gfx::SizeF top_right_;
Expand Down Expand Up @@ -159,6 +157,14 @@ class PLATFORM_EXPORT FloatRoundedRect {
// (https://github.com/w3c/csswg-drafts/issues/7103).
void OutsetForMarginOrShadow(float outset);

// Inflates the rounded rect by the specified amount on each side and corner
// for shape-margin. |outset| must be non-negative. This is different from
// other outset methods in that it always expands by radial distance (always
// produces rounding) rather than following rules for sharp corner
// preservation and cubic reduction of the radius. See
// https://drafts.csswg.org/css-shapes/#shape-margin-property.
void OutsetForShapeMargin(float outset);

// Returns a quickly computed rect enclosed by the rounded rect.
gfx::RectF RadiusCenterRect() const;

Expand Down
Expand Up @@ -352,6 +352,21 @@ TEST(FloatRoundedRectTest, InsetToBeNonRenderable) {
EXPECT_EQ(pie, small_pie);
}

TEST(FloatRoundedRectTest, OutsetForShapeMargin) {
FloatRoundedRect r(gfx::RectF(0, 0, 100, 100), gfx::SizeF(5, 10),
gfx::SizeF(15, 0), gfx::SizeF(0, 30), gfx::SizeF(0, 0));
r.OutsetForShapeMargin(0);
EXPECT_EQ(
FloatRoundedRect(gfx::RectF(0, 0, 100, 100), gfx::SizeF(5, 10),
gfx::SizeF(15, 0), gfx::SizeF(0, 30), gfx::SizeF(0, 0)),
r);
r.OutsetForShapeMargin(5);
EXPECT_EQ(
FloatRoundedRect(gfx::RectF(-5, -5, 110, 110), gfx::SizeF(10, 15),
gfx::SizeF(20, 5), gfx::SizeF(5, 35), gfx::SizeF(5, 5)),
r);
}

TEST(FloatRoundedRectTest, ToString) {
gfx::SizeF corner_rect(1, 2);
FloatRoundedRect rounded_rect(
Expand Down
@@ -0,0 +1,11 @@
<!DOCTYPE html>
<style>
#shape {
margin-left: 25px;
width: 200px;
height: 200px;
background-color: green;
}
</style>
<p>The test passes if there is a green square and no red.</p>
<div id="shape"></div>
@@ -0,0 +1,54 @@
<!DOCTYPE html>
<title>CSS Test: right float, shape-outside:border-box and shape-margin</title>
<link rel="author" title="Xianzhu Wang" href="mailto:wangxianzhu@chromium.org">
<link rel="help" href="http://www.w3.org/TR/css-shapes-1/#funcdef-inset">
<link rel="help" href="http://www.w3.org/TR/css-shapes-1/#shape-outside-property">
<link rel="help" href="http://www.w3.org/TR/css-shapes-1/#shape-margin-property">
<link rel="match" href="reference/shape-outside-padding-box-003-ref.html"/>
<meta name="flags" content="ahem" />
<meta name="assert" content="The test verfies a shape with shape-outside:border-box
and shape-margin has rounded corners.">
<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
<style>
#container {
position: relative;
width: 200px;
height: 200px;
overflow: hidden;
margin-left: 25px;
}
#test-container {
/* Allow at most 1.5 glyphs to overflow the left edge of #container. */
margin-left: -30px;
width: 230px;
height: 200px;
font: 20px/1 Ahem;
background-color: red;
color: green;
text-align: right;
}
#test-shape {
float: right;
width: 15px;
height: 10px;
margin: 95px 65px 95px 120px;
shape-margin: 70px;
shape-outside: border-box;
}
#static-shape {
position: absolute;
left: 50px;
top: 20px;
width: 150px;
height: 160px;
background-color: green;
}
</style>
<p>The test passes if there is a green square and no red.</p>
<div id="container">
<div id="test-container">
<div id="test-shape"></div>
XXXXXXXXXX XXXXX XXXX XXX XXX XXX XXX XXXX XXXXX XXXXXXXXXX
<div id="static-shape"></div>
</div>
</div>
@@ -0,0 +1,56 @@
<!DOCTYPE html>
<title>CSS Test: right float, shape-outside:content-box and shape-margin</title>
<link rel="author" title="Xianzhu Wang" href="mailto:wangxianzhu@chromium.org">
<link rel="help" href="http://www.w3.org/TR/css-shapes-1/#funcdef-inset">
<link rel="help" href="http://www.w3.org/TR/css-shapes-1/#shape-outside-property">
<link rel="help" href="http://www.w3.org/TR/css-shapes-1/#shape-margin-property">
<link rel="match" href="reference/shape-outside-padding-box-003-ref.html"/>
<meta name="flags" content="ahem" />
<meta name="assert" content="The test verfies a shape with shape-outside:content-box
and shape-margin has rounded corners.">
<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
<style>
#container {
position: relative;
width: 200px;
height: 200px;
overflow: hidden;
margin-left: 25px;
}
#test-container {
/* Allow at most 1.5 glyphs to overflow the left edge of #container. */
margin-left: -30px;
width: 230px;
height: 200px;
font: 20px/1 Ahem;
color: green;
background-color: red;
text-align: right;
}
#test-shape {
margin-left: 30px;
float: right;
box-sizing: border-box;
width: 200px;
height: 200px;
padding: 95px 40px 95px 120px;
shape-margin: 70px;
shape-outside: content-box;
}
#static-shape {
position: absolute;
left: 50px;
top: 20px;
width: 150px;
height: 160px;
background-color: green;
}
</style>
<p>The test passes if there is a green square and no red.</p>
<div id="container">
<div id="test-container">
<div id="test-shape"></div>
XXXXXXXXXX XXXXX XXXX XXX XXX XXX XXX XXXX XXXXX XXXXXXXXXX
<div id="static-shape"></div>
</div>
</div>
@@ -0,0 +1,58 @@
<!DOCTYPE html>
<title>CSS Test: right float, shape-outside:padding-box and shape-margin</title>
<link rel="author" title="Xianzhu Wang" href="mailto:wangxianzhu@chromium.org">
<link rel="help" href="http://www.w3.org/TR/css-shapes-1/#funcdef-inset">
<link rel="help" href="http://www.w3.org/TR/css-shapes-1/#shape-outside-property">
<link rel="help" href="http://www.w3.org/TR/css-shapes-1/#shape-margin-property">
<link rel="match" href="reference/shape-outside-padding-box-003-ref.html"/>
<meta name="flags" content="ahem" />
<meta name="assert" content="The test verfies a shape with shape-outside:padding-box
and shape-margin has rounded corners.">
<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
<style>
#container {
position: relative;
width: 200px;
height: 200px;
overflow: hidden;
margin-left: 25px;
}
#test-container {
/* Allow at most 1.5 glyphs to overflow the left edge of #container. */
margin-left: -30px;
width: 230px;
height: 200px;
font: 20px/1 Ahem;
background-color: red;
color: green;
text-align: right;
}
#test-shape {
margin-left: 30px;
float: right;
box-sizing: border-box;
width: 200px;
height: 200px;
border-width: 95px 40px 95px 120px;
border-style: solid;
border-color: transparent;
shape-margin: 70px;
shape-outside: padding-box;
}
#static-shape {
position: absolute;
left: 50px;
top: 20px;
width: 150px;
height: 160px;
background-color: green;
}
</style>
<p>The test passes if there is a green square and no red.</p>
<div id="container">
<div id="test-container">
<div id="test-shape"></div>
XXXXXXXXXX XXXXX XXXX XXX XXX XXX XXX XXXX XXXXX XXXXXXXXXX
<div id="static-shape"></div>
</div>
</div>
Expand Up @@ -31,7 +31,6 @@
float: right;
width: 200px;
height: 200px;
background-color: green;
shape-margin: 10px;
shape-outside: inset(60px 10px 60px 110px round 20px);
}
Expand Down

0 comments on commit 50f523f

Please sign in to comment.