diff --git a/Tests/LibWeb/Ref/invalidate-css-transform-property.html b/Tests/LibWeb/Ref/invalidate-css-transform-property.html new file mode 100644 index 0000000000..1233b26089 --- /dev/null +++ b/Tests/LibWeb/Ref/invalidate-css-transform-property.html @@ -0,0 +1,16 @@ + + + +
+ diff --git a/Tests/LibWeb/Ref/reference/invalidate-css-transform-property-ref.html b/Tests/LibWeb/Ref/reference/invalidate-css-transform-property-ref.html new file mode 100644 index 0000000000..9bf9470f1c --- /dev/null +++ b/Tests/LibWeb/Ref/reference/invalidate-css-transform-property-ref.html @@ -0,0 +1,12 @@ + + +
diff --git a/Userland/Libraries/LibWeb/DOM/Element.cpp b/Userland/Libraries/LibWeb/DOM/Element.cpp index f6605b55c8..366d257436 100644 --- a/Userland/Libraries/LibWeb/DOM/Element.cpp +++ b/Userland/Libraries/LibWeb/DOM/Element.cpp @@ -63,6 +63,7 @@ #include #include #include +#include #include #include #include @@ -590,6 +591,9 @@ Element::RequiredInvalidationAfterStyleChange Element::recompute_style() m_computed_css_values = move(new_computed_css_values); computed_css_values_changed(); + if (invalidation.repaint && document().navigable()) + document().navigable()->set_needs_to_resolve_paint_only_properties(); + if (!invalidation.rebuild_layout_tree && layout_node()) { // If we're keeping the layout tree, we can just apply the new style to the existing layout tree. layout_node()->apply_style(*m_computed_css_values); @@ -909,6 +913,11 @@ JS::NonnullGCPtr Element::get_client_rects() const VERIFY(document().navigable()); auto viewport_offset = document().navigable()->viewport_scroll_offset(); + if (document().paintable()) { + // NOTE: Make sure CSS transforms are resolved before it is used to calculate the rect position. + const_cast(document().paintable())->resolve_paint_only_properties(); + } + Gfx::AffineTransform transform; for (auto const* containing_block = this->layout_node(); containing_block; containing_block = containing_block->containing_block()) { Gfx::AffineTransform containing_block_transform; diff --git a/Userland/Libraries/LibWeb/HTML/Navigable.cpp b/Userland/Libraries/LibWeb/HTML/Navigable.cpp index 8f8bec3af0..f8b0cf3325 100644 --- a/Userland/Libraries/LibWeb/HTML/Navigable.cpp +++ b/Userland/Libraries/LibWeb/HTML/Navigable.cpp @@ -2082,6 +2082,11 @@ void Navigable::paint(Painting::RecordingPainter& recording_painter, PaintConfig context.set_should_paint_overlay(config.paint_overlay); context.set_has_focus(config.has_focus); + if (m_needs_to_resolve_paint_only_properties) { + document->paintable()->resolve_paint_only_properties(); + m_needs_to_resolve_paint_only_properties = false; + } + HashMap scroll_frames; if (is_traversable()) { document->paintable()->assign_scroll_frame_ids(scroll_frames); diff --git a/Userland/Libraries/LibWeb/HTML/Navigable.h b/Userland/Libraries/LibWeb/HTML/Navigable.h index 7b4e8b6456..c753d7e0f7 100644 --- a/Userland/Libraries/LibWeb/HTML/Navigable.h +++ b/Userland/Libraries/LibWeb/HTML/Navigable.h @@ -179,6 +179,8 @@ public: }; void paint(Painting::RecordingPainter&, PaintConfig); + void set_needs_to_resolve_paint_only_properties() { m_needs_to_resolve_paint_only_properties = true; } + protected: Navigable(); @@ -220,6 +222,8 @@ private: CSSPixelSize m_size; CSSPixelPoint m_viewport_scroll_offset; + + bool m_needs_to_resolve_paint_only_properties { true }; }; HashTable& all_navigables(); diff --git a/Userland/Libraries/LibWeb/Layout/LayoutState.cpp b/Userland/Libraries/LibWeb/Layout/LayoutState.cpp index 886524f794..c37095d976 100644 --- a/Userland/Libraries/LibWeb/Layout/LayoutState.cpp +++ b/Userland/Libraries/LibWeb/Layout/LayoutState.cpp @@ -197,252 +197,6 @@ 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 = (s_top != 0) ? min(f, l_top / s_top) : f; - f = (s_right != 0) ? min(f, l_right / s_right) : f; - f = (s_bottom != 0) ? min(f, l_bottom / s_bottom) : f; - f = (s_left != 0) ? min(f, l_left / s_left) : f; - - // 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_layout_dependent_properties() -{ - // Resolves layout-dependent properties not handled during layout and stores them in the paint tree. - // Properties resolved include: - // - Border radii - // - Box shadows - // - Text shadows - // - Transforms - // - Transform origins - - for (auto& it : used_values_per_layout_node) { - auto& used_values = *it.value; - auto& node = const_cast(used_values.node()); - - auto* paintable = node.paintable(); - auto const is_inline_paintable = paintable && paintable->is_inline_paintable(); - auto const is_paintable_box = paintable && paintable->is_paintable_box(); - auto const is_paintable_with_lines = paintable && paintable->is_paintable_with_lines(); - - // Border radii - if (is_inline_paintable) { - auto& inline_paintable = static_cast(*paintable); - auto& fragments = inline_paintable.fragments(); - - 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); - } - } - - // Border radii - if (is_paintable_box) { - auto& paintable_box = static_cast(*paintable); - - CSSPixelRect const border_rect { 0, 0, used_values.border_box_width(), used_values.border_box_height() }; - - 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); - } - - // Box shadows - auto const& box_shadow_data = node.computed_values().box_shadow(); - if (!box_shadow_data.is_empty()) { - Vector resolved_box_shadow_data; - resolved_box_shadow_data.ensure_capacity(box_shadow_data.size()); - for (auto const& layer : box_shadow_data) { - resolved_box_shadow_data.empend( - layer.color, - layer.offset_x.to_px(node), - layer.offset_y.to_px(node), - layer.blur_radius.to_px(node), - layer.spread_distance.to_px(node), - layer.placement == CSS::ShadowPlacement::Outer ? Painting::ShadowPlacement::Outer : Painting::ShadowPlacement::Inner); - } - - if (paintable && is(*paintable)) { - auto& paintable_box = static_cast(*paintable); - paintable_box.set_box_shadow_data(move(resolved_box_shadow_data)); - } else if (paintable && is(*paintable)) { - auto& inline_paintable = static_cast(*paintable); - inline_paintable.set_box_shadow_data(move(resolved_box_shadow_data)); - } else { - VERIFY_NOT_REACHED(); - } - } - - // Text shadows - if (is_paintable_with_lines) { - auto const& paintable_with_lines = static_cast(*paintable); - for (auto const& fragment : paintable_with_lines.fragments()) { - auto const& text_shadow = fragment.m_layout_node->computed_values().text_shadow(); - if (!text_shadow.is_empty()) { - Vector resolved_shadow_data; - resolved_shadow_data.ensure_capacity(text_shadow.size()); - for (auto const& layer : text_shadow) { - resolved_shadow_data.empend( - layer.color, - layer.offset_x.to_px(paintable_with_lines.layout_node()), - layer.offset_y.to_px(paintable_with_lines.layout_node()), - layer.blur_radius.to_px(paintable_with_lines.layout_node()), - layer.spread_distance.to_px(paintable_with_lines.layout_node()), - Painting::ShadowPlacement::Outer); - } - const_cast(fragment).set_shadows(move(resolved_shadow_data)); - } - } - } - - // Transform and transform origin - if (is_paintable_box) { - auto& paintable_box = static_cast(*paintable); - auto const& transformations = paintable_box.computed_values().transformations(); - if (!transformations.is_empty()) { - auto matrix = Gfx::FloatMatrix4x4::identity(); - for (auto const& transform : transformations) - matrix = matrix * transform.to_matrix(paintable_box).release_value(); - paintable_box.set_transform(matrix); - } - - auto const& transform_origin = paintable_box.computed_values().transform_origin(); - // https://www.w3.org/TR/css-transforms-1/#transform-box - auto transform_box = paintable_box.computed_values().transform_box(); - // For SVG elements without associated CSS layout box, the used value for content-box is fill-box and for - // border-box is stroke-box. - // FIXME: This currently detects any SVG element except the one. Is that correct? - // And is it correct to use `else` below? - if (is(paintable_box)) { - switch (transform_box) { - case CSS::TransformBox::ContentBox: - transform_box = CSS::TransformBox::FillBox; - break; - case CSS::TransformBox::BorderBox: - transform_box = CSS::TransformBox::StrokeBox; - break; - default: - break; - } - } - // For elements with associated CSS layout box, the used value for fill-box is content-box and for - // stroke-box and view-box is border-box. - else { - switch (transform_box) { - case CSS::TransformBox::FillBox: - transform_box = CSS::TransformBox::ContentBox; - break; - case CSS::TransformBox::StrokeBox: - case CSS::TransformBox::ViewBox: - transform_box = CSS::TransformBox::BorderBox; - break; - default: - break; - } - } - - CSSPixelRect reference_box = [&]() { - switch (transform_box) { - case CSS::TransformBox::ContentBox: - // Uses the content box as reference box. - // FIXME: The reference box of a table is the border box of its table wrapper box, not its table box. - return paintable_box.absolute_rect(); - case CSS::TransformBox::BorderBox: - // Uses the border box as reference box. - // FIXME: The reference box of a table is the border box of its table wrapper box, not its table box. - return paintable_box.absolute_border_box_rect(); - case CSS::TransformBox::FillBox: - // Uses the object bounding box as reference box. - // FIXME: For now we're using the content rect as an approximation. - return paintable_box.absolute_rect(); - case CSS::TransformBox::StrokeBox: - // Uses the stroke bounding box as reference box. - // FIXME: For now we're using the border rect as an approximation. - return paintable_box.absolute_border_box_rect(); - case CSS::TransformBox::ViewBox: - // Uses the nearest SVG viewport as reference box. - // FIXME: If a viewBox attribute is specified for the SVG viewport creating element: - // - The reference box is positioned at the origin of the coordinate system established by the viewBox attribute. - // - The dimension of the reference box is set to the width and height values of the viewBox attribute. - auto* svg_paintable = paintable_box.first_ancestor_of_type(); - if (!svg_paintable) - return paintable_box.absolute_border_box_rect(); - return svg_paintable->absolute_rect(); - } - VERIFY_NOT_REACHED(); - }(); - auto x = reference_box.left() + transform_origin.x.to_px(node, reference_box.width()); - auto y = reference_box.top() + transform_origin.y.to_px(node, reference_box.height()); - paintable_box.set_transform_origin({ x, y }); - paintable_box.set_transform_origin({ x, y }); - } - } -} - void LayoutState::commit(Box& root) { // Only the top-level LayoutState should ever be committed. @@ -599,8 +353,6 @@ void LayoutState::commit(Box& root) auto const& box = static_cast(used_values.node()); measure_scrollable_overflow(box); } - - resolve_layout_dependent_properties(); } 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 5a87f2402b..7c8ed97549 100644 --- a/Userland/Libraries/LibWeb/Layout/LayoutState.h +++ b/Userland/Libraries/LibWeb/Layout/LayoutState.h @@ -189,7 +189,6 @@ struct LayoutState { private: void resolve_relative_positions(); - void resolve_layout_dependent_properties(); }; } diff --git a/Userland/Libraries/LibWeb/Painting/Paintable.h b/Userland/Libraries/LibWeb/Painting/Paintable.h index fd7c8449e9..851ad5d3ab 100644 --- a/Userland/Libraries/LibWeb/Painting/Paintable.h +++ b/Userland/Libraries/LibWeb/Painting/Paintable.h @@ -88,6 +88,18 @@ public: return TraversalDecision::Continue; } + template + TraversalDecision for_each_in_inclusive_subtree(Callback callback) + { + if (auto decision = callback(*this); decision != TraversalDecision::Continue) + return decision; + for (auto* child = first_child(); child; child = child->next_sibling()) { + if (child->for_each_in_inclusive_subtree(callback) == TraversalDecision::Break) + return TraversalDecision::Break; + } + return TraversalDecision::Continue; + } + template TraversalDecision for_each_in_inclusive_subtree(Callback callback) const { diff --git a/Userland/Libraries/LibWeb/Painting/PaintableFragment.h b/Userland/Libraries/LibWeb/Painting/PaintableFragment.h index d1b1684627..e0813aaca4 100644 --- a/Userland/Libraries/LibWeb/Painting/PaintableFragment.h +++ b/Userland/Libraries/LibWeb/Painting/PaintableFragment.h @@ -14,7 +14,7 @@ namespace Web::Painting { class PaintableFragment { - friend struct Layout::LayoutState; + friend class ViewportPaintable; public: explicit PaintableFragment(Layout::LineBoxFragment const&); diff --git a/Userland/Libraries/LibWeb/Painting/ViewportPaintable.cpp b/Userland/Libraries/LibWeb/Painting/ViewportPaintable.cpp index c7bd5df984..f2a8f449fb 100644 --- a/Userland/Libraries/LibWeb/Painting/ViewportPaintable.cpp +++ b/Userland/Libraries/LibWeb/Painting/ViewportPaintable.cpp @@ -5,6 +5,8 @@ */ #include +#include +#include #include #include @@ -153,4 +155,256 @@ void ViewportPaintable::assign_clip_rectangles() }); } +static Painting::BorderRadiiData normalize_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 = (s_top != 0) ? min(f, l_top / s_top) : f; + f = (s_right != 0) ? min(f, l_right / s_right) : f; + f = (s_bottom != 0) ? min(f, l_bottom / s_bottom) : f; + f = (s_left != 0) ? min(f, l_left / s_left) : f; + + // 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 ViewportPaintable::resolve_paint_only_properties() +{ + // Resolves layout-dependent properties not handled during layout and stores them in the paint tree. + // Properties resolved include: + // - Border radii + // - Box shadows + // - Text shadows + // - Transforms + // - Transform origins + for_each_in_inclusive_subtree([&](Paintable& paintable) { + auto& node = paintable.layout_node(); + + auto const is_inline_paintable = paintable.is_inline_paintable(); + auto const is_paintable_box = paintable.is_paintable_box(); + auto const is_paintable_with_lines = paintable.is_paintable_with_lines(); + + // Border radii + if (is_inline_paintable) { + auto& inline_paintable = static_cast(paintable); + auto& fragments = inline_paintable.fragments(); + + 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 = normalize_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); + } + } + + // Border radii + if (is_paintable_box) { + auto& paintable_box = static_cast(paintable); + + CSSPixelRect const border_rect { 0, 0, paintable_box.border_box_width(), paintable_box.border_box_height() }; + 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 = normalize_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); + } + + // Box shadows + auto const& box_shadow_data = node.computed_values().box_shadow(); + if (!box_shadow_data.is_empty()) { + Vector resolved_box_shadow_data; + resolved_box_shadow_data.ensure_capacity(box_shadow_data.size()); + for (auto const& layer : box_shadow_data) { + resolved_box_shadow_data.empend( + layer.color, + layer.offset_x.to_px(node), + layer.offset_y.to_px(node), + layer.blur_radius.to_px(node), + layer.spread_distance.to_px(node), + layer.placement == CSS::ShadowPlacement::Outer ? Painting::ShadowPlacement::Outer + : Painting::ShadowPlacement::Inner); + } + + if (is(paintable)) { + auto& paintable_box = static_cast(paintable); + paintable_box.set_box_shadow_data(move(resolved_box_shadow_data)); + } else if (is(paintable)) { + auto& inline_paintable = static_cast(paintable); + inline_paintable.set_box_shadow_data(move(resolved_box_shadow_data)); + } + } + + // Text shadows + if (is_paintable_with_lines) { + auto const& paintable_with_lines = static_cast(paintable); + for (auto const& fragment : paintable_with_lines.fragments()) { + auto const& text_shadow = fragment.m_layout_node->computed_values().text_shadow(); + if (!text_shadow.is_empty()) { + Vector resolved_shadow_data; + resolved_shadow_data.ensure_capacity(text_shadow.size()); + for (auto const& layer : text_shadow) { + resolved_shadow_data.empend( + layer.color, + layer.offset_x.to_px(paintable_with_lines.layout_node()), + layer.offset_y.to_px(paintable_with_lines.layout_node()), + layer.blur_radius.to_px(paintable_with_lines.layout_node()), + layer.spread_distance.to_px(paintable_with_lines.layout_node()), + Painting::ShadowPlacement::Outer); + } + const_cast(fragment).set_shadows(move(resolved_shadow_data)); + } + } + } + + // Transform and transform origin + if (is_paintable_box) { + auto& paintable_box = static_cast(paintable); + auto const& transformations = paintable_box.computed_values().transformations(); + if (!transformations.is_empty()) { + auto matrix = Gfx::FloatMatrix4x4::identity(); + for (auto const& transform : transformations) + matrix = matrix * transform.to_matrix(paintable_box).release_value(); + paintable_box.set_transform(matrix); + } + + auto const& transform_origin = paintable_box.computed_values().transform_origin(); + // https://www.w3.org/TR/css-transforms-1/#transform-box + auto transform_box = paintable_box.computed_values().transform_box(); + // For SVG elements without associated CSS layout box, the used value for content-box is fill-box and for + // border-box is stroke-box. + // FIXME: This currently detects any SVG element except the one. Is that correct? + // And is it correct to use `else` below? + if (is(paintable_box)) { + switch (transform_box) { + case CSS::TransformBox::ContentBox: + transform_box = CSS::TransformBox::FillBox; + break; + case CSS::TransformBox::BorderBox: + transform_box = CSS::TransformBox::StrokeBox; + break; + default: + break; + } + } + // For elements with associated CSS layout box, the used value for fill-box is content-box and for + // stroke-box and view-box is border-box. + else { + switch (transform_box) { + case CSS::TransformBox::FillBox: + transform_box = CSS::TransformBox::ContentBox; + break; + case CSS::TransformBox::StrokeBox: + case CSS::TransformBox::ViewBox: + transform_box = CSS::TransformBox::BorderBox; + break; + default: + break; + } + } + + CSSPixelRect reference_box = [&]() { + switch (transform_box) { + case CSS::TransformBox::ContentBox: + // Uses the content box as reference box. + // FIXME: The reference box of a table is the border box of its table wrapper box, not its table box. + return paintable_box.absolute_rect(); + case CSS::TransformBox::BorderBox: + // Uses the border box as reference box. + // FIXME: The reference box of a table is the border box of its table wrapper box, not its table box. + return paintable_box.absolute_border_box_rect(); + case CSS::TransformBox::FillBox: + // Uses the object bounding box as reference box. + // FIXME: For now we're using the content rect as an approximation. + return paintable_box.absolute_rect(); + case CSS::TransformBox::StrokeBox: + // Uses the stroke bounding box as reference box. + // FIXME: For now we're using the border rect as an approximation. + return paintable_box.absolute_border_box_rect(); + case CSS::TransformBox::ViewBox: + // Uses the nearest SVG viewport as reference box. + // FIXME: If a viewBox attribute is specified for the SVG viewport creating element: + // - The reference box is positioned at the origin of the coordinate system established by the viewBox attribute. + // - The dimension of the reference box is set to the width and height values of the viewBox attribute. + auto* svg_paintable = paintable_box.first_ancestor_of_type(); + if (!svg_paintable) + return paintable_box.absolute_border_box_rect(); + return svg_paintable->absolute_rect(); + } + VERIFY_NOT_REACHED(); + }(); + auto x = reference_box.left() + transform_origin.x.to_px(node, reference_box.width()); + auto y = reference_box.top() + transform_origin.y.to_px(node, reference_box.height()); + paintable_box.set_transform_origin({ x, y }); + paintable_box.set_transform_origin({ x, y }); + } + + return TraversalDecision::Continue; + }); +} + } diff --git a/Userland/Libraries/LibWeb/Painting/ViewportPaintable.h b/Userland/Libraries/LibWeb/Painting/ViewportPaintable.h index 53d10ab12f..a246dcd969 100644 --- a/Userland/Libraries/LibWeb/Painting/ViewportPaintable.h +++ b/Userland/Libraries/LibWeb/Painting/ViewportPaintable.h @@ -26,6 +26,7 @@ public: }; void assign_scroll_frame_ids(HashMap&) const; void assign_clip_rectangles(); + void resolve_paint_only_properties(); private: void build_stacking_context_tree();