From d1d6da6ab65acb1720e0af9e37bd1ad5fc393c27 Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Thu, 7 Dec 2023 06:16:17 +0100 Subject: [PATCH] LibWeb: Resolve border radius during layout and save it in paintables This change fixes a problem that we should not call `to_px()` to resolve any length or percentage values during paintables traversal because that is supposed to happen while performing layout. Also it improves performance because before we were resolving border radii during each painting phase but now it happens only once during layout. --- .../Libraries/LibWeb/Layout/LayoutState.cpp | 123 ++++++++++++++++++ .../Libraries/LibWeb/Layout/LayoutState.h | 1 + .../Libraries/LibWeb/Layout/LineBoxFragment.h | 5 + .../LibWeb/Painting/BorderPainting.cpp | 54 -------- .../LibWeb/Painting/BorderPainting.h | 2 - .../LibWeb/Painting/InlinePaintable.cpp | 13 +- .../LibWeb/Painting/PaintableBox.cpp | 11 +- .../Libraries/LibWeb/Painting/PaintableBox.h | 5 + 8 files changed, 139 insertions(+), 75 deletions(-) diff --git a/Userland/Libraries/LibWeb/Layout/LayoutState.cpp b/Userland/Libraries/LibWeb/Layout/LayoutState.cpp index 7c35847431..ef3f3ec9dd 100644 --- a/Userland/Libraries/LibWeb/Layout/LayoutState.cpp +++ b/Userland/Libraries/LibWeb/Layout/LayoutState.cpp @@ -10,6 +10,8 @@ #include #include #include +#include +#include #include namespace Web::Layout { @@ -215,6 +217,125 @@ static void build_paint_tree(Node& node, Painting::Paintable* parent_paintable = } } +static Painting::BorderRadiiData normalized_border_radii_data(Layout::Node const& node, CSSPixelRect const& rect, CSS::BorderRadiusData top_left_radius, CSS::BorderRadiusData top_right_radius, CSS::BorderRadiusData bottom_right_radius, CSS::BorderRadiusData bottom_left_radius) +{ + Painting::BorderRadiusData bottom_left_radius_px {}; + Painting::BorderRadiusData bottom_right_radius_px {}; + Painting::BorderRadiusData top_left_radius_px {}; + Painting::BorderRadiusData top_right_radius_px {}; + + bottom_left_radius_px.horizontal_radius = bottom_left_radius.horizontal_radius.to_px(node, rect.width()); + bottom_right_radius_px.horizontal_radius = bottom_right_radius.horizontal_radius.to_px(node, rect.width()); + top_left_radius_px.horizontal_radius = top_left_radius.horizontal_radius.to_px(node, rect.width()); + top_right_radius_px.horizontal_radius = top_right_radius.horizontal_radius.to_px(node, rect.width()); + + bottom_left_radius_px.vertical_radius = bottom_left_radius.vertical_radius.to_px(node, rect.height()); + bottom_right_radius_px.vertical_radius = bottom_right_radius.vertical_radius.to_px(node, rect.height()); + top_left_radius_px.vertical_radius = top_left_radius.vertical_radius.to_px(node, rect.height()); + top_right_radius_px.vertical_radius = top_right_radius.vertical_radius.to_px(node, rect.height()); + + // Scale overlapping curves according to https://www.w3.org/TR/css-backgrounds-3/#corner-overlap + // Let f = min(Li/Si), where i ∈ {top, right, bottom, left}, + // Si is the sum of the two corresponding radii of the corners on side i, + // and Ltop = Lbottom = the width of the box, and Lleft = Lright = the height of the box. + auto l_top = rect.width(); + auto l_bottom = l_top; + auto l_left = rect.height(); + auto l_right = l_left; + auto s_top = (top_left_radius_px.horizontal_radius + top_right_radius_px.horizontal_radius); + auto s_right = (top_right_radius_px.vertical_radius + bottom_right_radius_px.vertical_radius); + auto s_bottom = (bottom_left_radius_px.horizontal_radius + bottom_right_radius_px.horizontal_radius); + auto s_left = (top_left_radius_px.vertical_radius + bottom_left_radius_px.vertical_radius); + CSSPixelFraction f = 1; + f = min(f, l_top / s_top); + f = min(f, l_right / s_right); + f = min(f, l_bottom / s_bottom); + f = min(f, l_left / s_left); + + // If f < 1, then all corner radii are reduced by multiplying them by f. + if (f < 1) { + top_left_radius_px.horizontal_radius *= f; + top_left_radius_px.vertical_radius *= f; + top_right_radius_px.horizontal_radius *= f; + top_right_radius_px.vertical_radius *= f; + bottom_right_radius_px.horizontal_radius *= f; + bottom_right_radius_px.vertical_radius *= f; + bottom_left_radius_px.horizontal_radius *= f; + bottom_left_radius_px.vertical_radius *= f; + } + + return Painting::BorderRadiiData { top_left_radius_px, top_right_radius_px, bottom_right_radius_px, bottom_left_radius_px }; +} + +void LayoutState::resolve_border_radii() +{ + Vector inline_paintables; + + for (auto& it : used_values_per_layout_node) { + auto& used_values = *it.value; + auto& node = const_cast(used_values.node()); + + auto* paintable = node.paintable(); + + if (paintable && is(*paintable)) { + auto& inline_paintable = static_cast(*paintable); + inline_paintables.append(inline_paintable); + } + + if (paintable && is(*paintable)) { + auto& paintable_box = static_cast(*paintable); + + CSSPixelRect const content_rect { 0, 0, used_values.content_width(), used_values.content_height() }; + auto border_rect = content_rect.inflated(used_values.border_top, used_values.border_right, used_values.border_bottom, used_values.border_left); + + auto const& border_top_left_radius = node.computed_values().border_top_left_radius(); + auto const& border_top_right_radius = node.computed_values().border_top_right_radius(); + auto const& border_bottom_right_radius = node.computed_values().border_bottom_right_radius(); + auto const& border_bottom_left_radius = node.computed_values().border_bottom_left_radius(); + + auto radii_data = normalized_border_radii_data(node, border_rect, border_top_left_radius, border_top_right_radius, border_bottom_right_radius, border_bottom_left_radius); + paintable_box.set_border_radii_data(radii_data); + } + } + + for (auto& inline_paintable : inline_paintables) { + Vector fragments; + verify_cast(*inline_paintable.containing_block()->paintable_box()).for_each_fragment([&](auto& fragment) { + if (inline_paintable.layout_node().is_inclusive_ancestor_of(fragment.layout_node())) + fragments.append(const_cast(fragment)); + return IterationDecision::Continue; + }); + + auto const& top_left_border_radius = inline_paintable.computed_values().border_top_left_radius(); + auto const& top_right_border_radius = inline_paintable.computed_values().border_top_right_radius(); + auto const& bottom_right_border_radius = inline_paintable.computed_values().border_bottom_right_radius(); + auto const& bottom_left_border_radius = inline_paintable.computed_values().border_bottom_left_radius(); + + auto containing_block_position_in_absolute_coordinates = inline_paintable.containing_block()->paintable_box()->absolute_position(); + for (size_t i = 0; i < fragments.size(); ++i) { + auto is_first_fragment = i == 0; + auto is_last_fragment = i == fragments.size() - 1; + auto& fragment = fragments[i]; + + CSSPixelRect absolute_fragment_rect { containing_block_position_in_absolute_coordinates.translated(fragment.offset()), fragment.size() }; + + if (is_first_fragment) { + auto extra_start_width = inline_paintable.box_model().padding.left; + absolute_fragment_rect.translate_by(-extra_start_width, 0); + absolute_fragment_rect.set_width(absolute_fragment_rect.width() + extra_start_width); + } + + if (is_last_fragment) { + auto extra_end_width = inline_paintable.box_model().padding.right; + absolute_fragment_rect.set_width(absolute_fragment_rect.width() + extra_end_width); + } + + auto border_radii_data = normalized_border_radii_data(inline_paintable.layout_node(), absolute_fragment_rect, top_left_border_radius, top_right_border_radius, bottom_right_border_radius, bottom_left_border_radius); + fragment.set_border_radii_data(border_radii_data); + } + } +} + void LayoutState::commit(Box& root) { // Only the top-level LayoutState should ever be committed. @@ -309,6 +430,8 @@ void LayoutState::commit(Box& root) auto const& box = static_cast(used_values.node()); measure_scrollable_overflow(box); } + + resolve_border_radii(); } void LayoutState::UsedValues::set_node(NodeWithStyle& node, UsedValues const* containing_block_used_values) diff --git a/Userland/Libraries/LibWeb/Layout/LayoutState.h b/Userland/Libraries/LibWeb/Layout/LayoutState.h index 0cb4c34b31..f93815d909 100644 --- a/Userland/Libraries/LibWeb/Layout/LayoutState.h +++ b/Userland/Libraries/LibWeb/Layout/LayoutState.h @@ -186,6 +186,7 @@ struct LayoutState { private: void resolve_relative_positions(Vector const&); + void resolve_border_radii(); }; } diff --git a/Userland/Libraries/LibWeb/Layout/LineBoxFragment.h b/Userland/Libraries/LibWeb/Layout/LineBoxFragment.h index ac9ad0dc18..cdefae8740 100644 --- a/Userland/Libraries/LibWeb/Layout/LineBoxFragment.h +++ b/Userland/Libraries/LibWeb/Layout/LineBoxFragment.h @@ -10,6 +10,7 @@ #include #include #include +#include #include namespace Web::Layout { @@ -75,6 +76,9 @@ public: Vector const& glyph_run() const { return m_glyph_run; } + Painting::BorderRadiiData const& border_radii_data() const { return m_border_radii_data; } + void set_border_radii_data(Painting::BorderRadiiData const& border_radii_data) { m_border_radii_data = border_radii_data; } + private: JS::NonnullGCPtr m_layout_node; int m_start { 0 }; @@ -85,6 +89,7 @@ private: CSSPixels m_border_box_bottom { 0 }; CSSPixels m_baseline { 0 }; Vector m_glyph_run; + Painting::BorderRadiiData m_border_radii_data; }; } diff --git a/Userland/Libraries/LibWeb/Painting/BorderPainting.cpp b/Userland/Libraries/LibWeb/Painting/BorderPainting.cpp index f55837bce2..bbe67fc533 100644 --- a/Userland/Libraries/LibWeb/Painting/BorderPainting.cpp +++ b/Userland/Libraries/LibWeb/Painting/BorderPainting.cpp @@ -8,65 +8,11 @@ #include #include -#include -#include #include #include -#include -#include namespace Web::Painting { -BorderRadiiData normalized_border_radii_data(Layout::Node const& node, CSSPixelRect const& rect, CSS::BorderRadiusData top_left_radius, CSS::BorderRadiusData top_right_radius, CSS::BorderRadiusData bottom_right_radius, CSS::BorderRadiusData bottom_left_radius) -{ - BorderRadiusData bottom_left_radius_px {}; - BorderRadiusData bottom_right_radius_px {}; - BorderRadiusData top_left_radius_px {}; - BorderRadiusData top_right_radius_px {}; - - bottom_left_radius_px.horizontal_radius = bottom_left_radius.horizontal_radius.to_px(node, rect.width()); - bottom_right_radius_px.horizontal_radius = bottom_right_radius.horizontal_radius.to_px(node, rect.width()); - top_left_radius_px.horizontal_radius = top_left_radius.horizontal_radius.to_px(node, rect.width()); - top_right_radius_px.horizontal_radius = top_right_radius.horizontal_radius.to_px(node, rect.width()); - - bottom_left_radius_px.vertical_radius = bottom_left_radius.vertical_radius.to_px(node, rect.height()); - bottom_right_radius_px.vertical_radius = bottom_right_radius.vertical_radius.to_px(node, rect.height()); - top_left_radius_px.vertical_radius = top_left_radius.vertical_radius.to_px(node, rect.height()); - top_right_radius_px.vertical_radius = top_right_radius.vertical_radius.to_px(node, rect.height()); - - // Scale overlapping curves according to https://www.w3.org/TR/css-backgrounds-3/#corner-overlap - // Let f = min(Li/Si), where i ∈ {top, right, bottom, left}, - // Si is the sum of the two corresponding radii of the corners on side i, - // and Ltop = Lbottom = the width of the box, and Lleft = Lright = the height of the box. - auto l_top = rect.width(); - auto l_bottom = l_top; - auto l_left = rect.height(); - auto l_right = l_left; - auto s_top = (top_left_radius_px.horizontal_radius + top_right_radius_px.horizontal_radius); - auto s_right = (top_right_radius_px.vertical_radius + bottom_right_radius_px.vertical_radius); - auto s_bottom = (bottom_left_radius_px.horizontal_radius + bottom_right_radius_px.horizontal_radius); - auto s_left = (top_left_radius_px.vertical_radius + bottom_left_radius_px.vertical_radius); - CSSPixelFraction f = 1; - f = min(f, l_top / s_top); - f = min(f, l_right / s_right); - f = min(f, l_bottom / s_bottom); - f = min(f, l_left / s_left); - - // If f < 1, then all corner radii are reduced by multiplying them by f. - if (f < 1) { - top_left_radius_px.horizontal_radius *= f; - top_left_radius_px.vertical_radius *= f; - top_right_radius_px.horizontal_radius *= f; - top_right_radius_px.vertical_radius *= f; - bottom_right_radius_px.horizontal_radius *= f; - bottom_right_radius_px.vertical_radius *= f; - bottom_left_radius_px.horizontal_radius *= f; - bottom_left_radius_px.vertical_radius *= f; - } - - return BorderRadiiData { top_left_radius_px, top_right_radius_px, bottom_right_radius_px, bottom_left_radius_px }; -} - static constexpr double dark_light_absolute_value_difference = 1. / 3; static Color light_color_for_inset_and_outset(Color const& color) diff --git a/Userland/Libraries/LibWeb/Painting/BorderPainting.h b/Userland/Libraries/LibWeb/Painting/BorderPainting.h index e51075f7ef..dbc7ef4f6a 100644 --- a/Userland/Libraries/LibWeb/Painting/BorderPainting.h +++ b/Userland/Libraries/LibWeb/Painting/BorderPainting.h @@ -16,8 +16,6 @@ namespace Web::Painting { -BorderRadiiData normalized_border_radii_data(Layout::Node const&, CSSPixelRect const&, CSS::BorderRadiusData top_left_radius, CSS::BorderRadiusData top_right_radius, CSS::BorderRadiusData bottom_right_radius, CSS::BorderRadiusData bottom_left_radius); - enum class BorderEdge { Top, Right, diff --git a/Userland/Libraries/LibWeb/Painting/InlinePaintable.cpp b/Userland/Libraries/LibWeb/Painting/InlinePaintable.cpp index 5d7e714f99..98162fd5c9 100644 --- a/Userland/Libraries/LibWeb/Painting/InlinePaintable.cpp +++ b/Userland/Libraries/LibWeb/Painting/InlinePaintable.cpp @@ -34,10 +34,6 @@ void InlinePaintable::paint(PaintContext& context, PaintPhase phase) const auto& painter = context.recording_painter(); if (phase == PaintPhase::Background) { - auto top_left_border_radius = computed_values().border_top_left_radius(); - auto top_right_border_radius = computed_values().border_top_right_radius(); - auto bottom_right_border_radius = computed_values().border_bottom_right_radius(); - auto bottom_left_border_radius = computed_values().border_bottom_left_radius(); auto containing_block_position_in_absolute_coordinates = containing_block()->paintable_box()->absolute_position(); for_each_fragment([&](auto const& fragment, bool is_first_fragment, bool is_last_fragment) { @@ -54,7 +50,7 @@ void InlinePaintable::paint(PaintContext& context, PaintPhase phase) const absolute_fragment_rect.set_width(absolute_fragment_rect.width() + extra_end_width); } - auto border_radii_data = normalized_border_radii_data(layout_node(), absolute_fragment_rect, top_left_border_radius, top_right_border_radius, bottom_right_border_radius, bottom_left_border_radius); + auto const& border_radii_data = fragment.border_radii_data(); paint_background(context, layout_node(), absolute_fragment_rect, computed_values().background_color(), computed_values().image_rendering(), &computed_values().background_layers(), border_radii_data); if (auto computed_box_shadow = computed_values().box_shadow(); !computed_box_shadow.is_empty()) { @@ -87,11 +83,6 @@ void InlinePaintable::paint(PaintContext& context, PaintPhase phase) const } auto paint_border_or_outline = [&](Optional outline_data = {}, CSSPixels outline_offset = 0) { - auto top_left_border_radius = computed_values().border_top_left_radius(); - auto top_right_border_radius = computed_values().border_top_right_radius(); - auto bottom_right_border_radius = computed_values().border_bottom_right_radius(); - auto bottom_left_border_radius = computed_values().border_bottom_left_radius(); - auto borders_data = BordersData { .top = computed_values().border_top(), .right = computed_values().border_right(), @@ -116,7 +107,7 @@ void InlinePaintable::paint(PaintContext& context, PaintPhase phase) const } auto borders_rect = absolute_fragment_rect.inflated(borders_data.top.width, borders_data.right.width, borders_data.bottom.width, borders_data.left.width); - auto border_radii_data = normalized_border_radii_data(layout_node(), borders_rect, top_left_border_radius, top_right_border_radius, bottom_right_border_radius, bottom_left_border_radius); + auto border_radii_data = fragment.border_radii_data(); if (outline_data.has_value()) { auto outline_offset_x = outline_offset; diff --git a/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp b/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp index e4ea3a9bee..34474357c4 100644 --- a/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp +++ b/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp @@ -393,15 +393,10 @@ void PaintableBox::paint_box_shadow(PaintContext& context) const BorderRadiiData PaintableBox::normalized_border_radii_data(ShrinkRadiiForBorders shrink) const { - auto border_radius_data = Painting::normalized_border_radii_data(layout_box(), - absolute_border_box_rect(), - computed_values().border_top_left_radius(), - computed_values().border_top_right_radius(), - computed_values().border_bottom_right_radius(), - computed_values().border_bottom_left_radius()); + auto border_radii_data = this->border_radii_data(); if (shrink == ShrinkRadiiForBorders::Yes) - border_radius_data.shrink(computed_values().border_top().width, computed_values().border_right().width, computed_values().border_bottom().width, computed_values().border_left().width); - return border_radius_data; + border_radii_data.shrink(computed_values().border_top().width, computed_values().border_right().width, computed_values().border_bottom().width, computed_values().border_left().width); + return border_radii_data; } Optional PaintableBox::calculate_overflow_clipped_rect() const diff --git a/Userland/Libraries/LibWeb/Painting/PaintableBox.h b/Userland/Libraries/LibWeb/Painting/PaintableBox.h index da0d5bf4fd..0d1e023d46 100644 --- a/Userland/Libraries/LibWeb/Painting/PaintableBox.h +++ b/Userland/Libraries/LibWeb/Painting/PaintableBox.h @@ -188,6 +188,9 @@ public: BorderRadiiData normalized_border_radii_data(ShrinkRadiiForBorders shrink = ShrinkRadiiForBorders::No) const; + BorderRadiiData const& border_radii_data() const { return m_border_radii_data; } + void set_border_radii_data(BorderRadiiData const& border_radii_data) { m_border_radii_data = border_radii_data; } + protected: explicit PaintableBox(Layout::Box const&); @@ -223,6 +226,8 @@ private: Optional m_override_borders_data; Optional m_table_cell_coordinates; + + BorderRadiiData m_border_radii_data; }; class PaintableWithLines : public PaintableBox {