summaryrefslogtreecommitdiff
path: root/Userland/Libraries/LibWeb/CSS
diff options
context:
space:
mode:
authorThomas Fach-Pedersen <thomas@fach-pedersen.net>2022-06-09 19:08:57 +0200
committerSam Atkins <atkinssj@gmail.com>2022-06-15 19:10:43 +0100
commit019e3a342d3c1fc21fade6b07e90cfe2361490df (patch)
tree82c28e4a5e6980fc50d57c6daeecc76cf9e408bf /Userland/Libraries/LibWeb/CSS
parent83c79fec1c44c92a343d53256eeb702141978061 (diff)
downloadserenity-019e3a342d3c1fc21fade6b07e90cfe2361490df.zip
LibWeb: Parse rgb and hsl functions according to CSS Module Level 4
Implement parsing of rgb(..) and hsl(..) in both the modern level 4 syntax without commas, and the legacy syntax with commas. The parser accepts non-integer numbers but rounds to integer values for now.
Diffstat (limited to 'Userland/Libraries/LibWeb/CSS')
-rw-r--r--Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp252
-rw-r--r--Userland/Libraries/LibWeb/CSS/Parser/Parser.h1
2 files changed, 132 insertions, 121 deletions
diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp
index ad768fa60d..039900a2a1 100644
--- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp
+++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp
@@ -3012,150 +3012,160 @@ RefPtr<StyleValue> Parser::parse_identifier_value(ComponentValue const& componen
return {};
}
-Optional<Color> Parser::parse_color(ComponentValue const& component_value)
+Optional<Color> Parser::parse_rgb_or_hsl_color(StringView function_name, Vector<ComponentValue> const& component_values)
{
- // https://www.w3.org/TR/css-color-3/
- if (component_value.is(Token::Type::Ident)) {
- auto ident = component_value.token().ident();
+ Token params[4];
+ bool legacy_syntax = false;
+ auto tokens = TokenStream { component_values };
- auto color = Color::from_string(ident);
- if (color.has_value())
- return color;
+ tokens.skip_whitespace();
+ auto& component1 = tokens.next_token();
- } else if (component_value.is(Token::Type::Hash)) {
- auto color = Color::from_string(String::formatted("#{}", component_value.token().hash_value()));
- if (color.has_value())
- return color;
+ if (!component1.is(Token::Type::Number)
+ && !component1.is(Token::Type::Percentage)
+ && !component1.is(Token::Type::Dimension))
return {};
+ params[0] = component1.token();
- } else if (component_value.is_function()) {
- auto& function = component_value.function();
- auto& values = function.values();
+ tokens.skip_whitespace();
+ if (tokens.peek_token().is(Token::Type::Comma)) {
+ legacy_syntax = true;
+ tokens.next_token();
+ }
- Vector<Token> params;
- for (size_t i = 0; i < values.size(); ++i) {
- auto& value = values.at(i);
- if (value.is(Token::Type::Whitespace))
- continue;
+ tokens.skip_whitespace();
+ auto& component2 = tokens.next_token();
+ if (!component2.is(Token::Type::Number) && !component2.is(Token::Type::Percentage))
+ return {};
+ params[1] = component2.token();
- 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;
+ tokens.skip_whitespace();
+ if (legacy_syntax && !tokens.next_token().is(Token::Type::Comma))
+ return {};
- return {};
- }
- }
+ tokens.skip_whitespace();
+ auto& component3 = tokens.next_token();
+ if (!component3.is(Token::Type::Number) && !component3.is(Token::Type::Percentage))
+ return {};
+ params[2] = component3.token();
+
+ tokens.skip_whitespace();
+ auto& alpha_separator = tokens.peek_token();
+ bool has_comma = alpha_separator.is(Token::Type::Comma);
+ bool has_slash = alpha_separator.is(Token::Type::Delim) && alpha_separator.token().delim() == '/';
+ if (legacy_syntax ? has_comma : has_slash) {
+ tokens.next_token();
+
+ tokens.skip_whitespace();
+ auto& component4 = tokens.next_token();
+ if (!component4.is(Token::Type::Number) && !component4.is(Token::Type::Percentage))
+ return {};
+ params[3] = component4.token();
+ }
+
+ tokens.skip_whitespace();
+ if (tokens.has_next_token())
+ return {};
+
+ if (function_name.equals_ignoring_case("rgb"sv)
+ || function_name.equals_ignoring_case("rgba"sv)) {
+
+ // https://www.w3.org/TR/css-color-4/#rgb-functions
+
+ u8 a_val = 255;
+ if (params[3].is(Token::Type::Number))
+ a_val = clamp(lroundf(params[3].number_value() * 255.0f), 0, 255);
+ else if (params[3].is(Token::Type::Percentage))
+ a_val = clamp(lroundf(params[3].percentage() * 2.55f), 0, 255);
+
+ if (params[0].is(Token::Type::Number)
+ && params[1].is(Token::Type::Number)
+ && params[2].is(Token::Type::Number)) {
+
+ u8 r_val = clamp(llround(params[0].number_value()), 0, 255);
+ u8 g_val = clamp(llround(params[1].number_value()), 0, 255);
+ u8 b_val = clamp(llround(params[2].number_value()), 0, 255);
+
+ return Color(r_val, g_val, b_val, a_val);
}
- if (function.name().equals_ignoring_case("rgb")) {
- if (params.size() != 3)
- return {};
+ if (params[0].is(Token::Type::Percentage)
+ && params[1].is(Token::Type::Percentage)
+ && params[2].is(Token::Type::Percentage)) {
- auto r_val = params[0];
- auto g_val = params[1];
- auto b_val = params[2];
+ u8 r_val = lroundf(clamp(params[0].percentage() * 2.55f, 0, 255));
+ u8 g_val = lroundf(clamp(params[1].percentage() * 2.55f, 0, 255));
+ u8 b_val = lroundf(clamp(params[2].percentage() * 2.55f, 0, 255));
- if (r_val.is(Token::Type::Number) && r_val.number().is_integer()
- && g_val.is(Token::Type::Number) && g_val.number().is_integer()
- && b_val.is(Token::Type::Number) && b_val.number().is_integer()) {
+ return Color(r_val, g_val, b_val, a_val);
+ }
+ } else if (function_name.equals_ignoring_case("hsl"sv)
+ || function_name.equals_ignoring_case("hsla"sv)) {
- auto r = r_val.to_integer();
- auto g = g_val.to_integer();
- auto b = b_val.to_integer();
- if (AK::is_within_range<u8>(r) && AK::is_within_range<u8>(g) && AK::is_within_range<u8>(b))
- return Color(r, g, b);
+ // https://www.w3.org/TR/css-color-4/#the-hsl-notation
- } else if (r_val.is(Token::Type::Percentage)
- && g_val.is(Token::Type::Percentage)
- && b_val.is(Token::Type::Percentage)) {
+ float a_val = 1.0f;
+ if (params[3].is(Token::Type::Number))
+ a_val = params[3].number_value();
+ else if (params[3].is(Token::Type::Percentage))
+ a_val = params[3].percentage() / 100.0f;
- u8 r = clamp(lroundf(r_val.percentage() * 2.55f), 0, 255);
- u8 g = clamp(lroundf(g_val.percentage() * 2.55f), 0, 255);
- u8 b = clamp(lroundf(b_val.percentage() * 2.55f), 0, 255);
- return Color(r, g, b);
- }
- } else if (function.name().equals_ignoring_case("rgba")) {
- if (params.size() != 4)
- return {};
+ if (params[0].is(Token::Type::Dimension)
+ && params[1].is(Token::Type::Percentage)
+ && params[2].is(Token::Type::Percentage)) {
- 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::Type::Number) && r_val.number().is_integer()
- && g_val.is(Token::Type::Number) && g_val.number().is_integer()
- && b_val.is(Token::Type::Number) && b_val.number().is_integer()
- && a_val.is(Token::Type::Number)) {
-
- auto r = r_val.to_integer();
- auto g = g_val.to_integer();
- auto b = b_val.to_integer();
- auto a = clamp(lroundf(a_val.number_value() * 255.0f), 0, 255);
- if (AK::is_within_range<u8>(r) && AK::is_within_range<u8>(g) && AK::is_within_range<u8>(b))
- return Color(r, g, b, 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 r = r_val.percentage();
- auto g = g_val.percentage();
- auto b = b_val.percentage();
- auto a = a_val.number_value();
-
- u8 r_255 = clamp(lroundf(r * 2.55f), 0, 255);
- u8 g_255 = clamp(lroundf(g * 2.55f), 0, 255);
- u8 b_255 = clamp(lroundf(b * 2.55f), 0, 255);
- u8 a_255 = clamp(lroundf(a * 255.0f), 0, 255);
- return Color(r_255, g_255, b_255, a_255);
- }
- } else if (function.name().equals_ignoring_case("hsl")) {
- if (params.size() != 3)
+ float numeric_value = params[0].dimension_value();
+ auto unit_string = params[0].dimension_unit();
+ auto angle_type = Angle::unit_from_name(unit_string);
+
+ if (!angle_type.has_value())
return {};
- auto h_val = params[0];
- auto s_val = params[1];
- auto l_val = params[2];
+ auto angle = Angle { numeric_value, angle_type.release_value() };
- if (h_val.is(Token::Type::Number)
- && s_val.is(Token::Type::Percentage)
- && l_val.is(Token::Type::Percentage)) {
+ float h_val = fmodf(angle.to_degrees(), 360.0f);
+ float s_val = params[1].percentage() / 100.0f;
+ float l_val = params[2].percentage() / 100.0f;
- auto h = h_val.number_value();
- auto s = s_val.percentage() / 100.0f;
- auto l = l_val.percentage() / 100.0f;
- return Color::from_hsl(h, s, l);
- }
- } else if (function.name().equals_ignoring_case("hsla")) {
- if (params.size() != 4)
- return {};
+ return Color::from_hsla(h_val, s_val, l_val, a_val);
+ }
- 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 h = h_val.number_value();
- auto s = s_val.percentage() / 100.0f;
- auto l = l_val.percentage() / 100.0f;
- auto a = a_val.number_value();
- return Color::from_hsla(h, s, l, a);
- }
+ if (params[0].is(Token::Type::Number)
+ && params[1].is(Token::Type::Percentage)
+ && params[2].is(Token::Type::Percentage)) {
+
+ float h_val = fmodf(params[0].number_value(), 360.0f);
+ float s_val = params[1].percentage() / 100.0f;
+ float l_val = params[2].percentage() / 100.0f;
+
+ return Color::from_hsla(h_val, s_val, l_val, a_val);
}
+ }
+
+ return {};
+}
+
+Optional<Color> Parser::parse_color(ComponentValue const& component_value)
+{
+ // https://www.w3.org/TR/css-color-4/
+ if (component_value.is(Token::Type::Ident)) {
+ auto ident = component_value.token().ident();
+
+ auto color = Color::from_string(ident);
+ if (color.has_value())
+ return color;
+
+ } else if (component_value.is(Token::Type::Hash)) {
+ auto color = Color::from_string(String::formatted("#{}", component_value.token().hash_value()));
+ if (color.has_value())
+ return color;
return {};
+
+ } else if (component_value.is_function()) {
+ auto& function = component_value.function();
+ auto& values = function.values();
+
+ return parse_rgb_or_hsl_color(function.name(), values);
}
// https://quirks.spec.whatwg.org/#the-hashless-hex-color-quirk
diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.h b/Userland/Libraries/LibWeb/CSS/Parser/Parser.h
index c378b30625..79aaab3460 100644
--- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.h
+++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.h
@@ -304,6 +304,7 @@ private:
Variant<Angle, Frequency, Length, Percentage, Resolution, Time> m_value;
};
Optional<Dimension> parse_dimension(ComponentValue const&);
+ Optional<Color> parse_rgb_or_hsl_color(StringView function_name, Vector<ComponentValue> const&);
Optional<Color> parse_color(ComponentValue const&);
Optional<Length> parse_length(ComponentValue const&);
Optional<Ratio> parse_ratio(TokenStream<ComponentValue>&);