1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 23:07:35 +00:00

LibWeb: Support CSS property background-clip: text

From https://drafts.csswg.org/css-backgrounds-4/#background-clip
"The background is painted within (clipped to) the intersection of the
border box and the geometry of the text in the element and its in-flow
and floated descendants"

This change implements it in the following way:
1. Traverse the descendants of the element, collecting the Gfx::Path of
   glyphs into a vector.
2. The vector of collected paths is saved in the background painting
   command.
3. The painting commands executor uses the list of glyphs to paint a
   mask for background clipping.

Co-authored-by: Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
This commit is contained in:
Zac Brannelly 2024-03-03 14:59:02 +11:00 committed by Alexander Kalenik
parent 4a3680cafc
commit 9165faca5e
27 changed files with 353 additions and 105 deletions

View file

@ -6,6 +6,7 @@
*/
#include <AK/GenericShorthands.h>
#include <LibGfx/Font/ScaledFont.h>
#include <LibUnicode/CharacterTypes.h>
#include <LibWeb/CSS/SystemColor.h>
#include <LibWeb/DOM/Document.h>
@ -455,7 +456,72 @@ 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();
Painting::paint_background(context, layout_box(), background_rect, background_color, computed_values().image_rendering(), background_layers, normalized_border_radii_data());
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;
}
void PaintableBox::paint_box_shadow(PaintContext& context) const