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 | ||||
|   BlockContainer <html> at (0,0) content-size 800x216 [BFC] children: not-inline | ||||
|     BlockContainer <body> at (8,8) content-size 784x200 children: inline | ||||
|       frag 0 from SVGSVGBox start: 0, length: 0, rect: [8,8 200x200] baseline: 200 | ||||
|       SVGSVGBox <svg> at (8,8) content-size 200x200 [SVG] children: inline | ||||
|   BlockContainer <html> at (0,0) content-size 800x416 [BFC] children: not-inline | ||||
|     BlockContainer <body> at (8,8) content-size 784x400 children: inline | ||||
|       frag 0 from SVGSVGBox start: 0, length: 0, rect: [8,8 400x400] baseline: 400 | ||||
|       SVGSVGBox <svg> at (8,8) content-size 400x400 [SVG] children: inline | ||||
|         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> | ||||
|         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> | ||||
| 
 | ||||
| ViewportPaintable (Viewport<#document>) [0,0 800x600] | ||||
|   PaintableWithLines (BlockContainer<HTML>) [0,0 800x216] | ||||
|     PaintableWithLines (BlockContainer<BODY>) [8,8 784x200] | ||||
|       SVGSVGPaintable (SVGSVGBox<svg>) [8,8 200x200] | ||||
|   PaintableWithLines (BlockContainer<HTML>) [0,0 800x416] | ||||
|     PaintableWithLines (BlockContainer<BODY>) [8,8 784x400] | ||||
|       SVGSVGPaintable (SVGSVGBox<svg>) [8,8 400x400] | ||||
|         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> | ||||
|         SVGSVGBox <svg> at (8,8) content-size 300x150 [SVG] children: inline | ||||
|           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 | ||||
|               TextNode <#text> | ||||
|               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<DIV>) [8,8 784x150] | ||||
|         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] | ||||
|               SVGPathPaintable (SVGGeometryBox<path>) [92.375,26.75 131.25x112.15625] | ||||
|  |  | |||
|  | @ -1,8 +1,9 @@ | |||
| <!DOCTYPE html> | ||||
| <svg width="200" height="200"> | ||||
| <svg width="400" height="400"> | ||||
|     <mask id="myMask" maskContentUnits="objectBoundingBox"> | ||||
|         <rect x="0" y="0" width="1" height="1" fill="white"/> | ||||
|         <circle cx="0.5" cy="0.5" r="0.4" fill="black"/> | ||||
|     </mask> | ||||
|     <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> | ||||
|  |  | |||
|  | @ -469,6 +469,7 @@ set(SOURCES | |||
|     Layout/SVGGeometryBox.cpp | ||||
|     Layout/SVGGraphicsBox.cpp | ||||
|     Layout/SVGSVGBox.cpp | ||||
|     Layout/SVGMaskBox.cpp | ||||
|     Layout/SVGTextBox.cpp | ||||
|     Layout/SVGTextPathBox.cpp | ||||
|     Layout/TableFormattingContext.cpp | ||||
|  | @ -523,6 +524,7 @@ set(SOURCES | |||
|     Painting/RecordingPainter.cpp | ||||
|     Painting/SVGPathPaintable.cpp | ||||
|     Painting/SVGGraphicsPaintable.cpp | ||||
|     Painting/SVGMaskPaintable.cpp | ||||
|     Painting/SVGPaintable.cpp | ||||
|     Painting/SVGSVGPaintable.cpp | ||||
|     Painting/ShadowPainting.cpp | ||||
|  |  | |||
|  | @ -14,6 +14,7 @@ | |||
| #include <LibWeb/Layout/BlockFormattingContext.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> | ||||
|  | @ -289,6 +290,8 @@ void SVGFormattingContext::run(Box const& box, LayoutMode layout_mode, Available | |||
|     }(); | ||||
| 
 | ||||
|     for_each_in_subtree(box, [&](Node const& descendant) { | ||||
|         if (is<SVGMaskBox>(descendant)) | ||||
|             return TraversalDecision::SkipChildrenAndContinue; | ||||
|         if (is<SVG::SVGViewport>(descendant.dom_node())) { | ||||
|             // Layout for a nested SVG viewport.
 | ||||
|             // 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
 | ||||
|     // 5.2. Grouping: the ‘g’ element
 | ||||
|     // 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)) { | ||||
|             Gfx::BoundingBox<CSSPixels> bounding_box; | ||||
|             descendant.for_each_in_subtree_of_type<Box>([&](Box const& child_of_svg_container) { | ||||
|                 auto& box_state = m_state.get(child_of_svg_container); | ||||
|             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)) | ||||
|                     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.translated(box_state.content_width(), box_state.content_height())); | ||||
|                 return IterationDecision::Continue; | ||||
|                 return TraversalDecision::Continue; | ||||
|             }); | ||||
| 
 | ||||
|             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; | ||||
|     }); | ||||
| 
 | ||||
|     // 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/ListItemMarkerBox.h> | ||||
| #include <LibWeb/Layout/Node.h> | ||||
| #include <LibWeb/Layout/SVGMaskBox.h> | ||||
| #include <LibWeb/Layout/TableGrid.h> | ||||
| #include <LibWeb/Layout/TableWrapper.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); | ||||
|     } | ||||
| 
 | ||||
|     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) | ||||
|         return; | ||||
| 
 | ||||
|  | @ -389,6 +396,19 @@ void TreeBuilder::create_layout_tree(DOM::Node& dom_node, TreeBuilder::Context& | |||
|         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
 | ||||
|     // 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()) { | ||||
|  |  | |||
|  | @ -23,6 +23,7 @@ public: | |||
| private: | ||||
|     struct Context { | ||||
|         bool has_svg_root = false; | ||||
|         bool layout_svg_mask = false; | ||||
|     }; | ||||
| 
 | ||||
|     i32 calculate_list_item_index(DOM::Node&); | ||||
|  |  | |||
|  | @ -5,6 +5,7 @@ | |||
|  */ | ||||
| 
 | ||||
| #include <LibWeb/Layout/ImageBox.h> | ||||
| #include <LibWeb/Layout/SVGMaskBox.h> | ||||
| #include <LibWeb/Painting/CommandExecutorCPU.h> | ||||
| #include <LibWeb/Painting/SVGGraphicsPaintable.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 | ||||
| { | ||||
|     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 | ||||
| { | ||||
|     auto const& graphics_element = verify_cast<SVG::SVGGraphicsElement const>(*dom_node()); | ||||
|     auto mask = graphics_element.mask(); | ||||
|     if (!mask) | ||||
|     auto* mask_box = graphics_element.layout_node()->first_child_of_type<Layout::SVGMaskBox>(); | ||||
|     if (!mask_box) | ||||
|         return {}; | ||||
|     auto target_area = [&] { | ||||
|         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); | ||||
|     return mask_box->dom_node().resolve_masking_area(mask_box->paintable_box()->absolute_border_box_rect()); | ||||
| } | ||||
| 
 | ||||
| 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 | ||||
| { | ||||
|     auto const& graphics_element = verify_cast<SVG::SVGGraphicsElement const>(*dom_node()); | ||||
|     auto mask = graphics_element.mask(); | ||||
|     VERIFY(mask); | ||||
|     if (mask->mask_content_units() != SVG::MaskContentUnits::UserSpaceOnUse) { | ||||
|         // FIXME: Implement support for maskContentUnits=objectBoundingBox
 | ||||
|         return {}; | ||||
|     } | ||||
|     auto* mask_box = graphics_element.layout_node()->first_child_of_type<Layout::SVGMaskBox>(); | ||||
|     VERIFY(mask_box); | ||||
|     auto mask_rect = context.enclosing_device_rect(masking_area); | ||||
|     RefPtr<Gfx::Bitmap> mask_bitmap = {}; | ||||
|     if (mask && mask->layout_node() && is<PaintableBox>(mask->layout_node()->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>()); | ||||
|         if (mask_bitmap_or_error.is_error()) | ||||
|             return {}; | ||||
|         mask_bitmap = mask_bitmap_or_error.release_value(); | ||||
|         { | ||||
|             CommandList painting_commands; | ||||
|             RecordingPainter recording_painter(painting_commands); | ||||
|             recording_painter.translate(-mask_rect.location().to_type<int>()); | ||||
|             auto paint_context = context.clone(recording_painter); | ||||
|             paint_context.set_svg_transform(graphics_element.get_transform()); | ||||
|             StackingContext::paint_node_as_stacking_context(mask_paintable, paint_context); | ||||
|             CommandExecutorCPU executor { *mask_bitmap }; | ||||
|             painting_commands.execute(executor); | ||||
|         } | ||||
|     auto& mask_paintable = static_cast<PaintableBox const&>(*mask_box->paintable()); | ||||
|     auto mask_bitmap_or_error = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, mask_rect.size().to_type<int>()); | ||||
|     if (mask_bitmap_or_error.is_error()) | ||||
|         return {}; | ||||
|     mask_bitmap = mask_bitmap_or_error.release_value(); | ||||
|     { | ||||
|         CommandList painting_commands; | ||||
|         RecordingPainter recording_painter(painting_commands); | ||||
|         recording_painter.translate(-mask_rect.location().to_type<int>()); | ||||
|         auto paint_context = context.clone(recording_painter); | ||||
|         paint_context.set_svg_transform(graphics_element.get_transform()); | ||||
|         StackingContext::paint_node_as_stacking_context(mask_paintable, paint_context); | ||||
|         CommandExecutorCPU executor { *mask_bitmap }; | ||||
|         painting_commands.execute(executor); | ||||
|     } | ||||
|     return mask_bitmap; | ||||
| } | ||||
|  |  | |||
|  | @ -49,8 +49,6 @@ public: | |||
| 
 | ||||
|     Layout::SVGGraphicsBox const& layout_box() const; | ||||
| 
 | ||||
|     virtual bool forms_unconnected_subtree() const override; | ||||
| 
 | ||||
|     virtual Optional<CSSPixelRect> get_masking_area() 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; | ||||
|  |  | |||
							
								
								
									
										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)); | ||||
| } | ||||
| 
 | ||||
| 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: | ||||
|     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: | ||||
|     SVGDefsElement(DOM::Document&, DOM::QualifiedName); | ||||
|  |  | |||
|  | @ -7,7 +7,6 @@ | |||
| 
 | ||||
| #include <LibWeb/Bindings/SVGMaskElementPrototype.h> | ||||
| #include <LibWeb/DOM/Document.h> | ||||
| #include <LibWeb/Layout/SVGGraphicsBox.h> | ||||
| #include <LibWeb/SVG/AttributeNames.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)); | ||||
| } | ||||
| 
 | ||||
| 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) | ||||
|  |  | |||
|  | @ -8,16 +8,34 @@ | |||
| 
 | ||||
| #include <LibWeb/SVG/AttributeParser.h> | ||||
| #include <LibWeb/SVG/SVGGraphicsElement.h> | ||||
| #include <LibWeb/SVG/SVGViewport.h> | ||||
| 
 | ||||
| namespace Web::SVG { | ||||
| 
 | ||||
| class SVGMaskElement final : public SVGGraphicsElement { | ||||
| class SVGMaskElement final : public SVGGraphicsElement | ||||
|     , public SVGViewport { | ||||
| 
 | ||||
|     WEB_PLATFORM_OBJECT(SVGMaskElement, SVGGraphicsElement); | ||||
|     JS_DECLARE_ALLOCATOR(SVGMaskElement); | ||||
| 
 | ||||
| public: | ||||
|     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 JS::GCPtr<Layout::Node> create_layout_node(NonnullRefPtr<CSS::StyleProperties>) override; | ||||
|  |  | |||
|  | @ -6,8 +6,7 @@ | |||
| 
 | ||||
| #include <LibWeb/Bindings/Intrinsics.h> | ||||
| #include <LibWeb/CSS/Parser/Parser.h> | ||||
| #include <LibWeb/CSS/StyleValues/IdentifierStyleValue.h> | ||||
| #include <LibWeb/Layout/BlockContainer.h> | ||||
| #include <LibWeb/CSS/StyleProperties.h> | ||||
| #include <LibWeb/SVG/AttributeNames.h> | ||||
| #include <LibWeb/SVG/AttributeParser.h> | ||||
| #include <LibWeb/SVG/SVGStopElement.h> | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 MacDue
						MacDue