mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-07-28 11:49:44 +00:00
LibWeb: Lay out SVG <clipPath>
uses
This uses the same trick as done for masks in #23554. Each use of an SVG `<clipPath>` becomes it's own layout subtree rooted at it's user. This allows each use have it's own layout (which allows supporting features such as `clipPathUnits`).
This commit is contained in:
parent
0a061a6a63
commit
c1b5fe61d1
Notes:
sideshowbarker
2024-07-17 08:13:43 +09:00
Author: https://github.com/MacDue
Commit: c1b5fe61d1
Pull-request: https://github.com/SerenityOS/serenity/pull/23736
Issue: https://github.com/SerenityOS/serenity/issues/19648
Issue: https://github.com/SerenityOS/serenity/issues/23006
Reviewed-by: https://github.com/nico
11 changed files with 192 additions and 35 deletions
|
@ -12,12 +12,14 @@
|
|||
#include <LibGfx/Font/ScaledFont.h>
|
||||
#include <LibGfx/TextLayout.h>
|
||||
#include <LibWeb/Layout/BlockFormattingContext.h>
|
||||
#include <LibWeb/Layout/SVGClipBox.h>
|
||||
#include <LibWeb/Layout/SVGFormattingContext.h>
|
||||
#include <LibWeb/Layout/SVGGeometryBox.h>
|
||||
#include <LibWeb/Layout/SVGMaskBox.h>
|
||||
#include <LibWeb/Layout/SVGSVGBox.h>
|
||||
#include <LibWeb/Layout/SVGTextBox.h>
|
||||
#include <LibWeb/Layout/SVGTextPathBox.h>
|
||||
#include <LibWeb/SVG/SVGClipPathElement.h>
|
||||
#include <LibWeb/SVG/SVGForeignObjectElement.h>
|
||||
#include <LibWeb/SVG/SVGGElement.h>
|
||||
#include <LibWeb/SVG/SVGMaskElement.h>
|
||||
|
@ -290,7 +292,7 @@ void SVGFormattingContext::run(Box const& box, LayoutMode layout_mode, Available
|
|||
}();
|
||||
|
||||
for_each_in_subtree(box, [&](Node const& descendant) {
|
||||
if (is<SVGMaskBox>(descendant))
|
||||
if (is<SVGMaskBox>(descendant) || is<SVGClipBox>(descendant))
|
||||
return TraversalDecision::SkipChildrenAndContinue;
|
||||
if (is<SVG::SVGViewport>(descendant.dom_node())) {
|
||||
// Layout for a nested SVG viewport.
|
||||
|
@ -400,8 +402,8 @@ void SVGFormattingContext::run(Box const& box, LayoutMode layout_mode, Available
|
|||
for_each_in_subtree(descendant, [&](Node const& child_of_svg_container) {
|
||||
if (!is<SVGBox>(child_of_svg_container))
|
||||
return TraversalDecision::Continue;
|
||||
// Masks do not change the bounding box of their parents.
|
||||
if (is<SVGMaskBox>(child_of_svg_container))
|
||||
// Masks/clips do not change the bounding box of their parents.
|
||||
if (is<SVGMaskBox>(child_of_svg_container) || is<SVGClipBox>(child_of_svg_container))
|
||||
return TraversalDecision::SkipChildrenAndContinue;
|
||||
auto& box_state = m_state.get(static_cast<SVGBox const&>(child_of_svg_container));
|
||||
bounding_box.add_point(box_state.offset);
|
||||
|
@ -420,27 +422,34 @@ void SVGFormattingContext::run(Box const& box, LayoutMode layout_mode, Available
|
|||
return IterationDecision::Continue;
|
||||
});
|
||||
|
||||
// Lay out masks last (as their parent needs to be sized first).
|
||||
box.for_each_in_subtree_of_type<SVGMaskBox>([&](SVGMaskBox const& mask_box) {
|
||||
auto& mask_state = m_state.get_mutable(static_cast<Box const&>(mask_box));
|
||||
// Lay out masks/clip paths last (as their parent needs to be sized first).
|
||||
for_each_in_subtree(box, [&](Node const& descendant) {
|
||||
SVG::SVGUnits content_units {};
|
||||
if (is<SVGMaskBox>(descendant))
|
||||
content_units = static_cast<SVGMaskBox const&>(descendant).dom_node().mask_content_units();
|
||||
else if (is<SVGClipBox>(descendant))
|
||||
content_units = static_cast<SVGClipBox const&>(descendant).dom_node().clip_path_units();
|
||||
else
|
||||
return TraversalDecision::Continue;
|
||||
// FIXME: Somehow limit <clipPath> contents to: shape elements, <text>, and <use>.
|
||||
auto& layout_state = m_state.get_mutable(static_cast<Box const&>(descendant));
|
||||
auto parent_viewbox_transform = viewbox_transform;
|
||||
if (mask_box.dom_node().mask_content_units() == SVG::MaskContentUnits::ObjectBoundingBox) {
|
||||
auto* masked_node = mask_box.parent();
|
||||
auto& masked_node_state = m_state.get(*masked_node);
|
||||
mask_state.set_content_width(masked_node_state.content_width());
|
||||
mask_state.set_content_height(masked_node_state.content_height());
|
||||
parent_viewbox_transform = Gfx::AffineTransform {}.translate(masked_node_state.offset.to_type<float>());
|
||||
if (content_units == SVG::SVGUnits::ObjectBoundingBox) {
|
||||
auto* parent_node = descendant.parent();
|
||||
auto& parent_node_state = m_state.get(*parent_node);
|
||||
layout_state.set_content_width(parent_node_state.content_width());
|
||||
layout_state.set_content_height(parent_node_state.content_height());
|
||||
parent_viewbox_transform = Gfx::AffineTransform {}.translate(parent_node_state.offset.to_type<float>());
|
||||
} else {
|
||||
mask_state.set_content_width(viewport_width);
|
||||
mask_state.set_content_height(viewport_height);
|
||||
layout_state.set_content_width(viewport_width);
|
||||
layout_state.set_content_height(viewport_height);
|
||||
}
|
||||
// Pretend masks are a viewport so we can scale the contents depending on the `maskContentUnits`.
|
||||
SVGFormattingContext nested_context(m_state, static_cast<Box const&>(mask_box), this, parent_viewbox_transform);
|
||||
mask_state.set_has_definite_width(true);
|
||||
mask_state.set_has_definite_height(true);
|
||||
nested_context.run(static_cast<Box const&>(mask_box), layout_mode, available_space);
|
||||
return IterationDecision::Continue;
|
||||
// Pretend masks/clips are a viewport so we can scale the contents depending on the `contentUnits`.
|
||||
SVGFormattingContext nested_context(m_state, static_cast<Box const&>(descendant), this, parent_viewbox_transform);
|
||||
layout_state.set_has_definite_width(true);
|
||||
layout_state.set_has_definite_height(true);
|
||||
nested_context.run(static_cast<Box const&>(descendant), layout_mode, available_space);
|
||||
return TraversalDecision::SkipChildrenAndContinue;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue