Paint the background phase for replaced boxes reached through SVG foreignObject traversal before foreground content. This lets img background images render correctly when the containing SVG establishes a stacking context. Add a ref test for a paint-contained SVG with a foreignObject-hosted img that uses a transparent SVG source and a CSS background image.
100 lines
3.9 KiB
C++
100 lines
3.9 KiB
C++
/*
|
|
* Copyright (c) 2018-2022, Andreas Kling <andreas@ladybird.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <LibWeb/Layout/ReplacedBox.h>
|
|
#include <LibWeb/Painting/DisplayList.h>
|
|
#include <LibWeb/Painting/DisplayListRecorder.h>
|
|
#include <LibWeb/Painting/SVGSVGPaintable.h>
|
|
|
|
namespace Web::Painting {
|
|
|
|
NonnullRefPtr<SVGSVGPaintable> SVGSVGPaintable::create(Layout::SVGSVGBox const& layout_box)
|
|
{
|
|
return adopt_ref(*new SVGSVGPaintable(layout_box));
|
|
}
|
|
|
|
SVGSVGPaintable::SVGSVGPaintable(Layout::SVGSVGBox const& layout_box)
|
|
: PaintableBox(layout_box)
|
|
{
|
|
}
|
|
|
|
static void record_foreign_object_descendant_hit_test_items(DisplayListRecordingContext& context, PaintableBox const& paintable)
|
|
{
|
|
paintable.for_each_child_of_type<PaintableBox>([&](PaintableBox& child) {
|
|
child.record_hit_test_items(context, PaintPhase::Background);
|
|
record_foreign_object_descendant_hit_test_items(context, child);
|
|
child.record_hit_test_items(context, PaintPhase::Foreground);
|
|
child.record_hit_test_items(context, PaintPhase::Overlay);
|
|
return IterationDecision::Continue;
|
|
});
|
|
}
|
|
|
|
void SVGSVGPaintable::paint_svg_box(DisplayListRecordingContext& context, PaintableBox const& svg_box, PaintPhase phase)
|
|
{
|
|
context.display_list_recorder().set_accumulated_visual_context(svg_box.accumulated_visual_context_index());
|
|
|
|
// For elements with SVG filters, emit a transparent FillRect to trigger filter application.
|
|
// This ensures content-generating filters (feFlood, feImage) work even with empty source.
|
|
if (auto const& bounds = svg_box.filter().svg_filter_bounds; bounds.has_value()) {
|
|
auto device_rect = context.enclosing_device_rect(*bounds).to_type<int>();
|
|
context.display_list_recorder().fill_rect_transparent(device_rect);
|
|
}
|
|
|
|
// Collect masks (SVG <mask>, SVG <clipPath>).
|
|
Vector<DisplayListRecorder::MaskInfo> masks;
|
|
|
|
bool skip_painting = false;
|
|
|
|
auto mask_area = svg_box.get_mask_area();
|
|
if (mask_area.has_value()) {
|
|
if (mask_area->is_empty()) {
|
|
skip_painting = true;
|
|
} else if (auto mask_display_list = svg_box.calculate_mask(context, *mask_area); mask_display_list.has_value()) {
|
|
auto rect = context.enclosing_device_rect(*mask_area).to_type<int>();
|
|
auto kind = svg_box.get_mask_type().value_or(Gfx::MaskKind::Alpha);
|
|
masks.append({ mask_display_list.release_value(), rect, kind });
|
|
}
|
|
}
|
|
|
|
auto clip_area = svg_box.get_clip_area();
|
|
if (clip_area.has_value()) {
|
|
if (clip_area->is_empty()) {
|
|
skip_painting = true;
|
|
} else if (auto clip_display_list = svg_box.calculate_clip(context, *clip_area); clip_display_list.has_value()) {
|
|
auto rect = context.enclosing_device_rect(*clip_area).to_type<int>();
|
|
masks.append({ clip_display_list.release_value(), rect, Gfx::MaskKind::Alpha });
|
|
}
|
|
}
|
|
|
|
context.display_list_recorder().begin_masks(masks);
|
|
|
|
if (!skip_painting) {
|
|
svg_box.record_hit_test_items(context, phase);
|
|
if (svg_box.layout_node().is_svg_foreign_object_box())
|
|
record_foreign_object_descendant_hit_test_items(context, svg_box);
|
|
if (!svg_box.is_svg_paintable()
|
|
&& !svg_box.is_svg_svg_paintable()
|
|
&& is<Layout::ReplacedBox>(svg_box.layout_node()))
|
|
svg_box.paint(context, PaintPhase::Background);
|
|
svg_box.paint(context, PaintPhase::Foreground);
|
|
paint_descendants(context, svg_box, phase);
|
|
}
|
|
|
|
context.display_list_recorder().end_masks(masks);
|
|
}
|
|
|
|
void SVGSVGPaintable::paint_descendants(DisplayListRecordingContext& context, PaintableBox const& paintable, PaintPhase phase)
|
|
{
|
|
if (phase != PaintPhase::Foreground)
|
|
return;
|
|
|
|
paintable.for_each_child_of_type<PaintableBox>([&](PaintableBox& child) {
|
|
paint_svg_box(context, child, phase);
|
|
return IterationDecision::Continue;
|
|
});
|
|
}
|
|
|
|
}
|