Coverage Report

Created: 2026-02-16 07:47

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/serenity/Userland/Libraries/LibWeb/Painting/SVGMaskable.cpp
Line
Count
Source
1
/*
2
 * Copyright (c) 2024, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
3
 *
4
 * SPDX-License-Identifier: BSD-2-Clause
5
 */
6
7
#include <LibWeb/Layout/ImageBox.h>
8
#include <LibWeb/Layout/SVGClipBox.h>
9
#include <LibWeb/Layout/SVGMaskBox.h>
10
#include <LibWeb/Painting/DisplayListPlayerCPU.h>
11
#include <LibWeb/Painting/SVGClipPaintable.h>
12
#include <LibWeb/Painting/SVGGraphicsPaintable.h>
13
#include <LibWeb/Painting/StackingContext.h>
14
#include <LibWeb/SVG/SVGSVGElement.h>
15
16
namespace Web::Painting {
17
18
template<typename T>
19
static T const* first_child_layout_node_of_type(SVG::SVGGraphicsElement const& graphics_element)
20
0
{
21
0
    return graphics_element.layout_node()->first_child_of_type<T>();
22
0
}
Unexecuted instantiation: SVGMaskable.cpp:Web::Layout::SVGMaskBox const* Web::Painting::first_child_layout_node_of_type<Web::Layout::SVGMaskBox>(Web::SVG::SVGGraphicsElement const&)
Unexecuted instantiation: SVGMaskable.cpp:Web::Layout::SVGClipBox const* Web::Painting::first_child_layout_node_of_type<Web::Layout::SVGClipBox>(Web::SVG::SVGGraphicsElement const&)
23
24
static auto get_mask_box(SVG::SVGGraphicsElement const& graphics_element)
25
0
{
26
0
    return first_child_layout_node_of_type<Layout::SVGMaskBox>(graphics_element);
27
0
}
28
29
static auto get_clip_box(SVG::SVGGraphicsElement const& graphics_element)
30
0
{
31
0
    return first_child_layout_node_of_type<Layout::SVGClipBox>(graphics_element);
32
0
}
33
34
Optional<CSSPixelRect> SVGMaskable::get_masking_area_of_svg() const
35
0
{
36
0
    auto const& graphics_element = verify_cast<SVG::SVGGraphicsElement const>(*dom_node_of_svg());
37
0
    Optional<CSSPixelRect> masking_area = {};
38
0
    if (auto* mask_box = get_mask_box(graphics_element)) {
39
0
        masking_area = mask_box->dom_node().resolve_masking_area(mask_box->paintable_box()->absolute_border_box_rect());
40
0
    }
41
0
    if (auto* clip_box = get_clip_box(graphics_element)) {
42
        // This is a bit ad-hoc, but if we have both a mask and a clip-path, intersect the two areas to find the masking area.
43
0
        auto clip_area = clip_box->paintable_box()->absolute_border_box_rect();
44
0
        if (masking_area.has_value())
45
0
            masking_area = masking_area->intersected(clip_area);
46
0
        else
47
0
            masking_area = clip_area;
48
0
    }
49
0
    return masking_area;
50
0
}
51
52
static Gfx::Bitmap::MaskKind mask_type_to_gfx_mask_kind(CSS::MaskType mask_type)
53
0
{
54
0
    switch (mask_type) {
55
0
    case CSS::MaskType::Alpha:
56
0
        return Gfx::Bitmap::MaskKind::Alpha;
57
0
    case CSS::MaskType::Luminance:
58
0
        return Gfx::Bitmap::MaskKind::Luminance;
59
0
    default:
60
0
        VERIFY_NOT_REACHED();
61
0
    }
62
0
}
63
64
Optional<Gfx::Bitmap::MaskKind> SVGMaskable::get_mask_type_of_svg() const
65
0
{
66
0
    auto const& graphics_element = verify_cast<SVG::SVGGraphicsElement const>(*dom_node_of_svg());
67
0
    if (auto* mask_box = get_mask_box(graphics_element))
68
0
        return mask_type_to_gfx_mask_kind(mask_box->computed_values().mask_type());
69
0
    if (get_clip_box(graphics_element))
70
0
        return Gfx::Bitmap::MaskKind::Alpha;
71
0
    return {};
72
0
}
73
74
RefPtr<Gfx::Bitmap> SVGMaskable::calculate_mask_of_svg(PaintContext& context, CSSPixelRect const& masking_area) const
75
0
{
76
0
    auto const& graphics_element = verify_cast<SVG::SVGGraphicsElement const>(*dom_node_of_svg());
77
0
    auto mask_rect = context.enclosing_device_rect(masking_area);
78
0
    auto paint_mask_or_clip = [&](PaintableBox const& paintable) -> RefPtr<Gfx::Bitmap> {
79
0
        auto mask_bitmap_or_error = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, mask_rect.size().to_type<int>());
80
0
        RefPtr<Gfx::Bitmap> mask_bitmap = {};
81
0
        if (mask_bitmap_or_error.is_error())
82
0
            return {};
83
0
        mask_bitmap = mask_bitmap_or_error.release_value();
84
0
        auto display_list = DisplayList::create();
85
0
        DisplayListRecorder display_list_recorder(*display_list);
86
0
        display_list_recorder.translate(-mask_rect.location().to_type<int>());
87
0
        auto paint_context = context.clone(display_list_recorder);
88
0
        paint_context.set_svg_transform(graphics_element.get_transform());
89
0
        paint_context.set_draw_svg_geometry_for_clip_path(is<SVGClipPaintable>(paintable));
90
0
        StackingContext::paint_node_as_stacking_context(paintable, paint_context);
91
0
        DisplayListPlayerCPU display_list_player { *mask_bitmap };
92
0
        display_list_player.execute(display_list);
93
0
        return mask_bitmap;
94
0
    };
95
0
    RefPtr<Gfx::Bitmap> mask_bitmap = {};
96
0
    if (auto* mask_box = get_mask_box(graphics_element)) {
97
0
        auto& mask_paintable = static_cast<PaintableBox const&>(*mask_box->paintable());
98
0
        mask_bitmap = paint_mask_or_clip(mask_paintable);
99
0
    }
100
0
    if (auto* clip_box = get_clip_box(graphics_element)) {
101
0
        auto& clip_paintable = static_cast<PaintableBox const&>(*clip_box->paintable());
102
0
        auto clip_bitmap = paint_mask_or_clip(clip_paintable);
103
        // Combine the clip-path with the mask (if present).
104
0
        if (mask_bitmap && clip_bitmap)
105
0
            mask_bitmap->apply_mask(*clip_bitmap, Gfx::Bitmap::MaskKind::Alpha);
106
0
        if (!mask_bitmap)
107
0
            mask_bitmap = clip_bitmap;
108
0
    }
109
0
    return mask_bitmap;
110
0
}
111
112
}