mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-09-21 16:58:58 +00:00
LibWeb: Support text-underline-position: under
This commit is contained in:
parent
b0e3af7d10
commit
34b8947ca0
Notes:
github-actions[bot]
2025-09-15 14:25:23 +00:00
Author: https://github.com/Calme1709
Commit: 34b8947ca0
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/6190
Reviewed-by: https://github.com/AtkinsSJ ✅
7 changed files with 85 additions and 3 deletions
|
@ -35,6 +35,7 @@
|
|||
#include <LibWeb/CSS/StyleValues/ShadowStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/StringStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/StyleValueList.h>
|
||||
#include <LibWeb/CSS/StyleValues/TextUnderlinePositionStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/TransformationStyleValue.h>
|
||||
#include <LibWeb/Layout/BlockContainer.h>
|
||||
#include <LibWeb/Layout/Node.h>
|
||||
|
@ -884,6 +885,16 @@ CSSPixels ComputedProperties::text_underline_offset() const
|
|||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
TextUnderlinePosition ComputedProperties::text_underline_position() const
|
||||
{
|
||||
auto const& computed_text_underline_position = property(PropertyID::TextUnderlinePosition).as_text_underline_position();
|
||||
|
||||
return {
|
||||
.horizontal = computed_text_underline_position.horizontal(),
|
||||
.vertical = computed_text_underline_position.vertical()
|
||||
};
|
||||
}
|
||||
|
||||
PointerEvents ComputedProperties::pointer_events() const
|
||||
{
|
||||
auto const& value = property(PropertyID::PointerEvents);
|
||||
|
|
|
@ -92,6 +92,7 @@ public:
|
|||
TextOverflow text_overflow() const;
|
||||
TextRendering text_rendering() const;
|
||||
CSSPixels text_underline_offset() const;
|
||||
TextUnderlinePosition text_underline_position() const;
|
||||
Length border_spacing_horizontal(Layout::Node const&) const;
|
||||
Length border_spacing_vertical(Layout::Node const&) const;
|
||||
CaptionSide caption_side() const;
|
||||
|
|
|
@ -87,6 +87,11 @@ struct ScrollbarColorData {
|
|||
Color track_color { Color::Transparent };
|
||||
};
|
||||
|
||||
struct TextUnderlinePosition {
|
||||
TextUnderlinePositionHorizontal horizontal { TextUnderlinePositionHorizontal::Auto };
|
||||
TextUnderlinePositionVertical vertical { TextUnderlinePositionVertical::Auto };
|
||||
};
|
||||
|
||||
struct WillChange {
|
||||
enum class Type : u8 {
|
||||
Contents,
|
||||
|
@ -153,6 +158,7 @@ public:
|
|||
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 TextUnderlinePosition text_underline_position() { return { .horizontal = TextUnderlinePositionHorizontal::Auto, .vertical = TextUnderlinePositionVertical::Auto }; }
|
||||
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; }
|
||||
|
@ -483,6 +489,7 @@ public:
|
|||
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; }
|
||||
TextUnderlinePosition text_underline_position() const { return m_inherited.text_underline_position; }
|
||||
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; }
|
||||
|
@ -701,6 +708,7 @@ protected:
|
|||
CSS::TextWrapMode text_wrap_mode { InitialValues::text_wrap_mode() };
|
||||
CSS::TextRendering text_rendering { InitialValues::text_rendering() };
|
||||
CSSPixels text_underline_offset { InitialValues::text_underline_offset() };
|
||||
TextUnderlinePosition text_underline_position { InitialValues::text_underline_position() };
|
||||
CSS::WhiteSpaceCollapse white_space_collapse { InitialValues::white_space_collapse() };
|
||||
CSS::WordBreak word_break { InitialValues::word_break() };
|
||||
CSSPixels word_spacing { InitialValues::word_spacing() };
|
||||
|
@ -919,6 +927,7 @@ public:
|
|||
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_text_underline_position(TextUnderlinePosition value) { m_inherited.text_underline_position = 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; }
|
||||
|
|
|
@ -609,6 +609,7 @@ void NodeWithStyle::apply_style(CSS::ComputedProperties const& computed_style)
|
|||
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());
|
||||
computed_values.set_text_underline_position(computed_style.text_underline_position());
|
||||
|
||||
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());
|
||||
|
|
|
@ -815,6 +815,7 @@ void paint_text_decoration(DisplayListRecordingContext& context, TextPaintable c
|
|||
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();
|
||||
auto text_underline_position = paintable.computed_values().text_underline_position();
|
||||
for (auto line : text_decoration_lines) {
|
||||
DevicePixelPoint line_start_point {};
|
||||
DevicePixelPoint line_end_point {};
|
||||
|
@ -850,10 +851,39 @@ void paint_text_decoration(DisplayListRecordingContext& context, TextPaintable c
|
|||
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 + text_underline_offset));
|
||||
line_end_point = context.rounded_device_point(fragment_box.top_right().translated(0, baseline + text_underline_offset));
|
||||
case CSS::TextDecorationLine::Underline: {
|
||||
// https://drafts.csswg.org/css-text-decor-4/#text-underline-position-property
|
||||
auto underline_position_without_offset = [&]() {
|
||||
// FIXME: Support text-decoration: underline on vertical text
|
||||
switch (text_underline_position.horizontal) {
|
||||
case Web::CSS::TextUnderlinePositionHorizontal::Auto:
|
||||
// The user agent may use any algorithm to determine the underline’s position; however it must be
|
||||
// placed at or under the alphabetic baseline.
|
||||
|
||||
// Spec Note: It is suggested that the default underline position be close to the alphabetic
|
||||
// baseline,
|
||||
// FIXME: unless that would either cross subscripted (or otherwise lowered) text or draw over
|
||||
// glyphs from Asian scripts such as Han or Tibetan for which an alphabetic underline is
|
||||
// too high: in such cases, shifting the underline lower or aligning to the em box edge
|
||||
// as described for under may be more appropriate.
|
||||
return fragment.baseline();
|
||||
case Web::CSS::TextUnderlinePositionHorizontal::FromFont:
|
||||
// FIXME: If the first available font has metrics indicating a preferred underline offset, use that
|
||||
// offset, otherwise behaves as auto.
|
||||
return fragment.baseline();
|
||||
case Web::CSS::TextUnderlinePositionHorizontal::Under:
|
||||
// The underline is positioned under the element’s text content. In this case the underline usually
|
||||
// does not cross the descenders. (This is sometimes called “accounting” underline.)
|
||||
return fragment.baseline() + CSSPixels { font.pixel_metrics().descent };
|
||||
}
|
||||
|
||||
VERIFY_NOT_REACHED();
|
||||
}();
|
||||
|
||||
line_start_point = context.rounded_device_point(fragment_box.top_left().translated(0, underline_position_without_offset + text_underline_offset));
|
||||
line_end_point = context.rounded_device_point(fragment_box.top_right().translated(0, underline_position_without_offset + text_underline_offset));
|
||||
break;
|
||||
}
|
||||
case CSS::TextDecorationLine::Overline:
|
||||
line_start_point = context.rounded_device_point(fragment_box.top_left().translated(0, baseline - glyph_height));
|
||||
line_end_point = context.rounded_device_point(fragment_box.top_right().translated(0, baseline - glyph_height));
|
||||
|
|
12
Tests/LibWeb/Text/expected/css/text-underline-position.txt
Normal file
12
Tests/LibWeb/Text/expected/css/text-underline-position.txt
Normal file
|
@ -0,0 +1,12 @@
|
|||
SaveLayer
|
||||
PushStackingContext opacity=1 isolate=false has_clip_path=false transform=[1 0 0 1 0 0]
|
||||
PushStackingContext opacity=1 isolate=false has_clip_path=false transform=[1 0 0 1 0 0]
|
||||
DrawGlyphRun rect=[35,8 8x18] translation=[35.15625,21.796875] color=rgb(0, 0, 0) scale=1
|
||||
DrawGlyphRun rect=[8,8 28x18] translation=[8,21.796875] color=rgb(0, 0, 0) scale=1
|
||||
DrawLine from=[8,24] to=[35,24] color=rgb(0, 0, 0) thickness=2
|
||||
DrawGlyphRun rect=[43,8 28x18] translation=[43.15625,21.796875] color=rgb(0, 0, 0) scale=1
|
||||
DrawLine from=[43,27] to=[71,27] color=rgb(0, 0, 0) thickness=2
|
||||
PopStackingContext
|
||||
PopStackingContext
|
||||
Restore
|
||||
|
18
Tests/LibWeb/Text/input/css/text-underline-position.html
Normal file
18
Tests/LibWeb/Text/input/css/text-underline-position.html
Normal file
|
@ -0,0 +1,18 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<style>
|
||||
span {
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
<div>
|
||||
<span style="text-underline-position: auto">foo</span>
|
||||
<span style="text-underline-position: under">bar</span>
|
||||
</div>
|
||||
<script src="../include.js"></script>
|
||||
<script>
|
||||
test(() => {
|
||||
println(internals.dumpDisplayList());
|
||||
});
|
||||
</script>
|
||||
</html>
|
Loading…
Add table
Add a link
Reference in a new issue