Reduce transform parsing duplication (#49279)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49279

I ended up using this same pattern for filter parsing where the logic betweeen functions is very similar. Let's deduplicate the logic for transform parsing a bit. This also separates `rotate()` and `rotateZ()` types, to be handled the same at a different layer.

Changelog: [Internal]

Reviewed By: joevilches

Differential Revision: D69326443

fbshipit-source-id: 9bf910c6d4e07748ff032433167576f9d58cd8d6
This commit is contained in:
Nick Gerleman
2025-02-07 18:14:42 -08:00
committed by Facebook GitHub Bot
parent 722f5ba786
commit f40d69f06d
3 changed files with 173 additions and 195 deletions
@@ -8,8 +8,8 @@
#pragma once
#include <array>
#include <memory>
#include <optional>
#include <variant>
#include <react/renderer/css/CSSAngle.h>
#include <react/renderer/css/CSSCompoundDataType.h>
@@ -20,10 +20,90 @@
#include <react/renderer/css/CSSNumber.h>
#include <react/renderer/css/CSSValueParser.h>
#include <react/renderer/css/CSSZero.h>
#include <react/utils/TemplateStringLiteral.h>
#include <react/utils/iequals.h>
namespace facebook::react {
namespace detail {
template <
typename DataT,
TemplateStringLiteral Name,
CSSDataType... AllowedComponentsT>
requires(std::is_same_v<
decltype(DataT::value),
std::variant<AllowedComponentsT...>>)
struct CSSVariantComponentTransformParser {
static constexpr auto consumeFunctionBlock(
const CSSFunctionBlock& func,
CSSSyntaxParser& parser) -> std::optional<DataT> {
if (!iequals(func.name, Name)) {
return {};
}
auto val = parseNextCSSValue<AllowedComponentsT...>(parser);
return std::visit(
[&](auto&& v) -> std::optional<DataT> {
if constexpr (std::is_same_v<
std::remove_cvref_t<decltype(v)>,
std::monostate>) {
return {};
} else {
return DataT{.value = std::forward<decltype(v)>(v)};
}
},
val);
}
};
template <typename DataT, TemplateStringLiteral Name>
requires(std::is_same_v<decltype(DataT::value), float>)
struct CSSNumberPercentTransformParser {
static constexpr auto consumeFunctionBlock(
const CSSFunctionBlock& func,
CSSSyntaxParser& parser) -> std::optional<DataT> {
if (!iequals(func.name, Name)) {
return {};
}
auto val = parseNextCSSValue<CSSNumber, CSSPercentage>(parser);
if (std::holds_alternative<std::monostate>(val)) {
return {};
}
return DataT{
.value = std::holds_alternative<CSSNumber>(val)
? std::get<CSSNumber>(val).value
: std::get<CSSPercentage>(val).value / 100.0f};
}
};
template <typename DataT, TemplateStringLiteral Name>
requires(std::is_same_v<decltype(DataT::degrees), float>)
struct CSSAngleTransformParser {
static constexpr auto consumeFunctionBlock(
const CSSFunctionBlock& func,
CSSSyntaxParser& parser) -> std::optional<DataT> {
if (!iequals(func.name, Name)) {
return {};
}
auto value = parseNextCSSValue<CSSAngle, CSSZero>(parser);
if (std::holds_alternative<std::monostate>(value)) {
return {};
}
return DataT{
.degrees = std::holds_alternative<CSSAngle>(value)
? std::get<CSSAngle>(value).degrees
: 0.0f,
};
}
};
} // namespace detail
/**
* Representation of matrix() transform function.
*/
@@ -164,26 +244,12 @@ struct CSSTranslateX {
};
template <>
struct CSSDataTypeParser<CSSTranslateX> {
static constexpr auto consumeFunctionBlock(
const CSSFunctionBlock& func,
CSSSyntaxParser& parser) -> std::optional<CSSTranslateX> {
if (!iequals(func.name, "translateX")) {
return {};
}
auto val = parseNextCSSValue<CSSLengthPercentage>(parser);
if (std::holds_alternative<std::monostate>(val)) {
return {};
}
return CSSTranslateX{
.value = std::holds_alternative<CSSLength>(val)
? std::variant<CSSLength, CSSPercentage>{std::get<CSSLength>(val)}
: std::variant<CSSLength, CSSPercentage>{
std::get<CSSPercentage>(val)}};
}
};
struct CSSDataTypeParser<CSSTranslateX>
: public detail::CSSVariantComponentTransformParser<
CSSTranslateX,
"translateX",
CSSLength,
CSSPercentage> {};
static_assert(CSSDataType<CSSTranslateX>);
@@ -197,26 +263,12 @@ struct CSSTranslateY {
};
template <>
struct CSSDataTypeParser<CSSTranslateY> {
static constexpr auto consumeFunctionBlock(
const CSSFunctionBlock& func,
CSSSyntaxParser& parser) -> std::optional<CSSTranslateY> {
if (!iequals(func.name, "translateY")) {
return {};
}
auto val = parseNextCSSValue<CSSLengthPercentage>(parser);
if (std::holds_alternative<std::monostate>(val)) {
return {};
}
return CSSTranslateY{
.value = std::holds_alternative<CSSLength>(val)
? std::variant<CSSLength, CSSPercentage>{std::get<CSSLength>(val)}
: std::variant<CSSLength, CSSPercentage>{
std::get<CSSPercentage>(val)}};
}
};
struct CSSDataTypeParser<CSSTranslateY>
: public detail::CSSVariantComponentTransformParser<
CSSTranslateY,
"translateY",
CSSLength,
CSSPercentage> {};
static_assert(CSSDataType<CSSTranslateY>);
@@ -274,25 +326,8 @@ struct CSSScaleX {
};
template <>
struct CSSDataTypeParser<CSSScaleX> {
static constexpr auto consumeFunctionBlock(
const CSSFunctionBlock& func,
CSSSyntaxParser& parser) -> std::optional<CSSScaleX> {
if (!iequals(func.name, "scaleX")) {
return {};
}
auto x = parseNextCSSValue<CSSNumber, CSSPercentage>(parser);
if (std::holds_alternative<std::monostate>(x)) {
return {};
}
return CSSScaleX{
.value = std::holds_alternative<CSSNumber>(x)
? std::get<CSSNumber>(x).value
: std::get<CSSPercentage>(x).value / 100.0f};
}
};
struct CSSDataTypeParser<CSSScaleX>
: public detail::CSSNumberPercentTransformParser<CSSScaleX, "scaleX"> {};
static_assert(CSSDataType<CSSScaleX>);
@@ -306,61 +341,25 @@ struct CSSScaleY {
};
template <>
struct CSSDataTypeParser<CSSScaleY> {
static constexpr auto consumeFunctionBlock(
const CSSFunctionBlock& func,
CSSSyntaxParser& parser) -> std::optional<CSSScaleY> {
if (!iequals(func.name, "scaleY")) {
return {};
}
auto y = parseNextCSSValue<CSSNumber, CSSPercentage>(parser);
if (std::holds_alternative<std::monostate>(y)) {
return {};
}
return CSSScaleY{
.value = std::holds_alternative<CSSNumber>(y)
? std::get<CSSNumber>(y).value
: std::get<CSSPercentage>(y).value / 100.0f};
}
};
struct CSSDataTypeParser<CSSScaleY>
: public detail::CSSNumberPercentTransformParser<CSSScaleY, "scaleY"> {};
static_assert(CSSDataType<CSSScaleY>);
/**
* Representation of rotate() or rotateZ() transform function.
* Representation of rotate() transform function.
*/
struct CSSRotateZ {
struct CSSRotate {
float degrees{};
constexpr bool operator==(const CSSRotateZ& rhs) const = default;
constexpr bool operator==(const CSSRotate& rhs) const = default;
};
template <>
struct CSSDataTypeParser<CSSRotateZ> {
static constexpr auto consumeFunctionBlock(
const CSSFunctionBlock& func,
CSSSyntaxParser& parser) -> std::optional<CSSRotateZ> {
if (!(iequals(func.name, "rotate") || iequals(func.name, "rotateZ"))) {
return {};
}
struct CSSDataTypeParser<CSSRotate>
: public detail::CSSAngleTransformParser<CSSRotate, "rotate"> {};
auto value = parseNextCSSValue<CSSAngle, CSSZero>(parser);
if (std::holds_alternative<std::monostate>(value)) {
return {};
}
return CSSRotateZ{
.degrees = std::holds_alternative<CSSAngle>(value)
? std::get<CSSAngle>(value).degrees
: 0.0f,
};
}
};
static_assert(CSSDataType<CSSRotateZ>);
static_assert(CSSDataType<CSSRotate>);
/**
* Representation of rotateX() transform function.
@@ -372,26 +371,8 @@ struct CSSRotateX {
};
template <>
struct CSSDataTypeParser<CSSRotateX> {
static constexpr auto consumeFunctionBlock(
const CSSFunctionBlock& func,
CSSSyntaxParser& parser) -> std::optional<CSSRotateX> {
if (!iequals(func.name, "rotateX")) {
return {};
}
auto value = parseNextCSSValue<CSSAngle, CSSZero>(parser);
if (std::holds_alternative<std::monostate>(value)) {
return {};
}
return CSSRotateX{
.degrees = std::holds_alternative<CSSAngle>(value)
? std::get<CSSAngle>(value).degrees
: 0.0f,
};
}
};
struct CSSDataTypeParser<CSSRotateX>
: public detail::CSSAngleTransformParser<CSSRotateX, "rotateX"> {};
static_assert(CSSDataType<CSSRotateX>);
@@ -405,29 +386,26 @@ struct CSSRotateY {
};
template <>
struct CSSDataTypeParser<CSSRotateY> {
static constexpr auto consumeFunctionBlock(
const CSSFunctionBlock& func,
CSSSyntaxParser& parser) -> std::optional<CSSRotateY> {
if (!iequals(func.name, "rotateY")) {
return {};
}
auto value = parseNextCSSValue<CSSAngle, CSSZero>(parser);
if (std::holds_alternative<std::monostate>(value)) {
return {};
}
return CSSRotateY{
.degrees = std::holds_alternative<CSSAngle>(value)
? std::get<CSSAngle>(value).degrees
: 0.0f,
};
}
};
struct CSSDataTypeParser<CSSRotateY>
: public detail::CSSAngleTransformParser<CSSRotateY, "rotateY"> {};
static_assert(CSSDataType<CSSRotateY>);
/**
* Representation of rotateZ() transform function.
*/
struct CSSRotateZ {
float degrees{};
constexpr bool operator==(const CSSRotateZ& rhs) const = default;
};
template <>
struct CSSDataTypeParser<CSSRotateZ>
: public detail::CSSAngleTransformParser<CSSRotateZ, "rotateZ"> {};
static_assert(CSSDataType<CSSRotateZ>);
/**
* Representation of skewX() transform function.
*/
@@ -438,26 +416,8 @@ struct CSSSkewX {
};
template <>
struct CSSDataTypeParser<CSSSkewX> {
static constexpr auto consumeFunctionBlock(
const CSSFunctionBlock& func,
CSSSyntaxParser& parser) -> std::optional<CSSSkewX> {
if (!iequals(func.name, "skewX")) {
return {};
}
auto value = parseNextCSSValue<CSSAngle, CSSZero>(parser);
if (std::holds_alternative<std::monostate>(value)) {
return {};
}
return CSSSkewX{
.degrees = std::holds_alternative<CSSAngle>(value)
? std::get<CSSAngle>(value).degrees
: 0.0f,
};
}
};
struct CSSDataTypeParser<CSSSkewX>
: public detail::CSSAngleTransformParser<CSSSkewX, "skewX"> {};
static_assert(CSSDataType<CSSSkewX>);
@@ -471,26 +431,8 @@ struct CSSSkewY {
};
template <>
struct CSSDataTypeParser<CSSSkewY> {
static constexpr auto consumeFunctionBlock(
const CSSFunctionBlock& func,
CSSSyntaxParser& parser) -> std::optional<CSSSkewY> {
if (!iequals(func.name, "skewY")) {
return {};
}
auto value = parseNextCSSValue<CSSAngle, CSSZero>(parser);
if (std::holds_alternative<std::monostate>(value)) {
return {};
}
return CSSSkewY{
.degrees = std::holds_alternative<CSSAngle>(value)
? std::get<CSSAngle>(value).degrees
: 0.0f,
};
}
};
struct CSSDataTypeParser<CSSSkewY>
: public detail::CSSAngleTransformParser<CSSSkewY, "skewY"> {};
static_assert(CSSDataType<CSSSkewY>);
@@ -539,9 +481,10 @@ using CSSTransformFunction = CSSCompoundDataType<
CSSScale,
CSSScaleX,
CSSScaleY,
CSSRotateZ, // same as rotate()
CSSRotate,
CSSRotateX,
CSSRotateY,
CSSRotateZ,
CSSSkewX,
CSSSkewY,
CSSPerspective>;
@@ -370,24 +370,24 @@ TEST(CSSTransform, scale_y_length) {
TEST(CSSTransform, rotate_basic) {
auto val = parseCSSProperty<CSSTransformFunction>("rotate(90deg)");
EXPECT_TRUE(std::holds_alternative<CSSRotateZ>(val));
auto& rotate = std::get<CSSRotateZ>(val);
EXPECT_TRUE(std::holds_alternative<CSSRotate>(val));
auto& rotate = std::get<CSSRotate>(val);
EXPECT_EQ(rotate.degrees, 90.0f);
}
TEST(CSSTransform, rotate_turn) {
auto val = parseCSSProperty<CSSTransformFunction>("rotate(1turn)");
EXPECT_TRUE(std::holds_alternative<CSSRotateZ>(val));
auto& rotate = std::get<CSSRotateZ>(val);
EXPECT_TRUE(std::holds_alternative<CSSRotate>(val));
auto& rotate = std::get<CSSRotate>(val);
EXPECT_EQ(rotate.degrees, 360.0f);
}
TEST(CSSTransform, rotate_zero) {
auto val = parseCSSProperty<CSSTransformFunction>("rotate(0)");
EXPECT_TRUE(std::holds_alternative<CSSRotateZ>(val));
auto& rotate = std::get<CSSRotateZ>(val);
EXPECT_TRUE(std::holds_alternative<CSSRotate>(val));
auto& rotate = std::get<CSSRotate>(val);
EXPECT_EQ(rotate.degrees, 0.0f);
}
@@ -402,8 +402,8 @@ TEST(CSSTransform, rotate_z) {
TEST(CSSTransform, rotate_funky) {
auto val = parseCSSProperty<CSSTransformFunction>("roTate(90deg)");
EXPECT_TRUE(std::holds_alternative<CSSRotateZ>(val));
auto& rotate = std::get<CSSRotateZ>(val);
EXPECT_TRUE(std::holds_alternative<CSSRotate>(val));
auto& rotate = std::get<CSSRotate>(val);
EXPECT_EQ(rotate.degrees, 90.0f);
}
@@ -667,7 +667,7 @@ TEST(CSSTransform, transform_list) {
EXPECT_EQ(transformList.size(), 3);
EXPECT_TRUE(std::holds_alternative<CSSTranslate>(transformList[0]));
EXPECT_TRUE(std::holds_alternative<CSSRotateZ>(transformList[1]));
EXPECT_TRUE(std::holds_alternative<CSSRotate>(transformList[1]));
EXPECT_TRUE(std::holds_alternative<CSSScale>(transformList[2]));
auto& translate = std::get<CSSTranslate>(transformList[0]);
@@ -679,7 +679,7 @@ TEST(CSSTransform, transform_list) {
EXPECT_EQ(std::get<CSSLength>(translate.x).unit, CSSLengthUnit::Px);
EXPECT_EQ(std::get<CSSLength>(translate.y).unit, CSSLengthUnit::Px);
auto& rotate = std::get<CSSRotateZ>(transformList[1]);
auto& rotate = std::get<CSSRotate>(transformList[1]);
EXPECT_EQ(rotate.degrees, 90.0f);
auto& scale = std::get<CSSScale>(transformList[2]);
@@ -0,0 +1,35 @@
/*
* 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 <algorithm>
#include <array>
#include <string_view>
namespace facebook::react {
/**
* Helper to allow passing string literals as template parameters.
* https://ctrpeach.io/posts/cpp20-string-literal-template-parameters/
*/
template <size_t N>
struct TemplateStringLiteral {
/* implicit */ constexpr TemplateStringLiteral(const char (&str)[N]) {
std::copy_n(str, N, value.data());
}
constexpr operator std::string_view() const {
return {value.begin(), value.end() - 1};
}
// Not private, since structural types required for template parameters cannot
// have non-punlic data members.
std::array<char, N> value{};
};
} // namespace facebook::react