From 417ff5fec92cb8e7e91581942e0431064b4b4d52 Mon Sep 17 00:00:00 2001 From: Jorge Cabiedes Acosta Date: Mon, 20 May 2024 07:29:51 -0700 Subject: [PATCH] Add to CSSParser (#44624) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/44624 The syntax for is defined as (https://www.w3.org/TR/css-color-4/#color-syntax): ``` = | currentColor | = | | | transparent = | | | | | | | | | ``` This diff implements in particular: ``` = ``` We parse over a `` to extract a 3, 4, 6 or 8 digit long hexadecimal and parse it into a CSSColor struct representation: ``` struct Color { uint8_t r // 0-255 uint8_t g // 0-255 uint8_t b // 0-255 uint8_t a // 0.0 - 1.0 } ``` Changelog: [Internal] Reviewed By: NickGerleman Differential Revision: D57287309 fbshipit-source-id: 35ce82e52fdc8fc86425e03d7883ae09b429b9e4 --- .../ReactCommon/react/renderer/css/CSSValue.h | 8 ++ .../react/renderer/css/CSSValueParser.h | 94 ++++++++++++++++++- .../react/renderer/css/CSSValueVariant.h | 15 +++ .../renderer/css/tests/CSSValueParserTest.cpp | 57 +++++++++++ 4 files changed, 173 insertions(+), 1 deletion(-) diff --git a/packages/react-native/ReactCommon/react/renderer/css/CSSValue.h b/packages/react-native/ReactCommon/react/renderer/css/CSSValue.h index ad19bcaddde..62c890c3df2 100644 --- a/packages/react-native/ReactCommon/react/renderer/css/CSSValue.h +++ b/packages/react-native/ReactCommon/react/renderer/css/CSSValue.h @@ -27,6 +27,7 @@ enum class CSSValueType : uint8_t { Percentage, Ratio, Angle, + Color, }; /** @@ -81,4 +82,11 @@ struct CSSAngle { float degrees{}; }; +struct CSSColor { + uint8_t r{}; + uint8_t g{}; + uint8_t b{}; + uint8_t a{}; +}; + } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/css/CSSValueParser.h b/packages/react-native/ReactCommon/react/renderer/css/CSSValueParser.h index 82142fde049..a525c52b9f5 100644 --- a/packages/react-native/ReactCommon/react/renderer/css/CSSValueParser.h +++ b/packages/react-native/ReactCommon/react/renderer/css/CSSValueParser.h @@ -76,7 +76,12 @@ class CSSValueParser { return *percentage; } } - + // + if constexpr (hasType()) { + if (auto colorValue = consumeColorToken(token)) { + return *colorValue; + } + } return CSSValue{}; }); // TODO: support function component values and simple blocks @@ -224,6 +229,93 @@ class CSSValueParser { value != -std::numeric_limits::infinity(); } + enum class HexColorType { + Long, + Short, + }; + + constexpr std::optional consumeColorToken( + const CSSPreservedToken& token) { + // https://www.w3.org/TR/css-color-4/#hex-color + std::string_view hexColorValue = token.stringValue(); + if (isValidHexColor(hexColorValue)) { + if (hexColorValue.length() == 3) { + return CSSValue::color( + hexToNumeric(hexColorValue.substr(0, 1), HexColorType::Short), + hexToNumeric(hexColorValue.substr(1, 1), HexColorType::Short), + hexToNumeric(hexColorValue.substr(2, 1), HexColorType::Short), + 255u); + } else if (hexColorValue.length() == 4) { + return CSSValue::color( + hexToNumeric(hexColorValue.substr(0, 1), HexColorType::Short), + hexToNumeric(hexColorValue.substr(1, 1), HexColorType::Short), + hexToNumeric(hexColorValue.substr(2, 1), HexColorType::Short), + hexToNumeric(hexColorValue.substr(3, 1), HexColorType::Short)); + } else if (hexColorValue.length() == 6) { + return CSSValue::color( + hexToNumeric(hexColorValue.substr(0, 2), HexColorType::Long), + hexToNumeric(hexColorValue.substr(2, 2), HexColorType::Long), + hexToNumeric(hexColorValue.substr(4, 2), HexColorType::Long), + 255u); + } else if (hexColorValue.length() == 8) { + return CSSValue::color( + hexToNumeric(hexColorValue.substr(0, 2), HexColorType::Long), + hexToNumeric(hexColorValue.substr(2, 2), HexColorType::Long), + hexToNumeric(hexColorValue.substr(4, 2), HexColorType::Long), + hexToNumeric(hexColorValue.substr(6, 2), HexColorType::Long)); + } + } + return {}; + } + + constexpr char toLower(char c) { + if (c >= 'A' && c <= 'Z') { + return static_cast(c + 32); + } + return c; + } + + constexpr uint8_t hexToNumeric(std::string_view hex, HexColorType hexType) { + int result = 0; + for (char c : hex) { + int value = 0; + if (c >= '0' && c <= '9') { + value = c - '0'; + } else { + value = toLower(c) - 'a' + 10; + } + result *= 16; + result += value; + } + + if (hexType == HexColorType::Short) { + return result * 16 + result; + } else { + return result; + } + } + + constexpr bool isValidHexColor(std::string_view hex) { + // The syntax of a is a token whose value consists + // of 3, 4, 6, or 8 hexadecimal digits. + if (hex.size() != 3 && hex.size() != 4 && hex.size() != 6 && + hex.size() != 8) { + return false; + } + + for (auto c : hex) { + if (!isHexDigit(c)) { + return false; + } + } + + return true; + } + + constexpr bool isHexDigit(char c) { + return (c >= '0' && c <= '9') || (toLower(c) >= 'a' && toLower(c) <= 'f'); + } + CSSSyntaxParser parser_; }; diff --git a/packages/react-native/ReactCommon/react/renderer/css/CSSValueVariant.h b/packages/react-native/ReactCommon/react/renderer/css/CSSValueVariant.h index ff0cccdec48..b063c13d4e2 100644 --- a/packages/react-native/ReactCommon/react/renderer/css/CSSValueVariant.h +++ b/packages/react-native/ReactCommon/react/renderer/css/CSSValueVariant.h @@ -109,6 +109,13 @@ class CSSValueVariant { return CSSValueVariant(CSSValueType::Angle, CSSAngle{degrees}); } + static constexpr CSSValueVariant + color(uint8_t r, uint8_t g, uint8_t b, uint8_t a) + requires(canRepresent()) + { + return CSSValueVariant(CSSValueType::Color, CSSColor{r, g, b, a}); + } + constexpr CSSValueType type() const { return type_; } @@ -155,6 +162,12 @@ class CSSValueVariant { return getIf(); } + constexpr CSSColor getColor() const + requires(canRepresent()) + { + return getIf(); + } + constexpr bool hasValue() const requires(canRepresent()) { @@ -185,6 +198,8 @@ class CSSValueVariant { return getRatio() == other.getRatio(); case CSSValueType::Angle: return getAngle() == other.getAngle(); + case CSSValueType::Color: + return getColor() == other.getColor(); } return false; diff --git a/packages/react-native/ReactCommon/react/renderer/css/tests/CSSValueParserTest.cpp b/packages/react-native/ReactCommon/react/renderer/css/tests/CSSValueParserTest.cpp index 530e11e90ef..473ed1bb6a6 100644 --- a/packages/react-native/ReactCommon/react/renderer/css/tests/CSSValueParserTest.cpp +++ b/packages/react-native/ReactCommon/react/renderer/css/tests/CSSValueParserTest.cpp @@ -320,4 +320,61 @@ TEST(CSSValueParser, parse_length_prop_constexpr) { EXPECT_EQ(pxValue.getLength().unit, CSSLengthUnit::Px); } +TEST(CSSValueParser, hex_color_values) { + auto emptyValue = parseCSSValue(""); + EXPECT_EQ(emptyValue.type(), CSSValueType::CSSWideKeyword); + EXPECT_EQ(emptyValue.getCSSWideKeyword(), CSSWideKeyword::Unset); + + auto hex3DigitColorValue = parseCSSValue("#fff"); + EXPECT_EQ(hex3DigitColorValue.type(), CSSValueType::Color); + EXPECT_EQ(hex3DigitColorValue.getColor().r, 255); + EXPECT_EQ(hex3DigitColorValue.getColor().g, 255); + EXPECT_EQ(hex3DigitColorValue.getColor().b, 255); + EXPECT_EQ(hex3DigitColorValue.getColor().a, 255); + + auto hex4DigitColorValue = parseCSSValue("#ffff"); + EXPECT_EQ(hex4DigitColorValue.type(), CSSValueType::Color); + EXPECT_EQ(hex4DigitColorValue.getColor().r, 255); + EXPECT_EQ(hex4DigitColorValue.getColor().g, 255); + EXPECT_EQ(hex4DigitColorValue.getColor().b, 255); + EXPECT_EQ(hex4DigitColorValue.getColor().a, 255); + + auto hex6DigitColorValue = parseCSSValue("#ffffff"); + EXPECT_EQ(hex6DigitColorValue.type(), CSSValueType::Color); + EXPECT_EQ(hex6DigitColorValue.getColor().r, 255); + EXPECT_EQ(hex6DigitColorValue.getColor().g, 255); + EXPECT_EQ(hex6DigitColorValue.getColor().b, 255); + EXPECT_EQ(hex6DigitColorValue.getColor().a, 255); + + auto hex8DigitColorValue = + parseCSSValue("#ffffffff"); + EXPECT_EQ(hex8DigitColorValue.type(), CSSValueType::Color); + EXPECT_EQ(hex8DigitColorValue.getColor().r, 255); + EXPECT_EQ(hex8DigitColorValue.getColor().g, 255); + EXPECT_EQ(hex8DigitColorValue.getColor().b, 255); + EXPECT_EQ(hex8DigitColorValue.getColor().a, 255); + + auto hexMixedCaseColorValue = + parseCSSValue("#FFCc99"); + EXPECT_EQ(hexMixedCaseColorValue.type(), CSSValueType::Color); + EXPECT_EQ(hexMixedCaseColorValue.getColor().r, 255); + EXPECT_EQ(hexMixedCaseColorValue.getColor().g, 204); + EXPECT_EQ(hexMixedCaseColorValue.getColor().b, 153); + EXPECT_EQ(hexMixedCaseColorValue.getColor().a, 255); + + auto hexDigitOnlyColorValue = parseCSSValue("#369"); + EXPECT_EQ(hexDigitOnlyColorValue.type(), CSSValueType::Color); + EXPECT_EQ(hexDigitOnlyColorValue.getColor().r, 51); + EXPECT_EQ(hexDigitOnlyColorValue.getColor().g, 102); + EXPECT_EQ(hexDigitOnlyColorValue.getColor().b, 153); + EXPECT_EQ(hexDigitOnlyColorValue.getColor().a, 255); + + auto hexAlphaTestValue = parseCSSValue("#FFFFFFCC"); + EXPECT_EQ(hexAlphaTestValue.type(), CSSValueType::Color); + EXPECT_EQ(hexAlphaTestValue.getColor().r, 255); + EXPECT_EQ(hexAlphaTestValue.getColor().g, 255); + EXPECT_EQ(hexAlphaTestValue.getColor().b, 255); + EXPECT_EQ(hexAlphaTestValue.getColor().a, 204); +} + } // namespace facebook::react