ladybird/Libraries/LibWeb/Painting/ShadowPainting.cpp
Aliaksandr Kalenik 537f289c0c LibGfx+LibWeb: Move display-list corner types to LibGfx
Prep for moving display-list rasterization out of LibWeb by moving the
device-pixel corner radius, corner clip, and anti-aliasing types to
LibGfx.

Keep BorderRadiiData in LibWeb as the CSS-pixel representation and
convert it to Gfx corner radii when recording display-list commands.
2026-05-12 16:30:26 +02:00

151 lines
6.8 KiB
C++

/*
* Copyright (c) 2018-2020, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2021-2022, Sam Atkins <atkinssj@serenityos.org>
* Copyright (c) 2022, MacDue <macdue@dueutil.tech>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibWeb/Painting/BorderRadiusCornerClipper.h>
#include <LibWeb/Painting/DisplayListRecorder.h>
#include <LibWeb/Painting/DisplayListRecordingContext.h>
#include <LibWeb/Painting/PaintableBox.h>
#include <LibWeb/Painting/ShadowPainting.h>
namespace Web::Painting {
void paint_box_shadow(DisplayListRecordingContext& context,
CSSPixelRect const& bordered_content_rect,
CSSPixelRect const& borderless_content_rect,
BordersData const& borders_data,
BorderRadiiData const& border_radii,
Vector<ShadowData> const& box_shadow_layers)
{
auto corner_radii = border_radii.as_corners(context.device_pixel_converter());
// Note: Box-shadow layers are ordered front-to-back, so we paint them in reverse
for (auto& box_shadow_data : box_shadow_layers.in_reverse()) {
auto offset_x = context.rounded_device_pixels(box_shadow_data.offset_x).value();
auto offset_y = context.rounded_device_pixels(box_shadow_data.offset_y).value();
auto blur_radius = context.rounded_device_pixels(box_shadow_data.blur_radius).value();
auto spread_distance = context.rounded_device_pixels(box_shadow_data.spread_distance).value();
DevicePixelRect device_content_rect;
if (box_shadow_data.placement == ShadowPlacement::Inner) {
device_content_rect = context.rounded_device_rect(borderless_content_rect);
} else {
device_content_rect = context.rounded_device_rect(bordered_content_rect);
}
auto device_content_rect_int = device_content_rect.to_type<int>();
if (box_shadow_data.placement == ShadowPlacement::Inner) {
auto outer_shadow_rect = device_content_rect_int.translated({ offset_x, offset_y });
auto inner_shadow_rect = outer_shadow_rect.inflated(-spread_distance, -spread_distance, -spread_distance, -spread_distance);
outer_shadow_rect.inflate(
blur_radius + offset_y,
blur_radius + abs(offset_x),
blur_radius + abs(offset_y),
blur_radius + offset_x);
auto inner_shadow_corner_radii = corner_radii;
inner_shadow_corner_radii.adjust_corners_for_spread_distance(-spread_distance);
auto shrinked_border_radii = border_radii;
shrinked_border_radii.shrink(borders_data.top.width, borders_data.right.width, borders_data.bottom.width, borders_data.left.width);
ScopedCornerRadiusClip corner_clipper { context, device_content_rect, shrinked_border_radii, Gfx::CornerClip::Outside };
context.display_list_recorder().paint_inner_box_shadow(PaintInnerBoxShadow {
.color = box_shadow_data.color,
.blur_radius = blur_radius,
.device_content_rect = device_content_rect_int,
.content_corner_radii = corner_radii,
.outer_shadow_rect = outer_shadow_rect,
.inner_shadow_rect = inner_shadow_rect,
.inner_shadow_corner_radii = inner_shadow_corner_radii,
});
} else {
auto shadow_rect = device_content_rect_int;
shadow_rect.inflate(spread_distance, spread_distance, spread_distance, spread_distance);
shadow_rect.translate_by(offset_x, offset_y);
auto shadow_corner_radii = corner_radii;
shadow_corner_radii.adjust_corners_for_spread_distance(spread_distance);
ScopedCornerRadiusClip corner_clipper { context, device_content_rect, border_radii, Gfx::CornerClip::Inside };
context.display_list_recorder().paint_outer_box_shadow(PaintOuterBoxShadow {
.color = box_shadow_data.color,
.blur_radius = blur_radius,
.device_content_rect = device_content_rect_int,
.content_corner_radii = corner_radii,
.shadow_rect = shadow_rect,
.shadow_corner_radii = shadow_corner_radii,
});
}
}
}
void paint_text_shadow(DisplayListRecordingContext& context, PaintableFragment::FragmentSpan const& span)
{
auto const& fragment = span.fragment;
auto const& shadow_layers = span.shadow_layers.has_value() ? *span.shadow_layers : fragment.shadows();
if (shadow_layers.is_empty())
return;
auto glyph_run = fragment.glyph_run();
if (!glyph_run || glyph_run->glyphs().is_empty())
return;
// If this is a partial span, slice the glyph run to only include the relevant glyphs.
auto const& glyphs = glyph_run->glyphs();
if (span.start_code_unit != 0 || span.end_code_unit != fragment.length_in_code_units()) {
size_t start_glyph = 0;
size_t glyph_count = 0;
size_t code_unit_offset = 0;
for (size_t i = 0; i < glyphs.size(); ++i) {
if (code_unit_offset == span.start_code_unit)
start_glyph = i;
code_unit_offset += glyphs[i].length_in_code_units;
if (code_unit_offset == span.end_code_unit) {
glyph_count = i - start_glyph + 1;
break;
}
}
if (glyph_count > 0)
glyph_run = glyph_run->slice(start_glyph, glyph_count);
}
auto fragment_width = context.enclosing_device_pixels(fragment.width()).value();
auto fragment_height = context.enclosing_device_pixels(fragment.height()).value();
auto fragment_baseline = context.rounded_device_pixels(fragment.baseline()).value();
auto fragment_absolute_rect = fragment.absolute_rect();
// Note: Box-shadow layers are ordered front-to-back, so we paint them in reverse
for (auto const& layer : shadow_layers.in_reverse()) {
int blur_radius = context.rounded_device_pixels(layer.blur_radius).value();
// Space around the painted text to allow it to blur.
int margin = blur_radius * 2;
Gfx::IntRect text_rect {
margin, margin,
fragment_width, fragment_height
};
Gfx::IntRect bounding_rect {
0, 0,
text_rect.width() + margin + margin,
text_rect.height() + margin + margin
};
// FIXME: this is close but not quite perfect. non integer scale values can be offset by tiny amounts.
auto css_margin = layer.blur_radius * 2;
auto scale = context.device_pixels_per_css_pixel();
auto draw_location = Gfx::FloatPoint {
fragment_absolute_rect.x() + layer.offset_x - css_margin,
fragment_absolute_rect.y() + layer.offset_y - css_margin,
} * (float)scale;
context.display_list_recorder().paint_text_shadow(blur_radius, bounding_rect, text_rect.translated(0, fragment_baseline), *glyph_run, scale, layer.color, draw_location);
}
}
}