diff options
author | Sam Atkins <atkinssj@serenityos.org> | 2022-04-14 16:22:35 +0100 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2022-04-14 21:54:10 +0200 |
commit | 7c91fda088dd92907e64d38af1093db6e693f641 (patch) | |
tree | cda471ea725253e44ec5a832b16f8936b900756f | |
parent | 85da8cbb07322addd8838df4d4c206843d77714f (diff) | |
download | serenity-7c91fda088dd92907e64d38af1093db6e693f641.zip |
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.
-rw-r--r-- | Base/res/html/misc/text-decoration.html | 2 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/CSS/ComputedValues.h | 7 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp | 60 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/CSS/Parser/Parser.h | 1 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.cpp | 12 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/CSS/StyleProperties.cpp | 17 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/CSS/StyleProperties.h | 2 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/Layout/Node.cpp | 4 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/Painting/PaintableBox.cpp | 103 |
9 files changed, 142 insertions, 66 deletions
diff --git a/Base/res/html/misc/text-decoration.html b/Base/res/html/misc/text-decoration.html index 562fb4b772..2c64113332 100644 --- a/Base/res/html/misc/text-decoration.html +++ b/Base/res/html/misc/text-decoration.html @@ -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> diff --git a/Userland/Libraries/LibWeb/CSS/ComputedValues.h b/Userland/Libraries/LibWeb/CSS/ComputedValues.h index cd12e06de9..ce032a5aa9 100644 --- a/Userland/Libraries/LibWeb/CSS/ComputedValues.h +++ b/Userland/Libraries/LibWeb/CSS/ComputedValues.h @@ -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; } diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp index d714e559b4..ba316883f6 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp @@ -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(); diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.h b/Userland/Libraries/LibWeb/CSS/Parser/Parser.h index f825f39a0c..14ab3e79f5 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.h +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.h @@ -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&); diff --git a/Userland/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.cpp b/Userland/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.cpp index a43077c5d8..d11bf4d9a2 100644 --- a/Userland/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.cpp +++ b/Userland/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.cpp @@ -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: diff --git a/Userland/Libraries/LibWeb/CSS/StyleProperties.cpp b/Userland/Libraries/LibWeb/CSS/StyleProperties.cpp index 38bfe0b2f9..da699579ce 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleProperties.cpp +++ b/Userland/Libraries/LibWeb/CSS/StyleProperties.cpp @@ -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 diff --git a/Userland/Libraries/LibWeb/CSS/StyleProperties.h b/Userland/Libraries/LibWeb/CSS/StyleProperties.h index 9826d266da..9505efdd0a 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleProperties.h +++ b/Userland/Libraries/LibWeb/CSS/StyleProperties.h @@ -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; diff --git a/Userland/Libraries/LibWeb/Layout/Node.cpp b/Userland/Libraries/LibWeb/Layout/Node.cpp index 729633876a..86ed73a1d0 100644 --- a/Userland/Libraries/LibWeb/Layout/Node.cpp +++ b/Userland/Libraries/LibWeb/Layout/Node.cpp @@ -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()) diff --git a/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp b/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp index 16c329971b..94250a753f 100644 --- a/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp +++ b/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp @@ -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; + } } } |