diff --git a/Tests/LibWeb/Ref/reference/simple-svg-mask-ref.html b/Tests/LibWeb/Ref/reference/simple-svg-mask-ref.html index 1b879e142c..dcdc1df625 100644 --- a/Tests/LibWeb/Ref/reference/simple-svg-mask-ref.html +++ b/Tests/LibWeb/Ref/reference/simple-svg-mask-ref.html @@ -1,4 +1,4 @@ - + diff --git a/Tests/LibWeb/Ref/simple-svg-mask.html b/Tests/LibWeb/Ref/simple-svg-mask.html index 0aeecdf337..4cf89f669e 100644 --- a/Tests/LibWeb/Ref/simple-svg-mask.html +++ b/Tests/LibWeb/Ref/simple-svg-mask.html @@ -1,5 +1,5 @@ - + diff --git a/Tests/LibWeb/Ref/svg-alpha-mask.html b/Tests/LibWeb/Ref/svg-alpha-mask.html index c6172a2cd6..f3b8be4e05 100644 --- a/Tests/LibWeb/Ref/svg-alpha-mask.html +++ b/Tests/LibWeb/Ref/svg-alpha-mask.html @@ -1,5 +1,5 @@ - + diff --git a/Tests/LibWeb/Ref/svg-mask-in-defs.html b/Tests/LibWeb/Ref/svg-mask-in-defs.html index c17d67b90c..277c69fc3b 100644 --- a/Tests/LibWeb/Ref/svg-mask-in-defs.html +++ b/Tests/LibWeb/Ref/svg-mask-in-defs.html @@ -1,5 +1,5 @@ - + diff --git a/Tests/LibWeb/Ref/svg-mask-maskUnits-userSpaceOnUse.html b/Tests/LibWeb/Ref/svg-mask-maskUnits-userSpaceOnUse.html index 1f0f521739..7ba529188b 100644 --- a/Tests/LibWeb/Ref/svg-mask-maskUnits-userSpaceOnUse.html +++ b/Tests/LibWeb/Ref/svg-mask-maskUnits-userSpaceOnUse.html @@ -1,5 +1,5 @@ - + diff --git a/Userland/Libraries/LibWeb/Layout/LayoutState.cpp b/Userland/Libraries/LibWeb/Layout/LayoutState.cpp index 75a7d67a5e..bf439b4bb6 100644 --- a/Userland/Libraries/LibWeb/Layout/LayoutState.cpp +++ b/Userland/Libraries/LibWeb/Layout/LayoutState.cpp @@ -265,6 +265,11 @@ void LayoutState::commit(Box& root) paintable_with_lines.set_line_boxes(move(used_values.line_boxes)); paintables_with_lines.append(paintable_with_lines); } + + if (used_values.svg_path_data().has_value() && is(paintable_box)) { + auto& svg_geometry_paintable = static_cast(paintable_box); + svg_geometry_paintable.set_path_data(move(*used_values.svg_path_data())); + } } } diff --git a/Userland/Libraries/LibWeb/Layout/LayoutState.h b/Userland/Libraries/LibWeb/Layout/LayoutState.h index ef09813f47..087cec7e60 100644 --- a/Userland/Libraries/LibWeb/Layout/LayoutState.h +++ b/Userland/Libraries/LibWeb/Layout/LayoutState.h @@ -11,6 +11,7 @@ #include #include #include +#include namespace Web::Layout { @@ -123,6 +124,9 @@ struct LayoutState { void set_table_cell_coordinates(Painting::PaintableBox::TableCellCoordinates const& table_cell_coordinates) { m_table_cell_coordinates = table_cell_coordinates; } auto const& table_cell_coordinates() const { return m_table_cell_coordinates; } + void set_svg_path_data(Painting::SVGGeometryPaintable::PathData const& svg_path_data) { m_svg_path_data = svg_path_data; } + auto& svg_path_data() const { return m_svg_path_data; } + private: AvailableSize available_width_inside() const; AvailableSize available_height_inside() const; @@ -146,6 +150,8 @@ struct LayoutState { Optional m_override_borders_data; Optional m_table_cell_coordinates; + + Optional m_svg_path_data; }; // Commits the used values produced by layout and builds a paintable tree. diff --git a/Userland/Libraries/LibWeb/Layout/SVGFormattingContext.cpp b/Userland/Libraries/LibWeb/Layout/SVGFormattingContext.cpp index 212b6f5827..9e5d43cba9 100644 --- a/Userland/Libraries/LibWeb/Layout/SVGFormattingContext.cpp +++ b/Userland/Libraries/LibWeb/Layout/SVGFormattingContext.cpp @@ -173,7 +173,8 @@ void SVGFormattingContext::run(Box const& box, LayoutMode layout_mode, Available auto& dom_node = const_cast(geometry_box).dom_node(); auto& path = dom_node.get_path(); - auto path_transform = dom_node.get_transform(); + auto svg_transform = dom_node.get_transform(); + Gfx::AffineTransform viewbox_transform; double viewbox_scale = 1; auto maybe_view_box = dom_node.view_box(); @@ -190,18 +191,21 @@ void SVGFormattingContext::run(Box const& box, LayoutMode layout_mode, Available // The initial value for preserveAspectRatio is xMidYMid meet. auto preserve_aspect_ratio = svg_svg_element.preserve_aspect_ratio().value_or(SVG::PreserveAspectRatio {}); - auto viewbox_transform = scale_and_align_viewbox_content(preserve_aspect_ratio, view_box, { scale_width, scale_height }, svg_box_state); - path_transform = Gfx::AffineTransform {}.translate(viewbox_transform.offset.to_type()).scale(viewbox_transform.scale_factor, viewbox_transform.scale_factor).translate({ -view_box.min_x, -view_box.min_y }).multiply(path_transform); - viewbox_scale = viewbox_transform.scale_factor; + auto viewbox_offset_and_scale = scale_and_align_viewbox_content(preserve_aspect_ratio, view_box, { scale_width, scale_height }, svg_box_state); + viewbox_transform = Gfx::AffineTransform {}.translate(viewbox_offset_and_scale.offset.to_type()).scale(viewbox_offset_and_scale.scale_factor, viewbox_offset_and_scale.scale_factor).translate({ -view_box.min_x, -view_box.min_y }); + + viewbox_scale = viewbox_offset_and_scale.scale_factor; } // Stroke increases the path's size by stroke_width/2 per side. + auto path_transform = Gfx::AffineTransform {}.multiply(viewbox_transform).multiply(svg_transform); auto path_bounding_box = path_transform.map(path.bounding_box()).to_type(); CSSPixels stroke_width = CSSPixels::nearest_value_for(static_cast(geometry_box.dom_node().visible_stroke_width()) * viewbox_scale); path_bounding_box.inflate(stroke_width, stroke_width); geometry_box_state.set_content_offset(path_bounding_box.top_left()); geometry_box_state.set_content_width(path_bounding_box.width()); geometry_box_state.set_content_height(path_bounding_box.height()); + geometry_box_state.set_svg_path_data(Painting::SVGGeometryPaintable::PathData(path, viewbox_transform, svg_transform)); } else if (is(descendant)) { SVGFormattingContext nested_context(m_state, static_cast(descendant), this); nested_context.run(static_cast(descendant), layout_mode, available_space); diff --git a/Userland/Libraries/LibWeb/Layout/SVGGeometryBox.cpp b/Userland/Libraries/LibWeb/Layout/SVGGeometryBox.cpp index 0341ac86c3..88b0dc13ae 100644 --- a/Userland/Libraries/LibWeb/Layout/SVGGeometryBox.cpp +++ b/Userland/Libraries/LibWeb/Layout/SVGGeometryBox.cpp @@ -17,43 +17,6 @@ SVGGeometryBox::SVGGeometryBox(DOM::Document& document, SVG::SVGGeometryElement& { } -CSSPixelPoint SVGGeometryBox::viewbox_origin() const -{ - auto* svg_box = dom_node().shadow_including_first_ancestor_of_type(); - if (!svg_box || !svg_box->view_box().has_value()) - return { 0, 0 }; - return { svg_box->view_box().value().min_x, svg_box->view_box().value().min_y }; -} - -Optional SVGGeometryBox::layout_transform(Gfx::AffineTransform additional_svg_transform) const -{ - auto& geometry_element = dom_node(); - auto transform = geometry_element.get_transform(); - auto* svg_box = geometry_element.shadow_including_first_ancestor_of_type(); - double scaling = 1; - auto origin = viewbox_origin().to_type(); - Gfx::FloatPoint paint_offset = {}; - if (svg_box && geometry_element.view_box().has_value()) { - // Note: SVGFormattingContext has already done the scaling based on the viewbox, - // we now have to derive what it was from the original bounding box size. - // FIXME: It would be nice if we could store the transform from layout somewhere, so we don't have to solve for it here. - auto original_bounding_box = Gfx::AffineTransform {}.translate(-origin).multiply(transform).map(const_cast(geometry_element).get_path().bounding_box()); - float stroke_width = geometry_element.visible_stroke_width(); - original_bounding_box.inflate(stroke_width, stroke_width); - // If the transform (or path) results in a empty box we can't display this. - if (original_bounding_box.is_empty()) - return {}; - auto scaled_width = paintable_box()->content_width().to_double(); - auto scaled_height = paintable_box()->content_height().to_double(); - scaling = min(scaled_width / static_cast(original_bounding_box.width()), scaled_height / static_cast(original_bounding_box.height())); - auto scaled_bounding_box = original_bounding_box.scaled(scaling, scaling); - paint_offset = (paintable_box()->absolute_rect().location() - svg_box->paintable_box()->absolute_rect().location()).to_type() - scaled_bounding_box.location(); - } - // Note: The "additional_svg_transform" is applied during mask painting to transform the mask element to match its target. - // It has to be applied while still in the SVG coordinate space. - return Gfx::AffineTransform {}.translate(paint_offset).scale(scaling, scaling).translate(-origin).multiply(additional_svg_transform).multiply(transform); -} - JS::GCPtr SVGGeometryBox::create_paintable() const { return Painting::SVGGeometryPaintable::create(*this); diff --git a/Userland/Libraries/LibWeb/Layout/SVGGeometryBox.h b/Userland/Libraries/LibWeb/Layout/SVGGeometryBox.h index ac615bbd5b..9e198f319e 100644 --- a/Userland/Libraries/LibWeb/Layout/SVGGeometryBox.h +++ b/Userland/Libraries/LibWeb/Layout/SVGGeometryBox.h @@ -22,13 +22,9 @@ public: SVG::SVGGeometryElement& dom_node() { return static_cast(SVGGraphicsBox::dom_node()); } SVG::SVGGeometryElement const& dom_node() const { return static_cast(SVGGraphicsBox::dom_node()); } - Optional layout_transform(Gfx::AffineTransform additional_svg_transform) const; - virtual JS::GCPtr create_paintable() const override; private: - CSSPixelPoint viewbox_origin() const; - virtual bool is_svg_geometry_box() const final { return true; } }; diff --git a/Userland/Libraries/LibWeb/Painting/SVGGeometryPaintable.cpp b/Userland/Libraries/LibWeb/Painting/SVGGeometryPaintable.cpp index 0a3d8eac8b..2f6737cbda 100644 --- a/Userland/Libraries/LibWeb/Painting/SVGGeometryPaintable.cpp +++ b/Userland/Libraries/LibWeb/Painting/SVGGeometryPaintable.cpp @@ -30,15 +30,11 @@ Layout::SVGGeometryBox const& SVGGeometryPaintable::layout_box() const Optional SVGGeometryPaintable::hit_test(CSSPixelPoint position, HitTestType type) const { auto result = SVGGraphicsPaintable::hit_test(position, type); - if (!result.has_value()) + if (!result.has_value() || !path_data().has_value()) + return {}; + auto transformed_bounding_box = path_data()->svg_to_css_pixels_transform().map_to_quad(path_data()->computed_path().bounding_box()); + if (!transformed_bounding_box.contains(position.to_type())) return {}; - auto& geometry_element = layout_box().dom_node(); - if (auto transform = layout_box().layout_transform({}); transform.has_value()) { - auto transformed_bounding_box = transform->map_to_quad( - const_cast(geometry_element).get_path().bounding_box()); - if (!transformed_bounding_box.contains(position.to_type())) - return {}; - } return result; } @@ -56,7 +52,7 @@ static Gfx::Painter::WindingRule to_gfx_winding_rule(SVG::FillRule fill_rule) void SVGGeometryPaintable::paint(PaintContext& context, PaintPhase phase) const { - if (!is_visible()) + if (!is_visible() || !path_data().has_value()) return; SVGGraphicsPaintable::paint(context, phase); @@ -73,17 +69,10 @@ void SVGGeometryPaintable::paint(PaintContext& context, PaintPhase phase) const RecordingPainterStateSaver save_painter { context.painter() }; auto offset = context.floored_device_point(svg_element_rect.location()).to_type().to_type(); - auto maybe_view_box = geometry_element.view_box(); - auto transform = layout_box().layout_transform(context.svg_transform()); - if (!transform.has_value()) - return; - - auto css_scale = context.device_pixels_per_css_pixel(); - auto paint_transform = Gfx::AffineTransform {}.scale(css_scale, css_scale).multiply(*transform); - auto const& original_path = const_cast(geometry_element).get_path(); - Gfx::Path path = original_path.copy_transformed(paint_transform); + auto paint_transform = path_data()->svg_to_device_pixels_transform(context, context.svg_transform()); + Gfx::Path path = path_data()->computed_path().copy_transformed(paint_transform); // Fills are computed as though all subpaths are closed (https://svgwg.org/svg2-draft/painting.html#FillProperties) auto closed_path = [&] { @@ -106,7 +95,7 @@ void SVGGeometryPaintable::paint(PaintContext& context, PaintPhase phase) const SVG::SVGPaintContext paint_context { .viewport = svg_viewport, - .path_bounding_box = original_path.bounding_box(), + .path_bounding_box = path_data()->computed_path().bounding_box(), .transform = paint_transform }; diff --git a/Userland/Libraries/LibWeb/Painting/SVGGeometryPaintable.h b/Userland/Libraries/LibWeb/Painting/SVGGeometryPaintable.h index 6da035091d..db3f5b9a04 100644 --- a/Userland/Libraries/LibWeb/Painting/SVGGeometryPaintable.h +++ b/Userland/Libraries/LibWeb/Painting/SVGGeometryPaintable.h @@ -15,6 +15,41 @@ class SVGGeometryPaintable final : public SVGGraphicsPaintable { JS_CELL(SVGGeometryPaintable, SVGGraphicsPaintable); public: + class PathData { + public: + PathData(Gfx::Path path, Gfx::AffineTransform svg_to_viewbox_transform, Gfx::AffineTransform svg_transform) + : m_computed_path(move(path)) + , m_svg_to_viewbox_transform(svg_to_viewbox_transform) + , m_svg_transform(svg_transform) + { + } + + Gfx::Path const& computed_path() const { return m_computed_path; } + + Gfx::AffineTransform const& svg_to_viewbox_transform() const { return m_svg_to_viewbox_transform; } + + Gfx::AffineTransform const& svg_transform() const { return m_svg_transform; } + + Gfx::AffineTransform svg_to_css_pixels_transform( + Optional additional_svg_transform = {}) const + { + return Gfx::AffineTransform {}.multiply(svg_to_viewbox_transform()).multiply(additional_svg_transform.value_or(Gfx::AffineTransform {})).multiply(svg_transform()); + } + + Gfx::AffineTransform svg_to_device_pixels_transform( + PaintContext const& context, + Gfx::AffineTransform const& additional_svg_transform) const + { + auto css_scale = context.device_pixels_per_css_pixel(); + return Gfx::AffineTransform {}.scale({ css_scale, css_scale }).multiply(svg_to_css_pixels_transform(additional_svg_transform)); + } + + private: + Gfx::Path m_computed_path; + Gfx::AffineTransform m_svg_to_viewbox_transform; + Gfx::AffineTransform m_svg_transform; + }; + static JS::NonnullGCPtr create(Layout::SVGGeometryBox const&); virtual Optional hit_test(CSSPixelPoint, HitTestType) const override; @@ -23,8 +58,17 @@ public: Layout::SVGGeometryBox const& layout_box() const; + void set_path_data(PathData path_data) + { + m_path_data = move(path_data); + } + + Optional const& path_data() const { return m_path_data; } + protected: SVGGeometryPaintable(Layout::SVGGeometryBox const&); + + Optional m_path_data = {}; }; }