diff --git a/packages/react-native/ReactCommon/react/renderer/css/CSSAngleUnit.h b/packages/react-native/ReactCommon/react/renderer/css/CSSAngleUnit.h new file mode 100644 index 00000000000..1261fcef660 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/css/CSSAngleUnit.h @@ -0,0 +1,66 @@ +/* + * 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 + +namespace facebook::react { + +/** + * Unit for the CSS type. + * https://www.w3.org/TR/css-values-4/#angles + */ +enum class CSSAngleUnit : uint8_t { + Deg, + Grad, + Rad, + Turn, +}; + +/** + * Parses a unit from a dimension token into a CSS angle unit. + */ +constexpr std::optional parseCSSAngleUnit(std::string_view unit) { + switch (fnv1a(unit)) { + case fnv1a("deg"): + return CSSAngleUnit::Deg; + case fnv1a("grad"): + return CSSAngleUnit::Grad; + case fnv1a("rad"): + return CSSAngleUnit::Rad; + case fnv1a("turn"): + return CSSAngleUnit::Turn; + default: + return std::nullopt; + } +} + +/** + * Converts a specified CSS angle to its cannonical unit (degrees) + */ +constexpr float canonicalize(float value, CSSAngleUnit unit) { + switch (unit) { + case CSSAngleUnit::Deg: + return value; + case CSSAngleUnit::Grad: + return value * 0.9f; + case CSSAngleUnit::Rad: + return value * 57.295779513f; + case CSSAngleUnit::Turn: + return value * 360.0f; + default: + return std::numeric_limits::quiet_NaN(); + } +} + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/css/CSSDeclaredStyle.h b/packages/react-native/ReactCommon/react/renderer/css/CSSDeclaredStyle.h index c52bf22e679..d3c02343bbd 100644 --- a/packages/react-native/ReactCommon/react/renderer/css/CSSDeclaredStyle.h +++ b/packages/react-native/ReactCommon/react/renderer/css/CSSDeclaredStyle.h @@ -93,7 +93,8 @@ class CSSDeclaredStyle { CSSLength, CSSNumber, CSSPercentage, - CSSRatio>)> + CSSRatio, + CSSAngle>)> value; constexpr bool operator<(const PropMapping& rhs) const { diff --git a/packages/react-native/ReactCommon/react/renderer/css/CSSParser.h b/packages/react-native/ReactCommon/react/renderer/css/CSSParser.h index c2ea7d9bc94..7724ddb34ae 100644 --- a/packages/react-native/ReactCommon/react/renderer/css/CSSParser.h +++ b/packages/react-native/ReactCommon/react/renderer/css/CSSParser.h @@ -9,6 +9,7 @@ #include +#include #include #include #include @@ -107,6 +108,12 @@ class CSSParser { return CSSValueT::length(consumeToken().numericValue(), *unit); } } + if constexpr (traits::containsType()) { + if (auto unit = parseCSSAngleUnit(peek().unit())) { + return CSSValueT::angle( + canonicalize(consumeToken().numericValue(), *unit)); + } + } return {}; } diff --git a/packages/react-native/ReactCommon/react/renderer/css/CSSValue.h b/packages/react-native/ReactCommon/react/renderer/css/CSSValue.h index fd7dfb3575c..3d18b971f79 100644 --- a/packages/react-native/ReactCommon/react/renderer/css/CSSValue.h +++ b/packages/react-native/ReactCommon/react/renderer/css/CSSValue.h @@ -28,6 +28,7 @@ enum class CSSValueType : uint8_t { Number, Percentage, Ratio, + Angle, }; /** @@ -74,6 +75,14 @@ struct CSSRatio { float denominator{}; }; +/** + * Representation of CSS data type + * https://www.w3.org/TR/css-values-4/#angles + */ +struct CSSAngle { + float degrees{}; +}; + /** * CSSValueVariant represents a CSS component value: * https://www.w3.org/TR/css-values-4/#component-types @@ -161,6 +170,12 @@ class CSSValueVariant { CSSValueType::Ratio, CSSRatio{numerator, denominator}); } + static constexpr CSSValueVariant angle(float degrees) + requires(canRepresent()) + { + return CSSValueVariant(CSSValueType::Angle, CSSAngle{degrees}); + } + constexpr CSSValueType type() const { return type_; } @@ -201,6 +216,12 @@ class CSSValueVariant { return getIf(); } + constexpr CSSAngle getAngle() const + requires(canRepresent()) + { + return getIf(); + } + constexpr operator bool() const requires(canRepresent()) { @@ -225,6 +246,8 @@ class CSSValueVariant { return getPercentage() == other.getPercentage(); case CSSValueType::Ratio: return getRatio() == other.getRatio(); + case CSSValueType::Angle: + return getAngle() == other.getAngle(); } return false; diff --git a/packages/react-native/ReactCommon/react/renderer/css/tests/CSSParserTest.cpp b/packages/react-native/ReactCommon/react/renderer/css/tests/CSSParserTest.cpp index 2ff8760e0dc..26482354da2 100644 --- a/packages/react-native/ReactCommon/react/renderer/css/tests/CSSParserTest.cpp +++ b/packages/react-native/ReactCommon/react/renderer/css/tests/CSSParserTest.cpp @@ -267,6 +267,34 @@ TEST(CSSParser, number_ratio_values) { EXPECT_EQ(degenerateRatio.getNumber().value, 0.0f); } +TEST(CSSParser, angle_values) { + auto emptyValue = parseCSSComponentValue(""); + EXPECT_EQ(emptyValue.type(), CSSValueType::CSSWideKeyword); + EXPECT_EQ(emptyValue.getCSSWideKeyword(), CSSWideKeyword::Unset); + + auto degreeValue = parseCSSComponentValue("10deg"); + EXPECT_EQ(degreeValue.type(), CSSValueType::Angle); + EXPECT_EQ(degreeValue.getAngle().degrees, 10.0f); + + auto radianValue = parseCSSComponentValue("10rad"); + EXPECT_EQ(radianValue.type(), CSSValueType::Angle); + EXPECT_NEAR(radianValue.getAngle().degrees, 572.958f, 0.001f); + + auto negativeRadianValue = + parseCSSComponentValue("-10rad"); + EXPECT_EQ(negativeRadianValue.type(), CSSValueType::Angle); + EXPECT_NEAR(negativeRadianValue.getAngle().degrees, -572.958f, 0.001f); + + auto gradianValue = + parseCSSComponentValue("10grad"); + EXPECT_EQ(gradianValue.type(), CSSValueType::Angle); + ASSERT_NEAR(gradianValue.getAngle().degrees, 9.0f, 0.001f); + + auto turnValue = parseCSSComponentValue("1turn"); + EXPECT_EQ(turnValue.type(), CSSValueType::Angle); + EXPECT_EQ(turnValue.getAngle().degrees, 360.0f); +} + TEST(CSSParser, parse_prop) { auto emptyValue = parseCSSProp(""); EXPECT_EQ(emptyValue.type(), CSSValueType::CSSWideKeyword);