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.
This commit is contained in:
Aliaksandr Kalenik 2024-02-10 17:52:57 +01:00 committed by Andreas Kling
parent 1ae416fa94
commit d4932196cc
9 changed files with 133 additions and 32 deletions

View file

@ -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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View file

@ -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">

View file

@ -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);

View file

@ -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;
};
}

View file

@ -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 {};
}

View file

@ -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();
}

View file

@ -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;

View file

@ -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);
}
}