diff --git a/Userland/Libraries/LibWeb/Layout/SVGFormattingContext.cpp b/Userland/Libraries/LibWeb/Layout/SVGFormattingContext.cpp index 6bcdfee60e..00ce1fca2e 100644 --- a/Userland/Libraries/LibWeb/Layout/SVGFormattingContext.cpp +++ b/Userland/Libraries/LibWeb/Layout/SVGFormattingContext.cpp @@ -1,6 +1,7 @@ /* * Copyright (c) 2021, Andreas Kling * Copyright (c) 2022, Sam Atkins + * Copyright (c) 2022, Tobias Christiansen * * SPDX-License-Identifier: BSD-2-Clause */ @@ -8,6 +9,7 @@ #include #include #include +#include namespace Web::Layout { @@ -22,20 +24,57 @@ SVGFormattingContext::~SVGFormattingContext() void SVGFormattingContext::run(Box const& box, LayoutMode) { - box.for_each_in_subtree_of_type([&](auto const& descendant) { + box.for_each_in_subtree_of_type([&](SVGBox const& descendant) { if (is(descendant)) { auto const& geometry_box = static_cast(descendant); - auto& path = const_cast(geometry_box).dom_node().get_path(); - auto bounding_box = path.bounding_box(); + + auto& geometry_box_state = m_state.get_mutable(geometry_box); + + auto& dom_node = const_cast(geometry_box).dom_node(); + + SVG::SVGSVGElement* svg_element = dom_node.first_ancestor_of_type(); + + if (svg_element->has_attribute(HTML::AttributeNames::width) && svg_element->has_attribute(HTML::AttributeNames::width)) { + geometry_box_state.offset = { 0, 0 }; + auto& layout_node = static_cast(*(svg_element->layout_node())); + + // FIXME: Allow for relative lengths here + geometry_box_state.content_width = layout_node.computed_values().width().value().resolved(layout_node, { 0, CSS::Length::Type::Px }).to_px(layout_node); + geometry_box_state.content_height = layout_node.computed_values().height().value().resolved(layout_node, { 0, CSS::Length::Type::Px }).to_px(layout_node); + + return IterationDecision::Continue; + } + + // FIXME: Allow for one of {width, height} to not be specified} + if (svg_element->has_attribute(HTML::AttributeNames::width)) { + } + + if (svg_element->has_attribute(HTML::AttributeNames::height)) { + } + + auto& path = dom_node.get_path(); + auto path_bounding_box = path.bounding_box(); // Stroke increases the path's size by stroke_width/2 per side. auto stroke_width = geometry_box.dom_node().stroke_width().value_or(0); - bounding_box.inflate(stroke_width, stroke_width); + path_bounding_box.inflate(stroke_width, stroke_width); - auto& geometry_box_state = m_state.get_mutable(geometry_box); - geometry_box_state.offset = bounding_box.top_left(); - geometry_box_state.content_width = bounding_box.width(); - geometry_box_state.content_height = bounding_box.height(); + auto& maybe_view_box = svg_element->view_box(); + + if (maybe_view_box.has_value()) { + auto view_box = maybe_view_box.value(); + Gfx::FloatPoint viewbox_offset = { view_box.min_x, view_box.min_y }; + geometry_box_state.offset = path_bounding_box.top_left() + viewbox_offset; + + geometry_box_state.content_width = view_box.width; + geometry_box_state.content_height = view_box.height; + + return IterationDecision::Continue; + } + + geometry_box_state.offset = path_bounding_box.top_left(); + geometry_box_state.content_width = path_bounding_box.width(); + geometry_box_state.content_height = path_bounding_box.height(); } return IterationDecision::Continue; diff --git a/Userland/Libraries/LibWeb/Layout/SVGGeometryBox.cpp b/Userland/Libraries/LibWeb/Layout/SVGGeometryBox.cpp index 0f6c4a774f..6f20b7867f 100644 --- a/Userland/Libraries/LibWeb/Layout/SVGGeometryBox.cpp +++ b/Userland/Libraries/LibWeb/Layout/SVGGeometryBox.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2020, Matthew Olsson + * Copyright (c) 2022, Tobias Christiansen * * SPDX-License-Identifier: BSD-2-Clause */ @@ -8,6 +9,7 @@ #include #include #include +#include namespace Web::Layout { @@ -27,7 +29,6 @@ void SVGGeometryBox::paint(PaintContext& context, PaintPhase phase) return; auto& geometry_element = dom_node(); - auto& path = geometry_element.get_path(); Gfx::AntiAliasingPainter painter { context.painter() }; auto& svg_context = context.svg_context(); @@ -35,6 +36,56 @@ void SVGGeometryBox::paint(PaintContext& context, PaintPhase phase) auto offset = svg_context.svg_element_position(); painter.translate(offset); + SVG::SVGSVGElement* svg_element = geometry_element.first_ancestor_of_type(); + auto maybe_view_box = svg_element->view_box(); + + context.painter().add_clip_rect((Gfx::Rect)absolute_rect()); + + Gfx::Path path = geometry_element.get_path(); + + if (maybe_view_box.has_value()) { + Gfx::Path new_path; + auto scaling = viewbox_scaling(); + auto origin = viewbox_origin(); + + auto transform_point = [&scaling, &origin](Gfx::FloatPoint const& point) -> Gfx::FloatPoint { + auto new_point = point; + new_point.translate_by({ -origin.x(), -origin.y() }); + new_point.scale_by(scaling); + return new_point; + }; + + for (auto& segment : path.segments()) { + switch (segment.type()) { + case Gfx::Segment::Type::Invalid: + break; + case Gfx::Segment::Type::MoveTo: + new_path.move_to(transform_point(segment.point())); + break; + case Gfx::Segment::Type::LineTo: + new_path.line_to(transform_point(segment.point())); + break; + case Gfx::Segment::Type::QuadraticBezierCurveTo: { + auto& quadratic_bezier_segment = static_cast(segment); + new_path.quadratic_bezier_curve_to(transform_point(quadratic_bezier_segment.through()), transform_point(quadratic_bezier_segment.point())); + break; + } + case Gfx::Segment::Type::CubicBezierCurveTo: { + auto& cubic_bezier_segment = static_cast(segment); + new_path.cubic_bezier_curve_to(transform_point(cubic_bezier_segment.through_0()), transform_point(cubic_bezier_segment.through_1()), transform_point(cubic_bezier_segment.point())); + break; + } + case Gfx::Segment::Type::EllipticalArcTo: { + auto& elliptical_arc_segment = static_cast(segment); + new_path.elliptical_arc_to(transform_point(elliptical_arc_segment.point()), elliptical_arc_segment.radii().scaled(scaling, scaling), elliptical_arc_segment.x_axis_rotation(), false, false); + break; + } + } + } + + path = new_path; + } + if (auto fill_color = geometry_element.fill_color().value_or(svg_context.fill_color()); fill_color.alpha() > 0) { // We need to fill the path before applying the stroke, however the filled // path must be closed, whereas the stroke path may not necessary be closed. @@ -57,6 +108,35 @@ void SVGGeometryBox::paint(PaintContext& context, PaintPhase phase) } painter.translate(-offset); + context.painter().clear_clip_rect(); +} + +float SVGGeometryBox::viewbox_scaling() const +{ + auto* svg_box = dom_node().first_ancestor_of_type(); + + if (!svg_box || !svg_box->view_box().has_value()) + return 1; + + auto view_box = svg_box->view_box().value(); + + bool has_specified_width = svg_box->has_attribute(HTML::AttributeNames::width); + auto specified_width = content_width(); + + bool has_specified_height = svg_box->has_attribute(HTML::AttributeNames::height); + auto specified_height = content_height(); + + auto scale_width = has_specified_width ? specified_width / view_box.width : 1; + auto scale_height = has_specified_height ? specified_height / view_box.height : 1; + + return min(scale_width, scale_height); +} +Gfx::FloatPoint SVGGeometryBox::viewbox_origin() const +{ + auto* svg_box = dom_node().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 }; } } diff --git a/Userland/Libraries/LibWeb/Layout/SVGGeometryBox.h b/Userland/Libraries/LibWeb/Layout/SVGGeometryBox.h index 197750d1b5..e53ee44df5 100644 --- a/Userland/Libraries/LibWeb/Layout/SVGGeometryBox.h +++ b/Userland/Libraries/LibWeb/Layout/SVGGeometryBox.h @@ -21,6 +21,9 @@ public: virtual void paint(PaintContext& context, PaintPhase phase) override; + float viewbox_scaling() const; + Gfx::FloatPoint viewbox_origin() const; + private: virtual bool is_svg_geometry_box() const final { return true; } };