mirror of
https://github.com/RGBCube/serenity
synced 2025-07-27 13:27:35 +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
|
@ -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&);
|
||||
};
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue