diff --git a/packages/react-native/ReactCommon/react/renderer/css/CSSKeywords.h b/packages/react-native/ReactCommon/react/renderer/css/CSSKeywords.h new file mode 100644 index 00000000000..1eecb1558cb --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/css/CSSKeywords.h @@ -0,0 +1,419 @@ +/* + * 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 + +namespace facebook::react { + +/** + * One of any predefined CSS keywords. + * https://www.w3.org/TR/css-values-3/#keywords + */ +enum class CSSKeyword : uint8_t { + Absolute, + Auto, + Baseline, + Center, + Column, + ColumnReverse, + Flex, + FlexEnd, + FlexStart, + Hidden, + Inherit, + Initial, + Inline, + Ltr, + None, + NoWrap, + Relative, + Row, + RowReverse, + Rtl, + Scroll, + SpaceAround, + SpaceBetween, + SpaceEvenly, + Static, + Stretch, + Unset, + Visible, + Wrap, + WrapReverse, +}; + +/** + * Represents a set of CSS keywords, including CSS-wide keywords. + */ +template +concept CSSKeywordSet = std::is_enum_v && requires { + { T::Inherit } -> std::same_as; + { T::Initial } -> std::same_as; + { T::Unset } -> std::same_as; +}; + +/** + * Defines a new set of CSS keywords + */ +#define CSS_DEFINE_KEYWORD_SET(name, ...) \ + enum class name : uint8_t { \ + Inherit = to_underlying(CSSKeyword::Inherit), \ + Initial = to_underlying(CSSKeyword::Initial), \ + Unset = to_underlying(CSSKeyword::Unset), \ + __VA_ARGS__ \ + }; + +/** + * CSS-wide keywords. + * https://www.w3.org/TR/css-values-4/#common-keywords + */ +CSS_DEFINE_KEYWORD_SET(CSSWideKeyword) + +/** + * CSS-wide keywords along with a context-dependent "auto" keyword. + */ +CSS_DEFINE_KEYWORD_SET(CSSAutoKeyword, Auto = to_underlying(CSSKeyword::Auto)) + +/** + * Keywords for the CSS "align-content" property. + * https://www.w3.org/TR/css-flexbox-1/#align-content-property + * https://www.w3.org/TR/css-align-3/#align-justify-content + */ +CSS_DEFINE_KEYWORD_SET( + CSSAlignContent, + Center = to_underlying(CSSKeyword::Center), + FlexEnd = to_underlying(CSSKeyword::FlexEnd), + FlexStart = to_underlying(CSSKeyword::FlexStart), + SpaceAround = to_underlying(CSSKeyword::SpaceAround), + SpaceBetween = to_underlying(CSSKeyword::SpaceBetween), + SpaceEvenly = to_underlying(CSSKeyword::SpaceEvenly), + Stretch = to_underlying(CSSKeyword::Stretch)) + +/** + * Keywords for the CSS "align-items" property. + * https://www.w3.org/TR/css-flexbox-1/#align-items-property + * https://www.w3.org/TR/css-align-3/#align-items-property + */ +CSS_DEFINE_KEYWORD_SET( + CSSAlignItems, + Baseline = to_underlying(CSSKeyword::Baseline), + Center = to_underlying(CSSKeyword::Center), + FlexEnd = to_underlying(CSSKeyword::FlexEnd), + FlexStart = to_underlying(CSSKeyword::FlexStart), + Stretch = to_underlying(CSSKeyword::Stretch)) + +/** + * Keywords for the CSS "align-items" property. + * https://www.w3.org/TR/css-flexbox-1/#align-self-property + * https://www.w3.org/TR/css-align-3/#align-self-property + */ +CSS_DEFINE_KEYWORD_SET( + CSSAlignSelf, + Auto = to_underlying(CSSKeyword::Auto), + Baseline = to_underlying(CSSKeyword::Baseline), + Center = to_underlying(CSSKeyword::Center), + FlexEnd = to_underlying(CSSKeyword::FlexEnd), + FlexStart = to_underlying(CSSKeyword::FlexStart), + Stretch = to_underlying(CSSKeyword::Stretch)) + +/** + * Keywords for the CSS "direction" property. + * https://www.w3.org/TR/css-writing-modes-3/#direction + */ +CSS_DEFINE_KEYWORD_SET( + CSSDirection, + Ltr = to_underlying(CSSKeyword::Ltr), + Rtl = to_underlying(CSSKeyword::Rtl)) + +/** + * Keywords for the CSS "display" property. + * https://www.w3.org/TR/css-display-3/#display-type + */ +CSS_DEFINE_KEYWORD_SET( + CSSDisplay, + Flex = to_underlying(CSSKeyword::Flex), + Inline = to_underlying(CSSKeyword::Inline), + None = to_underlying(CSSKeyword::None)) + +/** + * Keywords for the CSS "flex-direction" property. + * https://www.w3.org/TR/css-flexbox-1/#flex-direction-property + */ +CSS_DEFINE_KEYWORD_SET( + CSSFlexDirection, + Column = to_underlying(CSSKeyword::Column), + ColumnReverse = to_underlying(CSSKeyword::ColumnReverse), + Row = to_underlying(CSSKeyword::Row), + RowReverse = to_underlying(CSSKeyword::RowReverse)) + +/** + * Keywords for the CSS "flex-wrap" property. + * https://www.w3.org/TR/css-flexbox-1/#flex-wrap-property + */ +CSS_DEFINE_KEYWORD_SET( + CSSFlexWrap, + NoWrap = to_underlying(CSSKeyword::NoWrap), + Wrap = to_underlying(CSSKeyword::Wrap), + WrapReverse = to_underlying(CSSKeyword::WrapReverse)) + +/** + * Keywords for the CSS "justify-content" property. + * https://www.w3.org/TR/css-flexbox-1/#justify-content-property + * https://www.w3.org/TR/css-align-3/#align-justify-content + */ +CSS_DEFINE_KEYWORD_SET( + CSSJustifyContent, + Center = to_underlying(CSSKeyword::Center), + FlexEnd = to_underlying(CSSKeyword::FlexEnd), + FlexStart = to_underlying(CSSKeyword::FlexStart), + SpaceAround = to_underlying(CSSKeyword::SpaceAround), + SpaceBetween = to_underlying(CSSKeyword::SpaceBetween), + SpaceEvenly = to_underlying(CSSKeyword::SpaceEvenly)) + +/** + * Keywords for the CSS "overflow" property. + * https://www.w3.org/TR/css-overflow-3/#overflow-control + */ +CSS_DEFINE_KEYWORD_SET( + CSSOverflow, + Hidden = to_underlying(CSSKeyword::Hidden), + Scroll = to_underlying(CSSKeyword::Scroll), + Visible = to_underlying(CSSKeyword::Visible)) + +/** + * Keywords for the CSS "position" property. + * https://www.w3.org/TR/css-position-3/#position-property + */ +CSS_DEFINE_KEYWORD_SET( + CSSPosition, + Absolute = to_underlying(CSSKeyword::Absolute), + Relative = to_underlying(CSSKeyword::Relative), + Static = to_underlying(CSSKeyword::Static)) + +/** + * Compare two keywords of any representation + */ +constexpr bool operator==(CSSKeywordSet auto lhs, CSSKeywordSet auto rhs) { + return to_underlying(lhs) == to_underlying(rhs); +} + +/** + * Defines a concept for whether an enum has a given member. + */ +#define CSS_DEFINE_KEYWORD_CONEPTS(name) \ + namespace detail { \ + template \ + concept has##name = (CSSKeywordSet && requires() { T::name; }); \ + } + +CSS_DEFINE_KEYWORD_CONEPTS(Absolute) +CSS_DEFINE_KEYWORD_CONEPTS(Auto) +CSS_DEFINE_KEYWORD_CONEPTS(Baseline) +CSS_DEFINE_KEYWORD_CONEPTS(Center) +CSS_DEFINE_KEYWORD_CONEPTS(Column) +CSS_DEFINE_KEYWORD_CONEPTS(ColumnReverse) +CSS_DEFINE_KEYWORD_CONEPTS(Flex) +CSS_DEFINE_KEYWORD_CONEPTS(FlexEnd) +CSS_DEFINE_KEYWORD_CONEPTS(FlexStart) +CSS_DEFINE_KEYWORD_CONEPTS(Hidden) +CSS_DEFINE_KEYWORD_CONEPTS(Inherit) +CSS_DEFINE_KEYWORD_CONEPTS(Initial) +CSS_DEFINE_KEYWORD_CONEPTS(Inline) +CSS_DEFINE_KEYWORD_CONEPTS(Ltr) +CSS_DEFINE_KEYWORD_CONEPTS(None) +CSS_DEFINE_KEYWORD_CONEPTS(NoWrap) +CSS_DEFINE_KEYWORD_CONEPTS(Relative) +CSS_DEFINE_KEYWORD_CONEPTS(Row) +CSS_DEFINE_KEYWORD_CONEPTS(RowReverse) +CSS_DEFINE_KEYWORD_CONEPTS(Rtl) +CSS_DEFINE_KEYWORD_CONEPTS(Scroll) +CSS_DEFINE_KEYWORD_CONEPTS(SpaceAround) +CSS_DEFINE_KEYWORD_CONEPTS(SpaceBetween) +CSS_DEFINE_KEYWORD_CONEPTS(SpaceEvenly) +CSS_DEFINE_KEYWORD_CONEPTS(Static) +CSS_DEFINE_KEYWORD_CONEPTS(Stretch) +CSS_DEFINE_KEYWORD_CONEPTS(Unset) +CSS_DEFINE_KEYWORD_CONEPTS(Visible) +CSS_DEFINE_KEYWORD_CONEPTS(Wrap) +CSS_DEFINE_KEYWORD_CONEPTS(WrapReverse) + +/** + * Parses an ident token, case-insensitive, into a keyword. + * + * Returns KeywordT::Unset if the ident does not match any entries + * in the keyword-set, or CSS-wide keywords. + */ +template +constexpr std::optional parseCSSKeyword(std::string_view ident) { + struct LowerCaseTransform { + char operator()(char c) const { + return static_cast(tolower(c)); + } + }; + + switch (fnv1a(ident)) { + case fnv1a("absolute"): + if constexpr (detail::hasAbsolute) { + return KeywordT::Absolute; + } + break; + case fnv1a("auto"): + if constexpr (detail::hasAuto) { + return KeywordT::Auto; + } + break; + case fnv1a("baseline"): + if constexpr (detail::hasBaseline) { + return KeywordT::Baseline; + } + break; + case fnv1a("center"): + if constexpr (detail::hasCenter) { + return KeywordT::Center; + } + break; + case fnv1a("column"): + if constexpr (detail::hasColumn) { + return KeywordT::Column; + } + break; + case fnv1a("column-reverse"): + if constexpr (detail::hasColumnReverse) { + return KeywordT::ColumnReverse; + } + break; + case fnv1a("flex"): + if constexpr (detail::hasFlex) { + return KeywordT::Flex; + } + break; + case fnv1a("flex-end"): + if constexpr (detail::hasFlexEnd) { + return KeywordT::FlexEnd; + } + break; + case fnv1a("flex-start"): + if constexpr (detail::hasFlexStart) { + return KeywordT::FlexStart; + } + break; + case fnv1a("hidden"): + if constexpr (detail::hasHidden) { + return KeywordT::Hidden; + } + break; + case fnv1a("inherit"): + if constexpr (detail::hasInherit) { + return KeywordT::Inherit; + } + break; + case fnv1a("inline"): + if constexpr (detail::hasInline) { + return KeywordT::Inline; + } + break; + case fnv1a("ltr"): + if constexpr (detail::hasLtr) { + return KeywordT::Ltr; + } + break; + case fnv1a("none"): + if constexpr (detail::hasNone) { + return KeywordT::None; + } + break; + case fnv1a("no-wrap"): + if constexpr (detail::hasNoWrap) { + return KeywordT::NoWrap; + } + break; + case fnv1a("relative"): + if constexpr (detail::hasRelative) { + return KeywordT::Relative; + } + break; + case fnv1a("row"): + if constexpr (detail::hasRow) { + return KeywordT::Row; + } + break; + case fnv1a("row-reverse"): + if constexpr (detail::hasRowReverse) { + return KeywordT::RowReverse; + } + break; + case fnv1a("rtl"): + if constexpr (detail::hasRtl) { + return KeywordT::Rtl; + } + break; + case fnv1a("space-between"): + if constexpr (detail::hasSpaceBetween) { + return KeywordT::SpaceBetween; + } + break; + case fnv1a("space-around"): + if constexpr (detail::hasSpaceAround) { + return KeywordT::SpaceAround; + } + break; + case fnv1a("space-evenly"): + if constexpr (detail::hasSpaceEvenly) { + return KeywordT::SpaceEvenly; + } + break; + case fnv1a("scroll"): + if constexpr (detail::hasScroll) { + return KeywordT::Scroll; + } + break; + case fnv1a("static"): + if constexpr (detail::hasStatic) { + return KeywordT::Static; + } + break; + case fnv1a("stretch"): + if constexpr (detail::hasStretch) { + return KeywordT::Stretch; + } + break; + case fnv1a("unset"): + if constexpr (detail::hasUnset) { + return KeywordT::Unset; + } + break; + case fnv1a("visible"): + if constexpr (detail::hasVisible) { + return KeywordT::Visible; + } + break; + case fnv1a("wrap"): + if constexpr (detail::hasWrap) { + return KeywordT::Wrap; + } + break; + case fnv1a("wrap-reverse"): + if constexpr (detail::hasWrapReverse) { + return KeywordT::WrapReverse; + } + break; + default: + break; + } + + return std::nullopt; +} + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/css/CSSLengthUnit.h b/packages/react-native/ReactCommon/react/renderer/css/CSSLengthUnit.h new file mode 100644 index 00000000000..dea830d6727 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/css/CSSLengthUnit.h @@ -0,0 +1,165 @@ +/* + * 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 + +namespace facebook::react { + +/** + * Unit for the CSS type. + * https://www.w3.org/TR/css-values-4/#lengths + */ +enum class CSSLengthUnit : uint8_t { + Cap, + Ch, + Cm, + Dvb, + Dvh, + Dvi, + Dvmax, + Dvmin, + Dvw, + Em, + Ex, + Ic, + In, + Lh, + Lvb, + Lvh, + Lvi, + Lvmax, + Lvmin, + Lvw, + Mm, + Pc, + Pt, + Px, + Q, + Rcap, + Rch, + Rem, + Rex, + Ric, + Rlh, + Svb, + Svh, + Svi, + Svmax, + Svmin, + Svw, + Vb, + Vh, + Vi, + Vmax, + Vmin, + Vw, +}; + +/** + * Parses a unit from a dimension token into a CSS length unit. + */ +constexpr std::optional parseCSSLengthUnit( + std::string_view unit) { + switch (fnv1a(unit)) { + case fnv1a("cap"): + return CSSLengthUnit::Cap; + case fnv1a("ch"): + return CSSLengthUnit::Ch; + case fnv1a("cm"): + return CSSLengthUnit::Cm; + case fnv1a("dvb"): + return CSSLengthUnit::Dvb; + case fnv1a("dvh"): + return CSSLengthUnit::Dvh; + case fnv1a("dvi"): + return CSSLengthUnit::Dvi; + case fnv1a("dvmax"): + return CSSLengthUnit::Dvmax; + case fnv1a("dvmin"): + return CSSLengthUnit::Dvmin; + case fnv1a("dvw"): + return CSSLengthUnit::Dvw; + case fnv1a("em"): + return CSSLengthUnit::Em; + case fnv1a("ex"): + return CSSLengthUnit::Ex; + case fnv1a("ic"): + return CSSLengthUnit::Ic; + case fnv1a("in"): + return CSSLengthUnit::In; + case fnv1a("lh"): + return CSSLengthUnit::Lh; + case fnv1a("lvb"): + return CSSLengthUnit::Lvb; + case fnv1a("lvh"): + return CSSLengthUnit::Lvh; + case fnv1a("lvi"): + return CSSLengthUnit::Lvi; + case fnv1a("lvmax"): + return CSSLengthUnit::Lvmax; + case fnv1a("lvmin"): + return CSSLengthUnit::Lvmin; + case fnv1a("lvw"): + return CSSLengthUnit::Lvw; + case fnv1a("mm"): + return CSSLengthUnit::Mm; + case fnv1a("pc"): + return CSSLengthUnit::Pc; + case fnv1a("pt"): + return CSSLengthUnit::Pt; + case fnv1a("px"): + return CSSLengthUnit::Px; + case fnv1a("q"): + return CSSLengthUnit::Q; + case fnv1a("rcap"): + return CSSLengthUnit::Rcap; + case fnv1a("rch"): + return CSSLengthUnit::Rch; + case fnv1a("rem"): + return CSSLengthUnit::Rem; + case fnv1a("rex"): + return CSSLengthUnit::Rex; + case fnv1a("ric"): + return CSSLengthUnit::Ric; + case fnv1a("rlh"): + return CSSLengthUnit::Rlh; + case fnv1a("svb"): + return CSSLengthUnit::Svb; + case fnv1a("svh"): + return CSSLengthUnit::Svh; + case fnv1a("svi"): + return CSSLengthUnit::Svi; + case fnv1a("svmax"): + return CSSLengthUnit::Svmax; + case fnv1a("svmin"): + return CSSLengthUnit::Svmin; + case fnv1a("svw"): + return CSSLengthUnit::Svw; + case fnv1a("vb"): + return CSSLengthUnit::Vb; + case fnv1a("vh"): + return CSSLengthUnit::Vh; + case fnv1a("vi"): + return CSSLengthUnit::Vi; + case fnv1a("vmax"): + return CSSLengthUnit::Vmax; + case fnv1a("vmin"): + return CSSLengthUnit::Vmin; + case fnv1a("vw"): + return CSSLengthUnit::Vw; + default: + return std::nullopt; + } +} + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/css/CSSParser.h b/packages/react-native/ReactCommon/react/renderer/css/CSSParser.h new file mode 100644 index 00000000000..c31cbf26fd2 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/css/CSSParser.h @@ -0,0 +1,91 @@ +/* + * 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 + +namespace facebook::react { + +namespace detail { +template +CSSValueVariant consumeComponentValue( + const CSSToken& token) { + using CSSValueT = CSSValueVariant; + switch (token.type()) { + case CSSTokenType::Ident: + if (auto keyword = parseCSSKeyword(token.stringValue())) { + return CSSValueT::keyword(*keyword); + } + break; + case CSSTokenType::Dimension: + if constexpr (traits::containsType()) { + if (auto unit = parseCSSLengthUnit(token.unit())) { + return CSSValueT::length(token.numericValue(), *unit); + } + } + break; + case CSSTokenType::Percentage: + if constexpr (traits::containsType()) { + return CSSValueT::percentage(token.numericValue()); + } + break; + case CSSTokenType::Number: + // For zero lengths the unit identifier is optional (i.e. can be + // syntactically represented as the 0). However, if a 0 could + // be parsed as either a or a in a property (such as + // line-height), it must parse as a . + // https://www.w3.org/TR/css-values-4/#lengths + if constexpr (traits::containsType()) { + return CSSValueT::number(token.numericValue()); + } else if constexpr (traits::containsType()) { + if (token.numericValue() == 0) { + return CSSValueT::length(0, CSSLengthUnit::Px); + } + } + break; + default: + break; + } + return CSSValueT{}; +} +} // namespace detail + +/* + * Parse a single CSS component value as a keyword constrained to those + * allowable by KeywordRepresentationT. + * https://www.w3.org/TR/css-syntax-3/#parse-component-value + */ +template +CSSValueVariant parseCSSValue(std::string_view css) { + CSSTokenizer tokenizer(css); + + auto token = tokenizer.next(); + while (token.type() == CSSTokenType::WhiteSpace) { + token = tokenizer.next(); + } + + auto value = detail::consumeComponentValue(token); + + token = tokenizer.next(); + while (token.type() == CSSTokenType::WhiteSpace) { + token = tokenizer.next(); + } + if (token.type() == CSSTokenType::EndOfFile) { + return value; + } + + return {}; +} + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/css/CSSTokenizer.cpp b/packages/react-native/ReactCommon/react/renderer/css/CSSTokenizer.cpp index 99ddce3a209..3443786de5f 100644 --- a/packages/react-native/ReactCommon/react/renderer/css/CSSTokenizer.cpp +++ b/packages/react-native/ReactCommon/react/renderer/css/CSSTokenizer.cpp @@ -141,7 +141,7 @@ CSSToken CSSTokenizer::consumeNumeric() { } else if (peek() == '%') { advance(); consumeRunningValue(); - return {CSSTokenType::Percent, numberToken.numericValue()}; + return {CSSTokenType::Percentage, numberToken.numericValue()}; } else { return numberToken; } diff --git a/packages/react-native/ReactCommon/react/renderer/css/CSSTokenizer.h b/packages/react-native/ReactCommon/react/renderer/css/CSSTokenizer.h index 647e9bd8789..400d2916cd1 100644 --- a/packages/react-native/ReactCommon/react/renderer/css/CSSTokenizer.h +++ b/packages/react-native/ReactCommon/react/renderer/css/CSSTokenizer.h @@ -21,7 +21,7 @@ enum class CSSTokenType { EndOfFile, Ident, Number, - Percent, + Percentage, WhiteSpace, }; diff --git a/packages/react-native/ReactCommon/react/renderer/css/CSSValue.h b/packages/react-native/ReactCommon/react/renderer/css/CSSValue.h new file mode 100644 index 00000000000..6f6659c97a4 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/css/CSSValue.h @@ -0,0 +1,169 @@ +/* + * 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 + +namespace facebook::react { + +/** + * Represents a CSS component value type. + * https://www.w3.org/TR/css-values-3/#component-types + */ +enum class CSSValueType : uint8_t { + Keyword, + Length, + Number, + Percentage, +}; + +/** + * Concrete representation for a CSS basic data type. + * https://www.w3.org/TR/css-values-3/#component-types + */ +template +concept CSSBasicDataType = std::is_trivially_destructible_v && + std::is_default_constructible_v && requires() { + sizeof(T); +}; + +#pragma pack(push, 1) +/** + * Representation of CSS data type + * https://www.w3.org/TR/css-values-3/#lengths + */ +struct CSSLength { + float value{}; + CSSLengthUnit unit{CSSLengthUnit::Px}; + constexpr bool operator==(const CSSLength& rhs) const = default; +}; +#pragma pack(pop) + +/** + * Representation of CSS data type + * https://www.w3.org/TR/css-values-3/#percentages + */ +struct CSSPercentage { + float value{}; + constexpr bool operator==(const CSSPercentage& rhs) const = default; +}; + +/** + * Representation of CSS data type + * https://www.w3.org/TR/css-values-3/#numbers + */ +struct CSSNumber { + float value{}; + constexpr bool operator==(const CSSNumber& rhs) const = default; +}; + +/** + * CSSValueVariant represents a CSS component value: + * https://www.w3.org/TR/css-values-3/#component-types + * + * A CSSValueVariant must be constrained to the set of possible CSS types it may + * encounter. E.g. a dimension which accepts a CSS-wide keywords, "auto" or a + * would be modeled as + * `CSSValueVariant`. This + * allows for efficient storage, and customizing parsing based on the allowed + * set of values. + */ +#pragma pack(push, 1) +template +class CSSValueVariant { + template + constexpr ValueT getIf() const { + if (type_ == Type) { + return *std::launder(reinterpret_cast(data_.data())); + } else { + return ValueT{}; + } + } + + template + static constexpr bool canRepresent() { + return traits::containsType(); + } + + public: + constexpr CSSValueVariant() + : CSSValueVariant(CSSValueType::Keyword, KeywordT::Unset) {} + + static constexpr CSSValueVariant keyword(KeywordT keyword) { + return CSSValueVariant(CSSValueType::Keyword, KeywordT{keyword}); + } + + static constexpr CSSValueVariant length( + float value, + CSSLengthUnit unit) requires(canRepresent()) { + return CSSValueVariant(CSSValueType::Length, CSSLength{value, unit}); + } + + static constexpr CSSValueVariant number(float value) requires( + canRepresent()) { + return CSSValueVariant(CSSValueType::Number, CSSNumber{value}); + } + + static constexpr CSSValueVariant percentage(float value) requires( + canRepresent()) { + return CSSValueVariant(CSSValueType::Percentage, CSSPercentage{value}); + } + + constexpr CSSValueType type() const { + return type_; + } + + constexpr KeywordT getKeyword() const { + return getIf(); + } + + constexpr CSSLength getLength() const requires(canRepresent()) { + return getIf(); + } + + constexpr CSSNumber getNumber() const requires(canRepresent()) { + return getIf(); + } + + constexpr CSSPercentage getPercentage() const + requires(canRepresent()) { + return getIf(); + } + + constexpr operator bool() const { + return *this != CSSValueVariant{}; + } + + constexpr bool operator==(const CSSValueVariant& rhs) const = default; + + private: + constexpr CSSValueVariant(CSSValueType type, CSSBasicDataType auto&& value) + : type_(type) { + new (data_.data()) std::remove_cvref_t{ + std::forward(value)}; + } + + CSSValueType type_; + std::array()> data_; +}; +#pragma pack(pop) + +static_assert(sizeof(CSSValueVariant) == 2); +static_assert(sizeof(CSSValueVariant) == 6); +static_assert( + sizeof(CSSValueVariant) == 6); +static_assert(sizeof(CSSValueVariant) == 5); + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/css/tests/CSSParserTest.cpp b/packages/react-native/ReactCommon/react/renderer/css/tests/CSSParserTest.cpp new file mode 100644 index 00000000000..ecde3849121 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/css/tests/CSSParserTest.cpp @@ -0,0 +1,125 @@ +/* + * 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 + +namespace facebook::react { + +TEST(CSSParser, keyword_values) { + auto emptyValue = parseCSSValue(""); + EXPECT_EQ(emptyValue.type(), CSSValueType::Keyword); + EXPECT_EQ(emptyValue.getKeyword(), CSSKeyword::Unset); + + auto autoValue = parseCSSValue("auto"); + EXPECT_EQ(autoValue.type(), CSSValueType::Keyword); + EXPECT_EQ(autoValue.getKeyword(), CSSKeyword::Auto); + + auto autoCapsValue = parseCSSValue("AuTO"); + EXPECT_EQ(autoCapsValue.type(), CSSValueType::Keyword); + EXPECT_EQ(autoCapsValue.getKeyword(), CSSKeyword::Auto); + + auto autoDisallowedValue = parseCSSValue("auto"); + EXPECT_EQ(autoDisallowedValue.type(), CSSValueType::Keyword); + EXPECT_EQ(autoDisallowedValue.getKeyword(), CSSKeyword::Unset); + + auto whitespaceValue = parseCSSValue(" flex-start "); + EXPECT_EQ(whitespaceValue.type(), CSSValueType::Keyword); + EXPECT_EQ(whitespaceValue.getKeyword(), CSSAlignItems::FlexStart); + + auto badIdentValue = parseCSSValue("bad"); + EXPECT_EQ(badIdentValue.type(), CSSValueType::Keyword); + EXPECT_EQ(badIdentValue.getKeyword(), CSSKeyword::Unset); + + auto pxValue = parseCSSValue("20px"); + EXPECT_EQ(pxValue.type(), CSSValueType::Keyword); + EXPECT_EQ(pxValue.getKeyword(), CSSKeyword::Unset); + + auto multiValue = parseCSSValue("auto flex-start"); + EXPECT_EQ(multiValue.type(), CSSValueType::Keyword); + EXPECT_EQ(multiValue.getKeyword(), CSSKeyword::Unset); +} + +TEST(CSSParser, length_values) { + auto emptyValue = parseCSSValue(""); + EXPECT_EQ(emptyValue.type(), CSSValueType::Keyword); + EXPECT_EQ(emptyValue.getKeyword(), CSSKeyword::Unset); + + auto autoValue = parseCSSValue("auto"); + EXPECT_EQ(autoValue.type(), CSSValueType::Keyword); + EXPECT_EQ(autoValue.getKeyword(), CSSKeyword::Auto); + + auto pxValue = parseCSSValue("20px"); + EXPECT_EQ(pxValue.type(), CSSValueType::Length); + EXPECT_EQ(pxValue.getLength().value, 20.0f); + EXPECT_EQ(pxValue.getLength().unit, CSSLengthUnit::Px); + + auto cmValue = parseCSSValue("453cm"); + EXPECT_EQ(cmValue.type(), CSSValueType::Length); + EXPECT_EQ(cmValue.getLength().value, 453.0f); + EXPECT_EQ(cmValue.getLength().unit, CSSLengthUnit::Cm); + + auto unitlessZeroValue = parseCSSValue("0"); + EXPECT_EQ(unitlessZeroValue.type(), CSSValueType::Length); + EXPECT_EQ(unitlessZeroValue.getLength().value, 0.0f); + EXPECT_EQ(unitlessZeroValue.getLength().unit, CSSLengthUnit::Px); + + auto unitlessNonzeroValue = parseCSSValue("123"); + EXPECT_EQ(unitlessNonzeroValue.type(), CSSValueType::Keyword); + EXPECT_EQ(unitlessNonzeroValue.getKeyword(), CSSKeyword::Unset); + + auto pctValue = parseCSSValue("-40%"); + EXPECT_EQ(pctValue.type(), CSSValueType::Keyword); + EXPECT_EQ(pctValue.getKeyword(), CSSKeyword::Unset); +} + +TEST(CSSParser, length_percentage_values) { + auto emptyValue = parseCSSValue(""); + EXPECT_EQ(emptyValue.type(), CSSValueType::Keyword); + EXPECT_EQ(emptyValue.getKeyword(), CSSKeyword::Unset); + + auto autoValue = + parseCSSValue("auto"); + EXPECT_EQ(autoValue.type(), CSSValueType::Keyword); + EXPECT_EQ(autoValue.getKeyword(), CSSKeyword::Auto); + + auto pxValue = + parseCSSValue("20px"); + EXPECT_EQ(pxValue.type(), CSSValueType::Length); + EXPECT_EQ(pxValue.getLength().value, 20.0f); + EXPECT_EQ(pxValue.getLength().unit, CSSLengthUnit::Px); + + auto pctValue = + parseCSSValue("-40%"); + EXPECT_EQ(pctValue.type(), CSSValueType::Percentage); + EXPECT_EQ(pctValue.getPercentage().value, -40.0f); +} + +TEST(CSSParser, number_values) { + auto emptyValue = parseCSSValue(""); + EXPECT_EQ(emptyValue.type(), CSSValueType::Keyword); + EXPECT_EQ(emptyValue.getKeyword(), CSSKeyword::Unset); + + auto inheritValue = parseCSSValue("inherit"); + EXPECT_EQ(inheritValue.type(), CSSValueType::Keyword); + EXPECT_EQ(inheritValue.getKeyword(), CSSKeyword::Inherit); + + auto pxValue = parseCSSValue("20px"); + EXPECT_EQ(pxValue.type(), CSSValueType::Keyword); + EXPECT_EQ(pxValue.getKeyword(), CSSKeyword::Unset); + + auto numberValue = parseCSSValue("123.456"); + EXPECT_EQ(numberValue.type(), CSSValueType::Number); + EXPECT_EQ(numberValue.getNumber().value, 123.456f); + + auto unitlessZeroValue = + parseCSSValue("0"); + EXPECT_EQ(unitlessZeroValue.type(), CSSValueType::Number); + EXPECT_EQ(unitlessZeroValue.getNumber().value, 0.0f); +} + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/css/tests/CSSTokenizerTest.cpp b/packages/react-native/ReactCommon/react/renderer/css/tests/CSSTokenizerTest.cpp index cac2e8885b8..e40d289c677 100644 --- a/packages/react-native/ReactCommon/react/renderer/css/tests/CSSTokenizerTest.cpp +++ b/packages/react-native/ReactCommon/react/renderer/css/tests/CSSTokenizerTest.cpp @@ -104,12 +104,12 @@ TEST(CSSTokenizer, dimension_values) { TEST(CSSTokenizer, percent_values) { expectTokens( "12%", - {CSSToken{CSSTokenType::Percent, 12.0f}, + {CSSToken{CSSTokenType::Percentage, 12.0f}, CSSToken{CSSTokenType::EndOfFile}}); expectTokens( "-28.5%", - {CSSToken{CSSTokenType::Percent, -28.5f}, + {CSSToken{CSSTokenType::Percentage, -28.5f}, CSSToken{CSSTokenType::EndOfFile}}); } diff --git a/packages/react-native/ReactCommon/react/utils/PackTraits.h b/packages/react-native/ReactCommon/react/utils/PackTraits.h new file mode 100644 index 00000000000..0f14add9652 --- /dev/null +++ b/packages/react-native/ReactCommon/react/utils/PackTraits.h @@ -0,0 +1,40 @@ +/* + * 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 + +namespace facebook::react::traits { + +template +static constexpr size_t maxSizeof() { + if constexpr (sizeof...(RestT) > 0) { + return std::max(sizeof(T), maxSizeof()); + } else { + return sizeof(T); + } +} + +template +static constexpr bool containsType() { + return false; +} + +template +static constexpr bool containsType() { + if constexpr (sizeof...(RestT) > 0) { + return std::is_same_v || + containsType(); + } else { + return std::is_same_v; + } +} + +} // namespace facebook::react::traits diff --git a/packages/react-native/ReactCommon/react/utils/fnv1a.h b/packages/react-native/ReactCommon/react/utils/fnv1a.h index fe2721d4bc0..20c5b341e18 100644 --- a/packages/react-native/ReactCommon/react/utils/fnv1a.h +++ b/packages/react-native/ReactCommon/react/utils/fnv1a.h @@ -7,6 +7,10 @@ #pragma once +#include +#include +#include + namespace facebook::react { /** @@ -17,13 +21,14 @@ namespace facebook::react { * when std::hash does not provide the needed functionality. For example, * constexpr. */ +template constexpr uint32_t fnv1a(std::string_view string) noexcept { constexpr uint32_t offset_basis = 2166136261; uint32_t hash = offset_basis; for (auto const& c : string) { - hash ^= static_cast(c); + hash ^= static_cast(CharTransformT{}(c)); // Using shifts and adds instead of multiplication with a prime number. // This is faster when compiled with optimizations. hash += diff --git a/packages/react-native/ReactCommon/react/utils/to_underlying.h b/packages/react-native/ReactCommon/react/utils/to_underlying.h new file mode 100644 index 00000000000..6d8d19d2b34 --- /dev/null +++ b/packages/react-native/ReactCommon/react/utils/to_underlying.h @@ -0,0 +1,22 @@ +/* + * 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 + +namespace facebook::react { + +/** + * Polyfill of C++ 23 to_underlying() + * https://en.cppreference.com/w/cpp/utility/to_underlying + */ +constexpr auto to_underlying(auto e) noexcept { + return static_cast>(e); +} + +} // namespace facebook::react