Browse Source

LibWeb: Add `background-clip: text` support for InlinePaintable

Moves background mask calculation into `paint_background()` that is
shared between PaintableBox and InlinePaintable.
Aliaksandr Kalenik 1 year ago
parent
commit
a044e9cf4f

+ 11 - 0
Tests/LibWeb/Ref/inline-paintable-background-clip-text.html

@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<link rel="match" href="reference/inline-paintable-background-clip-text-ref.html" />
+<style>
+  #text {
+    background: linear-gradient(#6d98cc, #8a64e5);
+    background-clip: text;
+    color: transparent;
+    font-size: 50px;
+  }
+</style>
+<span id="text">Hello</span>

+ 10 - 0
Tests/LibWeb/Ref/reference/inline-paintable-background-clip-text-ref.html

@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<style>
+  #text {
+    background: linear-gradient(#6d98cc, #8a64e5);
+    background-clip: text;
+    color: transparent;
+    font-size: 50px;
+  }
+</style>
+<div id="text">Hello</div>

+ 68 - 6
Userland/Libraries/LibWeb/Painting/BackgroundPainting.cpp

@@ -7,14 +7,11 @@
  */
 
 #include <LibGfx/AntiAliasingPainter.h>
+#include <LibGfx/Font/ScaledFont.h>
 #include <LibWeb/Layout/Node.h>
 #include <LibWeb/Layout/Viewport.h>
 #include <LibWeb/Painting/BackgroundPainting.h>
-#include <LibWeb/Painting/BorderRadiusCornerClipper.h>
-#include <LibWeb/Painting/GradientPainting.h>
-#include <LibWeb/Painting/PaintContext.h>
-#include <LibWeb/Painting/PaintableBox.h>
-#include <LibWeb/Painting/RecordingPainter.h>
+#include <LibWeb/Painting/InlinePaintable.h>
 
 namespace Web::Painting {
 
@@ -60,9 +57,74 @@ static CSSPixelSize run_default_sizing_algorithm(
     return default_size;
 }
 
+static Vector<Gfx::Path> compute_text_clip_paths(PaintContext& context, Paintable const& paintable)
+{
+    Vector<Gfx::Path> text_clip_paths;
+    auto add_text_clip_path = [&](PaintableFragment const& fragment) {
+        // Scale to the device pixels.
+        Gfx::Path glyph_run_path;
+        for (auto glyph : fragment.glyph_run().glyphs()) {
+            glyph.visit([&](auto& glyph) {
+                glyph.font = *glyph.font->with_size(glyph.font->point_size() * static_cast<float>(context.device_pixels_per_css_pixel()));
+                glyph.position = glyph.position.scaled(context.device_pixels_per_css_pixel());
+            });
+
+            if (glyph.has<Gfx::DrawGlyph>()) {
+                auto const& draw_glyph = glyph.get<Gfx::DrawGlyph>();
+
+                // Get the path for the glyph.
+                Gfx::Path glyph_path;
+                auto const& scaled_font = static_cast<Gfx::ScaledFont const&>(*draw_glyph.font);
+                auto glyph_id = scaled_font.glyph_id_for_code_point(draw_glyph.code_point);
+                scaled_font.append_glyph_path_to(glyph_path, glyph_id);
+
+                // Transform the path to the fragment's position.
+                // FIXME: Record glyphs and use Painter::draw_glyphs() instead to avoid this duplicated code.
+                auto top_left = draw_glyph.position + Gfx::FloatPoint(scaled_font.glyph_left_bearing(draw_glyph.code_point), 0);
+                auto glyph_position = Gfx::GlyphRasterPosition::get_nearest_fit_for(top_left);
+                auto transform = Gfx::AffineTransform {}.translate(glyph_position.blit_position.to_type<float>());
+                glyph_run_path.append_path(glyph_path.copy_transformed(transform));
+            }
+        }
+
+        // Calculate the baseline start position.
+        auto fragment_absolute_rect = fragment.absolute_rect();
+        auto fragment_absolute_device_rect = context.enclosing_device_rect(fragment_absolute_rect);
+        DevicePixelPoint baseline_start { fragment_absolute_device_rect.x(), fragment_absolute_device_rect.y() + context.rounded_device_pixels(fragment.baseline()) };
+
+        // Add the path to text_clip_paths.
+        auto transform = Gfx::AffineTransform {}.translate(baseline_start.to_type<int>().to_type<float>());
+        text_clip_paths.append(glyph_run_path.copy_transformed(transform));
+    };
+
+    paintable.for_each_in_inclusive_subtree([&](auto& paintable) {
+        if (is<PaintableWithLines>(paintable)) {
+            auto const& paintable_lines = static_cast<PaintableWithLines const&>(paintable);
+            for (auto const& fragment : paintable_lines.fragments()) {
+                if (is<Layout::TextNode>(fragment.layout_node()))
+                    add_text_clip_path(fragment);
+            }
+        } else if (is<InlinePaintable>(paintable)) {
+            auto const& inline_paintable = static_cast<InlinePaintable const&>(paintable);
+            for (auto const& fragment : inline_paintable.fragments()) {
+                if (is<Layout::TextNode>(fragment.layout_node()))
+                    add_text_clip_path(fragment);
+            }
+        }
+        return TraversalDecision::Continue;
+    });
+
+    return text_clip_paths;
+}
+
 // https://www.w3.org/TR/css-backgrounds-3/#backgrounds
-void paint_background(PaintContext& context, Layout::NodeWithStyleAndBoxModelMetrics const& layout_node, CSSPixelRect const& border_rect, Color background_color, CSS::ImageRendering image_rendering, Vector<CSS::BackgroundLayerData> const* background_layers, BorderRadiiData const& border_radii, Vector<Gfx::Path> const& clip_paths)
+void paint_background(PaintContext& context, Layout::NodeWithStyleAndBoxModelMetrics const& layout_node, CSSPixelRect const& border_rect, Color background_color, CSS::ImageRendering image_rendering, Vector<CSS::BackgroundLayerData> const* background_layers, BorderRadiiData const& border_radii)
 {
+    Vector<Gfx::Path> clip_paths {};
+    if (background_layers && !background_layers->is_empty() && background_layers->last().clip == CSS::BackgroundBox::Text) {
+        clip_paths = compute_text_clip_paths(context, *layout_node.paintable());
+    }
+
     auto& painter = context.recording_painter();
 
     struct BackgroundBox {

+ 1 - 1
Userland/Libraries/LibWeb/Painting/BackgroundPainting.h

@@ -12,6 +12,6 @@
 
 namespace Web::Painting {
 
-void paint_background(PaintContext&, Layout::NodeWithStyleAndBoxModelMetrics const&, CSSPixelRect const&, Color background_color, CSS::ImageRendering, Vector<CSS::BackgroundLayerData> const*, BorderRadiiData const&, Vector<Gfx::Path> const& clip_paths = {});
+void paint_background(PaintContext&, Layout::NodeWithStyleAndBoxModelMetrics const&, CSSPixelRect const&, Color background_color, CSS::ImageRendering, Vector<CSS::BackgroundLayerData> const*, BorderRadiiData const&);
 
 }

+ 1 - 66
Userland/Libraries/LibWeb/Painting/PaintableBox.cpp

@@ -478,72 +478,7 @@ void PaintableBox::paint_background(PaintContext& context) const
     if (computed_values().border_top().width != 0 || computed_values().border_right().width != 0 || computed_values().border_bottom().width != 0 || computed_values().border_left().width != 0)
         background_rect = absolute_border_box_rect();
 
-    Vector<Gfx::Path> text_clip_paths {};
-    if (background_layers && !background_layers->is_empty() && background_layers->last().clip == CSS::BackgroundBox::Text) {
-        text_clip_paths = compute_text_clip_paths(context);
-    }
-
-    Painting::paint_background(context, layout_box(), background_rect, background_color, computed_values().image_rendering(), background_layers, normalized_border_radii_data(), text_clip_paths);
-}
-
-Vector<Gfx::Path> PaintableBox::compute_text_clip_paths(PaintContext& context) const
-{
-    Vector<Gfx::Path> text_clip_paths;
-    auto add_text_clip_path = [&](PaintableFragment const& fragment) {
-        // Scale to the device pixels.
-        Gfx::Path glyph_run_path;
-        for (auto glyph : fragment.glyph_run().glyphs()) {
-            glyph.visit([&](auto& glyph) {
-                glyph.font = *glyph.font->with_size(glyph.font->point_size() * static_cast<float>(context.device_pixels_per_css_pixel()));
-                glyph.position = glyph.position.scaled(context.device_pixels_per_css_pixel());
-            });
-
-            if (glyph.has<Gfx::DrawGlyph>()) {
-                auto const& draw_glyph = glyph.get<Gfx::DrawGlyph>();
-
-                // Get the path for the glyph.
-                Gfx::Path glyph_path;
-                auto const& scaled_font = static_cast<Gfx::ScaledFont const&>(*draw_glyph.font);
-                auto glyph_id = scaled_font.glyph_id_for_code_point(draw_glyph.code_point);
-                scaled_font.append_glyph_path_to(glyph_path, glyph_id);
-
-                // Transform the path to the fragment's position.
-                // FIXME: Record glyphs and use Painter::draw_glyphs() instead to avoid this duplicated code.
-                auto top_left = draw_glyph.position + Gfx::FloatPoint(scaled_font.glyph_left_bearing(draw_glyph.code_point), 0);
-                auto glyph_position = Gfx::GlyphRasterPosition::get_nearest_fit_for(top_left);
-                auto transform = Gfx::AffineTransform {}.translate(glyph_position.blit_position.to_type<float>());
-                glyph_run_path.append_path(glyph_path.copy_transformed(transform));
-            }
-        }
-
-        // Calculate the baseline start position.
-        auto fragment_absolute_rect = fragment.absolute_rect();
-        auto fragment_absolute_device_rect = context.enclosing_device_rect(fragment_absolute_rect);
-        DevicePixelPoint baseline_start { fragment_absolute_device_rect.x(), fragment_absolute_device_rect.y() + context.rounded_device_pixels(fragment.baseline()) };
-
-        // Add the path to text_clip_paths.
-        auto transform = Gfx::AffineTransform {}.translate(baseline_start.to_type<int>().to_type<float>());
-        text_clip_paths.append(glyph_run_path.copy_transformed(transform));
-    };
-
-    for_each_in_inclusive_subtree([&](auto& paintable) {
-        if (is<PaintableWithLines>(paintable)) {
-            auto const& paintable_lines = static_cast<PaintableWithLines const&>(paintable);
-            for (auto const& fragment : paintable_lines.fragments()) {
-                if (is<Layout::TextNode>(fragment.layout_node()))
-                    add_text_clip_path(fragment);
-            }
-        } else if (is<InlinePaintable>(paintable)) {
-            auto const& inline_paintable = static_cast<InlinePaintable const&>(paintable);
-            for (auto const& fragment : inline_paintable.fragments()) {
-                if (is<Layout::TextNode>(fragment.layout_node()))
-                    add_text_clip_path(fragment);
-            }
-        }
-        return TraversalDecision::Continue;
-    });
-
-    return text_clip_paths;
+    Painting::paint_background(context, layout_box(), background_rect, background_color, computed_values().image_rendering(), background_layers, normalized_border_radii_data());
 }
 
 void PaintableBox::paint_box_shadow(PaintContext& context) const

+ 0 - 2
Userland/Libraries/LibWeb/Painting/PaintableBox.h

@@ -227,8 +227,6 @@ protected:
     virtual CSSPixelRect compute_absolute_paint_rect() const;
 
 private:
-    Vector<Gfx::Path> compute_text_clip_paths(PaintContext&) const;
-
     [[nodiscard]] virtual bool is_paintable_box() const final { return true; }
 
     enum class ScrollDirection {