From 750ac91d2c4f526a7bb9982c3e301a2dde690f6a Mon Sep 17 00:00:00 2001 From: Nick Gerleman Date: Tue, 13 Feb 2024 17:02:46 -0800 Subject: [PATCH 1/4] Temporary Commit at 2/2/2024, 8:26:58 PM Differential Revision: D53377527, fbshipit-source-id: 9540b53c6feaf8f275b4754cc0da0df9f5bf6b6d --- .../ReactAndroid/build.gradle.kts | 4 ++++ .../cmake-utils/ReactNative-application.cmake | 2 ++ .../ReactAndroid/src/main/jni/CMakeLists.txt | 1 + .../src/main/jni/react/fabric/CMakeLists.txt | 1 + .../ReactCommon/React-Fabric.podspec | 5 +++++ .../react/renderer/css/CMakeLists.txt | 20 +++++++++++++++++++ .../{components/view => css}/CSSTokenizer.cpp | 2 +- .../{components/view => css}/CSSTokenizer.h | 0 .../view => css}/tests/CSSTokenizerTest.cpp | 3 +-- 9 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 packages/react-native/ReactCommon/react/renderer/css/CMakeLists.txt rename packages/react-native/ReactCommon/react/renderer/{components/view => css}/CSSTokenizer.cpp (98%) rename packages/react-native/ReactCommon/react/renderer/{components/view => css}/CSSTokenizer.h (100%) rename packages/react-native/ReactCommon/react/renderer/{components/view => css}/tests/CSSTokenizerTest.cpp (98%) 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/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/components/view/CSSTokenizer.cpp b/packages/react-native/ReactCommon/react/renderer/css/CSSTokenizer.cpp similarity index 98% rename from packages/react-native/ReactCommon/react/renderer/components/view/CSSTokenizer.cpp rename to packages/react-native/ReactCommon/react/renderer/css/CSSTokenizer.cpp index 945d395e4422..99ddce3a2098 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/CSSTokenizer.cpp +++ b/packages/react-native/ReactCommon/react/renderer/css/CSSTokenizer.cpp @@ -9,7 +9,7 @@ #include #include -#include +#include namespace facebook::react { diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/CSSTokenizer.h b/packages/react-native/ReactCommon/react/renderer/css/CSSTokenizer.h similarity index 100% rename from packages/react-native/ReactCommon/react/renderer/components/view/CSSTokenizer.h rename to packages/react-native/ReactCommon/react/renderer/css/CSSTokenizer.h 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 98% 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..cac2e8885b88 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 { From 07bb3a5270318e0bd7294c11b47aff2d2de46c41 Mon Sep 17 00:00:00 2001 From: Nick Gerleman Date: Tue, 13 Feb 2024 17:02:46 -0800 Subject: [PATCH 2/4] Add CSSValue and parseCSSValue() Summary: This adds: 1. `CSSValue`: A union-y type, mapping to a collection of CSS data types. The aim here is to more closely model the data types after the CSS spec, to allow RN to store them correctly, while not taking up too much space. These types will form the foundation of Yoga prop storage (and probably some other props down the line), so compactness is a priority. 2. `parseCSSValue()`: This uses the previously added Tokenizer, along with parsing rules, to be able to parse a single component value, into a literal keyword, ``, ``, ``, or ``. This will be wired to the props parsing infrastructure. Changelog: [Internal] Differential Revision: D53342595 fbshipit-source-id: 436078dfea7aafeae7e4a55c732aabb1d73220dc --- .../react/renderer/css/CSSKeywords.h | 419 ++++++++++++++++++ .../react/renderer/css/CSSLengthUnit.h | 165 +++++++ .../react/renderer/css/CSSParser.h | 91 ++++ .../react/renderer/css/CSSTokenizer.cpp | 2 +- .../react/renderer/css/CSSTokenizer.h | 2 +- .../ReactCommon/react/renderer/css/CSSValue.h | 169 +++++++ .../renderer/css/tests/CSSParserTest.cpp | 125 ++++++ .../renderer/css/tests/CSSTokenizerTest.cpp | 4 +- .../ReactCommon/react/utils/PackTraits.h | 40 ++ .../ReactCommon/react/utils/fnv1a.h | 7 +- .../ReactCommon/react/utils/to_underlying.h | 22 + 11 files changed, 1041 insertions(+), 5 deletions(-) create mode 100644 packages/react-native/ReactCommon/react/renderer/css/CSSKeywords.h create mode 100644 packages/react-native/ReactCommon/react/renderer/css/CSSLengthUnit.h create mode 100644 packages/react-native/ReactCommon/react/renderer/css/CSSParser.h create mode 100644 packages/react-native/ReactCommon/react/renderer/css/CSSValue.h create mode 100644 packages/react-native/ReactCommon/react/renderer/css/tests/CSSParserTest.cpp create mode 100644 packages/react-native/ReactCommon/react/utils/PackTraits.h create mode 100644 packages/react-native/ReactCommon/react/utils/to_underlying.h 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..1eecb1558cbd --- /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 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..c31cbf26fd28 --- /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 99ddce3a2098..3443786de5f6 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 647e9bd87899..400d2916cd14 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 000000000000..6f6659c97a4f --- /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 000000000000..ecde38491219 --- /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 cac2e8885b88..e40d289c6773 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 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 From e2d0f0d27404e158ac0d6726672adc7581d2f4c4 Mon Sep 17 00:00:00 2001 From: Nick Gerleman Date: Tue, 13 Feb 2024 17:02:46 -0800 Subject: [PATCH 3/4] Add Support for CSS Parser Summary: Adds support for parsing and storing the CSS basic data type. Changelog: [Internal] Differential Revision: D53457930 fbshipit-source-id: b0f6e1203a5f3aa636a4c59d4ea6c69ef475606b --- .../react/renderer/css/CSSKeywords.h | 2 +- .../react/renderer/css/CSSParser.h | 208 ++++++++++++---- .../react/renderer/css/CSSTokenizer.cpp | 187 --------------- .../react/renderer/css/CSSTokenizer.h | 226 +++++++++++++++--- .../ReactCommon/react/renderer/css/CSSValue.h | 40 +++- .../renderer/css/tests/CSSParserTest.cpp | 180 ++++++++++++-- .../renderer/css/tests/CSSTokenizerTest.cpp | 11 + 7 files changed, 547 insertions(+), 307 deletions(-) delete mode 100644 packages/react-native/ReactCommon/react/renderer/css/CSSTokenizer.cpp diff --git a/packages/react-native/ReactCommon/react/renderer/css/CSSKeywords.h b/packages/react-native/ReactCommon/react/renderer/css/CSSKeywords.h index 1eecb1558cbd..d7606b51f202 100644 --- a/packages/react-native/ReactCommon/react/renderer/css/CSSKeywords.h +++ b/packages/react-native/ReactCommon/react/renderer/css/CSSKeywords.h @@ -19,7 +19,7 @@ namespace facebook::react { /** * One of any predefined CSS keywords. - * https://www.w3.org/TR/css-values-3/#keywords + * https://www.w3.org/TR/css-values-4/#keywords */ enum class CSSKeyword : uint8_t { Absolute, diff --git a/packages/react-native/ReactCommon/react/renderer/css/CSSParser.h b/packages/react-native/ReactCommon/react/renderer/css/CSSParser.h index c31cbf26fd28..6c31b259ab13 100644 --- a/packages/react-native/ReactCommon/react/renderer/css/CSSParser.h +++ b/packages/react-native/ReactCommon/react/renderer/css/CSSParser.h @@ -18,74 +18,176 @@ 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); +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 (auto keyword = + parseCSSKeyword(consumeToken().stringValue())) { + return CSSValueT::keyword(*keyword); + } + return {}; + } + + template + constexpr std::optional consumeDimensionToken() { + if constexpr (traits::containsType()) { + if (auto unit = parseCSSLengthUnit(peek().unit())) { + return CSSValueT::length(consumeToken().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); + } + 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); } - break; - default: - break; + } + + 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 {}; } - return CSSValueT{}; -} + + 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. + * 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 -CSSValueVariant parseCSSValue(std::string_view css) { - CSSTokenizer tokenizer(css); - - auto token = tokenizer.next(); - while (token.type() == CSSTokenType::WhiteSpace) { - token = tokenizer.next(); - } +template +constexpr CSSValueVariant parseCSSComponentValue( + std::string_view css) { + detail::CSSParser parser(css); - auto value = detail::consumeComponentValue(token); + parser.consumeWhitespace(); + auto value = parser.consumeComponentValue(); + parser.consumeWhitespace(); - token = tokenizer.next(); - while (token.type() == CSSTokenType::WhiteSpace) { - token = tokenizer.next(); - } - if (token.type() == CSSTokenType::EndOfFile) { + if (parser.hasMoreTokens()) { + return {}; + } else { 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 deleted file mode 100644 index 3443786de5f6..000000000000 --- a/packages/react-native/ReactCommon/react/renderer/css/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::Percentage, 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/css/CSSTokenizer.h b/packages/react-native/ReactCommon/react/renderer/css/CSSTokenizer.h index 400d2916cd14..85a9bcda6122 100644 --- a/packages/react-native/ReactCommon/react/renderer/css/CSSTokenizer.h +++ b/packages/react-native/ReactCommon/react/renderer/css/CSSTokenizer.h @@ -7,6 +7,7 @@ #pragma once +#include #include namespace facebook::react { @@ -25,37 +26,42 @@ enum class CSSTokenType { WhiteSpace, }; -struct CSSToken { - explicit CSSToken(CSSTokenType type) : type_(type) {} - CSSToken(CSSTokenType type, std::string_view value) +/* + * 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} {} - CSSToken(CSSTokenType type, float value) + constexpr CSSToken(CSSTokenType type, float value) : type_{type}, numericValue_{value} {} - CSSToken(CSSTokenType type, float value, std::string_view unit) + constexpr 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; + constexpr CSSToken(const CSSToken& other) = default; + constexpr CSSToken(CSSToken&& other) = default; + constexpr CSSToken& operator=(const CSSToken& other) = default; + constexpr CSSToken& operator=(CSSToken&& other) = default; - CSSTokenType type() const { + constexpr CSSTokenType type() const { return type_; } - std::string_view stringValue() const { + constexpr std::string_view stringValue() const { return stringValue_; } - float numericValue() const { + constexpr float numericValue() const { return numericValue_; } - std::string_view unit() const { + constexpr std::string_view unit() const { return unit_; } - bool operator==(const CSSToken& other) const = default; + constexpr bool operator==(const CSSToken& other) const = default; private: CSSTokenType type_; @@ -74,26 +80,182 @@ struct CSSToken { */ class CSSTokenizer { public: - explicit CSSTokenizer(std::string_view characters); - CSSToken next(); + 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: - 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); + 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}; diff --git a/packages/react-native/ReactCommon/react/renderer/css/CSSValue.h b/packages/react-native/ReactCommon/react/renderer/css/CSSValue.h index 6f6659c97a4f..255db87185a3 100644 --- a/packages/react-native/ReactCommon/react/renderer/css/CSSValue.h +++ b/packages/react-native/ReactCommon/react/renderer/css/CSSValue.h @@ -20,18 +20,19 @@ namespace facebook::react { /** * Represents a CSS component value type. - * https://www.w3.org/TR/css-values-3/#component-types + * https://www.w3.org/TR/css-values-4/#component-types */ enum class CSSValueType : uint8_t { Keyword, Length, Number, Percentage, + Ratio, }; /** * Concrete representation for a CSS basic data type. - * https://www.w3.org/TR/css-values-3/#component-types + * https://www.w3.org/TR/css-values-4/#component-types */ template concept CSSBasicDataType = std::is_trivially_destructible_v && @@ -42,36 +43,42 @@ concept CSSBasicDataType = std::is_trivially_destructible_v && #pragma pack(push, 1) /** * Representation of CSS data type - * https://www.w3.org/TR/css-values-3/#lengths + * https://www.w3.org/TR/css-values-4/#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 + * https://www.w3.org/TR/css-values-4/#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 + * https://www.w3.org/TR/css-values-4/#numbers */ struct CSSNumber { float value{}; - constexpr bool operator==(const CSSNumber& rhs) const = default; +}; + +/** + * 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-3/#component-types + * 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 @@ -121,6 +128,13 @@ class CSSValueVariant { 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_; } @@ -142,6 +156,10 @@ class CSSValueVariant { return getIf(); } + constexpr CSSRatio getRatio() const requires(canRepresent()) { + return getIf(); + } + constexpr operator bool() const { return *this != CSSValueVariant{}; } @@ -164,6 +182,8 @@ static_assert(sizeof(CSSValueVariant) == 2); static_assert(sizeof(CSSValueVariant) == 6); static_assert( sizeof(CSSValueVariant) == 6); -static_assert(sizeof(CSSValueVariant) == 5); +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 index ecde38491219..a940fcc922c1 100644 --- a/packages/react-native/ReactCommon/react/renderer/css/tests/CSSParserTest.cpp +++ b/packages/react-native/ReactCommon/react/renderer/css/tests/CSSParserTest.cpp @@ -11,115 +11,247 @@ namespace facebook::react { TEST(CSSParser, keyword_values) { - auto emptyValue = parseCSSValue(""); + auto emptyValue = parseCSSComponentValue(""); EXPECT_EQ(emptyValue.type(), CSSValueType::Keyword); EXPECT_EQ(emptyValue.getKeyword(), CSSKeyword::Unset); - auto autoValue = parseCSSValue("auto"); + auto autoValue = parseCSSComponentValue("auto"); EXPECT_EQ(autoValue.type(), CSSValueType::Keyword); EXPECT_EQ(autoValue.getKeyword(), CSSKeyword::Auto); - auto autoCapsValue = parseCSSValue("AuTO"); + auto autoCapsValue = parseCSSComponentValue("AuTO"); EXPECT_EQ(autoCapsValue.type(), CSSValueType::Keyword); EXPECT_EQ(autoCapsValue.getKeyword(), CSSKeyword::Auto); - auto autoDisallowedValue = parseCSSValue("auto"); + auto autoDisallowedValue = parseCSSComponentValue("auto"); EXPECT_EQ(autoDisallowedValue.type(), CSSValueType::Keyword); EXPECT_EQ(autoDisallowedValue.getKeyword(), CSSKeyword::Unset); - auto whitespaceValue = parseCSSValue(" flex-start "); + auto whitespaceValue = + parseCSSComponentValue(" flex-start "); EXPECT_EQ(whitespaceValue.type(), CSSValueType::Keyword); EXPECT_EQ(whitespaceValue.getKeyword(), CSSAlignItems::FlexStart); - auto badIdentValue = parseCSSValue("bad"); + auto badIdentValue = parseCSSComponentValue("bad"); EXPECT_EQ(badIdentValue.type(), CSSValueType::Keyword); EXPECT_EQ(badIdentValue.getKeyword(), CSSKeyword::Unset); - auto pxValue = parseCSSValue("20px"); + auto pxValue = parseCSSComponentValue("20px"); EXPECT_EQ(pxValue.type(), CSSValueType::Keyword); EXPECT_EQ(pxValue.getKeyword(), CSSKeyword::Unset); - auto multiValue = parseCSSValue("auto flex-start"); + auto multiValue = parseCSSComponentValue("auto flex-start"); EXPECT_EQ(multiValue.type(), CSSValueType::Keyword); EXPECT_EQ(multiValue.getKeyword(), CSSKeyword::Unset); } TEST(CSSParser, length_values) { - auto emptyValue = parseCSSValue(""); + auto emptyValue = parseCSSComponentValue(""); EXPECT_EQ(emptyValue.type(), CSSValueType::Keyword); EXPECT_EQ(emptyValue.getKeyword(), CSSKeyword::Unset); - auto autoValue = parseCSSValue("auto"); + auto autoValue = parseCSSComponentValue("auto"); EXPECT_EQ(autoValue.type(), CSSValueType::Keyword); EXPECT_EQ(autoValue.getKeyword(), CSSKeyword::Auto); - auto pxValue = parseCSSValue("20px"); + 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 = parseCSSValue("453cm"); + 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 = parseCSSValue("0"); + 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 = parseCSSValue("123"); + auto unitlessNonzeroValue = + parseCSSComponentValue("123"); EXPECT_EQ(unitlessNonzeroValue.type(), CSSValueType::Keyword); EXPECT_EQ(unitlessNonzeroValue.getKeyword(), CSSKeyword::Unset); - auto pctValue = parseCSSValue("-40%"); + auto pctValue = parseCSSComponentValue("-40%"); EXPECT_EQ(pctValue.type(), CSSValueType::Keyword); EXPECT_EQ(pctValue.getKeyword(), CSSKeyword::Unset); } TEST(CSSParser, length_percentage_values) { - auto emptyValue = parseCSSValue(""); + auto emptyValue = + parseCSSComponentValue(""); EXPECT_EQ(emptyValue.type(), CSSValueType::Keyword); EXPECT_EQ(emptyValue.getKeyword(), CSSKeyword::Unset); auto autoValue = - parseCSSValue("auto"); + parseCSSComponentValue("auto"); EXPECT_EQ(autoValue.type(), CSSValueType::Keyword); EXPECT_EQ(autoValue.getKeyword(), CSSKeyword::Auto); auto pxValue = - parseCSSValue("20px"); + parseCSSComponentValue("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%"); + parseCSSComponentValue("-40%"); EXPECT_EQ(pctValue.type(), CSSValueType::Percentage); EXPECT_EQ(pctValue.getPercentage().value, -40.0f); } TEST(CSSParser, number_values) { - auto emptyValue = parseCSSValue(""); + auto emptyValue = parseCSSComponentValue(""); EXPECT_EQ(emptyValue.type(), CSSValueType::Keyword); EXPECT_EQ(emptyValue.getKeyword(), CSSKeyword::Unset); - auto inheritValue = parseCSSValue("inherit"); + auto inheritValue = parseCSSComponentValue("inherit"); EXPECT_EQ(inheritValue.type(), CSSValueType::Keyword); EXPECT_EQ(inheritValue.getKeyword(), CSSKeyword::Inherit); - auto pxValue = parseCSSValue("20px"); + auto pxValue = parseCSSComponentValue("20px"); EXPECT_EQ(pxValue.type(), CSSValueType::Keyword); EXPECT_EQ(pxValue.getKeyword(), CSSKeyword::Unset); - auto numberValue = parseCSSValue("123.456"); + auto numberValue = parseCSSComponentValue("123.456"); EXPECT_EQ(numberValue.type(), CSSValueType::Number); EXPECT_EQ(numberValue.getNumber().value, 123.456f); auto unitlessZeroValue = - parseCSSValue("0"); + 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::Keyword); + EXPECT_EQ(emptyValue.getKeyword(), CSSKeyword::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::Keyword); + EXPECT_EQ(negativeNumber.getKeyword(), CSSKeyword::Unset); + + auto missingDenominator = parseCSSComponentValue("16/"); + EXPECT_EQ(missingDenominator.type(), CSSValueType::Keyword); + EXPECT_EQ(missingDenominator.getKeyword(), CSSKeyword::Unset); + + auto negativeNumerator = + parseCSSComponentValue("-16/9"); + EXPECT_EQ(negativeNumerator.type(), CSSValueType::Keyword); + EXPECT_EQ(negativeNumerator.getKeyword(), CSSKeyword::Unset); + + auto negativeDenominator = + parseCSSComponentValue("16/-9"); + EXPECT_EQ(negativeDenominator.type(), CSSValueType::Keyword); + EXPECT_EQ(negativeDenominator.getKeyword(), CSSKeyword::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::Keyword); + EXPECT_EQ(degenerateRatio.getKeyword(), CSSKeyword::Unset); +} + +TEST(CSSParser, number_ratio_values) { + auto emptyValue = parseCSSComponentValue(""); + EXPECT_EQ(emptyValue.type(), CSSValueType::Keyword); + EXPECT_EQ(emptyValue.getKeyword(), CSSKeyword::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::Keyword); + EXPECT_EQ(missingDenominator.getKeyword(), CSSKeyword::Unset); + + auto negativeNumerator = + parseCSSComponentValue("-16/9"); + EXPECT_EQ(negativeNumerator.type(), CSSValueType::Keyword); + EXPECT_EQ(negativeNumerator.getKeyword(), CSSKeyword::Unset); + + auto negativeDenominator = + parseCSSComponentValue("16/-9"); + EXPECT_EQ(negativeDenominator.type(), CSSValueType::Keyword); + EXPECT_EQ(negativeDenominator.getKeyword(), CSSKeyword::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); +} + } // 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 e40d289c6773..e2ac957e3a1f 100644 --- a/packages/react-native/ReactCommon/react/renderer/css/tests/CSSTokenizerTest.cpp +++ b/packages/react-native/ReactCommon/react/renderer/css/tests/CSSTokenizerTest.cpp @@ -122,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*", From 757cfaf6bfe2f38f29da0fc1b74e2174fec41a53 Mon Sep 17 00:00:00 2001 From: Nick Gerleman Date: Tue, 13 Feb 2024 17:03:02 -0800 Subject: [PATCH 4/4] Add CSSProp and CSSPropDefinition (#42913) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/42913 This structures properties into a `CSSProp` enum (so that we can have a runtime-key per style prop), associated with a `CSSPropDefinition` structure which groups the supported types and keywords. This has some niceness of removing the macro bits, but more importantly, means we can query parse related information without a field of the value yet existing (need for sparse storage of CSS values). In the future, it will serve as where we define "initial" values, and likely, the processes for interpolation and inheritance. We restructure `CSSValueVariant` to not always support keywords, as it may not be a valid possibility for computed values (which do not have CSS wide keywords). Computed values themselves may also reduce more keywords than the global ones (e.g. border width computed value absolutizes keywords). We also flesh out more of the prop definitions, and parsing. All the properties here relay back to YogaStylableProps of today, but I intentionally filled out the prop definitions a bit more than we do anything with right now (will design higher level to ignore unknown props). Changelog: [Internal] Reviewed By: rozele Differential Revision: D53518450 fbshipit-source-id: 4308795814632c5319acb5667298b5c1c22ef69b --- .../react/renderer/css/CSSKeywords.h | 280 ++++--- .../react/renderer/css/CSSParser.h | 63 +- .../react/renderer/css/CSSProperties.h | 712 ++++++++++++++++++ .../ReactCommon/react/renderer/css/CSSValue.h | 79 +- .../renderer/css/tests/CSSParserTest.cpp | 231 +++--- 5 files changed, 1085 insertions(+), 280 deletions(-) create mode 100644 packages/react-native/ReactCommon/react/renderer/css/CSSProperties.h diff --git a/packages/react-native/ReactCommon/react/renderer/css/CSSKeywords.h b/packages/react-native/ReactCommon/react/renderer/css/CSSKeywords.h index d7606b51f202..15e5037c6845 100644 --- a/packages/react-native/ReactCommon/react/renderer/css/CSSKeywords.h +++ b/packages/react-native/ReactCommon/react/renderer/css/CSSKeywords.h @@ -25,9 +25,15 @@ enum class CSSKeyword : uint8_t { Absolute, Auto, Baseline, + Block, Center, + Clip, Column, ColumnReverse, + Content, + Contents, + End, + Fixed, Flex, FlexEnd, FlexStart, @@ -35,8 +41,16 @@ enum class CSSKeyword : uint8_t { Inherit, Initial, Inline, + InlineBlock, + InlineFlex, + InlineGrid, Ltr, + Grid, + MaxContent, + Medium, + MinContent, None, + Normal, NoWrap, Relative, Row, @@ -46,8 +60,12 @@ enum class CSSKeyword : uint8_t { SpaceAround, SpaceBetween, SpaceEvenly, + Start, Static, + Sticky, Stretch, + Thick, + Thin, Unset, Visible, Wrap, @@ -55,159 +73,21 @@ enum class CSSKeyword : uint8_t { }; /** - * Represents a set of CSS keywords, including CSS-wide keywords. + * Represents a contrained set of CSS 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__ \ - }; +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 */ -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); -} +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. @@ -221,18 +101,32 @@ constexpr bool operator==(CSSKeywordSet auto lhs, CSSKeywordSet auto rhs) { 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) @@ -242,8 +136,12 @@ 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) @@ -279,11 +177,21 @@ constexpr std::optional parseCSSKeyword(std::string_view ident) { 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; @@ -294,6 +202,25 @@ constexpr std::optional parseCSSKeyword(std::string_view ident) { 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; @@ -309,6 +236,11 @@ constexpr std::optional parseCSSKeyword(std::string_view ident) { 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; @@ -324,17 +256,52 @@ constexpr std::optional parseCSSKeyword(std::string_view ident) { 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("no-wrap"): + case fnv1a("normal"): + if constexpr (detail::hasNormal) { + return KeywordT::Normal; + } + break; + case fnv1a("nowrap"): if constexpr (detail::hasNoWrap) { return KeywordT::NoWrap; } @@ -379,16 +346,35 @@ constexpr std::optional parseCSSKeyword(std::string_view ident) { 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; diff --git a/packages/react-native/ReactCommon/react/renderer/css/CSSParser.h b/packages/react-native/ReactCommon/react/renderer/css/CSSParser.h index 6c31b259ab13..f9ad61c4846b 100644 --- a/packages/react-native/ReactCommon/react/renderer/css/CSSParser.h +++ b/packages/react-native/ReactCommon/react/renderer/css/CSSParser.h @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -23,13 +24,13 @@ class CSSParser { explicit constexpr CSSParser(std::string_view css) : tokenizer_{css}, currentToken_(tokenizer_.next()) {} - template - constexpr CSSValueVariant - consumeComponentValue() { - using CSSValueT = CSSValueVariant; + template + constexpr CSSValueVariant consumeComponentValue() { + using CSSValueT = CSSValueVariant; switch (peek().type()) { case CSSTokenType::Ident: - if (auto keywordValue = consumeIdentToken()) { + if (auto keywordValue = + consumeIdentToken()) { return *keywordValue; } break; @@ -80,16 +81,26 @@ class CSSParser { return prevToken; } - template + template constexpr std::optional consumeIdentToken() { - if (auto keyword = - parseCSSKeyword(consumeToken().stringValue())) { - return CSSValueT::keyword(*keyword); + 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 + template constexpr std::optional consumeDimensionToken() { if constexpr (traits::containsType()) { if (auto unit = parseCSSLengthUnit(peek().unit())) { @@ -99,7 +110,7 @@ class CSSParser { return {}; } - template + template constexpr std::optional consumePercentageToken() { if constexpr (traits::containsType()) { return CSSValueT::percentage(consumeToken().numericValue()); @@ -107,7 +118,7 @@ class CSSParser { return {}; } - template + template constexpr std::optional consumeNumberToken() { // = [ / ]? // https://www.w3.org/TR/css-values-4/#ratio @@ -174,20 +185,36 @@ class CSSParser { * * https://www.w3.org/TR/css-syntax-3/#parse-component-value */ -template -constexpr CSSValueVariant parseCSSComponentValue( - std::string_view css) { +template +constexpr void parseCSSComponentValue( + std::string_view css, + CSSValueVariant& value) { detail::CSSParser parser(css); parser.consumeWhitespace(); - auto value = parser.consumeComponentValue(); + auto componentValue = parser.consumeComponentValue(); parser.consumeWhitespace(); if (parser.hasMoreTokens()) { - return {}; + value = {}; } else { - return value; + 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/CSSValue.h b/packages/react-native/ReactCommon/react/renderer/css/CSSValue.h index 255db87185a3..4663dd9e8189 100644 --- a/packages/react-native/ReactCommon/react/renderer/css/CSSValue.h +++ b/packages/react-native/ReactCommon/react/renderer/css/CSSValue.h @@ -23,6 +23,7 @@ namespace facebook::react { * https://www.w3.org/TR/css-values-4/#component-types */ enum class CSSValueType : uint8_t { + CSSWideKeyword, Keyword, Length, Number, @@ -31,11 +32,11 @@ enum class CSSValueType : uint8_t { }; /** - * Concrete representation for a CSS basic data type. + * Concrete representation for a CSS basic data type, or keywords * https://www.w3.org/TR/css-values-4/#component-types */ template -concept CSSBasicDataType = std::is_trivially_destructible_v && +concept CSSDataType = std::is_trivially_destructible_v && std::is_default_constructible_v && requires() { sizeof(T); }; @@ -88,9 +89,9 @@ struct CSSRatio { * set of values. */ #pragma pack(push, 1) -template +template class CSSValueVariant { - template + template constexpr ValueT getIf() const { if (type_ == Type) { return *std::launder(reinterpret_cast(data_.data())); @@ -99,16 +100,49 @@ class CSSValueVariant { } } - template + template static constexpr bool canRepresent() { - return traits::containsType(); + 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: - constexpr CSSValueVariant() - : CSSValueVariant(CSSValueType::Keyword, KeywordT::Unset) {} + using Keyword = typename PackedKeywordSet::Type; + + constexpr CSSValueVariant() requires(canRepresent()) + : CSSValueVariant(CSSValueType::CSSWideKeyword, CSSWideKeyword::Unset) {} - static constexpr CSSValueVariant keyword(KeywordT keyword) { + 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}); } @@ -139,8 +173,14 @@ class CSSValueVariant { return type_; } - constexpr KeywordT getKeyword() const { - return getIf(); + constexpr CSSWideKeyword getCSSWideKeyword() const + requires(canRepresent()) { + return getIf(); + } + + constexpr Keyword getKeyword() const + requires(hasKeywordSet()) { + return getIf(); } constexpr CSSLength getLength() const requires(canRepresent()) { @@ -160,30 +200,29 @@ class CSSValueVariant { return getIf(); } - constexpr operator bool() const { + constexpr operator bool() const requires(canRepresent()) { return *this != CSSValueVariant{}; } constexpr bool operator==(const CSSValueVariant& rhs) const = default; private: - constexpr CSSValueVariant(CSSValueType type, CSSBasicDataType auto&& value) + constexpr CSSValueVariant(CSSValueType type, CSSDataType auto&& value) : type_(type) { new (data_.data()) std::remove_cvref_t{ std::forward(value)}; } CSSValueType type_; - std::array()> data_; + 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) == 2); +static_assert(sizeof(CSSValueVariant) == 6); static_assert( - sizeof(CSSValueVariant) == 9); + 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 index a940fcc922c1..280dc43e8beb 100644 --- a/packages/react-native/ReactCommon/react/renderer/css/tests/CSSParserTest.cpp +++ b/packages/react-native/ReactCommon/react/renderer/css/tests/CSSParserTest.cpp @@ -11,247 +11,288 @@ namespace facebook::react { TEST(CSSParser, keyword_values) { - auto emptyValue = parseCSSComponentValue(""); - EXPECT_EQ(emptyValue.type(), CSSValueType::Keyword); - EXPECT_EQ(emptyValue.getKeyword(), CSSKeyword::Unset); + auto emptyValue = parseCSSComponentValue(""); + EXPECT_EQ(emptyValue.type(), CSSValueType::CSSWideKeyword); + EXPECT_EQ(emptyValue.getCSSWideKeyword(), CSSWideKeyword::Unset); - auto autoValue = parseCSSComponentValue("auto"); + auto autoValue = parseCSSComponentValue("auto"); EXPECT_EQ(autoValue.type(), CSSValueType::Keyword); EXPECT_EQ(autoValue.getKeyword(), CSSKeyword::Auto); - auto autoCapsValue = parseCSSComponentValue("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::Keyword); - EXPECT_EQ(autoDisallowedValue.getKeyword(), CSSKeyword::Unset); + auto autoDisallowedValue = parseCSSComponentValue("auto"); + EXPECT_EQ(autoDisallowedValue.type(), CSSValueType::CSSWideKeyword); + EXPECT_EQ(autoDisallowedValue.getCSSWideKeyword(), CSSWideKeyword::Unset); auto whitespaceValue = - parseCSSComponentValue(" flex-start "); + parseCSSComponentValue(" flex-start "); EXPECT_EQ(whitespaceValue.type(), CSSValueType::Keyword); - EXPECT_EQ(whitespaceValue.getKeyword(), CSSAlignItems::FlexStart); + EXPECT_EQ(whitespaceValue.getKeyword(), CSSKeyword::FlexStart); - auto badIdentValue = parseCSSComponentValue("bad"); - EXPECT_EQ(badIdentValue.type(), CSSValueType::Keyword); - EXPECT_EQ(badIdentValue.getKeyword(), CSSKeyword::Unset); + 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::Keyword); - EXPECT_EQ(pxValue.getKeyword(), CSSKeyword::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::Keyword); - EXPECT_EQ(multiValue.getKeyword(), CSSKeyword::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::Keyword); - EXPECT_EQ(emptyValue.getKeyword(), CSSKeyword::Unset); + auto emptyValue = parseCSSComponentValue(""); + EXPECT_EQ(emptyValue.type(), CSSValueType::CSSWideKeyword); + EXPECT_EQ(emptyValue.getCSSWideKeyword(), CSSWideKeyword::Unset); - auto autoValue = parseCSSComponentValue("auto"); + auto autoValue = + parseCSSComponentValue("auto"); EXPECT_EQ(autoValue.type(), CSSValueType::Keyword); EXPECT_EQ(autoValue.getKeyword(), CSSKeyword::Auto); - auto pxValue = parseCSSComponentValue("20px"); + 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"); + 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"); + 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::Keyword); - EXPECT_EQ(unitlessNonzeroValue.getKeyword(), CSSKeyword::Unset); + parseCSSComponentValue("123"); + EXPECT_EQ(unitlessNonzeroValue.type(), CSSValueType::CSSWideKeyword); + EXPECT_EQ(unitlessNonzeroValue.getCSSWideKeyword(), CSSWideKeyword::Unset); - auto pctValue = parseCSSComponentValue("-40%"); - EXPECT_EQ(pctValue.type(), CSSValueType::Keyword); - EXPECT_EQ(pctValue.getKeyword(), CSSKeyword::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::Keyword); - EXPECT_EQ(emptyValue.getKeyword(), CSSKeyword::Unset); - - auto autoValue = - parseCSSComponentValue("auto"); + 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"); + 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%"); + 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::Keyword); - EXPECT_EQ(emptyValue.getKeyword(), CSSKeyword::Unset); + auto emptyValue = parseCSSComponentValue(""); + EXPECT_EQ(emptyValue.type(), CSSValueType::CSSWideKeyword); + EXPECT_EQ(emptyValue.getCSSWideKeyword(), CSSWideKeyword::Unset); - auto inheritValue = parseCSSComponentValue("inherit"); - EXPECT_EQ(inheritValue.type(), CSSValueType::Keyword); - EXPECT_EQ(inheritValue.getKeyword(), CSSKeyword::Inherit); + 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::Keyword); - EXPECT_EQ(pxValue.getKeyword(), CSSKeyword::Unset); + auto pxValue = + parseCSSComponentValue("20px"); + EXPECT_EQ(pxValue.type(), CSSValueType::CSSWideKeyword); + EXPECT_EQ(pxValue.getCSSWideKeyword(), CSSWideKeyword::Unset); - auto numberValue = parseCSSComponentValue("123.456"); + auto numberValue = + parseCSSComponentValue("123.456"); EXPECT_EQ(numberValue.type(), CSSValueType::Number); EXPECT_EQ(numberValue.getNumber().value, 123.456f); auto unitlessZeroValue = - parseCSSComponentValue("0"); + 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::Keyword); - EXPECT_EQ(emptyValue.getKeyword(), CSSKeyword::Unset); + auto emptyValue = parseCSSComponentValue(""); + EXPECT_EQ(emptyValue.type(), CSSValueType::CSSWideKeyword); + EXPECT_EQ(emptyValue.getCSSWideKeyword(), CSSWideKeyword::Unset); - auto validRatio = parseCSSComponentValue("16/9"); + 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"); + 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"); + 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"); + 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::Keyword); - EXPECT_EQ(negativeNumber.getKeyword(), CSSKeyword::Unset); + 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::Keyword); - EXPECT_EQ(missingDenominator.getKeyword(), CSSKeyword::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::Keyword); - EXPECT_EQ(negativeNumerator.getKeyword(), CSSKeyword::Unset); + 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::Keyword); - EXPECT_EQ(negativeDenominator.getKeyword(), CSSKeyword::Unset); + parseCSSComponentValue("16/-9"); + EXPECT_EQ(negativeDenominator.type(), CSSValueType::CSSWideKeyword); + EXPECT_EQ(negativeDenominator.getCSSWideKeyword(), CSSWideKeyword::Unset); auto fractionalNumerator = - parseCSSComponentValue("16.5/9"); + 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"); + 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::Keyword); - EXPECT_EQ(degenerateRatio.getKeyword(), CSSKeyword::Unset); + 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::Keyword); - EXPECT_EQ(emptyValue.getKeyword(), CSSKeyword::Unset); + auto emptyValue = + parseCSSComponentValue(""); + EXPECT_EQ(emptyValue.type(), CSSValueType::CSSWideKeyword); + EXPECT_EQ(emptyValue.getCSSWideKeyword(), CSSWideKeyword::Unset); auto validRatio = - parseCSSComponentValue("16/9"); + 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"); + 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"); + 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"); + 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"); + parseCSSComponentValue("-16"); EXPECT_EQ(negativeNumber.type(), CSSValueType::Number); EXPECT_EQ(negativeNumber.getNumber().value, -16.0f); auto missingDenominator = - parseCSSComponentValue("16/"); - EXPECT_EQ(missingDenominator.type(), CSSValueType::Keyword); - EXPECT_EQ(missingDenominator.getKeyword(), CSSKeyword::Unset); + parseCSSComponentValue("16/"); + EXPECT_EQ(missingDenominator.type(), CSSValueType::CSSWideKeyword); + EXPECT_EQ(missingDenominator.getCSSWideKeyword(), CSSWideKeyword::Unset); auto negativeNumerator = - parseCSSComponentValue("-16/9"); - EXPECT_EQ(negativeNumerator.type(), CSSValueType::Keyword); - EXPECT_EQ(negativeNumerator.getKeyword(), CSSKeyword::Unset); + 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::Keyword); - EXPECT_EQ(negativeDenominator.getKeyword(), CSSKeyword::Unset); + parseCSSComponentValue("16/-9"); + EXPECT_EQ(negativeDenominator.type(), CSSValueType::CSSWideKeyword); + EXPECT_EQ(negativeDenominator.getCSSWideKeyword(), CSSWideKeyword::Unset); auto fractionalNumerator = - parseCSSComponentValue("16.5/9"); + 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"); + 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"); + 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