diff --git a/Tests/LibWeb/Ref/nested-boxes-with-hidden-overflow-and-border-radius.html b/Tests/LibWeb/Ref/nested-boxes-with-hidden-overflow-and-border-radius.html new file mode 100644 index 0000000000..4201da1ab0 --- /dev/null +++ b/Tests/LibWeb/Ref/nested-boxes-with-hidden-overflow-and-border-radius.html @@ -0,0 +1,30 @@ + + + +
+
+
+
+
diff --git a/Tests/LibWeb/Ref/reference/images/nested-boxes-with-hidden-overflow-and-border-radius-ref.png b/Tests/LibWeb/Ref/reference/images/nested-boxes-with-hidden-overflow-and-border-radius-ref.png new file mode 100644 index 0000000000..84b09d71a6 Binary files /dev/null and b/Tests/LibWeb/Ref/reference/images/nested-boxes-with-hidden-overflow-and-border-radius-ref.png differ diff --git a/Tests/LibWeb/Ref/reference/nested-boxes-with-hidden-overflow-and-border-radius-ref.html b/Tests/LibWeb/Ref/reference/nested-boxes-with-hidden-overflow-and-border-radius-ref.html new file mode 100644 index 0000000000..c9a882ba78 --- /dev/null +++ b/Tests/LibWeb/Ref/reference/nested-boxes-with-hidden-overflow-and-border-radius-ref.html @@ -0,0 +1,10 @@ + + diff --git a/Userland/Libraries/LibWeb/Painting/BorderRadiiData.h b/Userland/Libraries/LibWeb/Painting/BorderRadiiData.h index 8ebd81d26d..625cd31948 100644 --- a/Userland/Libraries/LibWeb/Painting/BorderRadiiData.h +++ b/Userland/Libraries/LibWeb/Painting/BorderRadiiData.h @@ -31,6 +31,12 @@ struct BorderRadiusData { if (vertical_radius != 0) vertical_radius = max(CSSPixels(0), vertical_radius - vertical); } + + inline void union_max_radii(BorderRadiusData const& other) + { + horizontal_radius = max(horizontal_radius, other.horizontal_radius); + vertical_radius = max(vertical_radius, other.vertical_radius); + } }; using CornerRadius = Gfx::AntiAliasingPainter::CornerRadius; @@ -58,6 +64,14 @@ struct BorderRadiiData { return top_left || top_right || bottom_right || bottom_left; } + inline void union_max_radii(BorderRadiiData const& other) + { + top_left.union_max_radii(other.top_left); + top_right.union_max_radii(other.top_right); + bottom_right.union_max_radii(other.bottom_right); + bottom_left.union_max_radii(other.bottom_left); + } + inline void shrink(CSSPixels top, CSSPixels right, CSSPixels bottom, CSSPixels left) { top_left.shrink(left, top); diff --git a/Userland/Libraries/LibWeb/Painting/ClipFrame.h b/Userland/Libraries/LibWeb/Painting/ClipFrame.h new file mode 100644 index 0000000000..43e34b70b1 --- /dev/null +++ b/Userland/Libraries/LibWeb/Painting/ClipFrame.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2024, Aliaksandr Kalenik + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace Web::Painting { + +struct BorderRadiiClip { + CSSPixelRect rect; + BorderRadiiData radii; +}; + +struct ClipFrame : public RefCounted { + Vector const& border_radii_clips() const { return m_border_radii_clips; } + void add_border_radii_clip(BorderRadiiClip border_radii_clip) + { + for (auto& existing_clip : m_border_radii_clips) { + if (border_radii_clip.rect == existing_clip.rect) { + existing_clip.radii.union_max_radii(border_radii_clip.radii); + return; + } + } + m_border_radii_clips.append(border_radii_clip); + } + + CSSPixelRect rect() const { return m_rect; } + void set_rect(CSSPixelRect rect) { m_rect = rect; } + +private: + CSSPixelRect m_rect; + Vector m_border_radii_clips; +}; + +} diff --git a/Userland/Libraries/LibWeb/Painting/InlinePaintable.cpp b/Userland/Libraries/LibWeb/Painting/InlinePaintable.cpp index 0de7ec5e91..7a965240be 100644 --- a/Userland/Libraries/LibWeb/Painting/InlinePaintable.cpp +++ b/Userland/Libraries/LibWeb/Painting/InlinePaintable.cpp @@ -44,7 +44,7 @@ Optional InlinePaintable::enclosing_scroll_frame_offset() const Optional InlinePaintable::clip_rect() const { if (m_enclosing_clip_frame) - return m_enclosing_clip_frame->rect; + return m_enclosing_clip_frame->rect(); return {}; } diff --git a/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp b/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp index cfc279cc1e..26ba547947 100644 --- a/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp +++ b/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp @@ -214,14 +214,14 @@ Optional PaintableBox::enclosing_scroll_frame_offset() const Optional PaintableBox::clip_rect() const { if (m_enclosing_clip_frame) - return m_enclosing_clip_frame->rect; + return m_enclosing_clip_frame->rect(); return {}; } -Optional PaintableBox::corner_clip_radii() const +Span PaintableBox::border_radii_clips() const { if (m_enclosing_clip_frame) - return m_enclosing_clip_frame->corner_clip_radii; + return m_enclosing_clip_frame->border_radii_clips(); return {}; } @@ -444,12 +444,17 @@ void PaintableBox::apply_clip_overflow_rect(PaintContext& context, PaintPhase ph m_clipping_overflow = true; context.recording_painter().save(); context.recording_painter().add_clip_rect(context.enclosing_device_rect(overflow_clip_rect).to_type()); - if (corner_clip_radii().has_value()) { - VERIFY(!m_corner_clipper_id.has_value()); - m_corner_clipper_id = context.allocate_corner_clipper_id(); - auto corner_radii = corner_clip_radii()->as_corners(context); - if (corner_radii.has_any_radius()) - 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(), CornerClip::Outside); + auto const& border_radii_clips = this->border_radii_clips(); + m_corner_clipper_ids.resize(border_radii_clips.size()); + for (size_t corner_clip_index = 0; corner_clip_index < border_radii_clips.size(); ++corner_clip_index) { + auto const& corner_clip = border_radii_clips[corner_clip_index]; + auto corners = corner_clip.radii.as_corners(context); + if (!corners.has_any_radius()) + continue; + auto corner_clipper_id = context.allocate_corner_clipper_id(); + m_corner_clipper_ids[corner_clip_index] = corner_clipper_id; + auto rect = corner_clip.rect.translated(-combined_transform.translation().to_type()); + context.recording_painter().sample_under_corners(corner_clipper_id, corner_clip.radii.as_corners(context), context.rounded_device_rect(rect).to_type(), CornerClip::Outside); } } } @@ -461,12 +466,17 @@ void PaintableBox::clear_clip_overflow_rect(PaintContext& context, PaintPhase ph if (m_clipping_overflow) { m_clipping_overflow = false; - if (corner_clip_radii().has_value()) { - VERIFY(m_corner_clipper_id.has_value()); - auto corner_radii = corner_clip_radii()->as_corners(context); - if (corner_radii.has_any_radius()) - context.recording_painter().blit_corner_clipping(*m_corner_clipper_id, context.rounded_device_rect(*clip_rect()).to_type()); - m_corner_clipper_id = {}; + auto combined_transform = compute_combined_css_transform(); + auto const& border_radii_clips = this->border_radii_clips(); + for (size_t corner_clip_index = 0; corner_clip_index < border_radii_clips.size(); ++corner_clip_index) { + auto const& corner_clip = border_radii_clips[corner_clip_index]; + auto corners = corner_clip.radii.as_corners(context); + if (!corners.has_any_radius()) + continue; + auto corner_clipper_id = m_corner_clipper_ids[corner_clip_index]; + m_corner_clipper_ids[corner_clip_index] = corner_clipper_id; + auto rect = corner_clip.rect.translated(-combined_transform.translation().to_type()); + context.recording_painter().blit_corner_clipping(corner_clipper_id, context.rounded_device_rect(rect).to_type()); } context.recording_painter().restore(); } diff --git a/Userland/Libraries/LibWeb/Painting/PaintableBox.h b/Userland/Libraries/LibWeb/Painting/PaintableBox.h index 4ce58e87a2..bd7458e689 100644 --- a/Userland/Libraries/LibWeb/Painting/PaintableBox.h +++ b/Userland/Libraries/LibWeb/Painting/PaintableBox.h @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -19,11 +20,6 @@ struct ScrollFrame : public RefCounted { CSSPixelPoint offset; }; -struct ClipFrame : public RefCounted { - CSSPixelRect rect; - Optional corner_clip_radii; -}; - class PaintableBox : public Paintable { JS_CELL(PaintableBox, Paintable); @@ -210,7 +206,7 @@ public: Optional scroll_frame_id() const; Optional enclosing_scroll_frame_offset() const; Optional clip_rect() const; - Optional corner_clip_radii() const; + Span border_radii_clips() const; protected: explicit PaintableBox(Layout::Box const&); @@ -235,7 +231,7 @@ private: Optional mutable m_absolute_paint_rect; mutable bool m_clipping_overflow { false }; - mutable Optional m_corner_clipper_id; + mutable Vector m_corner_clipper_ids; RefPtr m_enclosing_scroll_frame; RefPtr m_enclosing_clip_frame; diff --git a/Userland/Libraries/LibWeb/Painting/ViewportPaintable.cpp b/Userland/Libraries/LibWeb/Painting/ViewportPaintable.cpp index 5ee40397ca..c157cd8d61 100644 --- a/Userland/Libraries/LibWeb/Painting/ViewportPaintable.cpp +++ b/Userland/Libraries/LibWeb/Painting/ViewportPaintable.cpp @@ -149,21 +149,22 @@ void ViewportPaintable::refresh_clip_state() auto const& block_paintable_box = *block->paintable_box(); auto block_overflow_x = block_paintable_box.computed_values().overflow_x(); auto block_overflow_y = block_paintable_box.computed_values().overflow_y(); - if (block_overflow_x != CSS::Overflow::Visible && block_overflow_y != CSS::Overflow::Visible) - overflow_clip_rect.intersect(block_paintable_box.compute_absolute_padding_rect_with_css_transform_applied()); + if (block_overflow_x != CSS::Overflow::Visible && block_overflow_y != CSS::Overflow::Visible) { + auto rect = block_paintable_box.compute_absolute_padding_rect_with_css_transform_applied(); + overflow_clip_rect.intersect(rect); + auto border_radii_data = block_paintable_box.normalized_border_radii_data(ShrinkRadiiForBorders::Yes); + if (border_radii_data.has_any_radius()) { + BorderRadiiClip border_radii_clip { .rect = rect, .radii = border_radii_data }; + clip_frame.add_border_radii_clip(border_radii_clip); + } + } if (auto css_clip_property_rect = block->paintable_box()->get_clip_rect(); css_clip_property_rect.has_value()) overflow_clip_rect.intersect(css_clip_property_rect.value()); } clip_rect = overflow_clip_rect; } - auto border_radii_data = paintable_box.normalized_border_radii_data(ShrinkRadiiForBorders::Yes); - if (border_radii_data.has_any_radius()) { - // FIXME: Border radii of all boxes in containing block chain should be taken into account. - clip_frame.corner_clip_radii = border_radii_data; - } - - clip_frame.rect = *clip_rect; + clip_frame.set_rect(*clip_rect); } }