LibWeb: Deduplicate clipping code

This makes it so that PaintableWithLines no longer has its own
bespoke clipping logic, using the same code as regular scroll/
overflow clipping.
This commit is contained in:
Psychpsyo 2025-05-03 11:22:14 +02:00 committed by Alexander Kalenik
commit 85883ee5ce
Notes: github-actions[bot] 2025-05-13 12:32:43 +00:00
7 changed files with 52 additions and 44 deletions

View file

@ -38,11 +38,9 @@ Optional<CSSPixelRect> ClippableAndScrollable::clip_rect_for_hit_testing() const
return {}; return {};
} }
void ClippableAndScrollable::apply_clip(PaintContext& context) const void ClippableAndScrollable::apply_clip(PaintContext& context, RefPtr<ClipFrame const> from_clip_frame) const
{ {
if (!m_enclosing_clip_frame) auto const& clip_rects = from_clip_frame->clip_rects();
return;
auto const& clip_rects = m_enclosing_clip_frame->clip_rects();
if (clip_rects.is_empty()) if (clip_rects.is_empty())
return; return;
@ -64,11 +62,9 @@ void ClippableAndScrollable::apply_clip(PaintContext& context) const
} }
} }
void ClippableAndScrollable::restore_clip(PaintContext& context) const void ClippableAndScrollable::restore_clip(PaintContext& context, RefPtr<ClipFrame const> from_clip_frame) const
{ {
if (!m_enclosing_clip_frame) auto const& clip_rects = from_clip_frame->clip_rects();
return;
auto const& clip_rects = m_enclosing_clip_frame->clip_rects();
if (clip_rects.is_empty()) if (clip_rects.is_empty())
return; return;

View file

@ -16,7 +16,9 @@ public:
virtual ~ClippableAndScrollable() = default; virtual ~ClippableAndScrollable() = default;
void set_enclosing_scroll_frame(RefPtr<ScrollFrame const> scroll_frame) { m_enclosing_scroll_frame = scroll_frame; } void set_enclosing_scroll_frame(RefPtr<ScrollFrame const> scroll_frame) { m_enclosing_scroll_frame = scroll_frame; }
void set_own_scroll_frame(RefPtr<ScrollFrame> scroll_frame) { m_own_scroll_frame = scroll_frame; }
void set_enclosing_clip_frame(RefPtr<ClipFrame const> clip_frame) { m_enclosing_clip_frame = clip_frame; } void set_enclosing_clip_frame(RefPtr<ClipFrame const> clip_frame) { m_enclosing_clip_frame = clip_frame; }
void set_own_clip_frame(RefPtr<ClipFrame const> clip_frame) { m_own_clip_frame = clip_frame; }
[[nodiscard]] RefPtr<ScrollFrame const> enclosing_scroll_frame() const { return m_enclosing_scroll_frame; } [[nodiscard]] RefPtr<ScrollFrame const> enclosing_scroll_frame() const { return m_enclosing_scroll_frame; }
[[nodiscard]] Optional<int> scroll_frame_id() const; [[nodiscard]] Optional<int> scroll_frame_id() const;
@ -31,10 +33,12 @@ public:
return m_own_scroll_frame->own_offset(); return m_own_scroll_frame->own_offset();
return {}; return {};
} }
void set_own_scroll_frame(RefPtr<ScrollFrame> scroll_frame) { m_own_scroll_frame = scroll_frame; }
void apply_clip(PaintContext&) const; [[nodiscard]] RefPtr<ClipFrame const> enclosing_clip_frame() const { return m_enclosing_clip_frame; }
void restore_clip(PaintContext&) const; [[nodiscard]] RefPtr<ClipFrame const> own_clip_frame() const { return m_own_clip_frame; }
void apply_clip(PaintContext&, RefPtr<ClipFrame const>) const;
void restore_clip(PaintContext&, RefPtr<ClipFrame const>) const;
Gfx::AffineTransform const& combined_css_transform() const { return m_combined_css_transform; } Gfx::AffineTransform const& combined_css_transform() const { return m_combined_css_transform; }
void set_combined_css_transform(Gfx::AffineTransform const& transform) { m_combined_css_transform = transform; } void set_combined_css_transform(Gfx::AffineTransform const& transform) { m_combined_css_transform = transform; }
@ -43,6 +47,7 @@ private:
RefPtr<ScrollFrame const> m_enclosing_scroll_frame; RefPtr<ScrollFrame const> m_enclosing_scroll_frame;
RefPtr<ScrollFrame const> m_own_scroll_frame; RefPtr<ScrollFrame const> m_own_scroll_frame;
RefPtr<ClipFrame const> m_enclosing_clip_frame; RefPtr<ClipFrame const> m_enclosing_clip_frame;
RefPtr<ClipFrame const> m_own_clip_frame;
Gfx::AffineTransform m_combined_css_transform; Gfx::AffineTransform m_combined_css_transform;
}; };

View file

@ -246,6 +246,13 @@ CSSPixelRect PaintableBox::absolute_border_box_rect() const
return rect; return rect;
} }
// https://drafts.csswg.org/css-overflow-4/#overflow-clip-edge
CSSPixelRect PaintableBox::overflow_clip_edge_rect() const
{
// FIXME: Apply overflow-clip-margin-* properties
return absolute_padding_box_rect();
}
CSSPixelRect PaintableBox::absolute_paint_rect() const CSSPixelRect PaintableBox::absolute_paint_rect() const
{ {
if (!m_absolute_paint_rect.has_value()) if (!m_absolute_paint_rect.has_value())
@ -623,18 +630,24 @@ void PaintableBox::reset_scroll_offset(PaintContext& context, PaintPhase) const
void PaintableBox::apply_clip_overflow_rect(PaintContext& context, PaintPhase phase) const void PaintableBox::apply_clip_overflow_rect(PaintContext& context, PaintPhase phase) const
{ {
if (!enclosing_clip_frame())
return;
if (!AK::first_is_one_of(phase, PaintPhase::Background, PaintPhase::Border, PaintPhase::TableCollapsedBorder, PaintPhase::Foreground, PaintPhase::Outline)) if (!AK::first_is_one_of(phase, PaintPhase::Background, PaintPhase::Border, PaintPhase::TableCollapsedBorder, PaintPhase::Foreground, PaintPhase::Outline))
return; return;
apply_clip(context); apply_clip(context, enclosing_clip_frame());
} }
void PaintableBox::clear_clip_overflow_rect(PaintContext& context, PaintPhase phase) const void PaintableBox::clear_clip_overflow_rect(PaintContext& context, PaintPhase phase) const
{ {
if (!enclosing_clip_frame())
return;
if (!AK::first_is_one_of(phase, PaintPhase::Background, PaintPhase::Border, PaintPhase::TableCollapsedBorder, PaintPhase::Foreground, PaintPhase::Outline)) if (!AK::first_is_one_of(phase, PaintPhase::Background, PaintPhase::Border, PaintPhase::TableCollapsedBorder, PaintPhase::Foreground, PaintPhase::Outline))
return; return;
restore_clip(context); restore_clip(context, enclosing_clip_frame());
} }
void paint_cursor_if_needed(PaintContext& context, TextPaintable const& paintable, PaintableFragment const& fragment) void paint_cursor_if_needed(PaintContext& context, TextPaintable const& paintable, PaintableFragment const& fragment)
@ -847,32 +860,8 @@ void PaintableWithLines::paint(PaintContext& context, PaintPhase phase) const
PaintableBox::paint(context, phase); PaintableBox::paint(context, phase);
if (fragments().is_empty()) if (own_clip_frame()) {
return; apply_clip(context, own_clip_frame());
bool should_clip_overflow = computed_values().overflow_x() != CSS::Overflow::Visible && computed_values().overflow_y() != CSS::Overflow::Visible;
auto clip_box = absolute_padding_box_rect();
if (get_clip_rect().has_value()) {
clip_box.intersect(get_clip_rect().value());
should_clip_overflow = true;
}
if (should_clip_overflow) {
context.display_list_recorder().save();
// FIXME: Handle overflow-x and overflow-y being different values.
context.display_list_recorder().add_clip_rect(context.rounded_device_rect(clip_box).to_type<int>());
auto border_radii = normalized_border_radii_data(ShrinkRadiiForBorders::Yes);
CornerRadii corner_radii {
.top_left = border_radii.top_left.as_corner(context),
.top_right = border_radii.top_right.as_corner(context),
.bottom_right = border_radii.bottom_right.as_corner(context),
.bottom_left = border_radii.bottom_left.as_corner(context)
};
if (corner_radii.has_any_radius()) {
context.display_list_recorder().add_rounded_rect_clip(corner_radii, context.rounded_device_rect(clip_box).to_type<int>(), CornerClip::Outside);
}
if (own_scroll_frame_id().has_value()) { if (own_scroll_frame_id().has_value()) {
context.display_list_recorder().push_scroll_frame_id(own_scroll_frame_id().value()); context.display_list_recorder().push_scroll_frame_id(own_scroll_frame_id().value());
} }
@ -900,12 +889,11 @@ void PaintableWithLines::paint(PaintContext& context, PaintPhase phase) const
paint_text_fragment(context, static_cast<TextPaintable const&>(fragment.paintable()), fragment, phase); paint_text_fragment(context, static_cast<TextPaintable const&>(fragment.paintable()), fragment, phase);
} }
if (should_clip_overflow) { if (own_clip_frame()) {
context.display_list_recorder().restore();
if (own_scroll_frame_id().has_value()) { if (own_scroll_frame_id().has_value()) {
context.display_list_recorder().pop_scroll_frame_id(); context.display_list_recorder().pop_scroll_frame_id();
} }
restore_clip(context, own_clip_frame());
} }
} }

View file

@ -84,6 +84,7 @@ public:
CSSPixelRect absolute_rect() const; CSSPixelRect absolute_rect() const;
CSSPixelRect absolute_padding_box_rect() const; CSSPixelRect absolute_padding_box_rect() const;
CSSPixelRect absolute_border_box_rect() const; CSSPixelRect absolute_border_box_rect() const;
CSSPixelRect overflow_clip_edge_rect() const;
CSSPixelRect absolute_paint_rect() const; CSSPixelRect absolute_paint_rect() const;
// These united versions of the above rects take continuation into account. // These united versions of the above rects take continuation into account.

View file

@ -136,6 +136,12 @@ void ViewportPaintable::assign_clip_frames()
}); });
for_each_in_subtree([&](auto const& paintable) { for_each_in_subtree([&](auto const& paintable) {
if (paintable.is_paintable_box()) {
auto const& paintable_box = static_cast<PaintableBox const&>(paintable);
if (auto clip_frame = clip_state.get(paintable_box); clip_frame.has_value()) {
const_cast<PaintableBox&>(paintable_box).set_own_clip_frame(clip_frame.value());
}
}
for (auto block = paintable.containing_block(); !block->is_viewport(); block = block->containing_block()) { for (auto block = paintable.containing_block(); !block->is_viewport(); block = block->containing_block()) {
if (auto clip_frame = clip_state.get(block); clip_frame.has_value()) { if (auto clip_frame = clip_state.get(block); clip_frame.has_value()) {
if (paintable.is_paintable_box()) { if (paintable.is_paintable_box()) {

View file

@ -7,7 +7,6 @@
#container { #container {
width: 300px; width: 300px;
height: 300px; height: 300px;
border: 5px solid black;
border-radius: 50%; border-radius: 50%;
overflow: hidden; overflow: hidden;
} }

View file

@ -5,15 +5,27 @@
scrollbar-width: none; scrollbar-width: none;
} }
#outer {
/*
This needs to be here so that the text is double-clipped.
This happens in the reference and causes a slight difference
in aliasing around the edges.
*/
width: 300px;
height: 300px;
border-radius: 50%;
overflow: hidden;
}
#container { #container {
width: 300px; width: 300px;
height: 300px; height: 300px;
border: 5px solid black;
border-radius: 50%; border-radius: 50%;
overflow: scroll; overflow: scroll;
font-size: 40px; font-size: 40px;
} }
</style> </style>
<div id="outer">
<div id="container"> <div id="container">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut ultrices neque eu nisi facilisis Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut ultrices neque eu nisi facilisis
viverra. Integer lacinia, lacus vel condimentum suscipit, lacus felis porta nulla, eget lacinia viverra. Integer lacinia, lacus vel condimentum suscipit, lacus felis porta nulla, eget lacinia
@ -23,6 +35,7 @@
non elit in vehicula. Etiam malesuada neque eu porta rhoncus. Curabitur vel nunc finibus ligula non elit in vehicula. Etiam malesuada neque eu porta rhoncus. Curabitur vel nunc finibus ligula
posuere venenatis. posuere venenatis.
</div> </div>
</div>
<script> <script>
const scrollContainer = document.getElementById("container"); const scrollContainer = document.getElementById("container");
scrollContainer.scrollTop = 300; scrollContainer.scrollTop = 300;