mirror of
https://github.com/SerenityOS/serenity.git
synced 2025-01-24 10:22:05 -05:00
LibWeb: Add background-clip: text
support for InlinePaintable
Moves background mask calculation into `paint_background()` that is shared between PaintableBox and InlinePaintable.
This commit is contained in:
parent
3aea14093f
commit
a044e9cf4f
6 changed files with 92 additions and 76 deletions
11
Tests/LibWeb/Ref/inline-paintable-background-clip-text.html
Normal file
11
Tests/LibWeb/Ref/inline-paintable-background-clip-text.html
Normal file
|
@ -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>
|
|
@ -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>
|
|
@ -7,14 +7,11 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <LibGfx/AntiAliasingPainter.h>
|
#include <LibGfx/AntiAliasingPainter.h>
|
||||||
|
#include <LibGfx/Font/ScaledFont.h>
|
||||||
#include <LibWeb/Layout/Node.h>
|
#include <LibWeb/Layout/Node.h>
|
||||||
#include <LibWeb/Layout/Viewport.h>
|
#include <LibWeb/Layout/Viewport.h>
|
||||||
#include <LibWeb/Painting/BackgroundPainting.h>
|
#include <LibWeb/Painting/BackgroundPainting.h>
|
||||||
#include <LibWeb/Painting/BorderRadiusCornerClipper.h>
|
#include <LibWeb/Painting/InlinePaintable.h>
|
||||||
#include <LibWeb/Painting/GradientPainting.h>
|
|
||||||
#include <LibWeb/Painting/PaintContext.h>
|
|
||||||
#include <LibWeb/Painting/PaintableBox.h>
|
|
||||||
#include <LibWeb/Painting/RecordingPainter.h>
|
|
||||||
|
|
||||||
namespace Web::Painting {
|
namespace Web::Painting {
|
||||||
|
|
||||||
|
@ -60,9 +57,74 @@ static CSSPixelSize run_default_sizing_algorithm(
|
||||||
return default_size;
|
return default_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://www.w3.org/TR/css-backgrounds-3/#backgrounds
|
static Vector<Gfx::Path> compute_text_clip_paths(PaintContext& context, Paintable const& paintable)
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
|
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> 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();
|
auto& painter = context.recording_painter();
|
||||||
|
|
||||||
struct BackgroundBox {
|
struct BackgroundBox {
|
||||||
|
|
|
@ -12,6 +12,6 @@
|
||||||
|
|
||||||
namespace Web::Painting {
|
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&);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
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();
|
background_rect = absolute_border_box_rect();
|
||||||
|
|
||||||
Vector<Gfx::Path> text_clip_paths {};
|
Painting::paint_background(context, layout_box(), background_rect, background_color, computed_values().image_rendering(), background_layers, normalized_border_radii_data());
|
||||||
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
|
void PaintableBox::paint_box_shadow(PaintContext& context) const
|
||||||
|
|
|
@ -227,8 +227,6 @@ protected:
|
||||||
virtual CSSPixelRect compute_absolute_paint_rect() const;
|
virtual CSSPixelRect compute_absolute_paint_rect() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Vector<Gfx::Path> compute_text_clip_paths(PaintContext&) const;
|
|
||||||
|
|
||||||
[[nodiscard]] virtual bool is_paintable_box() const final { return true; }
|
[[nodiscard]] virtual bool is_paintable_box() const final { return true; }
|
||||||
|
|
||||||
enum class ScrollDirection {
|
enum class ScrollDirection {
|
||||||
|
|
Loading…
Add table
Reference in a new issue