Skip to content

Commit dc9cb44

Browse files
MacDuekalenikaliaksandr
authored andcommitted
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.
1 parent 1931394 commit dc9cb44

12 files changed

+76
-69
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<svg width="200" viewBox="-10 -10 120 120">
1+
<svg width="120" viewBox="-10 -10 120 120">
22
<rect x="-10" y="-10" width="120" height="120" fill="blue" />
33
<rect x="50" y="10" width="40" height="80" fill="red" />
44
</svg>

Tests/LibWeb/Ref/simple-svg-mask.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<link rel="match" href="reference/simple-svg-mask-ref.html" />
2-
<svg width="200" viewBox="-10 -10 120 120">
2+
<svg width="120" viewBox="-10 -10 120 120">
33
<mask id="myMask">
44
<!-- Everything under a white pixel will be visible -->
55
<rect x="0" y="0" width="100" height="100" fill="white" />

Tests/LibWeb/Ref/svg-alpha-mask.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<link rel="match" href="reference/simple-svg-mask-ref.html" />
2-
<svg width="200" viewBox="-10 -10 120 120">
2+
<svg width="120" viewBox="-10 -10 120 120">
33
<defs>
44
<mask id="myMask" style="mask-type:alpha">
55
<!-- Everything solid pixel (alpha=255) will be visible -->

Tests/LibWeb/Ref/svg-mask-in-defs.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<link rel="match" href="reference/simple-svg-mask-ref.html" />
2-
<svg width="200" viewBox="-10 -10 120 120">
2+
<svg width="120" viewBox="-10 -10 120 120">
33
<defs>
44
<mask id="myMask">
55
<!-- Everything under a white pixel will be visible -->

Tests/LibWeb/Ref/svg-mask-maskUnits-userSpaceOnUse.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<link rel="match" href="reference/simple-svg-mask-ref.html" />
2-
<svg width="200" viewBox="-10 -10 120 120">
2+
<svg width="120" viewBox="-10 -10 120 120">
33
<defs>
44
<mask id="myMask" maskUnits="userSpaceOnUse">
55
<!-- Everything under a white pixel will be visible -->

Userland/Libraries/LibWeb/Layout/LayoutState.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,11 @@ void LayoutState::commit(Box& root)
265265
paintable_with_lines.set_line_boxes(move(used_values.line_boxes));
266266
paintables_with_lines.append(paintable_with_lines);
267267
}
268+
269+
if (used_values.svg_path_data().has_value() && is<Painting::SVGGeometryPaintable>(paintable_box)) {
270+
auto& svg_geometry_paintable = static_cast<Painting::SVGGeometryPaintable&>(paintable_box);
271+
svg_geometry_paintable.set_path_data(move(*used_values.svg_path_data()));
272+
}
268273
}
269274
}
270275

Userland/Libraries/LibWeb/Layout/LayoutState.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include <LibWeb/Layout/Box.h>
1212
#include <LibWeb/Layout/LineBox.h>
1313
#include <LibWeb/Painting/PaintableBox.h>
14+
#include <LibWeb/Painting/SVGGeometryPaintable.h>
1415

1516
namespace Web::Layout {
1617

@@ -123,6 +124,9 @@ struct LayoutState {
123124
void set_table_cell_coordinates(Painting::PaintableBox::TableCellCoordinates const& table_cell_coordinates) { m_table_cell_coordinates = table_cell_coordinates; }
124125
auto const& table_cell_coordinates() const { return m_table_cell_coordinates; }
125126

127+
void set_svg_path_data(Painting::SVGGeometryPaintable::PathData const& svg_path_data) { m_svg_path_data = svg_path_data; }
128+
auto& svg_path_data() const { return m_svg_path_data; }
129+
126130
private:
127131
AvailableSize available_width_inside() const;
128132
AvailableSize available_height_inside() const;
@@ -146,6 +150,8 @@ struct LayoutState {
146150

147151
Optional<Painting::PaintableBox::BordersDataWithElementKind> m_override_borders_data;
148152
Optional<Painting::PaintableBox::TableCellCoordinates> m_table_cell_coordinates;
153+
154+
Optional<Painting::SVGGeometryPaintable::PathData> m_svg_path_data;
149155
};
150156

151157
// Commits the used values produced by layout and builds a paintable tree.

Userland/Libraries/LibWeb/Layout/SVGFormattingContext.cpp

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,8 @@ void SVGFormattingContext::run(Box const& box, LayoutMode layout_mode, Available
173173
auto& dom_node = const_cast<SVGGeometryBox&>(geometry_box).dom_node();
174174

175175
auto& path = dom_node.get_path();
176-
auto path_transform = dom_node.get_transform();
176+
auto svg_transform = dom_node.get_transform();
177+
Gfx::AffineTransform viewbox_transform;
177178

178179
double viewbox_scale = 1;
179180
auto maybe_view_box = dom_node.view_box();
@@ -190,18 +191,21 @@ void SVGFormattingContext::run(Box const& box, LayoutMode layout_mode, Available
190191

191192
// The initial value for preserveAspectRatio is xMidYMid meet.
192193
auto preserve_aspect_ratio = svg_svg_element.preserve_aspect_ratio().value_or(SVG::PreserveAspectRatio {});
193-
auto viewbox_transform = scale_and_align_viewbox_content(preserve_aspect_ratio, view_box, { scale_width, scale_height }, svg_box_state);
194-
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);
195-
viewbox_scale = viewbox_transform.scale_factor;
194+
auto viewbox_offset_and_scale = scale_and_align_viewbox_content(preserve_aspect_ratio, view_box, { scale_width, scale_height }, svg_box_state);
195+
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 });
196+
197+
viewbox_scale = viewbox_offset_and_scale.scale_factor;
196198
}
197199

198200
// Stroke increases the path's size by stroke_width/2 per side.
201+
auto path_transform = Gfx::AffineTransform {}.multiply(viewbox_transform).multiply(svg_transform);
199202
auto path_bounding_box = path_transform.map(path.bounding_box()).to_type<CSSPixels>();
200203
CSSPixels stroke_width = CSSPixels::nearest_value_for(static_cast<double>(geometry_box.dom_node().visible_stroke_width()) * viewbox_scale);
201204
path_bounding_box.inflate(stroke_width, stroke_width);
202205
geometry_box_state.set_content_offset(path_bounding_box.top_left());
203206
geometry_box_state.set_content_width(path_bounding_box.width());
204207
geometry_box_state.set_content_height(path_bounding_box.height());
208+
geometry_box_state.set_svg_path_data(Painting::SVGGeometryPaintable::PathData(path, viewbox_transform, svg_transform));
205209
} else if (is<SVGSVGBox>(descendant)) {
206210
SVGFormattingContext nested_context(m_state, static_cast<SVGSVGBox const&>(descendant), this);
207211
nested_context.run(static_cast<SVGSVGBox const&>(descendant), layout_mode, available_space);

Userland/Libraries/LibWeb/Layout/SVGGeometryBox.cpp

Lines changed: 0 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -17,43 +17,6 @@ SVGGeometryBox::SVGGeometryBox(DOM::Document& document, SVG::SVGGeometryElement&
1717
{
1818
}
1919

20-
CSSPixelPoint SVGGeometryBox::viewbox_origin() const
21-
{
22-
auto* svg_box = dom_node().shadow_including_first_ancestor_of_type<SVG::SVGSVGElement>();
23-
if (!svg_box || !svg_box->view_box().has_value())
24-
return { 0, 0 };
25-
return { svg_box->view_box().value().min_x, svg_box->view_box().value().min_y };
26-
}
27-
28-
Optional<Gfx::AffineTransform> SVGGeometryBox::layout_transform(Gfx::AffineTransform additional_svg_transform) const
29-
{
30-
auto& geometry_element = dom_node();
31-
auto transform = geometry_element.get_transform();
32-
auto* svg_box = geometry_element.shadow_including_first_ancestor_of_type<SVG::SVGSVGElement>();
33-
double scaling = 1;
34-
auto origin = viewbox_origin().to_type<float>();
35-
Gfx::FloatPoint paint_offset = {};
36-
if (svg_box && geometry_element.view_box().has_value()) {
37-
// Note: SVGFormattingContext has already done the scaling based on the viewbox,
38-
// we now have to derive what it was from the original bounding box size.
39-
// FIXME: It would be nice if we could store the transform from layout somewhere, so we don't have to solve for it here.
40-
auto original_bounding_box = Gfx::AffineTransform {}.translate(-origin).multiply(transform).map(const_cast<SVG::SVGGeometryElement&>(geometry_element).get_path().bounding_box());
41-
float stroke_width = geometry_element.visible_stroke_width();
42-
original_bounding_box.inflate(stroke_width, stroke_width);
43-
// If the transform (or path) results in a empty box we can't display this.
44-
if (original_bounding_box.is_empty())
45-
return {};
46-
auto scaled_width = paintable_box()->content_width().to_double();
47-
auto scaled_height = paintable_box()->content_height().to_double();
48-
scaling = min(scaled_width / static_cast<double>(original_bounding_box.width()), scaled_height / static_cast<double>(original_bounding_box.height()));
49-
auto scaled_bounding_box = original_bounding_box.scaled(scaling, scaling);
50-
paint_offset = (paintable_box()->absolute_rect().location() - svg_box->paintable_box()->absolute_rect().location()).to_type<float>() - scaled_bounding_box.location();
51-
}
52-
// Note: The "additional_svg_transform" is applied during mask painting to transform the mask element to match its target.
53-
// It has to be applied while still in the SVG coordinate space.
54-
return Gfx::AffineTransform {}.translate(paint_offset).scale(scaling, scaling).translate(-origin).multiply(additional_svg_transform).multiply(transform);
55-
}
56-
5720
JS::GCPtr<Painting::Paintable> SVGGeometryBox::create_paintable() const
5821
{
5922
return Painting::SVGGeometryPaintable::create(*this);

Userland/Libraries/LibWeb/Layout/SVGGeometryBox.h

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,9 @@ class SVGGeometryBox final : public SVGGraphicsBox {
2222
SVG::SVGGeometryElement& dom_node() { return static_cast<SVG::SVGGeometryElement&>(SVGGraphicsBox::dom_node()); }
2323
SVG::SVGGeometryElement const& dom_node() const { return static_cast<SVG::SVGGeometryElement const&>(SVGGraphicsBox::dom_node()); }
2424

25-
Optional<Gfx::AffineTransform> layout_transform(Gfx::AffineTransform additional_svg_transform) const;
26-
2725
virtual JS::GCPtr<Painting::Paintable> create_paintable() const override;
2826

2927
private:
30-
CSSPixelPoint viewbox_origin() const;
31-
3228
virtual bool is_svg_geometry_box() const final { return true; }
3329
};
3430

0 commit comments

Comments
 (0)