diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp index 06cdd6ada7c..0d670616bfe 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp @@ -2277,139 +2277,6 @@ RefPtr Parser::parse_identifier_value(TokenStream& t return nullptr; } -Optional Parser::parse_rgb_or_hsl_color(StringView function_name, Vector const& component_values) -{ - Token params[4]; - bool legacy_syntax = false; - auto tokens = TokenStream { component_values }; - - tokens.skip_whitespace(); - auto const& component1 = tokens.next_token(); - - if (!component1.is(Token::Type::Number) - && !component1.is(Token::Type::Percentage) - && !component1.is(Token::Type::Dimension)) - return {}; - params[0] = component1.token(); - - tokens.skip_whitespace(); - if (tokens.peek_token().is(Token::Type::Comma)) { - legacy_syntax = true; - tokens.next_token(); - } - - tokens.skip_whitespace(); - auto const& component2 = tokens.next_token(); - if (!component2.is(Token::Type::Number) && !component2.is(Token::Type::Percentage)) - return {}; - params[1] = component2.token(); - - tokens.skip_whitespace(); - if (legacy_syntax && !tokens.next_token().is(Token::Type::Comma)) - return {}; - - tokens.skip_whitespace(); - auto const& component3 = tokens.next_token(); - if (!component3.is(Token::Type::Number) && !component3.is(Token::Type::Percentage)) - return {}; - params[2] = component3.token(); - - tokens.skip_whitespace(); - auto const& alpha_separator = tokens.peek_token(); - bool has_comma = alpha_separator.is(Token::Type::Comma); - bool has_slash = alpha_separator.is_delim('/'); - if (legacy_syntax ? has_comma : has_slash) { - tokens.next_token(); - - tokens.skip_whitespace(); - auto const& 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_ascii_case("rgb"sv) - || function_name.equals_ignoring_ascii_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(lround(params[3].number_value() * 255.0), 0, 255); - else if (params[3].is(Token::Type::Percentage)) - a_val = clamp(lround(params[3].percentage() * 2.55), 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(llroundf(params[0].number_value()), 0, 255); - u8 g_val = clamp(llroundf(params[1].number_value()), 0, 255); - u8 b_val = clamp(llroundf(params[2].number_value()), 0, 255); - - return Color(r_val, g_val, b_val, a_val); - } - - if (params[0].is(Token::Type::Percentage) - && params[1].is(Token::Type::Percentage) - && params[2].is(Token::Type::Percentage)) { - - u8 r_val = lround(clamp(params[0].percentage() * 2.55, 0, 255)); - u8 g_val = lround(clamp(params[1].percentage() * 2.55, 0, 255)); - u8 b_val = lround(clamp(params[2].percentage() * 2.55, 0, 255)); - - return Color(r_val, g_val, b_val, a_val); - } - } else if (function_name.equals_ignoring_ascii_case("hsl"sv) - || function_name.equals_ignoring_ascii_case("hsla"sv)) { - - // https://www.w3.org/TR/css-color-4/#the-hsl-notation - - auto a_val = 1.0; - 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.0; - - if (params[0].is(Token::Type::Dimension) - && params[1].is(Token::Type::Percentage) - && params[2].is(Token::Type::Percentage)) { - - auto 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 angle = Angle { numeric_value, angle_type.release_value() }; - - float h_val = fmod(angle.to_degrees(), 360.0); - float s_val = params[1].percentage() / 100.0; - float l_val = params[2].percentage() / 100.0; - - return Color::from_hsla(h_val, s_val, l_val, a_val); - } - - if (params[0].is(Token::Type::Number) - && params[1].is(Token::Type::Percentage) - && params[2].is(Token::Type::Percentage)) { - - float h_val = fmod(params[0].number_value(), 360.0); - float s_val = params[1].percentage() / 100.0; - float l_val = params[2].percentage() / 100.0; - - return Color::from_hsla(h_val, s_val, l_val, a_val); - } - } - - return {}; -} - // https://www.w3.org/TR/CSS2/visufx.html#value-def-shape RefPtr Parser::parse_rect_value(TokenStream& tokens) { @@ -2488,6 +2355,210 @@ RefPtr Parser::parse_rect_value(TokenStream& tokens) return RectStyleValue::create(EdgeRect { params[0], params[1], params[2], params[3] }); } +Optional Parser::parse_rgb_color(Vector const& component_values) +{ + u8 r_val = 0; + u8 g_val = 0; + u8 b_val = 0; + + auto tokens = TokenStream { component_values }; + + tokens.skip_whitespace(); + auto const& red = tokens.next_token(); + + if (!red.is(Token::Type::Number) + && !red.is(Token::Type::Percentage)) + return {}; + + tokens.skip_whitespace(); + bool legacy_syntax = tokens.peek_token().is(Token::Type::Comma); + if (legacy_syntax) { + // Legacy syntax. + tokens.next_token(); + tokens.skip_whitespace(); + + auto const& green = tokens.next_token(); + tokens.skip_whitespace(); + + tokens.next_token(); + tokens.skip_whitespace(); + + auto const& blue = tokens.next_token(); + + if (red.is(Token::Type::Percentage)) { + // Percentage components. + if (!green.is(Token::Type::Percentage) || !blue.is(Token::Type::Percentage)) + return {}; + + r_val = lround(clamp(red.token().percentage() * 2.55, 0, 255)); + g_val = lround(clamp(green.token().percentage() * 2.55, 0, 255)); + b_val = lround(clamp(blue.token().percentage() * 2.55, 0, 255)); + } else { + // Number components. + if (!green.is(Token::Type::Number) || !blue.is(Token::Type::Number)) + return {}; + + r_val = clamp(llroundf(red.token().number_value()), 0, 255); + g_val = clamp(llroundf(green.token().number_value()), 0, 255); + b_val = clamp(llroundf(blue.token().number_value()), 0, 255); + } + } else { + // Modern syntax. + + if (red.is(Token::Type::Number)) { + r_val = clamp(llroundf(red.token().number_value()), 0, 255); + } else { + r_val = lround(clamp(red.token().percentage() * 2.55, 0, 255)); + } + + auto const& green = tokens.next_token(); + if (green.is(Token::Type::Number)) { + g_val = clamp(llroundf(green.token().number_value()), 0, 255); + } else if (green.is(Token::Type::Percentage)) { + g_val = lround(clamp(green.token().percentage() * 2.55, 0, 255)); + } else { + return {}; + } + + tokens.skip_whitespace(); + auto const& blue = tokens.next_token(); + if (blue.is(Token::Type::Number)) { + b_val = clamp(llroundf(blue.token().number_value()), 0, 255); + } else if (blue.is(Token::Type::Percentage)) { + b_val = lround(clamp(blue.token().percentage() * 2.55, 0, 255)); + } else { + return {}; + } + } + + u8 alpha_val = 255; + tokens.skip_whitespace(); + if (tokens.has_next_token()) { + auto const& separator = tokens.next_token(); + bool is_comma = separator.is(Token::Type::Comma); + bool is_slash = separator.is_delim('/'); + if (legacy_syntax ? !is_comma : !is_slash) + return {}; + + tokens.skip_whitespace(); + auto const& alpha = tokens.next_token(); + + if (alpha.is(Token::Type::Number)) + alpha_val = clamp(lround(alpha.token().number_value() * 255.0), 0, 255); + else if (alpha.is(Token::Type::Percentage)) + alpha_val = clamp(lround(alpha.token().percentage() * 2.55), 0, 255); + else + return {}; + + tokens.skip_whitespace(); + if (tokens.has_next_token()) + return {}; // should have consumed all arguments. + } + + return Color(r_val, g_val, b_val, alpha_val); +} + +Optional Parser::parse_hsl_color(Vector const& component_values) +{ + float h_val = 0.0; + float s_val = 0.0; + float l_val = 0.0; + + auto tokens = TokenStream { component_values }; + + tokens.skip_whitespace(); + auto const& hue = tokens.next_token(); + + if (!hue.is(Token::Type::Number) + && !hue.is(Token::Type::Dimension)) + return {}; + + if (hue.is(Token::Type::Number)) { + h_val = fmod(hue.token().number_value(), 360.0); + } else { + auto numeric_value = hue.token().dimension_value(); + auto unit_string = hue.token().dimension_unit(); + auto angle_type = Angle::unit_from_name(unit_string); + + if (!angle_type.has_value()) + return {}; + + auto angle = Angle { numeric_value, angle_type.release_value() }; + + h_val = fmod(angle.to_degrees(), 360.0); + } + + tokens.skip_whitespace(); + bool legacy_syntax = tokens.peek_token().is(Token::Type::Comma); + if (legacy_syntax) { + // legacy syntax. + tokens.next_token(); + tokens.skip_whitespace(); + + auto const& saturation = tokens.next_token(); + if (!saturation.is(Token::Type::Percentage)) + return {}; + s_val = max(saturation.token().percentage() / 100.0, 0); + + tokens.skip_whitespace(); + tokens.next_token(); + tokens.skip_whitespace(); + + auto const& lightness = tokens.next_token(); + if (!lightness.is(Token::Type::Percentage)) + return {}; + l_val = lightness.token().percentage() / 100.0; + } else { + // Modern syntax. + + auto const& saturation = tokens.next_token(); + if (saturation.is(Token::Type::Number)) { + s_val = saturation.token().number_value() / 100.0; + } else if (saturation.is(Token::Type::Percentage)) { + s_val = saturation.token().percentage() / 100.0; + } else { + return {}; + } + s_val = max(s_val, 0); + + tokens.skip_whitespace(); + auto const& lightness = tokens.next_token(); + if (lightness.is(Token::Type::Number)) { + l_val = lightness.token().number_value() / 100.0; + } else if (lightness.is(Token::Type::Percentage)) { + l_val = lightness.token().percentage() / 100.0; + } else { + return {}; + } + } + + float alpha_val = 1.0; + tokens.skip_whitespace(); + if (tokens.has_next_token()) { + auto const& separator = tokens.next_token(); + bool is_comma = separator.is(Token::Type::Comma); + bool is_slash = separator.is_delim('/'); + if (legacy_syntax ? !is_comma : !is_slash) + return {}; + + tokens.skip_whitespace(); + auto const& alpha = tokens.next_token(); + + if (alpha.is(Token::Type::Number)) + alpha_val = alpha.token().number_value(); + else if (alpha.is(Token::Type::Percentage)) + alpha_val = alpha.token().percentage() / 100.0; + else + return {}; + + tokens.skip_whitespace(); + if (tokens.has_next_token()) + return {}; // should have consumed all arguments. + } + + return Color::from_hsla(h_val, s_val, l_val, alpha_val); +} + Optional Parser::parse_color(ComponentValue const& component_value) { // https://www.w3.org/TR/css-color-4/ @@ -2507,8 +2578,14 @@ Optional Parser::parse_color(ComponentValue const& component_value) } else if (component_value.is_function()) { auto const& function = component_value.function(); auto const& values = function.values(); + auto const function_name = function.name(); - return parse_rgb_or_hsl_color(function.name(), values); + if (function_name.equals_ignoring_ascii_case("rgb"sv) || function_name.equals_ignoring_ascii_case("rgba"sv)) + return parse_rgb_color(values); + if (function_name.equals_ignoring_ascii_case("hsl"sv) || function_name.equals_ignoring_ascii_case("hsla"sv)) + return parse_hsl_color(values); + + return {}; } // 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 1db62d2510e..ebf2e618738 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.h +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.h @@ -238,7 +238,8 @@ private: Optional parse_time(TokenStream&); Optional parse_time_percentage(TokenStream&); - Optional parse_rgb_or_hsl_color(StringView function_name, Vector const&); + Optional parse_rgb_color(Vector const&); + Optional parse_hsl_color(Vector const&); Optional parse_color(ComponentValue const&); Optional parse_source_size_value(TokenStream&); Optional parse_ratio(TokenStream&);