1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-27 21:57:35 +00:00

LibWeb: Store computed SVG path data/transforms in LayoutState

This removes the awkward hack to recompute the layout transform at paint
time, and makes it possible for path sizes to be computed during layout.

For example, it's possible to use relative units in SVG shapes (e.g.
<rect>), which can be resolved during layout, but would be hard to
resolve again during painting.
This commit is contained in:
MacDue 2023-10-29 17:08:35 +00:00 committed by Alexander Kalenik
parent 19313945f2
commit dc9cb449b1
12 changed files with 76 additions and 69 deletions

View file

@ -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<Painting::SVGGeometryPaintable>(paintable_box)) {
auto& svg_geometry_paintable = static_cast<Painting::SVGGeometryPaintable&>(paintable_box);
svg_geometry_paintable.set_path_data(move(*used_values.svg_path_data()));
}
}
}

View file

@ -11,6 +11,7 @@
#include <LibWeb/Layout/Box.h>
#include <LibWeb/Layout/LineBox.h>
#include <LibWeb/Painting/PaintableBox.h>
#include <LibWeb/Painting/SVGGeometryPaintable.h>
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<Painting::PaintableBox::BordersDataWithElementKind> m_override_borders_data;
Optional<Painting::PaintableBox::TableCellCoordinates> m_table_cell_coordinates;
Optional<Painting::SVGGeometryPaintable::PathData> m_svg_path_data;
};
// Commits the used values produced by layout and builds a paintable tree.

View file

@ -173,7 +173,8 @@ void SVGFormattingContext::run(Box const& box, LayoutMode layout_mode, Available
auto& dom_node = const_cast<SVGGeometryBox&>(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<float>()).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<float>()).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>();
CSSPixels stroke_width = CSSPixels::nearest_value_for(static_cast<double>(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<SVGSVGBox>(descendant)) {
SVGFormattingContext nested_context(m_state, static_cast<SVGSVGBox const&>(descendant), this);
nested_context.run(static_cast<SVGSVGBox const&>(descendant), layout_mode, available_space);

View file

@ -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<SVG::SVGSVGElement>();
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<Gfx::AffineTransform> 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<SVG::SVGSVGElement>();
double scaling = 1;
auto origin = viewbox_origin().to_type<float>();
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<SVG::SVGGeometryElement&>(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<double>(original_bounding_box.width()), scaled_height / static_cast<double>(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<float>() - 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<Painting::Paintable> SVGGeometryBox::create_paintable() const
{
return Painting::SVGGeometryPaintable::create(*this);

View file

@ -22,13 +22,9 @@ public:
SVG::SVGGeometryElement& dom_node() { return static_cast<SVG::SVGGeometryElement&>(SVGGraphicsBox::dom_node()); }
SVG::SVGGeometryElement const& dom_node() const { return static_cast<SVG::SVGGeometryElement const&>(SVGGraphicsBox::dom_node()); }
Optional<Gfx::AffineTransform> layout_transform(Gfx::AffineTransform additional_svg_transform) const;
virtual JS::GCPtr<Painting::Paintable> create_paintable() const override;
private:
CSSPixelPoint viewbox_origin() const;
virtual bool is_svg_geometry_box() const final { return true; }
};