mirror of
https://github.com/LadybirdBrowser/ladybird
synced 2026-05-11 09:27:00 +02:00
Move shadow rect computation, offset translation, and corner radii spread-distance adjustment from the Skia display list player to the recording site in ShadowPainting.cpp. This avoids recomputing derived geometry every time the display list is replayed. Replace PaintBoxShadowParams with precomputed fields directly on PaintOuterBoxShadow and PaintInnerBoxShadow command structs. Add CornerRadii::adjust_corners_for_spread_distance() to centralize the CSS spec's spread-distance-to-radius adjustment logic.
152 lines
6.8 KiB
C++
152 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, 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, 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);
|
|
}
|
|
}
|
|
|
|
}
|