/* * Copyright (c) 2024, Aliaksandr Kalenik * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include #include namespace Web::Painting { template static T const* first_child_layout_node_of_type(SVG::SVGGraphicsElement const& graphics_element) { if (!graphics_element.layout_node()) return nullptr; return graphics_element.layout_node()->first_child_of_type(); } static auto get_mask_box(SVG::SVGGraphicsElement const& graphics_element) { return first_child_layout_node_of_type(graphics_element); } static auto get_clip_box(SVG::SVGGraphicsElement const& graphics_element) { return first_child_layout_node_of_type(graphics_element); } Optional SVGMaskable::get_masking_area_of_svg() const { auto const& graphics_element = as(*dom_node_of_svg()); Optional masking_area = {}; if (auto* mask_box = get_mask_box(graphics_element)) { masking_area = mask_box->dom_node().resolve_masking_area(mask_box->paintable_box()->absolute_border_box_rect()); } if (auto* clip_box = get_clip_box(graphics_element)) { // 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. auto clip_area = clip_box->paintable_box()->absolute_border_box_rect(); if (masking_area.has_value()) masking_area = masking_area->intersected(clip_area); else masking_area = clip_area; } return masking_area; } static Gfx::Bitmap::MaskKind mask_type_to_gfx_mask_kind(CSS::MaskType mask_type) { switch (mask_type) { case CSS::MaskType::Alpha: return Gfx::Bitmap::MaskKind::Alpha; case CSS::MaskType::Luminance: return Gfx::Bitmap::MaskKind::Luminance; default: VERIFY_NOT_REACHED(); } } Optional SVGMaskable::get_mask_type_of_svg() const { auto const& graphics_element = as(*dom_node_of_svg()); if (auto* mask_box = get_mask_box(graphics_element)) return mask_type_to_gfx_mask_kind(mask_box->computed_values().mask_type()); if (get_clip_box(graphics_element)) return Gfx::Bitmap::MaskKind::Alpha; return {}; } RefPtr SVGMaskable::calculate_mask_of_svg(PaintContext& context, CSSPixelRect const& masking_area) const { auto const& graphics_element = as(*dom_node_of_svg()); auto mask_rect = context.enclosing_device_rect(masking_area); auto paint_mask_or_clip = [&](PaintableBox const& paintable) -> RefPtr { auto mask_bitmap_or_error = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, mask_rect.size().to_type()); RefPtr mask_bitmap = {}; if (mask_bitmap_or_error.is_error()) return {}; mask_bitmap = mask_bitmap_or_error.release_value(); auto display_list = DisplayList::create(); DisplayListRecorder display_list_recorder(*display_list); display_list_recorder.translate(-mask_rect.location().to_type()); auto paint_context = context.clone(display_list_recorder); paint_context.set_svg_transform(graphics_element.get_transform()); paint_context.set_draw_svg_geometry_for_clip_path(is(paintable)); StackingContext::paint_svg(paint_context, paintable, PaintPhase::Foreground); DisplayListPlayerSkia display_list_player { *mask_bitmap }; display_list_player.execute(display_list); return mask_bitmap; }; RefPtr mask_bitmap = {}; if (auto* mask_box = get_mask_box(graphics_element)) { auto& mask_paintable = static_cast(*mask_box->first_paintable()); mask_bitmap = paint_mask_or_clip(mask_paintable); } if (auto* clip_box = get_clip_box(graphics_element)) { auto& clip_paintable = static_cast(*clip_box->first_paintable()); auto clip_bitmap = paint_mask_or_clip(clip_paintable); // Combine the clip-path with the mask (if present). if (mask_bitmap && clip_bitmap) mask_bitmap->apply_mask(*clip_bitmap, Gfx::Bitmap::MaskKind::Alpha); if (!mask_bitmap) mask_bitmap = clip_bitmap; } return Gfx::ImmutableBitmap::create(*mask_bitmap); } }