From 5aad32b5042ebd3fedc8f0d0dc5871d78a7ac6c5 Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Thu, 24 Mar 2022 15:20:25 +0000 Subject: [PATCH] LibWeb: Implement text-shadow painting We don't yet take the spread-distance parameter into account, since we don't have a way to "inflate" the text shadow. Also, I'm not sure if we need to inflate the shadow slightly anyway. Blurred shadows of our pixel fonts seem very faint. Part of this is that a blur of < 3px does nothing, see #13231, but even so we might want to inflate it a little. --- .../LibWeb/Painting/PaintableBox.cpp | 29 +++++++++++ .../LibWeb/Painting/ShadowPainting.cpp | 50 +++++++++++++++++++ .../LibWeb/Painting/ShadowPainting.h | 2 + 3 files changed, 81 insertions(+) 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&); }