mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 19:32:45 +00:00 
			
		
		
		
	LibWeb: Special case SVG masks during layout
Rather than try to lay out masks normally, this updates the TreeBuilder to create layout nodes for masks as a child of their user (i.e. the masked element). This allows each use of a mask to be laid out differently, which makes supporting `maskContentUnits=objectBoundingBox` fairly easy. The `SVGFormattingContext` is then updated to lay out masks last (as their sizing may depend on their parent), and treats them like viewports. This is pretty ad-hoc, but the SVG specification does not give any guidance on how to actually implement this.
This commit is contained in:
		
							parent
							
								
									15e3b0ebde
								
							
						
					
					
						commit
						163b6bb401
					
				
					 18 changed files with 232 additions and 76 deletions
				
			
		|  | @ -1,22 +1,31 @@ | ||||||
| Viewport <#document> at (0,0) content-size 800x600 children: not-inline | Viewport <#document> at (0,0) content-size 800x600 children: not-inline | ||||||
|   BlockContainer <html> at (0,0) content-size 800x216 [BFC] children: not-inline |   BlockContainer <html> at (0,0) content-size 800x416 [BFC] children: not-inline | ||||||
|     BlockContainer <body> at (8,8) content-size 784x200 children: inline |     BlockContainer <body> at (8,8) content-size 784x400 children: inline | ||||||
|       frag 0 from SVGSVGBox start: 0, length: 0, rect: [8,8 200x200] baseline: 200 |       frag 0 from SVGSVGBox start: 0, length: 0, rect: [8,8 400x400] baseline: 400 | ||||||
|       SVGSVGBox <svg> at (8,8) content-size 200x200 [SVG] children: inline |       SVGSVGBox <svg> at (8,8) content-size 400x400 [SVG] children: inline | ||||||
|         TextNode <#text> |         TextNode <#text> | ||||||
|         SVGGraphicsBox <mask#myMask> at (8,8) content-size 1x1 children: inline |  | ||||||
|           TextNode <#text> |  | ||||||
|           SVGGeometryBox <rect> at (8,8) content-size 1x1 children: not-inline |  | ||||||
|           TextNode <#text> |  | ||||||
|           SVGGeometryBox <circle> at (8.09375,8.09375) content-size 0.8125x0.8125 children: not-inline |  | ||||||
|           TextNode <#text> |  | ||||||
|         TextNode <#text> |         TextNode <#text> | ||||||
|         SVGGeometryBox <rect> at (8,8) content-size 200x200 children: not-inline |         SVGGeometryBox <rect> at (8,8) content-size 200x200 children: not-inline | ||||||
|  |           SVGMaskBox <mask#myMask> at (8,8) content-size 200x200 children: inline | ||||||
|  |             TextNode <#text> | ||||||
|  |             SVGGeometryBox <rect> at (8,8) content-size 200x200 children: not-inline | ||||||
|  |             TextNode <#text> | ||||||
|  |             SVGGeometryBox <circle> at (26.75,26.75) content-size 162.5x162.5 children: not-inline | ||||||
|  |             TextNode <#text> | ||||||
|  |         TextNode <#text> | ||||||
|  |         SVGGeometryBox <rect> at (208,208) content-size 200x100 children: not-inline | ||||||
|  |           SVGMaskBox <mask#myMask> at (208,208) content-size 200x100 children: inline | ||||||
|  |             TextNode <#text> | ||||||
|  |             SVGGeometryBox <rect> at (208,208) content-size 200x100 children: not-inline | ||||||
|  |             TextNode <#text> | ||||||
|  |             SVGGeometryBox <circle> at (226.75,217.375) content-size 162.5x81.25 children: not-inline | ||||||
|  |             TextNode <#text> | ||||||
|         TextNode <#text> |         TextNode <#text> | ||||||
|       TextNode <#text> |       TextNode <#text> | ||||||
| 
 | 
 | ||||||
| ViewportPaintable (Viewport<#document>) [0,0 800x600] | ViewportPaintable (Viewport<#document>) [0,0 800x600] | ||||||
|   PaintableWithLines (BlockContainer<HTML>) [0,0 800x216] |   PaintableWithLines (BlockContainer<HTML>) [0,0 800x416] | ||||||
|     PaintableWithLines (BlockContainer<BODY>) [8,8 784x200] |     PaintableWithLines (BlockContainer<BODY>) [8,8 784x400] | ||||||
|       SVGSVGPaintable (SVGSVGBox<svg>) [8,8 200x200] |       SVGSVGPaintable (SVGSVGBox<svg>) [8,8 400x400] | ||||||
|         SVGPathPaintable (SVGGeometryBox<rect>) [8,8 200x200] |         SVGPathPaintable (SVGGeometryBox<rect>) [8,8 200x200] | ||||||
|  |         SVGPathPaintable (SVGGeometryBox<rect>) [208,208 200x100] | ||||||
|  |  | ||||||
|  | @ -9,7 +9,7 @@ Viewport <#document> at (0,0) content-size 800x600 children: not-inline | ||||||
|         TextNode <#text> |         TextNode <#text> | ||||||
|         SVGSVGBox <svg> at (8,8) content-size 300x150 [SVG] children: inline |         SVGSVGBox <svg> at (8,8) content-size 300x150 [SVG] children: inline | ||||||
|           TextNode <#text> |           TextNode <#text> | ||||||
|           SVGGraphicsBox <use> at (92.375,26.75) content-size 131.25x112.15625 children: not-inline |           SVGGraphicsBox <use> at (8,8) content-size 300x150 children: not-inline | ||||||
|             SVGGraphicsBox <symbol#braces> at (92.375,26.75) content-size 131.25x112.15625 [BFC] children: inline |             SVGGraphicsBox <symbol#braces> at (92.375,26.75) content-size 131.25x112.15625 [BFC] children: inline | ||||||
|               TextNode <#text> |               TextNode <#text> | ||||||
|               SVGGeometryBox <path> at (92.375,26.75) content-size 131.25x112.15625 children: inline |               SVGGeometryBox <path> at (92.375,26.75) content-size 131.25x112.15625 children: inline | ||||||
|  | @ -24,6 +24,6 @@ ViewportPaintable (Viewport<#document>) [0,0 800x600] | ||||||
|       PaintableWithLines (BlockContainer(anonymous)) [8,8 784x0] |       PaintableWithLines (BlockContainer(anonymous)) [8,8 784x0] | ||||||
|       PaintableWithLines (BlockContainer<DIV>) [8,8 784x150] |       PaintableWithLines (BlockContainer<DIV>) [8,8 784x150] | ||||||
|         SVGSVGPaintable (SVGSVGBox<svg>) [8,8 300x150] |         SVGSVGPaintable (SVGSVGBox<svg>) [8,8 300x150] | ||||||
|           SVGGraphicsPaintable (SVGGraphicsBox<use>) [92.375,26.75 131.25x112.15625] |           SVGGraphicsPaintable (SVGGraphicsBox<use>) [8,8 300x150] | ||||||
|             SVGGraphicsPaintable (SVGGraphicsBox<symbol>#braces) [92.375,26.75 131.25x112.15625] |             SVGGraphicsPaintable (SVGGraphicsBox<symbol>#braces) [92.375,26.75 131.25x112.15625] | ||||||
|               SVGPathPaintable (SVGGeometryBox<path>) [92.375,26.75 131.25x112.15625] |               SVGPathPaintable (SVGGeometryBox<path>) [92.375,26.75 131.25x112.15625] | ||||||
|  |  | ||||||
|  | @ -1,8 +1,9 @@ | ||||||
| <!DOCTYPE html> | <!DOCTYPE html> | ||||||
| <svg width="200" height="200"> | <svg width="400" height="400"> | ||||||
|     <mask id="myMask" maskContentUnits="objectBoundingBox"> |     <mask id="myMask" maskContentUnits="objectBoundingBox"> | ||||||
|         <rect x="0" y="0" width="1" height="1" fill="white"/> |         <rect x="0" y="0" width="1" height="1" fill="white"/> | ||||||
|         <circle cx="0.5" cy="0.5" r="0.4" fill="black"/> |         <circle cx="0.5" cy="0.5" r="0.4" fill="black"/> | ||||||
|     </mask> |     </mask> | ||||||
|     <rect x="0" y="0" width="200" height="200" fill="green" mask="url(#myMask)"/> |     <rect x="0" y="0" width="200" height="200" fill="green" mask="url(#myMask)"/> | ||||||
|  |     <rect x="200" y="200" width="200" height="100" fill="red" mask="url(#myMask)"/> | ||||||
| </svg> | </svg> | ||||||
|  |  | ||||||
|  | @ -469,6 +469,7 @@ set(SOURCES | ||||||
|     Layout/SVGGeometryBox.cpp |     Layout/SVGGeometryBox.cpp | ||||||
|     Layout/SVGGraphicsBox.cpp |     Layout/SVGGraphicsBox.cpp | ||||||
|     Layout/SVGSVGBox.cpp |     Layout/SVGSVGBox.cpp | ||||||
|  |     Layout/SVGMaskBox.cpp | ||||||
|     Layout/SVGTextBox.cpp |     Layout/SVGTextBox.cpp | ||||||
|     Layout/SVGTextPathBox.cpp |     Layout/SVGTextPathBox.cpp | ||||||
|     Layout/TableFormattingContext.cpp |     Layout/TableFormattingContext.cpp | ||||||
|  | @ -523,6 +524,7 @@ set(SOURCES | ||||||
|     Painting/RecordingPainter.cpp |     Painting/RecordingPainter.cpp | ||||||
|     Painting/SVGPathPaintable.cpp |     Painting/SVGPathPaintable.cpp | ||||||
|     Painting/SVGGraphicsPaintable.cpp |     Painting/SVGGraphicsPaintable.cpp | ||||||
|  |     Painting/SVGMaskPaintable.cpp | ||||||
|     Painting/SVGPaintable.cpp |     Painting/SVGPaintable.cpp | ||||||
|     Painting/SVGSVGPaintable.cpp |     Painting/SVGSVGPaintable.cpp | ||||||
|     Painting/ShadowPainting.cpp |     Painting/ShadowPainting.cpp | ||||||
|  |  | ||||||
|  | @ -14,6 +14,7 @@ | ||||||
| #include <LibWeb/Layout/BlockFormattingContext.h> | #include <LibWeb/Layout/BlockFormattingContext.h> | ||||||
| #include <LibWeb/Layout/SVGFormattingContext.h> | #include <LibWeb/Layout/SVGFormattingContext.h> | ||||||
| #include <LibWeb/Layout/SVGGeometryBox.h> | #include <LibWeb/Layout/SVGGeometryBox.h> | ||||||
|  | #include <LibWeb/Layout/SVGMaskBox.h> | ||||||
| #include <LibWeb/Layout/SVGSVGBox.h> | #include <LibWeb/Layout/SVGSVGBox.h> | ||||||
| #include <LibWeb/Layout/SVGTextBox.h> | #include <LibWeb/Layout/SVGTextBox.h> | ||||||
| #include <LibWeb/Layout/SVGTextPathBox.h> | #include <LibWeb/Layout/SVGTextPathBox.h> | ||||||
|  | @ -289,6 +290,8 @@ void SVGFormattingContext::run(Box const& box, LayoutMode layout_mode, Available | ||||||
|     }(); |     }(); | ||||||
| 
 | 
 | ||||||
|     for_each_in_subtree(box, [&](Node const& descendant) { |     for_each_in_subtree(box, [&](Node const& descendant) { | ||||||
|  |         if (is<SVGMaskBox>(descendant)) | ||||||
|  |             return TraversalDecision::SkipChildrenAndContinue; | ||||||
|         if (is<SVG::SVGViewport>(descendant.dom_node())) { |         if (is<SVG::SVGViewport>(descendant.dom_node())) { | ||||||
|             // Layout for a nested SVG viewport.
 |             // Layout for a nested SVG viewport.
 | ||||||
|             // https://svgwg.org/svg2-draft/coords.html#EstablishingANewSVGViewport.
 |             // https://svgwg.org/svg2-draft/coords.html#EstablishingANewSVGViewport.
 | ||||||
|  | @ -391,14 +394,19 @@ void SVGFormattingContext::run(Box const& box, LayoutMode layout_mode, Available | ||||||
|     // https://svgwg.org/svg2-draft/struct.html#Groups
 |     // https://svgwg.org/svg2-draft/struct.html#Groups
 | ||||||
|     // 5.2. Grouping: the ‘g’ element
 |     // 5.2. Grouping: the ‘g’ element
 | ||||||
|     // The ‘g’ element is a container element for grouping together related graphics elements.
 |     // The ‘g’ element is a container element for grouping together related graphics elements.
 | ||||||
|     box.for_each_in_subtree_of_type<Box>([&](Box const& descendant) { |     box.for_each_in_subtree_of_type<SVGBox>([&](SVGBox const& descendant) { | ||||||
|         if (is_container_element(descendant)) { |         if (is_container_element(descendant)) { | ||||||
|             Gfx::BoundingBox<CSSPixels> bounding_box; |             Gfx::BoundingBox<CSSPixels> bounding_box; | ||||||
|             descendant.for_each_in_subtree_of_type<Box>([&](Box const& child_of_svg_container) { |             for_each_in_subtree(descendant, [&](Node const& child_of_svg_container) { | ||||||
|                 auto& box_state = m_state.get(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)) | ||||||
|  |                     return TraversalDecision::SkipChildrenAndContinue; | ||||||
|  |                 auto& box_state = m_state.get(static_cast<SVGBox const&>(child_of_svg_container)); | ||||||
|                 bounding_box.add_point(box_state.offset); |                 bounding_box.add_point(box_state.offset); | ||||||
|                 bounding_box.add_point(box_state.offset.translated(box_state.content_width(), box_state.content_height())); |                 bounding_box.add_point(box_state.offset.translated(box_state.content_width(), box_state.content_height())); | ||||||
|                 return IterationDecision::Continue; |                 return TraversalDecision::Continue; | ||||||
|             }); |             }); | ||||||
| 
 | 
 | ||||||
|             auto& box_state = m_state.get_mutable(descendant); |             auto& box_state = m_state.get_mutable(descendant); | ||||||
|  | @ -411,6 +419,28 @@ void SVGFormattingContext::run(Box const& box, LayoutMode layout_mode, Available | ||||||
|         } |         } | ||||||
|         return IterationDecision::Continue; |         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)); | ||||||
|  |         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>()); | ||||||
|  |         } else { | ||||||
|  |             mask_state.set_content_width(viewport_width); | ||||||
|  |             mask_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; | ||||||
|  |     }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										23
									
								
								Userland/Libraries/LibWeb/Layout/SVGMaskBox.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								Userland/Libraries/LibWeb/Layout/SVGMaskBox.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,23 @@ | ||||||
|  | /*
 | ||||||
|  |  * Copyright (c) 2024, MacDue <macdue@dueutil.tech> | ||||||
|  |  * | ||||||
|  |  * SPDX-License-Identifier: BSD-2-Clause | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | #include <LibWeb/Layout/SVGMaskBox.h> | ||||||
|  | #include <LibWeb/Painting/SVGMaskPaintable.h> | ||||||
|  | #include <LibWeb/Painting/StackingContext.h> | ||||||
|  | 
 | ||||||
|  | namespace Web::Layout { | ||||||
|  | 
 | ||||||
|  | SVGMaskBox::SVGMaskBox(DOM::Document& document, SVG::SVGMaskElement& element, NonnullRefPtr<CSS::StyleProperties> properties) | ||||||
|  |     : SVGGraphicsBox(document, element, properties) | ||||||
|  | { | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | JS::GCPtr<Painting::Paintable> SVGMaskBox::create_paintable() const | ||||||
|  | { | ||||||
|  |     return Painting::SVGMaskPaintable::create(*this); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } | ||||||
							
								
								
									
										28
									
								
								Userland/Libraries/LibWeb/Layout/SVGMaskBox.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								Userland/Libraries/LibWeb/Layout/SVGMaskBox.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,28 @@ | ||||||
|  | /*
 | ||||||
|  |  * Copyright (c) 2024, MacDue <macdue@dueutil.tech> | ||||||
|  |  * | ||||||
|  |  * SPDX-License-Identifier: BSD-2-Clause | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <LibWeb/Layout/SVGGraphicsBox.h> | ||||||
|  | #include <LibWeb/SVG/SVGElement.h> | ||||||
|  | #include <LibWeb/SVG/SVGMaskElement.h> | ||||||
|  | 
 | ||||||
|  | namespace Web::Layout { | ||||||
|  | 
 | ||||||
|  | class SVGMaskBox : public SVGGraphicsBox { | ||||||
|  |     JS_CELL(SVGMaskBox, SVGBox); | ||||||
|  | 
 | ||||||
|  | public: | ||||||
|  |     SVGMaskBox(DOM::Document&, SVG::SVGMaskElement&, NonnullRefPtr<CSS::StyleProperties>); | ||||||
|  |     virtual ~SVGMaskBox() override = default; | ||||||
|  | 
 | ||||||
|  |     SVG::SVGMaskElement& dom_node() { return verify_cast<SVG::SVGMaskElement>(SVGGraphicsBox::dom_node()); } | ||||||
|  |     SVG::SVGMaskElement const& dom_node() const { return verify_cast<SVG::SVGMaskElement>(SVGGraphicsBox::dom_node()); } | ||||||
|  | 
 | ||||||
|  |     virtual JS::GCPtr<Painting::Paintable> create_paintable() const override; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -26,6 +26,7 @@ | ||||||
| #include <LibWeb/Layout/ListItemBox.h> | #include <LibWeb/Layout/ListItemBox.h> | ||||||
| #include <LibWeb/Layout/ListItemMarkerBox.h> | #include <LibWeb/Layout/ListItemMarkerBox.h> | ||||||
| #include <LibWeb/Layout/Node.h> | #include <LibWeb/Layout/Node.h> | ||||||
|  | #include <LibWeb/Layout/SVGMaskBox.h> | ||||||
| #include <LibWeb/Layout/TableGrid.h> | #include <LibWeb/Layout/TableGrid.h> | ||||||
| #include <LibWeb/Layout/TableWrapper.h> | #include <LibWeb/Layout/TableWrapper.h> | ||||||
| #include <LibWeb/Layout/TextNode.h> | #include <LibWeb/Layout/TextNode.h> | ||||||
|  | @ -335,6 +336,12 @@ void TreeBuilder::create_layout_tree(DOM::Node& dom_node, TreeBuilder::Context& | ||||||
|         display = CSS::Display(CSS::DisplayOutside::Inline, CSS::DisplayInside::Flow); |         display = CSS::Display(CSS::DisplayOutside::Inline, CSS::DisplayInside::Flow); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     if (context.layout_svg_mask && is<SVG::SVGMaskElement>(dom_node)) { | ||||||
|  |         layout_node = document.heap().allocate_without_realm<Layout::SVGMaskBox>(document, static_cast<SVG::SVGMaskElement&>(dom_node), *style); | ||||||
|  |         // We're here if our parent is a use of an SVG mask, but we don't want to lay out any <mask> elements that could be a child of this mask.
 | ||||||
|  |         context.layout_svg_mask = false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     if (!layout_node) |     if (!layout_node) | ||||||
|         return; |         return; | ||||||
| 
 | 
 | ||||||
|  | @ -389,6 +396,19 @@ void TreeBuilder::create_layout_tree(DOM::Node& dom_node, TreeBuilder::Context& | ||||||
|         pop_parent(); |         pop_parent(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     if (is<SVG::SVGGraphicsElement>(dom_node)) { | ||||||
|  |         auto& graphics_element = static_cast<SVG::SVGGraphicsElement&>(dom_node); | ||||||
|  |         // Create the layout tree for the SVG mask as a child of the masked element. Note: This will create
 | ||||||
|  |         // a new subtree for each use of the mask (so there's  not a 1-to-1 mapping from DOM node to mask
 | ||||||
|  |         // layout node). Each use of a mask may be laid out differently so this duplication is necessary.
 | ||||||
|  |         if (auto mask = graphics_element.mask()) { | ||||||
|  |             TemporaryChange<bool> layout_mask(context.layout_svg_mask, true); | ||||||
|  |             push_parent(verify_cast<NodeWithStyle>(*layout_node)); | ||||||
|  |             create_layout_tree(const_cast<SVG::SVGMaskElement&>(*mask), context); | ||||||
|  |             pop_parent(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     // https://html.spec.whatwg.org/multipage/rendering.html#button-layout
 |     // https://html.spec.whatwg.org/multipage/rendering.html#button-layout
 | ||||||
|     // If the computed value of 'inline-size' is 'auto', then the used value is the fit-content inline size.
 |     // If the computed value of 'inline-size' is 'auto', then the used value is the fit-content inline size.
 | ||||||
|     if (dom_node.is_html_button_element() && dom_node.layout_node()->computed_values().width().is_auto()) { |     if (dom_node.is_html_button_element() && dom_node.layout_node()->computed_values().width().is_auto()) { | ||||||
|  |  | ||||||
|  | @ -23,6 +23,7 @@ public: | ||||||
| private: | private: | ||||||
|     struct Context { |     struct Context { | ||||||
|         bool has_svg_root = false; |         bool has_svg_root = false; | ||||||
|  |         bool layout_svg_mask = false; | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     i32 calculate_list_item_index(DOM::Node&); |     i32 calculate_list_item_index(DOM::Node&); | ||||||
|  |  | ||||||
|  | @ -5,6 +5,7 @@ | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| #include <LibWeb/Layout/ImageBox.h> | #include <LibWeb/Layout/ImageBox.h> | ||||||
|  | #include <LibWeb/Layout/SVGMaskBox.h> | ||||||
| #include <LibWeb/Painting/CommandExecutorCPU.h> | #include <LibWeb/Painting/CommandExecutorCPU.h> | ||||||
| #include <LibWeb/Painting/SVGGraphicsPaintable.h> | #include <LibWeb/Painting/SVGGraphicsPaintable.h> | ||||||
| #include <LibWeb/Painting/StackingContext.h> | #include <LibWeb/Painting/StackingContext.h> | ||||||
|  | @ -23,12 +24,6 @@ SVGGraphicsPaintable::SVGGraphicsPaintable(Layout::SVGGraphicsBox const& layout_ | ||||||
| { | { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool SVGGraphicsPaintable::forms_unconnected_subtree() const |  | ||||||
| { |  | ||||||
|     // Masks should not be painted (i.e. reachable) unless referenced by another element.
 |  | ||||||
|     return is<SVG::SVGMaskElement>(dom_node()); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| Layout::SVGGraphicsBox const& SVGGraphicsPaintable::layout_box() const | Layout::SVGGraphicsBox const& SVGGraphicsPaintable::layout_box() const | ||||||
| { | { | ||||||
|     return static_cast<Layout::SVGGraphicsBox const&>(layout_node()); |     return static_cast<Layout::SVGGraphicsBox const&>(layout_node()); | ||||||
|  | @ -37,20 +32,10 @@ Layout::SVGGraphicsBox const& SVGGraphicsPaintable::layout_box() const | ||||||
| Optional<CSSPixelRect> SVGGraphicsPaintable::get_masking_area() const | Optional<CSSPixelRect> SVGGraphicsPaintable::get_masking_area() const | ||||||
| { | { | ||||||
|     auto const& graphics_element = verify_cast<SVG::SVGGraphicsElement const>(*dom_node()); |     auto const& graphics_element = verify_cast<SVG::SVGGraphicsElement const>(*dom_node()); | ||||||
|     auto mask = graphics_element.mask(); |     auto* mask_box = graphics_element.layout_node()->first_child_of_type<Layout::SVGMaskBox>(); | ||||||
|     if (!mask) |     if (!mask_box) | ||||||
|         return {}; |         return {}; | ||||||
|     auto target_area = [&] { |     return mask_box->dom_node().resolve_masking_area(mask_box->paintable_box()->absolute_border_box_rect()); | ||||||
|         auto mask_units = mask->mask_units(); |  | ||||||
|         if (mask_units == SVG::MaskUnits::UserSpaceOnUse) { |  | ||||||
|             auto const* svg_element = graphics_element.shadow_including_first_ancestor_of_type<SVG::SVGSVGElement>(); |  | ||||||
|             return svg_element->paintable_box()->absolute_border_box_rect(); |  | ||||||
|         } else { |  | ||||||
|             VERIFY(mask_units == SVG::MaskUnits::ObjectBoundingBox); |  | ||||||
|             return absolute_border_box_rect(); |  | ||||||
|         } |  | ||||||
|     }(); |  | ||||||
|     return mask->resolve_masking_area(target_area); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static Gfx::Bitmap::MaskKind mask_type_to_gfx_mask_kind(CSS::MaskType mask_type) | static Gfx::Bitmap::MaskKind mask_type_to_gfx_mask_kind(CSS::MaskType mask_type) | ||||||
|  | @ -77,30 +62,24 @@ Optional<Gfx::Bitmap::MaskKind> SVGGraphicsPaintable::get_mask_type() const | ||||||
| RefPtr<Gfx::Bitmap> SVGGraphicsPaintable::calculate_mask(PaintContext& context, CSSPixelRect const& masking_area) const | RefPtr<Gfx::Bitmap> SVGGraphicsPaintable::calculate_mask(PaintContext& context, CSSPixelRect const& masking_area) const | ||||||
| { | { | ||||||
|     auto const& graphics_element = verify_cast<SVG::SVGGraphicsElement const>(*dom_node()); |     auto const& graphics_element = verify_cast<SVG::SVGGraphicsElement const>(*dom_node()); | ||||||
|     auto mask = graphics_element.mask(); |     auto* mask_box = graphics_element.layout_node()->first_child_of_type<Layout::SVGMaskBox>(); | ||||||
|     VERIFY(mask); |     VERIFY(mask_box); | ||||||
|     if (mask->mask_content_units() != SVG::MaskContentUnits::UserSpaceOnUse) { |  | ||||||
|         // FIXME: Implement support for maskContentUnits=objectBoundingBox
 |  | ||||||
|         return {}; |  | ||||||
|     } |  | ||||||
|     auto mask_rect = context.enclosing_device_rect(masking_area); |     auto mask_rect = context.enclosing_device_rect(masking_area); | ||||||
|     RefPtr<Gfx::Bitmap> mask_bitmap = {}; |     RefPtr<Gfx::Bitmap> mask_bitmap = {}; | ||||||
|     if (mask && mask->layout_node() && is<PaintableBox>(mask->layout_node()->paintable())) { |     auto& mask_paintable = static_cast<PaintableBox const&>(*mask_box->paintable()); | ||||||
|         auto& mask_paintable = static_cast<PaintableBox const&>(*mask->layout_node()->paintable()); |     auto mask_bitmap_or_error = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, mask_rect.size().to_type<int>()); | ||||||
|         auto mask_bitmap_or_error = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, mask_rect.size().to_type<int>()); |     if (mask_bitmap_or_error.is_error()) | ||||||
|         if (mask_bitmap_or_error.is_error()) |         return {}; | ||||||
|             return {}; |     mask_bitmap = mask_bitmap_or_error.release_value(); | ||||||
|         mask_bitmap = mask_bitmap_or_error.release_value(); |     { | ||||||
|         { |         CommandList painting_commands; | ||||||
|             CommandList painting_commands; |         RecordingPainter recording_painter(painting_commands); | ||||||
|             RecordingPainter recording_painter(painting_commands); |         recording_painter.translate(-mask_rect.location().to_type<int>()); | ||||||
|             recording_painter.translate(-mask_rect.location().to_type<int>()); |         auto paint_context = context.clone(recording_painter); | ||||||
|             auto paint_context = context.clone(recording_painter); |         paint_context.set_svg_transform(graphics_element.get_transform()); | ||||||
|             paint_context.set_svg_transform(graphics_element.get_transform()); |         StackingContext::paint_node_as_stacking_context(mask_paintable, paint_context); | ||||||
|             StackingContext::paint_node_as_stacking_context(mask_paintable, paint_context); |         CommandExecutorCPU executor { *mask_bitmap }; | ||||||
|             CommandExecutorCPU executor { *mask_bitmap }; |         painting_commands.execute(executor); | ||||||
|             painting_commands.execute(executor); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
|     return mask_bitmap; |     return mask_bitmap; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -49,8 +49,6 @@ public: | ||||||
| 
 | 
 | ||||||
|     Layout::SVGGraphicsBox const& layout_box() const; |     Layout::SVGGraphicsBox const& layout_box() const; | ||||||
| 
 | 
 | ||||||
|     virtual bool forms_unconnected_subtree() const override; |  | ||||||
| 
 |  | ||||||
|     virtual Optional<CSSPixelRect> get_masking_area() const override; |     virtual Optional<CSSPixelRect> get_masking_area() const override; | ||||||
|     virtual Optional<Gfx::Bitmap::MaskKind> get_mask_type() const override; |     virtual Optional<Gfx::Bitmap::MaskKind> get_mask_type() const override; | ||||||
|     virtual RefPtr<Gfx::Bitmap> calculate_mask(PaintContext&, CSSPixelRect const& masking_area) const override; |     virtual RefPtr<Gfx::Bitmap> calculate_mask(PaintContext&, CSSPixelRect const& masking_area) const override; | ||||||
|  |  | ||||||
							
								
								
									
										21
									
								
								Userland/Libraries/LibWeb/Painting/SVGMaskPaintable.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								Userland/Libraries/LibWeb/Painting/SVGMaskPaintable.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,21 @@ | ||||||
|  | /*
 | ||||||
|  |  * Copyright (c) 2024, MacDue <macdue@dueutil.tech> | ||||||
|  |  * | ||||||
|  |  * SPDX-License-Identifier: BSD-2-Clause | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | #include <LibWeb/Painting/SVGMaskPaintable.h> | ||||||
|  | 
 | ||||||
|  | namespace Web::Painting { | ||||||
|  | 
 | ||||||
|  | JS::NonnullGCPtr<SVGMaskPaintable> SVGMaskPaintable::create(Layout::SVGMaskBox const& layout_box) | ||||||
|  | { | ||||||
|  |     return layout_box.heap().allocate_without_realm<SVGMaskPaintable>(layout_box); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | SVGMaskPaintable::SVGMaskPaintable(Layout::SVGMaskBox const& layout_box) | ||||||
|  |     : SVGGraphicsPaintable(layout_box) | ||||||
|  | { | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } | ||||||
							
								
								
									
										30
									
								
								Userland/Libraries/LibWeb/Painting/SVGMaskPaintable.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								Userland/Libraries/LibWeb/Painting/SVGMaskPaintable.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,30 @@ | ||||||
|  | /*
 | ||||||
|  |  * Copyright (c) 2024, MacDue <macdue@dueutil.tech> | ||||||
|  |  * | ||||||
|  |  * SPDX-License-Identifier: BSD-2-Clause | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <LibWeb/Layout/SVGMaskBox.h> | ||||||
|  | #include <LibWeb/Painting/SVGGraphicsPaintable.h> | ||||||
|  | 
 | ||||||
|  | namespace Web::Painting { | ||||||
|  | 
 | ||||||
|  | class SVGMaskPaintable : public SVGGraphicsPaintable { | ||||||
|  |     JS_CELL(SVGMaskPaintable, SVGGraphicsPaintable); | ||||||
|  | 
 | ||||||
|  | public: | ||||||
|  |     static JS::NonnullGCPtr<SVGMaskPaintable> create(Layout::SVGMaskBox const&); | ||||||
|  | 
 | ||||||
|  |     bool forms_unconnected_subtree() const override | ||||||
|  |     { | ||||||
|  |         // Masks should not be painted (i.e. reachable) unless referenced by another element.
 | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | protected: | ||||||
|  |     SVGMaskPaintable(Layout::SVGMaskBox const&); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -27,10 +27,4 @@ void SVGDefsElement::initialize(JS::Realm& realm) | ||||||
|     set_prototype(&Bindings::ensure_web_prototype<Bindings::SVGDefsElementPrototype>(realm, "SVGDefsElement"_fly_string)); |     set_prototype(&Bindings::ensure_web_prototype<Bindings::SVGDefsElementPrototype>(realm, "SVGDefsElement"_fly_string)); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| JS::GCPtr<Layout::Node> SVGDefsElement::create_layout_node(NonnullRefPtr<CSS::StyleProperties> style) |  | ||||||
| { |  | ||||||
|     // FIXME: We need this layout node so any <mask>s inside this element get layout computed.
 |  | ||||||
|     return heap().allocate_without_realm<Layout::SVGBox>(document(), *this, move(style)); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -17,7 +17,10 @@ class SVGDefsElement final : public SVGGraphicsElement { | ||||||
| public: | public: | ||||||
|     virtual ~SVGDefsElement(); |     virtual ~SVGDefsElement(); | ||||||
| 
 | 
 | ||||||
|     virtual JS::GCPtr<Layout::Node> create_layout_node(NonnullRefPtr<CSS::StyleProperties>) override; |     virtual JS::GCPtr<Layout::Node> create_layout_node(NonnullRefPtr<CSS::StyleProperties>) override | ||||||
|  |     { | ||||||
|  |         return nullptr; | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|     SVGDefsElement(DOM::Document&, DOM::QualifiedName); |     SVGDefsElement(DOM::Document&, DOM::QualifiedName); | ||||||
|  |  | ||||||
|  | @ -7,7 +7,6 @@ | ||||||
| 
 | 
 | ||||||
| #include <LibWeb/Bindings/SVGMaskElementPrototype.h> | #include <LibWeb/Bindings/SVGMaskElementPrototype.h> | ||||||
| #include <LibWeb/DOM/Document.h> | #include <LibWeb/DOM/Document.h> | ||||||
| #include <LibWeb/Layout/SVGGraphicsBox.h> |  | ||||||
| #include <LibWeb/SVG/AttributeNames.h> | #include <LibWeb/SVG/AttributeNames.h> | ||||||
| #include <LibWeb/SVG/SVGMaskElement.h> | #include <LibWeb/SVG/SVGMaskElement.h> | ||||||
| 
 | 
 | ||||||
|  | @ -28,9 +27,10 @@ void SVGMaskElement::initialize(JS::Realm& realm) | ||||||
|     set_prototype(&Bindings::ensure_web_prototype<Bindings::SVGMaskElementPrototype>(realm, "SVGMaskElement"_fly_string)); |     set_prototype(&Bindings::ensure_web_prototype<Bindings::SVGMaskElementPrototype>(realm, "SVGMaskElement"_fly_string)); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| JS::GCPtr<Layout::Node> SVGMaskElement::create_layout_node(NonnullRefPtr<CSS::StyleProperties> style) | JS::GCPtr<Layout::Node> SVGMaskElement::create_layout_node(NonnullRefPtr<CSS::StyleProperties>) | ||||||
| { | { | ||||||
|     return heap().allocate_without_realm<Layout::SVGGraphicsBox>(document(), *this, move(style)); |     // Masks are handled as a special case in the TreeBuilder.
 | ||||||
|  |     return nullptr; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void SVGMaskElement::attribute_changed(FlyString const& name, Optional<String> const& value) | void SVGMaskElement::attribute_changed(FlyString const& name, Optional<String> const& value) | ||||||
|  |  | ||||||
|  | @ -8,16 +8,34 @@ | ||||||
| 
 | 
 | ||||||
| #include <LibWeb/SVG/AttributeParser.h> | #include <LibWeb/SVG/AttributeParser.h> | ||||||
| #include <LibWeb/SVG/SVGGraphicsElement.h> | #include <LibWeb/SVG/SVGGraphicsElement.h> | ||||||
|  | #include <LibWeb/SVG/SVGViewport.h> | ||||||
| 
 | 
 | ||||||
| namespace Web::SVG { | namespace Web::SVG { | ||||||
| 
 | 
 | ||||||
| class SVGMaskElement final : public SVGGraphicsElement { | class SVGMaskElement final : public SVGGraphicsElement | ||||||
|  |     , public SVGViewport { | ||||||
|  | 
 | ||||||
|     WEB_PLATFORM_OBJECT(SVGMaskElement, SVGGraphicsElement); |     WEB_PLATFORM_OBJECT(SVGMaskElement, SVGGraphicsElement); | ||||||
|     JS_DECLARE_ALLOCATOR(SVGMaskElement); |     JS_DECLARE_ALLOCATOR(SVGMaskElement); | ||||||
| 
 | 
 | ||||||
| public: | public: | ||||||
|     virtual ~SVGMaskElement() override; |     virtual ~SVGMaskElement() override; | ||||||
| 
 | 
 | ||||||
|  |     virtual Optional<ViewBox> view_box() const override | ||||||
|  |     { | ||||||
|  |         // maskContentUnits = objectBoundingBox acts like the mask is sized to the bounding box
 | ||||||
|  |         // of the target element, with a viewBox of "0 0 1 1".
 | ||||||
|  |         if (mask_content_units() == MaskContentUnits::ObjectBoundingBox) | ||||||
|  |             return ViewBox { 0, 0, 1, 1 }; | ||||||
|  |         return {}; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     virtual Optional<PreserveAspectRatio> preserve_aspect_ratio() const override | ||||||
|  |     { | ||||||
|  |         // preserveAspectRatio = none (allow mask to be scaled in both x and y to match target size)
 | ||||||
|  |         return PreserveAspectRatio { PreserveAspectRatio::Align::None, {} }; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     virtual void attribute_changed(FlyString const& name, Optional<String> const& value) override; |     virtual void attribute_changed(FlyString const& name, Optional<String> const& value) override; | ||||||
| 
 | 
 | ||||||
|     virtual JS::GCPtr<Layout::Node> create_layout_node(NonnullRefPtr<CSS::StyleProperties>) override; |     virtual JS::GCPtr<Layout::Node> create_layout_node(NonnullRefPtr<CSS::StyleProperties>) override; | ||||||
|  |  | ||||||
|  | @ -6,8 +6,7 @@ | ||||||
| 
 | 
 | ||||||
| #include <LibWeb/Bindings/Intrinsics.h> | #include <LibWeb/Bindings/Intrinsics.h> | ||||||
| #include <LibWeb/CSS/Parser/Parser.h> | #include <LibWeb/CSS/Parser/Parser.h> | ||||||
| #include <LibWeb/CSS/StyleValues/IdentifierStyleValue.h> | #include <LibWeb/CSS/StyleProperties.h> | ||||||
| #include <LibWeb/Layout/BlockContainer.h> |  | ||||||
| #include <LibWeb/SVG/AttributeNames.h> | #include <LibWeb/SVG/AttributeNames.h> | ||||||
| #include <LibWeb/SVG/AttributeParser.h> | #include <LibWeb/SVG/AttributeParser.h> | ||||||
| #include <LibWeb/SVG/SVGStopElement.h> | #include <LibWeb/SVG/SVGStopElement.h> | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 MacDue
						MacDue