mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-08-11 02:29:21 +00:00
LibGfx+LibWeb: Draw glyph runs with subpixel accuracy
This improves the quality of our font rendering, especially when animations are involved. Relevant changes: * Skia fonts have their subpixel flag set, which means that individual glyphs are rendered at subpixel offsets causing glyph runs as a whole to look better. * Fragment offsets are no longer rounded to whole device pixels, and instead the floating point offset is kept. This allows us to pass through the floating point baseline position all the way to the Skia calls, which already expected that to be a float position. The `scrollable-contains-table.html` ref test needed different table headings since they would slightly inflate the column size in the test file, but not the reference.
This commit is contained in:
parent
e32a9b2c6f
commit
4d9f17eddf
Notes:
github-actions[bot]
2024-12-21 22:10:48 +00:00
Author: https://github.com/gmta
Commit: 4d9f17eddf
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/2850
Reviewed-by: https://github.com/shlyakpavel
83 changed files with 524 additions and 525 deletions
|
@ -5,10 +5,8 @@
|
|||
*/
|
||||
|
||||
#include <AK/Utf8View.h>
|
||||
#include <LibWeb/DOM/Range.h>
|
||||
#include <LibWeb/Layout/LayoutState.h>
|
||||
#include <LibWeb/Layout/TextNode.h>
|
||||
#include <LibWeb/Layout/Viewport.h>
|
||||
#include <ctype.h>
|
||||
|
||||
namespace Web::Layout {
|
||||
|
|
|
@ -269,7 +269,7 @@ void LineBuilder::update_last_line()
|
|||
for (size_t i = 0; i < line_box.fragments().size(); ++i) {
|
||||
auto& fragment = line_box.fragments()[i];
|
||||
|
||||
CSSPixels new_fragment_inline_offset = round(inline_offset + fragment.inline_offset());
|
||||
CSSPixels new_fragment_inline_offset = inline_offset + fragment.inline_offset();
|
||||
CSSPixels new_fragment_block_offset = 0;
|
||||
|
||||
auto block_offset_value_for_alignment = [&](CSS::VerticalAlign vertical_align) {
|
||||
|
|
|
@ -7,10 +7,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <AK/NonnullRefPtr.h>
|
||||
#include <AK/TypeCasts.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibGC/Root.h>
|
||||
#include <LibGfx/Rect.h>
|
||||
#include <LibJS/Heap/Cell.h>
|
||||
#include <LibWeb/CSS/ComputedValues.h>
|
||||
#include <LibWeb/CSS/StyleComputer.h>
|
||||
|
|
|
@ -29,9 +29,12 @@ static RefPtr<DisplayList> compute_text_clip_paths(PaintContext& context, Painta
|
|||
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()) };
|
||||
auto scale = context.device_pixels_per_css_pixel();
|
||||
display_list_recorder.draw_text_run(baseline_start.to_type<int>(), *glyph_run, Gfx::Color::Black, fragment_absolute_device_rect.to_type<int>(), scale, fragment.orientation());
|
||||
auto baseline_start = Gfx::FloatPoint {
|
||||
fragment_absolute_rect.x().to_float(),
|
||||
fragment_absolute_rect.y().to_float() + fragment.baseline().to_float(),
|
||||
} * scale;
|
||||
display_list_recorder.draw_text_run(baseline_start, *glyph_run, Gfx::Color::Black, fragment_absolute_device_rect.to_type<int>(), scale, fragment.orientation());
|
||||
};
|
||||
|
||||
paintable.for_each_in_inclusive_subtree([&](auto& paintable) {
|
||||
|
|
|
@ -164,12 +164,12 @@ struct PaintTextShadow {
|
|||
double glyph_run_scale { 1 };
|
||||
Gfx::IntRect shadow_bounding_rect;
|
||||
Gfx::IntRect text_rect;
|
||||
Gfx::IntPoint draw_location;
|
||||
Gfx::FloatPoint draw_location;
|
||||
int blur_radius;
|
||||
Color color;
|
||||
|
||||
[[nodiscard]] Gfx::IntRect bounding_rect() const { return { draw_location, shadow_bounding_rect.size() }; }
|
||||
void translate_by(Gfx::IntPoint const& offset) { draw_location.translate_by(offset); }
|
||||
[[nodiscard]] Gfx::IntRect bounding_rect() const { return { draw_location.to_type<int>(), shadow_bounding_rect.size() }; }
|
||||
void translate_by(Gfx::IntPoint const& offset) { draw_location.translate_by(offset.to_type<float>()); }
|
||||
};
|
||||
|
||||
struct FillRectWithRoundedCorners {
|
||||
|
|
|
@ -504,7 +504,7 @@ void DisplayListPlayerSkia::paint_text_shadow(PaintTextShadow const& command)
|
|||
.glyph_run = command.glyph_run,
|
||||
.scale = command.glyph_run_scale,
|
||||
.rect = command.text_rect,
|
||||
.translation = command.draw_location.to_type<float>() + command.text_rect.location().to_type<float>(),
|
||||
.translation = command.draw_location + command.text_rect.location().to_type<float>(),
|
||||
.color = command.color,
|
||||
});
|
||||
canvas.restore();
|
||||
|
|
|
@ -244,10 +244,10 @@ void DisplayListRecorder::draw_text(Gfx::IntRect const& rect, String raw_text, G
|
|||
}
|
||||
auto metrics = font.pixel_metrics();
|
||||
float baseline_y = static_cast<float>(rect.y()) + metrics.ascent + (static_cast<float>(rect.height()) - (metrics.ascent + metrics.descent)) / 2.0f;
|
||||
draw_text_run(Gfx::IntPoint(roundf(baseline_x), roundf(baseline_y)), *glyph_run, color, rect, 1.0, Orientation::Horizontal);
|
||||
draw_text_run({ baseline_x, baseline_y }, *glyph_run, color, rect, 1.0, Orientation::Horizontal);
|
||||
}
|
||||
|
||||
void DisplayListRecorder::draw_text_run(Gfx::IntPoint baseline_start, Gfx::GlyphRun const& glyph_run, Color color, Gfx::IntRect const& rect, double scale, Orientation orientation)
|
||||
void DisplayListRecorder::draw_text_run(Gfx::FloatPoint baseline_start, Gfx::GlyphRun const& glyph_run, Color color, Gfx::IntRect const& rect, double scale, Orientation orientation)
|
||||
{
|
||||
if (rect.is_empty())
|
||||
return;
|
||||
|
@ -255,7 +255,7 @@ void DisplayListRecorder::draw_text_run(Gfx::IntPoint baseline_start, Gfx::Glyph
|
|||
.glyph_run = glyph_run,
|
||||
.scale = scale,
|
||||
.rect = rect,
|
||||
.translation = baseline_start.to_type<float>(),
|
||||
.translation = baseline_start,
|
||||
.color = color,
|
||||
.orientation = orientation,
|
||||
});
|
||||
|
@ -331,7 +331,7 @@ void DisplayListRecorder::paint_inner_box_shadow_params(PaintBoxShadowParams par
|
|||
append(PaintInnerBoxShadow { .box_shadow_params = params });
|
||||
}
|
||||
|
||||
void DisplayListRecorder::paint_text_shadow(int blur_radius, Gfx::IntRect bounding_rect, Gfx::IntRect text_rect, Gfx::GlyphRun const& glyph_run, double glyph_run_scale, Color color, Gfx::IntPoint draw_location)
|
||||
void DisplayListRecorder::paint_text_shadow(int blur_radius, Gfx::IntRect bounding_rect, Gfx::IntRect text_rect, Gfx::GlyphRun const& glyph_run, double glyph_run_scale, Color color, Gfx::FloatPoint draw_location)
|
||||
{
|
||||
append(PaintTextShadow {
|
||||
.glyph_run = glyph_run,
|
||||
|
|
|
@ -8,8 +8,6 @@
|
|||
|
||||
#include <AK/Forward.h>
|
||||
#include <AK/NonnullRefPtr.h>
|
||||
#include <AK/SegmentedVector.h>
|
||||
#include <AK/Utf8View.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibGfx/Color.h>
|
||||
#include <LibGfx/Forward.h>
|
||||
|
@ -106,7 +104,7 @@ public:
|
|||
void draw_text(Gfx::IntRect const&, String, Gfx::Font const&, Gfx::TextAlignment, Color);
|
||||
|
||||
// Streamlined text drawing routine that does no wrapping/elision/alignment.
|
||||
void draw_text_run(Gfx::IntPoint baseline_start, Gfx::GlyphRun const& glyph_run, Color color, Gfx::IntRect const& rect, double scale, Gfx::Orientation);
|
||||
void draw_text_run(Gfx::FloatPoint baseline_start, Gfx::GlyphRun const& glyph_run, Color color, Gfx::IntRect const& rect, double scale, Gfx::Orientation);
|
||||
|
||||
void add_clip_rect(Gfx::IntRect const& rect);
|
||||
|
||||
|
@ -137,7 +135,7 @@ public:
|
|||
|
||||
void paint_outer_box_shadow_params(PaintBoxShadowParams params);
|
||||
void paint_inner_box_shadow_params(PaintBoxShadowParams params);
|
||||
void paint_text_shadow(int blur_radius, Gfx::IntRect bounding_rect, Gfx::IntRect text_rect, Gfx::GlyphRun const&, double glyph_run_scale, Color color, Gfx::IntPoint draw_location);
|
||||
void paint_text_shadow(int blur_radius, Gfx::IntRect bounding_rect, Gfx::IntRect text_rect, Gfx::GlyphRun const&, double glyph_run_scale, Color color, Gfx::FloatPoint draw_location);
|
||||
|
||||
void fill_rect_with_rounded_corners(Gfx::IntRect const& rect, Color color, CornerRadius top_left_radius, CornerRadius top_right_radius, CornerRadius bottom_right_radius, CornerRadius bottom_left_radius);
|
||||
void fill_rect_with_rounded_corners(Gfx::IntRect const& a_rect, Color color, int radius);
|
||||
|
|
|
@ -683,16 +683,19 @@ void paint_text_fragment(PaintContext& context, TextPaintable const& paintable,
|
|||
if (!glyph_run)
|
||||
return;
|
||||
|
||||
DevicePixelPoint baseline_start { fragment_absolute_device_rect.x(), fragment_absolute_device_rect.y() + context.rounded_device_pixels(fragment.baseline()) };
|
||||
auto scale = context.device_pixels_per_css_pixel();
|
||||
painter.draw_text_run(baseline_start.to_type<int>(), *glyph_run, paintable.computed_values().webkit_text_fill_color(), fragment_absolute_device_rect.to_type<int>(), scale, fragment.orientation());
|
||||
auto baseline_start = Gfx::FloatPoint {
|
||||
fragment_absolute_rect.x().to_float(),
|
||||
fragment_absolute_rect.y().to_float() + fragment.baseline().to_float(),
|
||||
} * scale;
|
||||
painter.draw_text_run(baseline_start, *glyph_run, paintable.computed_values().webkit_text_fill_color(), fragment_absolute_device_rect.to_type<int>(), scale, fragment.orientation());
|
||||
|
||||
auto selection_rect = context.enclosing_device_rect(fragment.selection_rect()).to_type<int>();
|
||||
if (!selection_rect.is_empty()) {
|
||||
painter.fill_rect(selection_rect, CSS::SystemColor::highlight());
|
||||
DisplayListRecorderStateSaver saver(painter);
|
||||
painter.add_clip_rect(selection_rect);
|
||||
painter.draw_text_run(baseline_start.to_type<int>(), *glyph_run, CSS::SystemColor::highlight_text(), fragment_absolute_device_rect.to_type<int>(), scale, fragment.orientation());
|
||||
painter.draw_text_run(baseline_start, *glyph_run, CSS::SystemColor::highlight_text(), fragment_absolute_device_rect.to_type<int>(), scale, fragment.orientation());
|
||||
}
|
||||
|
||||
paint_text_decoration(context, paintable, fragment);
|
||||
|
@ -744,9 +747,8 @@ void PaintableWithLines::paint(PaintContext& context, PaintPhase phase) const
|
|||
// So, we paint the shadows before painting any text.
|
||||
// FIXME: Find a smarter way to do this?
|
||||
if (phase == PaintPhase::Foreground) {
|
||||
for (auto& fragment : fragments()) {
|
||||
for (auto& fragment : fragments())
|
||||
paint_text_shadow(context, fragment, fragment.shadows());
|
||||
}
|
||||
}
|
||||
|
||||
for (auto const& fragment : m_fragments) {
|
||||
|
|
|
@ -74,13 +74,10 @@ void paint_text_shadow(PaintContext& context, PaintableFragment const& fragment,
|
|||
|
||||
auto fragment_width = context.enclosing_device_pixels(fragment.width()).value();
|
||||
auto fragment_height = context.enclosing_device_pixels(fragment.height()).value();
|
||||
auto draw_rect = context.enclosing_device_rect(fragment.absolute_rect()).to_type<int>();
|
||||
auto fragment_baseline = context.rounded_device_pixels(fragment.baseline()).value();
|
||||
|
||||
// Note: Box-shadow layers are ordered front-to-back, so we paint them in reverse
|
||||
for (auto& layer : shadow_layers.in_reverse()) {
|
||||
int offset_x = context.rounded_device_pixels(layer.offset_x).value();
|
||||
int offset_y = context.rounded_device_pixels(layer.offset_y).value();
|
||||
int blur_radius = context.rounded_device_pixels(layer.blur_radius).value();
|
||||
|
||||
// Space around the painted text to allow it to blur.
|
||||
|
@ -95,12 +92,14 @@ void paint_text_shadow(PaintContext& context, PaintableFragment const& fragment,
|
|||
text_rect.width() + margin + margin,
|
||||
text_rect.height() + margin + margin
|
||||
};
|
||||
Gfx::IntPoint draw_location {
|
||||
draw_rect.x() + offset_x - margin,
|
||||
draw_rect.y() + offset_y - margin
|
||||
};
|
||||
|
||||
context.display_list_recorder().paint_text_shadow(blur_radius, bounding_rect, text_rect.translated(0, fragment_baseline), *glyph_run, context.device_pixels_per_css_pixel(), layer.color, draw_location);
|
||||
auto scale = context.device_pixels_per_css_pixel();
|
||||
auto draw_location = Gfx::FloatPoint {
|
||||
fragment.absolute_rect().x() + layer.offset_x - margin,
|
||||
fragment.absolute_rect().y() + layer.offset_y - margin,
|
||||
} * 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue