From 26c2067bc74c75a39010e9600896280a9a4d6b2a Mon Sep 17 00:00:00 2001 From: Nick Gerleman Date: Wed, 29 Jan 2025 12:08:39 -0800 Subject: [PATCH] Support parsing (#48991) Summary: This adds support for parsing the `` data type. In combination with `CSSCommaSeparatedList`, we can now parse box shadow expressions. Changelog: [Internal] Reviewed By: lenaic Differential Revision: D68744811 --- .../react/renderer/css/CSSShadow.h | 152 ++++++++ .../renderer/css/tests/CSSShadowTest.cpp | 339 ++++++++++++++++++ 2 files changed, 491 insertions(+) create mode 100644 packages/react-native/ReactCommon/react/renderer/css/CSSShadow.h create mode 100644 packages/react-native/ReactCommon/react/renderer/css/tests/CSSShadowTest.cpp diff --git a/packages/react-native/ReactCommon/react/renderer/css/CSSShadow.h b/packages/react-native/ReactCommon/react/renderer/css/CSSShadow.h new file mode 100644 index 00000000000000..3c445f0fb498e4 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/css/CSSShadow.h @@ -0,0 +1,152 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace facebook::react { + +/** + * Representation of CSS data type + * https://drafts.csswg.org/css-backgrounds/#typedef-shadow + */ +struct CSSShadow { + CSSLength offsetX{}; + CSSLength offsetY{}; + CSSLength blurRadius{}; + CSSLength spreadDistance{}; + CSSColor color{CSSColor::black()}; + bool inset{false}; + + constexpr bool operator==(const CSSShadow& rhs) const = default; +}; + +/** + * Represents a keyword for an inset shadow. + */ +enum class CSSInsetShadowKeyword : std::underlying_type_t { + Inset = to_underlying(CSSKeyword::Inset), +}; + +static_assert(CSSDataType); + +template <> +struct CSSDataTypeParser { + static constexpr auto consume(CSSSyntaxParser& parser) + -> std::optional { + std::optional color{}; + bool inset{false}; + std::optional> + lengths{}; + + for (auto nextValue = + parseNextCSSValue( + parser); + !std::holds_alternative(nextValue); + nextValue = + parseNextCSSValue( + parser, CSSDelimiter::Whitespace)) { + if (std::holds_alternative(nextValue)) { + if (lengths.has_value()) { + return {}; + } + lengths = parseRestLengths(std::get(nextValue), parser); + if (!lengths.has_value()) { + return {}; + } + continue; + } + + if (std::holds_alternative(nextValue)) { + if (color.has_value()) { + return {}; + } + color = std::get(nextValue); + continue; + } + + if (std::holds_alternative(nextValue)) { + if (inset) { + return {}; + } + inset = true; + continue; + } + } + + if (!lengths.has_value()) { + return {}; + } + + return CSSShadow{ + .offsetX = std::get<0>(*lengths), + .offsetY = std::get<1>(*lengths), + .blurRadius = std::get<2>(*lengths), + .spreadDistance = std::get<3>(*lengths), + .color = color.value_or(CSSColor::black()), + .inset = inset, + }; + } + + private: + static constexpr auto parseRestLengths( + CSSLength offsetX, + CSSSyntaxParser& parser) + -> std::optional> { + auto offsetY = + parseNextCSSValue(parser, CSSDelimiter::Whitespace); + if (std::holds_alternative(offsetY)) { + return {}; + } + + auto blurRadius = + parseNextCSSValue(parser, CSSDelimiter::Whitespace); + if (std::holds_alternative(blurRadius)) { + return std::make_tuple( + offsetX, std::get(offsetY), CSSLength{}, CSSLength{}); + } + if (std::get(blurRadius).value < 0) { + return {}; + } + + auto spreadDistance = + parseNextCSSValue(parser, CSSDelimiter::Whitespace); + if (std::holds_alternative(spreadDistance)) { + return std::make_tuple( + offsetX, + std::get(offsetY), + std::get(blurRadius), + CSSLength{}); + } + + return std::make_tuple( + offsetX, + std::get(offsetY), + std::get(blurRadius), + std::get(spreadDistance)); + } +}; + +static_assert(CSSDataType); + +/** + * Represents a comma separated list of at least one + * # + */ +using CSSShadowList = CSSCommaSeparatedList; + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/css/tests/CSSShadowTest.cpp b/packages/react-native/ReactCommon/react/renderer/css/tests/CSSShadowTest.cpp new file mode 100644 index 00000000000000..0de2f13d187145 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/css/tests/CSSShadowTest.cpp @@ -0,0 +1,339 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include +#include +#include +#include + +namespace facebook::react { + +TEST(CSSShadow, basic) { + auto value = parseCSSProperty("10px 5px"); + EXPECT_TRUE(std::holds_alternative(value)); + auto& shadow = std::get(value); + + EXPECT_EQ(shadow.offsetX.value, 10.0f); + EXPECT_EQ(shadow.offsetX.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadow.offsetY.value, 5.0f); + EXPECT_EQ(shadow.offsetY.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadow.blurRadius.value, 0.0f); + EXPECT_EQ(shadow.blurRadius.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadow.spreadDistance.value, 0.0f); + EXPECT_EQ(shadow.spreadDistance.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadow.color, CSSColor::black()); + EXPECT_FALSE(shadow.inset); +} + +TEST(CSSShadow, rem_unit) { + auto value = parseCSSProperty("10px 5rem"); + EXPECT_TRUE(std::holds_alternative(value)); + auto& shadow = std::get(value); + + EXPECT_EQ(shadow.offsetX.value, 10.0f); + EXPECT_EQ(shadow.offsetX.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadow.offsetY.value, 5.0f); + EXPECT_EQ(shadow.offsetY.unit, CSSLengthUnit::Rem); + EXPECT_EQ(shadow.blurRadius.value, 0.0f); + EXPECT_EQ(shadow.blurRadius.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadow.spreadDistance.value, 0.0f); + EXPECT_EQ(shadow.spreadDistance.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadow.color, CSSColor::black()); + EXPECT_FALSE(shadow.inset); +} + +TEST(CSSShadow, unitless_zero_length) { + auto value = parseCSSProperty("10px 0"); + EXPECT_TRUE(std::holds_alternative(value)); + auto& shadow = std::get(value); + + EXPECT_EQ(shadow.offsetX.value, 10.0f); + EXPECT_EQ(shadow.offsetX.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadow.offsetY.value, 0.0f); + EXPECT_EQ(shadow.offsetY.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadow.blurRadius.value, 0.0f); + EXPECT_EQ(shadow.blurRadius.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadow.spreadDistance.value, 0.0f); + EXPECT_EQ(shadow.spreadDistance.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadow.color, CSSColor::black()); + EXPECT_FALSE(shadow.inset); +} + +TEST(CSSShadow, multiple_whitespace) { + auto value = parseCSSProperty("10px 5px"); + EXPECT_TRUE(std::holds_alternative(value)); + auto& shadow = std::get(value); + + EXPECT_EQ(shadow.offsetX.value, 10.0f); + EXPECT_EQ(shadow.offsetX.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadow.offsetY.value, 5.0f); + EXPECT_EQ(shadow.offsetY.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadow.blurRadius.value, 0.0f); + EXPECT_EQ(shadow.blurRadius.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadow.spreadDistance.value, 0.0f); + EXPECT_EQ(shadow.spreadDistance.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadow.color, CSSColor::black()); + EXPECT_FALSE(shadow.inset); +} + +TEST(CSSShadow, trailing_color) { + auto value = parseCSSProperty("10px 5px red"); + EXPECT_TRUE(std::holds_alternative(value)); + auto& shadow = std::get(value); + + EXPECT_EQ(shadow.offsetX.value, 10.0f); + EXPECT_EQ(shadow.offsetX.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadow.offsetY.value, 5.0f); + EXPECT_EQ(shadow.offsetY.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadow.blurRadius.value, 0.0f); + EXPECT_EQ(shadow.blurRadius.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadow.spreadDistance.value, 0.0f); + EXPECT_EQ(shadow.spreadDistance.unit, CSSLengthUnit::Px); + + CSSColor red{255u, 0u, 0u, 255u}; + EXPECT_EQ(shadow.color, red); + EXPECT_FALSE(shadow.inset); +} + +TEST(CSSShadow, leading_color) { + auto value = parseCSSProperty("red 10px 5px"); + EXPECT_TRUE(std::holds_alternative(value)); + auto& shadow = std::get(value); + + EXPECT_EQ(shadow.offsetX.value, 10.0f); + EXPECT_EQ(shadow.offsetX.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadow.offsetY.value, 5.0f); + EXPECT_EQ(shadow.offsetY.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadow.blurRadius.value, 0.0f); + EXPECT_EQ(shadow.blurRadius.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadow.spreadDistance.value, 0.0f); + EXPECT_EQ(shadow.spreadDistance.unit, CSSLengthUnit::Px); + + CSSColor red{255u, 0u, 0u, 255u}; + EXPECT_EQ(shadow.color, red); + EXPECT_FALSE(shadow.inset); +} + +TEST(CSSShadow, color_function) { + auto value = parseCSSProperty("10px 5px rgba(255, 0, 0, 0.5)"); + EXPECT_TRUE(std::holds_alternative(value)); + auto& shadow = std::get(value); + + EXPECT_EQ(shadow.offsetX.value, 10.0f); + EXPECT_EQ(shadow.offsetX.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadow.offsetY.value, 5.0f); + EXPECT_EQ(shadow.offsetY.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadow.blurRadius.value, 0.0f); + EXPECT_EQ(shadow.blurRadius.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadow.spreadDistance.value, 0.0f); + EXPECT_EQ(shadow.spreadDistance.unit, CSSLengthUnit::Px); + + CSSColor red{255u, 0u, 0u, 128u}; + EXPECT_EQ(shadow.color, red); + EXPECT_FALSE(shadow.inset); +} + +TEST(CSSShadow, blur_radius) { + auto value = parseCSSProperty("10px 5px 2px"); + EXPECT_TRUE(std::holds_alternative(value)); + auto& shadow = std::get(value); + + EXPECT_EQ(shadow.offsetX.value, 10.0f); + EXPECT_EQ(shadow.offsetX.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadow.offsetY.value, 5.0f); + EXPECT_EQ(shadow.offsetY.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadow.blurRadius.value, 2.0f); + EXPECT_EQ(shadow.blurRadius.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadow.spreadDistance.value, 0.0f); + EXPECT_EQ(shadow.spreadDistance.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadow.color, CSSColor::black()); + EXPECT_FALSE(shadow.inset); +} + +TEST(CSSShadow, spread_distance) { + auto value = parseCSSProperty("10px 5px 2px 3px"); + EXPECT_TRUE(std::holds_alternative(value)); + auto& shadow = std::get(value); + + EXPECT_EQ(shadow.offsetX.value, 10.0f); + EXPECT_EQ(shadow.offsetX.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadow.offsetY.value, 5.0f); + EXPECT_EQ(shadow.offsetY.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadow.blurRadius.value, 2.0f); + EXPECT_EQ(shadow.blurRadius.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadow.spreadDistance.value, 3.0f); + EXPECT_EQ(shadow.spreadDistance.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadow.color, CSSColor::black()); + EXPECT_FALSE(shadow.inset); +} + +TEST(CSSShadow, inset) { + auto value = parseCSSProperty("5px 2px inset"); + EXPECT_TRUE(std::holds_alternative(value)); + auto& shadow = std::get(value); + + EXPECT_EQ(shadow.offsetX.value, 5.0f); + EXPECT_EQ(shadow.offsetX.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadow.offsetY.value, 2.0f); + EXPECT_EQ(shadow.offsetY.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadow.blurRadius.value, 0.0f); + EXPECT_EQ(shadow.blurRadius.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadow.spreadDistance.value, 0.0f); + EXPECT_EQ(shadow.spreadDistance.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadow.color, CSSColor::black()); + EXPECT_TRUE(shadow.inset); +} + +TEST(CSShadow, color_length_inset) { + auto value = parseCSSProperty("red 10px 10px inset"); + EXPECT_TRUE(std::holds_alternative(value)); + auto& shadow = std::get(value); + + EXPECT_EQ(shadow.offsetX.value, 10.0f); + EXPECT_EQ(shadow.offsetX.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadow.offsetY.value, 10.0f); + EXPECT_EQ(shadow.offsetY.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadow.blurRadius.value, 0.0f); + EXPECT_EQ(shadow.blurRadius.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadow.spreadDistance.value, 0.0f); + EXPECT_EQ(shadow.spreadDistance.unit, CSSLengthUnit::Px); + + CSSColor red{255u, 0u, 0u, 255u}; + EXPECT_EQ(shadow.color, red); + EXPECT_TRUE(shadow.inset); +} + +TEST(CSSShadow, multiple_shadows) { + auto value = parseCSSProperty( + "10px 5px red, 5px 12px inset, inset 10px 45px 13px red"); + EXPECT_TRUE(std::holds_alternative(value)); + auto& shadows = std::get(value); + + EXPECT_EQ(shadows.size(), 3); + + EXPECT_EQ(shadows[0].offsetX.value, 10.0f); + EXPECT_EQ(shadows[0].offsetX.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadows[0].offsetY.value, 5.0f); + EXPECT_EQ(shadows[0].offsetY.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadows[0].blurRadius.value, 0.0f); + EXPECT_EQ(shadows[0].blurRadius.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadows[0].spreadDistance.value, 0.0f); + EXPECT_EQ(shadows[0].spreadDistance.unit, CSSLengthUnit::Px); + + CSSColor red{255u, 0u, 0u, 255u}; + EXPECT_EQ(shadows[0].color, red); + EXPECT_FALSE(shadows[0].inset); + + EXPECT_EQ(shadows[1].offsetX.value, 5.0f); + EXPECT_EQ(shadows[1].offsetX.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadows[1].offsetY.value, 12.0f); + EXPECT_EQ(shadows[1].offsetY.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadows[1].blurRadius.value, 0.0f); + EXPECT_EQ(shadows[1].blurRadius.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadows[1].spreadDistance.value, 0.0f); + EXPECT_EQ(shadows[1].spreadDistance.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadows[1].color, CSSColor::black()); + EXPECT_TRUE(shadows[1].inset); + + EXPECT_EQ(shadows[2].offsetX.value, 10.0f); + EXPECT_EQ(shadows[2].offsetX.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadows[2].offsetY.value, 45.0f); + EXPECT_EQ(shadows[2].offsetY.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadows[2].blurRadius.value, 13.0f); + EXPECT_EQ(shadows[2].blurRadius.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadows[2].spreadDistance.value, 0.0f); + EXPECT_EQ(shadows[2].spreadDistance.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadows[2].color, red); + EXPECT_TRUE(shadows[2].inset); +} + +TEST(CSSShadow, multiple_shadows_with_new_line) { + auto value = parseCSSProperty( + "10px 5px red, \n5px 12px inset,\n inset 10px 45px 13px red"); + EXPECT_TRUE(std::holds_alternative(value)); + auto& shadows = std::get(value); + + EXPECT_EQ(shadows.size(), 3); + + EXPECT_EQ(shadows[0].offsetX.value, 10.0f); + EXPECT_EQ(shadows[0].offsetX.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadows[0].offsetY.value, 5.0f); + EXPECT_EQ(shadows[0].offsetY.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadows[0].blurRadius.value, 0.0f); + EXPECT_EQ(shadows[0].blurRadius.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadows[0].spreadDistance.value, 0.0f); + EXPECT_EQ(shadows[0].spreadDistance.unit, CSSLengthUnit::Px); + + CSSColor red{255u, 0u, 0u, 255u}; + EXPECT_EQ(shadows[0].color, red); + EXPECT_FALSE(shadows[0].inset); + + EXPECT_EQ(shadows[1].offsetX.value, 5.0f); + EXPECT_EQ(shadows[1].offsetX.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadows[1].offsetY.value, 12.0f); + EXPECT_EQ(shadows[1].offsetY.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadows[1].blurRadius.value, 0.0f); + EXPECT_EQ(shadows[1].blurRadius.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadows[1].spreadDistance.value, 0.0f); + EXPECT_EQ(shadows[1].spreadDistance.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadows[1].color, CSSColor::black()); + EXPECT_TRUE(shadows[1].inset); + + EXPECT_EQ(shadows[2].offsetX.value, 10.0f); + EXPECT_EQ(shadows[2].offsetX.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadows[2].offsetY.value, 45.0f); + EXPECT_EQ(shadows[2].offsetY.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadows[2].blurRadius.value, 13.0f); + EXPECT_EQ(shadows[2].blurRadius.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadows[2].spreadDistance.value, 0.0f); + EXPECT_EQ(shadows[2].spreadDistance.unit, CSSLengthUnit::Px); + EXPECT_EQ(shadows[2].color, red); + EXPECT_TRUE(shadows[2].inset); +} + +TEST(CSSShadow, invalid_units) { + auto value = parseCSSProperty("red 10em 5$ 2| 3rp"); + EXPECT_TRUE(std::holds_alternative(value)); +} + +TEST(CSSShadow, too_many_lengths) { + auto value = parseCSSProperty("10px 5px 2px 3px 10px 10px"); + EXPECT_TRUE(std::holds_alternative(value)); +} + +TEST(CSSShadow, too_many_lengths_as_part_of_multiple) { + auto value = + parseCSSProperty("10px 5px 2px 3px 10px 10px, 10px 5px"); + EXPECT_TRUE(std::holds_alternative(value)); +} + +TEST(CSSShadow, inset_between_lengths) { + auto value = parseCSSProperty("10px inset 5px"); + EXPECT_TRUE(std::holds_alternative(value)); +} + +TEST(CSSShadow, color_between_lengths) { + auto value = parseCSSProperty("10px blue 5px"); + EXPECT_TRUE(std::holds_alternative(value)); +} + +TEST(CSSShadow, invalid_keyword) { + auto value = parseCSSProperty("10px 5px outset"); + EXPECT_TRUE(std::holds_alternative(value)); +} + +TEST(CSSShadow, negative_blur) { + auto value = parseCSSProperty("red 5px 2px -3px"); + EXPECT_TRUE(std::holds_alternative(value)); +} + +TEST(CSSShadow, missing_unit) { + auto value = parseCSSProperty("10px 5"); + EXPECT_TRUE(std::holds_alternative(value)); +} + +} // namespace facebook::react