From 7abfb18656d7ecf389b10b1a75d2f1e8f5f2a718 Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Sun, 18 Jul 2021 21:20:40 +0100 Subject: [PATCH] LibWeb: Implement CSS color parsing from Tokens This was broken when we switched away from using StringStyleValues. While I was at it, I have implemented hsl/a() and the percentage syntax for rgb/a(). As a bonus, added `colors.html` as a test page for the various CSS color syntaxes, since nothing was testing rgb() or rgba() before. Much of the parsing code in LibGFX/Color.h seems to be centered around CSS color values, but this is not used by the new Parser. (And can't be used, because it requires a String value and we have a list of Tokens of some kind instead.) Maybe that should be removed from there when the new CSS parser is operational. --- Base/res/html/misc/colors.html | 29 +++ Base/res/html/misc/welcome.html | 1 + .../Libraries/LibWeb/CSS/Parser/Parser.cpp | 172 +++++++++++++++++- Userland/Libraries/LibWeb/CSS/Parser/Token.h | 2 + 4 files changed, 195 insertions(+), 9 deletions(-) create mode 100644 Base/res/html/misc/colors.html diff --git a/Base/res/html/misc/colors.html b/Base/res/html/misc/colors.html new file mode 100644 index 0000000000..df0d67de1a --- /dev/null +++ b/Base/res/html/misc/colors.html @@ -0,0 +1,29 @@ + + + CSS test + + + +
This is green, using a named color.
+
This is green, using #0F0.
+
This is green, using #00ff00.
+
This is green, using rgb(0, 255, 0).
+
This is green, using rgb(0%, 100%, 0%).
+
This is green, using rgb(-5%, 120%, -42%).
+
This is green, using rgba(0, 255, 0, 1).
+
This is green, using rgba(0%, 100%, 0%, 1).
+
This is green, using hsl(120, 100%, 50%).
+
This is green, using hsla(120, 100%, 50%, 1).
+ + diff --git a/Base/res/html/misc/welcome.html b/Base/res/html/misc/welcome.html index fdaafa4972..e13f9b9f84 100644 --- a/Base/res/html/misc/welcome.html +++ b/Base/res/html/misc/welcome.html @@ -114,6 +114,7 @@
  • form
  • borders
  • css
  • +
  • css colors
  • acid1
  • acid2
  • attribute selectors
  • diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp index e1ec171571..e99e6f3d9a 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp @@ -1405,17 +1405,171 @@ RefPtr Parser::parse_identifier_value(ParsingContext const&, StyleCo RefPtr Parser::parse_color_value(ParsingContext const&, StyleComponentValueRule const& component_value) { + // https://www.w3.org/TR/css-color-3/ auto parse_css_color = [&]() -> Optional { - if (component_value.is(Token::Type::Ident) && component_value.token().ident().equals_ignoring_case("transparent")) - return Color::from_rgba(0x00000000); + if (component_value.is(Token::Type::Ident)) { + auto ident = component_value.token().ident(); + if (ident.equals_ignoring_case("transparent")) + return Color::from_rgba(0x00000000); - // FIXME: Handle all the different color notations. - // https://www.w3.org/TR/css-color-3/ - // Right now, this uses non-CSS-specific parsing, and assumes the whole color value is one token, - // which is isn't if it's a function-style syntax. - auto color = Color::from_string(component_value.token().m_value.to_string().to_lowercase()); - if (color.has_value()) - return color; + auto color = Color::from_string(ident.to_string().to_lowercase()); + if (color.has_value()) + return color; + + } else if (component_value.is(Token::Type::Hash)) { + // FIXME: Read it directly + auto color = Color::from_string(String::formatted("#{}", component_value.token().m_value.to_string().to_lowercase())); + if (color.has_value()) + return color; + + } else if (component_value.is_function()) { + auto& function = component_value.function(); + auto& values = function.values(); + + Vector params; + for (size_t i = 0; i < values.size(); ++i) { + auto& value = values.at(i); + if (value.is(Token::Type::Whitespace)) + continue; + + if (value.is(Token::Type::Percentage) || value.is(Token::Type::Number)) { + params.append(value.token()); + // Eat following comma and whitespace + while ((i + 1) < values.size()) { + auto& next = values.at(i + 1); + if (next.is(Token::Type::Whitespace)) + i++; + else if (next.is(Token::Type::Comma)) + break; + + return {}; + } + } + } + + if (function.name().equals_ignoring_case("rgb")) { + if (params.size() != 3) + return {}; + + auto r_val = params[0]; + auto g_val = params[1]; + auto b_val = params[2]; + + if (r_val.is(Token::NumberType::Integer) + && g_val.is(Token::NumberType::Integer) + && b_val.is(Token::NumberType::Integer)) { + + auto maybe_r = r_val.m_value.string_view().to_uint(); + auto maybe_g = g_val.m_value.string_view().to_uint(); + auto maybe_b = b_val.m_value.string_view().to_uint(); + if (maybe_r.has_value() && maybe_g.has_value() && maybe_b.has_value()) + return Color(maybe_r.value(), maybe_g.value(), maybe_b.value()); + + } else if (r_val.is(Token::Type::Percentage) + && g_val.is(Token::Type::Percentage) + && b_val.is(Token::Type::Percentage)) { + + auto maybe_r = try_parse_float(r_val.m_value.string_view()); + auto maybe_g = try_parse_float(g_val.m_value.string_view()); + auto maybe_b = try_parse_float(b_val.m_value.string_view()); + if (maybe_r.has_value() && maybe_g.has_value() && maybe_b.has_value()) { + u8 r = clamp(lroundf(maybe_r.value() * 2.55f), 0, 255); + u8 g = clamp(lroundf(maybe_g.value() * 2.55f), 0, 255); + u8 b = clamp(lroundf(maybe_b.value() * 2.55f), 0, 255); + return Color(r, g, b); + } + } + } else if (function.name().equals_ignoring_case("rgba")) { + if (params.size() != 4) + return {}; + + auto r_val = params[0]; + auto g_val = params[1]; + auto b_val = params[2]; + auto a_val = params[3]; + + if (r_val.is(Token::NumberType::Integer) + && g_val.is(Token::NumberType::Integer) + && b_val.is(Token::NumberType::Integer) + && a_val.is(Token::Type::Number)) { + + auto maybe_r = r_val.m_value.string_view().to_uint(); + auto maybe_g = g_val.m_value.string_view().to_uint(); + auto maybe_b = b_val.m_value.string_view().to_uint(); + auto maybe_a = try_parse_float(a_val.m_value.string_view()); + if (maybe_r.has_value() && maybe_g.has_value() && maybe_b.has_value() && maybe_a.has_value()) { + u8 a = clamp(lroundf(maybe_a.value() * 255.0f), 0, 255); + return Color(maybe_r.value(), maybe_g.value(), maybe_b.value(), a); + } + + } else if (r_val.is(Token::Type::Percentage) + && g_val.is(Token::Type::Percentage) + && b_val.is(Token::Type::Percentage) + && a_val.is(Token::Type::Number)) { + + auto maybe_r = try_parse_float(r_val.m_value.string_view()); + auto maybe_g = try_parse_float(g_val.m_value.string_view()); + auto maybe_b = try_parse_float(b_val.m_value.string_view()); + auto maybe_a = try_parse_float(a_val.m_value.string_view()); + if (maybe_r.has_value() && maybe_g.has_value() && maybe_b.has_value() && maybe_a.has_value()) { + u8 r = clamp(lroundf(maybe_r.value() * 2.55f), 0, 255); + u8 g = clamp(lroundf(maybe_g.value() * 2.55f), 0, 255); + u8 b = clamp(lroundf(maybe_b.value() * 2.55f), 0, 255); + u8 a = clamp(lroundf(maybe_a.value() * 255.0f), 0, 255); + return Color(r, g, b, a); + } + } + } else if (function.name().equals_ignoring_case("hsl")) { + if (params.size() != 3) + return {}; + + auto h_val = params[0]; + auto s_val = params[1]; + auto l_val = params[2]; + + if (h_val.is(Token::Type::Number) + && s_val.is(Token::Type::Percentage) + && l_val.is(Token::Type::Percentage)) { + + auto maybe_h = try_parse_float(h_val.m_value.string_view()); + auto maybe_s = try_parse_float(s_val.m_value.string_view()); + auto maybe_l = try_parse_float(l_val.m_value.string_view()); + if (maybe_h.has_value() && maybe_s.has_value() && maybe_l.has_value()) { + float h = maybe_h.value(); + float s = maybe_s.value() / 100.0f; + float l = maybe_l.value() / 100.0f; + return Color::from_hsl(h, s, l); + } + } + } else if (function.name().equals_ignoring_case("hsla")) { + if (params.size() != 4) + return {}; + + auto h_val = params[0]; + auto s_val = params[1]; + auto l_val = params[2]; + auto a_val = params[3]; + + if (h_val.is(Token::Type::Number) + && s_val.is(Token::Type::Percentage) + && l_val.is(Token::Type::Percentage) + && a_val.is(Token::Type::Number)) { + + auto maybe_h = try_parse_float(h_val.m_value.string_view()); + auto maybe_s = try_parse_float(s_val.m_value.string_view()); + auto maybe_l = try_parse_float(l_val.m_value.string_view()); + auto maybe_a = try_parse_float(a_val.m_value.string_view()); + if (maybe_h.has_value() && maybe_s.has_value() && maybe_l.has_value() && maybe_a.has_value()) { + float h = maybe_h.value(); + float s = maybe_s.value() / 100.0f; + float l = maybe_l.value() / 100.0f; + float a = maybe_a.value(); + return Color::from_hsla(h, s, l, a); + } + } + } + return {}; + } return {}; }; diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Token.h b/Userland/Libraries/LibWeb/CSS/Parser/Token.h index d72b8c881e..3006b734db 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Token.h +++ b/Userland/Libraries/LibWeb/CSS/Parser/Token.h @@ -76,6 +76,8 @@ public: return m_value.string_view(); } + bool is(NumberType number_type) const { return is(Token::Type::Number) && m_number_type == number_type; } + int integer() const { VERIFY(m_type == Type::Number && m_number_type == NumberType::Integer);