소스 검색

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.
Aliaksandr Kalenik 1 년 전
부모
커밋
d4932196cc

+ 30 - 0
Tests/LibWeb/Ref/nested-boxes-with-hidden-overflow-and-border-radius.html

@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<link rel="match" href="reference/nested-boxes-with-hidden-overflow-and-border-radius-ref.html" />
+<style>
+    .outer {
+        overflow: hidden;
+        border-radius: 100px;
+        background-color: magenta;
+        width: 500px;
+        height: 500px;
+    }
+
+    .middle {
+        overflow: hidden;
+        border-radius: 50px;
+        transform: translate(10px, 10px);
+        background-color: lawngreen;
+    }
+
+    .inner {
+        width: 100px;
+        height: 100px;
+        background-color: black;
+        transform: translate(10px, 10px);
+    }
+</style>
+<div class="outer">
+    <div class="middle">
+        <div class="inner"></div>
+    </div>
+</div>

BIN
Tests/LibWeb/Ref/reference/images/nested-boxes-with-hidden-overflow-and-border-radius-ref.png


+ 10 - 0
Tests/LibWeb/Ref/reference/nested-boxes-with-hidden-overflow-and-border-radius-ref.html

@@ -0,0 +1,10 @@
+<style>
+    * {
+        margin: 0;
+    }
+
+    body {
+        background-color: white;
+    }
+</style>
+<img src="./images/nested-boxes-with-hidden-overflow-and-border-radius-ref.png">

+ 14 - 0
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);

+ 40 - 0
Userland/Libraries/LibWeb/Painting/ClipFrame.h

@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2024, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <LibWeb/Painting/BorderRadiiData.h>
+#include <LibWeb/PixelUnits.h>
+
+namespace Web::Painting {
+
+struct BorderRadiiClip {
+    CSSPixelRect rect;
+    BorderRadiiData radii;
+};
+
+struct ClipFrame : public RefCounted<ClipFrame> {
+    Vector<BorderRadiiClip> 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<BorderRadiiClip> m_border_radii_clips;
+};
+
+}

+ 1 - 1
Userland/Libraries/LibWeb/Painting/InlinePaintable.cpp

@@ -44,7 +44,7 @@ Optional<CSSPixelPoint> InlinePaintable::enclosing_scroll_frame_offset() const
 Optional<CSSPixelRect> InlinePaintable::clip_rect() const
 {
     if (m_enclosing_clip_frame)
-        return m_enclosing_clip_frame->rect;
+        return m_enclosing_clip_frame->rect();
     return {};
 }
 

+ 25 - 15
Userland/Libraries/LibWeb/Painting/PaintableBox.cpp

@@ -214,14 +214,14 @@ Optional<CSSPixelPoint> PaintableBox::enclosing_scroll_frame_offset() const
 Optional<CSSPixelRect> PaintableBox::clip_rect() const
 {
     if (m_enclosing_clip_frame)
-        return m_enclosing_clip_frame->rect;
+        return m_enclosing_clip_frame->rect();
     return {};
 }
 
-Optional<BorderRadiiData> PaintableBox::corner_clip_radii() const
+Span<BorderRadiiClip const> 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<int>());
-        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<int>(), 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<CSSPixels>());
+            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);
         }
     }
 }
@@ -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<int>());
-            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<CSSPixels>());
+            context.recording_painter().blit_corner_clipping(corner_clipper_id, context.rounded_device_rect(rect).to_type<int>());
         }
         context.recording_painter().restore();
     }

+ 3 - 7
Userland/Libraries/LibWeb/Painting/PaintableBox.h

@@ -8,6 +8,7 @@
 
 #include <LibWeb/Painting/BorderPainting.h>
 #include <LibWeb/Painting/BorderRadiusCornerClipper.h>
+#include <LibWeb/Painting/ClipFrame.h>
 #include <LibWeb/Painting/Paintable.h>
 #include <LibWeb/Painting/PaintableFragment.h>
 #include <LibWeb/Painting/ShadowPainting.h>
@@ -19,11 +20,6 @@ struct ScrollFrame : public RefCounted<ScrollFrame> {
     CSSPixelPoint offset;
 };
 
-struct ClipFrame : public RefCounted<ClipFrame> {
-    CSSPixelRect rect;
-    Optional<BorderRadiiData> corner_clip_radii;
-};
-
 class PaintableBox : public Paintable {
     JS_CELL(PaintableBox, Paintable);
 
@@ -210,7 +206,7 @@ public:
     Optional<int> scroll_frame_id() const;
     Optional<CSSPixelPoint> enclosing_scroll_frame_offset() const;
     Optional<CSSPixelRect> clip_rect() const;
-    Optional<BorderRadiiData> corner_clip_radii() const;
+    Span<BorderRadiiClip const> border_radii_clips() const;
 
 protected:
     explicit PaintableBox(Layout::Box const&);
@@ -235,7 +231,7 @@ private:
     Optional<CSSPixelRect> mutable m_absolute_paint_rect;
 
     mutable bool m_clipping_overflow { false };
-    mutable Optional<u32> m_corner_clipper_id;
+    mutable Vector<u32> m_corner_clipper_ids;
 
     RefPtr<ScrollFrame const> m_enclosing_scroll_frame;
     RefPtr<ClipFrame const> m_enclosing_clip_frame;

+ 10 - 9
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);
     }
 }