mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-12-29 06:02:07 +03:00
LibWeb: Allow multiple text-decoration-lines
The spec grammar for `text-decoration-line` is: `none | [ underline || overline || line-through || blink ]` Which means that it's either `none`, or any combination of the other values. This patch makes that parse for `text-decoration-line` and `text-decoration`, stores the results as a Vector, and adjusts `paint_text_decoration()` to run as a loop over all the values that are provided. As noted, storing a Vector of values is a bit wasteful, as they could be stored as flags in a single `u8`. But I was getting too confused trying to do that in a nice way.
This commit is contained in:
parent
85da8cbb07
commit
7c91fda088
Notes:
sideshowbarker
2024-07-17 11:48:28 +09:00
Author: https://github.com/AtkinsSJ Commit: https://github.com/SerenityOS/serenity/commit/7c91fda088 Pull-request: https://github.com/SerenityOS/serenity/pull/13676
@ -8,6 +8,7 @@
|
||||
.strikethrough { text-decoration: line-through dotted green; }
|
||||
.blink { text-decoration: blink; }
|
||||
.current-color { color: #8B4513; text-decoration: underline; }
|
||||
.overboard { text-decoration: double overline underline line-through magenta; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@ -16,5 +17,6 @@
|
||||
<p class="strikethrough">Wombling</p>
|
||||
<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>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -121,7 +121,7 @@ public:
|
||||
Optional<int> const& z_index() const { return m_noninherited.z_index; }
|
||||
CSS::TextAlign text_align() const { return m_inherited.text_align; }
|
||||
CSS::TextJustify text_justify() const { return m_inherited.text_justify; }
|
||||
CSS::TextDecorationLine text_decoration_line() const { return m_noninherited.text_decoration_line; }
|
||||
Vector<CSS::TextDecorationLine> text_decoration_line() const { return m_noninherited.text_decoration_line; }
|
||||
CSS::LengthPercentage text_decoration_thickness() const { return m_noninherited.text_decoration_thickness; }
|
||||
CSS::TextDecorationStyle text_decoration_style() const { return m_noninherited.text_decoration_style; }
|
||||
Color text_decoration_color() const { return m_noninherited.text_decoration_color; }
|
||||
@ -217,7 +217,8 @@ protected:
|
||||
CSS::Clear clear { InitialValues::clear() };
|
||||
CSS::Display display { InitialValues::display() };
|
||||
Optional<int> z_index;
|
||||
CSS::TextDecorationLine text_decoration_line { InitialValues::text_decoration_line() };
|
||||
// FIXME: Store this as flags in a u8.
|
||||
Vector<CSS::TextDecorationLine> text_decoration_line { InitialValues::text_decoration_line() };
|
||||
CSS::LengthPercentage text_decoration_thickness { InitialValues::text_decoration_thickness() };
|
||||
CSS::TextDecorationStyle text_decoration_style { InitialValues::text_decoration_style() };
|
||||
Color text_decoration_color { InitialValues::color() };
|
||||
@ -282,7 +283,7 @@ public:
|
||||
void set_z_index(Optional<int> value) { m_noninherited.z_index = value; }
|
||||
void set_text_align(CSS::TextAlign text_align) { m_inherited.text_align = text_align; }
|
||||
void set_text_justify(CSS::TextJustify text_justify) { m_inherited.text_justify = text_justify; }
|
||||
void set_text_decoration_line(CSS::TextDecorationLine value) { m_noninherited.text_decoration_line = value; }
|
||||
void set_text_decoration_line(Vector<CSS::TextDecorationLine> value) { m_noninherited.text_decoration_line = move(value); }
|
||||
void set_text_decoration_thickness(CSS::LengthPercentage value) { m_noninherited.text_decoration_thickness = value; }
|
||||
void set_text_decoration_style(CSS::TextDecorationStyle value) { m_noninherited.text_decoration_style = value; }
|
||||
void set_text_decoration_color(Color value) { m_noninherited.text_decoration_color = value; }
|
||||
|
@ -4673,15 +4673,15 @@ RefPtr<StyleValue> Parser::parse_overflow_value(Vector<ComponentValue> const& co
|
||||
|
||||
RefPtr<StyleValue> Parser::parse_text_decoration_value(Vector<ComponentValue> const& component_values)
|
||||
{
|
||||
if (component_values.size() > 4)
|
||||
return nullptr;
|
||||
|
||||
RefPtr<StyleValue> decoration_line;
|
||||
RefPtr<StyleValue> decoration_thickness;
|
||||
RefPtr<StyleValue> decoration_style;
|
||||
RefPtr<StyleValue> decoration_color;
|
||||
|
||||
for (auto& part : component_values) {
|
||||
auto tokens = TokenStream { component_values };
|
||||
|
||||
while (tokens.has_next_token()) {
|
||||
auto& part = tokens.next_token();
|
||||
auto value = parse_css_value(part);
|
||||
if (!value)
|
||||
return nullptr;
|
||||
@ -4695,7 +4695,11 @@ RefPtr<StyleValue> Parser::parse_text_decoration_value(Vector<ComponentValue> co
|
||||
if (property_accepts_value(PropertyID::TextDecorationLine, *value)) {
|
||||
if (decoration_line)
|
||||
return nullptr;
|
||||
decoration_line = value.release_nonnull();
|
||||
tokens.reconsume_current_input_token();
|
||||
auto parsed_decoration_line = parse_text_decoration_line_value(tokens);
|
||||
if (!parsed_decoration_line)
|
||||
return nullptr;
|
||||
decoration_line = parsed_decoration_line.release_nonnull();
|
||||
continue;
|
||||
}
|
||||
if (property_accepts_value(PropertyID::TextDecorationThickness, *value)) {
|
||||
@ -4726,6 +4730,45 @@ RefPtr<StyleValue> Parser::parse_text_decoration_value(Vector<ComponentValue> co
|
||||
return TextDecorationStyleValue::create(decoration_line.release_nonnull(), decoration_thickness.release_nonnull(), decoration_style.release_nonnull(), decoration_color.release_nonnull());
|
||||
}
|
||||
|
||||
RefPtr<StyleValue> Parser::parse_text_decoration_line_value(TokenStream<ComponentValue>& tokens)
|
||||
{
|
||||
NonnullRefPtrVector<StyleValue> style_values;
|
||||
|
||||
while (tokens.has_next_token()) {
|
||||
auto& token = tokens.next_token();
|
||||
auto maybe_value = parse_css_value(token);
|
||||
if (!maybe_value || !property_accepts_value(PropertyID::TextDecorationLine, *maybe_value)) {
|
||||
tokens.reconsume_current_input_token();
|
||||
break;
|
||||
}
|
||||
auto value = maybe_value.release_nonnull();
|
||||
|
||||
if (auto maybe_line = value_id_to_text_decoration_line(value->to_identifier()); maybe_line.has_value()) {
|
||||
auto line = maybe_line.release_value();
|
||||
if (line == TextDecorationLine::None) {
|
||||
if (!style_values.is_empty()) {
|
||||
tokens.reconsume_current_input_token();
|
||||
break;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
if (style_values.contains_slow(value)) {
|
||||
tokens.reconsume_current_input_token();
|
||||
break;
|
||||
}
|
||||
style_values.append(move(value));
|
||||
continue;
|
||||
}
|
||||
|
||||
tokens.reconsume_current_input_token();
|
||||
break;
|
||||
}
|
||||
|
||||
if (style_values.is_empty())
|
||||
return nullptr;
|
||||
return StyleValueList::create(move(style_values), StyleValueList::Separator::Space);
|
||||
}
|
||||
|
||||
static Optional<CSS::TransformFunction> parse_transform_function_name(StringView name)
|
||||
{
|
||||
if (name == "matrix")
|
||||
@ -5076,6 +5119,13 @@ Result<NonnullRefPtr<StyleValue>, Parser::ParsingResult> Parser::parse_css_value
|
||||
if (auto parsed_value = parse_text_decoration_value(component_values))
|
||||
return parsed_value.release_nonnull();
|
||||
return ParsingResult::SyntaxError;
|
||||
case PropertyID::TextDecorationLine: {
|
||||
TokenStream tokens { component_values };
|
||||
auto parsed_value = parse_text_decoration_line_value(tokens);
|
||||
if (parsed_value && !tokens.has_next_token())
|
||||
return parsed_value.release_nonnull();
|
||||
return ParsingResult::SyntaxError;
|
||||
}
|
||||
case PropertyID::TextShadow:
|
||||
if (auto parsed_value = parse_shadow_value(component_values, AllowInsetKeyword::No))
|
||||
return parsed_value.release_nonnull();
|
||||
|
@ -319,6 +319,7 @@ private:
|
||||
RefPtr<StyleValue> parse_shadow_value(Vector<ComponentValue> const&, AllowInsetKeyword);
|
||||
RefPtr<StyleValue> parse_single_shadow_value(TokenStream<ComponentValue>&, AllowInsetKeyword);
|
||||
RefPtr<StyleValue> parse_text_decoration_value(Vector<ComponentValue> const&);
|
||||
RefPtr<StyleValue> parse_text_decoration_line_value(TokenStream<ComponentValue>&);
|
||||
RefPtr<StyleValue> parse_transform_value(Vector<ComponentValue> const&);
|
||||
RefPtr<StyleValue> parse_transform_origin_value(Vector<ComponentValue> const&);
|
||||
|
||||
|
@ -140,8 +140,16 @@ RefPtr<StyleValue> ResolvedCSSStyleDeclaration::style_value_for_property(Layout:
|
||||
}
|
||||
case CSS::PropertyID::TextAlign:
|
||||
return IdentifierStyleValue::create(to_value_id(layout_node.computed_values().text_align()));
|
||||
case CSS::PropertyID::TextDecorationLine:
|
||||
return IdentifierStyleValue::create(to_value_id(layout_node.computed_values().text_decoration_line()));
|
||||
case CSS::PropertyID::TextDecorationLine: {
|
||||
auto text_decoration_lines = layout_node.computed_values().text_decoration_line();
|
||||
if (text_decoration_lines.is_empty())
|
||||
return IdentifierStyleValue::create(ValueID::None);
|
||||
NonnullRefPtrVector<StyleValue> style_values;
|
||||
for (auto const& line : text_decoration_lines) {
|
||||
style_values.append(IdentifierStyleValue::create(to_value_id(line)));
|
||||
}
|
||||
return StyleValueList::create(move(style_values), StyleValueList::Separator::Space);
|
||||
}
|
||||
case CSS::PropertyID::TextDecorationStyle:
|
||||
return IdentifierStyleValue::create(to_value_id(layout_node.computed_values().text_decoration_style()));
|
||||
case CSS::PropertyID::TextTransform:
|
||||
|
@ -490,10 +490,23 @@ CSS::Display StyleProperties::display() const
|
||||
}
|
||||
}
|
||||
|
||||
Optional<CSS::TextDecorationLine> StyleProperties::text_decoration_line() const
|
||||
Vector<CSS::TextDecorationLine> StyleProperties::text_decoration_line() const
|
||||
{
|
||||
auto value = property(CSS::PropertyID::TextDecorationLine);
|
||||
return value_id_to_text_decoration_line(value->to_identifier());
|
||||
|
||||
if (value->is_value_list()) {
|
||||
Vector<CSS::TextDecorationLine> lines;
|
||||
auto& values = value->as_value_list().values();
|
||||
for (auto const& item : values) {
|
||||
lines.append(value_id_to_text_decoration_line(item.to_identifier()).value());
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
if (value->is_identifier() && value->to_identifier() == ValueID::None)
|
||||
return {};
|
||||
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
Optional<CSS::TextDecorationStyle> StyleProperties::text_decoration_style() const
|
||||
|
@ -55,7 +55,7 @@ public:
|
||||
Optional<CSS::Cursor> cursor() const;
|
||||
Optional<CSS::WhiteSpace> white_space() const;
|
||||
Optional<CSS::LineStyle> line_style(CSS::PropertyID) const;
|
||||
Optional<CSS::TextDecorationLine> text_decoration_line() const;
|
||||
Vector<CSS::TextDecorationLine> text_decoration_line() const;
|
||||
Optional<CSS::TextDecorationStyle> text_decoration_style() const;
|
||||
Optional<CSS::TextTransform> text_transform() const;
|
||||
Vector<CSS::ShadowData> text_shadow() const;
|
||||
|
@ -453,9 +453,7 @@ void NodeWithStyle::apply_style(const CSS::StyleProperties& computed_style)
|
||||
if (pointer_events.has_value())
|
||||
computed_values.set_pointer_events(pointer_events.value());
|
||||
|
||||
auto text_decoration_line = computed_style.text_decoration_line();
|
||||
if (text_decoration_line.has_value())
|
||||
computed_values.set_text_decoration_line(text_decoration_line.value());
|
||||
computed_values.set_text_decoration_line(computed_style.text_decoration_line());
|
||||
|
||||
auto text_decoration_style = computed_style.text_decoration_style();
|
||||
if (text_decoration_style.has_value())
|
||||
|
@ -289,36 +289,11 @@ static void paint_cursor_if_needed(PaintContext& context, Layout::TextNode const
|
||||
|
||||
static void paint_text_decoration(Gfx::Painter& painter, Layout::Node const& text_node, Layout::LineBoxFragment const& fragment)
|
||||
{
|
||||
Gfx::IntPoint line_start_point {};
|
||||
Gfx::IntPoint line_end_point {};
|
||||
|
||||
auto& font = fragment.layout_node().font();
|
||||
auto fragment_box = enclosing_int_rect(fragment.absolute_rect());
|
||||
auto glyph_height = font.pixel_size();
|
||||
auto baseline = fragment_box.height() / 2 - (glyph_height + 4) / 2 + glyph_height;
|
||||
|
||||
switch (text_node.computed_values().text_decoration_line()) {
|
||||
case CSS::TextDecorationLine::None:
|
||||
return;
|
||||
case CSS::TextDecorationLine::Underline:
|
||||
line_start_point = fragment_box.top_left().translated(0, baseline + 2);
|
||||
line_end_point = fragment_box.top_right().translated(0, baseline + 2);
|
||||
break;
|
||||
case CSS::TextDecorationLine::Overline:
|
||||
line_start_point = fragment_box.top_left().translated(0, baseline - glyph_height);
|
||||
line_end_point = fragment_box.top_right().translated(0, baseline - glyph_height);
|
||||
break;
|
||||
case CSS::TextDecorationLine::LineThrough: {
|
||||
auto x_height = font.x_height();
|
||||
line_start_point = fragment_box.top_left().translated(0, baseline - x_height / 2);
|
||||
line_end_point = fragment_box.top_right().translated(0, baseline - x_height / 2);
|
||||
break;
|
||||
}
|
||||
case CSS::TextDecorationLine::Blink:
|
||||
// Conforming user agents may simply not blink the text
|
||||
return;
|
||||
}
|
||||
|
||||
auto line_color = text_node.computed_values().text_decoration_color();
|
||||
|
||||
int line_thickness = [&] {
|
||||
@ -329,38 +304,66 @@ static void paint_text_decoration(Gfx::Painter& painter, Layout::Node const& tex
|
||||
return computed_thickness.to_px(text_node);
|
||||
}();
|
||||
|
||||
switch (text_node.computed_values().text_decoration_style()) {
|
||||
case CSS::TextDecorationStyle::Solid:
|
||||
painter.draw_line(line_start_point, line_end_point, line_color, line_thickness, Gfx::Painter::LineStyle::Solid);
|
||||
break;
|
||||
case CSS::TextDecorationStyle::Double:
|
||||
switch (text_node.computed_values().text_decoration_line()) {
|
||||
auto text_decoration_lines = text_node.computed_values().text_decoration_line();
|
||||
for (auto line : text_decoration_lines) {
|
||||
Gfx::IntPoint line_start_point {};
|
||||
Gfx::IntPoint line_end_point {};
|
||||
|
||||
switch (line) {
|
||||
case CSS::TextDecorationLine::None:
|
||||
return;
|
||||
case CSS::TextDecorationLine::Underline:
|
||||
line_start_point = fragment_box.top_left().translated(0, baseline + 2);
|
||||
line_end_point = fragment_box.top_right().translated(0, baseline + 2);
|
||||
break;
|
||||
case CSS::TextDecorationLine::Overline:
|
||||
line_start_point.translate_by(0, -line_thickness - 1);
|
||||
line_end_point.translate_by(0, -line_thickness - 1);
|
||||
line_start_point = fragment_box.top_left().translated(0, baseline - glyph_height);
|
||||
line_end_point = fragment_box.top_right().translated(0, baseline - glyph_height);
|
||||
break;
|
||||
case CSS::TextDecorationLine::LineThrough:
|
||||
line_start_point.translate_by(0, -line_thickness / 2);
|
||||
line_end_point.translate_by(0, -line_thickness / 2);
|
||||
case CSS::TextDecorationLine::LineThrough: {
|
||||
auto x_height = font.x_height();
|
||||
line_start_point = fragment_box.top_left().translated(0, baseline - x_height / 2);
|
||||
line_end_point = fragment_box.top_right().translated(0, baseline - x_height / 2);
|
||||
break;
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
case CSS::TextDecorationLine::Blink:
|
||||
// Conforming user agents may simply not blink the text
|
||||
return;
|
||||
}
|
||||
|
||||
painter.draw_line(line_start_point, line_end_point, line_color, line_thickness);
|
||||
painter.draw_line(line_start_point.translated(0, line_thickness + 1), line_end_point.translated(0, line_thickness + 1), line_color, line_thickness);
|
||||
break;
|
||||
case CSS::TextDecorationStyle::Dashed:
|
||||
painter.draw_line(line_start_point, line_end_point, line_color, line_thickness, Gfx::Painter::LineStyle::Dashed);
|
||||
break;
|
||||
case CSS::TextDecorationStyle::Dotted:
|
||||
painter.draw_line(line_start_point, line_end_point, line_color, line_thickness, Gfx::Painter::LineStyle::Dotted);
|
||||
break;
|
||||
case CSS::TextDecorationStyle::Wavy:
|
||||
painter.draw_triangle_wave(line_start_point, line_end_point, line_color, line_thickness + 1, line_thickness);
|
||||
break;
|
||||
switch (text_node.computed_values().text_decoration_style()) {
|
||||
case CSS::TextDecorationStyle::Solid:
|
||||
painter.draw_line(line_start_point, line_end_point, line_color, line_thickness, Gfx::Painter::LineStyle::Solid);
|
||||
break;
|
||||
case CSS::TextDecorationStyle::Double:
|
||||
switch (line) {
|
||||
case CSS::TextDecorationLine::Underline:
|
||||
break;
|
||||
case CSS::TextDecorationLine::Overline:
|
||||
line_start_point.translate_by(0, -line_thickness - 1);
|
||||
line_end_point.translate_by(0, -line_thickness - 1);
|
||||
break;
|
||||
case CSS::TextDecorationLine::LineThrough:
|
||||
line_start_point.translate_by(0, -line_thickness / 2);
|
||||
line_end_point.translate_by(0, -line_thickness / 2);
|
||||
break;
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
painter.draw_line(line_start_point, line_end_point, line_color, line_thickness);
|
||||
painter.draw_line(line_start_point.translated(0, line_thickness + 1), line_end_point.translated(0, line_thickness + 1), line_color, line_thickness);
|
||||
break;
|
||||
case CSS::TextDecorationStyle::Dashed:
|
||||
painter.draw_line(line_start_point, line_end_point, line_color, line_thickness, Gfx::Painter::LineStyle::Dashed);
|
||||
break;
|
||||
case CSS::TextDecorationStyle::Dotted:
|
||||
painter.draw_line(line_start_point, line_end_point, line_color, line_thickness, Gfx::Painter::LineStyle::Dotted);
|
||||
break;
|
||||
case CSS::TextDecorationStyle::Wavy:
|
||||
painter.draw_triangle_wave(line_start_point, line_end_point, line_color, line_thickness + 1, line_thickness);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user