diff --git a/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp b/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp index a11a555bd3..b89eeb1b65 100644 --- a/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp +++ b/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp @@ -407,6 +407,35 @@ void PaintableWithLines::paint(PaintContext& context, PaintPhase phase) const context.painter().translate(-scroll_offset.to_type()); } + // Text shadows + // This is yet another loop, but done here because all shadows should appear under all text. + // So, we paint the shadows before painting any text. + // FIXME: Find a smarter way to do this? + if (phase == PaintPhase::Foreground) { + for (auto& line_box : m_line_boxes) { + for (auto& fragment : line_box.fragments()) { + if (is(fragment.layout_node())) { + auto& text_shadow = fragment.layout_node().computed_values().text_shadow(); + if (!text_shadow.is_empty()) { + Vector resolved_shadow_data; + resolved_shadow_data.ensure_capacity(text_shadow.size()); + for (auto const& layer : text_shadow) { + resolved_shadow_data.empend( + layer.color, + static_cast(layer.offset_x.to_px(layout_box())), + static_cast(layer.offset_y.to_px(layout_box())), + static_cast(layer.blur_radius.to_px(layout_box())), + static_cast(layer.spread_distance.to_px(layout_box())), + ShadowPlacement::Outer); + } + context.painter().set_font(fragment.layout_node().font()); + Painting::paint_text_shadow(context, fragment, resolved_shadow_data); + } + } + } + } + } + for (auto& line_box : m_line_boxes) { for (auto& fragment : line_box.fragments()) { if (context.should_show_line_box_borders()) diff --git a/Userland/Libraries/LibWeb/Painting/ShadowPainting.cpp b/Userland/Libraries/LibWeb/Painting/ShadowPainting.cpp index 1c9e0739b1..6942f63773 100644 --- a/Userland/Libraries/LibWeb/Painting/ShadowPainting.cpp +++ b/Userland/Libraries/LibWeb/Painting/ShadowPainting.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -124,4 +125,53 @@ void paint_box_shadow(PaintContext& context, Gfx::IntRect const& content_rect, V } } +void paint_text_shadow(PaintContext& context, Layout::LineBoxFragment const& fragment, Vector const& shadow_layers) +{ + if (shadow_layers.is_empty()) + return; + + auto& painter = context.painter(); + + // Note: Box-shadow layers are ordered front-to-back, so we paint them in reverse + for (auto& layer : shadow_layers.in_reverse()) { + + // Space around the painted text to allow it to blur. + // FIXME: Include spread in this once we use that. + auto margin = layer.blur_radius * 2; + Gfx::IntRect text_rect { + margin, margin, + static_cast(ceilf(fragment.width())), + static_cast(ceilf(fragment.height())) + }; + Gfx::IntRect bounding_rect { + 0, 0, + text_rect.width() + margin + margin, + text_rect.height() + margin + margin + }; + // FIXME: Figure out the maximum bitmap size for all shadows and then allocate it once and reuse it? + auto maybe_shadow_bitmap = Gfx::Bitmap::try_create(Gfx::BitmapFormat::BGRA8888, bounding_rect.size()); + if (maybe_shadow_bitmap.is_error()) { + dbgln("Unable to allocate temporary bitmap for box-shadow rendering: {}", maybe_shadow_bitmap.error()); + return; + } + auto shadow_bitmap = maybe_shadow_bitmap.release_value(); + + Gfx::Painter shadow_painter { *shadow_bitmap }; + shadow_painter.set_font(context.painter().font()); + // FIXME: "Spread" the shadow somehow. + shadow_painter.draw_text(text_rect, fragment.text(), Gfx::TextAlignment::TopLeft, layer.color); + + // Blur + Gfx::FastBoxBlurFilter filter(*shadow_bitmap); + filter.apply_three_passes(layer.blur_radius); + + auto draw_rect = Gfx::enclosing_int_rect(fragment.absolute_rect()); + Gfx::IntPoint draw_location { + draw_rect.x() + layer.offset_x - margin, + draw_rect.y() + layer.offset_y - margin + }; + painter.blit(draw_location, *shadow_bitmap, bounding_rect); + } +} + } diff --git a/Userland/Libraries/LibWeb/Painting/ShadowPainting.h b/Userland/Libraries/LibWeb/Painting/ShadowPainting.h index 2e6738b75e..129a969682 100644 --- a/Userland/Libraries/LibWeb/Painting/ShadowPainting.h +++ b/Userland/Libraries/LibWeb/Painting/ShadowPainting.h @@ -7,6 +7,7 @@ #pragma once #include +#include #include namespace Web::Painting { @@ -26,5 +27,6 @@ struct ShadowData { }; void paint_box_shadow(PaintContext&, Gfx::IntRect const&, Vector const&); +void paint_text_shadow(PaintContext&, Layout::LineBoxFragment const&, Vector const&); }