1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-28 07:27:45 +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

@ -30,15 +30,11 @@ Layout::SVGGeometryBox const& SVGGeometryPaintable::layout_box() const
Optional<HitTestResult> 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<float>()))
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<SVG::SVGGeometryElement&>(geometry_element).get_path().bounding_box());
if (!transformed_bounding_box.contains(position.to_type<float>()))
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<int>().to_type<float>();
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<SVG::SVGGeometryElement&>(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
};

View file

@ -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<Gfx::AffineTransform const&> 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<SVGGeometryPaintable> create(Layout::SVGGeometryBox const&);
virtual Optional<HitTestResult> 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<PathData> const& path_data() const { return m_path_data; }
protected:
SVGGeometryPaintable(Layout::SVGGeometryBox const&);
Optional<PathData> m_path_data = {};
};
}