mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-09-21 08:48:57 +00:00
LibWeb: Respect text-underline-offset
when rendering underlines
This commit is contained in:
parent
9aa2d1bd3e
commit
815e77c04d
Notes:
github-actions[bot]
2025-09-12 06:08:11 +00:00
Author: https://github.com/Calme1709
Commit: 815e77c04d
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/6166
Reviewed-by: https://github.com/tcl3 ✅
11 changed files with 226 additions and 2 deletions
|
@ -859,6 +859,31 @@ TextRendering ComputedProperties::text_rendering() const
|
|||
return keyword_to_text_rendering(value.to_keyword()).release_value();
|
||||
}
|
||||
|
||||
CSSPixels ComputedProperties::text_underline_offset() const
|
||||
{
|
||||
auto const& computed_text_underline_offset = property(PropertyID::TextUnderlineOffset);
|
||||
|
||||
// auto
|
||||
if (computed_text_underline_offset.to_keyword() == Keyword::Auto)
|
||||
return InitialValues::text_underline_offset();
|
||||
|
||||
// <length>
|
||||
if (computed_text_underline_offset.is_length())
|
||||
return computed_text_underline_offset.as_length().length().absolute_length_to_px();
|
||||
|
||||
// <percentage>
|
||||
if (computed_text_underline_offset.is_percentage())
|
||||
return font_size().scaled(computed_text_underline_offset.as_percentage().percentage().as_fraction());
|
||||
|
||||
// NOTE: We also support calc()'d <length-percentage>
|
||||
if (computed_text_underline_offset.is_calculated())
|
||||
// NOTE: We don't need to pass a length resolution context here as lengths have already been absolutized in
|
||||
// StyleComputer::compute_text_underline_offset
|
||||
return computed_text_underline_offset.as_calculated().resolve_length({ .percentage_basis = Length::make_px(font_size()), .length_resolution_context = {} })->absolute_length_to_px();
|
||||
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
PointerEvents ComputedProperties::pointer_events() const
|
||||
{
|
||||
auto const& value = property(PropertyID::PointerEvents);
|
||||
|
|
|
@ -91,6 +91,7 @@ public:
|
|||
TextJustify text_justify() const;
|
||||
TextOverflow text_overflow() const;
|
||||
TextRendering text_rendering() const;
|
||||
CSSPixels text_underline_offset() const;
|
||||
Length border_spacing_horizontal(Layout::Node const&) const;
|
||||
Length border_spacing_vertical(Layout::Node const&) const;
|
||||
CaptionSide caption_side() const;
|
||||
|
|
|
@ -152,6 +152,7 @@ public:
|
|||
static CSS::LengthPercentage text_indent() { return CSS::Length::make_px(0); }
|
||||
static CSS::TextWrapMode text_wrap_mode() { return CSS::TextWrapMode::Wrap; }
|
||||
static CSS::TextRendering text_rendering() { return CSS::TextRendering::Auto; }
|
||||
static CSSPixels text_underline_offset() { return 2; }
|
||||
static CSS::Display display() { return CSS::Display { CSS::DisplayOutside::Inline, CSS::DisplayInside::Flow }; }
|
||||
static Color color() { return Color::Black; }
|
||||
static Color stop_color() { return Color::Black; }
|
||||
|
@ -481,6 +482,7 @@ public:
|
|||
CSS::LengthPercentage const& text_indent() const { return m_inherited.text_indent; }
|
||||
CSS::TextWrapMode text_wrap_mode() const { return m_inherited.text_wrap_mode; }
|
||||
CSS::TextRendering text_rendering() const { return m_inherited.text_rendering; }
|
||||
CSSPixels text_underline_offset() const { return m_inherited.text_underline_offset; }
|
||||
Vector<CSS::TextDecorationLine> const& text_decoration_line() const { return m_noninherited.text_decoration_line; }
|
||||
TextDecorationThickness const& text_decoration_thickness() const { return m_noninherited.text_decoration_thickness; }
|
||||
CSS::TextDecorationStyle text_decoration_style() const { return m_noninherited.text_decoration_style; }
|
||||
|
@ -698,6 +700,7 @@ protected:
|
|||
CSS::LengthPercentage text_indent { InitialValues::text_indent() };
|
||||
CSS::TextWrapMode text_wrap_mode { InitialValues::text_wrap_mode() };
|
||||
CSS::TextRendering text_rendering { InitialValues::text_rendering() };
|
||||
CSSPixels text_underline_offset { InitialValues::text_underline_offset() };
|
||||
CSS::WhiteSpaceCollapse white_space_collapse { InitialValues::white_space_collapse() };
|
||||
CSS::WordBreak word_break { InitialValues::word_break() };
|
||||
CSSPixels word_spacing { InitialValues::word_spacing() };
|
||||
|
@ -915,6 +918,7 @@ public:
|
|||
void set_text_wrap_mode(CSS::TextWrapMode value) { m_inherited.text_wrap_mode = value; }
|
||||
void set_text_overflow(CSS::TextOverflow value) { m_noninherited.text_overflow = value; }
|
||||
void set_text_rendering(CSS::TextRendering value) { m_inherited.text_rendering = value; }
|
||||
void set_text_underline_offset(CSSPixels value) { m_inherited.text_underline_offset = value; }
|
||||
void set_webkit_text_fill_color(Color value) { m_inherited.webkit_text_fill_color = value; }
|
||||
void set_position(CSS::Positioning position) { m_noninherited.position = position; }
|
||||
void set_white_space_collapse(CSS::WhiteSpaceCollapse value) { m_inherited.white_space_collapse = value; }
|
||||
|
|
|
@ -608,6 +608,7 @@ void NodeWithStyle::apply_style(CSS::ComputedProperties const& computed_style)
|
|||
computed_values.set_text_justify(computed_style.text_justify());
|
||||
computed_values.set_text_overflow(computed_style.text_overflow());
|
||||
computed_values.set_text_rendering(computed_style.text_rendering());
|
||||
computed_values.set_text_underline_offset(computed_style.text_underline_offset());
|
||||
|
||||
if (auto text_indent = computed_style.length_percentage(CSS::PropertyID::TextIndent, *this, CSS::ComputedProperties::ClampNegativeLengths::No); text_indent.has_value())
|
||||
computed_values.set_text_indent(text_indent.release_value());
|
||||
|
|
|
@ -814,6 +814,7 @@ void paint_text_decoration(DisplayListRecordingContext& context, TextPaintable c
|
|||
auto line_style = paintable.computed_values().text_decoration_style();
|
||||
auto device_line_thickness = context.rounded_device_pixels(fragment.text_decoration_thickness());
|
||||
auto text_decoration_lines = paintable.computed_values().text_decoration_line();
|
||||
auto text_underline_offset = paintable.computed_values().text_underline_offset();
|
||||
for (auto line : text_decoration_lines) {
|
||||
DevicePixelPoint line_start_point {};
|
||||
DevicePixelPoint line_end_point {};
|
||||
|
@ -826,6 +827,11 @@ void paint_text_decoration(DisplayListRecordingContext& context, TextPaintable c
|
|||
device_line_thickness = context.rounded_device_pixels(1);
|
||||
line_style = CSS::TextDecorationStyle::Wavy;
|
||||
line = CSS::TextDecorationLine::Underline;
|
||||
|
||||
// https://drafts.csswg.org/css-text-decor-4/#underline-offset
|
||||
// When the value of the text-decoration-line property is either spelling-error or grammar-error, the UA
|
||||
// must ignore the value of text-underline-position.
|
||||
text_underline_offset = CSS::InitialValues::text_underline_offset();
|
||||
} else if (line == CSS::TextDecorationLine::GrammarError) {
|
||||
// https://drafts.csswg.org/css-text-decor-4/#valdef-text-decoration-line-grammar-error
|
||||
// This value indicates the type of text decoration used by the user agent to highlight grammar mistakes.
|
||||
|
@ -834,14 +840,19 @@ void paint_text_decoration(DisplayListRecordingContext& context, TextPaintable c
|
|||
device_line_thickness = context.rounded_device_pixels(1);
|
||||
line_style = CSS::TextDecorationStyle::Wavy;
|
||||
line = CSS::TextDecorationLine::Underline;
|
||||
|
||||
// https://drafts.csswg.org/css-text-decor-4/#underline-offset
|
||||
// When the value of the text-decoration-line property is either spelling-error or grammar-error, the UA
|
||||
// must ignore the value of text-underline-position.
|
||||
text_underline_offset = CSS::InitialValues::text_underline_offset();
|
||||
}
|
||||
|
||||
switch (line) {
|
||||
case CSS::TextDecorationLine::None:
|
||||
return;
|
||||
case CSS::TextDecorationLine::Underline:
|
||||
line_start_point = context.rounded_device_point(fragment_box.top_left().translated(0, baseline + 2));
|
||||
line_end_point = context.rounded_device_point(fragment_box.top_right().translated(0, baseline + 2));
|
||||
line_start_point = context.rounded_device_point(fragment_box.top_left().translated(0, baseline + text_underline_offset));
|
||||
line_end_point = context.rounded_device_point(fragment_box.top_right().translated(0, baseline + text_underline_offset));
|
||||
break;
|
||||
case CSS::TextDecorationLine::Overline:
|
||||
line_start_point = context.rounded_device_point(fragment_box.top_left().translated(0, baseline - glyph_height));
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Non-reference case for text-underline-offset</title>
|
||||
<link rel="stylesheet" type="text/css" href="../../..../../../fonts/ahem.css" />
|
||||
<style>
|
||||
#main {
|
||||
margin: 2em;
|
||||
display:flex
|
||||
}
|
||||
div span {
|
||||
text-decoration: green underline;
|
||||
text-decoration-skip-ink: none;
|
||||
font: 20px/1 Ahem;
|
||||
color: transparent;
|
||||
padding-bottom: 20px;
|
||||
border: 1px dotted transparent;
|
||||
border-bottom-color: cyan;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<p class="instructions">Test passes if the box on the right has a lower underline than the box on the left</p>
|
||||
<div id="main">
|
||||
<div>
|
||||
<p>left<span>XXXX</span></p>
|
||||
</div>
|
||||
<div>
|
||||
<p><span id="rightbox">XXXX</span>right</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,35 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Reference case for text-underline-offset</title>
|
||||
<link rel="stylesheet" type="text/css" href="../../..../../../fonts/ahem.css" />
|
||||
<style>
|
||||
#main{
|
||||
border-bottom: 1px solid cyan;
|
||||
display: flex;
|
||||
}
|
||||
#text, #norm{
|
||||
text-decoration: green underline;
|
||||
text-decoration-skip-ink: none;
|
||||
text-underline-offset: 0px;
|
||||
font: 20px/1 Ahem;
|
||||
color: transparent;
|
||||
position: relative;
|
||||
top: 21px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body >
|
||||
<p class="instructions">Test passes if the lines are at the same level</p>
|
||||
<div id="main">
|
||||
<div>
|
||||
<p>left<span id="text">XXXX</span></p>
|
||||
</div>
|
||||
<div>
|
||||
<p><span id="norm">XXXX</span>right</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,10 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test case for negative values of text-underline-offset</title>
|
||||
</head>
|
||||
<body>
|
||||
<div style="font-size: 48px; text-decoration: underline; text-underline-offset: 0px; text-decoration-skip: none;">Hello</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,42 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>text-underline-offset test case</title>
|
||||
<meta name="assert" content="text-decoration:underline; there is a line at or under the alphabetic baseline">
|
||||
<link rel="author" title="Charlie Marlow" href="mailto:cmarlow@mozilla.com">
|
||||
<link rel="author" title="Mozilla" href="https://www.mozilla.org">
|
||||
<link rel="help" href="https://drafts.csswg.org/css-text-decor-4/#underline-offset">
|
||||
<link rel="mismatch" href="../../../../expected/wpt-import/css/css-text-decor/reference/text-underline-offset-001-notref.html">
|
||||
<link rel="stylesheet" type="text/css" href="../..../../fonts/ahem.css" />
|
||||
<style>
|
||||
#main {
|
||||
margin: 2em;
|
||||
display:flex
|
||||
}
|
||||
div span {
|
||||
text-decoration: green underline;
|
||||
text-decoration-skip-ink: none;
|
||||
font: 20px/1 Ahem;
|
||||
color: transparent;
|
||||
padding-bottom: 20px;
|
||||
border: 1px dotted transparent;
|
||||
border-bottom-color: cyan;
|
||||
}
|
||||
#rightbox {
|
||||
text-underline-offset: 24px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<p class="instructions">Test passes if the box on the right has a lower underline than the box on the left</p>
|
||||
<div id="main">
|
||||
<div>
|
||||
<p>left<span>XXXX</span></p>
|
||||
</div>
|
||||
<div>
|
||||
<p><span id="rightbox">XXXX</span>right</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,46 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test case for text-underline-offset</title>
|
||||
<link rel="author" title="Charlie Marlow" href="mailto:cmarlow@mozilla.com">
|
||||
<link rel="author" title="Mozilla" href="https://www.mozilla.org">
|
||||
<link rel="help" href="https://drafts.csswg.org/css-text-decor-4/#underline-offset">
|
||||
<link rel="match" href="../../../../expected/wpt-import/css/css-text-decor/reference/text-underline-offset-002-ref.html">
|
||||
<link rel="stylesheet" type="text/css" href="../..../../fonts/ahem.css" />
|
||||
<style>
|
||||
#main{
|
||||
border-bottom: 1px solid cyan;
|
||||
display: flex;
|
||||
}
|
||||
#text, #norm{
|
||||
text-decoration-color: green;
|
||||
text-decoration-line: underline;
|
||||
text-decoration-skip-ink: none;
|
||||
font: 20px/1 Ahem;
|
||||
color: transparent;
|
||||
position: relative;
|
||||
margin-right: 10px;
|
||||
}
|
||||
#text{
|
||||
top: 10px;
|
||||
text-underline-offset: 11px;
|
||||
}
|
||||
#norm{
|
||||
top: 21px;
|
||||
text-underline-offset: 0px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body >
|
||||
<p class="instructions">Test passes if the lines are at the same level</p>
|
||||
<div id="main">
|
||||
<div>
|
||||
<p>left<span id="text">XXXX</span></p>
|
||||
</div>
|
||||
<div>
|
||||
<p><span id="norm">XXXX</span>right</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,15 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test case for negative values of text-underline-offset</title>
|
||||
<meta name="assert" content="text-underline-offset: Negative values should not be clamped to zero">
|
||||
<link rel="author" title="Myles C. Maxfield" href="mmaxfield@apple.com">
|
||||
<link rel="author" title="Apple Inc." href="http://www.apple.com/">
|
||||
<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/4059">
|
||||
<link rel="mismatch" href="../../../../expected/wpt-import/css/css-text-decor/reference/text-underline-offset-negative-notref.html">
|
||||
</head>
|
||||
<body>
|
||||
<div style="font-size: 48px; text-decoration: underline; text-underline-offset: -20px; text-decoration-skip: none;">Hello</div>
|
||||
</body>
|
||||
</html>
|
Loading…
Add table
Add a link
Reference in a new issue