Forráskód Böngészése

LibWeb: Support overflow: hidden with a border-radius

Note: With this change the border-radius is clipped if ethier the
overflow-x or overflow-y is hidden (it is a little unclear what
happens if just one is set, but it seems like most browsers
treat one set + border-radius the same as if overflow: hidden
was set).
MacDue 3 éve
szülő
commit
f283e0ddc5

+ 49 - 5
Userland/Libraries/LibWeb/Painting/PaintableBox.cpp

@@ -246,17 +246,48 @@ void PaintableBox::before_children_paint(PaintContext& context, PaintPhase) cons
 {
     // FIXME: Support more overflow variations.
     auto clip_rect = absolute_padding_box_rect().to_rounded<int>();
-    if (computed_values().overflow_x() == CSS::Overflow::Hidden && computed_values().overflow_y() == CSS::Overflow::Hidden) {
-        context.painter().save();
-        context.painter().add_clip_rect(clip_rect);
+    auto overflow_x = computed_values().overflow_x();
+    auto overflow_y = computed_values().overflow_y();
+
+    auto clip_overflow = [&] {
+        if (!m_clipping_overflow) {
+            context.painter().save();
+            context.painter().add_clip_rect(clip_rect);
+            m_clipping_overflow = true;
+        }
+    };
+
+    if (overflow_x == CSS::Overflow::Hidden && overflow_y == CSS::Overflow::Hidden) {
+        clip_overflow();
+    }
+    if (overflow_y == CSS::Overflow::Hidden || overflow_x == CSS::Overflow::Hidden) {
+        auto border_radii_data = normalized_border_radii_data();
+        auto const& border = box_model().border;
+        border_radii_data.shrink(border.top, border.right, border.bottom, border.left);
+        if (border_radii_data.has_any_radius()) {
+            auto corner_clipper = BorderRadiusCornerClipper::create(clip_rect, border_radii_data, CornerClip::Outside, BorderRadiusCornerClipper::UseCachedBitmap::No);
+            if (corner_clipper.is_error()) {
+                dbgln("Failed to create overflow border-radius corner clipper: {}", corner_clipper.error());
+                return;
+            }
+            clip_overflow();
+            m_overflow_corner_radius_clipper = corner_clipper.release_value();
+            m_overflow_corner_radius_clipper->sample_under_corners(context.painter());
+        }
     }
 }
 
 void PaintableBox::after_children_paint(PaintContext& context, PaintPhase) const
 {
     // FIXME: Support more overflow variations.
-    if (computed_values().overflow_x() == CSS::Overflow::Hidden && computed_values().overflow_y() == CSS::Overflow::Hidden)
+    if (m_clipping_overflow) {
         context.painter().restore();
+        m_clipping_overflow = false;
+    }
+    if (m_overflow_corner_radius_clipper.has_value()) {
+        m_overflow_corner_radius_clipper->blit_corner_clipping(context.painter());
+        m_overflow_corner_radius_clipper = {};
+    }
 }
 
 static void paint_cursor_if_needed(PaintContext& context, Layout::TextNode const& text_node, Layout::LineBoxFragment const& fragment)
@@ -416,13 +447,24 @@ void PaintableWithLines::paint(PaintContext& context, PaintPhase phase) const
         return;
 
     bool should_clip_overflow = computed_values().overflow_x() != CSS::Overflow::Visible && computed_values().overflow_y() != CSS::Overflow::Visible;
+    Optional<BorderRadiusCornerClipper> corner_clipper;
 
     if (should_clip_overflow) {
         context.painter().save();
         // FIXME: Handle overflow-x and overflow-y being different values.
-        context.painter().add_clip_rect(enclosing_int_rect(absolute_padding_box_rect()));
+        auto clip_box = absolute_padding_box_rect().to_rounded<int>();
+        context.painter().add_clip_rect(clip_box);
         auto scroll_offset = static_cast<Layout::BlockContainer const&>(layout_box()).scroll_offset();
         context.painter().translate(-scroll_offset.to_type<int>());
+
+        auto border_radii = normalized_border_radii_data();
+        if (border_radii.has_any_radius()) {
+            auto clipper = BorderRadiusCornerClipper::create(clip_box, border_radii);
+            if (!clipper.is_error()) {
+                corner_clipper = clipper.release_value();
+                corner_clipper->sample_under_corners(context.painter());
+            }
+        }
     }
 
     // Text shadows
@@ -470,6 +512,8 @@ void PaintableWithLines::paint(PaintContext& context, PaintPhase phase) const
 
     if (should_clip_overflow) {
         context.painter().restore();
+        if (corner_clipper.has_value())
+            corner_clipper->blit_corner_clipping(context.painter());
     }
 
     // FIXME: Merge this loop with the above somehow..

+ 4 - 0
Userland/Libraries/LibWeb/Painting/PaintableBox.h

@@ -7,6 +7,7 @@
 #pragma once
 
 #include <LibWeb/Painting/BorderPainting.h>
+#include <LibWeb/Painting/BorderRadiusCornerClipper.h>
 #include <LibWeb/Painting/Paintable.h>
 
 namespace Web::Painting {
@@ -135,6 +136,9 @@ private:
     OwnPtr<Painting::StackingContext> m_stacking_context;
 
     Optional<Gfx::FloatRect> mutable m_absolute_rect;
+
+    mutable bool m_clipping_overflow { false };
+    Optional<BorderRadiusCornerClipper> mutable m_overflow_corner_radius_clipper;
 };
 
 class PaintableWithLines : public PaintableBox {