summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Base/res/html/misc/text-decoration.html2
-rw-r--r--Userland/Libraries/LibWeb/CSS/ComputedValues.h7
-rw-r--r--Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp60
-rw-r--r--Userland/Libraries/LibWeb/CSS/Parser/Parser.h1
-rw-r--r--Userland/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.cpp12
-rw-r--r--Userland/Libraries/LibWeb/CSS/StyleProperties.cpp17
-rw-r--r--Userland/Libraries/LibWeb/CSS/StyleProperties.h2
-rw-r--r--Userland/Libraries/LibWeb/Layout/Node.cpp4
-rw-r--r--Userland/Libraries/LibWeb/Painting/PaintableBox.cpp103
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;
+ }
}
}