|
@@ -2,6 +2,7 @@
|
|
* Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
|
|
* Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
|
|
* Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org>
|
|
* Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org>
|
|
* Copyright (c) 2022, Tobias Christiansen <tobyase@serenityos.org>
|
|
* Copyright (c) 2022, Tobias Christiansen <tobyase@serenityos.org>
|
|
|
|
+ * Copyright (c) 2023, MacDue <macdue@dueutil.tech>
|
|
*
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
*/
|
|
@@ -37,7 +38,8 @@ void SVGFormattingContext::run(Box const& box, LayoutMode, [[maybe_unused]] Avai
|
|
|
|
|
|
auto& svg_svg_element = verify_cast<SVG::SVGSVGElement>(*box.dom_node());
|
|
auto& svg_svg_element = verify_cast<SVG::SVGSVGElement>(*box.dom_node());
|
|
|
|
|
|
- auto root_offset = m_state.get(box).offset;
|
|
|
|
|
|
+ auto svg_box_state = m_state.get(box);
|
|
|
|
+ auto root_offset = svg_box_state.offset;
|
|
|
|
|
|
box.for_each_child_of_type<BlockContainer>([&](BlockContainer const& child_box) {
|
|
box.for_each_child_of_type<BlockContainer>([&](BlockContainer const& child_box) {
|
|
if (is<SVG::SVGForeignObjectElement>(child_box.dom_node())) {
|
|
if (is<SVG::SVGForeignObjectElement>(child_box.dom_node())) {
|
|
@@ -53,48 +55,41 @@ void SVGFormattingContext::run(Box const& box, LayoutMode, [[maybe_unused]] Avai
|
|
box.for_each_in_subtree_of_type<SVGBox>([&](SVGBox const& descendant) {
|
|
box.for_each_in_subtree_of_type<SVGBox>([&](SVGBox const& descendant) {
|
|
if (is<SVGGeometryBox>(descendant)) {
|
|
if (is<SVGGeometryBox>(descendant)) {
|
|
auto const& geometry_box = static_cast<SVGGeometryBox const&>(descendant);
|
|
auto const& geometry_box = static_cast<SVGGeometryBox const&>(descendant);
|
|
-
|
|
|
|
auto& geometry_box_state = m_state.get_mutable(geometry_box);
|
|
auto& geometry_box_state = m_state.get_mutable(geometry_box);
|
|
-
|
|
|
|
auto& dom_node = const_cast<SVGGeometryBox&>(geometry_box).dom_node();
|
|
auto& dom_node = const_cast<SVGGeometryBox&>(geometry_box).dom_node();
|
|
|
|
|
|
- auto& svg_svg_state = m_state.get(static_cast<Box const&>(*svg_svg_element.layout_node()));
|
|
|
|
-
|
|
|
|
- if (svg_svg_state.has_definite_width() && svg_svg_state.has_definite_height()) {
|
|
|
|
- geometry_box_state.set_content_offset({ 0, 0 });
|
|
|
|
- geometry_box_state.set_content_width(svg_svg_state.content_width());
|
|
|
|
- geometry_box_state.set_content_height(svg_svg_state.content_height());
|
|
|
|
- return IterationDecision::Continue;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // FIXME: Allow for one of {width, height} to not be specified}
|
|
|
|
- if (svg_svg_element.has_attribute(HTML::AttributeNames::width)) {
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (svg_svg_element.has_attribute(HTML::AttributeNames::height)) {
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
auto& path = dom_node.get_path();
|
|
auto& path = dom_node.get_path();
|
|
- auto path_bounding_box = path.bounding_box().to_type<CSSPixels>();
|
|
|
|
-
|
|
|
|
- // Stroke increases the path's size by stroke_width/2 per side.
|
|
|
|
- CSSPixels stroke_width = geometry_box.dom_node().stroke_width().value_or(0);
|
|
|
|
- path_bounding_box.inflate(stroke_width, stroke_width);
|
|
|
|
|
|
+ auto transform = dom_node.get_transform();
|
|
|
|
|
|
auto& maybe_view_box = svg_svg_element.view_box();
|
|
auto& maybe_view_box = svg_svg_element.view_box();
|
|
|
|
+ float viewbox_scale = 1.0f;
|
|
|
|
|
|
|
|
+ CSSPixelPoint offset {};
|
|
if (maybe_view_box.has_value()) {
|
|
if (maybe_view_box.has_value()) {
|
|
auto view_box = maybe_view_box.value();
|
|
auto view_box = maybe_view_box.value();
|
|
- CSSPixelPoint viewbox_offset = { view_box.min_x, view_box.min_y };
|
|
|
|
- geometry_box_state.set_content_offset(path_bounding_box.top_left() + viewbox_offset);
|
|
|
|
-
|
|
|
|
- geometry_box_state.set_content_width(view_box.width);
|
|
|
|
- geometry_box_state.set_content_height(view_box.height);
|
|
|
|
-
|
|
|
|
- return IterationDecision::Continue;
|
|
|
|
|
|
+ // FIXME: This should allow just one of width or height to be specified.
|
|
|
|
+ // E.g. We should be able to layout <svg width="100%"> where height is unspecified/auto.
|
|
|
|
+ if (!svg_box_state.has_definite_width() || !svg_box_state.has_definite_height()) {
|
|
|
|
+ dbgln("FIXME: Attempting to layout indefinitely sized SVG with a viewbox -- this likely won't work!");
|
|
|
|
+ }
|
|
|
|
+ auto scale_width = svg_box_state.has_definite_width() ? svg_box_state.content_width().value() / view_box.width : 1;
|
|
|
|
+ auto scale_height = svg_box_state.has_definite_height() ? svg_box_state.content_height().value() / view_box.height : 1;
|
|
|
|
+ viewbox_scale = min(scale_width, scale_height);
|
|
|
|
+
|
|
|
|
+ // Center the viewbox within the SVG element:
|
|
|
|
+ if (svg_box_state.has_definite_width())
|
|
|
|
+ offset.translate_by((svg_box_state.content_width() - (view_box.width * viewbox_scale)) / 2, 0);
|
|
|
|
+ if (svg_box_state.has_definite_height())
|
|
|
|
+ offset.translate_by(0, (svg_box_state.content_height() - (view_box.height * viewbox_scale)) / 2);
|
|
|
|
+
|
|
|
|
+ transform = Gfx::AffineTransform {}.scale(viewbox_scale, viewbox_scale).translate({ -view_box.min_x, -view_box.min_y }).multiply(transform);
|
|
}
|
|
}
|
|
|
|
|
|
- geometry_box_state.set_content_offset(path_bounding_box.top_left());
|
|
|
|
|
|
+ // Stroke increases the path's size by stroke_width/2 per side.
|
|
|
|
+ auto path_bounding_box = transform.map(path.bounding_box()).to_type<CSSPixels>();
|
|
|
|
+ CSSPixels stroke_width = geometry_box.dom_node().stroke_width().value_or(0);
|
|
|
|
+ path_bounding_box.inflate(stroke_width, stroke_width);
|
|
|
|
+ geometry_box_state.set_content_offset(path_bounding_box.top_left() + offset);
|
|
geometry_box_state.set_content_width(path_bounding_box.width());
|
|
geometry_box_state.set_content_width(path_bounding_box.width());
|
|
geometry_box_state.set_content_height(path_bounding_box.height());
|
|
geometry_box_state.set_content_height(path_bounding_box.height());
|
|
}
|
|
}
|