From 219672e77af250632bed950e883cb336ef3833bc Mon Sep 17 00:00:00 2001 From: Gingeh <39150378+Gingeh@users.noreply.github.com> Date: Sun, 5 Jan 2025 09:51:58 +1100 Subject: [PATCH] LibWeb/CSS: Implement the light-dark color function --- Libraries/LibWeb/CMakeLists.txt | 1 + Libraries/LibWeb/CSS/Parser/Parser.cpp | 36 +++++++++ Libraries/LibWeb/CSS/Parser/Parser.h | 1 + .../LibWeb/CSS/StyleValues/CSSColorValue.h | 1 + .../LibWeb/CSS/StyleValues/CSSLightDark.cpp | 37 ++++++++++ .../LibWeb/CSS/StyleValues/CSSLightDark.h | 43 +++++++++++ .../Screenshot/expected/color-scheme-ref.html | 10 +++ .../Screenshot/images/color-scheme-ref.png | Bin 0 -> 23319 bytes .../LibWeb/Screenshot/input/color-scheme.html | 69 ++++++++++++++++++ 9 files changed, 198 insertions(+) create mode 100644 Libraries/LibWeb/CSS/StyleValues/CSSLightDark.cpp create mode 100644 Libraries/LibWeb/CSS/StyleValues/CSSLightDark.h create mode 100644 Tests/LibWeb/Screenshot/expected/color-scheme-ref.html create mode 100644 Tests/LibWeb/Screenshot/images/color-scheme-ref.png create mode 100644 Tests/LibWeb/Screenshot/input/color-scheme.html diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt index 899c3eb259076..fe8e5c4eaca6e 100644 --- a/Libraries/LibWeb/CMakeLists.txt +++ b/Libraries/LibWeb/CMakeLists.txt @@ -138,6 +138,7 @@ set(SOURCES CSS/StyleValues/CSSKeywordValue.cpp CSS/StyleValues/CSSLabLike.cpp CSS/StyleValues/CSSLCHLike.cpp + CSS/StyleValues/CSSLightDark.cpp CSS/StyleValues/CSSRGB.cpp CSS/StyleValues/DisplayStyleValue.cpp CSS/StyleValues/EasingStyleValue.cpp diff --git a/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Libraries/LibWeb/CSS/Parser/Parser.cpp index a6ba66a60a1b2..a48357c2f07c3 100644 --- a/Libraries/LibWeb/CSS/Parser/Parser.cpp +++ b/Libraries/LibWeb/CSS/Parser/Parser.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include #include #include @@ -3491,6 +3492,39 @@ RefPtr Parser::parse_color_function(TokenStream& alpha.release_nonnull()); } +// https://drafts.csswg.org/css-color-5/#funcdef-light-dark +RefPtr Parser::parse_light_dark_color_value(TokenStream& outer_tokens) +{ + auto transaction = outer_tokens.begin_transaction(); + outer_tokens.discard_whitespace(); + + auto const& function_token = outer_tokens.consume_a_token(); + if (!function_token.is_function("light-dark"sv)) + return {}; + + auto inner_tokens = TokenStream { function_token.function().value }; + inner_tokens.discard_whitespace(); + + auto light = parse_color_value(inner_tokens); + if (!light) + return {}; + inner_tokens.discard_whitespace(); + + if (!inner_tokens.consume_a_token().is(Token::Type::Comma)) + return {}; + + auto dark = parse_color_value(inner_tokens); + if (!dark) + return {}; + inner_tokens.discard_whitespace(); + + if (inner_tokens.has_next_token()) + return {}; + + transaction.commit(); + return CSSLightDark::create(light, dark); +} + // https://www.w3.org/TR/css-color-4/#color-syntax RefPtr Parser::parse_color_value(TokenStream& tokens) { @@ -3522,6 +3556,8 @@ RefPtr Parser::parse_color_value(TokenStream& tok return oklab; if (auto oklch = parse_oklch_color_value(tokens)) return oklch; + if (auto light_dark = parse_light_dark_color_value(tokens)) + return light_dark; auto transaction = tokens.begin_transaction(); tokens.discard_whitespace(); diff --git a/Libraries/LibWeb/CSS/Parser/Parser.h b/Libraries/LibWeb/CSS/Parser/Parser.h index e9901d3221244..f3821349c0703 100644 --- a/Libraries/LibWeb/CSS/Parser/Parser.h +++ b/Libraries/LibWeb/CSS/Parser/Parser.h @@ -291,6 +291,7 @@ class Parser { RefPtr parse_lch_color_value(TokenStream&); RefPtr parse_oklch_color_value(TokenStream&); RefPtr parse_color_function(TokenStream&); + RefPtr parse_light_dark_color_value(TokenStream&); RefPtr parse_color_value(TokenStream&); RefPtr parse_color_scheme_value(TokenStream&); RefPtr parse_counter_value(TokenStream&); diff --git a/Libraries/LibWeb/CSS/StyleValues/CSSColorValue.h b/Libraries/LibWeb/CSS/StyleValues/CSSColorValue.h index 225a4e8fb4d6c..39ddf3e2df42f 100644 --- a/Libraries/LibWeb/CSS/StyleValues/CSSColorValue.h +++ b/Libraries/LibWeb/CSS/StyleValues/CSSColorValue.h @@ -39,6 +39,7 @@ class CSSColorValue : public CSSStyleValue { Rec2020, XYZD50, XYZD65, + LightDark, // This is used by CSSLightDark for light-dark(..., ...). }; ColorType color_type() const { return m_color_type; } diff --git a/Libraries/LibWeb/CSS/StyleValues/CSSLightDark.cpp b/Libraries/LibWeb/CSS/StyleValues/CSSLightDark.cpp new file mode 100644 index 0000000000000..ed171eef8d45f --- /dev/null +++ b/Libraries/LibWeb/CSS/StyleValues/CSSLightDark.cpp @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2025, Ladybird contributors + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "CSSLightDark.h" +#include + +namespace Web::CSS { + +Color CSSLightDark::to_color(Optional node) const +{ + if (node.has_value() && node.value().computed_values().color_scheme() == PreferredColorScheme::Dark) + return m_properties.dark->to_color(node); + + return m_properties.light->to_color(node); +} + +bool CSSLightDark::equals(CSSStyleValue const& other) const +{ + if (type() != other.type()) + return false; + auto const& other_color = other.as_color(); + if (color_type() != other_color.color_type()) + return false; + auto const& other_light_dark = verify_cast(other_color); + return m_properties == other_light_dark.m_properties; +} + +String CSSLightDark::to_string(SerializationMode mode) const +{ + // FIXME: We don't have enough information to determine the computed value here. + return MUST(String::formatted("light-dark({}, {})", m_properties.light->to_string(mode), m_properties.dark->to_string(mode))); +} + +} diff --git a/Libraries/LibWeb/CSS/StyleValues/CSSLightDark.h b/Libraries/LibWeb/CSS/StyleValues/CSSLightDark.h new file mode 100644 index 0000000000000..19d8935a55a69 --- /dev/null +++ b/Libraries/LibWeb/CSS/StyleValues/CSSLightDark.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2025, Ladybird contributors + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace Web::CSS { + +// https://drafts.csswg.org/css-color-5/#funcdef-light-dark +class CSSLightDark final : public CSSColorValue { +public: + virtual ~CSSLightDark() override = default; + + static ValueComparingNonnullRefPtr create(RefPtr light, RefPtr dark) + { + return AK::adopt_ref(*new (nothrow) CSSLightDark(move(light), move(dark))); + } + + virtual bool equals(CSSStyleValue const&) const override; + virtual Color to_color(Optional) const override; + virtual String to_string(SerializationMode) const override; + +private: + CSSLightDark(RefPtr light, RefPtr dark) + : CSSColorValue(CSSColorValue::ColorType::LightDark) + , m_properties { .light = move(light), .dark = move(dark) } + { + } + + struct Properties { + RefPtr light; + RefPtr dark; + bool operator==(Properties const&) const = default; + }; + + Properties m_properties; +}; + +} // Web::CSS diff --git a/Tests/LibWeb/Screenshot/expected/color-scheme-ref.html b/Tests/LibWeb/Screenshot/expected/color-scheme-ref.html new file mode 100644 index 0000000000000..4208be37a46fe --- /dev/null +++ b/Tests/LibWeb/Screenshot/expected/color-scheme-ref.html @@ -0,0 +1,10 @@ + + diff --git a/Tests/LibWeb/Screenshot/images/color-scheme-ref.png b/Tests/LibWeb/Screenshot/images/color-scheme-ref.png new file mode 100644 index 0000000000000000000000000000000000000000..283645ae512ffe76b01392857e6adfa8f7038ef7 GIT binary patch literal 23319 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 + +
+
+
a
+
a
+
a
+
a
+
a
+
a
+
a
+
a
+
a
+
a
+
a
+
a
+
a
+
a
+
a
+
a
+
a
+
a
+
a
+
+
+
+
+
+
+
+
+
light-dark(red,green)
+
+
+
a
+
a
+
a
+
a
+
a
+
a
+
a
+
a
+
a
+
a
+
a
+
a
+
a
+
a
+
a
+
a
+
a
+
a
+
a
+
+
+
+
+
+
+
+
+
light-dark(red,green)
+
+