LibWeb: Implement text-decoration: spelling-error and grammar-error

This commit is contained in:
Sam Atkins 2025-02-27 19:51:36 +00:00
parent 7668f91b60
commit 532c01c388
Notes: github-actions[bot] 2025-02-28 16:35:04 +00:00
9 changed files with 74 additions and 13 deletions

View file

@ -1008,8 +1008,11 @@ Vector<TextDecorationLine> ComputedProperties::text_decoration_line() const
return lines;
}
if (value.is_keyword() && value.to_keyword() == Keyword::None)
return {};
if (value.is_keyword()) {
if (value.to_keyword() == Keyword::None)
return {};
return { keyword_to_text_decoration_line(value.to_keyword()).release_value() };
}
dbgln("FIXME: Unsupported value for text-decoration-line: {}", value.to_string(CSSStyleValue::SerializationMode::Normal));
return {};

View file

@ -545,7 +545,9 @@
"underline",
"overline",
"line-through",
"blink"
"blink",
"spelling-error",
"grammar-error"
],
"text-decoration-style": [
"dashed",

View file

@ -190,6 +190,7 @@
"fullscreen",
"grab",
"grabbing",
"grammar-error",
"graytext",
"grid",
"groove",
@ -404,6 +405,7 @@
"space-around",
"space-between",
"space-evenly",
"spelling-error",
"square",
"square-button",
"srgb",

View file

@ -3510,6 +3510,8 @@ RefPtr<CSSStyleValue> Parser::parse_text_decoration_line_value(TokenStream<Compo
{
StyleValueVector style_values;
bool includes_spelling_or_grammar_error_value = false;
while (tokens.has_next_token()) {
auto maybe_value = parse_css_value_for_property(PropertyID::TextDecorationLine, tokens);
if (!maybe_value)
@ -3522,6 +3524,9 @@ RefPtr<CSSStyleValue> Parser::parse_text_decoration_line_value(TokenStream<Compo
break;
return value;
}
if (first_is_one_of(*maybe_line, TextDecorationLine::SpellingError, TextDecorationLine::GrammarError)) {
includes_spelling_or_grammar_error_value = true;
}
if (style_values.contains_slow(value))
break;
style_values.append(move(value));
@ -3534,6 +3539,13 @@ RefPtr<CSSStyleValue> Parser::parse_text_decoration_line_value(TokenStream<Compo
if (style_values.is_empty())
return nullptr;
// These can only appear on their own.
if (style_values.size() > 1 && includes_spelling_or_grammar_error_value)
return nullptr;
if (style_values.size() == 1)
return *style_values.first();
quick_sort(style_values, [](auto& left, auto& right) {
return *keyword_to_text_decoration_line(left->to_keyword()) < *keyword_to_text_decoration_line(right->to_keyword());
});

View file

@ -682,6 +682,7 @@ void paint_text_decoration(PaintContext& context, TextPaintable const& paintable
auto baseline = fragment.baseline();
auto line_color = paintable.computed_values().text_decoration_color();
auto line_style = paintable.computed_values().text_decoration_style();
auto const& text_paintable = static_cast<TextPaintable const&>(fragment.paintable());
auto device_line_thickness = context.rounded_device_pixels(text_paintable.text_decoration_thickness());
@ -690,6 +691,24 @@ void paint_text_decoration(PaintContext& context, TextPaintable const& paintable
DevicePixelPoint line_start_point {};
DevicePixelPoint line_end_point {};
if (line == CSS::TextDecorationLine::SpellingError) {
// https://drafts.csswg.org/css-text-decor-4/#valdef-text-decoration-line-spelling-error
// This value indicates the type of text decoration used by the user agent to highlight spelling mistakes.
// Its appearance is UA-defined, and may be platform-dependent. It is often rendered as a red wavy underline.
line_color = Color::Red;
device_line_thickness = context.rounded_device_pixels(1);
line_style = CSS::TextDecorationStyle::Wavy;
line = CSS::TextDecorationLine::Underline;
} 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.
// Its appearance is UA defined, and may be platform-dependent. It is often rendered as a green wavy underline.
line_color = Color::DarkGreen;
device_line_thickness = context.rounded_device_pixels(1);
line_style = CSS::TextDecorationStyle::Wavy;
line = CSS::TextDecorationLine::Underline;
}
switch (line) {
case CSS::TextDecorationLine::None:
return;
@ -710,9 +729,13 @@ void paint_text_decoration(PaintContext& context, TextPaintable const& paintable
case CSS::TextDecorationLine::Blink:
// Conforming user agents may simply not blink the text
return;
case CSS::TextDecorationLine::SpellingError:
case CSS::TextDecorationLine::GrammarError:
// Handled above.
VERIFY_NOT_REACHED();
}
switch (paintable.computed_values().text_decoration_style()) {
switch (line_style) {
case CSS::TextDecorationStyle::Solid:
painter.draw_line(line_start_point.to_type<int>(), line_end_point.to_type<int>(), line_color, device_line_thickness.value(), Gfx::LineStyle::Solid);
break;
@ -742,7 +765,24 @@ void paint_text_decoration(PaintContext& context, TextPaintable const& paintable
painter.draw_line(line_start_point.to_type<int>(), line_end_point.to_type<int>(), line_color, device_line_thickness.value(), Gfx::LineStyle::Dotted);
break;
case CSS::TextDecorationStyle::Wavy:
painter.draw_triangle_wave(line_start_point.to_type<int>(), line_end_point.to_type<int>(), line_color, device_line_thickness.value() + 1, device_line_thickness.value());
auto amplitude = device_line_thickness.value() * 3;
switch (line) {
case CSS::TextDecorationLine::Underline:
line_start_point.translate_by(0, device_line_thickness + context.rounded_device_pixels(1));
line_end_point.translate_by(0, device_line_thickness + context.rounded_device_pixels(1));
break;
case CSS::TextDecorationLine::Overline:
line_start_point.translate_by(0, -device_line_thickness - context.rounded_device_pixels(1));
line_end_point.translate_by(0, -device_line_thickness - context.rounded_device_pixels(1));
break;
case CSS::TextDecorationLine::LineThrough:
line_start_point.translate_by(0, -device_line_thickness / 2);
line_end_point.translate_by(0, -device_line_thickness / 2);
break;
default:
VERIFY_NOT_REACHED();
}
painter.draw_triangle_wave(line_start_point.to_type<int>(), line_end_point.to_type<int>(), line_color, amplitude, device_line_thickness.value());
break;
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View file

@ -8,6 +8,8 @@
.strikethrough { text-decoration: line-through dotted green 5px; }
.current-color { color: #8B4513; text-decoration: underline; }
.overboard { text-decoration: double overline underline line-through magenta; }
.spelling-error { text-decoration: spelling-error; }
.grammar-error { text-decoration: grammar-error; }
</style>
</head>
<body>
@ -17,5 +19,7 @@
<p class="blink">FREE!</p>
<p class="current-color">This underline should match the text color</p>
<p class="overboard">This should have an underline, overline and line-through, all in glorious magenta.</p>
<p class="spelling-error">This should look like a spelling error.</p>
<p class="grammar-error">This should look like a grammar error.</p>
</body>
</html>

View file

@ -2,8 +2,7 @@ Harness status: OK
Found 18 tests
16 Pass
2 Fail
18 Pass
Pass Property text-decoration-line value 'none'
Pass Property text-decoration-line value 'underline'
Pass Property text-decoration-line value 'overline'
@ -20,5 +19,5 @@ Pass Property text-decoration-line value 'underline overline blink'
Pass Property text-decoration-line value 'underline line-through blink'
Pass Property text-decoration-line value 'overline line-through blink'
Pass Property text-decoration-line value 'underline overline line-through blink'
Fail Property text-decoration-line value 'spelling-error'
Fail Property text-decoration-line value 'grammar-error'
Pass Property text-decoration-line value 'spelling-error'
Pass Property text-decoration-line value 'grammar-error'

View file

@ -2,8 +2,7 @@ Harness status: OK
Found 67 tests
65 Pass
2 Fail
67 Pass
Pass e.style['text-decoration-line'] = "none" should set the property value
Pass e.style['text-decoration-line'] = "underline" should set the property value
Pass e.style['text-decoration-line'] = "overline" should set the property value
@ -69,5 +68,5 @@ Pass e.style['text-decoration-line'] = "blink overline underline line-through" s
Pass e.style['text-decoration-line'] = "blink overline line-through underline" should set the property value
Pass e.style['text-decoration-line'] = "blink line-through underline overline" should set the property value
Pass e.style['text-decoration-line'] = "blink line-through overline underline" should set the property value
Fail e.style['text-decoration-line'] = "spelling-error" should set the property value
Fail e.style['text-decoration-line'] = "grammar-error" should set the property value
Pass e.style['text-decoration-line'] = "spelling-error" should set the property value
Pass e.style['text-decoration-line'] = "grammar-error" should set the property value