diff --git a/packages/react-native/ReactAndroid/build.gradle.kts b/packages/react-native/ReactAndroid/build.gradle.kts index 7e0640a0e50a..5253a08e1a92 100644 --- a/packages/react-native/ReactAndroid/build.gradle.kts +++ b/packages/react-native/ReactAndroid/build.gradle.kts @@ -79,6 +79,9 @@ val preparePrefab by // migrate one library at a time. input.set( listOf( + PrefabPreprocessingEntry( + "react_render_css", + Pair("../ReactCommon/react/renderer/css/", "react/renderer/css/")), PrefabPreprocessingEntry( "react_render_debug", Pair("../ReactCommon/react/renderer/debug/", "react/renderer/debug/")), @@ -632,6 +635,7 @@ android { create("react_render_debug") { headers = File(prefabHeadersDir, "react_render_debug").absolutePath } + create("react_render_css") { headers = File(prefabHeadersDir, "react_render_css").absolutePath } create("turbomodulejsijni") { headers = File(prefabHeadersDir, "turbomodulejsijni").absolutePath } diff --git a/packages/react-native/ReactAndroid/cmake-utils/ReactNative-application.cmake b/packages/react-native/ReactAndroid/cmake-utils/ReactNative-application.cmake index 1ad0af8843db..48cb5110ebe5 100644 --- a/packages/react-native/ReactAndroid/cmake-utils/ReactNative-application.cmake +++ b/packages/react-native/ReactAndroid/cmake-utils/ReactNative-application.cmake @@ -62,6 +62,7 @@ target_compile_options(${CMAKE_PROJECT_NAME} # Prefab packages from React Native find_package(ReactAndroid REQUIRED CONFIG) +add_library(react_render_css ALIAS ReactAndroid::react_render_css) add_library(react_render_debug ALIAS ReactAndroid::react_render_debug) add_library(turbomodulejsijni ALIAS ReactAndroid::turbomodulejsijni) add_library(runtimeexecutor ALIAS ReactAndroid::runtimeexecutor) @@ -102,6 +103,7 @@ target_link_libraries(${CMAKE_PROJECT_NAME} react_cxxreactpackage # prefab ready react_render_componentregistry # prefab ready react_render_core # prefab ready + react_render_css # prefab ready react_render_debug # prefab ready react_render_graphics # prefab ready react_render_imagemanager # prefab ready diff --git a/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt index 0fe09d67baa6..e406215f5438 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt +++ b/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt @@ -79,6 +79,7 @@ add_react_common_subdir(react/renderer/scheduler) add_react_common_subdir(react/renderer/telemetry) add_react_common_subdir(react/renderer/uimanager) add_react_common_subdir(react/renderer/core) +add_react_common_subdir(react/renderer/css) add_react_common_subdir(react/renderer/element) add_react_common_subdir(react/renderer/graphics) add_react_common_subdir(react/renderer/debug) diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/CMakeLists.txt index 38dfe037936a..a4407cf4d81c 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/CMakeLists.txt +++ b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/CMakeLists.txt @@ -31,6 +31,7 @@ target_link_libraries( react_render_attributedstring react_render_componentregistry react_render_core + react_render_css react_render_debug react_render_graphics react_render_imagemanager diff --git a/packages/react-native/ReactCommon/React-Fabric.podspec b/packages/react-native/ReactCommon/React-Fabric.podspec index 0963b3314aa8..11737a23798a 100644 --- a/packages/react-native/ReactCommon/React-Fabric.podspec +++ b/packages/react-native/ReactCommon/React-Fabric.podspec @@ -225,6 +225,11 @@ Pod::Spec.new do |s| end end + s.subspec "css" do |ss| + ss.source_files = "react/renderer/css/*.{cpp,h}" + ss.header_dir = "react/renderer/css" + end + s.subspec "imagemanager" do |ss| ss.dependency folly_dep_name, folly_version ss.compiler_flags = folly_compiler_flags diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/CSSTokenizer.cpp b/packages/react-native/ReactCommon/react/renderer/components/view/CSSTokenizer.cpp deleted file mode 100644 index 945d395e4422..000000000000 --- a/packages/react-native/ReactCommon/react/renderer/components/view/CSSTokenizer.cpp +++ /dev/null @@ -1,187 +0,0 @@ -/* - * 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 { - -CSSTokenizer::CSSTokenizer(std::string_view characters) - : remainingCharacters_{characters} {} - -CSSToken CSSTokenizer::next() { - // https://www.w3.org/TR/css-syntax-3/#token-diagrams - char nextChar = peek(); - if (isWhitespace(nextChar)) { - return consumeWhitespace(); - } else if (nextChar == '+') { - if (isDigit(peekNext())) { - return consumeNumeric(); - } else { - return consumeDelim(); - } - } else if (nextChar == '-') { - if (isDigit(peekNext())) { - return consumeNumeric(); - } else { - return consumeDelim(); - } - } else if (isDigit(nextChar)) { - return consumeNumeric(); - } else if (isIdentStart(nextChar)) { - return consumeIdent(); - } else if (nextChar == '\0') { - return CSSToken{CSSTokenType::EndOfFile}; - } else { - return consumeDelim(); - } -} - -char CSSTokenizer::peek() const { - auto index = position_; - return index >= remainingCharacters_.size() ? '\0' - : remainingCharacters_[index]; -} - -char CSSTokenizer::peekNext() const { - auto index = position_ + 1; - return index >= remainingCharacters_.size() ? '\0' - : remainingCharacters_[index]; -} - -void CSSTokenizer::advance() { - react_native_assert(remainingCharacters_.size() > position_); - position_ += 1; -} - -CSSToken CSSTokenizer::consumeDelim() { - advance(); - return {CSSTokenType::Delim, consumeRunningValue()}; -} - -CSSToken CSSTokenizer::consumeWhitespace() { - while (isWhitespace(peek())) { - advance(); - } - - consumeRunningValue(); - return CSSToken{CSSTokenType::WhiteSpace}; -} - -CSSToken CSSTokenizer::consumeNumber() { - // https://www.w3.org/TR/css-syntax-3/#consume-number - // https://www.w3.org/TR/css-syntax-3/#convert-a-string-to-a-number - int32_t signPart = 1.0; - if (peek() == '+' || peek() == '-') { - if (peek() == '-') { - signPart = -1.0; - } - advance(); - } - - int32_t intPart = 0; - while (isDigit(peek())) { - intPart = intPart * 10 + (peek() - '0'); - advance(); - } - - int32_t fractionalPart = 0; - int32_t fractionDigits = 0; - if (peek() == '.') { - advance(); - while (isDigit(peek())) { - fractionalPart = fractionalPart * 10 + (peek() - '0'); - fractionDigits++; - advance(); - } - } - - int32_t exponentSign = 1.0; - int32_t exponentPart = 0; - if (peek() == 'e' || peek() == 'E') { - advance(); - if (peek() == '+' || peek() == '-') { - if (peek() == '-') { - exponentSign = -1.0; - } - advance(); - } - - while (isDigit(peek())) { - exponentPart = exponentPart * 10 + (peek() - '0'); - advance(); - } - } - - auto value = static_cast( - signPart * (intPart + (fractionalPart * std::pow(10, -fractionDigits))) * - std::pow(10, exponentSign * exponentPart)); - - consumeRunningValue(); - return {CSSTokenType::Number, value}; -} - -CSSToken CSSTokenizer::consumeNumeric() { - // https://www.w3.org/TR/css-syntax-3/#consume-numeric-token - auto numberToken = consumeNumber(); - - if (isIdent(peek())) { - auto ident = consumeIdent(); - return { - CSSTokenType::Dimension, - numberToken.numericValue(), - ident.stringValue()}; - } else if (peek() == '%') { - advance(); - consumeRunningValue(); - return {CSSTokenType::Percent, numberToken.numericValue()}; - } else { - return numberToken; - } -} - -CSSToken CSSTokenizer::consumeIdent() { - // https://www.w3.org/TR/css-syntax-3/#consume-an-ident-sequence - while (isIdent(peek())) { - advance(); - } - - return {CSSTokenType::Ident, consumeRunningValue()}; -} - -std::string_view CSSTokenizer::consumeRunningValue() { - auto next = remainingCharacters_.substr(0, position_); - remainingCharacters_ = remainingCharacters_.substr(next.size()); - position_ = 0; - return next; -} - -/*static*/ bool CSSTokenizer::isDigit(char c) { - // https://www.w3.org/TR/css-syntax-3/#digit - return c >= '0' && c <= '9'; -} - -/*static*/ bool CSSTokenizer::isIdentStart(char c) { - // https://www.w3.org/TR/css-syntax-3/#ident-start-code-point - return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || - static_cast(c) > 0x80; -} - -/*static*/ bool CSSTokenizer::isIdent(char c) { - // https://www.w3.org/TR/css-syntax-3/#ident-code-point - return isIdentStart(c) || isDigit(c) || c == '-'; -} - -/*static*/ bool CSSTokenizer::isWhitespace(char c) { - // https://www.w3.org/TR/css-syntax-3/#whitespace - return c == ' ' || c == '\t' || c == '\r' || c == '\n'; -} - -} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/CSSTokenizer.h b/packages/react-native/ReactCommon/react/renderer/components/view/CSSTokenizer.h deleted file mode 100644 index 647e9bd87899..000000000000 --- a/packages/react-native/ReactCommon/react/renderer/components/view/CSSTokenizer.h +++ /dev/null @@ -1,101 +0,0 @@ -/* - * 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 { - -/** - * One of the tokens defined as part of - * https://www.w3.org/TR/css-syntax-3/#tokenizer-definitions - */ -enum class CSSTokenType { - Delim, - Dimension, - EndOfFile, - Ident, - Number, - Percent, - WhiteSpace, -}; - -struct CSSToken { - explicit CSSToken(CSSTokenType type) : type_(type) {} - CSSToken(CSSTokenType type, std::string_view value) - : type_{type}, stringValue_{value} {} - CSSToken(CSSTokenType type, float value) - : type_{type}, numericValue_{value} {} - CSSToken(CSSTokenType type, float value, std::string_view unit) - : type_{type}, numericValue_{value}, unit_{unit} {} - - CSSToken(const CSSToken& other) = default; - CSSToken(CSSToken&& other) = default; - CSSToken& operator=(const CSSToken& other) = default; - CSSToken& operator=(CSSToken&& other) = default; - - CSSTokenType type() const { - return type_; - } - - std::string_view stringValue() const { - return stringValue_; - } - - float numericValue() const { - return numericValue_; - } - - std::string_view unit() const { - return unit_; - } - - bool operator==(const CSSToken& other) const = default; - - private: - CSSTokenType type_; - std::string_view stringValue_; - float numericValue_{0.0f}; - std::string_view unit_; -}; - -/** - * A minimal tokenizer for a subset of CSS syntax. - * `auto`). - * - * This is based on the W3C CSS Syntax specification, with simplifications made - * for syntax which React Native does not attempt to support. - * https://www.w3.org/TR/css-syntax-3/#tokenizing-and-parsing - */ -class CSSTokenizer { - public: - explicit CSSTokenizer(std::string_view characters); - CSSToken next(); - - private: - char peek() const; - char peekNext() const; - void advance(); - - CSSToken consumeDelim(); - CSSToken consumeWhitespace(); - CSSToken consumeNumber(); - CSSToken consumeNumeric(); - CSSToken consumeIdent(); - - std::string_view consumeRunningValue(); - - static bool isDigit(char c); - static bool isIdentStart(char c); - static bool isIdent(char c); - static bool isWhitespace(char c); - - std::string_view remainingCharacters_; - size_t position_{0}; -}; -} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/css/CMakeLists.txt b/packages/react-native/ReactCommon/react/renderer/css/CMakeLists.txt new file mode 100644 index 000000000000..a4834c3a0256 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/css/CMakeLists.txt @@ -0,0 +1,20 @@ +# 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. + +cmake_minimum_required(VERSION 3.13) +set(CMAKE_VERBOSE_MAKEFILE on) + +add_compile_options( + -fexceptions + -frtti + -std=c++20 + -Wall + -Wpedantic) + +file(GLOB react_render_css_SRC CONFIGURE_DEPENDS *.cpp) +add_library(react_render_css STATIC ${react_render_css_SRC}) + +target_include_directories(react_render_css PUBLIC ${REACT_COMMON_DIR}) +target_link_libraries(react_render_css glog glog_init react_debug) 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 000000000000..15e5037c6845 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/css/CSSKeywords.h @@ -0,0 +1,405 @@ +/* + * 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-4/#keywords + */ +enum class CSSKeyword : uint8_t { + Absolute, + Auto, + Baseline, + Block, + Center, + Clip, + Column, + ColumnReverse, + Content, + Contents, + End, + Fixed, + Flex, + FlexEnd, + FlexStart, + Hidden, + Inherit, + Initial, + Inline, + InlineBlock, + InlineFlex, + InlineGrid, + Ltr, + Grid, + MaxContent, + Medium, + MinContent, + None, + Normal, + NoWrap, + Relative, + Row, + RowReverse, + Rtl, + Scroll, + SpaceAround, + SpaceBetween, + SpaceEvenly, + Start, + Static, + Sticky, + Stretch, + Thick, + Thin, + Unset, + Visible, + Wrap, + WrapReverse, +}; + +/** + * Represents a contrained set of CSS keywords. + */ +template +concept CSSKeywordSet = std::is_enum_v && std:: + is_same_v, std::underlying_type_t>; + +/** + * CSS-wide keywords. + * https://www.w3.org/TR/css-values-4/#common-keywords + */ +enum class CSSWideKeyword : std::underlying_type_t { + Inherit = to_underlying(CSSKeyword::Inherit), + Initial = to_underlying(CSSKeyword::Initial), + Unset = to_underlying(CSSKeyword::Unset), +}; + +/** + * 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(Block) +CSS_DEFINE_KEYWORD_CONEPTS(Center) +CSS_DEFINE_KEYWORD_CONEPTS(Clip) +CSS_DEFINE_KEYWORD_CONEPTS(Column) +CSS_DEFINE_KEYWORD_CONEPTS(ColumnReverse) +CSS_DEFINE_KEYWORD_CONEPTS(Content) +CSS_DEFINE_KEYWORD_CONEPTS(Contents) +CSS_DEFINE_KEYWORD_CONEPTS(End) +CSS_DEFINE_KEYWORD_CONEPTS(Fixed) +CSS_DEFINE_KEYWORD_CONEPTS(Flex) +CSS_DEFINE_KEYWORD_CONEPTS(FlexEnd) +CSS_DEFINE_KEYWORD_CONEPTS(FlexStart) +CSS_DEFINE_KEYWORD_CONEPTS(Grid) +CSS_DEFINE_KEYWORD_CONEPTS(Hidden) +CSS_DEFINE_KEYWORD_CONEPTS(Inherit) +CSS_DEFINE_KEYWORD_CONEPTS(Initial) +CSS_DEFINE_KEYWORD_CONEPTS(Inline) +CSS_DEFINE_KEYWORD_CONEPTS(InlineBlock) +CSS_DEFINE_KEYWORD_CONEPTS(InlineFlex) +CSS_DEFINE_KEYWORD_CONEPTS(InlineGrid) +CSS_DEFINE_KEYWORD_CONEPTS(Ltr) +CSS_DEFINE_KEYWORD_CONEPTS(MaxContent) +CSS_DEFINE_KEYWORD_CONEPTS(Medium) +CSS_DEFINE_KEYWORD_CONEPTS(MinContent) +CSS_DEFINE_KEYWORD_CONEPTS(None) +CSS_DEFINE_KEYWORD_CONEPTS(Normal) +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(Start) +CSS_DEFINE_KEYWORD_CONEPTS(Static) +CSS_DEFINE_KEYWORD_CONEPTS(Sticky) +CSS_DEFINE_KEYWORD_CONEPTS(Stretch) +CSS_DEFINE_KEYWORD_CONEPTS(Thick) +CSS_DEFINE_KEYWORD_CONEPTS(Thin) +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("block"): + if constexpr (detail::hasBlock) { + return KeywordT::Block; + } + break; + case fnv1a("center"): + if constexpr (detail::hasCenter) { + return KeywordT::Center; + } + break; + case fnv1a("clip"): + if constexpr (detail::hasClip) { + return KeywordT::Clip; + } + 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("content"): + if constexpr (detail::hasContent) { + return KeywordT::Content; + } + break; + case fnv1a("contents"): + if constexpr (detail::hasContents) { + return KeywordT::Contents; + } + break; + case fnv1a("end"): + if constexpr (detail::hasEnd) { + return KeywordT::End; + } + break; + case fnv1a("fixed"): + if constexpr (detail::hasFixed) { + return KeywordT::Fixed; + } + 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("grid"): + if constexpr (detail::hasGrid) { + return KeywordT::Grid; + } + 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("inline-block"): + if constexpr (detail::hasInlineBlock) { + return KeywordT::InlineBlock; + } + break; + case fnv1a("inline-flex"): + if constexpr (detail::hasInlineFlex) { + return KeywordT::InlineFlex; + } + break; + case fnv1a("inline-grid"): + if constexpr (detail::hasInlineGrid) { + return KeywordT::InlineGrid; + } + break; + case fnv1a("ltr"): + if constexpr (detail::hasLtr) { + return KeywordT::Ltr; + } + break; + case fnv1a("max-content"): + if constexpr (detail::hasMaxContent) { + return KeywordT::MaxContent; + } + break; + case fnv1a("medium"): + if constexpr (detail::hasMedium) { + return KeywordT::Medium; + } + break; + case fnv1a("min-content"): + if constexpr (detail::hasMinContent) { + return KeywordT::MinContent; + } + break; + case fnv1a("none"): + if constexpr (detail::hasNone) { + return KeywordT::None; + } + break; + case fnv1a("normal"): + if constexpr (detail::hasNormal) { + return KeywordT::Normal; + } + break; + case fnv1a("nowrap"): + 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("start"): + if constexpr (detail::hasStart) { + return KeywordT::Start; + } + case fnv1a("static"): + if constexpr (detail::hasStatic) { + return KeywordT::Static; + } + break; + case fnv1a("sticky"): + if constexpr (detail::hasSticky) { + return KeywordT::Sticky; + } + break; + case fnv1a("stretch"): + if constexpr (detail::hasStretch) { + return KeywordT::Stretch; + } + break; + case fnv1a("thick"): + if constexpr (detail::hasThick) { + return KeywordT::Thick; + } + break; + case fnv1a("thin"): + if constexpr (detail::hasThin) { + return KeywordT::Thin; + } + 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 000000000000..dea830d67275 --- /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 000000000000..f9ad61c4846b --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/css/CSSParser.h @@ -0,0 +1,220 @@ +/* + * 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 { + +namespace detail { +class CSSParser { + public: + explicit constexpr CSSParser(std::string_view css) + : tokenizer_{css}, currentToken_(tokenizer_.next()) {} + + template + constexpr CSSValueVariant consumeComponentValue() { + using CSSValueT = CSSValueVariant; + switch (peek().type()) { + case CSSTokenType::Ident: + if (auto keywordValue = + consumeIdentToken()) { + return *keywordValue; + } + break; + case CSSTokenType::Dimension: + if (auto dimensionValue = + consumeDimensionToken()) { + return *dimensionValue; + } + break; + case CSSTokenType::Percentage: + if (auto percentageValue = + consumePercentageToken()) { + return *percentageValue; + } + break; + case CSSTokenType::Number: + if (auto numberValue = + consumeNumberToken()) { + return *numberValue; + } + break; + default: + break; + } + + consumeToken(); + return {}; + } + + constexpr void consumeWhitespace() { + while (peek().type() == CSSTokenType::WhiteSpace) { + consumeToken(); + } + } + + constexpr bool hasMoreTokens() const { + return peek().type() != CSSTokenType::EndOfFile; + } + + private: + constexpr const CSSToken& peek() const { + return currentToken_; + } + + constexpr CSSToken consumeToken() { + auto prevToken = currentToken_; + currentToken_ = tokenizer_.next(); + return prevToken; + } + + template + constexpr std::optional consumeIdentToken() { + if constexpr (!std::is_same_v) { + if (auto keyword = parseCSSKeyword( + peek().stringValue())) { + consumeToken(); + return CSSValueT::keyword(*keyword); + } + } + if constexpr (traits::containsType()) { + if (auto keyword = + parseCSSKeyword(peek().stringValue())) { + consumeToken(); + return CSSValueT::cssWideKeyword(*keyword); + } + } + return {}; + } + + template + constexpr std::optional consumeDimensionToken() { + if constexpr (traits::containsType()) { + if (auto unit = parseCSSLengthUnit(peek().unit())) { + return CSSValueT::length(consumeToken().numericValue(), *unit); + } + } + return {}; + } + + template + constexpr std::optional consumePercentageToken() { + if constexpr (traits::containsType()) { + return CSSValueT::percentage(consumeToken().numericValue()); + } + return {}; + } + + template + constexpr std::optional consumeNumberToken() { + // = [ / ]? + // https://www.w3.org/TR/css-values-4/#ratio + if constexpr (traits::containsType()) { + if (isValidRatioPart(peek().numericValue())) { + float numerator = consumeToken().numericValue(); + float denominator = 1.0; + + consumeWhitespace(); + if (peek().type() != CSSTokenType::Ident && + peek().stringValue() == "/") { + consumeToken(); + consumeWhitespace(); + + if (peek().type() == CSSTokenType::Number && + isValidRatioPart(peek().numericValue())) { + denominator = consumeToken().numericValue(); + } else { + return CSSValueT{}; + } + } + + return CSSValueT::ratio(numerator, denominator); + } + } + + if constexpr (traits::containsType()) { + return CSSValueT::number(consumeToken().numericValue()); + } + + // 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()) { + if (peek().numericValue() == 0) { + return CSSValueT::length( + consumeToken().numericValue(), CSSLengthUnit::Px); + } + } + + return {}; + } + + static constexpr bool isValidRatioPart(float value) { + // If either number in the is 0 or infinite, it represents a + // degenerate ratio (and, generally, won’t do anything). + // https://www.w3.org/TR/css-values-4/#ratios + return value > 0.0f && value != +std::numeric_limits::infinity() && + value != -std::numeric_limits::infinity(); + } + + CSSTokenizer tokenizer_; + CSSToken currentToken_; +}; + +} // namespace detail + +/* + * Parse a single CSS component value as a keyword constrained to those + * allowable by KeywordRepresentationT. Returns a default-constructed + * CSSValueVariant (KeywordT::Unset) on syntax error. + * + * https://www.w3.org/TR/css-syntax-3/#parse-component-value + */ +template +constexpr void parseCSSComponentValue( + std::string_view css, + CSSValueVariant& value) { + detail::CSSParser parser(css); + + parser.consumeWhitespace(); + auto componentValue = parser.consumeComponentValue(); + parser.consumeWhitespace(); + + if (parser.hasMoreTokens()) { + value = {}; + } else { + value = componentValue; + } +}; + +template +CSSValueVariant parseCSSComponentValue(std::string_view css) { + CSSValueVariant value; + parseCSSComponentValue(css, value); + return value; +}; + +template +constexpr auto parseCSSProp(std::string_view css) { + // For now we only allow parsing props composed of a single component value. + CSSSpecifiedValue value; + parseCSSComponentValue(css, value); + return value; +} + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/css/CSSProperties.h b/packages/react-native/ReactCommon/react/renderer/css/CSSProperties.h new file mode 100644 index 000000000000..ad982552bcfb --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/css/CSSProperties.h @@ -0,0 +1,712 @@ +/* + * 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 { + +/** + * All CSS properties,including CSS,and React-Native specific shorthands + * https://www.w3.org/TR/css-cascade-4/#css-property + */ +enum class CSSProp { + AlignContent, + AlignItems, + AlignSelf, + AspectRatio, + BorderBlockEndWidth, + BorderBlockStartWidth, + BorderBlockWidth, + BorderBottomWidth, + BorderEndWidth, + BorderHorizontalWidth, + BorderInlineEndWidth, + BorderInlineStartWidth, + BorderInlineWidth, + BorderLeftWidth, + BorderRightWidth, + BorderStartWidth, + BorderTopWidth, + BorderVerticalWidth, + BorderWidth, + Bottom, + ColumnGap, + Direction, + Display, + End, + Flex, + FlexBasis, + FlexDirection, + FlexGrow, + FlexShrink, + FlexWrap, + Gap, + Height, + Inset, + InsetBlock, + InsetBlockEnd, + InsetBlockStart, + InsetInline, + InsetInlineEnd, + InsetInlineStart, + JustifyContent, + Left, + Margin, + MarginBlock, + MarginBlockEnd, + MarginBlockStart, + MarginBottom, + MarginEnd, + MarginHorizontal, + MarginInline, + MarginInlineEnd, + MarginInlineStart, + MarginLeft, + MarginRight, + MarginStart, + MarginTop, + MarginVertical, + MaxHeight, + MaxWidth, + MinHeight, + MinWidth, + Overflow, + Padding, + PaddingBlock, + PaddingBlockEnd, + PaddingBlockStart, + PaddingBottom, + PaddingEnd, + PaddingHorizontal, + PaddingInline, + PaddingInlineEnd, + PaddingInlineStart, + PaddingLeft, + PaddingRight, + PaddingStart, + PaddingTop, + PaddingVertical, + Position, + Right, + RowGap, + Start, + Top, + Width, + // Please update "kCSSPropCount" if adding a new prop to the end +}; + +/** + * The total number of CSS properties. + */ +constexpr auto kCSSPropCount = to_underlying(CSSProp::Width) + 1; + +/** + * CSSPropDefinition associates a CSSProp to its + * supported data types, Keyword, and other behaviors. + */ +template +struct CSSPropDefinition {}; + +template +using CSSAllowedKeywords = typename CSSPropDefinition

::Keyword; + +template +using CSSSpecifiedValue = typename CSSPropDefinition

::SpecifiedValue; + +template +using CSSComputedValue = typename CSSPropDefinition

::ComputedValue; + +/** + * 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 + */ +template <> +struct CSSPropDefinition { + enum class Keyword : std::underlying_type_t { + 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), + Start = to_underlying(CSSKeyword::Start), + End = to_underlying(CSSKeyword::End), + }; + + using SpecifiedValue = CSSValueVariant; + using ComputedValue = CSSValueVariant; +}; + +/** + * 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 + */ +template <> +struct CSSPropDefinition { + enum class Keyword : std::underlying_type_t { + 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), + Start = to_underlying(CSSKeyword::Start), + End = to_underlying(CSSKeyword::End), + }; + + using SpecifiedValue = CSSValueVariant; + using ComputedValue = CSSValueVariant; +}; + +/** + * CSS "align-items" property. + * https://www.w3.org/TR/css-flexbox-1/#propdef-align-self + * https://www.w3.org/TR/css-align-3/#align-self-property + */ +template <> +struct CSSPropDefinition { + enum class Keyword : std::underlying_type_t { + 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), + Start = to_underlying(CSSKeyword::Start), + End = to_underlying(CSSKeyword::End), + }; + + using SpecifiedValue = CSSValueVariant; + using ComputedValue = CSSValueVariant; +}; + +/** + * CSS "aspect-ratio" property. + * https://www.w3.org/TR/css-sizing-4/#aspect-ratio + */ +template <> +struct CSSPropDefinition { + enum class Keyword : std::underlying_type_t { + Auto = to_underlying(CSSKeyword::Auto), + }; + + using SpecifiedValue = CSSValueVariant; + using ComputedValue = CSSValueVariant; +}; + +/** + * CSS "border-width" properties + * https://www.w3.org/TR/css-backgrounds-3/#border-width + */ +template <> +struct CSSPropDefinition { + enum class Keyword : std::underlying_type_t { + Thin = to_underlying(CSSKeyword::Thin), + Medium = to_underlying(CSSKeyword::Medium), + Thick = to_underlying(CSSKeyword::Thick), + }; + + using SpecifiedValue = CSSValueVariant; + using ComputedValue = CSSValueVariant; +}; + +template <> +struct CSSPropDefinition + : CSSPropDefinition {}; + +template <> +struct CSSPropDefinition + : CSSPropDefinition {}; + +template <> +struct CSSPropDefinition + : CSSPropDefinition {}; + +template <> +struct CSSPropDefinition + : CSSPropDefinition {}; + +template <> +struct CSSPropDefinition + : CSSPropDefinition {}; + +template <> +struct CSSPropDefinition + : CSSPropDefinition {}; + +template <> +struct CSSPropDefinition + : CSSPropDefinition {}; + +template <> +struct CSSPropDefinition + : CSSPropDefinition {}; + +template <> +struct CSSPropDefinition + : CSSPropDefinition {}; + +template <> +struct CSSPropDefinition + : CSSPropDefinition {}; + +template <> +struct CSSPropDefinition + : CSSPropDefinition {}; + +template <> +struct CSSPropDefinition + : CSSPropDefinition {}; + +template <> +struct CSSPropDefinition + : CSSPropDefinition {}; + +template <> +struct CSSPropDefinition + : CSSPropDefinition {}; + +/** + * CSS "direction" property. + * https://www.w3.org/TR/css-writing-modes-3/#direction + */ +template <> +struct CSSPropDefinition { + enum class Keyword : std::underlying_type_t { + Ltr = to_underlying(CSSKeyword::Ltr), + Rtl = to_underlying(CSSKeyword::Rtl), + }; + + using SpecifiedValue = CSSValueVariant; + using ComputedValue = CSSValueVariant; +}; + +/** + * CSS "display" property. + * https://www.w3.org/TR/css-display-3/#display-type + */ +template <> +struct CSSPropDefinition { + enum class Keyword : std::underlying_type_t { + None = to_underlying(CSSKeyword::None), + Contents = to_underlying(CSSKeyword::Contents), + Inline = to_underlying(CSSKeyword::Inline), + Block = to_underlying(CSSKeyword::Block), + InlineBlock = to_underlying(CSSKeyword::InlineBlock), + Flex = to_underlying(CSSKeyword::Flex), + InlineFlex = to_underlying(CSSKeyword::InlineFlex), + Grid = to_underlying(CSSKeyword::Grid), + InlineGrid = to_underlying(CSSKeyword::InlineGrid), + }; + + using SpecifiedValue = CSSValueVariant; + using ComputedValue = CSSValueVariant; +}; + +/** + * CSS "flex" shorthand property. + * https://www.w3.org/TR/css-flexbox-1/#flex-property + * + * React Native's interpretation of this prop is currently different than in CSS + */ +template <> +struct CSSPropDefinition { + enum class Keyword : std::underlying_type_t { + Auto = to_underlying(CSSKeyword::Auto), + None = to_underlying(CSSKeyword::None), + }; + + using SpecifiedValue = CSSValueVariant; + using ComputedValue = CSSValueVariant; +}; + +/** + * CSS "flex-basis" property. + * https://www.w3.org/TR/css-flexbox-1/#flex-basis-property + */ +template <> +struct CSSPropDefinition { + enum class Keyword : std::underlying_type_t { + Auto = to_underlying(CSSKeyword::Auto), + Content = to_underlying(CSSKeyword::Content), + }; + + using SpecifiedValue = + CSSValueVariant; + using ComputedValue = CSSValueVariant; +}; + +/** + * CSS "flex-direction" property. + * https://www.w3.org/TR/css-flexbox-1/#flex-direction-property + */ +template <> +struct CSSPropDefinition { + enum class Keyword : std::underlying_type_t { + Row = to_underlying(CSSKeyword::Row), + RowReverse = to_underlying(CSSKeyword::RowReverse), + Column = to_underlying(CSSKeyword::Column), + ColumnReverse = to_underlying(CSSKeyword::ColumnReverse), + }; + + using SpecifiedValue = CSSValueVariant; + using ComputedValue = CSSValueVariant; +}; + +/** + * CSS "flex-grow" property. + * https://www.w3.org/TR/css-flexbox-1/#flex-grow-property + */ +template <> +struct CSSPropDefinition { + using SpecifiedValue = CSSValueVariant; + using ComputedValue = CSSValueVariant; +}; + +/** + * CSS "flex-shrink" property. + * https://www.w3.org/TR/css-flexbox-1/#flex-shrink-property + */ +template <> +struct CSSPropDefinition { + using SpecifiedValue = CSSValueVariant; + using ComputedValue = CSSValueVariant; +}; + +/** + * CSS "flex-wrap" property. + * https://www.w3.org/TR/css-flexbox-1/#flex-wrap-property + */ +template <> +struct CSSPropDefinition { + enum class Keyword : std::underlying_type_t { + NoWrap = to_underlying(CSSKeyword::NoWrap), + Wrap = to_underlying(CSSKeyword::Wrap), + WrapReverse = to_underlying(CSSKeyword::WrapReverse), + }; + + using SpecifiedValue = CSSValueVariant; + using ComputedValue = CSSValueVariant; +}; + +/** + * CSS gutter properties. + * https://www.w3.org/TR/css-align-3/#column-row-gap + */ +template <> +struct CSSPropDefinition { + enum class Keyword : std::underlying_type_t { + Normal = to_underlying(CSSKeyword::Normal), + }; + + using SpecifiedValue = CSSValueVariant; + using ComputedValue = CSSValueVariant; +}; + +template <> +struct CSSPropDefinition : CSSPropDefinition { +}; + +template <> +struct CSSPropDefinition : CSSPropDefinition {}; + +/** + * CSS sizing properties + * https://www.w3.org/TR/css-sizing-3/#sizing-properties + */ +template <> +struct CSSPropDefinition { + enum class Keyword : std::underlying_type_t { + Auto = to_underlying(CSSKeyword::Auto), + MaxContent = to_underlying(CSSKeyword::MaxContent), + MinContent = to_underlying(CSSKeyword::MinContent), + }; + + using SpecifiedValue = + CSSValueVariant; + using ComputedValue = CSSValueVariant; +}; + +template <> +struct CSSPropDefinition : CSSPropDefinition { +}; + +template <> +struct CSSPropDefinition + : CSSPropDefinition {}; + +template <> +struct CSSPropDefinition + : CSSPropDefinition {}; + +template <> +struct CSSPropDefinition + : CSSPropDefinition {}; + +template <> +struct CSSPropDefinition + : CSSPropDefinition {}; + +/** + * CSS box inset properties + * https://drafts.csswg.org/css-position-3/#insets + */ +template <> +struct CSSPropDefinition { + enum class Keyword : std::underlying_type_t { + Auto = to_underlying(CSSKeyword::Auto), + }; + + using SpecifiedValue = + CSSValueVariant; + using ComputedValue = CSSValueVariant; +}; + +template <> +struct CSSPropDefinition : CSSPropDefinition {}; + +template <> +struct CSSPropDefinition : CSSPropDefinition {}; + +template <> +struct CSSPropDefinition : CSSPropDefinition { +}; + +template <> +struct CSSPropDefinition : CSSPropDefinition {}; + +template <> +struct CSSPropDefinition : CSSPropDefinition {}; + +template <> +struct CSSPropDefinition : CSSPropDefinition {}; + +template <> +struct CSSPropDefinition + : CSSPropDefinition {}; + +template <> +struct CSSPropDefinition + : CSSPropDefinition {}; + +template <> +struct CSSPropDefinition + : CSSPropDefinition {}; + +template <> +struct CSSPropDefinition + : CSSPropDefinition {}; + +template <> +struct CSSPropDefinition + : CSSPropDefinition {}; + +template <> +struct CSSPropDefinition + : CSSPropDefinition {}; + +/** + * 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 + */ +template <> +struct CSSPropDefinition { + enum class Keyword : std::underlying_type_t { + 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), + Start = to_underlying(CSSKeyword::Start), + End = to_underlying(CSSKeyword::End), + }; + + using SpecifiedValue = CSSValueVariant; + using ComputedValue = CSSValueVariant; +}; + +/** + * CSS "margin" properties + * https://www.w3.org/TR/css-box-4/#margins + */ +template <> +struct CSSPropDefinition { + enum class Keyword : std::underlying_type_t { + Auto = to_underlying(CSSKeyword::Auto), + }; + + using SpecifiedValue = + CSSValueVariant; + using ComputedValue = CSSValueVariant; +}; + +template <> +struct CSSPropDefinition + : CSSPropDefinition {}; + +template <> +struct CSSPropDefinition + : CSSPropDefinition {}; + +template <> +struct CSSPropDefinition + : CSSPropDefinition {}; + +template <> +struct CSSPropDefinition + : CSSPropDefinition {}; + +template <> +struct CSSPropDefinition + : CSSPropDefinition {}; + +template <> +struct CSSPropDefinition + : CSSPropDefinition {}; + +template <> +struct CSSPropDefinition + : CSSPropDefinition {}; + +template <> +struct CSSPropDefinition + : CSSPropDefinition {}; + +template <> +struct CSSPropDefinition + : CSSPropDefinition {}; + +template <> +struct CSSPropDefinition + : CSSPropDefinition {}; + +template <> +struct CSSPropDefinition + : CSSPropDefinition {}; + +template <> +struct CSSPropDefinition + : CSSPropDefinition {}; + +template <> +struct CSSPropDefinition + : CSSPropDefinition {}; + +template <> +struct CSSPropDefinition + : CSSPropDefinition {}; + +/** + * CSS "overflow" property. + * https://www.w3.org/TR/css-overflow-3/#overflow-control + */ +template <> +struct CSSPropDefinition { + enum class Keyword : std::underlying_type_t { + Auto = to_underlying(CSSKeyword::Auto), + Clip = to_underlying(CSSKeyword::Clip), + Hidden = to_underlying(CSSKeyword::Hidden), + Scroll = to_underlying(CSSKeyword::Scroll), + Visible = to_underlying(CSSKeyword::Visible), + }; + + using SpecifiedValue = CSSValueVariant; + using ComputedValue = CSSValueVariant; +}; + +/** + * CSS padding properties + * https://www.w3.org/TR/css-box-4/#paddings + */ +template <> +struct CSSPropDefinition { + using SpecifiedValue = + CSSValueVariant; + using ComputedValue = CSSValueVariant; +}; + +template <> +struct CSSPropDefinition + : CSSPropDefinition {}; + +template <> +struct CSSPropDefinition + : CSSPropDefinition {}; + +template <> +struct CSSPropDefinition + : CSSPropDefinition {}; + +template <> +struct CSSPropDefinition + : CSSPropDefinition {}; + +template <> +struct CSSPropDefinition + : CSSPropDefinition {}; + +template <> +struct CSSPropDefinition + : CSSPropDefinition {}; + +template <> +struct CSSPropDefinition + : CSSPropDefinition {}; + +template <> +struct CSSPropDefinition + : CSSPropDefinition {}; + +template <> +struct CSSPropDefinition + : CSSPropDefinition {}; + +template <> +struct CSSPropDefinition + : CSSPropDefinition {}; + +template <> +struct CSSPropDefinition + : CSSPropDefinition {}; + +template <> +struct CSSPropDefinition + : CSSPropDefinition {}; + +template <> +struct CSSPropDefinition + : CSSPropDefinition {}; + +template <> +struct CSSPropDefinition + : CSSPropDefinition {}; + +/** + * CSS "position" property. + * https://www.w3.org/TR/css-position-3/#position-property + */ +template <> +struct CSSPropDefinition { + enum class Keyword : std::underlying_type_t { + Static = to_underlying(CSSKeyword::Static), + Relative = to_underlying(CSSKeyword::Relative), + Absolute = to_underlying(CSSKeyword::Absolute), + Fixed = to_underlying(CSSKeyword::Fixed), + Sticky = to_underlying(CSSKeyword::Sticky), + }; + + using SpecifiedValue = CSSValueVariant; + using ComputedValue = CSSValueVariant; +}; + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/css/CSSTokenizer.h b/packages/react-native/ReactCommon/react/renderer/css/CSSTokenizer.h new file mode 100644 index 000000000000..85a9bcda6122 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/css/CSSTokenizer.h @@ -0,0 +1,263 @@ +/* + * 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 + +namespace facebook::react { + +/** + * One of the tokens defined as part of + * https://www.w3.org/TR/css-syntax-3/#tokenizer-definitions + */ +enum class CSSTokenType { + Delim, + Dimension, + EndOfFile, + Ident, + Number, + Percentage, + WhiteSpace, +}; + +/* + * Represents one of the syntactic CSS tokens as provided by + * https://www.w3.org/TR/css-syntax-3/#tokenization + */ +class CSSToken { + public: + explicit constexpr CSSToken(CSSTokenType type) : type_(type) {} + constexpr CSSToken(CSSTokenType type, std::string_view value) + : type_{type}, stringValue_{value} {} + constexpr CSSToken(CSSTokenType type, float value) + : type_{type}, numericValue_{value} {} + constexpr CSSToken(CSSTokenType type, float value, std::string_view unit) + : type_{type}, numericValue_{value}, unit_{unit} {} + + constexpr CSSToken(const CSSToken& other) = default; + constexpr CSSToken(CSSToken&& other) = default; + constexpr CSSToken& operator=(const CSSToken& other) = default; + constexpr CSSToken& operator=(CSSToken&& other) = default; + + constexpr CSSTokenType type() const { + return type_; + } + + constexpr std::string_view stringValue() const { + return stringValue_; + } + + constexpr float numericValue() const { + return numericValue_; + } + + constexpr std::string_view unit() const { + return unit_; + } + + constexpr bool operator==(const CSSToken& other) const = default; + + private: + CSSTokenType type_; + std::string_view stringValue_; + float numericValue_{0.0f}; + std::string_view unit_; +}; + +/** + * A minimal tokenizer for a subset of CSS syntax. + * `auto`). + * + * This is based on the W3C CSS Syntax specification, with simplifications made + * for syntax which React Native does not attempt to support. + * https://www.w3.org/TR/css-syntax-3/#tokenizing-and-parsing + */ +class CSSTokenizer { + public: + explicit constexpr CSSTokenizer(std::string_view characters) + : remainingCharacters_{characters} {} + + constexpr CSSToken next() { + // https://www.w3.org/TR/css-syntax-3/#token-diagrams + char nextChar = peek(); + if (isWhitespace(nextChar)) { + return consumeWhitespace(); + } else if (nextChar == '+') { + if (isDigit(peekNext())) { + return consumeNumeric(); + } else { + return consumeDelim(); + } + } else if (nextChar == '-') { + if (isDigit(peekNext())) { + return consumeNumeric(); + } else { + return consumeDelim(); + } + } else if (isDigit(nextChar)) { + return consumeNumeric(); + } else if (isIdentStart(nextChar)) { + return consumeIdent(); + } else if (nextChar == '\0') { + return CSSToken{CSSTokenType::EndOfFile}; + } else { + return consumeDelim(); + } + } + + private: + constexpr char peek() const { + auto index = position_; + return index >= remainingCharacters_.size() ? '\0' + : remainingCharacters_[index]; + } + constexpr char peekNext() const { + auto index = position_ + 1; + return index >= remainingCharacters_.size() ? '\0' + : remainingCharacters_[index]; + } + + constexpr void advance() { + position_ += 1; + } + + constexpr CSSToken consumeDelim() { + advance(); + return {CSSTokenType::Delim, consumeRunningValue()}; + } + + constexpr CSSToken consumeWhitespace() { + while (isWhitespace(peek())) { + advance(); + } + + consumeRunningValue(); + return CSSToken{CSSTokenType::WhiteSpace}; + } + + constexpr CSSToken consumeNumber() { + // https://www.w3.org/TR/css-syntax-3/#consume-number + // https://www.w3.org/TR/css-syntax-3/#convert-a-string-to-a-number + int32_t signPart = 1.0; + if (peek() == '+' || peek() == '-') { + if (peek() == '-') { + signPart = -1.0; + } + advance(); + } + + int32_t intPart = 0; + while (isDigit(peek())) { + intPart = intPart * 10 + (peek() - '0'); + advance(); + } + + int32_t fractionalPart = 0; + int32_t fractionDigits = 0; + if (peek() == '.') { + advance(); + while (isDigit(peek())) { + fractionalPart = fractionalPart * 10 + (peek() - '0'); + fractionDigits++; + advance(); + } + } + + int32_t exponentSign = 1.0; + int32_t exponentPart = 0; + if (peek() == 'e' || peek() == 'E') { + advance(); + if (peek() == '+' || peek() == '-') { + if (peek() == '-') { + exponentSign = -1.0; + } + advance(); + } + + while (isDigit(peek())) { + exponentPart = exponentPart * 10 + (peek() - '0'); + advance(); + } + } + float value; + if (exponentPart == 0 && fractionalPart == 0) { + value = static_cast(signPart * intPart); + } else { + value = static_cast( + signPart * + (intPart + (fractionalPart * std::pow(10, -fractionDigits))) * + std::pow(10, exponentSign * exponentPart)); + } + + consumeRunningValue(); + return {CSSTokenType::Number, value}; + } + + constexpr CSSToken consumeNumeric() { + // https://www.w3.org/TR/css-syntax-3/#consume-numeric-token + auto numberToken = consumeNumber(); + + if (isIdent(peek())) { + auto ident = consumeIdent(); + return { + CSSTokenType::Dimension, + numberToken.numericValue(), + ident.stringValue()}; + } else if (peek() == '%') { + advance(); + consumeRunningValue(); + return {CSSTokenType::Percentage, numberToken.numericValue()}; + } else { + return numberToken; + } + } + + constexpr CSSToken consumeIdent() { + // https://www.w3.org/TR/css-syntax-3/#consume-an-ident-sequence + while (isIdent(peek())) { + advance(); + } + + return {CSSTokenType::Ident, consumeRunningValue()}; + } + + constexpr std::string_view consumeRunningValue() { + auto next = remainingCharacters_.substr(0, position_); + remainingCharacters_ = remainingCharacters_.substr(next.size()); + position_ = 0; + return next; + } + + static constexpr bool isDigit(char c) { + // https://www.w3.org/TR/css-syntax-3/#digit + return c >= '0' && c <= '9'; + } + + static constexpr bool isIdentStart(char c) { + // https://www.w3.org/TR/css-syntax-3/#ident-start-code-point + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || + static_cast(c) > 0x80; + } + + static constexpr bool isIdent(char c) { + { + // https://www.w3.org/TR/css-syntax-3/#ident-code-point + return isIdentStart(c) || isDigit(c) || c == '-'; + } + } + + static constexpr bool isWhitespace(char c) { + // https://www.w3.org/TR/css-syntax-3/#whitespace + return c == ' ' || c == '\t' || c == '\r' || c == '\n'; + } + + std::string_view remainingCharacters_; + size_t position_{0}; +}; +} // namespace facebook::react 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 000000000000..4663dd9e8189 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/css/CSSValue.h @@ -0,0 +1,228 @@ +/* + * 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-4/#component-types + */ +enum class CSSValueType : uint8_t { + CSSWideKeyword, + Keyword, + Length, + Number, + Percentage, + Ratio, +}; + +/** + * Concrete representation for a CSS basic data type, or keywords + * https://www.w3.org/TR/css-values-4/#component-types + */ +template +concept CSSDataType = 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-4/#lengths + */ +struct CSSLength { + float value{}; + CSSLengthUnit unit{CSSLengthUnit::Px}; +}; +#pragma pack(pop) + +/** + * Representation of CSS data type + * https://www.w3.org/TR/css-values-4/#percentages + */ +struct CSSPercentage { + float value{}; +}; + +/** + * Representation of CSS data type + * https://www.w3.org/TR/css-values-4/#numbers + */ +struct CSSNumber { + float value{}; +}; + +/** + * Representation of CSS data type + * https://www.w3.org/TR/css-values-4/#ratios + */ +struct CSSRatio { + float numerator{}; + float denominator{}; +}; + +/** + * CSSValueVariant represents a CSS component value: + * https://www.w3.org/TR/css-values-4/#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(); + } + + template + static constexpr bool hasKeywordSet() { + if constexpr (CSSKeywordSet && !std::is_same_v) { + return true; + } else if constexpr (sizeof...(Rest) == 0) { + return false; + } else { + return hasKeywordSet(); + } + } + + template + struct PackedKeywordSet { + using Type = void; + }; + + template + struct PackedKeywordSet { + using Type = std::conditional_t< + hasKeywordSet(), + T, + typename PackedKeywordSet::Type>; + }; + + public: + using Keyword = typename PackedKeywordSet::Type; + + constexpr CSSValueVariant() requires(canRepresent()) + : CSSValueVariant(CSSValueType::CSSWideKeyword, CSSWideKeyword::Unset) {} + + static constexpr CSSValueVariant cssWideKeyword(CSSWideKeyword keyword) { + return CSSValueVariant( + CSSValueType::CSSWideKeyword, CSSWideKeyword{keyword}); + } + + template + static constexpr CSSValueVariant keyword(KeywordT keyword) requires( + canRepresent()) { + 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}); + } + + static constexpr CSSValueVariant ratio( + float numerator, + float denominator) requires(canRepresent()) { + return CSSValueVariant( + CSSValueType::Ratio, CSSRatio{numerator, denominator}); + } + + constexpr CSSValueType type() const { + return type_; + } + + constexpr CSSWideKeyword getCSSWideKeyword() const + requires(canRepresent()) { + return getIf(); + } + + constexpr Keyword getKeyword() const + requires(hasKeywordSet()) { + 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 CSSRatio getRatio() const requires(canRepresent()) { + return getIf(); + } + + constexpr operator bool() const requires(canRepresent()) { + return *this != CSSValueVariant{}; + } + + constexpr bool operator==(const CSSValueVariant& rhs) const = default; + + private: + constexpr CSSValueVariant(CSSValueType type, CSSDataType 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); +static_assert(sizeof(CSSValueVariant) == 9); + +} // 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 000000000000..280dc43e8beb --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/css/tests/CSSParserTest.cpp @@ -0,0 +1,298 @@ +/* + * 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 = parseCSSComponentValue(""); + EXPECT_EQ(emptyValue.type(), CSSValueType::CSSWideKeyword); + EXPECT_EQ(emptyValue.getCSSWideKeyword(), CSSWideKeyword::Unset); + + auto autoValue = parseCSSComponentValue("auto"); + EXPECT_EQ(autoValue.type(), CSSValueType::Keyword); + EXPECT_EQ(autoValue.getKeyword(), CSSKeyword::Auto); + + auto autoCapsValue = + parseCSSComponentValue("AuTO"); + EXPECT_EQ(autoCapsValue.type(), CSSValueType::Keyword); + EXPECT_EQ(autoCapsValue.getKeyword(), CSSKeyword::Auto); + + auto autoDisallowedValue = parseCSSComponentValue("auto"); + EXPECT_EQ(autoDisallowedValue.type(), CSSValueType::CSSWideKeyword); + EXPECT_EQ(autoDisallowedValue.getCSSWideKeyword(), CSSWideKeyword::Unset); + + auto whitespaceValue = + parseCSSComponentValue(" flex-start "); + EXPECT_EQ(whitespaceValue.type(), CSSValueType::Keyword); + EXPECT_EQ(whitespaceValue.getKeyword(), CSSKeyword::FlexStart); + + auto badIdentValue = + parseCSSComponentValue("bad"); + EXPECT_EQ(badIdentValue.type(), CSSValueType::CSSWideKeyword); + EXPECT_EQ(badIdentValue.getCSSWideKeyword(), CSSWideKeyword::Unset); + + auto pxValue = parseCSSComponentValue("20px"); + EXPECT_EQ(pxValue.type(), CSSValueType::CSSWideKeyword); + EXPECT_EQ(pxValue.getCSSWideKeyword(), CSSWideKeyword::Unset); + + auto multiValue = parseCSSComponentValue("auto flex-start"); + EXPECT_EQ(multiValue.type(), CSSValueType::CSSWideKeyword); + EXPECT_EQ(multiValue.getCSSWideKeyword(), CSSWideKeyword::Unset); +} + +TEST(CSSParser, length_values) { + auto emptyValue = parseCSSComponentValue(""); + EXPECT_EQ(emptyValue.type(), CSSValueType::CSSWideKeyword); + EXPECT_EQ(emptyValue.getCSSWideKeyword(), CSSWideKeyword::Unset); + + auto autoValue = + parseCSSComponentValue("auto"); + EXPECT_EQ(autoValue.type(), CSSValueType::Keyword); + EXPECT_EQ(autoValue.getKeyword(), CSSKeyword::Auto); + + auto pxValue = parseCSSComponentValue("20px"); + EXPECT_EQ(pxValue.type(), CSSValueType::Length); + EXPECT_EQ(pxValue.getLength().value, 20.0f); + EXPECT_EQ(pxValue.getLength().unit, CSSLengthUnit::Px); + + auto cmValue = parseCSSComponentValue("453cm"); + EXPECT_EQ(cmValue.type(), CSSValueType::Length); + EXPECT_EQ(cmValue.getLength().value, 453.0f); + EXPECT_EQ(cmValue.getLength().unit, CSSLengthUnit::Cm); + + auto unitlessZeroValue = + parseCSSComponentValue("0"); + EXPECT_EQ(unitlessZeroValue.type(), CSSValueType::Length); + EXPECT_EQ(unitlessZeroValue.getLength().value, 0.0f); + EXPECT_EQ(unitlessZeroValue.getLength().unit, CSSLengthUnit::Px); + + auto unitlessNonzeroValue = + parseCSSComponentValue("123"); + EXPECT_EQ(unitlessNonzeroValue.type(), CSSValueType::CSSWideKeyword); + EXPECT_EQ(unitlessNonzeroValue.getCSSWideKeyword(), CSSWideKeyword::Unset); + + auto pctValue = parseCSSComponentValue("-40%"); + EXPECT_EQ(pctValue.type(), CSSValueType::CSSWideKeyword); + EXPECT_EQ(pctValue.getCSSWideKeyword(), CSSWideKeyword::Unset); +} + +TEST(CSSParser, length_percentage_values) { + auto emptyValue = + parseCSSComponentValue(""); + EXPECT_EQ(emptyValue.type(), CSSValueType::CSSWideKeyword); + EXPECT_EQ(emptyValue.getCSSWideKeyword(), CSSWideKeyword::Unset); + + auto autoValue = parseCSSComponentValue< + CSSWideKeyword, + CSSKeyword, + CSSLength, + CSSPercentage>("auto"); + EXPECT_EQ(autoValue.type(), CSSValueType::Keyword); + EXPECT_EQ(autoValue.getKeyword(), CSSKeyword::Auto); + + auto pxValue = + parseCSSComponentValue("20px"); + EXPECT_EQ(pxValue.type(), CSSValueType::Length); + EXPECT_EQ(pxValue.getLength().value, 20.0f); + EXPECT_EQ(pxValue.getLength().unit, CSSLengthUnit::Px); + + auto pctValue = + parseCSSComponentValue("-40%"); + EXPECT_EQ(pctValue.type(), CSSValueType::Percentage); + EXPECT_EQ(pctValue.getPercentage().value, -40.0f); +} + +TEST(CSSParser, number_values) { + auto emptyValue = parseCSSComponentValue(""); + EXPECT_EQ(emptyValue.type(), CSSValueType::CSSWideKeyword); + EXPECT_EQ(emptyValue.getCSSWideKeyword(), CSSWideKeyword::Unset); + + auto inheritValue = + parseCSSComponentValue("inherit"); + EXPECT_EQ(inheritValue.type(), CSSValueType::CSSWideKeyword); + EXPECT_EQ(inheritValue.getCSSWideKeyword(), CSSWideKeyword::Inherit); + + auto pxValue = + parseCSSComponentValue("20px"); + EXPECT_EQ(pxValue.type(), CSSValueType::CSSWideKeyword); + EXPECT_EQ(pxValue.getCSSWideKeyword(), CSSWideKeyword::Unset); + + auto numberValue = + parseCSSComponentValue("123.456"); + EXPECT_EQ(numberValue.type(), CSSValueType::Number); + EXPECT_EQ(numberValue.getNumber().value, 123.456f); + + auto unitlessZeroValue = + parseCSSComponentValue("0"); + EXPECT_EQ(unitlessZeroValue.type(), CSSValueType::Number); + EXPECT_EQ(unitlessZeroValue.getNumber().value, 0.0f); +} + +TEST(CSSParser, ratio_values) { + auto emptyValue = parseCSSComponentValue(""); + EXPECT_EQ(emptyValue.type(), CSSValueType::CSSWideKeyword); + EXPECT_EQ(emptyValue.getCSSWideKeyword(), CSSWideKeyword::Unset); + + auto validRatio = parseCSSComponentValue("16/9"); + EXPECT_EQ(validRatio.type(), CSSValueType::Ratio); + EXPECT_EQ(validRatio.getRatio().numerator, 16.0f); + EXPECT_EQ(validRatio.getRatio().denominator, 9.0f); + + auto validRatioWithWhitespace = + parseCSSComponentValue("16 / 9"); + EXPECT_EQ(validRatioWithWhitespace.type(), CSSValueType::Ratio); + EXPECT_EQ(validRatioWithWhitespace.getRatio().numerator, 16.0f); + EXPECT_EQ(validRatioWithWhitespace.getRatio().denominator, 9.0f); + + auto singleNumberRatio = + parseCSSComponentValue("16"); + EXPECT_EQ(singleNumberRatio.type(), CSSValueType::Ratio); + EXPECT_EQ(singleNumberRatio.getRatio().numerator, 16.0f); + EXPECT_EQ(singleNumberRatio.getRatio().denominator, 1.0f); + + auto fractionalNumber = + parseCSSComponentValue("16.5"); + EXPECT_EQ(fractionalNumber.type(), CSSValueType::Ratio); + EXPECT_EQ(fractionalNumber.getRatio().numerator, 16.5f); + EXPECT_EQ(fractionalNumber.getRatio().denominator, 1.0f); + + auto negativeNumber = parseCSSComponentValue("-16"); + EXPECT_EQ(negativeNumber.type(), CSSValueType::CSSWideKeyword); + EXPECT_EQ(negativeNumber.getCSSWideKeyword(), CSSWideKeyword::Unset); + + auto missingDenominator = + parseCSSComponentValue("16/"); + EXPECT_EQ(missingDenominator.type(), CSSValueType::CSSWideKeyword); + EXPECT_EQ(missingDenominator.getCSSWideKeyword(), CSSWideKeyword::Unset); + + auto negativeNumerator = + parseCSSComponentValue("-16/9"); + EXPECT_EQ(negativeNumerator.type(), CSSValueType::CSSWideKeyword); + EXPECT_EQ(negativeNumerator.getCSSWideKeyword(), CSSWideKeyword::Unset); + + auto negativeDenominator = + parseCSSComponentValue("16/-9"); + EXPECT_EQ(negativeDenominator.type(), CSSValueType::CSSWideKeyword); + EXPECT_EQ(negativeDenominator.getCSSWideKeyword(), CSSWideKeyword::Unset); + + auto fractionalNumerator = + parseCSSComponentValue("16.5/9"); + EXPECT_EQ(fractionalNumerator.type(), CSSValueType::Ratio); + EXPECT_EQ(fractionalNumerator.getRatio().numerator, 16.5f); + EXPECT_EQ(fractionalNumerator.getRatio().denominator, 9.0f); + + auto fractionalDenominator = + parseCSSComponentValue("16/9.5"); + EXPECT_EQ(fractionalDenominator.type(), CSSValueType::Ratio); + EXPECT_EQ(fractionalDenominator.getRatio().numerator, 16.0f); + EXPECT_EQ(fractionalDenominator.getRatio().denominator, 9.5f); + + auto degenerateRatio = parseCSSComponentValue("0"); + EXPECT_EQ(degenerateRatio.type(), CSSValueType::CSSWideKeyword); + EXPECT_EQ(degenerateRatio.getCSSWideKeyword(), CSSWideKeyword::Unset); +} + +TEST(CSSParser, number_ratio_values) { + auto emptyValue = + parseCSSComponentValue(""); + EXPECT_EQ(emptyValue.type(), CSSValueType::CSSWideKeyword); + EXPECT_EQ(emptyValue.getCSSWideKeyword(), CSSWideKeyword::Unset); + + auto validRatio = + parseCSSComponentValue("16/9"); + EXPECT_EQ(validRatio.type(), CSSValueType::Ratio); + EXPECT_EQ(validRatio.getRatio().numerator, 16.0f); + EXPECT_EQ(validRatio.getRatio().denominator, 9.0f); + + auto validRatioWithWhitespace = + parseCSSComponentValue("16 / 9"); + EXPECT_EQ(validRatioWithWhitespace.type(), CSSValueType::Ratio); + EXPECT_EQ(validRatioWithWhitespace.getRatio().numerator, 16.0f); + EXPECT_EQ(validRatioWithWhitespace.getRatio().denominator, 9.0f); + + auto singleNumberRatio = + parseCSSComponentValue("16"); + EXPECT_EQ(singleNumberRatio.type(), CSSValueType::Ratio); + EXPECT_EQ(singleNumberRatio.getRatio().numerator, 16.0f); + EXPECT_EQ(singleNumberRatio.getRatio().denominator, 1.0f); + + auto fractionalNumber = + parseCSSComponentValue("16.5"); + EXPECT_EQ(fractionalNumber.type(), CSSValueType::Ratio); + EXPECT_EQ(fractionalNumber.getRatio().numerator, 16.5f); + EXPECT_EQ(singleNumberRatio.getRatio().denominator, 1.0f); + + auto negativeNumber = + parseCSSComponentValue("-16"); + EXPECT_EQ(negativeNumber.type(), CSSValueType::Number); + EXPECT_EQ(negativeNumber.getNumber().value, -16.0f); + + auto missingDenominator = + parseCSSComponentValue("16/"); + EXPECT_EQ(missingDenominator.type(), CSSValueType::CSSWideKeyword); + EXPECT_EQ(missingDenominator.getCSSWideKeyword(), CSSWideKeyword::Unset); + + auto negativeNumerator = + parseCSSComponentValue("-16/9"); + EXPECT_EQ(negativeNumerator.type(), CSSValueType::CSSWideKeyword); + EXPECT_EQ(negativeNumerator.getCSSWideKeyword(), CSSWideKeyword::Unset); + + auto negativeDenominator = + parseCSSComponentValue("16/-9"); + EXPECT_EQ(negativeDenominator.type(), CSSValueType::CSSWideKeyword); + EXPECT_EQ(negativeDenominator.getCSSWideKeyword(), CSSWideKeyword::Unset); + + auto fractionalNumerator = + parseCSSComponentValue("16.5/9"); + EXPECT_EQ(fractionalNumerator.type(), CSSValueType::Ratio); + EXPECT_EQ(fractionalNumerator.getRatio().numerator, 16.5f); + EXPECT_EQ(fractionalNumerator.getRatio().denominator, 9.0f); + + auto fractionalDenominator = + parseCSSComponentValue("16/9.5"); + EXPECT_EQ(fractionalDenominator.type(), CSSValueType::Ratio); + EXPECT_EQ(fractionalDenominator.getRatio().numerator, 16.0f); + EXPECT_EQ(fractionalDenominator.getRatio().denominator, 9.5f); + + auto degenerateRatio = + parseCSSComponentValue("0"); + EXPECT_EQ(degenerateRatio.type(), CSSValueType::Number); + EXPECT_EQ(degenerateRatio.getNumber().value, 0.0f); +} + +TEST(CSSParser, parse_prop) { + auto emptyValue = parseCSSProp(""); + EXPECT_EQ(emptyValue.type(), CSSValueType::CSSWideKeyword); + EXPECT_EQ(emptyValue.getCSSWideKeyword(), CSSWideKeyword::Unset); + + auto numberWidthValue = parseCSSProp("50px"); + EXPECT_EQ(numberWidthValue.type(), CSSValueType::Length); + EXPECT_EQ(numberWidthValue.getLength().value, 50.0f); + EXPECT_EQ(numberWidthValue.getLength().unit, CSSLengthUnit::Px); + + auto percentWidthValue = parseCSSProp("50%"); + EXPECT_EQ(percentWidthValue.type(), CSSValueType::Percentage); + EXPECT_EQ(percentWidthValue.getPercentage().value, 50.0f); + + auto autoWidthValue = parseCSSProp("auto"); + EXPECT_EQ(autoWidthValue.type(), CSSValueType::Keyword); + EXPECT_EQ( + autoWidthValue.getKeyword(), CSSAllowedKeywords::Auto); + + auto invalidWidthValue = parseCSSProp("50"); + EXPECT_EQ(invalidWidthValue.type(), CSSValueType::CSSWideKeyword); + EXPECT_EQ(invalidWidthValue.getCSSWideKeyword(), CSSWideKeyword::Unset); + + auto invalidKeywordValue = parseCSSProp("flex-start"); + EXPECT_EQ(invalidKeywordValue.type(), CSSValueType::CSSWideKeyword); + EXPECT_EQ(invalidKeywordValue.getCSSWideKeyword(), CSSWideKeyword::Unset); +} + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/tests/CSSTokenizerTest.cpp b/packages/react-native/ReactCommon/react/renderer/css/tests/CSSTokenizerTest.cpp similarity index 88% rename from packages/react-native/ReactCommon/react/renderer/components/view/tests/CSSTokenizerTest.cpp rename to packages/react-native/ReactCommon/react/renderer/css/tests/CSSTokenizerTest.cpp index 9a11a79b62cc..e2ac957e3a1f 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/tests/CSSTokenizerTest.cpp +++ b/packages/react-native/ReactCommon/react/renderer/css/tests/CSSTokenizerTest.cpp @@ -6,8 +6,7 @@ */ #include -#include -#include +#include namespace facebook::react { @@ -105,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}}); } @@ -123,6 +122,17 @@ TEST(CSSTokenizer, mixed_values) { CSSToken{CSSTokenType::EndOfFile}}); } +TEST(CSSTokenizer, ratio_values) { + expectTokens( + "16 / 9", + {CSSToken{CSSTokenType::Number, 16.0f}, + CSSToken{CSSTokenType::WhiteSpace}, + CSSToken{CSSTokenType::Delim, "/"}, + CSSToken{CSSTokenType::WhiteSpace}, + CSSToken{CSSTokenType::Number, 9.0f}, + CSSToken{CSSTokenType::EndOfFile}}); +} + TEST(CSSTokenizer, invalid_values) { expectTokens( "100*", 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 000000000000..0f14add9652e --- /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 fe2721d4bc01..20c5b341e188 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 000000000000..6d8d19d2b345 --- /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