Skip to content

Commit d493219

Browse files
kalenikaliaksandrawesomekling
authored andcommitted
LibWeb: Account for all clipped border radii in containing block chain
With this change, instead of applying only the border-radius clipping from the closest containing block with hidden overflow, we now collect all boxes within the containing block chain and apply the clipping from all of them.
1 parent 1ae416f commit d493219

9 files changed

+133
-32
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<!DOCTYPE html>
2+
<link rel="match" href="reference/nested-boxes-with-hidden-overflow-and-border-radius-ref.html" />
3+
<style>
4+
.outer {
5+
overflow: hidden;
6+
border-radius: 100px;
7+
background-color: magenta;
8+
width: 500px;
9+
height: 500px;
10+
}
11+
12+
.middle {
13+
overflow: hidden;
14+
border-radius: 50px;
15+
transform: translate(10px, 10px);
16+
background-color: lawngreen;
17+
}
18+
19+
.inner {
20+
width: 100px;
21+
height: 100px;
22+
background-color: black;
23+
transform: translate(10px, 10px);
24+
}
25+
</style>
26+
<div class="outer">
27+
<div class="middle">
28+
<div class="inner"></div>
29+
</div>
30+
</div>
9.99 KB
Loading
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<style>
2+
* {
3+
margin: 0;
4+
}
5+
6+
body {
7+
background-color: white;
8+
}
9+
</style>
10+
<img src="./images/nested-boxes-with-hidden-overflow-and-border-radius-ref.png">

Userland/Libraries/LibWeb/Painting/BorderRadiiData.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ struct BorderRadiusData {
3131
if (vertical_radius != 0)
3232
vertical_radius = max(CSSPixels(0), vertical_radius - vertical);
3333
}
34+
35+
inline void union_max_radii(BorderRadiusData const& other)
36+
{
37+
horizontal_radius = max(horizontal_radius, other.horizontal_radius);
38+
vertical_radius = max(vertical_radius, other.vertical_radius);
39+
}
3440
};
3541

3642
using CornerRadius = Gfx::AntiAliasingPainter::CornerRadius;
@@ -58,6 +64,14 @@ struct BorderRadiiData {
5864
return top_left || top_right || bottom_right || bottom_left;
5965
}
6066

67+
inline void union_max_radii(BorderRadiiData const& other)
68+
{
69+
top_left.union_max_radii(other.top_left);
70+
top_right.union_max_radii(other.top_right);
71+
bottom_right.union_max_radii(other.bottom_right);
72+
bottom_left.union_max_radii(other.bottom_left);
73+
}
74+
6175
inline void shrink(CSSPixels top, CSSPixels right, CSSPixels bottom, CSSPixels left)
6276
{
6377
top_left.shrink(left, top);
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright (c) 2024, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
3+
*
4+
* SPDX-License-Identifier: BSD-2-Clause
5+
*/
6+
7+
#pragma once
8+
9+
#include <LibWeb/Painting/BorderRadiiData.h>
10+
#include <LibWeb/PixelUnits.h>
11+
12+
namespace Web::Painting {
13+
14+
struct BorderRadiiClip {
15+
CSSPixelRect rect;
16+
BorderRadiiData radii;
17+
};
18+
19+
struct ClipFrame : public RefCounted<ClipFrame> {
20+
Vector<BorderRadiiClip> const& border_radii_clips() const { return m_border_radii_clips; }
21+
void add_border_radii_clip(BorderRadiiClip border_radii_clip)
22+
{
23+
for (auto& existing_clip : m_border_radii_clips) {
24+
if (border_radii_clip.rect == existing_clip.rect) {
25+
existing_clip.radii.union_max_radii(border_radii_clip.radii);
26+
return;
27+
}
28+
}
29+
m_border_radii_clips.append(border_radii_clip);
30+
}
31+
32+
CSSPixelRect rect() const { return m_rect; }
33+
void set_rect(CSSPixelRect rect) { m_rect = rect; }
34+
35+
private:
36+
CSSPixelRect m_rect;
37+
Vector<BorderRadiiClip> m_border_radii_clips;
38+
};
39+
40+
}

Userland/Libraries/LibWeb/Painting/InlinePaintable.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ Optional<CSSPixelPoint> InlinePaintable::enclosing_scroll_frame_offset() const
4444
Optional<CSSPixelRect> InlinePaintable::clip_rect() const
4545
{
4646
if (m_enclosing_clip_frame)
47-
return m_enclosing_clip_frame->rect;
47+
return m_enclosing_clip_frame->rect();
4848
return {};
4949
}
5050

Userland/Libraries/LibWeb/Painting/PaintableBox.cpp

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -214,14 +214,14 @@ Optional<CSSPixelPoint> PaintableBox::enclosing_scroll_frame_offset() const
214214
Optional<CSSPixelRect> PaintableBox::clip_rect() const
215215
{
216216
if (m_enclosing_clip_frame)
217-
return m_enclosing_clip_frame->rect;
217+
return m_enclosing_clip_frame->rect();
218218
return {};
219219
}
220220

221-
Optional<BorderRadiiData> PaintableBox::corner_clip_radii() const
221+
Span<BorderRadiiClip const> PaintableBox::border_radii_clips() const
222222
{
223223
if (m_enclosing_clip_frame)
224-
return m_enclosing_clip_frame->corner_clip_radii;
224+
return m_enclosing_clip_frame->border_radii_clips();
225225
return {};
226226
}
227227

@@ -444,12 +444,17 @@ void PaintableBox::apply_clip_overflow_rect(PaintContext& context, PaintPhase ph
444444
m_clipping_overflow = true;
445445
context.recording_painter().save();
446446
context.recording_painter().add_clip_rect(context.enclosing_device_rect(overflow_clip_rect).to_type<int>());
447-
if (corner_clip_radii().has_value()) {
448-
VERIFY(!m_corner_clipper_id.has_value());
449-
m_corner_clipper_id = context.allocate_corner_clipper_id();
450-
auto corner_radii = corner_clip_radii()->as_corners(context);
451-
if (corner_radii.has_any_radius())
452-
context.recording_painter().sample_under_corners(*m_corner_clipper_id, corner_clip_radii()->as_corners(context), context.rounded_device_rect(overflow_clip_rect).to_type<int>(), CornerClip::Outside);
447+
auto const& border_radii_clips = this->border_radii_clips();
448+
m_corner_clipper_ids.resize(border_radii_clips.size());
449+
for (size_t corner_clip_index = 0; corner_clip_index < border_radii_clips.size(); ++corner_clip_index) {
450+
auto const& corner_clip = border_radii_clips[corner_clip_index];
451+
auto corners = corner_clip.radii.as_corners(context);
452+
if (!corners.has_any_radius())
453+
continue;
454+
auto corner_clipper_id = context.allocate_corner_clipper_id();
455+
m_corner_clipper_ids[corner_clip_index] = corner_clipper_id;
456+
auto rect = corner_clip.rect.translated(-combined_transform.translation().to_type<CSSPixels>());
457+
context.recording_painter().sample_under_corners(corner_clipper_id, corner_clip.radii.as_corners(context), context.rounded_device_rect(rect).to_type<int>(), CornerClip::Outside);
453458
}
454459
}
455460
}
@@ -461,12 +466,17 @@ void PaintableBox::clear_clip_overflow_rect(PaintContext& context, PaintPhase ph
461466

462467
if (m_clipping_overflow) {
463468
m_clipping_overflow = false;
464-
if (corner_clip_radii().has_value()) {
465-
VERIFY(m_corner_clipper_id.has_value());
466-
auto corner_radii = corner_clip_radii()->as_corners(context);
467-
if (corner_radii.has_any_radius())
468-
context.recording_painter().blit_corner_clipping(*m_corner_clipper_id, context.rounded_device_rect(*clip_rect()).to_type<int>());
469-
m_corner_clipper_id = {};
469+
auto combined_transform = compute_combined_css_transform();
470+
auto const& border_radii_clips = this->border_radii_clips();
471+
for (size_t corner_clip_index = 0; corner_clip_index < border_radii_clips.size(); ++corner_clip_index) {
472+
auto const& corner_clip = border_radii_clips[corner_clip_index];
473+
auto corners = corner_clip.radii.as_corners(context);
474+
if (!corners.has_any_radius())
475+
continue;
476+
auto corner_clipper_id = m_corner_clipper_ids[corner_clip_index];
477+
m_corner_clipper_ids[corner_clip_index] = corner_clipper_id;
478+
auto rect = corner_clip.rect.translated(-combined_transform.translation().to_type<CSSPixels>());
479+
context.recording_painter().blit_corner_clipping(corner_clipper_id, context.rounded_device_rect(rect).to_type<int>());
470480
}
471481
context.recording_painter().restore();
472482
}

Userland/Libraries/LibWeb/Painting/PaintableBox.h

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#include <LibWeb/Painting/BorderPainting.h>
1010
#include <LibWeb/Painting/BorderRadiusCornerClipper.h>
11+
#include <LibWeb/Painting/ClipFrame.h>
1112
#include <LibWeb/Painting/Paintable.h>
1213
#include <LibWeb/Painting/PaintableFragment.h>
1314
#include <LibWeb/Painting/ShadowPainting.h>
@@ -19,11 +20,6 @@ struct ScrollFrame : public RefCounted<ScrollFrame> {
1920
CSSPixelPoint offset;
2021
};
2122

22-
struct ClipFrame : public RefCounted<ClipFrame> {
23-
CSSPixelRect rect;
24-
Optional<BorderRadiiData> corner_clip_radii;
25-
};
26-
2723
class PaintableBox : public Paintable {
2824
JS_CELL(PaintableBox, Paintable);
2925

@@ -210,7 +206,7 @@ class PaintableBox : public Paintable {
210206
Optional<int> scroll_frame_id() const;
211207
Optional<CSSPixelPoint> enclosing_scroll_frame_offset() const;
212208
Optional<CSSPixelRect> clip_rect() const;
213-
Optional<BorderRadiiData> corner_clip_radii() const;
209+
Span<BorderRadiiClip const> border_radii_clips() const;
214210

215211
protected:
216212
explicit PaintableBox(Layout::Box const&);
@@ -235,7 +231,7 @@ class PaintableBox : public Paintable {
235231
Optional<CSSPixelRect> mutable m_absolute_paint_rect;
236232

237233
mutable bool m_clipping_overflow { false };
238-
mutable Optional<u32> m_corner_clipper_id;
234+
mutable Vector<u32> m_corner_clipper_ids;
239235

240236
RefPtr<ScrollFrame const> m_enclosing_scroll_frame;
241237
RefPtr<ClipFrame const> m_enclosing_clip_frame;

Userland/Libraries/LibWeb/Painting/ViewportPaintable.cpp

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -149,21 +149,22 @@ void ViewportPaintable::refresh_clip_state()
149149
auto const& block_paintable_box = *block->paintable_box();
150150
auto block_overflow_x = block_paintable_box.computed_values().overflow_x();
151151
auto block_overflow_y = block_paintable_box.computed_values().overflow_y();
152-
if (block_overflow_x != CSS::Overflow::Visible && block_overflow_y != CSS::Overflow::Visible)
153-
overflow_clip_rect.intersect(block_paintable_box.compute_absolute_padding_rect_with_css_transform_applied());
152+
if (block_overflow_x != CSS::Overflow::Visible && block_overflow_y != CSS::Overflow::Visible) {
153+
auto rect = block_paintable_box.compute_absolute_padding_rect_with_css_transform_applied();
154+
overflow_clip_rect.intersect(rect);
155+
auto border_radii_data = block_paintable_box.normalized_border_radii_data(ShrinkRadiiForBorders::Yes);
156+
if (border_radii_data.has_any_radius()) {
157+
BorderRadiiClip border_radii_clip { .rect = rect, .radii = border_radii_data };
158+
clip_frame.add_border_radii_clip(border_radii_clip);
159+
}
160+
}
154161
if (auto css_clip_property_rect = block->paintable_box()->get_clip_rect(); css_clip_property_rect.has_value())
155162
overflow_clip_rect.intersect(css_clip_property_rect.value());
156163
}
157164
clip_rect = overflow_clip_rect;
158165
}
159166

160-
auto border_radii_data = paintable_box.normalized_border_radii_data(ShrinkRadiiForBorders::Yes);
161-
if (border_radii_data.has_any_radius()) {
162-
// FIXME: Border radii of all boxes in containing block chain should be taken into account.
163-
clip_frame.corner_clip_radii = border_radii_data;
164-
}
165-
166-
clip_frame.rect = *clip_rect;
167+
clip_frame.set_rect(*clip_rect);
167168
}
168169
}
169170

0 commit comments

Comments
 (0)