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 {