first commit

This commit is contained in:
2026-03-10 16:18:05 +00:00
commit 11f9c069b5
31635 changed files with 3187747 additions and 0 deletions

View File

@@ -0,0 +1,36 @@
# 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)
include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake)
file(GLOB react_renderer_css_SRC CONFIGURE_DEPENDS *.cpp)
# We need to create library as INTERFACE if it is header only
if("${react_renderer_css_SRC}" STREQUAL "")
add_library(react_renderer_css INTERFACE)
target_include_directories(react_renderer_css INTERFACE ${REACT_COMMON_DIR})
target_link_libraries(react_renderer_css INTERFACE
fast_float
glog
react_debug
react_utils)
target_compile_reactnative_options(react_renderer_css INTERFACE)
target_compile_options(react_renderer_css INTERFACE -Wpedantic)
else()
add_library(react_renderer_css OBJECT ${react_renderer_css_SRC})
target_include_directories(react_renderer_css PUBLIC ${REACT_COMMON_DIR})
target_link_libraries(react_renderer_css
fast_float
glog
react_debug
react_utils)
target_compile_reactnative_options(react_renderer_css PRIVATE)
target_compile_options(react_renderer_css PRIVATE -Wpedantic)
endif()

View File

@@ -0,0 +1,42 @@
/*
* 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 <optional>
#include <react/renderer/css/CSSAngleUnit.h>
#include <react/renderer/css/CSSDataType.h>
namespace facebook::react {
/**
* Representation of CSS <angle> data type
* https://www.w3.org/TR/css-values-4/#angles
*/
struct CSSAngle {
float degrees{};
constexpr bool operator==(const CSSAngle &rhs) const = default;
};
template <>
struct CSSDataTypeParser<CSSAngle> {
static constexpr auto consumePreservedToken(const CSSPreservedToken &token) -> std::optional<CSSAngle>
{
if (token.type() == CSSTokenType::Dimension) {
if (auto unit = parseCSSAngleUnit(token.unit())) {
return CSSAngle{canonicalize(token.numericValue(), *unit)};
}
}
return {};
}
};
static_assert(CSSDataType<CSSAngle>);
} // namespace facebook::react

View File

@@ -0,0 +1,68 @@
/*
* 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 <cmath>
#include <cstdint>
#include <optional>
#include <string_view>
#include <react/utils/fnv1a.h>
namespace facebook::react {
/**
* Unit for the CSS <angle> type.
* https://www.w3.org/TR/css-values-4/#angles
*/
enum class CSSAngleUnit : uint8_t {
Deg,
Grad,
Rad,
Turn,
};
/**
* Parses a unit from a dimension token into a CSS angle unit.
*/
constexpr std::optional<CSSAngleUnit> parseCSSAngleUnit(std::string_view unit)
{
switch (fnv1aLowercase(unit)) {
case fnv1a("deg"):
return CSSAngleUnit::Deg;
case fnv1a("grad"):
return CSSAngleUnit::Grad;
case fnv1a("rad"):
return CSSAngleUnit::Rad;
case fnv1a("turn"):
return CSSAngleUnit::Turn;
default:
return std::nullopt;
}
}
/**
* Converts a specified CSS angle to its cannonical unit (degrees)
*/
constexpr float canonicalize(float value, CSSAngleUnit unit)
{
switch (unit) {
case CSSAngleUnit::Deg:
return value;
case CSSAngleUnit::Grad:
return value * 0.9f;
case CSSAngleUnit::Rad:
return value * 57.295779513f;
case CSSAngleUnit::Turn:
return value * 360.0f;
default:
return std::numeric_limits<float>::quiet_NaN();
}
}
} // namespace facebook::react

View File

@@ -0,0 +1,864 @@
/*
* 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 <optional>
#include <variant>
#include <react/renderer/css/CSSAngle.h>
#include <react/renderer/css/CSSColor.h>
#include <react/renderer/css/CSSCompoundDataType.h>
#include <react/renderer/css/CSSDataType.h>
#include <react/renderer/css/CSSLength.h>
#include <react/renderer/css/CSSLengthPercentage.h>
#include <react/renderer/css/CSSList.h>
#include <react/renderer/css/CSSPercentage.h>
#include <react/renderer/css/CSSValueParser.h>
#include <react/utils/TemplateStringLiteral.h>
#include <react/utils/fnv1a.h>
#include <react/utils/iequals.h>
namespace facebook::react {
enum class CSSLinearGradientDirectionKeyword : uint8_t {
ToTopLeft,
ToTopRight,
ToBottomLeft,
ToBottomRight,
};
struct CSSLinearGradientDirection {
// angle or keyword like "to bottom"
std::variant<CSSAngle, CSSLinearGradientDirectionKeyword> value;
bool operator==(const CSSLinearGradientDirection &rhs) const = default;
};
template <>
struct CSSDataTypeParser<CSSLinearGradientDirection> {
static constexpr auto consume(CSSSyntaxParser &parser) -> std::optional<CSSLinearGradientDirection>
{
return parseLinearGradientDirection(parser);
}
private:
static constexpr std::optional<CSSLinearGradientDirection> parseLinearGradientDirection(CSSSyntaxParser &parser)
{
auto angle = parseNextCSSValue<CSSAngle>(parser);
if (std::holds_alternative<CSSAngle>(angle)) {
return CSSLinearGradientDirection{std::get<CSSAngle>(angle)};
}
auto toResult = parser.consumeComponentValue<bool>([](const CSSPreservedToken &token) -> bool {
return token.type() == CSSTokenType::Ident && fnv1aLowercase(token.stringValue()) == fnv1a("to");
});
if (!toResult) {
// no direction found, default to 180 degrees (to bottom)
return CSSLinearGradientDirection{CSSAngle{180.0f}};
}
parser.consumeWhitespace();
std::optional<CSSKeyword> primaryDir;
auto primaryResult = parser.consumeComponentValue<std::optional<CSSKeyword>>(
[](const CSSPreservedToken &token) -> std::optional<CSSKeyword> {
if (token.type() == CSSTokenType::Ident) {
switch (fnv1aLowercase(token.stringValue())) {
case fnv1a("top"):
return CSSKeyword::Top;
case fnv1a("bottom"):
return CSSKeyword::Bottom;
case fnv1a("left"):
return CSSKeyword::Left;
case fnv1a("right"):
return CSSKeyword::Right;
}
}
return {};
});
if (!primaryResult) {
return {};
}
primaryDir = primaryResult;
parser.consumeWhitespace();
std::optional<CSSKeyword> secondaryDir;
auto secondaryResult = parser.consumeComponentValue<std::optional<CSSKeyword>>(
[&](const CSSPreservedToken &token) -> std::optional<CSSKeyword> {
if (token.type() == CSSTokenType::Ident) {
auto hash = fnv1aLowercase(token.stringValue());
// validate compatible combinations
if (primaryDir == CSSKeyword::Top || primaryDir == CSSKeyword::Bottom) {
if (hash == fnv1a("left")) {
return CSSKeyword::Left;
}
if (hash == fnv1a("right")) {
return CSSKeyword::Right;
}
}
if (primaryDir == CSSKeyword::Left || primaryDir == CSSKeyword::Right) {
if (hash == fnv1a("top")) {
return CSSKeyword::Top;
}
if (hash == fnv1a("bottom")) {
return CSSKeyword::Bottom;
}
}
}
return {};
});
if (secondaryResult) {
secondaryDir = secondaryResult;
}
if (primaryDir == CSSKeyword::Top) {
if (secondaryDir == CSSKeyword::Left) {
return CSSLinearGradientDirection{CSSLinearGradientDirectionKeyword::ToTopLeft};
} else if (secondaryDir == CSSKeyword::Right) {
return CSSLinearGradientDirection{CSSLinearGradientDirectionKeyword::ToTopRight};
} else {
// "to top" = 0 degrees
return CSSLinearGradientDirection{CSSAngle{0.0f}};
}
} else if (primaryDir == CSSKeyword::Bottom) {
if (secondaryDir == CSSKeyword::Left) {
return CSSLinearGradientDirection{CSSLinearGradientDirectionKeyword::ToBottomLeft};
} else if (secondaryDir == CSSKeyword::Right) {
return CSSLinearGradientDirection{CSSLinearGradientDirectionKeyword::ToBottomRight};
} else {
// "to bottom" = 180 degrees
return CSSLinearGradientDirection{CSSAngle{180.0f}};
}
} else if (primaryDir == CSSKeyword::Left) {
if (secondaryDir == CSSKeyword::Top) {
return CSSLinearGradientDirection{CSSLinearGradientDirectionKeyword::ToTopLeft};
} else if (secondaryDir == CSSKeyword::Bottom) {
return CSSLinearGradientDirection{CSSLinearGradientDirectionKeyword::ToBottomLeft};
} else {
// "to left" = 270 degrees
return CSSLinearGradientDirection{CSSAngle{270.0f}};
}
} else if (primaryDir == CSSKeyword::Right) {
if (secondaryDir == CSSKeyword::Top) {
return CSSLinearGradientDirection{CSSLinearGradientDirectionKeyword::ToTopRight};
} else if (secondaryDir == CSSKeyword::Bottom) {
return CSSLinearGradientDirection{CSSLinearGradientDirectionKeyword::ToBottomRight};
} else {
// "to right" = 90 degrees
return CSSLinearGradientDirection{CSSAngle{90.0f}};
}
}
return {};
}
};
static_assert(CSSDataType<CSSLinearGradientDirection>);
/**
* Representation of a color hint (interpolation hint)
*/
struct CSSColorHint {
std::variant<CSSLength, CSSPercentage> position{}; // Support both lengths and percentages
bool operator==(const CSSColorHint &rhs) const
{
return position == rhs.position;
}
};
template <>
struct CSSDataTypeParser<CSSColorHint> {
static auto consume(CSSSyntaxParser &parser) -> std::optional<CSSColorHint>
{
return parseCSSColorHint(parser);
}
private:
static std::optional<CSSColorHint> parseCSSColorHint(CSSSyntaxParser &parser)
{
auto position = parseNextCSSValue<CSSLengthPercentage>(parser);
if (std::holds_alternative<CSSLength>(position)) {
return CSSColorHint{std::get<CSSLength>(position)};
} else if (std::holds_alternative<CSSPercentage>(position)) {
return CSSColorHint{std::get<CSSPercentage>(position)};
}
return {};
}
};
static_assert(CSSDataType<CSSColorHint>);
struct CSSColorStop {
CSSColor color{};
std::optional<std::variant<CSSLength, CSSPercentage>> startPosition{};
std::optional<std::variant<CSSLength, CSSPercentage>> endPosition{};
bool operator==(const CSSColorStop &rhs) const
{
if (color != rhs.color) {
return false;
}
if (startPosition.has_value() != rhs.startPosition.has_value()) {
return false;
}
if (startPosition.has_value()) {
if (startPosition->index() != rhs.startPosition->index()) {
return false;
}
if (*startPosition != *rhs.startPosition) {
return false;
}
}
if (endPosition.has_value() != rhs.endPosition.has_value()) {
return false;
}
if (endPosition.has_value()) {
if (endPosition->index() != rhs.endPosition->index()) {
return false;
}
if (*endPosition != *rhs.endPosition) {
return false;
}
}
return true;
}
};
template <>
struct CSSDataTypeParser<CSSColorStop> {
static constexpr auto consume(CSSSyntaxParser &parser) -> std::optional<CSSColorStop>
{
return parseCSSColorStop(parser);
}
private:
static constexpr std::optional<CSSColorStop> parseCSSColorStop(CSSSyntaxParser &parser)
{
auto color = parseNextCSSValue<CSSColor>(parser);
if (!std::holds_alternative<CSSColor>(color)) {
return {};
}
CSSColorStop colorStop;
colorStop.color = std::get<CSSColor>(color);
auto startPosition = parseNextCSSValue<CSSLengthPercentage>(parser, CSSDelimiter::Whitespace);
if (std::holds_alternative<CSSLength>(startPosition)) {
colorStop.startPosition = std::get<CSSLength>(startPosition);
} else if (std::holds_alternative<CSSPercentage>(startPosition)) {
colorStop.startPosition = std::get<CSSPercentage>(startPosition);
}
if (colorStop.startPosition) {
// Try to parse second optional position (supports both lengths and
// percentages)
auto endPosition = parseNextCSSValue<CSSLengthPercentage>(parser, CSSDelimiter::Whitespace);
if (std::holds_alternative<CSSLength>(endPosition)) {
colorStop.endPosition = std::get<CSSLength>(endPosition);
} else if (std::holds_alternative<CSSPercentage>(endPosition)) {
colorStop.endPosition = std::get<CSSPercentage>(endPosition);
}
}
return colorStop;
}
};
static_assert(CSSDataType<CSSColorStop>);
struct CSSLinearGradientFunction {
std::optional<CSSLinearGradientDirection> direction{};
std::vector<std::variant<CSSColorStop, CSSColorHint>> items{}; // Color stops and color hints
bool operator==(const CSSLinearGradientFunction &rhs) const = default;
static std::pair<std::vector<std::variant<CSSColorStop, CSSColorHint>>, int> parseGradientColorStopsAndHints(
CSSSyntaxParser &parser)
{
std::vector<std::variant<CSSColorStop, CSSColorHint>> items;
int colorStopCount = 0;
std::optional<CSSColorStop> prevColorStop = std::nullopt;
do {
auto colorStop = parseNextCSSValue<CSSColorStop>(parser);
if (std::holds_alternative<CSSColorStop>(colorStop)) {
auto parsedColorStop = std::get<CSSColorStop>(colorStop);
items.emplace_back(parsedColorStop);
prevColorStop = parsedColorStop;
colorStopCount++;
} else {
auto colorHint = parseNextCSSValue<CSSColorHint>(parser);
if (std::holds_alternative<CSSColorHint>(colorHint)) {
// color hint must be between two color stops
if (!prevColorStop) {
return {};
}
auto nextColorStop = peekNextCSSValue<CSSColorStop>(parser, CSSDelimiter::Comma);
if (!std::holds_alternative<CSSColorStop>(nextColorStop)) {
return {};
}
items.emplace_back(std::get<CSSColorHint>(colorHint));
} else {
break; // No more valid items
}
}
} while (parser.consumeDelimiter(CSSDelimiter::Comma));
return {items, colorStopCount};
}
};
enum class CSSRadialGradientShape : uint8_t {
Circle,
Ellipse,
};
template <>
struct CSSDataTypeParser<CSSRadialGradientShape> {
static constexpr auto consumePreservedToken(const CSSPreservedToken &token) -> std::optional<CSSRadialGradientShape>
{
if (token.type() == CSSTokenType::Ident) {
auto lowercase = fnv1aLowercase(token.stringValue());
if (lowercase == fnv1a("circle")) {
return CSSRadialGradientShape::Circle;
} else if (lowercase == fnv1a("ellipse")) {
return CSSRadialGradientShape::Ellipse;
}
}
return {};
}
};
static_assert(CSSDataType<CSSRadialGradientShape>);
enum class CSSRadialGradientSizeKeyword : uint8_t {
ClosestSide,
ClosestCorner,
FarthestSide,
FarthestCorner,
};
template <>
struct CSSDataTypeParser<CSSRadialGradientSizeKeyword> {
static constexpr auto consumePreservedToken(const CSSPreservedToken &token)
-> std::optional<CSSRadialGradientSizeKeyword>
{
if (token.type() == CSSTokenType::Ident) {
auto lowercase = fnv1aLowercase(token.stringValue());
if (lowercase == fnv1a("closest-side")) {
return CSSRadialGradientSizeKeyword::ClosestSide;
} else if (lowercase == fnv1a("closest-corner")) {
return CSSRadialGradientSizeKeyword::ClosestCorner;
} else if (lowercase == fnv1a("farthest-side")) {
return CSSRadialGradientSizeKeyword::FarthestSide;
} else if (lowercase == fnv1a("farthest-corner")) {
return CSSRadialGradientSizeKeyword::FarthestCorner;
}
}
return {};
}
};
static_assert(CSSDataType<CSSRadialGradientSizeKeyword>);
struct CSSRadialGradientExplicitSize {
std::variant<CSSLength, CSSPercentage> sizeX{};
std::variant<CSSLength, CSSPercentage> sizeY{};
bool operator==(const CSSRadialGradientExplicitSize &rhs) const = default;
};
template <>
struct CSSDataTypeParser<CSSRadialGradientExplicitSize> {
static auto consume(CSSSyntaxParser &syntaxParser) -> std::optional<CSSRadialGradientExplicitSize>
{
auto sizeX = parseNextCSSValue<CSSLengthPercentage>(syntaxParser);
if (std::holds_alternative<std::monostate>(sizeX)) {
return {};
}
syntaxParser.consumeWhitespace();
auto sizeY = parseNextCSSValue<CSSLengthPercentage>(syntaxParser);
CSSRadialGradientExplicitSize result;
if (std::holds_alternative<CSSLength>(sizeX)) {
result.sizeX = std::get<CSSLength>(sizeX);
} else {
result.sizeX = std::get<CSSPercentage>(sizeX);
}
if (std::holds_alternative<CSSLength>(sizeY) || std::holds_alternative<CSSPercentage>(sizeY)) {
if (std::holds_alternative<CSSLength>(sizeY)) {
result.sizeY = std::get<CSSLength>(sizeY);
} else {
result.sizeY = std::get<CSSPercentage>(sizeY);
}
} else {
result.sizeY = result.sizeX;
}
return result;
}
};
static_assert(CSSDataType<CSSRadialGradientExplicitSize>);
using CSSRadialGradientSize = std::variant<CSSRadialGradientSizeKeyword, CSSRadialGradientExplicitSize>;
struct CSSRadialGradientPosition {
std::optional<std::variant<CSSLength, CSSPercentage>> top{};
std::optional<std::variant<CSSLength, CSSPercentage>> bottom{};
std::optional<std::variant<CSSLength, CSSPercentage>> left{};
std::optional<std::variant<CSSLength, CSSPercentage>> right{};
bool operator==(const CSSRadialGradientPosition &rhs) const
{
return top == rhs.top && bottom == rhs.bottom && left == rhs.left && right == rhs.right;
}
};
struct CSSRadialGradientFunction {
std::optional<CSSRadialGradientShape> shape{};
std::optional<CSSRadialGradientSize> size{};
std::optional<CSSRadialGradientPosition> position{};
std::vector<std::variant<CSSColorStop, CSSColorHint>> items{}; // Color stops and color hints
bool operator==(const CSSRadialGradientFunction &rhs) const = default;
};
template <>
struct CSSDataTypeParser<CSSRadialGradientFunction> {
static auto consumeFunctionBlock(const CSSFunctionBlock &func, CSSSyntaxParser &parser)
-> std::optional<CSSRadialGradientFunction>
{
if (!iequals(func.name, "radial-gradient")) {
return {};
}
CSSRadialGradientFunction gradient;
auto hasExplicitShape = false;
auto hasExplicitSingleSize = false;
auto shapeResult = parseNextCSSValue<CSSRadialGradientShape>(parser);
if (std::holds_alternative<CSSRadialGradientShape>(shapeResult)) {
parser.consumeWhitespace();
}
std::optional<CSSRadialGradientSize> sizeResult;
auto sizeKeywordResult = parseNextCSSValue<CSSRadialGradientSizeKeyword>(parser);
if (std::holds_alternative<CSSRadialGradientSizeKeyword>(sizeKeywordResult)) {
sizeResult = CSSRadialGradientSize{std::get<CSSRadialGradientSizeKeyword>(sizeKeywordResult)};
parser.consumeWhitespace();
} else {
auto explicitSizeResult = parseNextCSSValue<CSSRadialGradientExplicitSize>(parser);
if (std::holds_alternative<CSSRadialGradientExplicitSize>(explicitSizeResult)) {
auto explicitSize = std::get<CSSRadialGradientExplicitSize>(explicitSizeResult);
// negative value validation
if (std::holds_alternative<CSSLength>(explicitSize.sizeX)) {
const auto &lengthX = std::get<CSSLength>(explicitSize.sizeX);
if (lengthX.value < 0) {
return {};
}
} else if (std::holds_alternative<CSSPercentage>(explicitSize.sizeX)) {
const auto &percentageX = std::get<CSSPercentage>(explicitSize.sizeX);
if (percentageX.value < 0) {
return {};
}
}
if (std::holds_alternative<CSSLength>(explicitSize.sizeY)) {
const auto &lengthY = std::get<CSSLength>(explicitSize.sizeY);
if (lengthY.value < 0) {
return {};
}
} else if (std::holds_alternative<CSSPercentage>(explicitSize.sizeY)) {
const auto &percentageY = std::get<CSSPercentage>(explicitSize.sizeY);
if (percentageY.value < 0) {
return {};
}
}
// check if it's a single size (both X and Y are the same), we use it
// to set shape to circle
if (explicitSize.sizeX == explicitSize.sizeY) {
hasExplicitSingleSize = true;
}
sizeResult = CSSRadialGradientSize{explicitSize};
parser.consumeWhitespace();
}
}
if (std::holds_alternative<CSSRadialGradientShape>(shapeResult)) {
gradient.shape = std::get<CSSRadialGradientShape>(shapeResult);
hasExplicitShape = true;
} else {
// default to ellipse
gradient.shape = CSSRadialGradientShape::Ellipse;
}
if (sizeResult.has_value()) {
gradient.size = *sizeResult;
} else {
// default to farthest corner
gradient.size = CSSRadialGradientSize{CSSRadialGradientSizeKeyword::FarthestCorner};
}
if (!hasExplicitShape && hasExplicitSingleSize) {
gradient.shape = CSSRadialGradientShape::Circle;
}
if (hasExplicitSingleSize && hasExplicitShape && gradient.shape.value() == CSSRadialGradientShape::Ellipse) {
// if a single size is explicitly set and the shape is an ellipse do not
// apply any gradient. Same as web.
return {};
}
auto atResult = parser.consumeComponentValue<bool>([](const CSSPreservedToken &token) -> bool {
return token.type() == CSSTokenType::Ident && fnv1aLowercase(token.stringValue()) == fnv1a("at");
});
CSSRadialGradientPosition position;
if (atResult) {
parser.consumeWhitespace();
std::vector<std::variant<CSSLength, CSSPercentage, CSSKeyword>> positionKeywordValues;
for (int i = 0; i < 2; i++) {
auto keywordFound = false;
auto valueFound = false;
auto positionKeyword = parser.consumeComponentValue<std::optional<CSSKeyword>>(
[](const CSSPreservedToken &token) -> std::optional<CSSKeyword> {
if (token.type() == CSSTokenType::Ident) {
auto keyword = std::string(token.stringValue());
auto hash = fnv1aLowercase(keyword);
if (hash == fnv1a("top")) {
return CSSKeyword::Top;
} else if (hash == fnv1a("bottom")) {
return CSSKeyword::Bottom;
} else if (hash == fnv1a("left")) {
return CSSKeyword::Left;
} else if (hash == fnv1a("right")) {
return CSSKeyword::Right;
} else if (hash == fnv1a("center")) {
return CSSKeyword::Center;
}
}
return {};
});
if (positionKeyword) {
// invalid position declaration of same keyword "at top 10% top 20%"
for (const auto &existingValue : positionKeywordValues) {
if (std::holds_alternative<CSSKeyword>(existingValue)) {
if (std::get<CSSKeyword>(existingValue) == positionKeyword) {
return {};
}
}
}
positionKeywordValues.emplace_back(*positionKeyword);
keywordFound = true;
}
parser.consumeWhitespace();
auto lengthPercentageValue = parseNextCSSValue<CSSLengthPercentage>(parser);
std::optional<decltype(positionKeywordValues)::value_type> value;
if (std::holds_alternative<CSSLength>(lengthPercentageValue)) {
value = std::get<CSSLength>(lengthPercentageValue);
} else if (std::holds_alternative<CSSPercentage>(lengthPercentageValue)) {
value = std::get<CSSPercentage>(lengthPercentageValue);
}
if (value.has_value()) {
positionKeywordValues.emplace_back(*value);
valueFound = true;
}
parser.consumeWhitespace();
if (!keywordFound && !valueFound) {
break;
}
}
if (positionKeywordValues.empty()) {
return {};
}
// 1. [ left | center | right | top | bottom | <length-percentage> ]
if (positionKeywordValues.size() == 1) {
auto value = positionKeywordValues[0];
if (std::holds_alternative<CSSKeyword>(value)) {
auto keyword = std::get<CSSKeyword>(value);
if (keyword == CSSKeyword::Left) {
position.top = CSSPercentage{50.0f};
position.left = CSSPercentage{0.0f};
} else if (keyword == CSSKeyword::Right) {
position.top = CSSPercentage{50.0f};
position.left = CSSPercentage{100.0f};
} else if (keyword == CSSKeyword::Top) {
position.top = CSSPercentage{0.0f};
position.left = CSSPercentage{50.0f};
} else if (keyword == CSSKeyword::Bottom) {
position.top = CSSPercentage{100.0f};
position.left = CSSPercentage{50.0f};
} else if (keyword == CSSKeyword::Center) {
position.left = CSSPercentage{50.0f};
position.top = CSSPercentage{50.0f};
} else {
return {};
}
} else if ((std::holds_alternative<CSSLength>(value) || std::holds_alternative<CSSPercentage>(value))) {
if (std::holds_alternative<CSSLength>(value)) {
position.left = std::get<CSSLength>(value);
} else {
position.left = std::get<CSSPercentage>(value);
}
position.top = CSSPercentage{50.0f};
} else {
return {};
}
}
else if (positionKeywordValues.size() == 2) {
auto value1 = positionKeywordValues[0];
auto value2 = positionKeywordValues[1];
// 2. [ left | center | right ] && [ top | center | bottom ]
if (std::holds_alternative<CSSKeyword>(value1) && std::holds_alternative<CSSKeyword>(value2)) {
auto keyword1 = std::get<CSSKeyword>(value1);
auto keyword2 = std::get<CSSKeyword>(value2);
auto isHorizontal = [](CSSKeyword kw) {
return kw == CSSKeyword::Left || kw == CSSKeyword::Center || kw == CSSKeyword::Right;
};
auto isVertical = [](CSSKeyword kw) {
return kw == CSSKeyword::Top || kw == CSSKeyword::Center || kw == CSSKeyword::Bottom;
};
if (isHorizontal(keyword1) && isVertical(keyword2)) {
// First horizontal, second vertical
if (keyword1 == CSSKeyword::Left) {
position.left = CSSPercentage{0.0f};
} else if (keyword1 == CSSKeyword::Right) {
position.right = CSSPercentage{0.0f};
} else if (keyword1 == CSSKeyword::Center) {
position.left = CSSPercentage{50.0f};
}
if (keyword2 == CSSKeyword::Top) {
position.top = CSSPercentage{0.0f};
} else if (keyword2 == CSSKeyword::Bottom) {
position.bottom = CSSPercentage{0.0f};
} else if (keyword2 == CSSKeyword::Center) {
position.top = CSSPercentage{50.0f};
}
} else if (isVertical(keyword1) && isHorizontal(keyword2)) {
// First vertical, second horizontal
if (keyword1 == CSSKeyword::Top) {
position.top = CSSPercentage{0.0f};
} else if (keyword1 == CSSKeyword::Bottom) {
position.bottom = CSSPercentage{0.0f};
} else if (keyword1 == CSSKeyword::Center) {
position.top = CSSPercentage{50.0f};
}
if (keyword2 == CSSKeyword::Left) {
position.left = CSSPercentage{0.0f};
} else if (keyword2 == CSSKeyword::Right) {
position.left = CSSPercentage{100.0f};
} else if (keyword2 == CSSKeyword::Center) {
position.left = CSSPercentage{50.0f};
}
} else {
return {};
}
}
// 3. [ left | center | right | <length-percentage> ] [ top | center |
// bottom | <length-percentage> ]
else {
if (std::holds_alternative<CSSKeyword>(value1)) {
auto keyword1 = std::get<CSSKeyword>(value1);
if (keyword1 == CSSKeyword::Left) {
position.left = CSSPercentage{0.0f};
} else if (keyword1 == CSSKeyword::Right) {
position.right = CSSPercentage{0.0f};
} else if (keyword1 == CSSKeyword::Center) {
position.left = CSSPercentage{50.0f};
} else {
return {};
}
} else if ((std::holds_alternative<CSSLength>(value1) || std::holds_alternative<CSSPercentage>(value1))) {
if (std::holds_alternative<CSSLength>(value1)) {
position.left = std::get<CSSLength>(value1);
} else {
position.left = std::get<CSSPercentage>(value1);
}
} else {
return {};
}
if (std::holds_alternative<CSSKeyword>(value2)) {
auto keyword2 = std::get<CSSKeyword>(value2);
if (keyword2 == CSSKeyword::Top) {
position.top = CSSPercentage{0.0f};
} else if (keyword2 == CSSKeyword::Bottom) {
position.bottom = CSSPercentage{0.f};
} else if (keyword2 == CSSKeyword::Center) {
position.top = CSSPercentage{50.0f};
} else {
return {};
}
} else if ((std::holds_alternative<CSSLength>(value2) || std::holds_alternative<CSSPercentage>(value2))) {
if (std::holds_alternative<CSSLength>(value2)) {
position.top = std::get<CSSLength>(value2);
} else {
position.top = std::get<CSSPercentage>(value2);
}
} else {
return {};
}
}
}
// 4. [ [ left | right ] <length-percentage> ] && [ [ top | bottom ]
// <length-percentage> ]
else if (positionKeywordValues.size() == 4) {
auto value1 = positionKeywordValues[0];
auto value2 = positionKeywordValues[1];
auto value3 = positionKeywordValues[2];
auto value4 = positionKeywordValues[3];
if (!std::holds_alternative<CSSKeyword>(value1)) {
return {};
}
if (!std::holds_alternative<CSSKeyword>(value3)) {
return {};
}
if ((!std::holds_alternative<CSSLength>(value2) && !std::holds_alternative<CSSPercentage>(value2))) {
return {};
}
if ((!std::holds_alternative<CSSLength>(value4) && !std::holds_alternative<CSSPercentage>(value4))) {
return {};
}
auto parsedValue2 = std::holds_alternative<CSSLength>(value2)
? std::variant<CSSLength, CSSPercentage>{std::get<CSSLength>(value2)}
: std::variant<CSSLength, CSSPercentage>{std::get<CSSPercentage>(value2)};
auto parsedValue4 = std::holds_alternative<CSSLength>(value4)
? std::variant<CSSLength, CSSPercentage>{std::get<CSSLength>(value4)}
: std::variant<CSSLength, CSSPercentage>{std::get<CSSPercentage>(value4)};
auto keyword1 = std::get<CSSKeyword>(value1);
auto keyword3 = std::get<CSSKeyword>(value3);
if (keyword1 == CSSKeyword::Left) {
position.left = parsedValue2;
} else if (keyword1 == CSSKeyword::Right) {
position.right = parsedValue2;
} else if (keyword1 == CSSKeyword::Top) {
position.top = parsedValue2;
} else if (keyword1 == CSSKeyword::Bottom) {
position.bottom = parsedValue2;
} else {
return {};
}
if (keyword3 == CSSKeyword::Left) {
position.left = parsedValue4;
} else if (keyword3 == CSSKeyword::Right) {
position.right = parsedValue4;
} else if (keyword3 == CSSKeyword::Top) {
position.top = parsedValue4;
} else if (keyword3 == CSSKeyword::Bottom) {
position.bottom = parsedValue4;
} else {
return {};
}
} else {
return {};
}
gradient.position = position;
} else {
// Default position
position.top = CSSPercentage{50.0f};
position.left = CSSPercentage{50.0f};
gradient.position = position;
}
parser.consumeDelimiter(CSSDelimiter::Comma);
auto [items, colorStopCount] = CSSLinearGradientFunction::parseGradientColorStopsAndHints(parser);
if (items.empty() || colorStopCount < 2) {
return {};
}
gradient.items = std::move(items);
return gradient;
}
};
static_assert(CSSDataType<CSSRadialGradientFunction>);
template <>
struct CSSDataTypeParser<CSSLinearGradientFunction> {
static auto consumeFunctionBlock(const CSSFunctionBlock &func, CSSSyntaxParser &parser)
-> std::optional<CSSLinearGradientFunction>
{
if (!iequals(func.name, "linear-gradient")) {
return {};
}
CSSLinearGradientFunction gradient;
auto parsedDirection = parseNextCSSValue<CSSLinearGradientDirection>(parser);
if (!std::holds_alternative<CSSLinearGradientDirection>(parsedDirection)) {
return {};
}
parser.consumeDelimiter(CSSDelimiter::Comma);
gradient.direction = std::get<CSSLinearGradientDirection>(parsedDirection);
auto [items, colorStopCount] = CSSLinearGradientFunction::parseGradientColorStopsAndHints(parser);
if (items.empty() || colorStopCount < 2) {
return {};
}
gradient.items = std::move(items);
return gradient;
}
};
static_assert(CSSDataType<CSSLinearGradientFunction>);
/**
* Representation of <background-image>
* https://www.w3.org/TR/css-backgrounds-3/#background-image
*/
using CSSBackgroundImage = CSSCompoundDataType<CSSLinearGradientFunction, CSSRadialGradientFunction>;
/**
* Variant of possible CSS background image types
*/
using CSSBackgroundImageVariant = CSSVariantWithTypes<CSSBackgroundImage>;
/**
* Representation of <background-image-list>
*/
using CSSBackgroundImageList = CSSCommaSeparatedList<CSSBackgroundImage>;
} // namespace facebook::react

View File

@@ -0,0 +1,60 @@
/*
* 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 <optional>
#include <react/renderer/css/CSSColorFunction.h>
#include <react/renderer/css/CSSDataType.h>
#include <react/renderer/css/CSSHexColor.h>
#include <react/renderer/css/CSSNamedColor.h>
namespace facebook::react {
/**
* Representation of CSS <color> data type
* https://www.w3.org/TR/css-color-5/#typedef-color
*/
struct CSSColor {
uint8_t r{};
uint8_t g{};
uint8_t b{};
uint8_t a{};
constexpr bool operator==(const CSSColor &rhs) const = default;
static constexpr CSSColor black()
{
return {.r = 0, .g = 0, .b = 0, .a = 255};
}
};
template <>
struct CSSDataTypeParser<CSSColor> {
static constexpr auto consumePreservedToken(const CSSPreservedToken &token) -> std::optional<CSSColor>
{
switch (token.type()) {
case CSSTokenType::Ident:
return parseCSSNamedColor<CSSColor>(token.stringValue());
case CSSTokenType::Hash:
return parseCSSHexColor<CSSColor>(token.stringValue());
default:
return {};
}
}
static constexpr auto consumeFunctionBlock(const CSSFunctionBlock &func, CSSSyntaxParser &parser)
-> std::optional<CSSColor>
{
return parseCSSColorFunction<CSSColor>(func.name, parser);
}
};
static_assert(CSSDataType<CSSColor>);
} // namespace facebook::react

View File

@@ -0,0 +1,409 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <optional>
#include <string_view>
#include <tuple>
#include <react/renderer/css/CSSAngle.h>
#include <react/renderer/css/CSSNumber.h>
#include <react/renderer/css/CSSPercentage.h>
#include <react/renderer/css/CSSSyntaxParser.h>
#include <react/renderer/css/CSSValueParser.h>
#include <react/utils/PackTraits.h>
#include <react/utils/fnv1a.h>
namespace facebook::react {
namespace detail {
constexpr uint8_t clamp255Component(float f)
{
// Implementations should honor the precision of the channel as authored or
// calculated wherever possible. If this is not possible, the channel should
// be rounded towards +∞.
// https://www.w3.org/TR/css-color-4/#rgb-functions
auto i = static_cast<int32_t>(f);
auto ceiled = f > i ? i + 1 : i;
return static_cast<uint8_t>(std::clamp(ceiled, 0, 255));
}
constexpr std::optional<float> normalizeNumberComponent(const std::variant<std::monostate, CSSNumber> &component)
{
if (std::holds_alternative<CSSNumber>(component)) {
return std::get<CSSNumber>(component).value;
}
return {};
}
constexpr uint8_t clampAlpha(std::optional<float> alpha)
{
return alpha.has_value() ? clamp255Component(*alpha * 255.0f) : static_cast<uint8_t>(255u);
}
inline float normalizeHue(float hue)
{
auto rem = std::remainder(hue, 360.0f);
return (rem < 0 ? rem + 360 : rem) / 360.0f;
}
inline std::optional<float> normalizeHueComponent(const std::variant<std::monostate, CSSNumber, CSSAngle> &component)
{
if (std::holds_alternative<CSSNumber>(component)) {
return normalizeHue(std::get<CSSNumber>(component).value);
} else if (std::holds_alternative<CSSAngle>(component)) {
return normalizeHue(std::get<CSSAngle>(component).degrees);
}
return {};
}
constexpr float hueToRgb(float p, float q, float t)
{
if (t < 0.0f) {
t += 1.0f;
}
if (t > 1.0f) {
t -= 1.0f;
}
if (t < 1.0f / 6.0f) {
return p + (q - p) * 6 * t;
}
if (t < 1.0f / 2.0f) {
return q;
}
if (t < 2.0f / 3.0f) {
return p + (q - p) * (2.0f / 3.0f - t) * 6.0f;
}
return p;
}
inline std::tuple<uint8_t, uint8_t, uint8_t> hslToRgb(float h, float s, float l)
{
s = std::clamp(s / 100.0f, 0.0f, 1.0f);
l = std::clamp(l / 100.0f, 0.0f, 1.0f);
auto q = l < 0.5f ? l * (1.0f + s) : l + s - l * s;
auto p = 2.0f * l - q;
auto r = hueToRgb(p, q, h + 1.0f / 3.0f);
auto g = hueToRgb(p, q, h);
auto b = hueToRgb(p, q, h - 1.0f / 3.0f);
return {
static_cast<uint8_t>(std::round(r * 255.0f)),
static_cast<uint8_t>(std::round(g * 255.0f)),
static_cast<uint8_t>(std::round(b * 255.0f)),
};
}
inline std::tuple<uint8_t, uint8_t, uint8_t> hwbToRgb(float h, float w, float b)
{
w = std::clamp(w / 100.0f, 0.0f, 1.0f);
b = std::clamp(b / 100.0f, 0.0f, 1.0f);
if (w + b >= 1.0f) {
auto gray = w / (w + b);
return {
static_cast<uint8_t>(std::round(gray * 255.0f)),
static_cast<uint8_t>(std::round(gray * 255.0f)),
static_cast<uint8_t>(std::round(gray * 255.0f)),
};
}
auto red = hueToRgb(0.0f, 1.0f, h + 1.0f / 3.0f) * (1.0f - w - b) + w;
auto green = hueToRgb(0.0f, 1.0f, h) * (1.0f - w - b) + w;
auto blue = hueToRgb(0.0f, 1.0f, h - 1.0f / 3.0f) * (1.0f - w - b) + w;
return {
static_cast<uint8_t>(std::round(red * 255.0f)),
static_cast<uint8_t>(std::round(green * 255.0f)),
static_cast<uint8_t>(std::round(blue * 255.0f)),
};
}
template <typename... ComponentT>
requires((std::is_same_v<CSSNumber, ComponentT> || std::is_same_v<CSSPercentage, ComponentT>) && ...)
constexpr std::optional<float> normalizeComponent(
const std::variant<std::monostate, ComponentT...> &component,
float baseValue)
{
if constexpr (traits::containsType<CSSPercentage, ComponentT...>()) {
if (std::holds_alternative<CSSPercentage>(component)) {
return std::get<CSSPercentage>(component).value / 100.0f * baseValue;
}
}
if constexpr (traits::containsType<CSSNumber, ComponentT...>()) {
if (std::holds_alternative<CSSNumber>(component)) {
return std::get<CSSNumber>(component).value;
}
}
return {};
}
template <CSSDataType... FirstComponentAllowedTypesT>
constexpr bool isLegacyColorFunction(CSSSyntaxParser &parser)
{
auto lookahead = parser;
auto next = parseNextCSSValue<FirstComponentAllowedTypesT...>(lookahead);
if (std::holds_alternative<std::monostate>(next)) {
return false;
}
return lookahead.consumeComponentValue<bool>(
CSSDelimiter::OptionalWhitespace, [](CSSPreservedToken token) { return token.type() == CSSTokenType::Comma; });
}
/**
* Parses a legacy syntax rgb() or rgba() function and returns a CSSColor if it
* is valid.
* https://www.w3.org/TR/css-color-4/#typedef-legacy-rgb-syntax
*/
template <typename CSSColor>
constexpr std::optional<CSSColor> parseLegacyRgbFunction(CSSSyntaxParser &parser)
{
auto rawRed = parseNextCSSValue<CSSNumber, CSSPercentage>(parser);
bool usesNumber = std::holds_alternative<CSSNumber>(rawRed);
auto red = normalizeComponent(rawRed, 255.0f);
if (!red.has_value()) {
return {};
}
auto green = usesNumber ? normalizeNumberComponent(parseNextCSSValue<CSSNumber>(parser, CSSDelimiter::Comma))
: normalizeComponent(parseNextCSSValue<CSSPercentage>(parser, CSSDelimiter::Comma), 255.0f);
if (!green.has_value()) {
return {};
}
auto blue = usesNumber ? normalizeNumberComponent(parseNextCSSValue<CSSNumber>(parser, CSSDelimiter::Comma))
: normalizeComponent(parseNextCSSValue<CSSPercentage>(parser, CSSDelimiter::Comma), 255.0f);
if (!blue.has_value()) {
return {};
}
auto alpha = normalizeComponent(parseNextCSSValue<CSSNumber, CSSPercentage>(parser, CSSDelimiter::Comma), 1.0f);
return CSSColor{
.r = clamp255Component(*red),
.g = clamp255Component(*green),
.b = clamp255Component(*blue),
.a = clampAlpha(alpha),
};
}
/**
* Parses a modern syntax rgb() or rgba() function and returns a CSSColor if it
* is valid.
* https://www.w3.org/TR/css-color-4/#typedef-modern-rgb-syntax
*/
template <typename CSSColor>
constexpr std::optional<CSSColor> parseModernRgbFunction(CSSSyntaxParser &parser)
{
auto red = normalizeComponent(parseNextCSSValue<CSSNumber, CSSPercentage>(parser), 255.0f);
if (!red.has_value()) {
return {};
}
auto green =
normalizeComponent(parseNextCSSValue<CSSNumber, CSSPercentage>(parser, CSSDelimiter::Whitespace), 255.0f);
if (!green.has_value()) {
return {};
}
auto blue = normalizeComponent(parseNextCSSValue<CSSNumber, CSSPercentage>(parser, CSSDelimiter::Whitespace), 255.0f);
if (!blue.has_value()) {
return {};
}
auto alpha =
normalizeComponent(parseNextCSSValue<CSSNumber, CSSPercentage>(parser, CSSDelimiter::SolidusOrWhitespace), 1.0f);
return CSSColor{
.r = clamp255Component(*red),
.g = clamp255Component(*green),
.b = clamp255Component(*blue),
.a = clampAlpha(alpha),
};
}
/**
* Parses an rgb() or rgba() function and returns a CSSColor if it is valid.
* https://www.w3.org/TR/css-color-4/#funcdef-rgb
*/
template <typename CSSColor>
constexpr std::optional<CSSColor> parseRgbFunction(CSSSyntaxParser &parser)
{
if (isLegacyColorFunction<CSSNumber, CSSPercentage>(parser)) {
return parseLegacyRgbFunction<CSSColor>(parser);
} else {
return parseModernRgbFunction<CSSColor>(parser);
}
}
/**
* Parses a legacy syntax hsl() or hsla() function and returns a CSSColor if it
* is valid.
* https://www.w3.org/TR/css-color-4/#typedef-legacy-hsl-syntax
*/
template <typename CSSColor>
inline std::optional<CSSColor> parseLegacyHslFunction(CSSSyntaxParser &parser)
{
auto h = normalizeHueComponent(parseNextCSSValue<CSSNumber, CSSAngle>(parser));
if (!h.has_value()) {
return {};
}
auto s = normalizeComponent(parseNextCSSValue<CSSPercentage>(parser, CSSDelimiter::Comma), 100.0f);
if (!s.has_value()) {
return {};
}
auto l = normalizeComponent(parseNextCSSValue<CSSPercentage>(parser, CSSDelimiter::Comma), 100.0f);
if (!l.has_value()) {
return {};
}
auto a = normalizeComponent(parseNextCSSValue<CSSNumber, CSSPercentage>(parser, CSSDelimiter::Comma), 1.0f);
auto [r, g, b] = hslToRgb(*h, *s, *l);
return CSSColor{
.r = r,
.g = g,
.b = b,
.a = clampAlpha(a),
};
}
/**
* Parses a modern syntax hsl() or hsla() function and returns a CSSColor if
* it is valid. https://www.w3.org/TR/css-color-4/#typedef-modern-hsl-syntax
*/
template <typename CSSColor>
inline std::optional<CSSColor> parseModernHslFunction(CSSSyntaxParser &parser)
{
auto h = normalizeHueComponent(parseNextCSSValue<CSSNumber, CSSAngle>(parser));
if (!h.has_value()) {
return {};
}
auto s = normalizeComponent(parseNextCSSValue<CSSNumber, CSSPercentage>(parser, CSSDelimiter::Whitespace), 100.0f);
if (!s.has_value()) {
return {};
}
auto l = normalizeComponent(parseNextCSSValue<CSSNumber, CSSPercentage>(parser, CSSDelimiter::Whitespace), 100.0f);
if (!l.has_value()) {
return {};
}
auto a =
normalizeComponent(parseNextCSSValue<CSSNumber, CSSPercentage>(parser, CSSDelimiter::SolidusOrWhitespace), 1.0f);
auto [r, g, b] = hslToRgb(*h, *s, *l);
return CSSColor{
.r = r,
.g = g,
.b = b,
.a = clampAlpha(a),
};
}
/**
* Parses an hsl() or hsla() function and returns a CSSColor if it is valid.
* https://www.w3.org/TR/css-color-4/#funcdef-hsl
*/
template <typename CSSColor>
inline std::optional<CSSColor> parseHslFunction(CSSSyntaxParser &parser)
{
if (isLegacyColorFunction<CSSNumber, CSSAngle>(parser)) {
return parseLegacyHslFunction<CSSColor>(parser);
} else {
return parseModernHslFunction<CSSColor>(parser);
}
}
/**
* Parses an hwb() function and returns a CSSColor if it is valid.
* https://www.w3.org/TR/css-color-4/#funcdef-hwb
*/
template <typename CSSColor>
inline std::optional<CSSColor> parseHwbFunction(CSSSyntaxParser &parser)
{
auto h = normalizeHueComponent(parseNextCSSValue<CSSNumber, CSSAngle>(parser));
if (!h.has_value()) {
return {};
}
auto w = normalizeComponent(parseNextCSSValue<CSSNumber, CSSPercentage>(parser, CSSDelimiter::Whitespace), 100.0f);
if (!w.has_value()) {
return {};
}
auto b = normalizeComponent(parseNextCSSValue<CSSNumber, CSSPercentage>(parser, CSSDelimiter::Whitespace), 100.0f);
if (!b.has_value()) {
return {};
}
auto a =
normalizeComponent(parseNextCSSValue<CSSNumber, CSSPercentage>(parser, CSSDelimiter::SolidusOrWhitespace), 1.0f);
auto [red, green, blue] = hwbToRgb(*h, *w, *b);
return CSSColor{
.r = red,
.g = green,
.b = blue,
.a = clampAlpha(a),
};
}
} // namespace detail
/**
* Parses a CSS <color-function> value from function name and contents and
* returns a CSSColor if it is valid.
* https://www.w3.org/TR/css-color-4/#typedef-color-function
*/
template <typename CSSColor>
constexpr std::optional<CSSColor> parseCSSColorFunction(std::string_view colorFunction, CSSSyntaxParser &parser)
{
switch (fnv1aLowercase(colorFunction)) {
// CSS Color Module Level 4 treats the alpha variants of functions as the
// same as non-alpha variants (alpha is optional for both).
case fnv1a("rgb"):
case fnv1a("rgba"):
return detail::parseRgbFunction<CSSColor>(parser);
break;
case fnv1a("hsl"):
case fnv1a("hsla"):
return detail::parseHslFunction<CSSColor>(parser);
break;
case fnv1a("hwb"):
return detail::parseHwbFunction<CSSColor>(parser);
break;
// TODO T213000437: support lab(), lch(), oklab(), oklch(), color(),
// color-mix()
default:
return {};
}
return {};
}
} // namespace facebook::react

View File

@@ -0,0 +1,89 @@
/*
* 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 <react/renderer/css/CSSDataType.h>
namespace facebook::react {
namespace detail {
struct CSSCompoundDataTypeMarker {};
} // namespace detail
/**
* Allows grouping together multiple possible CSSDataType to parse.
*/
template <CSSDataType... AllowedTypesT>
struct CSSCompoundDataType : public detail::CSSCompoundDataTypeMarker {};
template <typename T>
concept CSSValidCompoundDataType = std::is_base_of_v<detail::CSSCompoundDataTypeMarker, T>;
/**
* A concrete data type, or a compound data type which represents multiple other
* data types.
*/
template <typename T>
concept CSSMaybeCompoundDataType = CSSDataType<T> || CSSValidCompoundDataType<T>;
namespace detail {
// inspired by https://stackoverflow.com/a/76255307
template <CSSMaybeCompoundDataType... AllowedTypesT>
struct merge_data_types;
template <CSSDataType... AlllowedTypes1T, CSSDataType... AlllowedTypes2T, CSSMaybeCompoundDataType... RestT>
struct merge_data_types<CSSCompoundDataType<AlllowedTypes1T...>, CSSCompoundDataType<AlllowedTypes2T...>, RestT...> {
using type = typename merge_data_types<CSSCompoundDataType<AlllowedTypes1T..., AlllowedTypes2T...>, RestT...>::type;
};
template <CSSDataType AlllowedType1T, CSSDataType... AlllowedTypes2T, CSSMaybeCompoundDataType... RestT>
struct merge_data_types<AlllowedType1T, CSSCompoundDataType<AlllowedTypes2T...>, RestT...> {
using type = typename merge_data_types<CSSCompoundDataType<AlllowedType1T, AlllowedTypes2T...>, RestT...>::type;
};
template <CSSDataType AlllowedType2T, CSSDataType... AlllowedTypes1T, CSSMaybeCompoundDataType... RestT>
struct merge_data_types<CSSCompoundDataType<AlllowedTypes1T...>, AlllowedType2T, RestT...> {
using type = typename merge_data_types<CSSCompoundDataType<AlllowedTypes1T..., AlllowedType2T>, RestT...>::type;
};
template <CSSDataType AlllowedType1T, CSSDataType AlllowedType2T, CSSMaybeCompoundDataType... RestT>
struct merge_data_types<AlllowedType1T, AlllowedType2T, RestT...> {
using type = typename merge_data_types<CSSCompoundDataType<AlllowedType1T, AlllowedType2T>, RestT...>::type;
};
template <CSSDataType... AllowedTypesT>
struct merge_data_types<CSSCompoundDataType<AllowedTypesT...>> {
using type = CSSCompoundDataType<AllowedTypesT...>;
};
template <CSSDataType AllowedTypeT>
struct merge_data_types<AllowedTypeT> {
using type = CSSCompoundDataType<AllowedTypeT>;
};
template <typename... T>
struct merge_variant;
template <CSSDataType... AllowedTypesT, typename... RestT>
struct merge_variant<CSSCompoundDataType<AllowedTypesT...>, RestT...> {
using type = std::variant<RestT..., AllowedTypesT...>;
};
} // namespace detail
/**
* Merges a set of compound or non-compound data types into a single compound
* data type
*/
template <CSSMaybeCompoundDataType... T>
using CSSMergedDataTypes = typename detail::merge_data_types<T...>::type;
template <typename MergedTypeT, typename... RestT>
using CSSVariantWithTypes = typename detail::merge_variant<MergedTypeT, RestT...>::type;
} // namespace facebook::react

View File

@@ -0,0 +1,76 @@
/*
* 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 <any>
#include <concepts>
#include <optional>
#include <variant>
#include <react/renderer/css/CSSSyntaxParser.h>
namespace facebook::react {
/**
* May be specialized to instruct the CSS value parser how to parse a given data
* type, according to CSSValidDataTypeParser.
*/
template <typename CSSDataTypeT>
struct CSSDataTypeParser {};
/**
* Accepts a CSS function block and may parse it (and future syntax) into a
* concrete representation.
*/
template <typename T, typename ReturnT = std::any>
concept CSSFunctionBlockSink = requires(const CSSFunctionBlock &func, CSSSyntaxParser &parser) {
{ T::consumeFunctionBlock(func, parser) } -> std::convertible_to<ReturnT>;
};
/**
* Accepts a CSS simple block and may parse it (and future syntax) into a
* concrete representation.
*/
template <typename T, typename ReturnT = std::any>
concept CSSSimpleBlockSink = requires(const CSSSimpleBlock &block, CSSSyntaxParser &parser) {
{ T::consumeSimpleBlock(block, parser) } -> std::convertible_to<ReturnT>;
};
/**
* Accepts a CSS preserved token and may parse it (and future syntax) into a
* concrete representation.
*/
template <typename T, typename ReturnT = std::any>
concept CSSPreservedTokenSink = requires(const CSSPreservedToken &token) {
{ T::consumePreservedToken(token) } -> std::convertible_to<ReturnT>;
};
/**
* Accepts a raw syntax parser, to be able to parse compounded values
*/
template <typename T, typename ReturnT = std::any>
concept CSSParserSink = requires(CSSSyntaxParser &parser) {
{ T::consume(parser) } -> std::convertible_to<ReturnT>;
};
/**
* Represents a valid specialization of CSSDataTypeParser
*/
template <typename T, typename ReturnT = std::any>
concept CSSValidDataTypeParser =
((CSSFunctionBlockSink<T, ReturnT> || CSSSimpleBlockSink<T, ReturnT> || CSSPreservedTokenSink<T, ReturnT>) &&
!CSSParserSink<T, ReturnT>) ||
CSSParserSink<T, ReturnT>;
/**
* Concrete representation for a CSS data type
*/
template <typename T>
concept CSSDataType = CSSValidDataTypeParser<CSSDataTypeParser<T>, std::optional<T>> && std::equality_comparable<T>;
} // namespace facebook::react

View File

@@ -0,0 +1,14 @@
/*
* 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.
*/
// This file is required for the CSS podpsec because this is a header-files only
// module. Headers file only modules are not supported by Cocoapods when
// building in dynamic framework mode. In that case, the dynamicFramework is not
// generated because there is no binary to generate. This means that other
// frameworks that depends on this will not find the headers they need. Adding a
// simple empty dummy source file forces Cocoapods to generate the frameworks
// and fixes the issue.

View File

@@ -0,0 +1,321 @@
/*
* 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 <array>
#include <optional>
#include <variant>
#include <react/renderer/css/CSSColor.h>
#include <react/renderer/css/CSSCompoundDataType.h>
#include <react/renderer/css/CSSDataType.h>
#include <react/renderer/css/CSSLength.h>
#include <react/renderer/css/CSSList.h>
#include <react/renderer/css/CSSNumber.h>
#include <react/renderer/css/CSSPercentage.h>
#include <react/renderer/css/CSSZero.h>
#include <react/utils/TemplateStringLiteral.h>
#include <react/utils/iequals.h>
namespace facebook::react {
namespace detail {
template <typename DataT, TemplateStringLiteral Name>
requires(std::is_same_v<decltype(DataT::amount), float>)
struct CSSFilterSimpleAmountParser {
static constexpr auto consumeFunctionBlock(const CSSFunctionBlock &func, CSSSyntaxParser &parser)
-> std::optional<DataT>
{
if (!iequals(func.name, Name)) {
return {};
}
auto amount = parseNextCSSValue<CSSNumber, CSSPercentage>(parser);
if (std::holds_alternative<CSSNumber>(amount)) {
if (std::get<CSSNumber>(amount).value < 0.0f) {
return {};
}
return DataT{std::get<CSSNumber>(amount).value};
} else if (std::holds_alternative<CSSPercentage>(amount)) {
if (std::get<CSSPercentage>(amount).value < 0.0f) {
return {};
}
return DataT{std::get<CSSPercentage>(amount).value / 100.0f};
} else {
return DataT{};
}
}
};
} // namespace detail
/**
* Representation of blur() function
*/
struct CSSBlurFilter {
CSSLength amount{};
constexpr bool operator==(const CSSBlurFilter &rhs) const = default;
};
template <>
struct CSSDataTypeParser<CSSBlurFilter> {
static constexpr auto consumeFunctionBlock(const CSSFunctionBlock &func, CSSSyntaxParser &parser)
-> std::optional<CSSBlurFilter>
{
if (!iequals(func.name, "blur")) {
return {};
}
auto len = parseNextCSSValue<CSSLength>(parser);
return CSSBlurFilter{std::holds_alternative<CSSLength>(len) ? std::get<CSSLength>(len) : CSSLength{}};
}
};
static_assert(CSSDataType<CSSBlurFilter>);
/**
* Representation of brightness() function
*/
struct CSSBrightnessFilter {
float amount{1.0f};
constexpr bool operator==(const CSSBrightnessFilter &rhs) const = default;
};
template <>
struct CSSDataTypeParser<CSSBrightnessFilter>
: public detail::CSSFilterSimpleAmountParser<CSSBrightnessFilter, "brightness"> {};
static_assert(CSSDataType<CSSBrightnessFilter>);
/**
* Representation of contrast() function
*/
struct CSSContrastFilter {
float amount{1.0f};
constexpr bool operator==(const CSSContrastFilter &rhs) const = default;
};
template <>
struct CSSDataTypeParser<CSSContrastFilter>
: public detail::CSSFilterSimpleAmountParser<CSSContrastFilter, "contrast"> {};
static_assert(CSSDataType<CSSContrastFilter>);
/**
* Representation of drop-shadow() function
*/
struct CSSDropShadowFilter {
CSSLength offsetX{};
CSSLength offsetY{};
CSSLength standardDeviation{};
CSSColor color{};
constexpr bool operator==(const CSSDropShadowFilter &rhs) const = default;
};
template <>
struct CSSDataTypeParser<CSSDropShadowFilter> {
static constexpr auto consumeFunctionBlock(const CSSFunctionBlock &func, CSSSyntaxParser &parser)
-> std::optional<CSSDropShadowFilter>
{
if (!iequals(func.name, "drop-shadow")) {
return {};
}
std::optional<CSSColor> color{};
std::optional<std::array<CSSLength, 3>> lengths{};
auto firstVal = parseNextCSSValue<CSSColor, CSSLength>(parser);
if (std::holds_alternative<std::monostate>(firstVal)) {
return {};
}
if (std::holds_alternative<CSSColor>(firstVal)) {
color = std::get<CSSColor>(firstVal);
} else {
lengths = parseLengths(std::get<CSSLength>(firstVal), parser);
if (!lengths.has_value()) {
return {};
}
}
auto secondVal = parseNextCSSValue<CSSColor, CSSLength>(parser, CSSDelimiter::Whitespace);
if (std::holds_alternative<CSSColor>(secondVal)) {
if (color.has_value()) {
return {};
}
color = std::get<CSSColor>(secondVal);
} else if (std::holds_alternative<CSSLength>(secondVal)) {
if (lengths.has_value()) {
return {};
}
lengths = parseLengths(std::get<CSSLength>(secondVal), parser);
}
if (!lengths.has_value()) {
return {};
}
return CSSDropShadowFilter{
.offsetX = (*lengths)[0],
.offsetY = (*lengths)[1],
.standardDeviation = (*lengths)[2],
.color = color.value_or(CSSColor::black()),
};
}
private:
static constexpr std::optional<std::array<CSSLength, 3>> parseLengths(CSSLength offsetX, CSSSyntaxParser &parser)
{
auto offsetY = parseNextCSSValue<CSSLength>(parser, CSSDelimiter::Whitespace);
if (!std::holds_alternative<CSSLength>(offsetY)) {
return {};
}
auto standardDeviation = parseNextCSSValue<CSSLength>(parser, CSSDelimiter::Whitespace);
if (std::holds_alternative<CSSLength>(standardDeviation) && std::get<CSSLength>(standardDeviation).value < 0.0f) {
return {};
}
return std::array<CSSLength, 3>{
offsetX,
std::get<CSSLength>(offsetY),
std::holds_alternative<CSSLength>(standardDeviation) ? std::get<CSSLength>(standardDeviation) : CSSLength{}};
}
};
static_assert(CSSDataType<CSSDropShadowFilter>);
/**
* Representation of grayscale() function
*/
struct CSSGrayscaleFilter {
float amount{1.0f};
constexpr bool operator==(const CSSGrayscaleFilter &rhs) const = default;
};
template <>
struct CSSDataTypeParser<CSSGrayscaleFilter>
: public detail::CSSFilterSimpleAmountParser<CSSGrayscaleFilter, "grayscale"> {};
static_assert(CSSDataType<CSSGrayscaleFilter>);
/**
* Representation of hue-rotate() function
*/
struct CSSHueRotateFilter {
float degrees{};
constexpr bool operator==(const CSSHueRotateFilter &rhs) const = default;
};
template <>
struct CSSDataTypeParser<CSSHueRotateFilter> {
static constexpr auto consumeFunctionBlock(const CSSFunctionBlock &func, CSSSyntaxParser &parser)
-> std::optional<CSSHueRotateFilter>
{
if (!iequals(func.name, "hue-rotate")) {
return {};
}
auto angle = parseNextCSSValue<CSSAngle, CSSZero>(parser);
return CSSHueRotateFilter{std::holds_alternative<CSSAngle>(angle) ? std::get<CSSAngle>(angle).degrees : 0.0f};
}
};
static_assert(CSSDataType<CSSHueRotateFilter>);
/**
* Representation of invert() function
*/
struct CSSInvertFilter {
float amount{1.0f};
constexpr bool operator==(const CSSInvertFilter &rhs) const = default;
};
template <>
struct CSSDataTypeParser<CSSInvertFilter> : public detail::CSSFilterSimpleAmountParser<CSSInvertFilter, "invert"> {};
static_assert(CSSDataType<CSSInvertFilter>);
/**
* Representation of opacity() function
*/
struct CSSOpacityFilter {
float amount{1.0f};
constexpr bool operator==(const CSSOpacityFilter &rhs) const = default;
};
template <>
struct CSSDataTypeParser<CSSOpacityFilter> : public detail::CSSFilterSimpleAmountParser<CSSOpacityFilter, "opacity"> {};
static_assert(CSSDataType<CSSOpacityFilter>);
/**
* Representation of saturate() function
*/
struct CSSSaturateFilter {
float amount{1.0f};
constexpr bool operator==(const CSSSaturateFilter &rhs) const = default;
};
template <>
struct CSSDataTypeParser<CSSSaturateFilter>
: public detail::CSSFilterSimpleAmountParser<CSSSaturateFilter, "saturate"> {};
static_assert(CSSDataType<CSSSaturateFilter>);
/**
* Representation of sepia() function
*/
struct CSSSepiaFilter {
float amount{1.0f};
constexpr bool operator==(const CSSSepiaFilter &rhs) const = default;
};
template <>
struct CSSDataTypeParser<CSSSepiaFilter> : public detail::CSSFilterSimpleAmountParser<CSSSepiaFilter, "sepia"> {};
static_assert(CSSDataType<CSSSepiaFilter>);
/**
* Representation of <filter-function>
* https://www.w3.org/TR/filter-effects-1/#typedef-filter-function
*/
using CSSFilterFunction = CSSCompoundDataType<
CSSBlurFilter,
CSSBrightnessFilter,
CSSContrastFilter,
CSSDropShadowFilter,
CSSGrayscaleFilter,
CSSHueRotateFilter,
CSSInvertFilter,
CSSOpacityFilter,
CSSSaturateFilter,
CSSSepiaFilter>;
/**
* Variant of possible CSS filter function types
*/
using CSSFilterFunctionVariant = CSSVariantWithTypes<CSSFilterFunction>;
/**
* Representation of <filter-value-list>
* https://www.w3.org/TR/filter-effects-1/#typedef-filter-value-list
*/
using CSSFilterList = CSSWhitespaceSeparatedList<CSSFilterFunction>;
} // namespace facebook::react

View File

@@ -0,0 +1,64 @@
/*
* 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 <react/renderer/css/CSSDataType.h>
#include <react/renderer/css/CSSKeyword.h>
#include <react/renderer/css/CSSList.h>
#include <react/utils/to_underlying.h>
namespace facebook::react {
/**
* Represents a possible font-variant keyword.
* https://drafts.csswg.org/css-fonts/#font-variant-prop
*/
enum class CSSFontVariant : std::underlying_type_t<CSSKeyword> {
SmallCaps = to_underlying(CSSKeyword::SmallCaps),
OldstyleNums = to_underlying(CSSKeyword::OldstyleNums),
LiningNums = to_underlying(CSSKeyword::LiningNums),
TabularNums = to_underlying(CSSKeyword::TabularNums),
CommonLigatures = to_underlying(CSSKeyword::CommonLigatures),
NoCommonLigatures = to_underlying(CSSKeyword::NoCommonLigatures),
DiscretionaryLigatures = to_underlying(CSSKeyword::DiscretionaryLigatures),
NoDiscretionaryLigatures = to_underlying(CSSKeyword::NoDiscretionaryLigatures),
HistoricalLigatures = to_underlying(CSSKeyword::HistoricalLigatures),
NoHistoricalLigatures = to_underlying(CSSKeyword::NoHistoricalLigatures),
Contextual = to_underlying(CSSKeyword::Contextual),
NoContextual = to_underlying(CSSKeyword::NoContextual),
ProportionalNums = to_underlying(CSSKeyword::ProportionalNums),
StylisticOne = to_underlying(CSSKeyword::StylisticOne),
StylisticTwo = to_underlying(CSSKeyword::StylisticTwo),
StylisticThree = to_underlying(CSSKeyword::StylisticThree),
StylisticFour = to_underlying(CSSKeyword::StylisticFour),
StylisticFive = to_underlying(CSSKeyword::StylisticFive),
StylisticSix = to_underlying(CSSKeyword::StylisticSix),
StylisticSeven = to_underlying(CSSKeyword::StylisticSeven),
StylisticEight = to_underlying(CSSKeyword::StylisticEight),
StylisticNine = to_underlying(CSSKeyword::StylisticNine),
StylisticTen = to_underlying(CSSKeyword::StylisticTen),
StylisticEleven = to_underlying(CSSKeyword::StylisticEleven),
StylisticTwelve = to_underlying(CSSKeyword::StylisticTwelve),
StylisticThirteen = to_underlying(CSSKeyword::StylisticThirteen),
StylisticFourteen = to_underlying(CSSKeyword::StylisticFourteen),
StylisticFifteen = to_underlying(CSSKeyword::StylisticFifteen),
StylisticSixteen = to_underlying(CSSKeyword::StylisticSixteen),
StylisticSeventeen = to_underlying(CSSKeyword::StylisticSeventeen),
StylisticEighteen = to_underlying(CSSKeyword::StylisticEighteen),
StylisticNineteen = to_underlying(CSSKeyword::StylisticNineteen),
StylisticTwenty = to_underlying(CSSKeyword::StylisticTwenty),
};
static_assert(CSSDataType<CSSFontVariant>);
/**
* Represents a whitespace-separated list of at least one font-variant.
*/
using CSSFontVariantList = CSSWhitespaceSeparatedList<CSSFontVariant>;
} // namespace facebook::react

View File

@@ -0,0 +1,105 @@
/*
* 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 <optional>
#include <string_view>
#include <react/utils/toLower.h>
namespace facebook::react {
namespace detail {
enum class HexColorType {
Long,
Short,
};
constexpr uint8_t hexToNumeric(std::string_view hex, HexColorType hexType)
{
int result = 0;
for (char c : hex) {
int value = 0;
if (c >= '0' && c <= '9') {
value = c - '0';
} else {
value = toLower(c) - 'a' + 10;
}
result *= 16;
result += value;
}
if (hexType == HexColorType::Short) {
return static_cast<uint8_t>(result * 16 + result);
} else {
return static_cast<uint8_t>(result);
}
}
constexpr bool isHexDigit(char c)
{
return (c >= '0' && c <= '9') || (toLower(c) >= 'a' && toLower(c) <= 'f');
}
constexpr bool isValidHexColor(std::string_view hex)
{
// The syntax of a <hex-color> is a <hash-token> token whose value consists
// of 3, 4, 6, or 8 hexadecimal digits.
if (hex.size() != 3 && hex.size() != 4 && hex.size() != 6 && hex.size() != 8) {
return false;
}
for (auto c : hex) {
if (!isHexDigit(c)) {
return false;
}
}
return true;
}
} // namespace detail
/**
* Parses a CSS <hex-color> value from hash token string value and returns a
* CSSColor if it is valid.
* https://www.w3.org/TR/css-color-4/#hex-color
*/
template <typename CSSColor>
constexpr std::optional<CSSColor> parseCSSHexColor(std::string_view hexColorValue)
{
if (detail::isValidHexColor(hexColorValue)) {
if (hexColorValue.length() == 3) {
return CSSColor{
hexToNumeric(hexColorValue.substr(0, 1), detail::HexColorType::Short),
hexToNumeric(hexColorValue.substr(1, 1), detail::HexColorType::Short),
hexToNumeric(hexColorValue.substr(2, 1), detail::HexColorType::Short),
255u};
} else if (hexColorValue.length() == 4) {
return CSSColor{
hexToNumeric(hexColorValue.substr(0, 1), detail::HexColorType::Short),
hexToNumeric(hexColorValue.substr(1, 1), detail::HexColorType::Short),
hexToNumeric(hexColorValue.substr(2, 1), detail::HexColorType::Short),
hexToNumeric(hexColorValue.substr(3, 1), detail::HexColorType::Short)};
} else if (hexColorValue.length() == 6) {
return CSSColor{
hexToNumeric(hexColorValue.substr(0, 2), detail::HexColorType::Long),
hexToNumeric(hexColorValue.substr(2, 2), detail::HexColorType::Long),
hexToNumeric(hexColorValue.substr(4, 2), detail::HexColorType::Long),
255u};
} else if (hexColorValue.length() == 8) {
return CSSColor{
hexToNumeric(hexColorValue.substr(0, 2), detail::HexColorType::Long),
hexToNumeric(hexColorValue.substr(2, 2), detail::HexColorType::Long),
hexToNumeric(hexColorValue.substr(4, 2), detail::HexColorType::Long),
hexToNumeric(hexColorValue.substr(6, 2), detail::HexColorType::Long)};
}
}
return {};
}
} // namespace facebook::react

View File

@@ -0,0 +1,368 @@
/*
* 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 <cstdint>
#include <optional>
#include <string_view>
#include <react/renderer/css/CSSDataType.h>
#include <react/utils/fnv1a.h>
#include <react/utils/to_underlying.h>
namespace facebook::react {
/**
* One of any predefined CSS keywords.
* https://www.w3.org/TR/css-values-4/#keywords
*/
enum class CSSKeyword : uint8_t {
Absolute,
Auto,
Baseline,
Block,
Bottom,
Center,
Clip,
Column,
ColumnReverse,
CommonLigatures,
Content,
Contents,
Contextual,
Dashed,
DiscretionaryLigatures,
Dotted,
Double,
End,
Fixed,
Flex,
FlexEnd,
FlexStart,
Grid,
Groove,
Hidden,
HistoricalLigatures,
Inherit,
Initial,
Inline,
InlineBlock,
InlineFlex,
InlineGrid,
Inset,
Left,
LiningNums,
Ltr,
MaxContent,
Medium,
MinContent,
NoCommonLigatures,
NoContextual,
NoDiscretionaryLigatures,
NoHistoricalLigatures,
None,
Normal,
NoWrap,
OldstyleNums,
Outset,
ProportionalNums,
Relative,
Ridge,
Right,
Row,
RowReverse,
Rtl,
Scroll,
SmallCaps,
Solid,
SpaceAround,
SpaceBetween,
SpaceEvenly,
Start,
Static,
Sticky,
Stretch,
StylisticEight,
StylisticEighteen,
StylisticEleven,
StylisticFifteen,
StylisticFive,
StylisticFour,
StylisticFourteen,
StylisticNine,
StylisticNineteen,
StylisticOne,
StylisticSeven,
StylisticSeventeen,
StylisticSix,
StylisticSixteen,
StylisticTen,
StylisticThirteen,
StylisticThree,
StylisticTwelve,
StylisticTwenty,
StylisticTwo,
TabularNums,
Thick,
Thin,
Top,
Unset,
Visible,
Wrap,
WrapReverse,
};
/**
* Represents a constrained set of CSS keywords.
*/
template <typename T>
concept CSSKeywordSet =
std::is_enum_v<T> && std::is_same_v<std::underlying_type_t<T>, std::underlying_type_t<CSSKeyword>>;
/**
* CSS-wide keywords.
* https://www.w3.org/TR/css-values-4/#common-keywords
*/
enum class CSSWideKeyword : std::underlying_type_t<CSSKeyword> {
Inherit = to_underlying(CSSKeyword::Inherit),
Initial = to_underlying(CSSKeyword::Initial),
Unset = to_underlying(CSSKeyword::Unset),
};
#define CSS_DEFINE_KEYWORD(enumName, name) \
namespace detail::css::keywords { \
template <typename T> \
concept has##enumName = (CSSKeywordSet<T> && requires() { T::enumName; }); \
constexpr std::string_view enumName{name}; \
static_assert(has##enumName<CSSKeyword>); \
}
CSS_DEFINE_KEYWORD(Absolute, "absolute")
CSS_DEFINE_KEYWORD(Auto, "auto")
CSS_DEFINE_KEYWORD(Baseline, "baseline")
CSS_DEFINE_KEYWORD(Block, "block")
CSS_DEFINE_KEYWORD(Bottom, "bottom")
CSS_DEFINE_KEYWORD(Center, "center")
CSS_DEFINE_KEYWORD(Clip, "clip")
CSS_DEFINE_KEYWORD(Column, "column")
CSS_DEFINE_KEYWORD(ColumnReverse, "column-reverse")
CSS_DEFINE_KEYWORD(CommonLigatures, "common-ligatures")
CSS_DEFINE_KEYWORD(Content, "content")
CSS_DEFINE_KEYWORD(Contents, "contents")
CSS_DEFINE_KEYWORD(Contextual, "contextual")
CSS_DEFINE_KEYWORD(Dashed, "dashed")
CSS_DEFINE_KEYWORD(DiscretionaryLigatures, "discretionary-ligatures")
CSS_DEFINE_KEYWORD(Dotted, "dotted")
CSS_DEFINE_KEYWORD(Double, "double")
CSS_DEFINE_KEYWORD(End, "end")
CSS_DEFINE_KEYWORD(Fixed, "fixed")
CSS_DEFINE_KEYWORD(Flex, "flex")
CSS_DEFINE_KEYWORD(FlexEnd, "flex-end")
CSS_DEFINE_KEYWORD(FlexStart, "flex-start")
CSS_DEFINE_KEYWORD(Grid, "grid")
CSS_DEFINE_KEYWORD(Groove, "groove")
CSS_DEFINE_KEYWORD(Hidden, "hidden")
CSS_DEFINE_KEYWORD(HistoricalLigatures, "historical-ligatures")
CSS_DEFINE_KEYWORD(Inherit, "inherit")
CSS_DEFINE_KEYWORD(Initial, "initial")
CSS_DEFINE_KEYWORD(Inline, "inline")
CSS_DEFINE_KEYWORD(InlineBlock, "inline-block")
CSS_DEFINE_KEYWORD(InlineFlex, "inline-flex")
CSS_DEFINE_KEYWORD(InlineGrid, "inline-grid")
CSS_DEFINE_KEYWORD(Inset, "inset")
CSS_DEFINE_KEYWORD(Left, "left")
CSS_DEFINE_KEYWORD(LiningNums, "lining-nums")
CSS_DEFINE_KEYWORD(Ltr, "ltr")
CSS_DEFINE_KEYWORD(MaxContent, "max-content")
CSS_DEFINE_KEYWORD(Medium, "medium")
CSS_DEFINE_KEYWORD(MinContent, "min-content")
CSS_DEFINE_KEYWORD(NoCommonLigatures, "no-common-ligatures")
CSS_DEFINE_KEYWORD(NoContextual, "no-contextual")
CSS_DEFINE_KEYWORD(NoDiscretionaryLigatures, "no-discretionary-ligatures")
CSS_DEFINE_KEYWORD(NoHistoricalLigatures, "no-historical-ligatures")
CSS_DEFINE_KEYWORD(None, "none")
CSS_DEFINE_KEYWORD(Normal, "normal")
CSS_DEFINE_KEYWORD(NoWrap, "nowrap")
CSS_DEFINE_KEYWORD(OldstyleNums, "oldstyle-nums")
CSS_DEFINE_KEYWORD(Outset, "outset")
CSS_DEFINE_KEYWORD(ProportionalNums, "proportional-nums")
CSS_DEFINE_KEYWORD(Relative, "relative")
CSS_DEFINE_KEYWORD(Ridge, "ridge")
CSS_DEFINE_KEYWORD(Right, "right")
CSS_DEFINE_KEYWORD(Row, "row")
CSS_DEFINE_KEYWORD(RowReverse, "row-reverse")
CSS_DEFINE_KEYWORD(Rtl, "rtl")
CSS_DEFINE_KEYWORD(Scroll, "scroll")
CSS_DEFINE_KEYWORD(SmallCaps, "small-caps")
CSS_DEFINE_KEYWORD(Solid, "solid")
CSS_DEFINE_KEYWORD(SpaceAround, "space-around")
CSS_DEFINE_KEYWORD(SpaceBetween, "space-between")
CSS_DEFINE_KEYWORD(SpaceEvenly, "space-evenly")
CSS_DEFINE_KEYWORD(Start, "start")
CSS_DEFINE_KEYWORD(Static, "static")
CSS_DEFINE_KEYWORD(Sticky, "sticky")
CSS_DEFINE_KEYWORD(Stretch, "stretch")
CSS_DEFINE_KEYWORD(StylisticEight, "stylistic-eight")
CSS_DEFINE_KEYWORD(StylisticEighteen, "stylistic-eighteen")
CSS_DEFINE_KEYWORD(StylisticEleven, "stylistic-eleven")
CSS_DEFINE_KEYWORD(StylisticFifteen, "stylistic-fifteen")
CSS_DEFINE_KEYWORD(StylisticFive, "stylistic-five")
CSS_DEFINE_KEYWORD(StylisticFour, "stylistic-four")
CSS_DEFINE_KEYWORD(StylisticFourteen, "stylistic-fourteen")
CSS_DEFINE_KEYWORD(StylisticNine, "stylistic-nine")
CSS_DEFINE_KEYWORD(StylisticNineteen, "stylistic-nineteen")
CSS_DEFINE_KEYWORD(StylisticOne, "stylistic-one")
CSS_DEFINE_KEYWORD(StylisticSeven, "stylistic-seven")
CSS_DEFINE_KEYWORD(StylisticSeventeen, "stylistic-seventeen")
CSS_DEFINE_KEYWORD(StylisticSix, "stylistic-six")
CSS_DEFINE_KEYWORD(StylisticSixteen, "stylistic-sixteen")
CSS_DEFINE_KEYWORD(StylisticTen, "stylistic-ten")
CSS_DEFINE_KEYWORD(StylisticThirteen, "stylistic-thirteen")
CSS_DEFINE_KEYWORD(StylisticThree, "stylistic-three")
CSS_DEFINE_KEYWORD(StylisticTwelve, "stylistic-twelve")
CSS_DEFINE_KEYWORD(StylisticTwenty, "stylistic-twenty")
CSS_DEFINE_KEYWORD(StylisticTwo, "stylistic-two")
CSS_DEFINE_KEYWORD(TabularNums, "tabular-nums")
CSS_DEFINE_KEYWORD(Thick, "thick")
CSS_DEFINE_KEYWORD(Thin, "thin")
CSS_DEFINE_KEYWORD(Top, "top")
CSS_DEFINE_KEYWORD(Unset, "unset")
CSS_DEFINE_KEYWORD(Visible, "visible")
CSS_DEFINE_KEYWORD(Wrap, "wrap")
CSS_DEFINE_KEYWORD(WrapReverse, "wrap-reverse")
#define CSS_HANDLE_KEYWORD(name) \
case fnv1a(detail::css::keywords::name): \
if constexpr (detail::css::keywords::has##name<KeywordT>) { \
return KeywordT::name; \
} \
break;
/**
* 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 <CSSKeywordSet KeywordT>
constexpr std::optional<KeywordT> parseCSSKeyword(std::string_view ident)
{
switch (fnv1aLowercase(ident)) {
CSS_HANDLE_KEYWORD(Absolute)
CSS_HANDLE_KEYWORD(Auto)
CSS_HANDLE_KEYWORD(Baseline)
CSS_HANDLE_KEYWORD(Block)
CSS_HANDLE_KEYWORD(Bottom)
CSS_HANDLE_KEYWORD(Center)
CSS_HANDLE_KEYWORD(Clip)
CSS_HANDLE_KEYWORD(Column)
CSS_HANDLE_KEYWORD(ColumnReverse)
CSS_HANDLE_KEYWORD(CommonLigatures)
CSS_HANDLE_KEYWORD(Content)
CSS_HANDLE_KEYWORD(Contents)
CSS_HANDLE_KEYWORD(Contextual)
CSS_HANDLE_KEYWORD(Dashed)
CSS_HANDLE_KEYWORD(DiscretionaryLigatures)
CSS_HANDLE_KEYWORD(Dotted)
CSS_HANDLE_KEYWORD(Double)
CSS_HANDLE_KEYWORD(End)
CSS_HANDLE_KEYWORD(Fixed)
CSS_HANDLE_KEYWORD(Flex)
CSS_HANDLE_KEYWORD(FlexEnd)
CSS_HANDLE_KEYWORD(FlexStart)
CSS_HANDLE_KEYWORD(Grid)
CSS_HANDLE_KEYWORD(Groove)
CSS_HANDLE_KEYWORD(Hidden)
CSS_HANDLE_KEYWORD(HistoricalLigatures)
CSS_HANDLE_KEYWORD(Inherit)
CSS_HANDLE_KEYWORD(Initial)
CSS_HANDLE_KEYWORD(Inline)
CSS_HANDLE_KEYWORD(InlineBlock)
CSS_HANDLE_KEYWORD(InlineFlex)
CSS_HANDLE_KEYWORD(InlineGrid)
CSS_HANDLE_KEYWORD(Inset)
CSS_HANDLE_KEYWORD(Left)
CSS_HANDLE_KEYWORD(LiningNums)
CSS_HANDLE_KEYWORD(Ltr)
CSS_HANDLE_KEYWORD(MaxContent)
CSS_HANDLE_KEYWORD(Medium)
CSS_HANDLE_KEYWORD(MinContent)
CSS_HANDLE_KEYWORD(NoCommonLigatures)
CSS_HANDLE_KEYWORD(NoContextual)
CSS_HANDLE_KEYWORD(NoDiscretionaryLigatures)
CSS_HANDLE_KEYWORD(NoHistoricalLigatures)
CSS_HANDLE_KEYWORD(None)
CSS_HANDLE_KEYWORD(Normal)
CSS_HANDLE_KEYWORD(NoWrap)
CSS_HANDLE_KEYWORD(OldstyleNums)
CSS_HANDLE_KEYWORD(Outset)
CSS_HANDLE_KEYWORD(ProportionalNums)
CSS_HANDLE_KEYWORD(Relative)
CSS_HANDLE_KEYWORD(Ridge)
CSS_HANDLE_KEYWORD(Right)
CSS_HANDLE_KEYWORD(Row)
CSS_HANDLE_KEYWORD(RowReverse)
CSS_HANDLE_KEYWORD(Rtl)
CSS_HANDLE_KEYWORD(Scroll)
CSS_HANDLE_KEYWORD(SmallCaps)
CSS_HANDLE_KEYWORD(Solid)
CSS_HANDLE_KEYWORD(SpaceAround)
CSS_HANDLE_KEYWORD(SpaceBetween)
CSS_HANDLE_KEYWORD(SpaceEvenly)
CSS_HANDLE_KEYWORD(Start)
CSS_HANDLE_KEYWORD(Static)
CSS_HANDLE_KEYWORD(Sticky)
CSS_HANDLE_KEYWORD(Stretch)
CSS_HANDLE_KEYWORD(StylisticEight)
CSS_HANDLE_KEYWORD(StylisticEighteen)
CSS_HANDLE_KEYWORD(StylisticEleven)
CSS_HANDLE_KEYWORD(StylisticFifteen)
CSS_HANDLE_KEYWORD(StylisticFive)
CSS_HANDLE_KEYWORD(StylisticFour)
CSS_HANDLE_KEYWORD(StylisticFourteen)
CSS_HANDLE_KEYWORD(StylisticNine)
CSS_HANDLE_KEYWORD(StylisticNineteen)
CSS_HANDLE_KEYWORD(StylisticOne)
CSS_HANDLE_KEYWORD(StylisticSeven)
CSS_HANDLE_KEYWORD(StylisticSeventeen)
CSS_HANDLE_KEYWORD(StylisticSix)
CSS_HANDLE_KEYWORD(StylisticSixteen)
CSS_HANDLE_KEYWORD(StylisticTen)
CSS_HANDLE_KEYWORD(StylisticThirteen)
CSS_HANDLE_KEYWORD(StylisticThree)
CSS_HANDLE_KEYWORD(StylisticTwelve)
CSS_HANDLE_KEYWORD(StylisticTwenty)
CSS_HANDLE_KEYWORD(StylisticTwo)
CSS_HANDLE_KEYWORD(TabularNums)
CSS_HANDLE_KEYWORD(Thick)
CSS_HANDLE_KEYWORD(Thin)
CSS_HANDLE_KEYWORD(Top)
CSS_HANDLE_KEYWORD(Unset)
CSS_HANDLE_KEYWORD(Visible)
CSS_HANDLE_KEYWORD(Wrap)
CSS_HANDLE_KEYWORD(WrapReverse)
}
return std::nullopt;
}
template <CSSKeywordSet KeywordT>
struct CSSDataTypeParser<KeywordT> {
static constexpr auto consumePreservedToken(const CSSPreservedToken &token) -> std::optional<KeywordT>
{
if (token.type() == CSSTokenType::Ident) {
return parseCSSKeyword<KeywordT>(token.stringValue());
}
return {};
}
};
static_assert(CSSDataType<CSSWideKeyword>);
} // namespace facebook::react

View File

@@ -0,0 +1,58 @@
/*
* 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 <optional>
#include <react/renderer/css/CSSDataType.h>
#include <react/renderer/css/CSSLengthUnit.h>
namespace facebook::react {
/**
* Representation of CSS <length> data type
* 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;
};
template <>
struct CSSDataTypeParser<CSSLength> {
static constexpr auto consumePreservedToken(const CSSPreservedToken &token) -> std::optional<CSSLength>
{
switch (token.type()) {
case CSSTokenType::Dimension:
if (auto unit = parseCSSLengthUnit(token.unit())) {
return CSSLength{.value = token.numericValue(), .unit = *unit};
}
break;
case CSSTokenType::Number:
// For zero lengths the unit identifier is optional (i.e. can be
// syntactically represented as the <number> 0). However, if a 0
// could be parsed as either a <number> or a <length> in a
// property (such as line-height), it must parse as a <number>.
// https://www.w3.org/TR/css-values-4/#lengths
if (token.numericValue() == 0) {
return CSSLength{.value = token.numericValue(), .unit = CSSLengthUnit::Px};
}
break;
default:
break;
}
return {};
}
};
static_assert(CSSDataType<CSSLength>);
} // namespace facebook::react

View File

@@ -0,0 +1,21 @@
/*
* 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 <react/renderer/css/CSSCompoundDataType.h>
#include <react/renderer/css/CSSLength.h>
#include <react/renderer/css/CSSPercentage.h>
namespace facebook::react {
/**
* Marker for the <length-percentage> data type
* https://drafts.csswg.org/css-values/#mixed-percentages
*/
using CSSLengthPercentage = CSSCompoundDataType<CSSLength, CSSPercentage>;
} // namespace facebook::react

View File

@@ -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 <cstdint>
#include <optional>
#include <string_view>
#include <react/utils/fnv1a.h>
namespace facebook::react {
/**
* Unit for the CSS <length> 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<CSSLengthUnit> parseCSSLengthUnit(std::string_view unit)
{
switch (fnv1aLowercase(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

View File

@@ -0,0 +1,72 @@
/*
* 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 <optional>
#include <type_traits>
#include <variant>
#include <react/renderer/css/CSSCompoundDataType.h>
#include <react/renderer/css/CSSDataType.h>
#include <react/renderer/css/CSSValueParser.h>
namespace facebook::react {
template <CSSMaybeCompoundDataType T, CSSDelimiter Delim>
struct CSSList;
template <CSSDataType AllowedTypeT, CSSDelimiter Delim>
struct CSSList<AllowedTypeT, Delim> : public std::vector<AllowedTypeT> {};
template <CSSValidCompoundDataType AllowedTypesT, CSSDelimiter Delim>
struct CSSList<AllowedTypesT, Delim> : public std::vector<CSSVariantWithTypes<AllowedTypesT>> {};
template <CSSMaybeCompoundDataType AllowedTypeT, CSSDelimiter Delim>
struct CSSDataTypeParser<CSSList<AllowedTypeT, Delim>> {
static inline auto consume(CSSSyntaxParser &parser) -> std::optional<CSSList<AllowedTypeT, Delim>>
{
CSSList<AllowedTypeT, Delim> result;
for (auto nextValue = parseNextCSSValue<AllowedTypeT>(parser); !std::holds_alternative<std::monostate>(nextValue);
nextValue = parseNextCSSValue<AllowedTypeT>(parser, Delim)) {
// Copy from the variant of possible values to the element (either the
// concrete type, or a variant of compound types which exlcudes the
// possibility of std::monostate for parse error)
std::visit(
[&](auto &&v) {
if constexpr (!std::is_same_v<std::remove_cvref_t<decltype(v)>, std::monostate>) {
result.push_back(std::forward<decltype(v)>(v));
}
},
nextValue);
}
if (result.empty()) {
return {};
}
return result;
}
};
/**
* Represents a comma-separated repetition of a single type, or compound type
* (represented as a variant of possible types).
* https://www.w3.org/TR/css-values-4/#mult-comma
*/
template <CSSMaybeCompoundDataType AllowedTypeT>
using CSSCommaSeparatedList = CSSList<AllowedTypeT, CSSDelimiter::Comma>;
/**
* Represents a whitespace-separated repetition of a single type, or compound
* type (represented as a variant of possible types).
* https://www.w3.org/TR/css-values-4/#component-combinators
*/
template <CSSMaybeCompoundDataType AllowedTypeT>
using CSSWhitespaceSeparatedList = CSSList<AllowedTypeT, CSSDelimiter::Whitespace>;
} // namespace facebook::react

View File

@@ -0,0 +1,329 @@
/*
* 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 <optional>
#include <string_view>
#include <react/utils/fnv1a.h>
namespace facebook::react {
/**
* Parse one of the given <named-color>, including the "transparent" special
* keyword.
* https://www.w3.org/TR/css-color-4/#named-colors
*/
template <typename CSSColor>
constexpr std::optional<CSSColor> parseCSSNamedColor(std::string_view name)
{
switch (fnv1aLowercase(name)) {
case fnv1a("aliceblue"):
return CSSColor{240, 248, 255, 255};
case fnv1a("antiquewhite"):
return CSSColor{250, 235, 215, 255};
case fnv1a("aqua"):
return CSSColor{0, 255, 255, 255};
case fnv1a("aquamarine"):
return CSSColor{127, 255, 212, 255};
case fnv1a("azure"):
return CSSColor{240, 255, 255, 255};
case fnv1a("beige"):
return CSSColor{245, 245, 220, 255};
case fnv1a("bisque"):
return CSSColor{255, 228, 196, 255};
case fnv1a("black"):
return CSSColor{0, 0, 0, 255};
case fnv1a("blanchedalmond"):
return CSSColor{255, 235, 205, 255};
case fnv1a("blue"):
return CSSColor{0, 0, 255, 255};
case fnv1a("blueviolet"):
return CSSColor{138, 43, 226, 255};
case fnv1a("brown"):
return CSSColor{165, 42, 42, 255};
case fnv1a("burlywood"):
return CSSColor{222, 184, 135, 255};
case fnv1a("cadetblue"):
return CSSColor{95, 158, 160, 255};
case fnv1a("chartreuse"):
return CSSColor{127, 255, 0, 255};
case fnv1a("chocolate"):
return CSSColor{210, 105, 30, 255};
case fnv1a("coral"):
return CSSColor{255, 127, 80, 255};
case fnv1a("cornflowerblue"):
return CSSColor{100, 149, 237, 255};
case fnv1a("cornsilk"):
return CSSColor{255, 248, 220, 255};
case fnv1a("crimson"):
return CSSColor{220, 20, 60, 255};
case fnv1a("cyan"):
return CSSColor{0, 255, 255, 255};
case fnv1a("darkblue"):
return CSSColor{0, 0, 139, 255};
case fnv1a("darkcyan"):
return CSSColor{0, 139, 139, 255};
case fnv1a("darkgoldenrod"):
return CSSColor{184, 134, 11, 255};
case fnv1a("darkgray"):
return CSSColor{169, 169, 169, 255};
case fnv1a("darkgreen"):
return CSSColor{0, 100, 0, 255};
case fnv1a("darkgrey"):
return CSSColor{169, 169, 169, 255};
case fnv1a("darkkhaki"):
return CSSColor{189, 183, 107, 255};
case fnv1a("darkmagenta"):
return CSSColor{139, 0, 139, 255};
case fnv1a("darkolivegreen"):
return CSSColor{85, 107, 47, 255};
case fnv1a("darkorange"):
return CSSColor{255, 140, 0, 255};
case fnv1a("darkorchid"):
return CSSColor{153, 50, 204, 255};
case fnv1a("darkred"):
return CSSColor{139, 0, 0, 255};
case fnv1a("darksalmon"):
return CSSColor{233, 150, 122, 255};
case fnv1a("darkseagreen"):
return CSSColor{143, 188, 143, 255};
case fnv1a("darkslateblue"):
return CSSColor{72, 61, 139, 255};
case fnv1a("darkslategray"):
return CSSColor{47, 79, 79, 255};
case fnv1a("darkslategrey"):
return CSSColor{47, 79, 79, 255};
case fnv1a("darkturquoise"):
return CSSColor{0, 206, 209, 255};
case fnv1a("darkviolet"):
return CSSColor{148, 0, 211, 255};
case fnv1a("deeppink"):
return CSSColor{255, 20, 147, 255};
case fnv1a("deepskyblue"):
return CSSColor{0, 191, 255, 255};
case fnv1a("dimgray"):
return CSSColor{105, 105, 105, 255};
case fnv1a("dimgrey"):
return CSSColor{105, 105, 105, 255};
case fnv1a("dodgerblue"):
return CSSColor{30, 144, 255, 255};
case fnv1a("firebrick"):
return CSSColor{178, 34, 34, 255};
case fnv1a("floralwhite"):
return CSSColor{255, 250, 240, 255};
case fnv1a("forestgreen"):
return CSSColor{34, 139, 34, 255};
case fnv1a("fuchsia"):
return CSSColor{255, 0, 255, 255};
case fnv1a("gainsboro"):
return CSSColor{220, 220, 220, 255};
case fnv1a("ghostwhite"):
return CSSColor{248, 248, 255, 255};
case fnv1a("gold"):
return CSSColor{255, 215, 0, 255};
case fnv1a("goldenrod"):
return CSSColor{218, 165, 32, 255};
case fnv1a("gray"):
return CSSColor{128, 128, 128, 255};
case fnv1a("green"):
return CSSColor{0, 128, 0, 255};
case fnv1a("greenyellow"):
return CSSColor{173, 255, 47, 255};
case fnv1a("grey"):
return CSSColor{128, 128, 128, 255};
case fnv1a("honeydew"):
return CSSColor{240, 255, 240, 255};
case fnv1a("hotpink"):
return CSSColor{255, 105, 180, 255};
case fnv1a("indianred"):
return CSSColor{205, 92, 92, 255};
case fnv1a("indigo"):
return CSSColor{75, 0, 130, 255};
case fnv1a("ivory"):
return CSSColor{255, 255, 240, 255};
case fnv1a("khaki"):
return CSSColor{240, 230, 140, 255};
case fnv1a("lavender"):
return CSSColor{230, 230, 250, 255};
case fnv1a("lavenderblush"):
return CSSColor{255, 240, 245, 255};
case fnv1a("lawngreen"):
return CSSColor{124, 252, 0, 255};
case fnv1a("lemonchiffon"):
return CSSColor{255, 250, 205, 255};
case fnv1a("lightblue"):
return CSSColor{173, 216, 230, 255};
case fnv1a("lightcoral"):
return CSSColor{240, 128, 128, 255};
case fnv1a("lightcyan"):
return CSSColor{224, 255, 255, 255};
case fnv1a("lightgoldenrodyellow"):
return CSSColor{250, 250, 210, 255};
case fnv1a("lightgray"):
return CSSColor{211, 211, 211, 255};
case fnv1a("lightgreen"):
return CSSColor{144, 238, 144, 255};
case fnv1a("lightgrey"):
return CSSColor{211, 211, 211, 255};
case fnv1a("lightpink"):
return CSSColor{255, 182, 193, 255};
case fnv1a("lightsalmon"):
return CSSColor{255, 160, 122, 255};
case fnv1a("lightseagreen"):
return CSSColor{32, 178, 170, 255};
case fnv1a("lightskyblue"):
return CSSColor{135, 206, 250, 255};
case fnv1a("lightslategray"):
return CSSColor{119, 136, 153, 255};
case fnv1a("lightslategrey"):
return CSSColor{119, 136, 153, 255};
case fnv1a("lightsteelblue"):
return CSSColor{176, 196, 222, 255};
case fnv1a("lightyellow"):
return CSSColor{255, 255, 224, 255};
case fnv1a("lime"):
return CSSColor{0, 255, 0, 255};
case fnv1a("limegreen"):
return CSSColor{50, 205, 50, 255};
case fnv1a("linen"):
return CSSColor{250, 240, 230, 255};
case fnv1a("magenta"):
return CSSColor{255, 0, 255, 255};
case fnv1a("maroon"):
return CSSColor{128, 0, 0, 255};
case fnv1a("mediumaquamarine"):
return CSSColor{102, 205, 170, 255};
case fnv1a("mediumblue"):
return CSSColor{0, 0, 205, 255};
case fnv1a("mediumorchid"):
return CSSColor{186, 85, 211, 255};
case fnv1a("mediumpurple"):
return CSSColor{147, 112, 219, 255};
case fnv1a("mediumseagreen"):
return CSSColor{60, 179, 113, 255};
case fnv1a("mediumslateblue"):
return CSSColor{123, 104, 238, 255};
case fnv1a("mediumspringgreen"):
return CSSColor{0, 250, 154, 255};
case fnv1a("mediumturquoise"):
return CSSColor{72, 209, 204, 255};
case fnv1a("mediumvioletred"):
return CSSColor{199, 21, 133, 255};
case fnv1a("midnightblue"):
return CSSColor{25, 25, 112, 255};
case fnv1a("mintcream"):
return CSSColor{245, 255, 250, 255};
case fnv1a("mistyrose"):
return CSSColor{255, 228, 225, 255};
case fnv1a("moccasin"):
return CSSColor{255, 228, 181, 255};
case fnv1a("navajowhite"):
return CSSColor{255, 222, 173, 255};
case fnv1a("navy"):
return CSSColor{0, 0, 128, 255};
case fnv1a("oldlace"):
return CSSColor{253, 245, 230, 255};
case fnv1a("olive"):
return CSSColor{128, 128, 0, 255};
case fnv1a("olivedrab"):
return CSSColor{107, 142, 35, 255};
case fnv1a("orange"):
return CSSColor{255, 165, 0, 255};
case fnv1a("orangered"):
return CSSColor{255, 69, 0, 255};
case fnv1a("orchid"):
return CSSColor{218, 112, 214, 255};
case fnv1a("palegoldenrod"):
return CSSColor{238, 232, 170, 255};
case fnv1a("palegreen"):
return CSSColor{152, 251, 152, 255};
case fnv1a("paleturquoise"):
return CSSColor{175, 238, 238, 255};
case fnv1a("palevioletred"):
return CSSColor{219, 112, 147, 255};
case fnv1a("papayawhip"):
return CSSColor{255, 239, 213, 255};
case fnv1a("peachpuff"):
return CSSColor{255, 218, 185, 255};
case fnv1a("peru"):
return CSSColor{205, 133, 63, 255};
case fnv1a("pink"):
return CSSColor{255, 192, 203, 255};
case fnv1a("plum"):
return CSSColor{221, 160, 221, 255};
case fnv1a("powderblue"):
return CSSColor{176, 224, 230, 255};
case fnv1a("purple"):
return CSSColor{128, 0, 128, 255};
case fnv1a("rebeccapurple"):
return CSSColor{102, 51, 153, 255};
case fnv1a("red"):
return CSSColor{255, 0, 0, 255};
case fnv1a("rosybrown"):
return CSSColor{188, 143, 143, 255};
case fnv1a("royalblue"):
return CSSColor{65, 105, 225, 255};
case fnv1a("saddlebrown"):
return CSSColor{139, 69, 19, 255};
case fnv1a("salmon"):
return CSSColor{250, 128, 114, 255};
case fnv1a("sandybrown"):
return CSSColor{244, 164, 96, 255};
case fnv1a("seagreen"):
return CSSColor{46, 139, 87, 255};
case fnv1a("seashell"):
return CSSColor{255, 245, 238, 255};
case fnv1a("sienna"):
return CSSColor{160, 82, 45, 255};
case fnv1a("silver"):
return CSSColor{192, 192, 192, 255};
case fnv1a("skyblue"):
return CSSColor{135, 206, 235, 255};
case fnv1a("slateblue"):
return CSSColor{106, 90, 205, 255};
case fnv1a("slategray"):
return CSSColor{112, 128, 144, 255};
case fnv1a("slategrey"):
return CSSColor{112, 128, 144, 255};
case fnv1a("snow"):
return CSSColor{255, 250, 250, 255};
case fnv1a("springgreen"):
return CSSColor{0, 255, 127, 255};
case fnv1a("steelblue"):
return CSSColor{70, 130, 180, 255};
case fnv1a("tan"):
return CSSColor{210, 180, 140, 255};
case fnv1a("teal"):
return CSSColor{0, 128, 128, 255};
case fnv1a("thistle"):
return CSSColor{216, 191, 216, 255};
case fnv1a("tomato"):
return CSSColor{255, 99, 71, 255};
case fnv1a("transparent"):
return CSSColor{0, 0, 0, 0};
case fnv1a("turquoise"):
return CSSColor{64, 224, 208, 255};
case fnv1a("violet"):
return CSSColor{238, 130, 238, 255};
case fnv1a("wheat"):
return CSSColor{245, 222, 179, 255};
case fnv1a("white"):
return CSSColor{255, 255, 255, 255};
case fnv1a("whitesmoke"):
return CSSColor{245, 245, 245, 255};
case fnv1a("yellow"):
return CSSColor{255, 255, 0, 255};
case fnv1a("yellowgreen"):
return CSSColor{154, 205, 50, 255};
default:
return std::nullopt;
}
}
} // namespace facebook::react

View File

@@ -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 <optional>
#include <react/renderer/css/CSSDataType.h>
namespace facebook::react {
/**
* Representation of CSS <number> data type
* https://www.w3.org/TR/css-values-4/#numbers
*/
struct CSSNumber {
float value{};
constexpr bool operator==(const CSSNumber &rhs) const = default;
};
template <>
struct CSSDataTypeParser<CSSNumber> {
static constexpr auto consumePreservedToken(const CSSPreservedToken &token) -> std::optional<CSSNumber>
{
if (token.type() == CSSTokenType::Number) {
return CSSNumber{token.numericValue()};
}
return {};
}
};
static_assert(CSSDataType<CSSNumber>);
} // namespace facebook::react

View File

@@ -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 <optional>
#include <react/renderer/css/CSSDataType.h>
namespace facebook::react {
/**
* Representation of CSS <percentage> data type
* https://www.w3.org/TR/css-values-4/#percentages
*/
struct CSSPercentage {
float value{};
constexpr bool operator==(const CSSPercentage &rhs) const = default;
};
template <>
struct CSSDataTypeParser<CSSPercentage> {
static constexpr auto consumePreservedToken(const CSSPreservedToken &token) -> std::optional<CSSPercentage>
{
if (token.type() == CSSTokenType::Percentage) {
return CSSPercentage{token.numericValue()};
}
return {};
}
};
static_assert(CSSDataType<CSSPercentage>);
} // namespace facebook::react

View File

@@ -0,0 +1,67 @@
/*
* 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 <optional>
#include <variant>
#include <react/renderer/css/CSSDataType.h>
#include <react/renderer/css/CSSNumber.h>
#include <react/renderer/css/CSSValueParser.h>
namespace facebook::react {
/**
* Representation of CSS <ratio> data type
* https://www.w3.org/TR/css-values-4/#ratios
*/
struct CSSRatio {
float numerator{};
float denominator{};
constexpr bool operator==(const CSSRatio &rhs) const = default;
constexpr bool isDegenerate() const
{
// If either number in the <ratio> is 0 or infinite, it represents a
// degenerate ratio (and, generally, wont do anything).
// https://www.w3.org/TR/css-values-4/#ratios
return numerator == 0.0f || numerator == std::numeric_limits<float>::infinity() || denominator == 0.0f ||
denominator == std::numeric_limits<float>::infinity();
}
};
template <>
struct CSSDataTypeParser<CSSRatio> {
static constexpr auto consume(CSSSyntaxParser &parser) -> std::optional<CSSRatio>
{
// <ratio> = <number [0,∞]> [ / <number [0,∞]> ]?
// https://www.w3.org/TR/css-values-4/#ratio
auto numerator = parseNextCSSValue<CSSNumber>(parser);
if (!std::holds_alternative<CSSNumber>(numerator)) {
return {};
}
auto numeratorValue = std::get<CSSNumber>(numerator).value;
if (numeratorValue >= 0) {
auto denominator = peekNextCSSValue<CSSNumber>(parser, CSSDelimiter::Solidus);
if (std::holds_alternative<CSSNumber>(denominator) && std::get<CSSNumber>(denominator).value >= 0) {
parseNextCSSValue<CSSNumber>(parser, CSSDelimiter::Solidus);
return CSSRatio{.numerator = numeratorValue, .denominator = std::get<CSSNumber>(denominator).value};
}
return CSSRatio{.numerator = numeratorValue, .denominator = 1.0f};
}
return {};
}
};
static_assert(CSSDataType<CSSRatio>);
} // namespace facebook::react

View File

@@ -0,0 +1,135 @@
/*
* 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 <optional>
#include <tuple>
#include <react/renderer/css/CSSColor.h>
#include <react/renderer/css/CSSDataType.h>
#include <react/renderer/css/CSSKeyword.h>
#include <react/renderer/css/CSSLength.h>
#include <react/renderer/css/CSSList.h>
#include <react/renderer/css/CSSValueParser.h>
#include <react/utils/to_underlying.h>
namespace facebook::react {
/**
* Representation of CSS <shadow> data type
* https://drafts.csswg.org/css-backgrounds/#typedef-shadow
*/
struct CSSShadow {
CSSLength offsetX{};
CSSLength offsetY{};
CSSLength blurRadius{};
CSSLength spreadDistance{};
CSSColor color{CSSColor::black()};
bool inset{false};
constexpr bool operator==(const CSSShadow &rhs) const = default;
};
/**
* Represents a keyword for an inset shadow.
*/
enum class CSSInsetShadowKeyword : std::underlying_type_t<CSSKeyword> {
Inset = to_underlying(CSSKeyword::Inset),
};
static_assert(CSSDataType<CSSInsetShadowKeyword>);
template <>
struct CSSDataTypeParser<CSSShadow> {
static constexpr auto consume(CSSSyntaxParser &parser) -> std::optional<CSSShadow>
{
std::optional<CSSColor> color{};
bool inset{false};
std::optional<std::tuple<CSSLength, CSSLength, CSSLength, CSSLength>> lengths{};
for (auto nextValue = parseNextCSSValue<CSSLength, CSSColor, CSSInsetShadowKeyword>(parser);
!std::holds_alternative<std::monostate>(nextValue);
nextValue = parseNextCSSValue<CSSLength, CSSColor, CSSInsetShadowKeyword>(parser, CSSDelimiter::Whitespace)) {
if (std::holds_alternative<CSSLength>(nextValue)) {
if (lengths.has_value()) {
return {};
}
lengths = parseRestLengths(std::get<CSSLength>(nextValue), parser);
if (!lengths.has_value()) {
return {};
}
continue;
}
if (std::holds_alternative<CSSColor>(nextValue)) {
if (color.has_value()) {
return {};
}
color = std::get<CSSColor>(nextValue);
continue;
}
if (std::holds_alternative<CSSInsetShadowKeyword>(nextValue)) {
if (inset) {
return {};
}
inset = true;
continue;
}
}
if (!lengths.has_value()) {
return {};
}
return CSSShadow{
.offsetX = std::get<0>(*lengths),
.offsetY = std::get<1>(*lengths),
.blurRadius = std::get<2>(*lengths),
.spreadDistance = std::get<3>(*lengths),
.color = color.value_or(CSSColor::black()),
.inset = inset,
};
}
private:
static constexpr auto parseRestLengths(CSSLength offsetX, CSSSyntaxParser &parser)
-> std::optional<std::tuple<CSSLength, CSSLength, CSSLength, CSSLength>>
{
auto offsetY = parseNextCSSValue<CSSLength>(parser, CSSDelimiter::Whitespace);
if (std::holds_alternative<std::monostate>(offsetY)) {
return {};
}
auto blurRadius = parseNextCSSValue<CSSLength>(parser, CSSDelimiter::Whitespace);
if (std::holds_alternative<std::monostate>(blurRadius)) {
return std::make_tuple(offsetX, std::get<CSSLength>(offsetY), CSSLength{}, CSSLength{});
}
if (std::get<CSSLength>(blurRadius).value < 0) {
return {};
}
auto spreadDistance = parseNextCSSValue<CSSLength>(parser, CSSDelimiter::Whitespace);
if (std::holds_alternative<std::monostate>(spreadDistance)) {
return std::make_tuple(offsetX, std::get<CSSLength>(offsetY), std::get<CSSLength>(blurRadius), CSSLength{});
}
return std::make_tuple(
offsetX, std::get<CSSLength>(offsetY), std::get<CSSLength>(blurRadius), std::get<CSSLength>(spreadDistance));
}
};
static_assert(CSSDataType<CSSShadow>);
/**
* Represents a comma separated list of at least one <shadow>
* <shadow>#
*/
using CSSShadowList = CSSCommaSeparatedList<CSSShadow>;
} // namespace facebook::react

View File

@@ -0,0 +1,408 @@
/*
* 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 <concepts>
#include <optional>
#include <react/renderer/css/CSSTokenizer.h>
namespace facebook::react {
class CSSSyntaxParser;
/**
* Describes context for a CSS function component value.
*/
struct CSSFunctionBlock {
std::string_view name{};
};
/**
* Describes a preserved token component value.
*/
using CSSPreservedToken = CSSToken;
/**
* Describes context for a CSS function component value.
*/
struct CSSSimpleBlock {
CSSTokenType openBracketType{};
};
/**
* Describes a valid return type for a CSSSyntaxParser visitor
*/
template <typename T>
concept CSSSyntaxVisitorReturn = std::is_default_constructible_v<T> && std::equality_comparable<T>;
/**
* A CSSFunctionVisitor is called to start parsing a function component value.
* At this point, the Parser is positioned at the start of the function
* component value list. It is expected that the visitor finishes before the end
* of the function block.
*/
template <typename T, typename ReturnT>
concept CSSFunctionVisitor =
CSSSyntaxVisitorReturn<ReturnT> && requires(T visitor, CSSFunctionBlock func, CSSSyntaxParser &blockParser) {
{ visitor(func, blockParser) } -> std::convertible_to<ReturnT>;
};
/**
* A CSSPreservedTokenVisitor is called after parsing a preserved token
* component value.
*/
template <typename T, typename ReturnT>
concept CSSPreservedTokenVisitor = CSSSyntaxVisitorReturn<ReturnT> && requires(T visitor, CSSPreservedToken token) {
{ visitor(token) } -> std::convertible_to<ReturnT>;
};
/**
* A CSSSimpleBlockVisitor is called after parsing a simple block component
* value. It is expected that the visitor finishes before the end
* of the block.
*/
template <typename T, typename ReturnT>
concept CSSSimpleBlockVisitor =
CSSSyntaxVisitorReturn<ReturnT> && requires(T visitor, CSSSimpleBlock block, CSSSyntaxParser &blockParser) {
{ visitor(block, blockParser) } -> std::convertible_to<ReturnT>;
};
/**
* Any visitor for a component value.
*/
template <typename T, typename ReturnT>
concept CSSComponentValueVisitor = CSSSyntaxVisitorReturn<ReturnT> &&
(CSSFunctionVisitor<T, ReturnT> || CSSPreservedTokenVisitor<T, ReturnT> || CSSSimpleBlockVisitor<T, ReturnT>);
/**
* Represents a variadic set of CSSComponentValueVisitor with no more than one
* of a specific type of visitor.
*/
template <typename ReturnT, typename... VisitorsT>
concept CSSUniqueComponentValueVisitors =
CSSSyntaxVisitorReturn<ReturnT> && (CSSComponentValueVisitor<VisitorsT, ReturnT> && ...) &&
((CSSFunctionVisitor<VisitorsT, ReturnT> ? 1 : 0) + ... + 0) <= 1 &&
((CSSPreservedTokenVisitor<VisitorsT, ReturnT> ? 1 : 0) + ... + 0) <= 1 &&
((CSSSimpleBlockVisitor<VisitorsT, ReturnT> ? 1 : 0) + ... + 0) <= 1;
/**
* Describes the delimeter to expect before the next component value.
*/
enum class CSSDelimiter {
Whitespace,
OptionalWhitespace,
Solidus,
SolidusOrWhitespace,
Comma,
CommaOrWhitespace,
None,
};
/**
* CSSSyntaxParser allows parsing streams of CSS text into "component
* values".
*
* https://www.w3.org/TR/css-syntax-3/#component-value
*/
class CSSSyntaxParser {
template <CSSSyntaxVisitorReturn ReturnT, CSSComponentValueVisitor<ReturnT>... VisitorsT>
friend struct CSSComponentValueVisitorDispatcher;
public:
/**
* Construct the parser over the given string_view, which must stay alive for
* the duration of the CSSSyntaxParser.
*/
explicit constexpr CSSSyntaxParser(std::string_view css) : tokenizer_{css}, currentToken_(tokenizer_.next()) {}
constexpr CSSSyntaxParser(const CSSSyntaxParser &) = default;
constexpr CSSSyntaxParser(CSSSyntaxParser &&) = default;
constexpr CSSSyntaxParser &operator=(const CSSSyntaxParser &) = default;
constexpr CSSSyntaxParser &operator=(CSSSyntaxParser &&) = default;
/**
* Directly consume the next component value. The component value is provided
* to a passed in "visitor", typically a lambda which accepts the component
* value in a new scope. The visitor may read this component parameter into a
* higher-level data structure, and continue parsing within its scope using
* the same underlying CSSSyntaxParser.
*
* The state of the parser is reset if a visitor returns a default-constructed
* value for the given return type, even if it previously advanced the parser.
* If no visitor returns a non-default-constructed value, the component value
* will not be consumed.
*
* https://www.w3.org/TR/css-syntax-3/#consume-component-value
*
* @tparam ReturnT caller-specified return type of visitors. This type will
* be set to its default constructed state if consuming a component value with
* no matching visitors, or syntax error
* @param visitors A unique list of CSSComponentValueVisitor to be called on a
* match
* @param delimiter The expected delimeter to occur before the next component
* value
* @returns the visitor returned value, or a default constructed value if no
* visitor was matched, or a syntax error occurred.
*/
template <CSSSyntaxVisitorReturn ReturnT>
constexpr ReturnT consumeComponentValue(
CSSDelimiter delimiter,
const CSSComponentValueVisitor<ReturnT> auto &...visitors)
requires(CSSUniqueComponentValueVisitors<ReturnT, decltype(visitors)...>);
template <CSSSyntaxVisitorReturn ReturnT>
constexpr ReturnT consumeComponentValue(const CSSComponentValueVisitor<ReturnT> auto &...visitors)
requires(CSSUniqueComponentValueVisitors<ReturnT, decltype(visitors)...>);
/**
* The parser is considered finished when there are no more remaining tokens
* to be processed
*/
constexpr bool isFinished() const
{
return currentToken_.type() == CSSTokenType::EndOfFile;
}
/**
* Consume any whitespace tokens.
*/
constexpr void consumeWhitespace()
{
if (currentToken_.type() == CSSTokenType::WhiteSpace) {
currentToken_ = tokenizer_.next();
}
}
/**
* Consume a delimiter, returning false if a required delimiter is not found.
*/
constexpr bool consumeDelimiter(CSSDelimiter delimiter)
{
if (delimiter == CSSDelimiter::None) {
return true;
}
bool hasWhiteSpace = peek().type() == CSSTokenType::WhiteSpace;
consumeWhitespace();
switch (delimiter) {
case CSSDelimiter::Comma:
if (peek().type() == CSSTokenType::Comma) {
consumeToken();
consumeWhitespace();
return true;
}
return false;
case CSSDelimiter::Whitespace:
return hasWhiteSpace;
case CSSDelimiter::OptionalWhitespace:
return true;
case CSSDelimiter::CommaOrWhitespace:
if (peek().type() == CSSTokenType::Comma) {
consumeToken();
consumeWhitespace();
return true;
}
return hasWhiteSpace;
case CSSDelimiter::Solidus:
if (peek().type() == CSSTokenType::Delim && peek().stringValue() == "/") {
consumeToken();
consumeWhitespace();
return true;
}
return false;
case CSSDelimiter::SolidusOrWhitespace:
if (peek().type() == CSSTokenType::Delim && peek().stringValue() == "/") {
consumeToken();
consumeWhitespace();
return true;
}
return hasWhiteSpace;
case CSSDelimiter::None:
return true;
}
return false;
}
private:
constexpr CSSSyntaxParser(CSSSyntaxParser &parser, CSSTokenType terminator)
: tokenizer_{parser.tokenizer_}, currentToken_{parser.currentToken_}, terminator_{terminator}
{
}
constexpr const CSSToken &peek() const
{
return currentToken_;
}
constexpr CSSToken consumeToken()
{
auto prevToken = currentToken_;
currentToken_ = tokenizer_.next();
return prevToken;
}
constexpr void advanceToBlockParser(CSSSyntaxParser &blockParser)
{
currentToken_ = blockParser.currentToken_;
tokenizer_ = blockParser.tokenizer_;
}
CSSTokenizer tokenizer_;
CSSToken currentToken_;
CSSTokenType terminator_{CSSTokenType::EndOfFile};
};
template <CSSSyntaxVisitorReturn ReturnT, CSSComponentValueVisitor<ReturnT>... VisitorsT>
struct CSSComponentValueVisitorDispatcher {
CSSSyntaxParser &parser;
constexpr ReturnT consumeComponentValue(CSSDelimiter delimiter, const VisitorsT &...visitors)
{
auto originalParser = parser;
if (!parser.consumeDelimiter(delimiter)) {
parser = originalParser;
return {};
}
if (parser.peek().type() == parser.terminator_) {
parser = originalParser;
return {};
}
switch (parser.peek().type()) {
case CSSTokenType::Function:
if (auto ret = visitFunction(visitors...)) {
return *ret;
}
break;
case CSSTokenType::OpenParen:
if (auto ret = visitSimpleBlock(CSSTokenType::CloseParen, visitors...)) {
return *ret;
}
break;
case CSSTokenType::OpenSquare:
if (auto ret = visitSimpleBlock(CSSTokenType::CloseSquare, visitors...)) {
return *ret;
}
break;
case CSSTokenType::OpenCurly:
if (auto ret = visitSimpleBlock(CSSTokenType::CloseCurly, visitors...)) {
return *ret;
}
break;
default:
if (auto ret = visitPreservedToken(visitors...)) {
return *ret;
}
break;
}
parser = originalParser;
return ReturnT{};
}
constexpr std::optional<ReturnT> visitFunction(
const CSSComponentValueVisitor<ReturnT> auto &visitor,
const CSSComponentValueVisitor<ReturnT> auto &...rest)
{
if constexpr (CSSFunctionVisitor<decltype(visitor), ReturnT>) {
auto name = parser.consumeToken().stringValue();
// CSS syntax spec says whitespace is a preserved token, but CSS values
// spec allows whitespace around parens for all function notation, so we
// allow this to let the visitors not need to deal with leading/trailing
// whitespace. https://www.w3.org/TR/css-values-3/#functional-notations
parser.consumeWhitespace();
auto blockParser = CSSSyntaxParser{parser, CSSTokenType::CloseParen /*terminator*/};
auto functionValue = visitor({name}, blockParser);
parser.advanceToBlockParser(blockParser);
parser.consumeWhitespace();
if (parser.peek().type() == CSSTokenType::CloseParen && functionValue != ReturnT{}) {
parser.consumeToken();
return functionValue;
}
return {};
}
return visitFunction(rest...);
}
constexpr std::optional<ReturnT> visitFunction()
{
return {};
}
constexpr std::optional<ReturnT> visitSimpleBlock(
CSSTokenType endToken,
const CSSComponentValueVisitor<ReturnT> auto &visitor,
const CSSComponentValueVisitor<ReturnT> auto &...rest)
{
if constexpr (CSSSimpleBlockVisitor<decltype(visitor), ReturnT>) {
auto openBracketType = parser.consumeToken().type();
parser.consumeWhitespace();
auto blockParser = CSSSyntaxParser{parser, endToken};
auto blockValue = visitor({openBracketType}, blockParser);
parser.advanceToBlockParser(blockParser);
parser.consumeWhitespace();
if (parser.peek().type() == endToken && blockValue != ReturnT{}) {
parser.consumeToken();
return blockValue;
}
return {};
}
return visitSimpleBlock(endToken, rest...);
}
constexpr std::optional<ReturnT> visitSimpleBlock(CSSTokenType endToken)
{
return {};
}
constexpr std::optional<ReturnT> visitPreservedToken(
const CSSComponentValueVisitor<ReturnT> auto &visitor,
const CSSComponentValueVisitor<ReturnT> auto &...rest)
{
if constexpr (CSSPreservedTokenVisitor<decltype(visitor), ReturnT>) {
auto ret = visitor(parser.consumeToken());
if (ret != ReturnT{}) {
return ret;
}
}
return visitPreservedToken(rest...);
}
constexpr std::optional<ReturnT> visitPreservedToken()
{
return {};
}
};
template <CSSSyntaxVisitorReturn ReturnT>
constexpr ReturnT CSSSyntaxParser::consumeComponentValue(
CSSDelimiter delimiter,
const CSSComponentValueVisitor<ReturnT> auto &...visitors)
requires(CSSUniqueComponentValueVisitors<ReturnT, decltype(visitors)...>)
{
return CSSComponentValueVisitorDispatcher<ReturnT, decltype(visitors)...>{*this}.consumeComponentValue(
delimiter, visitors...);
}
template <CSSSyntaxVisitorReturn ReturnT>
constexpr ReturnT CSSSyntaxParser::consumeComponentValue(const CSSComponentValueVisitor<ReturnT> auto &...visitors)
requires(CSSUniqueComponentValueVisitors<ReturnT, decltype(visitors)...>)
{
return consumeComponentValue<ReturnT>(CSSDelimiter::None, visitors...);
}
} // namespace facebook::react

View File

@@ -0,0 +1,88 @@
/*
* 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 <string_view>
namespace facebook::react {
/**
* One of the tokens defined as part of
* https://www.w3.org/TR/css-syntax-3/#tokenizer-definitions
*/
enum class CSSTokenType {
CloseCurly,
CloseParen,
CloseSquare,
Comma,
Delim,
Dimension,
EndOfFile,
Function,
Ident,
Number,
OpenCurly,
OpenParen,
OpenSquare,
Percentage,
WhiteSpace,
Hash,
};
/*
* Represents one of the syntactic CSS tokens as provided by
* https://www.w3.org/TR/css-syntax-3/#tokenization
*
* The token should not be kept long-term, and is only valid for the duration of
* the originating CSSTokenizer.
*/
class CSSToken {
public:
explicit constexpr CSSToken(CSSTokenType type) : type_(type) {}
constexpr CSSToken(CSSTokenType type, std::string_view value) : type_{type}, stringValue_{value} {}
constexpr CSSToken(CSSTokenType type, float value) : type_{type}, numericValue_{value} {}
constexpr CSSToken(CSSTokenType type, float value, std::string_view unit)
: type_{type}, numericValue_{value}, unit_{unit}
{
}
constexpr CSSToken(const CSSToken &other) = default;
constexpr CSSToken(CSSToken &&other) = default;
constexpr CSSToken &operator=(const CSSToken &other) = default;
constexpr CSSToken &operator=(CSSToken &&other) = default;
constexpr CSSTokenType type() const
{
return type_;
}
constexpr std::string_view stringValue() const
{
return stringValue_;
}
constexpr float numericValue() const
{
return numericValue_;
}
constexpr std::string_view unit() const
{
return unit_;
}
constexpr bool operator==(const CSSToken &other) const = default;
private:
CSSTokenType type_;
std::string_view stringValue_;
float numericValue_{0.0f};
std::string_view unit_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,248 @@
/*
* 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 <cmath>
#include <string_view>
#include <fast_float/fast_float.h>
#include <react/renderer/css/CSSToken.h>
namespace facebook::react {
/**
* A minimal tokenizer for a subset of CSS syntax.
*
* This is based on the W3C CSS Syntax specification, with simplifications made
* for syntax which React Native does not attempt to support.
* https://www.w3.org/TR/css-syntax-3/#tokenizing-and-parsing
*/
class CSSTokenizer {
public:
explicit constexpr CSSTokenizer(std::string_view characters) : remainingCharacters_{characters} {}
/**
* Returns the next token according to the algorithm described in
* https://www.w3.org/TR/css-syntax-3/#consume-token
*/
constexpr CSSToken next()
{
char nextChar = peek();
if (isWhitespace(nextChar)) {
return consumeWhitespace();
}
switch (nextChar) {
case '(':
return consumeCharacter(CSSTokenType::OpenParen);
case ')':
return consumeCharacter(CSSTokenType::CloseParen);
case '[':
return consumeCharacter(CSSTokenType::OpenSquare);
case ']':
return consumeCharacter(CSSTokenType::CloseSquare);
case '{':
return consumeCharacter(CSSTokenType::OpenCurly);
case '}':
return consumeCharacter(CSSTokenType::CloseCurly);
case ',':
return consumeCharacter(CSSTokenType::Comma);
case '+':
case '-':
case '.':
if (wouldStartNumber()) {
return consumeNumeric();
} else {
return consumeDelim();
}
case '#':
if (isIdent(peek(1))) {
return consumeHash();
} else {
return consumeDelim();
}
}
if (isDigit(nextChar)) {
return consumeNumeric();
}
if (isIdentStart(nextChar)) {
return consumeIdentlikeToken();
}
if (nextChar == '\0') {
return CSSToken{CSSTokenType::EndOfFile};
}
return consumeDelim();
}
private:
constexpr char peek(size_t i = 0) const
{
auto index = position_ + i;
return index >= remainingCharacters_.size() ? '\0' : remainingCharacters_[index];
}
constexpr void advance()
{
position_ += 1;
}
constexpr CSSToken consumeDelim()
{
advance();
return {CSSTokenType::Delim, consumeRunningValue()};
}
constexpr CSSToken consumeCharacter(CSSTokenType tokenType)
{
advance();
consumeRunningValue();
return CSSToken{tokenType};
}
constexpr CSSToken consumeWhitespace()
{
while (isWhitespace(peek())) {
advance();
}
consumeRunningValue();
return CSSToken{CSSTokenType::WhiteSpace};
}
constexpr bool wouldStartNumber() const
{
// https://www.w3.org/TR/css-syntax-3/#starts-with-a-number
if (peek() == '+' || peek() == '-') {
if (isDigit(peek(1))) {
return true;
}
if (peek(1) == '.' && isDigit(peek(2))) {
return true;
}
} else if (peek() == '.' && isDigit(peek(1))) {
return true;
} else if (isDigit(peek())) {
return true;
}
return false;
}
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
auto *b = remainingCharacters_.data();
auto *e = b + remainingCharacters_.size();
float value;
fast_float::parse_options options{fast_float::chars_format::general | fast_float::chars_format::allow_leading_plus};
auto [ptr, ec] = fast_float::from_chars_advanced(b, e, value, options);
// Do we need to handle any other errors?
// bool isOk = ec == std::errc();
position_ += ptr - b;
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 = consumeIdentSequence();
return {CSSTokenType::Dimension, numberToken.numericValue(), ident.stringValue()};
} else if (peek() == '%') {
advance();
consumeRunningValue();
return {CSSTokenType::Percentage, numberToken.numericValue()};
} else {
return numberToken;
}
}
constexpr CSSToken consumeIdentlikeToken()
{
// https://www.w3.org/TR/css-syntax-3/#consume-ident-like-token
auto ident = consumeIdentSequence();
if (peek() == '(') {
advance();
consumeRunningValue();
return {CSSTokenType::Function, ident.stringValue()};
}
return ident;
}
constexpr CSSToken consumeIdentSequence()
{
// https://www.w3.org/TR/css-syntax-3/#consume-an-ident-sequence
while (isIdent(peek())) {
advance();
}
return {CSSTokenType::Ident, consumeRunningValue()};
}
constexpr CSSToken consumeHash()
{
// https://www.w3.org/TR/css-syntax-3/#consume-token (U+0023 NUMBER SIGN)
advance();
consumeRunningValue();
return {CSSTokenType::Hash, consumeIdentSequence().stringValue()};
}
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<unsigned char>(c) > 0x80;
}
static constexpr bool isIdent(char c)
{
{
// https://www.w3.org/TR/css-syntax-3/#ident-code-point
return isIdentStart(c) || isDigit(c) || c == '-';
}
}
static constexpr bool isWhitespace(char c)
{
// https://www.w3.org/TR/css-syntax-3/#whitespace
return c == ' ' || c == '\t' || c == '\r' || c == '\n';
}
std::string_view remainingCharacters_;
size_t position_{0};
};
} // namespace facebook::react

View File

@@ -0,0 +1,461 @@
/*
* 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 <array>
#include <optional>
#include <variant>
#include <react/renderer/css/CSSAngle.h>
#include <react/renderer/css/CSSCompoundDataType.h>
#include <react/renderer/css/CSSDataType.h>
#include <react/renderer/css/CSSLength.h>
#include <react/renderer/css/CSSLengthPercentage.h>
#include <react/renderer/css/CSSList.h>
#include <react/renderer/css/CSSNumber.h>
#include <react/renderer/css/CSSValueParser.h>
#include <react/renderer/css/CSSZero.h>
#include <react/utils/TemplateStringLiteral.h>
#include <react/utils/iequals.h>
namespace facebook::react {
namespace detail {
template <typename DataT, TemplateStringLiteral Name, CSSDataType... AllowedComponentsT>
requires(std::is_same_v<decltype(DataT::value), std::variant<AllowedComponentsT...>>)
struct CSSVariantComponentTransformParser {
static constexpr auto consumeFunctionBlock(const CSSFunctionBlock &func, CSSSyntaxParser &parser)
-> std::optional<DataT>
{
if (!iequals(func.name, Name)) {
return {};
}
auto val = parseNextCSSValue<AllowedComponentsT...>(parser);
return std::visit(
[&](auto &&v) -> std::optional<DataT> {
if constexpr (std::is_same_v<std::remove_cvref_t<decltype(v)>, std::monostate>) {
return {};
} else {
return DataT{.value = std::forward<decltype(v)>(v)};
}
},
val);
}
};
template <typename DataT, TemplateStringLiteral Name>
requires(std::is_same_v<decltype(DataT::value), float>)
struct CSSNumberPercentTransformParser {
static constexpr auto consumeFunctionBlock(const CSSFunctionBlock &func, CSSSyntaxParser &parser)
-> std::optional<DataT>
{
if (!iequals(func.name, Name)) {
return {};
}
auto val = parseNextCSSValue<CSSNumber, CSSPercentage>(parser);
if (std::holds_alternative<std::monostate>(val)) {
return {};
}
return DataT{
.value = std::holds_alternative<CSSNumber>(val) ? std::get<CSSNumber>(val).value
: std::get<CSSPercentage>(val).value / 100.0f};
}
};
template <typename DataT, TemplateStringLiteral Name>
requires(std::is_same_v<decltype(DataT::degrees), float>)
struct CSSAngleTransformParser {
static constexpr auto consumeFunctionBlock(const CSSFunctionBlock &func, CSSSyntaxParser &parser)
-> std::optional<DataT>
{
if (!iequals(func.name, Name)) {
return {};
}
auto value = parseNextCSSValue<CSSAngle, CSSZero>(parser);
if (std::holds_alternative<std::monostate>(value)) {
return {};
}
return DataT{
.degrees = std::holds_alternative<CSSAngle>(value) ? std::get<CSSAngle>(value).degrees : 0.0f,
};
}
};
} // namespace detail
/**
* Representation of matrix() transform function.
*/
struct CSSMatrix {
std::array<float, 6> values{};
constexpr bool operator==(const CSSMatrix &rhs) const = default;
};
template <>
struct CSSDataTypeParser<CSSMatrix> {
static constexpr auto consumeFunctionBlock(const CSSFunctionBlock &func, CSSSyntaxParser &parser)
-> std::optional<CSSMatrix>
{
if (!iequals(func.name, "matrix")) {
return {};
}
CSSMatrix matrix{};
for (int i = 0; i < 6; i++) {
auto value = parseNextCSSValue<CSSNumber>(parser, i == 0 ? CSSDelimiter::None : CSSDelimiter::Comma);
if (std::holds_alternative<std::monostate>(value)) {
return {};
}
matrix.values[i] = std::get<CSSNumber>(value).value;
}
return matrix;
}
};
static_assert(CSSDataType<CSSMatrix>);
/**
* Representation of translate() transform function.
*/
struct CSSTranslate {
std::variant<CSSLength, CSSPercentage> x{};
std::variant<CSSLength, CSSPercentage> y{};
constexpr bool operator==(const CSSTranslate &rhs) const = default;
};
template <>
struct CSSDataTypeParser<CSSTranslate> {
static constexpr auto consumeFunctionBlock(const CSSFunctionBlock &func, CSSSyntaxParser &parser)
-> std::optional<CSSTranslate>
{
if (!iequals(func.name, "translate")) {
return {};
}
auto x = parseNextCSSValue<CSSLengthPercentage>(parser);
if (std::holds_alternative<std::monostate>(x)) {
return {};
}
auto y = parseNextCSSValue<CSSLengthPercentage>(parser, CSSDelimiter::Comma);
CSSTranslate translate{};
translate.x = std::holds_alternative<CSSLength>(x)
? std::variant<CSSLength, CSSPercentage>{std::get<CSSLength>(x)}
: std::variant<CSSLength, CSSPercentage>{std::get<CSSPercentage>(x)};
if (!std::holds_alternative<std::monostate>(y)) {
translate.y = std::holds_alternative<CSSLength>(y)
? std::variant<CSSLength, CSSPercentage>{std::get<CSSLength>(y)}
: std::variant<CSSLength, CSSPercentage>{std::get<CSSPercentage>(y)};
}
return translate;
}
};
static_assert(CSSDataType<CSSTranslate>);
/**
* Representation of translate() transform function.
*/
struct CSSTranslate3D {
std::variant<CSSLength, CSSPercentage> x{};
std::variant<CSSLength, CSSPercentage> y{};
CSSLength z{};
constexpr bool operator==(const CSSTranslate3D &rhs) const = default;
};
template <>
struct CSSDataTypeParser<CSSTranslate3D> {
static constexpr auto consumeFunctionBlock(const CSSFunctionBlock &func, CSSSyntaxParser &parser)
-> std::optional<CSSTranslate3D>
{
if (!iequals(func.name, "translate3d")) {
return {};
}
auto x = parseNextCSSValue<CSSLengthPercentage>(parser);
if (std::holds_alternative<std::monostate>(x)) {
return {};
}
auto y = parseNextCSSValue<CSSLengthPercentage>(parser, CSSDelimiter::Comma);
if (std::holds_alternative<std::monostate>(y)) {
return {};
}
auto z = parseNextCSSValue<CSSLength>(parser, CSSDelimiter::Comma);
if (std::holds_alternative<std::monostate>(z)) {
return {};
}
return CSSTranslate3D{
.x = std::holds_alternative<CSSLength>(x) ? std::variant<CSSLength, CSSPercentage>{std::get<CSSLength>(x)}
: std::variant<CSSLength, CSSPercentage>{std::get<CSSPercentage>(x)},
.y = std::holds_alternative<CSSLength>(y) ? std::variant<CSSLength, CSSPercentage>{std::get<CSSLength>(y)}
: std::variant<CSSLength, CSSPercentage>{std::get<CSSPercentage>(y)},
.z = std::get<CSSLength>(z),
};
}
};
static_assert(CSSDataType<CSSTranslate3D>);
/**
* Representation of translateX() transform function.
*/
struct CSSTranslateX {
std::variant<CSSLength, CSSPercentage> value{};
constexpr bool operator==(const CSSTranslateX &rhs) const = default;
};
template <>
struct CSSDataTypeParser<CSSTranslateX>
: public detail::CSSVariantComponentTransformParser<CSSTranslateX, "translateX", CSSLength, CSSPercentage> {};
static_assert(CSSDataType<CSSTranslateX>);
/**
* Representation of translateY() transform function.
*/
struct CSSTranslateY {
std::variant<CSSLength, CSSPercentage> value{};
constexpr bool operator==(const CSSTranslateY &rhs) const = default;
};
template <>
struct CSSDataTypeParser<CSSTranslateY>
: public detail::CSSVariantComponentTransformParser<CSSTranslateY, "translateY", CSSLength, CSSPercentage> {};
static_assert(CSSDataType<CSSTranslateY>);
/**
* Representation of scale() transform function.
*/
struct CSSScale {
float x{};
float y{};
constexpr bool operator==(const CSSScale &rhs) const = default;
};
template <>
struct CSSDataTypeParser<CSSScale> {
static constexpr auto consumeFunctionBlock(const CSSFunctionBlock &func, CSSSyntaxParser &parser)
-> std::optional<CSSScale>
{
if (!iequals(func.name, "scale")) {
return {};
}
// Transforms module level 2 allows percentage syntax
// https://drafts.csswg.org/css-transforms-2/#transform-functions
auto x = parseNextCSSValue<CSSNumber, CSSPercentage>(parser);
if (std::holds_alternative<std::monostate>(x)) {
return {};
}
auto y = parseNextCSSValue<CSSNumber, CSSPercentage>(parser, CSSDelimiter::Comma);
auto normX =
std::holds_alternative<CSSNumber>(x) ? std::get<CSSNumber>(x).value : std::get<CSSPercentage>(x).value / 100.0f;
auto normY = std::holds_alternative<std::monostate>(y) ? normX
: std::holds_alternative<CSSNumber>(y) ? std::get<CSSNumber>(y).value
: std::get<CSSPercentage>(y).value / 100.0f;
return CSSScale{.x = normX, .y = normY};
}
};
static_assert(CSSDataType<CSSScale>);
/**
* Representation of scaleX() transform function.
*/
struct CSSScaleX {
float value{};
constexpr bool operator==(const CSSScaleX &rhs) const = default;
};
template <>
struct CSSDataTypeParser<CSSScaleX> : public detail::CSSNumberPercentTransformParser<CSSScaleX, "scaleX"> {};
static_assert(CSSDataType<CSSScaleX>);
/**
* Representation of scaleY() transform function.
*/
struct CSSScaleY {
float value{};
constexpr bool operator==(const CSSScaleY &rhs) const = default;
};
template <>
struct CSSDataTypeParser<CSSScaleY> : public detail::CSSNumberPercentTransformParser<CSSScaleY, "scaleY"> {};
static_assert(CSSDataType<CSSScaleY>);
/**
* Representation of rotate() transform function.
*/
struct CSSRotate {
float degrees{};
constexpr bool operator==(const CSSRotate &rhs) const = default;
};
template <>
struct CSSDataTypeParser<CSSRotate> : public detail::CSSAngleTransformParser<CSSRotate, "rotate"> {};
static_assert(CSSDataType<CSSRotate>);
/**
* Representation of rotateX() transform function.
*/
struct CSSRotateX {
float degrees{};
constexpr bool operator==(const CSSRotateX &rhs) const = default;
};
template <>
struct CSSDataTypeParser<CSSRotateX> : public detail::CSSAngleTransformParser<CSSRotateX, "rotateX"> {};
static_assert(CSSDataType<CSSRotateX>);
/**
* Representation of rotateY() transform function.
*/
struct CSSRotateY {
float degrees{};
constexpr bool operator==(const CSSRotateY &rhs) const = default;
};
template <>
struct CSSDataTypeParser<CSSRotateY> : public detail::CSSAngleTransformParser<CSSRotateY, "rotateY"> {};
static_assert(CSSDataType<CSSRotateY>);
/**
* Representation of rotateZ() transform function.
*/
struct CSSRotateZ {
float degrees{};
constexpr bool operator==(const CSSRotateZ &rhs) const = default;
};
template <>
struct CSSDataTypeParser<CSSRotateZ> : public detail::CSSAngleTransformParser<CSSRotateZ, "rotateZ"> {};
static_assert(CSSDataType<CSSRotateZ>);
/**
* Representation of skewX() transform function.
*/
struct CSSSkewX {
float degrees{};
constexpr bool operator==(const CSSSkewX &rhs) const = default;
};
template <>
struct CSSDataTypeParser<CSSSkewX> : public detail::CSSAngleTransformParser<CSSSkewX, "skewX"> {};
static_assert(CSSDataType<CSSSkewX>);
/**
* Representation of skewY() transform function.
*/
struct CSSSkewY {
float degrees{};
constexpr bool operator==(const CSSSkewY &rhs) const = default;
};
template <>
struct CSSDataTypeParser<CSSSkewY> : public detail::CSSAngleTransformParser<CSSSkewY, "skewY"> {};
static_assert(CSSDataType<CSSSkewY>);
/**
* Representation of perspective() transform function.
*/
struct CSSPerspective {
CSSLength length{};
constexpr bool operator==(const CSSPerspective &rhs) const = default;
};
template <>
struct CSSDataTypeParser<CSSPerspective> {
static constexpr auto consumeFunctionBlock(const CSSFunctionBlock &func, CSSSyntaxParser &parser)
-> std::optional<CSSPerspective>
{
if (!iequals(func.name, "perspective")) {
return {};
}
auto value = parseNextCSSValue<CSSLength>(parser);
if (std::holds_alternative<std::monostate>(value) || std::get<CSSLength>(value).value < 0) {
return {};
}
return CSSPerspective{
.length = std::get<CSSLength>(value),
};
}
};
static_assert(CSSDataType<CSSPerspective>);
/**
* Represents one of the <transform-function> types supported by react-native.
* https://drafts.csswg.org/css-transforms-2/#transform-functions
*/
using CSSTransformFunction = CSSCompoundDataType<
CSSMatrix,
CSSTranslate,
CSSTranslateX,
CSSTranslateY,
CSSTranslate3D,
CSSScale,
CSSScaleX,
CSSScaleY,
CSSRotate,
CSSRotateX,
CSSRotateY,
CSSRotateZ,
CSSSkewX,
CSSSkewY,
CSSPerspective>;
/**
* Represents the <transform-list> type.
* https://drafts.csswg.org/css-transforms-1/#typedef-transform-list
*/
using CSSTransformList = CSSWhitespaceSeparatedList<CSSTransformFunction>;
} // namespace facebook::react

View File

@@ -0,0 +1,205 @@
/*
* 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 <optional>
#include <type_traits>
#include <variant>
#include <react/renderer/css/CSSDataType.h>
#include <react/renderer/css/CSSLength.h>
#include <react/renderer/css/CSSLengthPercentage.h>
#include <react/renderer/css/CSSValueParser.h>
namespace facebook::react {
/**
* One of the positional keywords for the `transform-origin` property.
*/
enum class CSSTransformOriginKeyword : std::underlying_type_t<CSSKeyword> {
Center = static_cast<std::underlying_type_t<CSSKeyword>>(CSSKeyword::Center),
Left = static_cast<std::underlying_type_t<CSSKeyword>>(CSSKeyword::Left),
Right = static_cast<std::underlying_type_t<CSSKeyword>>(CSSKeyword::Right),
Top = static_cast<std::underlying_type_t<CSSKeyword>>(CSSKeyword::Top),
Bottom = static_cast<std::underlying_type_t<CSSKeyword>>(CSSKeyword::Bottom),
};
static_assert(CSSDataType<CSSTransformOriginKeyword>);
/**
* Representation of the value produced by the `transform-origin` property.
* https://drafts.csswg.org/css-transforms/#propdef-transform-origin
*/
struct CSSTransformOrigin {
std::variant<CSSLength, CSSPercentage> x{};
std::variant<CSSLength, CSSPercentage> y{};
CSSLength z{};
constexpr bool operator==(const CSSTransformOrigin &rhs) const = default;
};
template <>
struct CSSDataTypeParser<CSSTransformOrigin> {
static constexpr auto consume(CSSSyntaxParser &parser) -> std::optional<CSSTransformOrigin>
{
// [ left | center | right | top | bottom | <length-percentage> ]
// |
// [ left | center | right | <length-percentage> ]
// [ top | center | bottom | <length-percentage> ] <length>?
// |
// [ [ center | left | right ] && [ center | top | bottom ] ] <length>?
auto firstValue = parseNextCSSValue<CSSLengthPercentage, CSSTransformOriginKeyword>(parser);
if (std::holds_alternative<std::monostate>(firstValue)) {
return {};
}
auto secondValue =
parseNextCSSValue<CSSLengthPercentage, CSSTransformOriginKeyword>(parser, CSSDelimiter::Whitespace);
if (std::holds_alternative<std::monostate>(secondValue)) {
return singleValue(firstValue);
}
auto thirdValue = parseNextCSSValue<CSSLength>(parser, CSSDelimiter::Whitespace);
if (std::holds_alternative<CSSLength>(firstValue) || std::holds_alternative<CSSPercentage>(firstValue) ||
std::holds_alternative<CSSLength>(secondValue) || std::holds_alternative<CSSPercentage>(secondValue)) {
return xyLengthPercentageValue(firstValue, secondValue, thirdValue);
}
if (std::holds_alternative<CSSTransformOriginKeyword>(firstValue) &&
std::holds_alternative<CSSTransformOriginKeyword>(secondValue)) {
return xyKeywordValue(
std::get<CSSTransformOriginKeyword>(firstValue),
std::get<CSSTransformOriginKeyword>(secondValue),
thirdValue);
}
return {};
}
private:
static constexpr CSSTransformOrigin singleValue(
const std::variant<std::monostate, CSSLength, CSSPercentage, CSSTransformOriginKeyword> &value)
{
CSSTransformOrigin result{};
if (std::holds_alternative<CSSLength>(value)) {
result.x = std::get<CSSLength>(value);
result.y = keywordPercentage(CSSTransformOriginKeyword::Center);
} else if (std::holds_alternative<CSSPercentage>(value)) {
result.x = std::get<CSSPercentage>(value);
result.y = keywordPercentage(CSSTransformOriginKeyword::Center);
} else if (std::holds_alternative<CSSTransformOriginKeyword>(value)) {
if (isHorizontalKeyword(std::get<CSSTransformOriginKeyword>(value))) {
result.x = keywordPercentage(std::get<CSSTransformOriginKeyword>(value));
result.y = keywordPercentage(CSSTransformOriginKeyword::Center);
} else {
result.x = keywordPercentage(CSSTransformOriginKeyword::Center);
result.y = keywordPercentage(std::get<CSSTransformOriginKeyword>(value));
}
}
return result;
}
static constexpr std::optional<CSSTransformOrigin> xyLengthPercentageValue(
const std::variant<std::monostate, CSSLength, CSSPercentage, CSSTransformOriginKeyword> &val1,
const std::variant<std::monostate, CSSLength, CSSPercentage, CSSTransformOriginKeyword> &val2,
const std::variant<std::monostate, CSSLength> &val3)
{
CSSTransformOrigin result{};
if (std::holds_alternative<CSSLength>(val1)) {
result.x = std::get<CSSLength>(val1);
} else if (std::holds_alternative<CSSPercentage>(val1)) {
result.x = std::get<CSSPercentage>(val1);
} else if (std::holds_alternative<CSSTransformOriginKeyword>(val1)) {
if (!isHorizontalKeyword(std::get<CSSTransformOriginKeyword>(val1))) {
return {};
}
result.x = keywordPercentage(std::get<CSSTransformOriginKeyword>(val1));
}
if (std::holds_alternative<CSSLength>(val2)) {
result.y = std::get<CSSLength>(val2);
} else if (std::holds_alternative<CSSPercentage>(val2)) {
result.y = std::get<CSSPercentage>(val2);
} else if (std::holds_alternative<CSSTransformOriginKeyword>(val2)) {
if (!isVerticalKeyword(std::get<CSSTransformOriginKeyword>(val2))) {
return {};
}
result.y = keywordPercentage(std::get<CSSTransformOriginKeyword>(val2));
}
if (std::holds_alternative<CSSLength>(val3)) {
result.z = std::get<CSSLength>(val3);
}
return result;
}
static constexpr std::optional<CSSTransformOrigin> xyKeywordValue(
CSSTransformOriginKeyword val1,
CSSTransformOriginKeyword val2,
const std::variant<std::monostate, CSSLength> &val3)
{
if (isHorizontalKeyword(val1) && isVerticalKeyword(val2)) {
return CSSTransformOrigin{
.x = keywordPercentage(val1),
.y = keywordPercentage(val2),
.z = std::holds_alternative<CSSLength>(val3) ? std::get<CSSLength>(val3) : CSSLength{}};
}
if (isVerticalKeyword(val1) && isHorizontalKeyword(val2)) {
return CSSTransformOrigin{
.x = keywordPercentage(val2),
.y = keywordPercentage(val1),
.z = std::holds_alternative<CSSLength>(val3) ? std::get<CSSLength>(val3) : CSSLength{}};
}
return {};
}
static constexpr bool isHorizontalKeyword(CSSTransformOriginKeyword keyword)
{
return keyword == CSSTransformOriginKeyword::Left || keyword == CSSTransformOriginKeyword::Center ||
keyword == CSSTransformOriginKeyword::Right;
}
static constexpr bool isVerticalKeyword(CSSTransformOriginKeyword keyword)
{
return keyword == CSSTransformOriginKeyword::Top || keyword == CSSTransformOriginKeyword::Center ||
keyword == CSSTransformOriginKeyword::Bottom;
}
static constexpr CSSPercentage keywordPercentage(CSSTransformOriginKeyword keyword)
{
switch (keyword) {
case CSSTransformOriginKeyword::Left:
return CSSPercentage{0.0f};
case CSSTransformOriginKeyword::Top:
return CSSPercentage{0.0f};
case CSSTransformOriginKeyword::Center:
return CSSPercentage{50.0f};
case CSSTransformOriginKeyword::Right:
return CSSPercentage{100.0f};
case CSSTransformOriginKeyword::Bottom:
return CSSPercentage{100.0f};
}
return {};
}
};
static_assert(CSSDataType<CSSTransformOrigin>);
} // namespace facebook::react

View File

@@ -0,0 +1,204 @@
/*
* 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 <optional>
#include <type_traits>
#include <variant>
#include <react/renderer/css/CSSCompoundDataType.h>
#include <react/renderer/css/CSSDataType.h>
#include <react/renderer/css/CSSKeyword.h>
#include <react/renderer/css/CSSSyntaxParser.h>
namespace facebook::react {
namespace detail {
class CSSValueParser {
public:
explicit constexpr CSSValueParser(CSSSyntaxParser &parser) : parser_{parser} {}
/*
* Attempts to parse the characters starting at the current component value
* into one of the given data types. Types are attempted in order, which the
* caller must consider (e.g. a number token should be preferred to be a
* <number> before <length>).
*/
template <CSSDataType... AllowedTypesT>
constexpr std::variant<std::monostate, AllowedTypesT...> consumeValue(
CSSDelimiter delimeter,
CSSCompoundDataType<AllowedTypesT...> /*unused*/)
{
using ReturnT = std::variant<std::monostate, AllowedTypesT...>;
auto consumedValue = tryConsumeParser<ReturnT, CSSDataTypeParser<AllowedTypesT>...>(delimeter);
if (!std::holds_alternative<std::monostate>(consumedValue)) {
return consumedValue;
}
return parser_.consumeComponentValue<ReturnT>(
delimeter,
[&](const CSSPreservedToken &token) {
return tryConsumePreservedToken<ReturnT, CSSDataTypeParser<AllowedTypesT>...>(token);
},
[&](const CSSSimpleBlock &block, CSSSyntaxParser &blockParser) {
return tryConsumeSimpleBlock<ReturnT, CSSDataTypeParser<AllowedTypesT>...>(block, blockParser);
},
[&](const CSSFunctionBlock &func, CSSSyntaxParser &blockParser) {
return tryConsumeFunctionBlock<ReturnT, CSSDataTypeParser<AllowedTypesT>...>(func, blockParser);
});
}
constexpr bool isFinished() const
{
return parser_.isFinished();
}
constexpr void consumeWhitespace()
{
parser_.consumeWhitespace();
}
private:
template <typename ReturnT>
constexpr ReturnT tryConsumePreservedToken(const CSSPreservedToken & /*token*/)
{
return {};
}
template <typename ReturnT, CSSValidDataTypeParser ParserT, CSSValidDataTypeParser... RestParserT>
constexpr ReturnT tryConsumePreservedToken(const CSSPreservedToken &token)
{
if constexpr (CSSPreservedTokenSink<ParserT>) {
if (auto ret = ParserT::consumePreservedToken(token)) {
return *ret;
}
}
return tryConsumePreservedToken<ReturnT, RestParserT...>(token);
}
template <typename ReturnT>
constexpr ReturnT tryConsumeSimpleBlock(const CSSSimpleBlock & /*token*/, CSSSyntaxParser & /*blockParser*/)
{
return {};
}
template <typename ReturnT, CSSValidDataTypeParser ParserT, CSSValidDataTypeParser... RestParserT>
constexpr ReturnT tryConsumeSimpleBlock(const CSSSimpleBlock &block, CSSSyntaxParser &blockParser)
{
if constexpr (CSSSimpleBlockSink<ParserT>) {
auto currentParser = blockParser;
if (auto ret = ParserT::consumeSimpleBlock(block, blockParser)) {
return *ret;
}
blockParser = currentParser;
}
return tryConsumeSimpleBlock<ReturnT, RestParserT...>(block, blockParser);
}
template <typename ReturnT>
constexpr ReturnT tryConsumeFunctionBlock(const CSSFunctionBlock & /*func*/, CSSSyntaxParser & /*blockParser*/)
{
return {};
}
template <typename ReturnT, CSSValidDataTypeParser ParserT, CSSValidDataTypeParser... RestParserT>
constexpr ReturnT tryConsumeFunctionBlock(const CSSFunctionBlock &func, CSSSyntaxParser &blockParser)
{
if constexpr (CSSFunctionBlockSink<ParserT>) {
auto currentParser = blockParser;
if (auto ret = ParserT::consumeFunctionBlock(func, blockParser)) {
return *ret;
}
blockParser = currentParser;
}
return tryConsumeFunctionBlock<ReturnT, RestParserT...>(func, blockParser);
}
template <typename ReturnT>
constexpr ReturnT tryConsumeParser(CSSDelimiter /*delimeter*/)
{
return {};
}
template <typename ReturnT, CSSValidDataTypeParser ParserT, CSSValidDataTypeParser... RestParserT>
constexpr ReturnT tryConsumeParser(CSSDelimiter delimeter)
{
if constexpr (CSSParserSink<ParserT>) {
auto originalParser = parser_;
if (parser_.consumeDelimiter(delimeter)) {
if (auto ret = ParserT::consume(parser_)) {
return *ret;
}
}
parser_ = originalParser;
}
return tryConsumeParser<ReturnT, RestParserT...>(delimeter);
}
CSSSyntaxParser &parser_;
};
} // namespace detail
/**
* Parse a single CSS property value. Returns a variant holding std::monostate
* on syntax error.
*/
template <CSSMaybeCompoundDataType... AllowedTypesT>
constexpr auto parseCSSProperty(std::string_view css)
-> CSSVariantWithTypes<CSSMergedDataTypes<CSSWideKeyword, AllowedTypesT...>, std::monostate>
{
CSSSyntaxParser syntaxParser(css);
detail::CSSValueParser parser(syntaxParser);
parser.consumeWhitespace();
auto value = parser.consumeValue(CSSDelimiter::None, CSSMergedDataTypes<CSSWideKeyword, AllowedTypesT...>{});
parser.consumeWhitespace();
if (parser.isFinished()) {
return value;
}
return {};
};
/**
* Attempts to parse the next CSS value of a given set of data types, at the
* current location of the syntax parser, advancing the syntax parser
*/
template <CSSMaybeCompoundDataType... AllowedTypesT>
constexpr auto parseNextCSSValue(CSSSyntaxParser &syntaxParser, CSSDelimiter delimeter = CSSDelimiter::None)
-> CSSVariantWithTypes<CSSMergedDataTypes<AllowedTypesT...>, std::monostate>
{
detail::CSSValueParser valueParser(syntaxParser);
return valueParser.consumeValue(delimeter, CSSMergedDataTypes<AllowedTypesT...>{});
}
/**
* Attempts to parse the next CSS value of a given set of data types, at the
* current location of the syntax parser, without advancing the syntax parser
*/
template <CSSMaybeCompoundDataType... AllowedTypesT>
constexpr auto peekNextCSSValue(CSSSyntaxParser &syntaxParser, CSSDelimiter delimeter = CSSDelimiter::None)
-> CSSVariantWithTypes<CSSMergedDataTypes<AllowedTypesT...>, std::monostate>
{
auto savedParser = syntaxParser;
detail::CSSValueParser valueParser(syntaxParser);
auto ret = valueParser.consumeValue(delimeter, CSSMergedDataTypes<AllowedTypesT...>{});
syntaxParser = savedParser;
return ret;
}
} // namespace facebook::react

View File

@@ -0,0 +1,41 @@
/*
* 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 <optional>
#include <react/renderer/css/CSSDataType.h>
namespace facebook::react {
/**
* The value <zero> represents a literal number with the value 0. Expressions
* that merely evaluate to a <number> with the value 0 (for example, calc(0)) do
* not match <zero>; only literal <number-token>s do.
*
* https://www.w3.org/TR/css-values-4/#zero-value
*/
struct CSSZero {
constexpr bool operator==(const CSSZero &rhs) const = default;
};
template <>
struct CSSDataTypeParser<CSSZero> {
static constexpr auto consumePreservedToken(const CSSPreservedToken &token) -> std::optional<CSSZero>
{
if (token.type() == CSSTokenType::Number && token.numericValue() == 0) {
return CSSZero{};
}
return {};
}
};
static_assert(CSSDataType<CSSZero>);
} // namespace facebook::react

View File

@@ -0,0 +1,47 @@
# 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.
require "json"
package = JSON.parse(File.read(File.join(__dir__, "..", "..", "..", "..", "package.json")))
version = package['version']
source = { :git => 'https://github.com/facebook/react-native.git' }
if version == '1000.0.0'
# This is an unpublished version, use the latest commit hash of the react-native repo, which were presumably in.
source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1")
else
source[:tag] = "v#{version}"
end
header_search_paths = []
if ENV['USE_FRAMEWORKS']
header_search_paths << "\"$(PODS_TARGET_SRCROOT)/../../..\"" # this is needed to allow the renderer/css access its own files
end
Pod::Spec.new do |s|
s.name = "React-renderercss"
s.version = version
s.summary = "Fabric CSS parser and data types"
s.homepage = "https://reactnative.dev/"
s.license = package["license"]
s.author = "Meta Platforms, Inc. and its affiliates"
s.platforms = min_supported_versions
s.source = source
s.source_files = podspec_sources("**/*.{cpp,h}", "**/*.h")
s.header_dir = "react/renderer/css"
s.exclude_files = "tests"
s.pod_target_xcconfig = {
"CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(),
"HEADER_SEARCH_PATHS" => header_search_paths.join(' '),
"DEFINES_MODULE" => "YES",
}
resolve_use_frameworks(s, header_mappings_dir: "../../..", module_name: "React_renderercss")
add_dependency(s, "React-debug")
add_dependency(s, "React-utils")
end

View File

@@ -0,0 +1,44 @@
/*
* 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 <gtest/gtest.h>
#include <react/renderer/css/CSSAngle.h>
#include <react/renderer/css/CSSValueParser.h>
namespace facebook::react {
TEST(CSSAngle, angle_values) {
auto emptyValue = parseCSSProperty<CSSAngle>("");
EXPECT_TRUE(std::holds_alternative<std::monostate>(emptyValue));
auto degreeValue = parseCSSProperty<CSSAngle>("10deg");
EXPECT_TRUE(std::holds_alternative<CSSAngle>(degreeValue));
EXPECT_EQ(std::get<CSSAngle>(degreeValue).degrees, 10.0f);
auto spongebobCaseValue = parseCSSProperty<CSSAngle>("20dEg");
EXPECT_TRUE(std::holds_alternative<CSSAngle>(spongebobCaseValue));
EXPECT_EQ(std::get<CSSAngle>(spongebobCaseValue).degrees, 20.0f);
auto radianValue = parseCSSProperty<CSSAngle>("10rad");
EXPECT_TRUE(std::holds_alternative<CSSAngle>(radianValue));
ASSERT_NEAR(std::get<CSSAngle>(radianValue).degrees, 572.958f, 0.001f);
auto negativeRadianValue = parseCSSProperty<CSSAngle>("-10rad");
EXPECT_TRUE(std::holds_alternative<CSSAngle>(negativeRadianValue));
ASSERT_NEAR(
std::get<CSSAngle>(negativeRadianValue).degrees, -572.958f, 0.001f);
auto gradianValue = parseCSSProperty<CSSAngle>("10grad");
EXPECT_TRUE(std::holds_alternative<CSSAngle>(gradianValue));
ASSERT_NEAR(std::get<CSSAngle>(gradianValue).degrees, 9.0f, 0.001f);
auto turnValue = parseCSSProperty<CSSAngle>(".25turn");
EXPECT_TRUE(std::holds_alternative<CSSAngle>(turnValue));
EXPECT_EQ(std::get<CSSAngle>(turnValue).degrees, 90.0f);
}
} // namespace facebook::react

View File

@@ -0,0 +1,560 @@
/*
* 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 <gtest/gtest.h>
#include <react/renderer/css/CSSBackgroundImage.h>
namespace facebook::react {
namespace {
CSSColorStop
makeCSSColorStop(uint8_t r, uint8_t g, uint8_t b, uint8_t a = 255) {
return CSSColorStop{.color = CSSColor{.r = r, .g = g, .b = b, .a = a}};
}
} // namespace
class CSSBackgroundImageTest : public ::testing::Test {};
TEST_F(CSSBackgroundImageTest, LinearGradientToRight) {
auto result = parseCSSProperty<CSSBackgroundImage>(
"linear-gradient(to right, red, blue)");
decltype(result) expected = CSSLinearGradientFunction{
.direction =
CSSLinearGradientDirection{.value = CSSAngle{.degrees = 90.0f}},
.items = {makeCSSColorStop(255, 0, 0), makeCSSColorStop(0, 0, 255)}};
ASSERT_EQ(result, expected);
}
TEST_F(CSSBackgroundImageTest, LinearGradientToBottomRight) {
auto result = parseCSSProperty<CSSBackgroundImage>(
"linear-gradient(to bottom right, red, blue)");
decltype(result) expected = CSSLinearGradientFunction{
.direction =
CSSLinearGradientDirection{
.value = CSSLinearGradientDirectionKeyword::ToBottomRight},
.items = {makeCSSColorStop(255, 0, 0), makeCSSColorStop(0, 0, 255)}};
ASSERT_EQ(result, expected);
}
TEST_F(CSSBackgroundImageTest, EmptyStringReturnsEmptyArray) {
auto result = parseCSSProperty<CSSBackgroundImageList>("");
ASSERT_TRUE(std::holds_alternative<std::monostate>(result));
}
TEST_F(CSSBackgroundImageTest, InvalidValueReturnsEmptyArray) {
auto result = parseCSSProperty<CSSBackgroundImageList>("linear-");
ASSERT_TRUE(std::holds_alternative<std::monostate>(result));
}
TEST_F(CSSBackgroundImageTest, LinearGradientWithWhitespacesInDirection) {
auto result = parseCSSProperty<CSSBackgroundImage>(
"linear-gradient(to bottom right, red, blue)");
decltype(result) expected = CSSLinearGradientFunction{
.direction =
CSSLinearGradientDirection{
.value = CSSLinearGradientDirectionKeyword::ToBottomRight},
.items = {makeCSSColorStop(255, 0, 0), makeCSSColorStop(0, 0, 255)}};
ASSERT_EQ(result, expected);
}
TEST_F(CSSBackgroundImageTest, LinearGradientWithRandomWhitespaces) {
auto result = parseCSSProperty<CSSBackgroundImage>(
" linear-gradient(to bottom right, red 30%, blue 80%) ");
decltype(result) expected = CSSLinearGradientFunction{
.direction =
CSSLinearGradientDirection{
.value = CSSLinearGradientDirectionKeyword::ToBottomRight},
.items = {
CSSColorStop{
.color = CSSColor{.r = 255, .g = 0, .b = 0, .a = 255},
.startPosition = CSSPercentage{.value = 30.0f}},
CSSColorStop{
.color = CSSColor{.r = 0, .g = 0, .b = 255, .a = 255},
.startPosition = CSSPercentage{.value = 80.0f}},
}};
ASSERT_EQ(result, expected);
}
TEST_F(CSSBackgroundImageTest, LinearGradientWithAngle) {
auto result =
parseCSSProperty<CSSBackgroundImage>("linear-gradient(45deg, red, blue)");
decltype(result) expected = CSSLinearGradientFunction{
.direction =
CSSLinearGradientDirection{.value = CSSAngle{.degrees = 45.0f}},
.items = {makeCSSColorStop(255, 0, 0), makeCSSColorStop(0, 0, 255)}};
ASSERT_EQ(result, expected);
}
TEST_F(CSSBackgroundImageTest, LinearGradientCaseInsensitive) {
auto result = parseCSSProperty<CSSBackgroundImage>(
"LiNeAr-GradieNt(To Bottom, Red, Blue)");
decltype(result) expected = CSSLinearGradientFunction{
.direction =
CSSLinearGradientDirection{.value = CSSAngle{.degrees = 180.0f}},
.items = {makeCSSColorStop(255, 0, 0), makeCSSColorStop(0, 0, 255)}};
ASSERT_EQ(result, expected);
}
TEST_F(CSSBackgroundImageTest, MultipleLinearGradientsWithNewlines) {
auto result = parseCSSProperty<CSSBackgroundImageList>(
"\n linear-gradient(to top, red, blue),\n linear-gradient(to bottom, green, yellow)");
decltype(result) expected = CSSBackgroundImageList{
{CSSLinearGradientFunction{
.direction =
CSSLinearGradientDirection{.value = CSSAngle{.degrees = 0.0f}},
.items =
{CSSColorStop{
.color = CSSColor{.r = 255, .g = 0, .b = 0, .a = 255}},
CSSColorStop{
.color = CSSColor{.r = 0, .g = 0, .b = 255, .a = 255}}}},
CSSLinearGradientFunction{
.direction =
CSSLinearGradientDirection{.value = CSSAngle{.degrees = 180.0f}},
.items = {
CSSColorStop{
.color = CSSColor{.r = 0, .g = 128, .b = 0, .a = 255}},
CSSColorStop{
.color = CSSColor{.r = 255, .g = 255, .b = 0, .a = 255}},
}}}};
ASSERT_EQ(result, expected);
}
TEST_F(CSSBackgroundImageTest, LinearGradientWithMultipleColorStops) {
auto result = parseCSSProperty<CSSBackgroundImage>(
"linear-gradient(to bottom, red 0%, green 50%, blue 100%)");
decltype(result) expected = CSSLinearGradientFunction{
.direction =
CSSLinearGradientDirection{.value = CSSAngle{.degrees = 180.0f}},
.items = {
CSSColorStop{
.color = CSSColor{.r = 255, .g = 0, .b = 0, .a = 255},
.startPosition = CSSPercentage{.value = 0.0f}},
CSSColorStop{
.color = CSSColor{.r = 0, .g = 128, .b = 0, .a = 255},
.startPosition = CSSPercentage{.value = 50.0f}},
CSSColorStop{
.color = CSSColor{.r = 0, .g = 0, .b = 255, .a = 255},
.startPosition = CSSPercentage{.value = 100.0f}},
}};
ASSERT_EQ(result, expected);
}
TEST_F(CSSBackgroundImageTest, LinearGradientWithColorStopEndPosition) {
auto result = parseCSSProperty<CSSBackgroundImage>(
"linear-gradient(red 10% 30%, blue 50%)");
decltype(result) expected = CSSLinearGradientFunction{
.direction =
CSSLinearGradientDirection{.value = CSSAngle{.degrees = 180.0f}},
.items = {
CSSColorStop{
.color = CSSColor{.r = 255, .g = 0, .b = 0, .a = 255},
.startPosition = CSSPercentage{.value = 10.0f},
.endPosition = CSSPercentage{.value = 30.0f}},
CSSColorStop{
.color = CSSColor{.r = 0, .g = 0, .b = 255, .a = 255},
.startPosition = CSSPercentage{.value = 50.0f}}}};
ASSERT_EQ(result, expected);
}
TEST_F(CSSBackgroundImageTest, LinearGradientMixedPositionedStops) {
auto result = parseCSSProperty<CSSBackgroundImage>(
"linear-gradient(to right, red, green, blue 60%, yellow, purple)");
decltype(result) expected = CSSLinearGradientFunction{
.direction =
CSSLinearGradientDirection{.value = CSSAngle{.degrees = 90.0f}},
.items = {
makeCSSColorStop(255, 0, 0),
makeCSSColorStop(0, 128, 0),
CSSColorStop{
.color = CSSColor{.r = 0, .g = 0, .b = 255, .a = 255},
.startPosition = CSSPercentage{.value = 60.0f}},
makeCSSColorStop(255, 255, 0),
makeCSSColorStop(128, 0, 128),
}};
ASSERT_EQ(result, expected);
}
TEST_F(CSSBackgroundImageTest, LinearGradientWithHslColors) {
auto result = parseCSSProperty<CSSBackgroundImage>(
"linear-gradient(hsl(330, 100%, 45.1%), hsl(0, 100%, 50%))");
decltype(result) expected = CSSLinearGradientFunction{
.direction =
CSSLinearGradientDirection{.value = CSSAngle{.degrees = 180.0f}},
.items = {makeCSSColorStop(230, 0, 115), makeCSSColorStop(255, 0, 0)}};
ASSERT_EQ(result, expected);
}
TEST_F(CSSBackgroundImageTest, LinearGradientWithoutDirection) {
auto result =
parseCSSProperty<CSSBackgroundImage>("linear-gradient(#e66465, #9198e5)");
decltype(result) expected = CSSLinearGradientFunction{
.direction =
CSSLinearGradientDirection{.value = CSSAngle{.degrees = 180.0f}},
.items = {
makeCSSColorStop(230, 100, 101), makeCSSColorStop(145, 152, 229)}};
ASSERT_EQ(result, expected);
}
TEST_F(CSSBackgroundImageTest, LinearGradientInvalidCases) {
const std::vector<std::string> invalidInputs = {
"linear-gradient(45deg, rede, blue)",
"linear-gradient(45 deg, red, blue)",
"linear-gradient(to left2, red, blue)",
"linear-gradient(to left, red 5, blue)"};
for (const auto& input : invalidInputs) {
const auto result = parseCSSProperty<CSSBackgroundImage>(input);
ASSERT_TRUE(std::holds_alternative<std::monostate>(result))
<< "Input should be invalid: " << input;
}
}
TEST_F(CSSBackgroundImageTest, LinearGradientWithMultipleTransitionHints) {
auto result = parseCSSProperty<CSSBackgroundImage>(
"linear-gradient(red, 20%, blue, 60%, green, 80%, yellow)");
decltype(result) expected = CSSLinearGradientFunction{
.direction =
CSSLinearGradientDirection{.value = CSSAngle{.degrees = 180.0f}},
.items = {
makeCSSColorStop(255, 0, 0),
CSSColorHint{.position = CSSPercentage{.value = 20.0f}},
makeCSSColorStop(0, 0, 255),
CSSColorHint{.position = CSSPercentage{.value = 60.0f}},
makeCSSColorStop(0, 128, 0),
CSSColorHint{.position = CSSPercentage{.value = 80.0f}},
makeCSSColorStop(255, 255, 0),
}};
ASSERT_EQ(result, expected);
}
TEST_F(CSSBackgroundImageTest, LinearGradientInvalidTransitionHints) {
const std::vector<std::string> invalidInputs = {
// color hints must be between two color stops
"linear-gradient(red, 30%, blue, 60%, green, 80%)",
"linear-gradient(red, 30%, 60%, green)",
"linear-gradient(20%, red, green)"};
for (const auto& input : invalidInputs) {
const auto result = parseCSSProperty<CSSBackgroundImageList>(input);
ASSERT_TRUE(std::holds_alternative<std::monostate>(result))
<< "Input should be invalid: " << input;
}
}
TEST_F(CSSBackgroundImageTest, LinearGradientWithMixedUnits) {
auto result = parseCSSProperty<CSSBackgroundImage>(
"linear-gradient(red 10%, 20px, blue 30%, purple 40px)");
decltype(result) expected = CSSLinearGradientFunction{
.direction =
CSSLinearGradientDirection{.value = CSSAngle{.degrees = 180.0f}},
.items = {
CSSColorStop{
.color = CSSColor{.r = 255, .g = 0, .b = 0, .a = 255},
.startPosition = CSSPercentage{.value = 10.0f}},
CSSColorHint{
.position = CSSLength{.value = 20.0f, .unit = CSSLengthUnit::Px}},
CSSColorStop{
.color = CSSColor{.r = 0, .g = 0, .b = 255, .a = 255},
.startPosition = CSSPercentage{.value = 30.0f}},
CSSColorStop{
.color = CSSColor{.r = 128, .g = 0, .b = 128, .a = 255},
.startPosition =
CSSLength{.value = 40.0f, .unit = CSSLengthUnit::Px}},
}};
ASSERT_EQ(result, expected);
}
TEST_F(CSSBackgroundImageTest, RadialGradientBasic) {
auto result =
parseCSSProperty<CSSBackgroundImage>("radial-gradient(red, blue)");
decltype(result) expected = CSSRadialGradientFunction{
.shape = CSSRadialGradientShape::Ellipse,
.size = CSSRadialGradientSizeKeyword::FarthestCorner,
.position =
CSSRadialGradientPosition{
.top = CSSPercentage{.value = 50.0f},
.left = CSSPercentage{.value = 50.0f}},
.items = {makeCSSColorStop(255, 0, 0), makeCSSColorStop(0, 0, 255)}};
ASSERT_EQ(result, expected);
}
TEST_F(CSSBackgroundImageTest, RadialGradientInferCircleFromSingleLength) {
auto result =
parseCSSProperty<CSSBackgroundImage>("radial-gradient(100px, red, blue)");
decltype(result) expected = CSSRadialGradientFunction{
.shape = CSSRadialGradientShape::Circle,
.size =
CSSRadialGradientExplicitSize{
.sizeX = CSSLength{.value = 100.0f, .unit = CSSLengthUnit::Px},
.sizeY = CSSLength{.value = 100.0f, .unit = CSSLengthUnit::Px}},
.position =
CSSRadialGradientPosition{
.top = CSSPercentage{.value = 50.0f},
.left = CSSPercentage{.value = 50.0f}},
.items = {makeCSSColorStop(255, 0, 0), makeCSSColorStop(0, 0, 255)}};
ASSERT_EQ(result, expected);
}
TEST_F(CSSBackgroundImageTest, RadialGradientInferEllipseFromDoubleLength) {
auto result = parseCSSProperty<CSSBackgroundImage>(
"radial-gradient(100px 50px, red, blue)");
decltype(result) expected = CSSRadialGradientFunction{
.shape = CSSRadialGradientShape::Ellipse,
.size =
CSSRadialGradientExplicitSize{
.sizeX = CSSLength{.value = 100.0f, .unit = CSSLengthUnit::Px},
.sizeY = CSSLength{.value = 50.0f, .unit = CSSLengthUnit::Px}},
.position =
CSSRadialGradientPosition{
.top = CSSPercentage{.value = 50.0f},
.left = CSSPercentage{.value = 50.0f}},
.items = {makeCSSColorStop(255, 0, 0), makeCSSColorStop(0, 0, 255)}};
ASSERT_EQ(result, expected);
}
TEST_F(CSSBackgroundImageTest, RadialGradientExplicitShapeWithSize) {
auto result = parseCSSProperty<CSSBackgroundImage>(
"radial-gradient(circle 100px at center, red, blue 80%)");
decltype(result) expected = CSSRadialGradientFunction{
.shape = CSSRadialGradientShape::Circle,
.size =
CSSRadialGradientExplicitSize{
.sizeX = CSSLength{.value = 100.0f, .unit = CSSLengthUnit::Px},
.sizeY = CSSLength{.value = 100.0f, .unit = CSSLengthUnit::Px}},
.position =
CSSRadialGradientPosition{
.top = CSSPercentage{.value = 50.0f},
.left = CSSPercentage{.value = 50.0f}},
.items = {
makeCSSColorStop(255, 0, 0),
CSSColorStop{
.color = CSSColor{.r = 0, .g = 0, .b = 255, .a = 255},
.startPosition = CSSPercentage{.value = 80.0f}}}};
ASSERT_EQ(result, expected);
}
// 1. position syntax: [ left | center | right | top | bottom |
// <length-percentage> ]
TEST_F(CSSBackgroundImageTest, RadialGradientPositionLengthSyntax) {
auto result = parseCSSProperty<CSSBackgroundImage>(
"radial-gradient(circle at 20px, red, blue)");
decltype(result) expected = CSSRadialGradientFunction{
.shape = CSSRadialGradientShape::Circle,
.size = CSSRadialGradientSizeKeyword::FarthestCorner,
.position =
CSSRadialGradientPosition{
.top = CSSPercentage{.value = 50.0f},
.left = CSSLength{.value = 20.0f, .unit = CSSLengthUnit::Px}},
.items = {makeCSSColorStop(255, 0, 0), makeCSSColorStop(0, 0, 255)}};
ASSERT_EQ(result, expected);
}
// 2. position syntax: [ left | center | right ] && [ top | center | bottom ]
TEST_F(CSSBackgroundImageTest, RadialGradientPositionKeywordCombinations) {
const std::vector<std::string> inputs = {
"radial-gradient(circle at left top, red, blue)",
"radial-gradient(circle at top left, red, blue)"};
decltype(parseCSSProperty<CSSBackgroundImage>(inputs[0])) expected =
CSSRadialGradientFunction{
.shape = CSSRadialGradientShape::Circle,
.size = CSSRadialGradientSizeKeyword::FarthestCorner,
.position =
CSSRadialGradientPosition{
.top = CSSPercentage{.value = 0.0f},
.left = CSSPercentage{.value = 0.0f}},
.items = {makeCSSColorStop(255, 0, 0), makeCSSColorStop(0, 0, 255)}};
for (const auto& input : inputs) {
auto result = parseCSSProperty<CSSBackgroundImage>(input);
ASSERT_EQ(result, expected) << "Failed for input: " << input;
}
}
// 3. position syntax: [ left | center | right | <length-percentage> ] [ top
// | center | bottom | <length-percentage> ]
TEST_F(CSSBackgroundImageTest, RadialGradientComplexPositionSyntax) {
const std::vector<std::pair<std::string, CSSRadialGradientPosition>>
testCases = {
{
"radial-gradient(circle at left 20px, red, blue)",
{.top = CSSLength{.value = 20.0f, .unit = CSSLengthUnit::Px},
.left = CSSPercentage{.value = 0.f}},
},
{
"radial-gradient(circle at 20px 20px, red, blue)",
{.top = CSSLength{.value = 20.0f, .unit = CSSLengthUnit::Px},
.left = CSSLength{.value = 20.0f, .unit = CSSLengthUnit::Px}},
},
{
"radial-gradient(circle at right 50px, red, blue)",
{.top = CSSLength{.value = 50.0f, .unit = CSSLengthUnit::Px},
.right = CSSPercentage{.value = 0.f}},
}};
for (const auto& [input, expectedPosition] : testCases) {
const auto result = parseCSSProperty<CSSBackgroundImage>(input);
decltype(result) expected = CSSRadialGradientFunction{
.shape = CSSRadialGradientShape::Circle,
.size = CSSRadialGradientSizeKeyword::FarthestCorner,
.position = expectedPosition,
.items = {makeCSSColorStop(255, 0, 0), makeCSSColorStop(0, 0, 255)}};
ASSERT_EQ(result, expected);
}
}
// 4. position syntax: [ [ left | right ] <length-percentage> ] && [ [ top |
// bottom ] <length-percentage> ]
TEST_F(CSSBackgroundImageTest, RadialGradientSeparatePositionPercentages) {
auto result = parseCSSProperty<CSSBackgroundImage>(
"radial-gradient(at top 0% right 10%, red, blue)");
decltype(result) expected = CSSRadialGradientFunction{
.shape = CSSRadialGradientShape::Ellipse,
.size = CSSRadialGradientSizeKeyword::FarthestCorner,
.position =
CSSRadialGradientPosition{
.top = CSSPercentage{.value = 0.0f},
.right = CSSPercentage{.value = 10.0f}},
.items = {makeCSSColorStop(255, 0, 0), makeCSSColorStop(0, 0, 255)}};
ASSERT_EQ(result, expected);
}
TEST_F(CSSBackgroundImageTest, RadialGradientWithTransitionHints) {
auto result = parseCSSProperty<CSSBackgroundImage>(
"radial-gradient(circle, red 0%, 25%, blue 50%, 75%, green 100%)");
decltype(result) expected = CSSRadialGradientFunction{
.shape = CSSRadialGradientShape::Circle,
.size = CSSRadialGradientSizeKeyword::FarthestCorner,
.position =
CSSRadialGradientPosition{
.top = CSSPercentage{.value = 50.0f},
.left = CSSPercentage{.value = 50.0f}},
.items = {
CSSColorStop{
.color = CSSColor{.r = 255, .g = 0, .b = 0, .a = 255},
.startPosition = CSSPercentage{.value = 0.0f}},
CSSColorHint{.position = CSSPercentage{.value = 25.0f}},
CSSColorStop{
.color = CSSColor{.r = 0, .g = 0, .b = 255, .a = 255},
.startPosition = CSSPercentage{.value = 50.0f}},
CSSColorHint{.position = CSSPercentage{.value = 75.0f}},
CSSColorStop{
.color = CSSColor{.r = 0, .g = 128, .b = 0, .a = 255},
.startPosition = CSSPercentage{.value = 100.0f}},
}};
ASSERT_EQ(result, expected);
}
TEST_F(CSSBackgroundImageTest, MultipleGradientsRadialAndLinear) {
auto result = parseCSSProperty<CSSBackgroundImageList>(
"radial-gradient(circle at top left, red, blue), linear-gradient(to bottom, green, yellow)");
decltype(result) expected = CSSBackgroundImageList{
{CSSRadialGradientFunction{
.shape = CSSRadialGradientShape::Circle,
.size = CSSRadialGradientSizeKeyword::FarthestCorner,
.position =
CSSRadialGradientPosition{
.top = CSSPercentage{.value = 0.0f},
.left = CSSPercentage{.value = 0.0f}},
.items = {makeCSSColorStop(255, 0, 0), makeCSSColorStop(0, 0, 255)}},
CSSLinearGradientFunction{
.direction =
CSSLinearGradientDirection{.value = CSSAngle{.degrees = 180.0f}},
.items = {
makeCSSColorStop(0, 128, 0), makeCSSColorStop(255, 255, 0)}}}};
ASSERT_EQ(result, expected);
}
TEST_F(CSSBackgroundImageTest, RadialGradientMixedCase) {
auto result = parseCSSProperty<CSSBackgroundImage>(
"RaDiAl-GrAdIeNt(CiRcLe ClOsEsT-sIdE aT cEnTeR, rEd, bLuE)");
decltype(result) expected = CSSRadialGradientFunction{
.shape = CSSRadialGradientShape::Circle,
.size = CSSRadialGradientSizeKeyword::ClosestSide,
.position =
CSSRadialGradientPosition{
.top = CSSPercentage{.value = 50.0f},
.left = CSSPercentage{.value = 50.0f}},
.items = {makeCSSColorStop(255, 0, 0), makeCSSColorStop(0, 0, 255)}};
ASSERT_EQ(result, expected);
}
TEST_F(CSSBackgroundImageTest, RadialGradientWhitespaceVariations) {
auto result = parseCSSProperty<CSSBackgroundImage>(
"radial-gradient( circle farthest-corner at 25% 75% , red 0% , blue 100% )");
decltype(result) expected = CSSRadialGradientFunction{
.shape = CSSRadialGradientShape::Circle,
.size = CSSRadialGradientSizeKeyword::FarthestCorner,
.position =
CSSRadialGradientPosition{
.top = CSSPercentage{.value = 75.0f},
.left = CSSPercentage{.value = 25.0f}},
.items = {
CSSColorStop{
.color = CSSColor{.r = 255, .g = 0, .b = 0, .a = 255},
.startPosition = CSSPercentage{.value = 0.0f}},
CSSColorStop{
.color = CSSColor{.r = 0, .g = 0, .b = 255, .a = 255},
.startPosition = CSSPercentage{.value = 100.0f}},
}};
ASSERT_EQ(result, expected);
}
TEST_F(CSSBackgroundImageTest, RadialGradientInvalidCases) {
const std::vector<std::string> invalidInputs = {
"radial-gradient(circle at top leftt, red, blue)",
"radial-gradient(circle at, red, blue)",
"radial-gradient(ellipse 100px, red, blue)",
"radial-gradient(ellipse at top 20% top 50%, red, blue)"};
for (const auto& input : invalidInputs) {
const auto result = parseCSSProperty<CSSBackgroundImageList>(input);
ASSERT_TRUE(std::holds_alternative<std::monostate>(result))
<< "Input should be invalid: " << input;
}
}
TEST_F(CSSBackgroundImageTest, RadialGradientMultipleColorStops) {
auto result = parseCSSProperty<CSSBackgroundImage>(
"radial-gradient(red 0%, yellow 30%, green 60%, blue 100%)");
decltype(result) expected = CSSRadialGradientFunction{
.shape = CSSRadialGradientShape::Ellipse,
.size = CSSRadialGradientSizeKeyword::FarthestCorner,
.position =
CSSRadialGradientPosition{
.top = CSSPercentage{.value = 50.0f},
.left = CSSPercentage{.value = 50.0f}},
.items = {
CSSColorStop{
.color = CSSColor{.r = 255, .g = 0, .b = 0, .a = 255},
.startPosition = CSSPercentage{.value = 0.0f}},
CSSColorStop{
.color = CSSColor{.r = 255, .g = 255, .b = 0, .a = 255},
.startPosition = CSSPercentage{.value = 30.0f}},
CSSColorStop{
.color = CSSColor{.r = 0, .g = 128, .b = 0, .a = 255},
.startPosition = CSSPercentage{.value = 60.0f}},
CSSColorStop{
.color = CSSColor{.r = 0, .g = 0, .b = 255, .a = 255},
.startPosition = CSSPercentage{.value = 100.0f}}}};
ASSERT_EQ(result, expected);
}
TEST_F(CSSBackgroundImageTest, InvalidGradientFunctionName) {
const std::string input =
"aoeusntial-gradient(red 0%, yellow 30%, green 60%, blue 100%)";
const auto result = parseCSSProperty<CSSBackgroundImageList>(input);
ASSERT_TRUE(std::holds_alternative<std::monostate>(result));
}
TEST_F(CSSBackgroundImageTest, RadialGradientNegativeRadius) {
const std::vector<std::string> invalidInputs = {
"radial-gradient(circle -100px, red, blue)",
"radial-gradient(ellipse 100px -40px, red, blue)"};
for (const auto& input : invalidInputs) {
const auto result = parseCSSProperty<CSSBackgroundImageList>(input);
ASSERT_TRUE(std::holds_alternative<std::monostate>(result))
<< "Input should be invalid: " << input;
}
}
} // namespace facebook::react

View File

@@ -0,0 +1,495 @@
/*
* 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 <gtest/gtest.h>
#include <react/renderer/css/CSSColor.h>
#include <react/renderer/css/CSSValueParser.h>
namespace facebook::react {
TEST(CSSColor, hex_color_values) {
auto emptyValue = parseCSSProperty<CSSColor>("");
EXPECT_TRUE(std::holds_alternative<std::monostate>(emptyValue));
auto hex3DigitColorValue = parseCSSProperty<CSSColor>("#fff");
EXPECT_TRUE(std::holds_alternative<CSSColor>(hex3DigitColorValue));
EXPECT_EQ(std::get<CSSColor>(hex3DigitColorValue).r, 255);
EXPECT_EQ(std::get<CSSColor>(hex3DigitColorValue).g, 255);
EXPECT_EQ(std::get<CSSColor>(hex3DigitColorValue).b, 255);
EXPECT_EQ(std::get<CSSColor>(hex3DigitColorValue).a, 255);
auto hex4DigitColorValue = parseCSSProperty<CSSColor>("#ffff");
EXPECT_TRUE(std::holds_alternative<CSSColor>(hex4DigitColorValue));
EXPECT_EQ(std::get<CSSColor>(hex4DigitColorValue).r, 255);
EXPECT_EQ(std::get<CSSColor>(hex4DigitColorValue).g, 255);
EXPECT_EQ(std::get<CSSColor>(hex4DigitColorValue).b, 255);
EXPECT_EQ(std::get<CSSColor>(hex4DigitColorValue).a, 255);
auto hex6DigitColorValue = parseCSSProperty<CSSColor>("#ffffff");
EXPECT_TRUE(std::holds_alternative<CSSColor>(hex6DigitColorValue));
EXPECT_EQ(std::get<CSSColor>(hex6DigitColorValue).r, 255);
EXPECT_EQ(std::get<CSSColor>(hex6DigitColorValue).g, 255);
EXPECT_EQ(std::get<CSSColor>(hex6DigitColorValue).b, 255);
EXPECT_EQ(std::get<CSSColor>(hex6DigitColorValue).a, 255);
auto hex8DigitColorValue = parseCSSProperty<CSSColor>("#ffffffff");
EXPECT_TRUE(std::holds_alternative<CSSColor>(hex8DigitColorValue));
EXPECT_EQ(std::get<CSSColor>(hex8DigitColorValue).r, 255);
EXPECT_EQ(std::get<CSSColor>(hex8DigitColorValue).g, 255);
EXPECT_EQ(std::get<CSSColor>(hex8DigitColorValue).b, 255);
EXPECT_EQ(std::get<CSSColor>(hex8DigitColorValue).a, 255);
auto hexMixedCaseColorValue = parseCSSProperty<CSSColor>("#FFCc99");
EXPECT_TRUE(std::holds_alternative<CSSColor>(hexMixedCaseColorValue));
EXPECT_EQ(std::get<CSSColor>(hexMixedCaseColorValue).r, 255);
EXPECT_EQ(std::get<CSSColor>(hexMixedCaseColorValue).g, 204);
EXPECT_EQ(std::get<CSSColor>(hexMixedCaseColorValue).b, 153);
EXPECT_EQ(std::get<CSSColor>(hexMixedCaseColorValue).a, 255);
auto hexDigitOnlyColorValue = parseCSSProperty<CSSColor>("#369");
EXPECT_TRUE(std::holds_alternative<CSSColor>(hexDigitOnlyColorValue));
EXPECT_EQ(std::get<CSSColor>(hexDigitOnlyColorValue).r, 51);
EXPECT_EQ(std::get<CSSColor>(hexDigitOnlyColorValue).g, 102);
EXPECT_EQ(std::get<CSSColor>(hexDigitOnlyColorValue).b, 153);
EXPECT_EQ(std::get<CSSColor>(hexDigitOnlyColorValue).a, 255);
auto hexAlphaTestValue = parseCSSProperty<CSSColor>("#FFFFFFCC");
EXPECT_TRUE(std::holds_alternative<CSSColor>(hexAlphaTestValue));
EXPECT_EQ(std::get<CSSColor>(hexAlphaTestValue).r, 255);
EXPECT_EQ(std::get<CSSColor>(hexAlphaTestValue).g, 255);
EXPECT_EQ(std::get<CSSColor>(hexAlphaTestValue).b, 255);
EXPECT_EQ(std::get<CSSColor>(hexAlphaTestValue).a, 204);
}
TEST(CSSColor, named_colors) {
auto invalidNamedColorTestValue = parseCSSProperty<CSSColor>("redd");
EXPECT_TRUE(
std::holds_alternative<std::monostate>(invalidNamedColorTestValue));
auto namedColorTestValue1 = parseCSSProperty<CSSColor>("red");
EXPECT_TRUE(std::holds_alternative<CSSColor>(namedColorTestValue1));
EXPECT_EQ(std::get<CSSColor>(namedColorTestValue1).r, 255);
EXPECT_EQ(std::get<CSSColor>(namedColorTestValue1).g, 0);
EXPECT_EQ(std::get<CSSColor>(namedColorTestValue1).b, 0);
EXPECT_EQ(std::get<CSSColor>(namedColorTestValue1).a, 255);
auto namedColorTestValue2 = parseCSSProperty<CSSColor>("cornsilk");
EXPECT_TRUE(std::holds_alternative<CSSColor>(namedColorTestValue2));
EXPECT_EQ(std::get<CSSColor>(namedColorTestValue2).r, 255);
EXPECT_EQ(std::get<CSSColor>(namedColorTestValue2).g, 248);
EXPECT_EQ(std::get<CSSColor>(namedColorTestValue2).b, 220);
EXPECT_EQ(std::get<CSSColor>(namedColorTestValue2).a, 255);
auto namedColorMixedCaseTestValue = parseCSSProperty<CSSColor>("sPrINgGrEEn");
EXPECT_TRUE(std::holds_alternative<CSSColor>(namedColorMixedCaseTestValue));
EXPECT_EQ(std::get<CSSColor>(namedColorMixedCaseTestValue).r, 0);
EXPECT_EQ(std::get<CSSColor>(namedColorMixedCaseTestValue).g, 255);
EXPECT_EQ(std::get<CSSColor>(namedColorMixedCaseTestValue).b, 127);
EXPECT_EQ(std::get<CSSColor>(namedColorMixedCaseTestValue).a, 255);
auto transparentColor = parseCSSProperty<CSSColor>("transparent");
EXPECT_TRUE(std::holds_alternative<CSSColor>(transparentColor));
EXPECT_EQ(std::get<CSSColor>(transparentColor).r, 0);
EXPECT_EQ(std::get<CSSColor>(transparentColor).g, 0);
EXPECT_EQ(std::get<CSSColor>(transparentColor).b, 0);
EXPECT_EQ(std::get<CSSColor>(transparentColor).a, 0);
}
TEST(CSSColor, rgb_rgba_values) {
auto simpleValue = parseCSSProperty<CSSColor>("rgb(255, 255, 255)");
EXPECT_TRUE(std::holds_alternative<CSSColor>(simpleValue));
EXPECT_EQ(std::get<CSSColor>(simpleValue).r, 255);
EXPECT_EQ(std::get<CSSColor>(simpleValue).g, 255);
EXPECT_EQ(std::get<CSSColor>(simpleValue).b, 255);
EXPECT_EQ(std::get<CSSColor>(simpleValue).a, 255);
auto capsValue = parseCSSProperty<CSSColor>("RGB(255, 255, 255)");
EXPECT_TRUE(std::holds_alternative<CSSColor>(capsValue));
EXPECT_EQ(std::get<CSSColor>(capsValue).r, 255);
EXPECT_EQ(std::get<CSSColor>(capsValue).g, 255);
EXPECT_EQ(std::get<CSSColor>(capsValue).b, 255);
EXPECT_EQ(std::get<CSSColor>(capsValue).a, 255);
auto modernSyntaxValue = parseCSSProperty<CSSColor>("rgb(255 255 255)");
EXPECT_TRUE(std::holds_alternative<CSSColor>(modernSyntaxValue));
EXPECT_EQ(std::get<CSSColor>(modernSyntaxValue).r, 255);
EXPECT_EQ(std::get<CSSColor>(modernSyntaxValue).g, 255);
EXPECT_EQ(std::get<CSSColor>(modernSyntaxValue).b, 255);
EXPECT_EQ(std::get<CSSColor>(modernSyntaxValue).a, 255);
auto mixedDelimeterValue = parseCSSProperty<CSSColor>("rgb(255,255 255)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(mixedDelimeterValue));
auto mixedSpacingValue = parseCSSProperty<CSSColor>("rgb( 5 4 3)");
EXPECT_TRUE(std::holds_alternative<CSSColor>(mixedSpacingValue));
EXPECT_EQ(std::get<CSSColor>(mixedSpacingValue).r, 5);
EXPECT_EQ(std::get<CSSColor>(mixedSpacingValue).g, 4);
EXPECT_EQ(std::get<CSSColor>(mixedSpacingValue).b, 3);
EXPECT_EQ(std::get<CSSColor>(mixedSpacingValue).a, 255);
auto clampedValue = parseCSSProperty<CSSColor>("rgb(-50, 500, 0)");
EXPECT_TRUE(std::holds_alternative<CSSColor>(clampedValue));
EXPECT_EQ(std::get<CSSColor>(clampedValue).r, 0);
EXPECT_EQ(std::get<CSSColor>(clampedValue).g, 255);
EXPECT_EQ(std::get<CSSColor>(clampedValue).b, 0);
EXPECT_EQ(std::get<CSSColor>(clampedValue).a, 255);
auto fractionalValue = parseCSSProperty<CSSColor>("rgb(0.5, 0.5, 0.5)");
EXPECT_TRUE(std::holds_alternative<CSSColor>(fractionalValue));
EXPECT_EQ(std::get<CSSColor>(fractionalValue).r, 1);
EXPECT_EQ(std::get<CSSColor>(fractionalValue).g, 1);
EXPECT_EQ(std::get<CSSColor>(fractionalValue).b, 1);
EXPECT_EQ(std::get<CSSColor>(fractionalValue).a, 255);
auto percentageValue = parseCSSProperty<CSSColor>("rgb(50%, 50%, 50%)");
EXPECT_TRUE(std::holds_alternative<CSSColor>(percentageValue));
EXPECT_EQ(std::get<CSSColor>(percentageValue).r, 128);
EXPECT_EQ(std::get<CSSColor>(percentageValue).g, 128);
EXPECT_EQ(std::get<CSSColor>(percentageValue).b, 128);
auto mixedLegacyNumberPercentageValue =
parseCSSProperty<CSSColor>("rgb(50%, 0.5, 50%)");
EXPECT_TRUE(
std::holds_alternative<std::monostate>(mixedLegacyNumberPercentageValue));
auto mixedModernNumberPercentageValue =
parseCSSProperty<CSSColor>("rgb(50% 0.5 50%)");
EXPECT_TRUE(
std::holds_alternative<CSSColor>(mixedModernNumberPercentageValue));
EXPECT_EQ(std::get<CSSColor>(mixedModernNumberPercentageValue).r, 128);
EXPECT_EQ(std::get<CSSColor>(mixedModernNumberPercentageValue).g, 1);
EXPECT_EQ(std::get<CSSColor>(mixedModernNumberPercentageValue).b, 128);
EXPECT_EQ(std::get<CSSColor>(mixedModernNumberPercentageValue).a, 255);
auto rgbWithNumberAlphaValue =
parseCSSProperty<CSSColor>("rgb(255 255 255 0.5)");
EXPECT_TRUE(std::holds_alternative<CSSColor>(rgbWithNumberAlphaValue));
EXPECT_EQ(std::get<CSSColor>(rgbWithNumberAlphaValue).r, 255);
EXPECT_EQ(std::get<CSSColor>(rgbWithNumberAlphaValue).g, 255);
EXPECT_EQ(std::get<CSSColor>(rgbWithNumberAlphaValue).b, 255);
EXPECT_EQ(std::get<CSSColor>(rgbWithNumberAlphaValue).a, 128);
auto rgbWithPercentageAlphaValue =
parseCSSProperty<CSSColor>("rgb(255 255 255 50%)");
EXPECT_TRUE(std::holds_alternative<CSSColor>(rgbWithPercentageAlphaValue));
EXPECT_EQ(std::get<CSSColor>(rgbWithPercentageAlphaValue).r, 255);
EXPECT_EQ(std::get<CSSColor>(rgbWithPercentageAlphaValue).g, 255);
EXPECT_EQ(std::get<CSSColor>(rgbWithPercentageAlphaValue).b, 255);
EXPECT_EQ(std::get<CSSColor>(rgbWithPercentageAlphaValue).a, 128);
auto rgbWithSolidusAlphaValue =
parseCSSProperty<CSSColor>("rgb(255 255 255 / 0.5)");
EXPECT_TRUE(std::holds_alternative<CSSColor>(rgbWithSolidusAlphaValue));
EXPECT_EQ(std::get<CSSColor>(rgbWithSolidusAlphaValue).r, 255);
EXPECT_EQ(std::get<CSSColor>(rgbWithSolidusAlphaValue).g, 255);
EXPECT_EQ(std::get<CSSColor>(rgbWithSolidusAlphaValue).b, 255);
EXPECT_EQ(std::get<CSSColor>(rgbWithSolidusAlphaValue).a, 128);
auto rgbLegacySyntaxWithSolidusAlphaValue =
parseCSSProperty<CSSColor>("rgb(1, 4, 5 /0.5)");
EXPECT_TRUE(
std::holds_alternative<std::monostate>(
rgbLegacySyntaxWithSolidusAlphaValue));
auto rgbaWithSolidusAlphaValue =
parseCSSProperty<CSSColor>("rgba(255 255 255 / 0.5)");
EXPECT_TRUE(std::holds_alternative<CSSColor>(rgbaWithSolidusAlphaValue));
EXPECT_EQ(std::get<CSSColor>(rgbaWithSolidusAlphaValue).r, 255);
EXPECT_EQ(std::get<CSSColor>(rgbaWithSolidusAlphaValue).g, 255);
EXPECT_EQ(std::get<CSSColor>(rgbaWithSolidusAlphaValue).b, 255);
EXPECT_EQ(std::get<CSSColor>(rgbaWithSolidusAlphaValue).a, 128);
auto rgbaWithPercentageSolidusAlphaValue =
parseCSSProperty<CSSColor>("rgba(255 255 255 / 50%)");
EXPECT_TRUE(
std::holds_alternative<CSSColor>(rgbaWithPercentageSolidusAlphaValue));
EXPECT_EQ(std::get<CSSColor>(rgbaWithPercentageSolidusAlphaValue).r, 255);
EXPECT_EQ(std::get<CSSColor>(rgbaWithPercentageSolidusAlphaValue).g, 255);
EXPECT_EQ(std::get<CSSColor>(rgbaWithPercentageSolidusAlphaValue).b, 255);
EXPECT_EQ(std::get<CSSColor>(rgbaWithPercentageSolidusAlphaValue).a, 128);
auto rgbaWithoutAlphaValue = parseCSSProperty<CSSColor>("rgba(255 255 255)");
EXPECT_TRUE(std::holds_alternative<CSSColor>(rgbaWithoutAlphaValue));
EXPECT_EQ(std::get<CSSColor>(rgbaWithoutAlphaValue).r, 255);
EXPECT_EQ(std::get<CSSColor>(rgbaWithoutAlphaValue).g, 255);
EXPECT_EQ(std::get<CSSColor>(rgbaWithoutAlphaValue).b, 255);
EXPECT_EQ(std::get<CSSColor>(rgbaWithoutAlphaValue).a, 255);
auto surroundingWhitespaceValue =
parseCSSProperty<CSSColor>(" rgb(255, 1, 2) ");
EXPECT_TRUE(std::holds_alternative<CSSColor>(surroundingWhitespaceValue));
EXPECT_EQ(std::get<CSSColor>(surroundingWhitespaceValue).r, 255);
EXPECT_EQ(std::get<CSSColor>(surroundingWhitespaceValue).g, 1);
EXPECT_EQ(std::get<CSSColor>(surroundingWhitespaceValue).b, 2);
EXPECT_EQ(std::get<CSSColor>(surroundingWhitespaceValue).a, 255);
auto valueWithSingleComponent = parseCSSProperty<CSSColor>("rgb(255)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(valueWithSingleComponent));
auto valueWithTooFewComponents = parseCSSProperty<CSSColor>("rgb(255, 255)");
EXPECT_TRUE(
std::holds_alternative<std::monostate>(valueWithTooFewComponents));
auto valueWithTooManyComponents =
parseCSSProperty<CSSColor>("rgb(255, 255, 255, 255, 255)");
EXPECT_TRUE(
std::holds_alternative<std::monostate>(valueWithTooManyComponents));
auto valueStartingWithComma = parseCSSProperty<CSSColor>("rgb(, 1, 2)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(valueStartingWithComma));
auto valueEndingWithComma = parseCSSProperty<CSSColor>("rgb(1, 2, )");
EXPECT_TRUE(std::holds_alternative<std::monostate>(valueEndingWithComma));
}
TEST(CSSColor, hsl_hsla_values) {
auto simpleValue = parseCSSProperty<CSSColor>("hsl(180, 50%, 50%)");
EXPECT_TRUE(std::holds_alternative<CSSColor>(simpleValue));
EXPECT_EQ(std::get<CSSColor>(simpleValue).r, 64);
EXPECT_EQ(std::get<CSSColor>(simpleValue).g, 191);
EXPECT_EQ(std::get<CSSColor>(simpleValue).b, 191);
EXPECT_EQ(std::get<CSSColor>(simpleValue).a, 255);
auto modernSyntaxValue = parseCSSProperty<CSSColor>("hsl(180 50% 50%)");
EXPECT_TRUE(std::holds_alternative<CSSColor>(modernSyntaxValue));
EXPECT_EQ(std::get<CSSColor>(modernSyntaxValue).r, 64);
EXPECT_EQ(std::get<CSSColor>(modernSyntaxValue).g, 191);
EXPECT_EQ(std::get<CSSColor>(modernSyntaxValue).b, 191);
EXPECT_EQ(std::get<CSSColor>(modernSyntaxValue).a, 255);
auto degreesValue = parseCSSProperty<CSSColor>("hsl(180deg, 50%, 50%)");
EXPECT_TRUE(std::holds_alternative<CSSColor>(degreesValue));
EXPECT_EQ(std::get<CSSColor>(degreesValue).r, 64);
EXPECT_EQ(std::get<CSSColor>(degreesValue).g, 191);
EXPECT_EQ(std::get<CSSColor>(degreesValue).b, 191);
EXPECT_EQ(std::get<CSSColor>(degreesValue).a, 255);
auto turnValue = parseCSSProperty<CSSColor>("hsl(0.5turn, 50%, 50%)");
EXPECT_TRUE(std::holds_alternative<CSSColor>(turnValue));
EXPECT_EQ(std::get<CSSColor>(turnValue).r, 64);
EXPECT_EQ(std::get<CSSColor>(turnValue).g, 191);
EXPECT_EQ(std::get<CSSColor>(turnValue).b, 191);
EXPECT_EQ(std::get<CSSColor>(turnValue).a, 255);
auto legacySyntaxAlphaValue =
parseCSSProperty<CSSColor>("hsl(70, 190%, 75%, 0.5)");
EXPECT_TRUE(std::holds_alternative<CSSColor>(legacySyntaxAlphaValue));
EXPECT_EQ(std::get<CSSColor>(legacySyntaxAlphaValue).r, 234);
EXPECT_EQ(std::get<CSSColor>(legacySyntaxAlphaValue).g, 255);
EXPECT_EQ(std::get<CSSColor>(legacySyntaxAlphaValue).b, 128);
EXPECT_EQ(std::get<CSSColor>(legacySyntaxAlphaValue).a, 128);
auto modernSyntaxAlphaValue =
parseCSSProperty<CSSColor>("hsl(70 190% 75% 0.5)");
EXPECT_TRUE(std::holds_alternative<CSSColor>(modernSyntaxAlphaValue));
EXPECT_EQ(std::get<CSSColor>(modernSyntaxAlphaValue).r, 234);
EXPECT_EQ(std::get<CSSColor>(modernSyntaxAlphaValue).g, 255);
EXPECT_EQ(std::get<CSSColor>(modernSyntaxAlphaValue).b, 128);
EXPECT_EQ(std::get<CSSColor>(modernSyntaxAlphaValue).a, 128);
auto modernSyntaxWithSolidusAlphaValue =
parseCSSProperty<CSSColor>("hsl(70 190% 75% 0.5)");
EXPECT_TRUE(
std::holds_alternative<CSSColor>(modernSyntaxWithSolidusAlphaValue));
EXPECT_EQ(std::get<CSSColor>(modernSyntaxWithSolidusAlphaValue).r, 234);
EXPECT_EQ(std::get<CSSColor>(modernSyntaxWithSolidusAlphaValue).g, 255);
EXPECT_EQ(std::get<CSSColor>(modernSyntaxWithSolidusAlphaValue).b, 128);
EXPECT_EQ(std::get<CSSColor>(modernSyntaxWithSolidusAlphaValue).a, 128);
auto percentageAlphaValue =
parseCSSProperty<CSSColor>("hsl(70 190% 75% 50%)");
EXPECT_TRUE(std::holds_alternative<CSSColor>(percentageAlphaValue));
EXPECT_EQ(std::get<CSSColor>(percentageAlphaValue).r, 234);
EXPECT_EQ(std::get<CSSColor>(percentageAlphaValue).g, 255);
EXPECT_EQ(std::get<CSSColor>(percentageAlphaValue).b, 128);
EXPECT_EQ(std::get<CSSColor>(percentageAlphaValue).a, 128);
auto hslaWithSolidusAlphaValue =
parseCSSProperty<CSSColor>("hsla(70 190% 75% / 0.5)");
EXPECT_TRUE(std::holds_alternative<CSSColor>(hslaWithSolidusAlphaValue));
EXPECT_EQ(std::get<CSSColor>(hslaWithSolidusAlphaValue).r, 234);
EXPECT_EQ(std::get<CSSColor>(hslaWithSolidusAlphaValue).g, 255);
EXPECT_EQ(std::get<CSSColor>(hslaWithSolidusAlphaValue).b, 128);
EXPECT_EQ(std::get<CSSColor>(hslaWithSolidusAlphaValue).a, 128);
auto rgbLegacySyntaxWithSolidusAlphaValue =
parseCSSProperty<CSSColor>("hsl(1, 4, 5 / 0.5)");
EXPECT_TRUE(
std::holds_alternative<std::monostate>(
rgbLegacySyntaxWithSolidusAlphaValue));
auto hslaWithoutAlphaValue = parseCSSProperty<CSSColor>("hsla(70 190% 75%)");
EXPECT_TRUE(std::holds_alternative<CSSColor>(hslaWithoutAlphaValue));
EXPECT_EQ(std::get<CSSColor>(hslaWithoutAlphaValue).r, 234);
EXPECT_EQ(std::get<CSSColor>(hslaWithoutAlphaValue).g, 255);
EXPECT_EQ(std::get<CSSColor>(hslaWithoutAlphaValue).b, 128);
EXPECT_EQ(std::get<CSSColor>(hslaWithoutAlphaValue).a, 255);
auto surroundingWhitespaceValue =
parseCSSProperty<CSSColor>(" hsl(180, 50%, 50%) ");
EXPECT_TRUE(std::holds_alternative<CSSColor>(surroundingWhitespaceValue));
EXPECT_EQ(std::get<CSSColor>(surroundingWhitespaceValue).r, 64);
EXPECT_EQ(std::get<CSSColor>(surroundingWhitespaceValue).g, 191);
EXPECT_EQ(std::get<CSSColor>(surroundingWhitespaceValue).b, 191);
EXPECT_EQ(std::get<CSSColor>(surroundingWhitespaceValue).a, 255);
auto modernSyntaxWithNumberComponent =
parseCSSProperty<CSSColor>("hsl(180 50 50%)");
EXPECT_TRUE(
std::holds_alternative<CSSColor>(modernSyntaxWithNumberComponent));
EXPECT_EQ(std::get<CSSColor>(modernSyntaxWithNumberComponent).r, 64);
EXPECT_EQ(std::get<CSSColor>(modernSyntaxWithNumberComponent).g, 191);
EXPECT_EQ(std::get<CSSColor>(modernSyntaxWithNumberComponent).b, 191);
EXPECT_EQ(std::get<CSSColor>(modernSyntaxWithNumberComponent).a, 255);
auto legacySyntaxWithNumberComponent =
parseCSSProperty<CSSColor>("hsl(180, 50, 50%)");
EXPECT_TRUE(
std::holds_alternative<std::monostate>(legacySyntaxWithNumberComponent));
auto clampedComponentValue =
parseCSSProperty<CSSColor>("hsl(360, -100%, 120%)");
EXPECT_TRUE(std::holds_alternative<CSSColor>(clampedComponentValue));
EXPECT_EQ(std::get<CSSColor>(clampedComponentValue).r, 255);
EXPECT_EQ(std::get<CSSColor>(clampedComponentValue).g, 255);
EXPECT_EQ(std::get<CSSColor>(clampedComponentValue).b, 255);
EXPECT_EQ(std::get<CSSColor>(clampedComponentValue).a, 255);
auto manyDegreesValue = parseCSSProperty<CSSColor>("hsl(540deg, 50%, 50%)");
EXPECT_TRUE(std::holds_alternative<CSSColor>(manyDegreesValue));
EXPECT_EQ(std::get<CSSColor>(manyDegreesValue).r, 64);
EXPECT_EQ(std::get<CSSColor>(manyDegreesValue).g, 191);
EXPECT_EQ(std::get<CSSColor>(manyDegreesValue).b, 191);
EXPECT_EQ(std::get<CSSColor>(manyDegreesValue).a, 255);
auto negativeDegreesValue =
parseCSSProperty<CSSColor>("hsl(-180deg, 50%, 50%)");
EXPECT_TRUE(std::holds_alternative<CSSColor>(negativeDegreesValue));
EXPECT_EQ(std::get<CSSColor>(negativeDegreesValue).r, 64);
EXPECT_EQ(std::get<CSSColor>(negativeDegreesValue).g, 191);
EXPECT_EQ(std::get<CSSColor>(negativeDegreesValue).b, 191);
EXPECT_EQ(std::get<CSSColor>(negativeDegreesValue).a, 255);
auto valueWithSingleComponent = parseCSSProperty<CSSColor>("hsl(180deg)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(valueWithSingleComponent));
auto valueWithTooFewComponents =
parseCSSProperty<CSSColor>("hsl(180deg, 50%)");
EXPECT_TRUE(
std::holds_alternative<std::monostate>(valueWithTooFewComponents));
auto valueWithTooManyComponents =
parseCSSProperty<CSSColor>("hsl(70 190% 75% 0.5 0.5)");
EXPECT_TRUE(
std::holds_alternative<std::monostate>(valueWithTooManyComponents));
auto valueStartingWithComma =
parseCSSProperty<CSSColor>("hsl(,540deg, 50%, 50%)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(valueStartingWithComma));
auto valueEndingWithComma =
parseCSSProperty<CSSColor>("hsl(540deg, 50%, 50%,)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(valueEndingWithComma));
}
TEST(CSSColor, hwb_values) {
auto simpleValue = parseCSSProperty<CSSColor>("hwb(208 14% 42%)");
EXPECT_TRUE(std::holds_alternative<CSSColor>(simpleValue));
EXPECT_EQ(std::get<CSSColor>(simpleValue).r, 36);
EXPECT_EQ(std::get<CSSColor>(simpleValue).g, 96);
EXPECT_EQ(std::get<CSSColor>(simpleValue).b, 148);
EXPECT_EQ(std::get<CSSColor>(simpleValue).a, 255);
auto grayValue = parseCSSProperty<CSSColor>("hwb(208 100 100)");
EXPECT_TRUE(std::holds_alternative<CSSColor>(grayValue));
EXPECT_EQ(std::get<CSSColor>(grayValue).r, 128);
EXPECT_EQ(std::get<CSSColor>(grayValue).g, 128);
EXPECT_EQ(std::get<CSSColor>(grayValue).b, 128);
EXPECT_EQ(std::get<CSSColor>(grayValue).a, 255);
auto angleValue = parseCSSProperty<CSSColor>("hwb(36.3028E-1rad 14% 42%)");
EXPECT_TRUE(std::holds_alternative<CSSColor>(angleValue));
EXPECT_EQ(std::get<CSSColor>(angleValue).r, 36);
EXPECT_EQ(std::get<CSSColor>(angleValue).g, 96);
EXPECT_EQ(std::get<CSSColor>(angleValue).b, 148);
EXPECT_EQ(std::get<CSSColor>(angleValue).a, 255);
auto legacySyntaxValue = parseCSSProperty<CSSColor>("hwb(208, 14%, 42%)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(legacySyntaxValue));
auto alphaValue = parseCSSProperty<CSSColor>("hwb(208 14% 42% 0.5)");
EXPECT_TRUE(std::holds_alternative<CSSColor>(alphaValue));
EXPECT_EQ(std::get<CSSColor>(alphaValue).r, 36);
EXPECT_EQ(std::get<CSSColor>(alphaValue).g, 96);
EXPECT_EQ(std::get<CSSColor>(alphaValue).b, 148);
EXPECT_EQ(std::get<CSSColor>(alphaValue).a, 128);
auto alphaPercentageValue =
parseCSSProperty<CSSColor>("hwb(208 14% 42% 50%)");
EXPECT_TRUE(std::holds_alternative<CSSColor>(alphaPercentageValue));
EXPECT_EQ(std::get<CSSColor>(alphaPercentageValue).r, 36);
EXPECT_EQ(std::get<CSSColor>(alphaPercentageValue).g, 96);
EXPECT_EQ(std::get<CSSColor>(alphaPercentageValue).b, 148);
EXPECT_EQ(std::get<CSSColor>(alphaPercentageValue).a, 128);
auto alphaSolidusValue = parseCSSProperty<CSSColor>("hwb(208 14% 42% / 0.5)");
EXPECT_TRUE(std::holds_alternative<CSSColor>(alphaSolidusValue));
EXPECT_EQ(std::get<CSSColor>(alphaSolidusValue).r, 36);
EXPECT_EQ(std::get<CSSColor>(alphaSolidusValue).g, 96);
EXPECT_EQ(std::get<CSSColor>(alphaSolidusValue).b, 148);
EXPECT_EQ(std::get<CSSColor>(alphaSolidusValue).a, 128);
auto mixedWhitespaceValue =
parseCSSProperty<CSSColor>(" hwb( 208 14% 42% /0.5 ) ");
EXPECT_TRUE(std::holds_alternative<CSSColor>(mixedWhitespaceValue));
EXPECT_EQ(std::get<CSSColor>(mixedWhitespaceValue).r, 36);
EXPECT_EQ(std::get<CSSColor>(mixedWhitespaceValue).g, 96);
EXPECT_EQ(std::get<CSSColor>(mixedWhitespaceValue).b, 148);
EXPECT_EQ(std::get<CSSColor>(mixedWhitespaceValue).a, 128);
auto extraDegreesValue = parseCSSProperty<CSSColor>("hwb(568 14% 42% / 0.5)");
EXPECT_TRUE(std::holds_alternative<CSSColor>(extraDegreesValue));
EXPECT_EQ(std::get<CSSColor>(extraDegreesValue).r, 36);
EXPECT_EQ(std::get<CSSColor>(extraDegreesValue).g, 96);
EXPECT_EQ(std::get<CSSColor>(extraDegreesValue).b, 148);
EXPECT_EQ(std::get<CSSColor>(extraDegreesValue).a, 128);
auto negativeDegreesValue =
parseCSSProperty<CSSColor>("hwb(-152 14% 42% / 0.5)");
EXPECT_TRUE(std::holds_alternative<CSSColor>(negativeDegreesValue));
EXPECT_EQ(std::get<CSSColor>(negativeDegreesValue).r, 36);
EXPECT_EQ(std::get<CSSColor>(negativeDegreesValue).g, 96);
EXPECT_EQ(std::get<CSSColor>(negativeDegreesValue).b, 148);
EXPECT_EQ(std::get<CSSColor>(negativeDegreesValue).a, 128);
auto missingComponentsValue = parseCSSProperty<CSSColor>("hwb(208 14%)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(missingComponentsValue));
auto tooManyComponentsValue =
parseCSSProperty<CSSColor>("hwb(208 14% 42% 0.5 0.5)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(tooManyComponentsValue));
auto valueStartingWithComma =
parseCSSProperty<CSSColor>("hwb(,208 14% 42% / 0.5)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(valueStartingWithComma));
auto valueEndingWithComma =
parseCSSProperty<CSSColor>("hwb(208 14% 42% / 0.5,)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(valueEndingWithComma));
}
TEST(CSSColor, constexpr_values) {
[[maybe_unused]] constexpr auto emptyValue = parseCSSProperty<CSSColor>("");
[[maybe_unused]] constexpr auto hexColorValue =
parseCSSProperty<CSSColor>("#fff");
[[maybe_unused]] constexpr auto rgbFunctionValue =
parseCSSProperty<CSSColor>("rgb(255, 255, 255)");
}
} // namespace facebook::react

View File

@@ -0,0 +1,577 @@
/*
* 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 <gtest/gtest.h>
#include <react/renderer/css/CSSFilter.h>
#include <react/renderer/css/CSSValueParser.h>
namespace facebook::react {
TEST(CSSFilter, blur) {
auto value = parseCSSProperty<CSSFilterFunction>("blur(10px)");
EXPECT_TRUE(std::holds_alternative<CSSBlurFilter>(value));
EXPECT_EQ(std::get<CSSBlurFilter>(value).amount.value, 10.0f);
EXPECT_EQ(std::get<CSSBlurFilter>(value).amount.unit, CSSLengthUnit::Px);
}
TEST(CSSFilter, blur_funky) {
auto value = parseCSSProperty<CSSFilterFunction>("bLUr( 10px )");
EXPECT_TRUE(std::holds_alternative<CSSBlurFilter>(value));
EXPECT_EQ(std::get<CSSBlurFilter>(value).amount.value, 10.0f);
EXPECT_EQ(std::get<CSSBlurFilter>(value).amount.unit, CSSLengthUnit::Px);
}
TEST(CSSFilter, blur_default) {
auto value = parseCSSProperty<CSSFilterFunction>("blur()");
EXPECT_TRUE(std::holds_alternative<CSSBlurFilter>(value));
EXPECT_EQ(std::get<CSSBlurFilter>(value).amount.value, 0.0f);
EXPECT_EQ(std::get<CSSBlurFilter>(value).amount.unit, CSSLengthUnit::Px);
}
TEST(CSSFilter, blur_number) {
auto value = parseCSSProperty<CSSFilterFunction>("blur(10)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(value));
}
TEST(CSSFilter, brightness_number) {
auto value = parseCSSProperty<CSSFilterFunction>("brightness(10)");
EXPECT_TRUE(std::holds_alternative<CSSBrightnessFilter>(value));
EXPECT_EQ(std::get<CSSBrightnessFilter>(value).amount, 10.0f);
}
TEST(CSSFilter, brightness_percent) {
auto value = parseCSSProperty<CSSFilterFunction>("brightness(10%)");
EXPECT_TRUE(std::holds_alternative<CSSBrightnessFilter>(value));
EXPECT_EQ(std::get<CSSBrightnessFilter>(value).amount, 0.1f);
}
TEST(CSSFilter, brightness_funky) {
auto value = parseCSSProperty<CSSFilterFunction>("brightneSS( 10% )");
EXPECT_TRUE(std::holds_alternative<CSSBrightnessFilter>(value));
EXPECT_EQ(std::get<CSSBrightnessFilter>(value).amount, 0.1f);
}
TEST(CSSFilter, brightness_default) {
auto value = parseCSSProperty<CSSFilterFunction>("brightness()");
EXPECT_TRUE(std::holds_alternative<CSSBrightnessFilter>(value));
EXPECT_EQ(std::get<CSSBrightnessFilter>(value).amount, 1.0f);
}
TEST(CSSFilter, brightness_negative_number) {
auto value = parseCSSProperty<CSSFilterFunction>("brightness(-10)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(value));
}
TEST(CSSFilter, brightness_negative_percent) {
auto value = parseCSSProperty<CSSFilterFunction>("brightness(-10%)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(value));
}
TEST(CSSFilter, bightness_length) {
auto value = parseCSSProperty<CSSFilterFunction>("brightness(10px)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(value));
}
TEST(CSSFilter, contrast_number) {
auto value = parseCSSProperty<CSSFilterFunction>("contrast(10)");
EXPECT_TRUE(std::holds_alternative<CSSContrastFilter>(value));
EXPECT_EQ(std::get<CSSContrastFilter>(value).amount, 10.0f);
}
TEST(CSSFilter, contrast_percent) {
auto value = parseCSSProperty<CSSFilterFunction>("contrast(10%)");
EXPECT_TRUE(std::holds_alternative<CSSContrastFilter>(value));
EXPECT_EQ(std::get<CSSContrastFilter>(value).amount, 0.1f);
}
TEST(CSSFilter, contrast_funky) {
auto value = parseCSSProperty<CSSFilterFunction>("contrast( 10% )");
EXPECT_TRUE(std::holds_alternative<CSSContrastFilter>(value));
EXPECT_EQ(std::get<CSSContrastFilter>(value).amount, 0.1f);
}
TEST(CSSFilter, contrast_default) {
auto value = parseCSSProperty<CSSFilterFunction>("contrast()");
EXPECT_TRUE(std::holds_alternative<CSSContrastFilter>(value));
EXPECT_EQ(std::get<CSSContrastFilter>(value).amount, 1.0f);
}
TEST(CSSFilter, contrast_negative_number) {
auto value = parseCSSProperty<CSSFilterFunction>("contrast(-10)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(value));
}
TEST(CSSFilter, contrast_negative_percent) {
auto value = parseCSSProperty<CSSFilterFunction>("contrast(-10%)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(value));
}
TEST(CSSFilter, contrast_length) {
auto value = parseCSSProperty<CSSFilterFunction>("contrast(10px)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(value));
}
TEST(CSSFilter, drop_shadow_no_blur) {
auto value = parseCSSProperty<CSSFilterFunction>("drop-shadow(10px 5px)");
EXPECT_TRUE(std::holds_alternative<CSSDropShadowFilter>(value));
EXPECT_EQ(std::get<CSSDropShadowFilter>(value).offsetX.value, 10.0f);
EXPECT_EQ(
std::get<CSSDropShadowFilter>(value).offsetX.unit, CSSLengthUnit::Px);
EXPECT_EQ(std::get<CSSDropShadowFilter>(value).offsetY.value, 5.0f);
EXPECT_EQ(
std::get<CSSDropShadowFilter>(value).offsetY.unit, CSSLengthUnit::Px);
EXPECT_EQ(std::get<CSSDropShadowFilter>(value).standardDeviation.value, 0.0f);
EXPECT_EQ(
std::get<CSSDropShadowFilter>(value).standardDeviation.unit,
CSSLengthUnit::Px);
EXPECT_EQ(std::get<CSSDropShadowFilter>(value).color, CSSColor::black());
}
TEST(CSSFilter, drop_shadow_no_blur_negative_offset) {
auto value = parseCSSProperty<CSSFilterFunction>("drop-shadow(10px -5em)");
EXPECT_TRUE(std::holds_alternative<CSSDropShadowFilter>(value));
EXPECT_EQ(std::get<CSSDropShadowFilter>(value).offsetX.value, 10.0f);
EXPECT_EQ(
std::get<CSSDropShadowFilter>(value).offsetX.unit, CSSLengthUnit::Px);
EXPECT_EQ(std::get<CSSDropShadowFilter>(value).offsetY.value, -5.0f);
EXPECT_EQ(
std::get<CSSDropShadowFilter>(value).offsetY.unit, CSSLengthUnit::Em);
EXPECT_EQ(std::get<CSSDropShadowFilter>(value).standardDeviation.value, 0.0f);
EXPECT_EQ(
std::get<CSSDropShadowFilter>(value).standardDeviation.unit,
CSSLengthUnit::Px);
EXPECT_EQ(std::get<CSSDropShadowFilter>(value).color, CSSColor::black());
}
TEST(CSSFilter, drop_shadow_no_blur_funky) {
auto value = parseCSSProperty<CSSFilterFunction>("drop-Shadow( 10px 5px )");
EXPECT_TRUE(std::holds_alternative<CSSDropShadowFilter>(value));
EXPECT_EQ(std::get<CSSDropShadowFilter>(value).offsetX.value, 10.0f);
EXPECT_EQ(
std::get<CSSDropShadowFilter>(value).offsetX.unit, CSSLengthUnit::Px);
EXPECT_EQ(std::get<CSSDropShadowFilter>(value).offsetY.value, 5.0f);
EXPECT_EQ(
std::get<CSSDropShadowFilter>(value).offsetY.unit, CSSLengthUnit::Px);
EXPECT_EQ(std::get<CSSDropShadowFilter>(value).standardDeviation.value, 0.0f);
EXPECT_EQ(
std::get<CSSDropShadowFilter>(value).standardDeviation.unit,
CSSLengthUnit::Px);
EXPECT_EQ(std::get<CSSDropShadowFilter>(value).color, CSSColor::black());
}
TEST(CSSFilter, drop_shadow_no_blur_pre_color) {
auto value = parseCSSProperty<CSSFilterFunction>("drop-shadow(red 10px 5px)");
EXPECT_TRUE(std::holds_alternative<CSSDropShadowFilter>(value));
EXPECT_EQ(std::get<CSSDropShadowFilter>(value).offsetX.value, 10.0f);
EXPECT_EQ(
std::get<CSSDropShadowFilter>(value).offsetX.unit, CSSLengthUnit::Px);
EXPECT_EQ(std::get<CSSDropShadowFilter>(value).offsetY.value, 5.0f);
EXPECT_EQ(
std::get<CSSDropShadowFilter>(value).offsetY.unit, CSSLengthUnit::Px);
EXPECT_EQ(std::get<CSSDropShadowFilter>(value).standardDeviation.value, 0.0f);
EXPECT_EQ(
std::get<CSSDropShadowFilter>(value).standardDeviation.unit,
CSSLengthUnit::Px);
CSSColor red{.r = 255, .g = 0, .b = 0, .a = 255};
EXPECT_EQ(std::get<CSSDropShadowFilter>(value).color, red);
}
TEST(CSSFilter, drop_shadow_no_blur_post_color) {
auto value =
parseCSSProperty<CSSFilterFunction>("drop-shadow( 10px 5px red )");
EXPECT_TRUE(std::holds_alternative<CSSDropShadowFilter>(value));
EXPECT_EQ(std::get<CSSDropShadowFilter>(value).offsetX.value, 10.0f);
EXPECT_EQ(
std::get<CSSDropShadowFilter>(value).offsetX.unit, CSSLengthUnit::Px);
EXPECT_EQ(std::get<CSSDropShadowFilter>(value).offsetY.value, 5.0f);
EXPECT_EQ(
std::get<CSSDropShadowFilter>(value).offsetY.unit, CSSLengthUnit::Px);
EXPECT_EQ(std::get<CSSDropShadowFilter>(value).standardDeviation.value, 0.0f);
EXPECT_EQ(
std::get<CSSDropShadowFilter>(value).standardDeviation.unit,
CSSLengthUnit::Px);
CSSColor red{.r = 255, .g = 0, .b = 0, .a = 255};
EXPECT_EQ(std::get<CSSDropShadowFilter>(value).color, red);
}
TEST(CSSFilter, drop_shadow_with_blur) {
auto value = parseCSSProperty<CSSFilterFunction>("drop-shadow(10px 5px 3px)");
EXPECT_TRUE(std::holds_alternative<CSSDropShadowFilter>(value));
EXPECT_EQ(std::get<CSSDropShadowFilter>(value).offsetX.value, 10.0f);
EXPECT_EQ(
std::get<CSSDropShadowFilter>(value).offsetX.unit, CSSLengthUnit::Px);
EXPECT_EQ(std::get<CSSDropShadowFilter>(value).offsetY.value, 5.0f);
EXPECT_EQ(
std::get<CSSDropShadowFilter>(value).offsetY.unit, CSSLengthUnit::Px);
EXPECT_EQ(std::get<CSSDropShadowFilter>(value).standardDeviation.value, 3.0f);
EXPECT_EQ(
std::get<CSSDropShadowFilter>(value).standardDeviation.unit,
CSSLengthUnit::Px);
EXPECT_EQ(std::get<CSSDropShadowFilter>(value).color, CSSColor::black());
}
TEST(CSSFilter, drop_shadow_with_blur_pre_color) {
auto value =
parseCSSProperty<CSSFilterFunction>("drop-shadow(red 10px 5px 3px )");
EXPECT_TRUE(std::holds_alternative<CSSDropShadowFilter>(value));
EXPECT_EQ(std::get<CSSDropShadowFilter>(value).offsetX.value, 10.0f);
EXPECT_EQ(
std::get<CSSDropShadowFilter>(value).offsetX.unit, CSSLengthUnit::Px);
EXPECT_EQ(std::get<CSSDropShadowFilter>(value).offsetY.value, 5.0f);
EXPECT_EQ(
std::get<CSSDropShadowFilter>(value).offsetY.unit, CSSLengthUnit::Px);
EXPECT_EQ(std::get<CSSDropShadowFilter>(value).standardDeviation.value, 3.0f);
EXPECT_EQ(
std::get<CSSDropShadowFilter>(value).standardDeviation.unit,
CSSLengthUnit::Px);
CSSColor red{.r = 255, .g = 0, .b = 0, .a = 255};
EXPECT_EQ(std::get<CSSDropShadowFilter>(value).color, red);
}
TEST(CSSFilter, drop_shadow_with_blur_post_color) {
auto value =
parseCSSProperty<CSSFilterFunction>("drop-shadow( 10px 5px 3px red )");
EXPECT_TRUE(std::holds_alternative<CSSDropShadowFilter>(value));
EXPECT_EQ(std::get<CSSDropShadowFilter>(value).offsetX.value, 10.0f);
EXPECT_EQ(
std::get<CSSDropShadowFilter>(value).offsetX.unit, CSSLengthUnit::Px);
EXPECT_EQ(std::get<CSSDropShadowFilter>(value).offsetY.value, 5.0f);
EXPECT_EQ(
std::get<CSSDropShadowFilter>(value).offsetY.unit, CSSLengthUnit::Px);
EXPECT_EQ(std::get<CSSDropShadowFilter>(value).standardDeviation.value, 3.0f);
EXPECT_EQ(
std::get<CSSDropShadowFilter>(value).standardDeviation.unit,
CSSLengthUnit::Px);
CSSColor red{.r = 255, .g = 0, .b = 0, .a = 255};
EXPECT_EQ(std::get<CSSDropShadowFilter>(value).color, red);
}
TEST(CSSFilter, drop_shadow_number_first) {
auto value = parseCSSProperty<CSSFilterFunction>("drop-shadow(10 5px 3px)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(value));
}
TEST(CSSFilter, drop_shadow_with_blur_negative) {
auto value =
parseCSSProperty<CSSFilterFunction>("drop-shadow(10px 5px -3px)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(value));
}
TEST(CSSFilter, drop_shadow_missing_length) {
auto value = parseCSSProperty<CSSFilterFunction>("drop-shadow(10px)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(value));
}
TEST(CSSFilter, drop_shadow_extra_length) {
auto value =
parseCSSProperty<CSSFilterFunction>("drop-shadow(10px 5px 3px 4px)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(value));
}
TEST(CSSFilter, drop_shadow_duplicate_colors) {
auto value =
parseCSSProperty<CSSFilterFunction>("drop-shadow(red 10px 5px red)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(value));
}
TEST(CSSFilter, grayscale_number) {
auto value = parseCSSProperty<CSSFilterFunction>("grayscale(10)");
EXPECT_TRUE(std::holds_alternative<CSSGrayscaleFilter>(value));
EXPECT_EQ(std::get<CSSGrayscaleFilter>(value).amount, 10.0f);
}
TEST(CSSFilter, grayscale_percent) {
auto value = parseCSSProperty<CSSFilterFunction>("grayscale(10%)");
EXPECT_TRUE(std::holds_alternative<CSSGrayscaleFilter>(value));
EXPECT_EQ(std::get<CSSGrayscaleFilter>(value).amount, 0.1f);
}
TEST(CSSFilter, grayscale_funky) {
auto value = parseCSSProperty<CSSFilterFunction>("grayscale( 10% )");
EXPECT_TRUE(std::holds_alternative<CSSGrayscaleFilter>(value));
EXPECT_EQ(std::get<CSSGrayscaleFilter>(value).amount, 0.1f);
}
TEST(CSSFilter, grayscale_default) {
auto value = parseCSSProperty<CSSFilterFunction>("grayscale()");
EXPECT_TRUE(std::holds_alternative<CSSGrayscaleFilter>(value));
EXPECT_EQ(std::get<CSSGrayscaleFilter>(value).amount, 1.0f);
}
TEST(CSSFilter, grayscale_negative_number) {
auto value = parseCSSProperty<CSSFilterFunction>("grayscale(-10)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(value));
}
TEST(CSSFilter, grayscale_negative_percent) {
auto value = parseCSSProperty<CSSFilterFunction>("grayscale(-10%)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(value));
}
TEST(CSSFilter, grayscale_length) {
auto value = parseCSSProperty<CSSFilterFunction>("grayscale(10px)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(value));
}
TEST(CSSFilter, hue_rotate_degrees) {
auto value = parseCSSProperty<CSSFilterFunction>("hue-rotate(10deg)");
EXPECT_TRUE(std::holds_alternative<CSSHueRotateFilter>(value));
EXPECT_EQ(std::get<CSSHueRotateFilter>(value).degrees, 10.0f);
}
TEST(CSSFilter, hue_rotate_turn) {
auto value = parseCSSProperty<CSSFilterFunction>("hue-rotate(0.5turn)");
EXPECT_TRUE(std::holds_alternative<CSSHueRotateFilter>(value));
EXPECT_EQ(std::get<CSSHueRotateFilter>(value).degrees, 180.0f);
}
TEST(CSSFilter, hue_rotate_zero) {
auto value = parseCSSProperty<CSSFilterFunction>("hue-rotate(0)");
EXPECT_TRUE(std::holds_alternative<CSSHueRotateFilter>(value));
EXPECT_EQ(std::get<CSSHueRotateFilter>(value).degrees, 0.0f);
}
TEST(CSSFilter, hue_rotate_negative) {
auto value = parseCSSProperty<CSSFilterFunction>("hue-rotate(-10deg)");
EXPECT_TRUE(std::holds_alternative<CSSHueRotateFilter>(value));
EXPECT_EQ(std::get<CSSHueRotateFilter>(value).degrees, -10.0f);
}
TEST(CSSFilter, hue_rotate_default) {
auto value = parseCSSProperty<CSSFilterFunction>("hue-rotate()");
EXPECT_TRUE(std::holds_alternative<CSSHueRotateFilter>(value));
EXPECT_EQ(std::get<CSSHueRotateFilter>(value).degrees, 0.0f);
}
TEST(CSSFilter, hue_rotate_funky) {
auto value = parseCSSProperty<CSSFilterFunction>("Hue-Rotate( 10deg )");
EXPECT_TRUE(std::holds_alternative<CSSHueRotateFilter>(value));
EXPECT_EQ(std::get<CSSHueRotateFilter>(value).degrees, 10.0f);
}
TEST(CSSFilter, hue_rotate_percent) {
auto value = parseCSSProperty<CSSFilterFunction>("hue-rotate(10%)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(value));
}
TEST(CSSFilter, hue_rotate_number) {
auto value = parseCSSProperty<CSSFilterFunction>("hue-rotate(10)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(value));
}
TEST(CSSFilter, invert_number) {
auto value = parseCSSProperty<CSSFilterFunction>("invert(10)");
EXPECT_TRUE(std::holds_alternative<CSSInvertFilter>(value));
EXPECT_EQ(std::get<CSSInvertFilter>(value).amount, 10.0f);
}
TEST(CSSFilter, invert_percent) {
auto value = parseCSSProperty<CSSFilterFunction>("invert(10%)");
EXPECT_TRUE(std::holds_alternative<CSSInvertFilter>(value));
EXPECT_EQ(std::get<CSSInvertFilter>(value).amount, 0.1f);
}
TEST(CSSFilter, invert_funky) {
auto value = parseCSSProperty<CSSFilterFunction>("inVert( 10% )");
EXPECT_TRUE(std::holds_alternative<CSSInvertFilter>(value));
EXPECT_EQ(std::get<CSSInvertFilter>(value).amount, 0.1f);
}
TEST(CSSFilter, invert_default) {
auto value = parseCSSProperty<CSSFilterFunction>("invert()");
EXPECT_TRUE(std::holds_alternative<CSSInvertFilter>(value));
EXPECT_EQ(std::get<CSSInvertFilter>(value).amount, 1.0f);
}
TEST(CSSFilter, invert_negative_number) {
auto value = parseCSSProperty<CSSFilterFunction>("invert(-10)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(value));
}
TEST(CSSFilter, invert_negative_percent) {
auto value = parseCSSProperty<CSSFilterFunction>("invert(-10%)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(value));
}
TEST(CSSFilter, invert_length) {
auto value = parseCSSProperty<CSSFilterFunction>("invert(10px)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(value));
}
TEST(CSSFilter, opacity_number) {
auto value = parseCSSProperty<CSSFilterFunction>("opacity(10)");
EXPECT_TRUE(std::holds_alternative<CSSOpacityFilter>(value));
EXPECT_EQ(std::get<CSSOpacityFilter>(value).amount, 10.0f);
}
TEST(CSSFilter, opacity_percent) {
auto value = parseCSSProperty<CSSFilterFunction>("opacity(10%)");
EXPECT_TRUE(std::holds_alternative<CSSOpacityFilter>(value));
EXPECT_EQ(std::get<CSSOpacityFilter>(value).amount, 0.1f);
}
TEST(CSSFilter, opacity_funky) {
auto value = parseCSSProperty<CSSFilterFunction>("oPAcity( 10% )");
EXPECT_TRUE(std::holds_alternative<CSSOpacityFilter>(value));
EXPECT_EQ(std::get<CSSOpacityFilter>(value).amount, 0.1f);
}
TEST(CSSFilter, opacity_default) {
auto value = parseCSSProperty<CSSFilterFunction>("opacity()");
EXPECT_TRUE(std::holds_alternative<CSSOpacityFilter>(value));
EXPECT_EQ(std::get<CSSOpacityFilter>(value).amount, 1.0f);
}
TEST(CSSFilter, opacity_negative_number) {
auto value = parseCSSProperty<CSSFilterFunction>("opacity(-10)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(value));
}
TEST(CSSFilter, opacity_negative_percent) {
auto value = parseCSSProperty<CSSFilterFunction>("opacity(-10%)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(value));
}
TEST(CSSFilter, opacity_length) {
auto value = parseCSSProperty<CSSFilterFunction>("opacity(10px)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(value));
}
TEST(CSSFilter, saturate_number) {
auto value = parseCSSProperty<CSSFilterFunction>("saturate(10)");
EXPECT_TRUE(std::holds_alternative<CSSSaturateFilter>(value));
EXPECT_EQ(std::get<CSSSaturateFilter>(value).amount, 10.0f);
}
TEST(CSSFilter, saturate_percent) {
auto value = parseCSSProperty<CSSFilterFunction>("saturate(10%)");
EXPECT_TRUE(std::holds_alternative<CSSSaturateFilter>(value));
EXPECT_EQ(std::get<CSSSaturateFilter>(value).amount, 0.1f);
}
TEST(CSSFilter, saturate_funky) {
auto value = parseCSSProperty<CSSFilterFunction>("saturATE( 10% )");
EXPECT_TRUE(std::holds_alternative<CSSSaturateFilter>(value));
EXPECT_EQ(std::get<CSSSaturateFilter>(value).amount, 0.1f);
}
TEST(CSSFilter, saturate_default) {
auto value = parseCSSProperty<CSSFilterFunction>("saturate()");
EXPECT_TRUE(std::holds_alternative<CSSSaturateFilter>(value));
EXPECT_EQ(std::get<CSSSaturateFilter>(value).amount, 1.0f);
}
TEST(CSSFilter, saturate_negative_number) {
auto value = parseCSSProperty<CSSFilterFunction>("saturate(-10)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(value));
}
TEST(CSSFilter, saturate_negative_percent) {
auto value = parseCSSProperty<CSSFilterFunction>("saturate(-10%)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(value));
}
TEST(CSSFilter, saturate_length) {
auto value = parseCSSProperty<CSSFilterFunction>("saturate(10px)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(value));
}
TEST(CSSFilter, sepia_number) {
auto value = parseCSSProperty<CSSFilterFunction>("sepia(10)");
EXPECT_TRUE(std::holds_alternative<CSSSepiaFilter>(value));
EXPECT_EQ(std::get<CSSSepiaFilter>(value).amount, 10.0f);
}
TEST(CSSFilter, sepia_percent) {
auto value = parseCSSProperty<CSSFilterFunction>("sepia(10%)");
EXPECT_TRUE(std::holds_alternative<CSSSepiaFilter>(value));
EXPECT_EQ(std::get<CSSSepiaFilter>(value).amount, 0.1f);
}
TEST(CSSFilter, sepia_funky) {
auto value = parseCSSProperty<CSSFilterFunction>("sepia( 10% )");
EXPECT_TRUE(std::holds_alternative<CSSSepiaFilter>(value));
EXPECT_EQ(std::get<CSSSepiaFilter>(value).amount, 0.1f);
}
TEST(CSSFilter, sepia_default) {
auto value = parseCSSProperty<CSSFilterFunction>("sepia()");
EXPECT_TRUE(std::holds_alternative<CSSSepiaFilter>(value));
EXPECT_EQ(std::get<CSSSepiaFilter>(value).amount, 1.0f);
}
TEST(CSSFilter, sepia_negative_number) {
auto value = parseCSSProperty<CSSFilterFunction>("sepia(-10)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(value));
}
TEST(CSSFilter, sepia_negative_percent) {
auto value = parseCSSProperty<CSSFilterFunction>("sepia(-10%)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(value));
}
TEST(CSSFilter, sepia_length) {
auto value = parseCSSProperty<CSSFilterFunction>("sepia(10px)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(value));
}
TEST(CSSFilter, filter_list) {
auto value = parseCSSProperty<CSSFilterList>(
"blur(10px) brightness(0.5) drop-shadow(10px 10px 10px red)\t\n drop-shadow(4px -20em)");
EXPECT_TRUE(std::holds_alternative<CSSFilterList>(value));
auto list = std::get<CSSFilterList>(value);
EXPECT_EQ(list.size(), 4);
EXPECT_TRUE(std::holds_alternative<CSSBlurFilter>(list[0]));
EXPECT_EQ(std::get<CSSBlurFilter>(list[0]).amount.value, 10.0f);
EXPECT_EQ(std::get<CSSBlurFilter>(list[0]).amount.unit, CSSLengthUnit::Px);
EXPECT_TRUE(std::holds_alternative<CSSBrightnessFilter>(list[1]));
EXPECT_EQ(std::get<CSSBrightnessFilter>(list[1]).amount, 0.5f);
EXPECT_TRUE(std::holds_alternative<CSSDropShadowFilter>(list[2]));
EXPECT_EQ(std::get<CSSDropShadowFilter>(list[2]).offsetX.value, 10.0f);
EXPECT_EQ(
std::get<CSSDropShadowFilter>(list[2]).offsetX.unit, CSSLengthUnit::Px);
EXPECT_EQ(std::get<CSSDropShadowFilter>(list[2]).offsetY.value, 10.0f);
EXPECT_EQ(
std::get<CSSDropShadowFilter>(list[2]).offsetY.unit, CSSLengthUnit::Px);
EXPECT_EQ(
std::get<CSSDropShadowFilter>(list[2]).standardDeviation.value, 10.0f);
EXPECT_EQ(
std::get<CSSDropShadowFilter>(list[2]).standardDeviation.unit,
CSSLengthUnit::Px);
CSSColor red{.r = 255, .g = 0, .b = 0, .a = 255};
EXPECT_EQ(std::get<CSSDropShadowFilter>(list[2]).color, red);
EXPECT_TRUE(std::holds_alternative<CSSDropShadowFilter>(list[3]));
EXPECT_EQ(std::get<CSSDropShadowFilter>(list[3]).offsetX.value, 4.0f);
EXPECT_EQ(
std::get<CSSDropShadowFilter>(list[3]).offsetX.unit, CSSLengthUnit::Px);
EXPECT_EQ(std::get<CSSDropShadowFilter>(list[3]).offsetY.value, -20.0f);
EXPECT_EQ(
std::get<CSSDropShadowFilter>(list[3]).offsetY.unit, CSSLengthUnit::Em);
EXPECT_EQ(
std::get<CSSDropShadowFilter>(list[3]).standardDeviation.value, 0.0f);
EXPECT_EQ(
std::get<CSSDropShadowFilter>(list[3]).standardDeviation.unit,
CSSLengthUnit::Px);
EXPECT_EQ(std::get<CSSDropShadowFilter>(list[3]).color, CSSColor::black());
}
TEST(CSSFilter, filter_list_commas) {
auto value = parseCSSProperty<CSSFilterList>(
"blur(10px), brightness(0.5), drop-shadow(10px 10px 10px red), drop-shadow(4px -20em)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(value));
}
} // namespace facebook::react

View File

@@ -0,0 +1,76 @@
/*
* 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 <gtest/gtest.h>
#include <react/renderer/css/CSSFontVariant.h>
#include <react/renderer/css/CSSValueParser.h>
namespace facebook::react {
TEST(CSSFontVariant, single_variant) {
auto commonLigatures = parseCSSProperty<CSSFontVariant>("common-ligatures");
EXPECT_TRUE(std::holds_alternative<CSSFontVariant>(commonLigatures));
EXPECT_EQ(
std::get<CSSFontVariant>(commonLigatures),
CSSFontVariant::CommonLigatures);
auto stylistic15 = parseCSSProperty<CSSFontVariant>("stylistic-fifteen");
EXPECT_TRUE(std::holds_alternative<CSSFontVariant>(stylistic15));
EXPECT_EQ(
std::get<CSSFontVariant>(stylistic15), CSSFontVariant::StylisticFifteen);
auto bogus = parseCSSProperty<CSSFontVariant>("inset");
EXPECT_TRUE(std::holds_alternative<std::monostate>(bogus));
}
TEST(CSSFontVariant, multiple_variants) {
auto commonLigatures =
parseCSSProperty<CSSFontVariantList>("common-ligatures");
EXPECT_TRUE(std::holds_alternative<CSSFontVariantList>(commonLigatures));
EXPECT_EQ(std::get<CSSFontVariantList>(commonLigatures).size(), 1);
EXPECT_EQ(
std::get<CSSFontVariantList>(commonLigatures)[0],
CSSFontVariant::CommonLigatures);
auto commonLigaturesAndHistoricalForms =
parseCSSProperty<CSSFontVariantList>("common-ligatures no-contextual");
EXPECT_TRUE(
std::holds_alternative<CSSFontVariantList>(
commonLigaturesAndHistoricalForms));
EXPECT_EQ(
std::get<CSSFontVariantList>(commonLigaturesAndHistoricalForms).size(),
2);
EXPECT_EQ(
std::get<CSSFontVariantList>(commonLigaturesAndHistoricalForms)[0],
CSSFontVariant::CommonLigatures);
EXPECT_EQ(
std::get<CSSFontVariantList>(commonLigaturesAndHistoricalForms)[1],
CSSFontVariant::NoContextual);
auto lotsOfWhitespace = parseCSSProperty<CSSFontVariantList>(
" no-discretionary-ligatures \n proportional-nums\tstylistic-twelve");
EXPECT_TRUE(std::holds_alternative<CSSFontVariantList>(lotsOfWhitespace));
EXPECT_EQ(std::get<CSSFontVariantList>(lotsOfWhitespace).size(), 3);
EXPECT_EQ(
std::get<CSSFontVariantList>(lotsOfWhitespace)[0],
CSSFontVariant::NoDiscretionaryLigatures);
EXPECT_EQ(
std::get<CSSFontVariantList>(lotsOfWhitespace)[1],
CSSFontVariant::ProportionalNums);
EXPECT_EQ(
std::get<CSSFontVariantList>(lotsOfWhitespace)[2],
CSSFontVariant::StylisticTwelve);
auto bogus = parseCSSProperty<CSSFontVariantList>("inset");
EXPECT_TRUE(std::holds_alternative<std::monostate>(bogus));
auto commaSeparated =
parseCSSProperty<CSSFontVariantList>("common-ligatures, stylistic-six");
EXPECT_TRUE(std::holds_alternative<std::monostate>(commaSeparated));
}
} // namespace facebook::react

View File

@@ -0,0 +1,52 @@
/*
* 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 <gtest/gtest.h>
#include <react/renderer/css/CSSKeyword.h>
#include <react/renderer/css/CSSValueParser.h>
namespace facebook::react {
TEST(CSSKeyword, keyword_values) {
auto emptyValue = parseCSSProperty<CSSKeyword>("");
EXPECT_TRUE(std::holds_alternative<std::monostate>(emptyValue));
auto inheritValue = parseCSSProperty<>("inherit");
EXPECT_TRUE(std::holds_alternative<CSSWideKeyword>(inheritValue));
EXPECT_EQ(std::get<CSSWideKeyword>(inheritValue), CSSWideKeyword::Inherit);
auto autoValue = parseCSSProperty<CSSKeyword>("auto");
EXPECT_TRUE(std::holds_alternative<CSSKeyword>(autoValue));
EXPECT_EQ(std::get<CSSKeyword>(autoValue), CSSKeyword::Auto);
auto autoCapsValue = parseCSSProperty<CSSKeyword>("AuTO");
EXPECT_TRUE(std::holds_alternative<CSSKeyword>(autoCapsValue));
EXPECT_EQ(std::get<CSSKeyword>(autoCapsValue), CSSKeyword::Auto);
auto autoDisallowedValue = parseCSSProperty<>("auto");
EXPECT_TRUE(std::holds_alternative<std::monostate>(autoDisallowedValue));
auto whitespaceValue = parseCSSProperty<CSSKeyword>(" flex-start ");
EXPECT_TRUE(std::holds_alternative<CSSKeyword>(whitespaceValue));
EXPECT_EQ(std::get<CSSKeyword>(whitespaceValue), CSSKeyword::FlexStart);
auto badIdentValue = parseCSSProperty<CSSKeyword>("bad");
EXPECT_TRUE(std::holds_alternative<std::monostate>(badIdentValue));
auto pxValue = parseCSSProperty<>("20px");
EXPECT_TRUE(std::holds_alternative<std::monostate>(pxValue));
auto multiValue = parseCSSProperty<>("auto flex-start");
EXPECT_TRUE(std::holds_alternative<std::monostate>(multiValue));
}
TEST(CSSKeyword, parse_constexpr) {
[[maybe_unused]] constexpr auto rowValue =
parseCSSProperty<CSSKeyword>("row");
}
} // namespace facebook::react

View File

@@ -0,0 +1,37 @@
/*
* 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 <gtest/gtest.h>
#include <react/renderer/css/CSSLengthPercentage.h>
#include <react/renderer/css/CSSValueParser.h>
namespace facebook::react {
TEST(CSSLengthPercentage, length_percentage_values) {
auto emptyValue = parseCSSProperty<CSSLengthPercentage>("");
EXPECT_TRUE(std::holds_alternative<std::monostate>(emptyValue));
auto autoValue = parseCSSProperty<CSSKeyword, CSSLengthPercentage>("auto");
EXPECT_TRUE(std::holds_alternative<CSSKeyword>(autoValue));
EXPECT_EQ(std::get<CSSKeyword>(autoValue), CSSKeyword::Auto);
auto autoValueReordered =
parseCSSProperty<CSSLengthPercentage, CSSKeyword>("auto");
EXPECT_TRUE(std::holds_alternative<CSSKeyword>(autoValueReordered));
EXPECT_EQ(std::get<CSSKeyword>(autoValueReordered), CSSKeyword::Auto);
auto pxValue = parseCSSProperty<CSSLengthPercentage>("20px");
EXPECT_TRUE(std::holds_alternative<CSSLength>(pxValue));
EXPECT_EQ(std::get<CSSLength>(pxValue).value, 20.0f);
EXPECT_EQ(std::get<CSSLength>(pxValue).unit, CSSLengthUnit::Px);
auto pctValue = parseCSSProperty<CSSLengthPercentage>("-40%");
EXPECT_TRUE(std::holds_alternative<CSSPercentage>(pctValue));
EXPECT_EQ(std::get<CSSPercentage>(pctValue).value, -40.0f);
}
} // namespace facebook::react

View File

@@ -0,0 +1,58 @@
/*
* 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 <gtest/gtest.h>
#include <react/renderer/css/CSSLength.h>
#include <react/renderer/css/CSSValueParser.h>
namespace facebook::react {
TEST(CSSLength, length_values) {
auto emptyValue = parseCSSProperty<CSSLength>("");
EXPECT_TRUE(std::holds_alternative<std::monostate>(emptyValue));
auto autoValue = parseCSSProperty<CSSKeyword, CSSLength>("auto");
EXPECT_TRUE(std::holds_alternative<CSSKeyword>(autoValue));
EXPECT_EQ(std::get<CSSKeyword>(autoValue), CSSKeyword::Auto);
auto pxValue = parseCSSProperty<CSSLength>("20px");
EXPECT_TRUE(std::holds_alternative<CSSLength>(pxValue));
EXPECT_EQ(std::get<CSSLength>(pxValue).value, 20.0f);
EXPECT_EQ(std::get<CSSLength>(pxValue).unit, CSSLengthUnit::Px);
auto capsValue = parseCSSProperty<CSSLength>("50PX");
EXPECT_TRUE(std::holds_alternative<CSSLength>(capsValue));
EXPECT_EQ(std::get<CSSLength>(capsValue).value, 50.0f);
EXPECT_EQ(std::get<CSSLength>(capsValue).unit, CSSLengthUnit::Px);
auto cmValue = parseCSSProperty<CSSLength>("453cm");
EXPECT_TRUE(std::holds_alternative<CSSLength>(cmValue));
EXPECT_TRUE(std::get<CSSLength>(cmValue).value == 453.0f);
EXPECT_EQ(std::get<CSSLength>(cmValue).unit, CSSLengthUnit::Cm);
auto unitlessZeroValue = parseCSSProperty<CSSLength>("0");
EXPECT_TRUE(std::holds_alternative<CSSLength>(unitlessZeroValue));
EXPECT_EQ(std::get<CSSLength>(unitlessZeroValue).value, 0.0f);
EXPECT_EQ(std::get<CSSLength>(unitlessZeroValue).unit, CSSLengthUnit::Px);
auto unitlessNonzeroValue = parseCSSProperty<CSSLength>("123");
EXPECT_TRUE(std::holds_alternative<std::monostate>(unitlessNonzeroValue));
auto pctValue = parseCSSProperty<CSSLength>("-40%");
EXPECT_TRUE(std::holds_alternative<std::monostate>(pctValue));
auto negativeValue = parseCSSProperty<CSSLength>("-20em");
EXPECT_TRUE(std::holds_alternative<CSSLength>(negativeValue));
EXPECT_EQ(std::get<CSSLength>(negativeValue).value, -20.0f);
EXPECT_EQ(std::get<CSSLength>(negativeValue).unit, CSSLengthUnit::Em);
}
TEST(CSSLength, parse_constexpr) {
[[maybe_unused]] constexpr auto pxValue = parseCSSProperty<CSSLength>("2px");
}
} // namespace facebook::react

View File

@@ -0,0 +1,157 @@
/*
* 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 <gtest/gtest.h>
#include <react/renderer/css/CSSCompoundDataType.h>
#include <react/renderer/css/CSSLength.h>
#include <react/renderer/css/CSSList.h>
#include <react/renderer/css/CSSNumber.h>
#include <react/renderer/css/CSSValueParser.h>
namespace facebook::react {
TEST(CSSList, empty_values) {
auto emptyValue = parseCSSProperty<CSSCommaSeparatedList<CSSNumber>>("");
EXPECT_TRUE(std::holds_alternative<std::monostate>(emptyValue));
auto whitespaceValue =
parseCSSProperty<CSSCommaSeparatedList<CSSNumber>>(" ");
EXPECT_TRUE(std::holds_alternative<std::monostate>(whitespaceValue));
auto commaValue = parseCSSProperty<CSSCommaSeparatedList<CSSNumber>>(",");
}
TEST(CSSList, single_value) {
auto simpleValue = parseCSSProperty<CSSCommaSeparatedList<CSSNumber>>("20");
EXPECT_TRUE(
std::holds_alternative<CSSCommaSeparatedList<CSSNumber>>(simpleValue));
EXPECT_EQ(std::get<CSSCommaSeparatedList<CSSNumber>>(simpleValue).size(), 1);
EXPECT_EQ(
std::get<CSSCommaSeparatedList<CSSNumber>>(simpleValue)[0].value, 20);
auto whitespaceValue =
parseCSSProperty<CSSCommaSeparatedList<CSSNumber>>(" 20 ");
EXPECT_TRUE(
std::holds_alternative<CSSCommaSeparatedList<CSSNumber>>(
whitespaceValue));
EXPECT_EQ(
std::get<CSSCommaSeparatedList<CSSNumber>>(whitespaceValue).size(), 1);
EXPECT_EQ(
std::get<CSSCommaSeparatedList<CSSNumber>>(whitespaceValue)[0].value, 20);
}
TEST(CSSList, wrong_type) {
auto simpleValue = parseCSSProperty<CSSCommaSeparatedList<CSSNumber>>("20px");
EXPECT_TRUE(std::holds_alternative<std::monostate>(simpleValue));
}
TEST(CSSList, multiple_comma_values) {
auto simpleValue =
parseCSSProperty<CSSCommaSeparatedList<CSSNumber>>("20, 30, 40");
EXPECT_TRUE(
std::holds_alternative<CSSCommaSeparatedList<CSSNumber>>(simpleValue));
EXPECT_EQ(std::get<CSSCommaSeparatedList<CSSNumber>>(simpleValue).size(), 3);
EXPECT_EQ(
std::get<CSSCommaSeparatedList<CSSNumber>>(simpleValue)[0].value, 20);
EXPECT_EQ(
std::get<CSSCommaSeparatedList<CSSNumber>>(simpleValue)[1].value, 30);
EXPECT_EQ(
std::get<CSSCommaSeparatedList<CSSNumber>>(simpleValue)[2].value, 40);
auto whitespaceValue =
parseCSSProperty<CSSCommaSeparatedList<CSSNumber>>(" 20 , 30 , 40 ");
EXPECT_TRUE(
std::holds_alternative<CSSCommaSeparatedList<CSSNumber>>(
whitespaceValue));
EXPECT_EQ(
std::get<CSSCommaSeparatedList<CSSNumber>>(whitespaceValue).size(), 3);
EXPECT_EQ(
std::get<CSSCommaSeparatedList<CSSNumber>>(whitespaceValue)[0].value, 20);
EXPECT_EQ(
std::get<CSSCommaSeparatedList<CSSNumber>>(whitespaceValue)[1].value, 30);
EXPECT_EQ(
std::get<CSSCommaSeparatedList<CSSNumber>>(whitespaceValue)[2].value, 40);
}
TEST(CSSList, multiple_space_values) {
auto simpleValue =
parseCSSProperty<CSSWhitespaceSeparatedList<CSSNumber>>("20 30 40");
EXPECT_TRUE(
std::holds_alternative<CSSWhitespaceSeparatedList<CSSNumber>>(
simpleValue));
EXPECT_EQ(
std::get<CSSWhitespaceSeparatedList<CSSNumber>>(simpleValue).size(), 3);
EXPECT_EQ(
std::get<CSSWhitespaceSeparatedList<CSSNumber>>(simpleValue)[0].value,
20);
EXPECT_EQ(
std::get<CSSWhitespaceSeparatedList<CSSNumber>>(simpleValue)[1].value,
30);
EXPECT_EQ(
std::get<CSSWhitespaceSeparatedList<CSSNumber>>(simpleValue)[2].value,
40);
auto whitespaceValue =
parseCSSProperty<CSSWhitespaceSeparatedList<CSSNumber>>(" 20 \n 30 40 ");
EXPECT_TRUE(
std::holds_alternative<CSSWhitespaceSeparatedList<CSSNumber>>(
whitespaceValue));
EXPECT_EQ(
std::get<CSSWhitespaceSeparatedList<CSSNumber>>(whitespaceValue).size(),
3);
EXPECT_EQ(
std::get<CSSWhitespaceSeparatedList<CSSNumber>>(whitespaceValue)[0].value,
20);
EXPECT_EQ(
std::get<CSSWhitespaceSeparatedList<CSSNumber>>(whitespaceValue)[1].value,
30);
EXPECT_EQ(
std::get<CSSWhitespaceSeparatedList<CSSNumber>>(whitespaceValue)[2].value,
40);
}
TEST(CSSList, extra_comma_tokens) {
auto extraTokensValue =
parseCSSProperty<CSSCommaSeparatedList<CSSNumber>>("20, 30, 40 50");
EXPECT_TRUE(std::holds_alternative<std::monostate>(extraTokensValue));
}
TEST(CSSList, extra_space_tokens) {
auto extraTokensValue =
parseCSSProperty<CSSWhitespaceSeparatedList<CSSNumber>>("20 30 40 ,50");
EXPECT_TRUE(std::holds_alternative<std::monostate>(extraTokensValue));
}
TEST(CSSList, extra_commas) {
auto prefixCommaValue =
parseCSSProperty<CSSCommaSeparatedList<CSSNumber>>(",20");
EXPECT_TRUE(std::holds_alternative<std::monostate>(prefixCommaValue));
auto suffixCommaValue =
parseCSSProperty<CSSCommaSeparatedList<CSSNumber>>("20,");
EXPECT_TRUE(std::holds_alternative<std::monostate>(suffixCommaValue));
}
TEST(CSSList, compound_data_type) {
using NumberLengthList =
CSSCommaSeparatedList<CSSCompoundDataType<CSSNumber, CSSLength>>;
auto compoundType = parseCSSProperty<NumberLengthList>("10px,20");
EXPECT_TRUE(std::holds_alternative<NumberLengthList>(compoundType));
auto& list = std::get<NumberLengthList>(compoundType);
EXPECT_EQ(list.size(), 2);
EXPECT_TRUE(std::holds_alternative<CSSLength>(list[0]));
EXPECT_EQ(std::get<CSSLength>(list[0]).value, 10);
EXPECT_EQ(std::get<CSSLength>(list[0]).unit, CSSLengthUnit::Px);
EXPECT_TRUE(std::holds_alternative<CSSNumber>(list[1]));
EXPECT_EQ(std::get<CSSNumber>(list[1]).value, 20);
}
} // namespace facebook::react

View File

@@ -0,0 +1,21 @@
/*
* 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 <gtest/gtest.h>
#include <react/renderer/css/CSSLength.h>
#include <react/renderer/css/CSSNumber.h>
#include <react/renderer/css/CSSValueParser.h>
namespace facebook::react {
TEST(CSSNumberLength, number_length_values) {
auto unitlessZeroValue = parseCSSProperty<CSSNumber, CSSLength>("0");
EXPECT_TRUE(std::holds_alternative<CSSNumber>(unitlessZeroValue));
EXPECT_EQ(std::get<CSSNumber>(unitlessZeroValue).value, 0.0f);
}
} // namespace facebook::react

View File

@@ -0,0 +1,30 @@
/*
* 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 <gtest/gtest.h>
#include <react/renderer/css/CSSNumber.h>
#include <react/renderer/css/CSSValueParser.h>
namespace facebook::react {
TEST(CSSNumber, number_values) {
auto emptyValue = parseCSSProperty<CSSNumber>("");
EXPECT_TRUE(std::holds_alternative<std::monostate>(emptyValue));
auto pxValue = parseCSSProperty<CSSNumber>("20px");
EXPECT_TRUE(std::holds_alternative<std::monostate>(pxValue));
auto numberValue = parseCSSProperty<CSSNumber>("123.456");
EXPECT_TRUE(std::holds_alternative<CSSNumber>(numberValue));
EXPECT_EQ(std::get<CSSNumber>(numberValue).value, 123.456f);
auto exponentValue = parseCSSProperty<CSSNumber>("-1.5E3");
EXPECT_TRUE(std::holds_alternative<CSSNumber>(exponentValue));
EXPECT_EQ(std::get<CSSNumber>(exponentValue).value, -1.5E3f);
}
} // namespace facebook::react

View File

@@ -0,0 +1,68 @@
/*
* 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 <gtest/gtest.h>
#include <react/renderer/css/CSSRatio.h>
#include <react/renderer/css/CSSValueParser.h>
namespace facebook::react {
TEST(CSSRatio, ratio_values) {
auto emptyValue = parseCSSProperty<CSSRatio>("");
EXPECT_TRUE(std::holds_alternative<std::monostate>(emptyValue));
auto validRatio = parseCSSProperty<CSSRatio>("16/9");
EXPECT_TRUE(std::holds_alternative<CSSRatio>(validRatio));
EXPECT_EQ(std::get<CSSRatio>(validRatio).numerator, 16.0f);
EXPECT_EQ(std::get<CSSRatio>(validRatio).denominator, 9.0f);
auto validRatioWithWhitespace = parseCSSProperty<CSSRatio>("16 / 9");
EXPECT_TRUE(std::holds_alternative<CSSRatio>(validRatioWithWhitespace));
EXPECT_EQ(std::get<CSSRatio>(validRatioWithWhitespace).numerator, 16.0f);
EXPECT_EQ(std::get<CSSRatio>(validRatioWithWhitespace).denominator, 9.0f);
auto singleNumberRatio = parseCSSProperty<CSSRatio>("16");
EXPECT_TRUE(std::holds_alternative<CSSRatio>(singleNumberRatio));
EXPECT_EQ(std::get<CSSRatio>(singleNumberRatio).numerator, 16.0f);
EXPECT_EQ(std::get<CSSRatio>(singleNumberRatio).denominator, 1.0f);
auto fractionalNumber = parseCSSProperty<CSSRatio>("16.5");
EXPECT_TRUE(std::holds_alternative<CSSRatio>(fractionalNumber));
EXPECT_EQ(std::get<CSSRatio>(fractionalNumber).numerator, 16.5f);
EXPECT_EQ(std::get<CSSRatio>(fractionalNumber).denominator, 1.0f);
auto fractionalNumerator = parseCSSProperty<CSSRatio>("16.5/9");
EXPECT_TRUE(std::holds_alternative<CSSRatio>(fractionalNumerator));
EXPECT_EQ(std::get<CSSRatio>(fractionalNumerator).numerator, 16.5f);
EXPECT_EQ(std::get<CSSRatio>(fractionalNumerator).denominator, 9.0f);
auto fractionalDenominator = parseCSSProperty<CSSRatio>("16/9.5");
EXPECT_TRUE(std::holds_alternative<CSSRatio>(fractionalDenominator));
EXPECT_EQ(std::get<CSSRatio>(fractionalDenominator).numerator, 16.0f);
}
TEST(CSSRatio, invalid_values) {
auto negativeNumber = parseCSSProperty<CSSRatio>("-16");
EXPECT_TRUE(std::holds_alternative<std::monostate>(negativeNumber));
auto missingDenominator = parseCSSProperty<CSSRatio>("16/");
EXPECT_TRUE(std::holds_alternative<std::monostate>(missingDenominator));
auto negativeNumerator = parseCSSProperty<CSSRatio>("-16/9");
EXPECT_TRUE(std::holds_alternative<std::monostate>(negativeNumerator));
auto negativeDenominator = parseCSSProperty<CSSRatio>("16/-9");
EXPECT_TRUE(std::holds_alternative<std::monostate>(negativeDenominator));
}
TEST(CSSRatio, degenerate_values) {
auto degenerateRatio = parseCSSProperty<CSSRatio>("0");
EXPECT_TRUE(std::holds_alternative<CSSRatio>(degenerateRatio));
EXPECT_TRUE(std::get<CSSRatio>(degenerateRatio).isDegenerate());
}
} // namespace facebook::react

View File

@@ -0,0 +1,339 @@
/*
* 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 <gtest/gtest.h>
#include <react/renderer/css/CSSList.h>
#include <react/renderer/css/CSSShadow.h>
#include <react/renderer/css/CSSValueParser.h>
namespace facebook::react {
TEST(CSSShadow, basic) {
auto value = parseCSSProperty<CSSShadow>("10px 5px");
EXPECT_TRUE(std::holds_alternative<CSSShadow>(value));
auto& shadow = std::get<CSSShadow>(value);
EXPECT_EQ(shadow.offsetX.value, 10.0f);
EXPECT_EQ(shadow.offsetX.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadow.offsetY.value, 5.0f);
EXPECT_EQ(shadow.offsetY.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadow.blurRadius.value, 0.0f);
EXPECT_EQ(shadow.blurRadius.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadow.spreadDistance.value, 0.0f);
EXPECT_EQ(shadow.spreadDistance.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadow.color, CSSColor::black());
EXPECT_FALSE(shadow.inset);
}
TEST(CSSShadow, rem_unit) {
auto value = parseCSSProperty<CSSShadow>("10px 5rem");
EXPECT_TRUE(std::holds_alternative<CSSShadow>(value));
auto& shadow = std::get<CSSShadow>(value);
EXPECT_EQ(shadow.offsetX.value, 10.0f);
EXPECT_EQ(shadow.offsetX.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadow.offsetY.value, 5.0f);
EXPECT_EQ(shadow.offsetY.unit, CSSLengthUnit::Rem);
EXPECT_EQ(shadow.blurRadius.value, 0.0f);
EXPECT_EQ(shadow.blurRadius.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadow.spreadDistance.value, 0.0f);
EXPECT_EQ(shadow.spreadDistance.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadow.color, CSSColor::black());
EXPECT_FALSE(shadow.inset);
}
TEST(CSSShadow, unitless_zero_length) {
auto value = parseCSSProperty<CSSShadow>("10px 0");
EXPECT_TRUE(std::holds_alternative<CSSShadow>(value));
auto& shadow = std::get<CSSShadow>(value);
EXPECT_EQ(shadow.offsetX.value, 10.0f);
EXPECT_EQ(shadow.offsetX.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadow.offsetY.value, 0.0f);
EXPECT_EQ(shadow.offsetY.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadow.blurRadius.value, 0.0f);
EXPECT_EQ(shadow.blurRadius.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadow.spreadDistance.value, 0.0f);
EXPECT_EQ(shadow.spreadDistance.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadow.color, CSSColor::black());
EXPECT_FALSE(shadow.inset);
}
TEST(CSSShadow, multiple_whitespace) {
auto value = parseCSSProperty<CSSShadow>("10px 5px");
EXPECT_TRUE(std::holds_alternative<CSSShadow>(value));
auto& shadow = std::get<CSSShadow>(value);
EXPECT_EQ(shadow.offsetX.value, 10.0f);
EXPECT_EQ(shadow.offsetX.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadow.offsetY.value, 5.0f);
EXPECT_EQ(shadow.offsetY.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadow.blurRadius.value, 0.0f);
EXPECT_EQ(shadow.blurRadius.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadow.spreadDistance.value, 0.0f);
EXPECT_EQ(shadow.spreadDistance.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadow.color, CSSColor::black());
EXPECT_FALSE(shadow.inset);
}
TEST(CSSShadow, trailing_color) {
auto value = parseCSSProperty<CSSShadow>("10px 5px red");
EXPECT_TRUE(std::holds_alternative<CSSShadow>(value));
auto& shadow = std::get<CSSShadow>(value);
EXPECT_EQ(shadow.offsetX.value, 10.0f);
EXPECT_EQ(shadow.offsetX.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadow.offsetY.value, 5.0f);
EXPECT_EQ(shadow.offsetY.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadow.blurRadius.value, 0.0f);
EXPECT_EQ(shadow.blurRadius.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadow.spreadDistance.value, 0.0f);
EXPECT_EQ(shadow.spreadDistance.unit, CSSLengthUnit::Px);
CSSColor red{.r = 255u, .g = 0u, .b = 0u, .a = 255u};
EXPECT_EQ(shadow.color, red);
EXPECT_FALSE(shadow.inset);
}
TEST(CSSShadow, leading_color) {
auto value = parseCSSProperty<CSSShadow>("red 10px 5px");
EXPECT_TRUE(std::holds_alternative<CSSShadow>(value));
auto& shadow = std::get<CSSShadow>(value);
EXPECT_EQ(shadow.offsetX.value, 10.0f);
EXPECT_EQ(shadow.offsetX.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadow.offsetY.value, 5.0f);
EXPECT_EQ(shadow.offsetY.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadow.blurRadius.value, 0.0f);
EXPECT_EQ(shadow.blurRadius.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadow.spreadDistance.value, 0.0f);
EXPECT_EQ(shadow.spreadDistance.unit, CSSLengthUnit::Px);
CSSColor red{.r = 255u, .g = 0u, .b = 0u, .a = 255u};
EXPECT_EQ(shadow.color, red);
EXPECT_FALSE(shadow.inset);
}
TEST(CSSShadow, color_function) {
auto value = parseCSSProperty<CSSShadow>("10px 5px rgba(255, 0, 0, 0.5)");
EXPECT_TRUE(std::holds_alternative<CSSShadow>(value));
auto& shadow = std::get<CSSShadow>(value);
EXPECT_EQ(shadow.offsetX.value, 10.0f);
EXPECT_EQ(shadow.offsetX.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadow.offsetY.value, 5.0f);
EXPECT_EQ(shadow.offsetY.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadow.blurRadius.value, 0.0f);
EXPECT_EQ(shadow.blurRadius.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadow.spreadDistance.value, 0.0f);
EXPECT_EQ(shadow.spreadDistance.unit, CSSLengthUnit::Px);
CSSColor red{.r = 255u, .g = 0u, .b = 0u, .a = 128u};
EXPECT_EQ(shadow.color, red);
EXPECT_FALSE(shadow.inset);
}
TEST(CSSShadow, blur_radius) {
auto value = parseCSSProperty<CSSShadow>("10px 5px 2px");
EXPECT_TRUE(std::holds_alternative<CSSShadow>(value));
auto& shadow = std::get<CSSShadow>(value);
EXPECT_EQ(shadow.offsetX.value, 10.0f);
EXPECT_EQ(shadow.offsetX.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadow.offsetY.value, 5.0f);
EXPECT_EQ(shadow.offsetY.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadow.blurRadius.value, 2.0f);
EXPECT_EQ(shadow.blurRadius.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadow.spreadDistance.value, 0.0f);
EXPECT_EQ(shadow.spreadDistance.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadow.color, CSSColor::black());
EXPECT_FALSE(shadow.inset);
}
TEST(CSSShadow, spread_distance) {
auto value = parseCSSProperty<CSSShadow>("10px 5px 2px 3px");
EXPECT_TRUE(std::holds_alternative<CSSShadow>(value));
auto& shadow = std::get<CSSShadow>(value);
EXPECT_EQ(shadow.offsetX.value, 10.0f);
EXPECT_EQ(shadow.offsetX.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadow.offsetY.value, 5.0f);
EXPECT_EQ(shadow.offsetY.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadow.blurRadius.value, 2.0f);
EXPECT_EQ(shadow.blurRadius.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadow.spreadDistance.value, 3.0f);
EXPECT_EQ(shadow.spreadDistance.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadow.color, CSSColor::black());
EXPECT_FALSE(shadow.inset);
}
TEST(CSSShadow, inset) {
auto value = parseCSSProperty<CSSShadow>("5px 2px inset");
EXPECT_TRUE(std::holds_alternative<CSSShadow>(value));
auto& shadow = std::get<CSSShadow>(value);
EXPECT_EQ(shadow.offsetX.value, 5.0f);
EXPECT_EQ(shadow.offsetX.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadow.offsetY.value, 2.0f);
EXPECT_EQ(shadow.offsetY.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadow.blurRadius.value, 0.0f);
EXPECT_EQ(shadow.blurRadius.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadow.spreadDistance.value, 0.0f);
EXPECT_EQ(shadow.spreadDistance.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadow.color, CSSColor::black());
EXPECT_TRUE(shadow.inset);
}
TEST(CSShadow, color_length_inset) {
auto value = parseCSSProperty<CSSShadow>("red 10px 10px inset");
EXPECT_TRUE(std::holds_alternative<CSSShadow>(value));
auto& shadow = std::get<CSSShadow>(value);
EXPECT_EQ(shadow.offsetX.value, 10.0f);
EXPECT_EQ(shadow.offsetX.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadow.offsetY.value, 10.0f);
EXPECT_EQ(shadow.offsetY.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadow.blurRadius.value, 0.0f);
EXPECT_EQ(shadow.blurRadius.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadow.spreadDistance.value, 0.0f);
EXPECT_EQ(shadow.spreadDistance.unit, CSSLengthUnit::Px);
CSSColor red{.r = 255u, .g = 0u, .b = 0u, .a = 255u};
EXPECT_EQ(shadow.color, red);
EXPECT_TRUE(shadow.inset);
}
TEST(CSSShadow, multiple_shadows) {
auto value = parseCSSProperty<CSSShadowList>(
"10px 5px red, 5px 12px inset, inset 10px 45px 13px red");
EXPECT_TRUE(std::holds_alternative<CSSShadowList>(value));
auto& shadows = std::get<CSSShadowList>(value);
EXPECT_EQ(shadows.size(), 3);
EXPECT_EQ(shadows[0].offsetX.value, 10.0f);
EXPECT_EQ(shadows[0].offsetX.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadows[0].offsetY.value, 5.0f);
EXPECT_EQ(shadows[0].offsetY.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadows[0].blurRadius.value, 0.0f);
EXPECT_EQ(shadows[0].blurRadius.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadows[0].spreadDistance.value, 0.0f);
EXPECT_EQ(shadows[0].spreadDistance.unit, CSSLengthUnit::Px);
CSSColor red{.r = 255u, .g = 0u, .b = 0u, .a = 255u};
EXPECT_EQ(shadows[0].color, red);
EXPECT_FALSE(shadows[0].inset);
EXPECT_EQ(shadows[1].offsetX.value, 5.0f);
EXPECT_EQ(shadows[1].offsetX.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadows[1].offsetY.value, 12.0f);
EXPECT_EQ(shadows[1].offsetY.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadows[1].blurRadius.value, 0.0f);
EXPECT_EQ(shadows[1].blurRadius.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadows[1].spreadDistance.value, 0.0f);
EXPECT_EQ(shadows[1].spreadDistance.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadows[1].color, CSSColor::black());
EXPECT_TRUE(shadows[1].inset);
EXPECT_EQ(shadows[2].offsetX.value, 10.0f);
EXPECT_EQ(shadows[2].offsetX.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadows[2].offsetY.value, 45.0f);
EXPECT_EQ(shadows[2].offsetY.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadows[2].blurRadius.value, 13.0f);
EXPECT_EQ(shadows[2].blurRadius.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadows[2].spreadDistance.value, 0.0f);
EXPECT_EQ(shadows[2].spreadDistance.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadows[2].color, red);
EXPECT_TRUE(shadows[2].inset);
}
TEST(CSSShadow, multiple_shadows_with_new_line) {
auto value = parseCSSProperty<CSSShadowList>(
"10px 5px red, \n5px 12px inset,\n inset 10px 45px 13px red");
EXPECT_TRUE(std::holds_alternative<CSSShadowList>(value));
auto& shadows = std::get<CSSShadowList>(value);
EXPECT_EQ(shadows.size(), 3);
EXPECT_EQ(shadows[0].offsetX.value, 10.0f);
EXPECT_EQ(shadows[0].offsetX.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadows[0].offsetY.value, 5.0f);
EXPECT_EQ(shadows[0].offsetY.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadows[0].blurRadius.value, 0.0f);
EXPECT_EQ(shadows[0].blurRadius.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadows[0].spreadDistance.value, 0.0f);
EXPECT_EQ(shadows[0].spreadDistance.unit, CSSLengthUnit::Px);
CSSColor red{.r = 255u, .g = 0u, .b = 0u, .a = 255u};
EXPECT_EQ(shadows[0].color, red);
EXPECT_FALSE(shadows[0].inset);
EXPECT_EQ(shadows[1].offsetX.value, 5.0f);
EXPECT_EQ(shadows[1].offsetX.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadows[1].offsetY.value, 12.0f);
EXPECT_EQ(shadows[1].offsetY.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadows[1].blurRadius.value, 0.0f);
EXPECT_EQ(shadows[1].blurRadius.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadows[1].spreadDistance.value, 0.0f);
EXPECT_EQ(shadows[1].spreadDistance.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadows[1].color, CSSColor::black());
EXPECT_TRUE(shadows[1].inset);
EXPECT_EQ(shadows[2].offsetX.value, 10.0f);
EXPECT_EQ(shadows[2].offsetX.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadows[2].offsetY.value, 45.0f);
EXPECT_EQ(shadows[2].offsetY.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadows[2].blurRadius.value, 13.0f);
EXPECT_EQ(shadows[2].blurRadius.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadows[2].spreadDistance.value, 0.0f);
EXPECT_EQ(shadows[2].spreadDistance.unit, CSSLengthUnit::Px);
EXPECT_EQ(shadows[2].color, red);
EXPECT_TRUE(shadows[2].inset);
}
TEST(CSSShadow, invalid_units) {
auto value = parseCSSProperty<CSSShadow>("red 10em 5$ 2| 3rp");
EXPECT_TRUE(std::holds_alternative<std::monostate>(value));
}
TEST(CSSShadow, too_many_lengths) {
auto value = parseCSSProperty<CSSShadow>("10px 5px 2px 3px 10px 10px");
EXPECT_TRUE(std::holds_alternative<std::monostate>(value));
}
TEST(CSSShadow, too_many_lengths_as_part_of_multiple) {
auto value =
parseCSSProperty<CSSShadowList>("10px 5px 2px 3px 10px 10px, 10px 5px");
EXPECT_TRUE(std::holds_alternative<std::monostate>(value));
}
TEST(CSSShadow, inset_between_lengths) {
auto value = parseCSSProperty<CSSShadow>("10px inset 5px");
EXPECT_TRUE(std::holds_alternative<std::monostate>(value));
}
TEST(CSSShadow, color_between_lengths) {
auto value = parseCSSProperty<CSSShadow>("10px blue 5px");
EXPECT_TRUE(std::holds_alternative<std::monostate>(value));
}
TEST(CSSShadow, invalid_keyword) {
auto value = parseCSSProperty<CSSShadow>("10px 5px outset");
EXPECT_TRUE(std::holds_alternative<std::monostate>(value));
}
TEST(CSSShadow, negative_blur) {
auto value = parseCSSProperty<CSSShadow>("red 5px 2px -3px");
EXPECT_TRUE(std::holds_alternative<std::monostate>(value));
}
TEST(CSSShadow, missing_unit) {
auto value = parseCSSProperty<CSSShadow>("10px 5");
EXPECT_TRUE(std::holds_alternative<std::monostate>(value));
}
} // namespace facebook::react

View File

@@ -0,0 +1,564 @@
/*
* 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 <gtest/gtest.h>
#include <react/renderer/css/CSSSyntaxParser.h>
namespace facebook::react {
TEST(CSSSyntaxParser, simple) {
CSSSyntaxParser parser{"1px solid black"};
auto pxValue = parser.consumeComponentValue<float>(
CSSDelimiter::OptionalWhitespace, [](const CSSPreservedToken& token) {
EXPECT_EQ(token.type(), CSSTokenType::Dimension);
EXPECT_EQ(token.numericValue(), 1.0f);
EXPECT_EQ(token.unit(), "px");
return token.numericValue();
});
EXPECT_EQ(pxValue, 1.0f);
auto identValue = parser.consumeComponentValue<std::string_view>(
CSSDelimiter::OptionalWhitespace, [](const CSSPreservedToken& token) {
EXPECT_EQ(token.type(), CSSTokenType::Ident);
EXPECT_EQ(token.stringValue(), "solid");
return token.stringValue();
});
EXPECT_EQ(identValue, "solid");
auto identValue2 = parser.consumeComponentValue<std::string_view>(
CSSDelimiter::OptionalWhitespace, [](const CSSPreservedToken& token) {
EXPECT_EQ(token.type(), CSSTokenType::Ident);
EXPECT_EQ(token.stringValue(), "black");
return token.stringValue();
});
EXPECT_EQ(identValue2, "black");
}
TEST(CSSSyntaxParser, single_function_no_args) {
CSSSyntaxParser parser{"foo()"};
auto funcName = parser.consumeComponentValue<std::string_view>(
[](const CSSFunctionBlock& function, CSSSyntaxParser& blockParser) {
EXPECT_EQ(function.name, "foo");
return function.name;
auto hasMoreTokens = blockParser.consumeComponentValue<bool>(
[](const CSSPreservedToken& /*token*/) { return true; });
EXPECT_FALSE(hasMoreTokens);
});
EXPECT_EQ(funcName, "foo");
}
TEST(CSSSyntaxParser, single_function_with_whitespace_delimited_args) {
CSSSyntaxParser parser{"foo( a b c)"};
auto funcArgs = parser.consumeComponentValue<std::vector<std::string>>(
[&](const CSSFunctionBlock& function, CSSSyntaxParser& blockParser) {
EXPECT_EQ(function.name, "foo");
std::vector<std::string> args;
args.emplace_back(blockParser.consumeComponentValue<std::string_view>(
CSSDelimiter::OptionalWhitespace,
[](const CSSPreservedToken& token) {
EXPECT_EQ(token.type(), CSSTokenType::Ident);
EXPECT_EQ(token.stringValue(), "a");
return token.stringValue();
}));
args.emplace_back(blockParser.consumeComponentValue<std::string_view>(
CSSDelimiter::Whitespace,
[](const CSSPreservedToken& token) {
EXPECT_EQ(token.type(), CSSTokenType::Ident);
EXPECT_EQ(token.stringValue(), "b");
return token.stringValue();
}));
args.emplace_back(blockParser.consumeComponentValue<std::string_view>(
CSSDelimiter::Whitespace,
[](const CSSPreservedToken& token) {
EXPECT_EQ(token.type(), CSSTokenType::Ident);
EXPECT_EQ(token.stringValue(), "c");
return token.stringValue();
}));
auto hasMoreTokens = blockParser.consumeComponentValue<bool>(
CSSDelimiter::Whitespace,
[](const CSSPreservedToken& /*token*/) { return true; });
EXPECT_FALSE(hasMoreTokens);
return args;
});
std::vector<std::string> expectedArgs{"a", "b", "c"};
EXPECT_EQ(funcArgs, expectedArgs);
}
TEST(CSSSyntaxParser, single_function_with_comma_delimited_args) {
CSSSyntaxParser parser{"rgb(100, 200, 50 )"};
auto funcArgs = parser.consumeComponentValue<std::array<uint8_t, 3>>(
[&](const CSSFunctionBlock& function, CSSSyntaxParser& blockParser) {
EXPECT_EQ(function.name, "rgb");
std::array<uint8_t, 3> rgb{};
rgb[0] = blockParser.consumeComponentValue<uint8_t>(
CSSDelimiter::OptionalWhitespace,
[](const CSSPreservedToken& token) {
EXPECT_EQ(token.type(), CSSTokenType::Number);
EXPECT_EQ(token.numericValue(), 100);
return static_cast<uint8_t>(token.numericValue());
});
rgb[1] = blockParser.consumeComponentValue<uint8_t>(
CSSDelimiter::Comma, [](const CSSPreservedToken& token) {
EXPECT_EQ(token.type(), CSSTokenType::Number);
EXPECT_EQ(token.numericValue(), 200);
return static_cast<uint8_t>(token.numericValue());
});
rgb[2] = blockParser.consumeComponentValue<uint8_t>(
CSSDelimiter::Comma, [](const CSSPreservedToken& token) {
EXPECT_EQ(token.type(), CSSTokenType::Number);
EXPECT_EQ(token.numericValue(), 50);
return static_cast<uint8_t>(token.numericValue());
});
auto hasMoreTokens = blockParser.consumeComponentValue<bool>(
CSSDelimiter::Whitespace,
[](const CSSPreservedToken& /*token*/) { return true; });
EXPECT_FALSE(hasMoreTokens);
return rgb;
});
std::array<uint8_t, 3> expectedArgs{{100, 200, 50}};
EXPECT_EQ(funcArgs, expectedArgs);
}
TEST(CSSSyntaxParser, single_function_with_mixed_delimited_args) {
CSSSyntaxParser parser{"rgb(100, 200 50 )"};
auto funcArgs = parser.consumeComponentValue<std::array<uint8_t, 3>>(
[&](const CSSFunctionBlock& function, CSSSyntaxParser& blockParser) {
EXPECT_EQ(function.name, "rgb");
std::array<uint8_t, 3> rgb{};
rgb[0] = blockParser.consumeComponentValue<uint8_t>(
CSSDelimiter::None, [](const CSSPreservedToken& token) {
EXPECT_EQ(token.type(), CSSTokenType::Number);
EXPECT_EQ(token.numericValue(), 100);
return static_cast<uint8_t>(token.numericValue());
});
rgb[1] = blockParser.consumeComponentValue<uint8_t>(
CSSDelimiter::CommaOrWhitespace,
[](const CSSPreservedToken& token) {
EXPECT_EQ(token.type(), CSSTokenType::Number);
EXPECT_EQ(token.numericValue(), 200);
return static_cast<uint8_t>(token.numericValue());
});
rgb[2] = blockParser.consumeComponentValue<uint8_t>(
CSSDelimiter::CommaOrWhitespace,
[](const CSSPreservedToken& token) {
EXPECT_EQ(token.type(), CSSTokenType::Number);
EXPECT_EQ(token.numericValue(), 50);
return static_cast<uint8_t>(token.numericValue());
});
auto hasMoreTokens = blockParser.consumeComponentValue<bool>(
CSSDelimiter::Whitespace,
[](const CSSPreservedToken& /*token*/) { return true; });
EXPECT_FALSE(hasMoreTokens);
return rgb;
});
std::array<uint8_t, 3> expectedArgs{{100, 200, 50}};
EXPECT_EQ(funcArgs, expectedArgs);
}
TEST(CSSSyntaxParser, complex) {
CSSSyntaxParser parser{"foo(a bar())baz() 12px"};
auto fooFunc = parser.consumeComponentValue<std::string_view>(
[&](const CSSFunctionBlock& function, CSSSyntaxParser& blockParser) {
EXPECT_EQ(function.name, "foo");
auto identArg = blockParser.consumeComponentValue<std::string_view>(
CSSDelimiter::OptionalWhitespace,
[](const CSSPreservedToken& token) {
EXPECT_EQ(token.type(), CSSTokenType::Ident);
EXPECT_EQ(token.stringValue(), "a");
return token.stringValue();
});
EXPECT_EQ(identArg, "a");
auto barFunc = blockParser.consumeComponentValue<std::string_view>(
CSSDelimiter::Whitespace,
[&](const CSSFunctionBlock& function,
CSSSyntaxParser& nestedBlockParser) {
EXPECT_EQ(function.name, "bar");
auto hasMoreTokens =
nestedBlockParser.consumeComponentValue<bool>(
CSSDelimiter::Whitespace,
[](const CSSPreservedToken& /*token*/) { return true; });
EXPECT_FALSE(hasMoreTokens);
return function.name;
});
EXPECT_EQ(barFunc, "bar");
auto hasMoreTokens = blockParser.consumeComponentValue<bool>(
CSSDelimiter::Whitespace,
[](const CSSPreservedToken& /*token*/) { return true; });
EXPECT_FALSE(hasMoreTokens);
return function.name;
});
EXPECT_EQ(fooFunc, "foo");
auto bazFunc = parser.consumeComponentValue<std::string_view>(
[&](const CSSFunctionBlock& function, CSSSyntaxParser& /*blockParser*/) {
EXPECT_EQ(function.name, "baz");
return function.name;
});
EXPECT_EQ(bazFunc, "baz");
auto pxValue = parser.consumeComponentValue<float>(
CSSDelimiter::Whitespace, [](const CSSPreservedToken& token) {
EXPECT_EQ(token.type(), CSSTokenType::Dimension);
EXPECT_EQ(token.numericValue(), 12.0f);
EXPECT_EQ(token.unit(), "px");
return token.numericValue();
});
EXPECT_EQ(pxValue, 12.0f);
}
TEST(CSSSyntaxParser, unterminated_functions) {
EXPECT_FALSE(
CSSSyntaxParser{"foo("}.consumeComponentValue<bool>(
[](const CSSFunctionBlock&, CSSSyntaxParser& /*blockParser*/) {
return true;
}));
EXPECT_FALSE(
CSSSyntaxParser{"foo(a bar()baz()"}.consumeComponentValue<bool>(
[](const CSSFunctionBlock&, CSSSyntaxParser& /*blockParser*/) {
return true;
}));
}
TEST(CSSSyntaxParser, simple_blocks) {
CSSSyntaxParser parser1{"(a)"};
auto identValue = parser1.consumeComponentValue<std::string_view>(
[&](const CSSSimpleBlock& block, CSSSyntaxParser& blockParser) {
EXPECT_EQ(block.openBracketType, CSSTokenType::OpenParen);
return blockParser.consumeComponentValue<std::string_view>(
[](const CSSPreservedToken& token) {
EXPECT_EQ(token.type(), CSSTokenType::Ident);
return token.stringValue();
});
});
EXPECT_EQ(identValue, "a");
CSSSyntaxParser parser2{"[b ]"};
auto identValue2 = parser2.consumeComponentValue<std::string_view>(
[&](const CSSSimpleBlock& block, CSSSyntaxParser& blockParser) {
EXPECT_EQ(block.openBracketType, CSSTokenType::OpenSquare);
return blockParser.consumeComponentValue<std::string_view>(
[](const CSSPreservedToken& token) {
EXPECT_EQ(token.type(), CSSTokenType::Ident);
return token.stringValue();
});
});
EXPECT_EQ(identValue2, "b");
CSSSyntaxParser parser3{"{c}"};
auto identValue3 = parser3.consumeComponentValue<std::string_view>(
[&](const CSSSimpleBlock& block, CSSSyntaxParser& blockParser) {
EXPECT_EQ(block.openBracketType, CSSTokenType::OpenCurly);
return blockParser.consumeComponentValue<std::string_view>(
[](const CSSPreservedToken& token) {
EXPECT_EQ(token.type(), CSSTokenType::Ident);
return token.stringValue();
});
});
EXPECT_EQ(identValue3, "c");
}
TEST(CSSSyntaxParser, unterminated_simple_blocks) {
CSSSyntaxParser parser1{"(a"};
auto identValue = parser1.consumeComponentValue<std::string_view>(
[&](const CSSSimpleBlock& block, CSSSyntaxParser& blockParser) {
EXPECT_EQ(block.openBracketType, CSSTokenType::OpenParen);
return blockParser.consumeComponentValue<std::string_view>(
[](const CSSPreservedToken& token) {
EXPECT_EQ(token.type(), CSSTokenType::Ident);
return token.stringValue();
});
});
EXPECT_EQ(identValue, "");
CSSSyntaxParser parser2{"[b "};
auto identValue2 = parser2.consumeComponentValue<std::string_view>(
[&](const CSSSimpleBlock& block, CSSSyntaxParser& blockParser) {
EXPECT_EQ(block.openBracketType, CSSTokenType::OpenSquare);
return blockParser.consumeComponentValue<std::string_view>(
[](const CSSPreservedToken& token) {
EXPECT_EQ(token.type(), CSSTokenType::Ident);
return token.stringValue();
});
});
EXPECT_EQ(identValue2, "");
CSSSyntaxParser parser3{"{c"};
auto identValue3 = parser3.consumeComponentValue<std::string_view>(
[&](const CSSSimpleBlock& block, CSSSyntaxParser& blockParser) {
EXPECT_EQ(block.openBracketType, CSSTokenType::OpenCurly);
return blockParser.consumeComponentValue<std::string_view>(
[](const CSSPreservedToken& token) {
EXPECT_EQ(token.type(), CSSTokenType::Ident);
return token.stringValue();
});
});
EXPECT_EQ(identValue3, "");
}
TEST(CSSSyntaxParser, unconsumed_function_args) {
CSSSyntaxParser parser{"foo(a)"};
auto funcValue =
parser.consumeComponentValue<std::optional<std::string_view>>(
[&](const CSSFunctionBlock& function,
CSSSyntaxParser& /*blockParser*/) {
EXPECT_EQ(function.name, "foo");
return function.name;
});
EXPECT_EQ(funcValue, std::nullopt);
}
TEST(CSSSyntaxParser, whitespace_surrounding_function_args) {
CSSSyntaxParser parser{"foo( a )"};
auto funcValue = parser.consumeComponentValue<std::string_view>(
[&](const CSSFunctionBlock& function, CSSSyntaxParser& blockParser) {
EXPECT_EQ(function.name, "foo");
auto identArg = blockParser.consumeComponentValue<std::string_view>(
CSSDelimiter::None, [](const CSSPreservedToken& token) {
EXPECT_EQ(token.type(), CSSTokenType::Ident);
EXPECT_EQ(token.stringValue(), "a");
return token.stringValue();
});
EXPECT_EQ(identArg, "a");
return function.name;
});
EXPECT_EQ(funcValue, "foo");
}
TEST(CSSSyntaxParser, unconsumed_simple_block_args) {
CSSSyntaxParser parser{"{a}"};
auto funcValue = parser.consumeComponentValue<std::optional<CSSTokenType>>(
[&](const CSSSimpleBlock& block, CSSSyntaxParser& /*blockParser*/) {
EXPECT_EQ(block.openBracketType, CSSTokenType::OpenCurly);
return block.openBracketType;
});
EXPECT_EQ(funcValue, std::nullopt);
}
TEST(CSSSyntaxParser, solidus_delimiter) {
CSSSyntaxParser parser{"foo / bar"};
auto identValue = parser.consumeComponentValue<std::string_view>(
[](const CSSPreservedToken& token) {
EXPECT_EQ(token.type(), CSSTokenType::Ident);
EXPECT_EQ(token.stringValue(), "foo");
return token.stringValue();
});
EXPECT_EQ(identValue, "foo");
auto identValue2 = parser.consumeComponentValue<std::string_view>(
CSSDelimiter::Solidus, [](const CSSPreservedToken& token) {
EXPECT_EQ(token.type(), CSSTokenType::Ident);
EXPECT_EQ(token.stringValue(), "bar");
return token.stringValue();
});
EXPECT_EQ(identValue2, "bar");
}
TEST(CSSSyntaxParser, solidus_delimiter_not_present) {
CSSSyntaxParser parser{"foo bar"};
auto identValue = parser.consumeComponentValue<std::string_view>(
[](const CSSPreservedToken& token) {
EXPECT_EQ(token.type(), CSSTokenType::Ident);
EXPECT_EQ(token.stringValue(), "foo");
return token.stringValue();
});
EXPECT_EQ(identValue, "foo");
auto identValue2 = parser.consumeComponentValue<bool>(
CSSDelimiter::Solidus,
[](const CSSPreservedToken& /*token*/) { return true; });
EXPECT_FALSE(identValue2);
}
TEST(CSSSyntaxParser, required_whitespace_not_present) {
CSSSyntaxParser parser{"foo/"};
auto identValue = parser.consumeComponentValue<std::string_view>(
[](const CSSPreservedToken& token) {
EXPECT_EQ(token.type(), CSSTokenType::Ident);
EXPECT_EQ(token.stringValue(), "foo");
return token.stringValue();
});
EXPECT_EQ(identValue, "foo");
auto delimValue1 = parser.consumeComponentValue<bool>(
CSSDelimiter::Whitespace,
[](const CSSPreservedToken& /*token*/) { return true; });
EXPECT_FALSE(delimValue1);
auto delimValue2 = parser.consumeComponentValue<std::string_view>(
CSSDelimiter::OptionalWhitespace, [](const CSSPreservedToken& token) {
EXPECT_EQ(token.type(), CSSTokenType::Delim);
EXPECT_EQ(token.stringValue(), "/");
return token.stringValue();
});
EXPECT_EQ(delimValue2, "/");
}
TEST(CSSSyntaxParser, solidus_or_whitespace) {
CSSSyntaxParser parser{"foo bar / baz potato, papaya"};
auto identValue1 = parser.consumeComponentValue<std::string_view>(
CSSDelimiter::OptionalWhitespace, [](const CSSPreservedToken& token) {
EXPECT_EQ(token.type(), CSSTokenType::Ident);
EXPECT_EQ(token.stringValue(), "foo");
return token.stringValue();
});
EXPECT_EQ(identValue1, "foo");
auto identValue2 = parser.consumeComponentValue<std::string_view>(
CSSDelimiter::SolidusOrWhitespace, [](const CSSPreservedToken& token) {
EXPECT_EQ(token.type(), CSSTokenType::Ident);
EXPECT_EQ(token.stringValue(), "bar");
return token.stringValue();
});
EXPECT_EQ(identValue2, "bar");
auto identValue3 = parser.consumeComponentValue<std::string_view>(
CSSDelimiter::SolidusOrWhitespace, [](const CSSPreservedToken& token) {
EXPECT_EQ(token.type(), CSSTokenType::Ident);
EXPECT_EQ(token.stringValue(), "baz");
return token.stringValue();
});
EXPECT_EQ(identValue3, "baz");
auto identValue4 = parser.consumeComponentValue<std::string_view>(
CSSDelimiter::SolidusOrWhitespace, [](const CSSPreservedToken& token) {
EXPECT_EQ(token.type(), CSSTokenType::Ident);
EXPECT_EQ(token.stringValue(), "potato");
return token.stringValue();
});
EXPECT_EQ(identValue4, "potato");
auto delimValue1 = parser.consumeComponentValue<bool>(
CSSDelimiter::SolidusOrWhitespace,
[](const CSSPreservedToken& /*token*/) { return true; });
EXPECT_FALSE(delimValue1);
}
TEST(CSSSyntaxParser, delimeter_not_consumed_when_no_component_value) {
CSSSyntaxParser parser{"foo ,"};
auto identValue = parser.consumeComponentValue<std::string_view>(
[](const CSSPreservedToken& token) {
EXPECT_EQ(token.type(), CSSTokenType::Ident);
EXPECT_EQ(token.stringValue(), "foo");
return token.stringValue();
});
EXPECT_EQ(identValue, "foo");
auto identValue2 = parser.consumeComponentValue<bool>(
CSSDelimiter::Comma,
[](const CSSPreservedToken& /*token*/) { return true; });
EXPECT_FALSE(identValue2);
auto hasComma = parser.consumeComponentValue<bool>(
CSSDelimiter::Whitespace, [](const CSSPreservedToken& token) {
EXPECT_EQ(token.type(), CSSTokenType::Comma);
return true;
});
EXPECT_TRUE(hasComma);
}
TEST(CSSSyntaxParser, component_value_not_consumed_on_visitor_failure) {
CSSSyntaxParser parser{"foo"};
bool visitor1Attempted = false;
bool visitor1Ret =
parser.consumeComponentValue<bool>([&](const CSSPreservedToken& token) {
EXPECT_EQ(token.stringValue(), "foo");
visitor1Attempted = true;
return false;
});
EXPECT_TRUE(visitor1Attempted);
EXPECT_FALSE(visitor1Ret);
bool visitor2Attempted = false;
parser.consumeComponentValue<bool>([&](const CSSPreservedToken& token) {
EXPECT_EQ(token.stringValue(), "foo");
visitor2Attempted = true;
return true;
});
EXPECT_TRUE(visitor2Attempted);
EXPECT_TRUE(visitor2Attempted);
bool visitor3Attempted = false;
bool visitor3Ret = parser.consumeComponentValue<bool>(
[&](const CSSPreservedToken& /*token*/) {
visitor3Attempted = true;
return true;
});
EXPECT_FALSE(visitor3Attempted);
EXPECT_FALSE(visitor3Ret);
}
} // namespace facebook::react

View File

@@ -0,0 +1,242 @@
/*
* 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 <gtest/gtest.h>
#include <react/renderer/css/CSSTokenizer.h>
namespace facebook::react {
#define EXPECT_TOKENS(characters, ...) \
{ \
CSSTokenizer tokenizer{characters}; \
\
for (const auto& expectedToken : \
std::initializer_list<CSSToken>{__VA_ARGS__}) { \
auto nextToken = tokenizer.next(); \
EXPECT_EQ(nextToken.type(), expectedToken.type()); \
EXPECT_EQ(nextToken.stringValue(), expectedToken.stringValue()); \
EXPECT_EQ(nextToken.numericValue(), expectedToken.numericValue()); \
EXPECT_EQ(nextToken.unit(), expectedToken.unit()); \
EXPECT_EQ(nextToken, expectedToken); \
} \
}
TEST(CSSTokenizer, eof_values) {
EXPECT_TOKENS("", CSSToken{CSSTokenType::EndOfFile});
}
TEST(CSSTokenizer, whitespace_values) {
EXPECT_TOKENS(
" ",
CSSToken{CSSTokenType::WhiteSpace},
CSSToken{CSSTokenType::EndOfFile});
EXPECT_TOKENS(
" \t",
CSSToken{CSSTokenType::WhiteSpace},
CSSToken{CSSTokenType::EndOfFile});
EXPECT_TOKENS(
"\n \t",
CSSToken{CSSTokenType::WhiteSpace},
CSSToken{CSSTokenType::EndOfFile});
}
TEST(CSSTokenizer, ident_values) {
EXPECT_TOKENS(
"auto",
CSSToken{CSSTokenType::Ident, "auto"},
CSSToken{CSSTokenType::EndOfFile});
EXPECT_TOKENS(
"inset auto left",
CSSToken{CSSTokenType::Ident, "inset"},
CSSToken{CSSTokenType::WhiteSpace},
CSSToken{CSSTokenType::Ident, "auto"},
CSSToken{CSSTokenType::WhiteSpace},
CSSToken{CSSTokenType::Ident, "left"},
CSSToken{CSSTokenType::EndOfFile});
}
TEST(CSSTokenizer, number_values) {
EXPECT_TOKENS(
"12",
CSSToken{CSSTokenType::Number, 12.0f},
CSSToken{CSSTokenType::EndOfFile});
EXPECT_TOKENS(
"-5",
CSSToken{CSSTokenType::Number, -5.0f},
CSSToken{CSSTokenType::EndOfFile});
EXPECT_TOKENS(
"123.0",
CSSToken{CSSTokenType::Number, 123.0f},
CSSToken{CSSTokenType::EndOfFile});
EXPECT_TOKENS(
"4.2E-1",
CSSToken{CSSTokenType::Number, 4.2e-1},
CSSToken{CSSTokenType::EndOfFile});
EXPECT_TOKENS(
"6e-10",
CSSToken{CSSTokenType::Number, 6e-10f},
CSSToken{CSSTokenType::EndOfFile});
EXPECT_TOKENS(
"+81.07e+0",
CSSToken{CSSTokenType::Number, +81.07e+0},
CSSToken{CSSTokenType::EndOfFile});
EXPECT_TOKENS(
"+.123e+0",
CSSToken{CSSTokenType::Number, +.123e+0},
CSSToken{CSSTokenType::EndOfFile});
EXPECT_TOKENS(
"3e-1000",
CSSToken{CSSTokenType::Number, 0},
CSSToken{CSSTokenType::EndOfFile});
EXPECT_TOKENS(
"3e1000",
CSSToken{CSSTokenType::Number, std::numeric_limits<float>::infinity()},
CSSToken{CSSTokenType::EndOfFile});
EXPECT_TOKENS(
"+.9999999999",
CSSToken{CSSTokenType::Number, +.9999999999},
CSSToken{CSSTokenType::EndOfFile});
}
TEST(CSSTokenizer, dimension_values) {
EXPECT_TOKENS(
"12px",
CSSToken{CSSTokenType::Dimension, 12.0f, "px"},
CSSToken{CSSTokenType::EndOfFile});
EXPECT_TOKENS(
"463.2abc",
CSSToken{CSSTokenType::Dimension, 463.2, "abc"},
CSSToken{CSSTokenType::EndOfFile});
EXPECT_TOKENS(
".3xyz",
CSSToken{CSSTokenType::Dimension, 0.3, "xyz"},
CSSToken{CSSTokenType::EndOfFile});
EXPECT_TOKENS(
"-0.5em",
CSSToken{CSSTokenType::Dimension, -0.5, "em"},
CSSToken{CSSTokenType::EndOfFile});
}
TEST(CSSTokenizer, percent_values) {
EXPECT_TOKENS(
"12%",
CSSToken{CSSTokenType::Percentage, 12.0f},
CSSToken{CSSTokenType::EndOfFile});
EXPECT_TOKENS(
"-28.5%",
CSSToken{CSSTokenType::Percentage, -28.5f},
CSSToken{CSSTokenType::EndOfFile});
EXPECT_TOKENS(
".02%",
CSSToken{CSSTokenType::Percentage, 0.02f},
CSSToken{CSSTokenType::EndOfFile});
}
TEST(CSSTokenizer, mixed_values) {
EXPECT_TOKENS(
"12px -100vh",
CSSToken{CSSTokenType::Dimension, 12.0f, "px"},
CSSToken{CSSTokenType::WhiteSpace},
CSSToken{CSSTokenType::Dimension, -100.0f, "vh"},
CSSToken{CSSTokenType::EndOfFile});
}
TEST(CSSTokenizer, ratio_values) {
EXPECT_TOKENS(
"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, function_values) {
EXPECT_TOKENS(
"blur(50px)",
CSSToken{CSSTokenType::Function, "blur"},
CSSToken{CSSTokenType::Dimension, 50.0f, "px"},
CSSToken{CSSTokenType::CloseParen},
CSSToken{CSSTokenType::EndOfFile});
EXPECT_TOKENS(
"scale3d( 1.2, 150% 0.5) ",
CSSToken{CSSTokenType::Function, "scale3d"},
CSSToken{CSSTokenType::WhiteSpace},
CSSToken{CSSTokenType::Number, 1.2f},
CSSToken{CSSTokenType::Comma},
CSSToken{CSSTokenType::WhiteSpace},
CSSToken{CSSTokenType::Percentage, 150.f},
CSSToken{CSSTokenType::WhiteSpace},
CSSToken{CSSTokenType::Number, 0.5f},
CSSToken{CSSTokenType::CloseParen},
CSSToken{CSSTokenType::WhiteSpace},
CSSToken{CSSTokenType::EndOfFile});
EXPECT_TOKENS(
"blur (50px)",
CSSToken{CSSTokenType::Ident, "blur"},
CSSToken{CSSTokenType::WhiteSpace},
CSSToken{CSSTokenType::OpenParen},
CSSToken{CSSTokenType::Dimension, 50.0f, "px"},
CSSToken{CSSTokenType::CloseParen},
CSSToken{CSSTokenType::EndOfFile});
}
TEST(CSSTokenizer, invalid_values) {
EXPECT_TOKENS(
"100*",
CSSToken{CSSTokenType::Number, 100.0f},
CSSToken{CSSTokenType::Delim, "*"},
CSSToken{CSSTokenType::EndOfFile});
EXPECT_TOKENS(
"+",
CSSToken{CSSTokenType::Delim, "+"},
CSSToken{CSSTokenType::EndOfFile});
EXPECT_TOKENS(
"(%",
CSSToken{CSSTokenType::OpenParen},
CSSToken{CSSTokenType::Delim, "%"},
CSSToken{CSSTokenType::EndOfFile});
}
TEST(CSSTokenizer, hash_values) {
EXPECT_TOKENS(
"#Ff03BC",
CSSToken{CSSTokenType::Hash, "Ff03BC"},
CSSToken{CSSTokenType::EndOfFile});
EXPECT_TOKENS(
"#identifier",
CSSToken{CSSTokenType::Hash, "identifier"},
CSSToken{CSSTokenType::EndOfFile});
EXPECT_TOKENS(
"#*",
CSSToken{CSSTokenType::Delim, "#"},
CSSToken{CSSTokenType::Delim, "*"},
CSSToken{CSSTokenType::EndOfFile});
}
} // namespace facebook::react

View File

@@ -0,0 +1,428 @@
/*
* 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 <gtest/gtest.h>
#include <react/renderer/css/CSSRatio.h>
#include <react/renderer/css/CSSTransformOrigin.h>
namespace facebook::react {
TEST(CSSTransformOrigin, empty) {
auto emptyValue = parseCSSProperty<CSSTransformOrigin>("");
EXPECT_TRUE(std::holds_alternative<std::monostate>(emptyValue));
}
TEST(CSSTransformOrigin, single_keywords) {
auto left = parseCSSProperty<CSSTransformOrigin>("left");
EXPECT_TRUE(std::holds_alternative<CSSTransformOrigin>(left));
auto& leftOrigin = std::get<CSSTransformOrigin>(left);
EXPECT_TRUE(std::holds_alternative<CSSPercentage>(leftOrigin.x));
EXPECT_EQ(std::get<CSSPercentage>(leftOrigin.x).value, 0.0f);
EXPECT_TRUE(std::holds_alternative<CSSPercentage>(leftOrigin.y));
EXPECT_EQ(std::get<CSSPercentage>(leftOrigin.y).value, 50.0f);
EXPECT_EQ(leftOrigin.z, CSSLength{});
auto right = parseCSSProperty<CSSTransformOrigin>("right");
EXPECT_TRUE(std::holds_alternative<CSSTransformOrigin>(right));
auto& rightOrigin = std::get<CSSTransformOrigin>(right);
EXPECT_TRUE(std::holds_alternative<CSSPercentage>(rightOrigin.x));
EXPECT_EQ(std::get<CSSPercentage>(rightOrigin.x).value, 100.0f);
EXPECT_TRUE(std::holds_alternative<CSSPercentage>(rightOrigin.y));
EXPECT_EQ(std::get<CSSPercentage>(rightOrigin.y).value, 50.0f);
EXPECT_EQ(rightOrigin.z, CSSLength{});
auto top = parseCSSProperty<CSSTransformOrigin>("top");
EXPECT_TRUE(std::holds_alternative<CSSTransformOrigin>(top));
auto& topOrigin = std::get<CSSTransformOrigin>(top);
EXPECT_TRUE(std::holds_alternative<CSSPercentage>(topOrigin.x));
EXPECT_EQ(std::get<CSSPercentage>(topOrigin.x).value, 50.0f);
EXPECT_TRUE(std::holds_alternative<CSSPercentage>(topOrigin.y));
EXPECT_EQ(std::get<CSSPercentage>(topOrigin.y).value, 0.0f);
EXPECT_EQ(topOrigin.z, CSSLength{});
auto bottom = parseCSSProperty<CSSTransformOrigin>("bottom");
EXPECT_TRUE(std::holds_alternative<CSSTransformOrigin>(bottom));
auto& bottomOrigin = std::get<CSSTransformOrigin>(bottom);
EXPECT_TRUE(std::holds_alternative<CSSPercentage>(bottomOrigin.x));
EXPECT_EQ(std::get<CSSPercentage>(bottomOrigin.x).value, 50.0f);
EXPECT_TRUE(std::holds_alternative<CSSPercentage>(bottomOrigin.y));
EXPECT_EQ(std::get<CSSPercentage>(bottomOrigin.y).value, 100.0f);
EXPECT_EQ(bottomOrigin.z, CSSLength{});
auto center = parseCSSProperty<CSSTransformOrigin>("center");
EXPECT_TRUE(std::holds_alternative<CSSTransformOrigin>(center));
auto& centerOrigin = std::get<CSSTransformOrigin>(center);
EXPECT_TRUE(std::holds_alternative<CSSPercentage>(centerOrigin.x));
EXPECT_EQ(std::get<CSSPercentage>(centerOrigin.x).value, 50.0f);
EXPECT_TRUE(std::holds_alternative<CSSPercentage>(centerOrigin.y));
EXPECT_EQ(std::get<CSSPercentage>(centerOrigin.y).value, 50.0f);
EXPECT_EQ(centerOrigin.z, CSSLength{});
}
TEST(CSSTransformOrigin, single_length) {
auto val = parseCSSProperty<CSSTransformOrigin>("500px");
EXPECT_TRUE(std::holds_alternative<CSSTransformOrigin>(val));
auto& origin = std::get<CSSTransformOrigin>(val);
EXPECT_TRUE(std::holds_alternative<CSSLength>(origin.x));
EXPECT_EQ(std::get<CSSLength>(origin.x).value, 500.0f);
EXPECT_EQ(std::get<CSSLength>(origin.x).unit, CSSLengthUnit::Px);
EXPECT_TRUE(std::holds_alternative<CSSPercentage>(origin.y));
EXPECT_EQ(std::get<CSSPercentage>(origin.y).value, 50.0f);
EXPECT_EQ(origin.z, CSSLength{});
}
TEST(CSSTransformOrigin, single_percentage) {
auto val = parseCSSProperty<CSSTransformOrigin>("9000%");
EXPECT_TRUE(std::holds_alternative<CSSTransformOrigin>(val));
auto& origin = std::get<CSSTransformOrigin>(val);
EXPECT_TRUE(std::holds_alternative<CSSPercentage>(origin.x));
EXPECT_EQ(std::get<CSSPercentage>(origin.x).value, 9000.0f);
EXPECT_TRUE(std::holds_alternative<CSSPercentage>(origin.y));
EXPECT_EQ(std::get<CSSPercentage>(origin.y).value, 50.0f);
EXPECT_EQ(origin.z, CSSLength{});
}
TEST(CSSTransformOrigin, left_top) {
auto val = parseCSSProperty<CSSTransformOrigin>("left top");
EXPECT_TRUE(std::holds_alternative<CSSTransformOrigin>(val));
auto& origin = std::get<CSSTransformOrigin>(val);
EXPECT_TRUE(std::holds_alternative<CSSPercentage>(origin.x));
EXPECT_EQ(std::get<CSSPercentage>(origin.x).value, 0.0f);
EXPECT_TRUE(std::holds_alternative<CSSPercentage>(origin.y));
EXPECT_EQ(std::get<CSSPercentage>(origin.y).value, 0.0f);
EXPECT_EQ(origin.z, CSSLength{});
}
TEST(CSSTransformOrigin, top_left) {
auto val = parseCSSProperty<CSSTransformOrigin>("top left");
EXPECT_TRUE(std::holds_alternative<CSSTransformOrigin>(val));
auto& origin = std::get<CSSTransformOrigin>(val);
EXPECT_TRUE(std::holds_alternative<CSSPercentage>(origin.x));
EXPECT_EQ(std::get<CSSPercentage>(origin.x).value, 0.0f);
EXPECT_TRUE(std::holds_alternative<CSSPercentage>(origin.y));
EXPECT_EQ(std::get<CSSPercentage>(origin.y).value, 0.0f);
EXPECT_EQ(origin.z, CSSLength{});
}
TEST(CSSTransformOrigin, right_top) {
auto val = parseCSSProperty<CSSTransformOrigin>("right top");
EXPECT_TRUE(std::holds_alternative<CSSTransformOrigin>(val));
auto& origin = std::get<CSSTransformOrigin>(val);
EXPECT_TRUE(std::holds_alternative<CSSPercentage>(origin.x));
EXPECT_EQ(std::get<CSSPercentage>(origin.x).value, 100.0f);
EXPECT_TRUE(std::holds_alternative<CSSPercentage>(origin.y));
EXPECT_EQ(std::get<CSSPercentage>(origin.y).value, 0.0f);
EXPECT_EQ(origin.z, CSSLength{});
}
TEST(CSSTransformOrigin, center_center) {
auto val = parseCSSProperty<CSSTransformOrigin>("center center");
EXPECT_TRUE(std::holds_alternative<CSSTransformOrigin>(val));
auto& origin = std::get<CSSTransformOrigin>(val);
EXPECT_TRUE(std::holds_alternative<CSSPercentage>(origin.x));
EXPECT_EQ(std::get<CSSPercentage>(origin.x).value, 50.0f);
EXPECT_TRUE(std::holds_alternative<CSSPercentage>(origin.y));
EXPECT_EQ(std::get<CSSPercentage>(origin.y).value, 50.0f);
EXPECT_EQ(origin.z, CSSLength{});
}
TEST(CSSTransformOrigin, center_left) {
auto val = parseCSSProperty<CSSTransformOrigin>("center left");
EXPECT_TRUE(std::holds_alternative<CSSTransformOrigin>(val));
auto& origin = std::get<CSSTransformOrigin>(val);
EXPECT_TRUE(std::holds_alternative<CSSPercentage>(origin.x));
EXPECT_EQ(std::get<CSSPercentage>(origin.x).value, 0.0f);
EXPECT_TRUE(std::holds_alternative<CSSPercentage>(origin.y));
EXPECT_EQ(std::get<CSSPercentage>(origin.y).value, 50.0f);
EXPECT_EQ(origin.z, CSSLength{});
}
TEST(CSSTransformOrigin, left_bottom) {
auto val = parseCSSProperty<CSSTransformOrigin>("left bottom");
EXPECT_TRUE(std::holds_alternative<CSSTransformOrigin>(val));
auto& origin = std::get<CSSTransformOrigin>(val);
EXPECT_TRUE(std::holds_alternative<CSSPercentage>(origin.x));
EXPECT_EQ(std::get<CSSPercentage>(origin.x).value, 0.0f);
EXPECT_TRUE(std::holds_alternative<CSSPercentage>(origin.y));
EXPECT_EQ(std::get<CSSPercentage>(origin.y).value, 100.0f);
EXPECT_EQ(origin.z, CSSLength{});
}
TEST(CSSTransformOrigin, bottom_left) {
auto val = parseCSSProperty<CSSTransformOrigin>("bottom left");
EXPECT_TRUE(std::holds_alternative<CSSTransformOrigin>(val));
auto& origin = std::get<CSSTransformOrigin>(val);
EXPECT_TRUE(std::holds_alternative<CSSPercentage>(origin.x));
EXPECT_EQ(std::get<CSSPercentage>(origin.x).value, 0.0f);
EXPECT_TRUE(std::holds_alternative<CSSPercentage>(origin.y));
EXPECT_EQ(std::get<CSSPercentage>(origin.y).value, 100.0f);
EXPECT_EQ(origin.z, CSSLength{});
}
TEST(CSSTransformOrigin, bottom_bottom) {
auto val = parseCSSProperty<CSSTransformOrigin>("bottom bottom");
EXPECT_TRUE(std::holds_alternative<std::monostate>(val));
}
TEST(CSSTransformOrigin, right_right) {
auto val = parseCSSProperty<CSSTransformOrigin>("right right");
EXPECT_TRUE(std::holds_alternative<std::monostate>(val));
}
TEST(CSSTransformOrigin, center_left_length) {
auto val = parseCSSProperty<CSSTransformOrigin>("center left 500px");
EXPECT_TRUE(std::holds_alternative<CSSTransformOrigin>(val));
auto& origin = std::get<CSSTransformOrigin>(val);
EXPECT_TRUE(std::holds_alternative<CSSPercentage>(origin.x));
EXPECT_EQ(std::get<CSSPercentage>(origin.x).value, 0.0f);
EXPECT_TRUE(std::holds_alternative<CSSPercentage>(origin.y));
EXPECT_EQ(std::get<CSSPercentage>(origin.y).value, 50.0f);
EXPECT_EQ(origin.z.value, 500.0f);
EXPECT_EQ(origin.z.unit, CSSLengthUnit::Px);
}
TEST(CSSTransformOrigin, center_left_percentage) {
auto val = parseCSSProperty<CSSTransformOrigin>("center left 9000%");
EXPECT_TRUE(std::holds_alternative<std::monostate>(val));
}
TEST(CSSTransformOrigin, pct_center) {
auto val = parseCSSProperty<CSSTransformOrigin>("9000% center");
EXPECT_TRUE(std::holds_alternative<CSSTransformOrigin>(val));
auto& origin = std::get<CSSTransformOrigin>(val);
EXPECT_TRUE(std::holds_alternative<CSSPercentage>(origin.x));
EXPECT_EQ(std::get<CSSPercentage>(origin.x).value, 9000.0f);
EXPECT_TRUE(std::holds_alternative<CSSPercentage>(origin.y));
EXPECT_EQ(std::get<CSSPercentage>(origin.y).value, 50.0f);
EXPECT_EQ(origin.z, CSSLength{});
}
TEST(CSSTransformOrigin, length_center) {
auto val = parseCSSProperty<CSSTransformOrigin>("500px center");
EXPECT_TRUE(std::holds_alternative<CSSTransformOrigin>(val));
auto& origin = std::get<CSSTransformOrigin>(val);
EXPECT_TRUE(std::holds_alternative<CSSLength>(origin.x));
EXPECT_EQ(std::get<CSSLength>(origin.x).value, 500.0f);
EXPECT_EQ(std::get<CSSLength>(origin.x).unit, CSSLengthUnit::Px);
EXPECT_TRUE(std::holds_alternative<CSSPercentage>(origin.y));
EXPECT_EQ(std::get<CSSPercentage>(origin.y).value, 50.0f);
EXPECT_EQ(origin.z, CSSLength{});
}
TEST(CSSTransformOrigin, length_top) {
auto val = parseCSSProperty<CSSTransformOrigin>("500px top");
EXPECT_TRUE(std::holds_alternative<CSSTransformOrigin>(val));
auto& origin = std::get<CSSTransformOrigin>(val);
EXPECT_TRUE(std::holds_alternative<CSSLength>(origin.x));
EXPECT_EQ(std::get<CSSLength>(origin.x).value, 500.0f);
EXPECT_EQ(std::get<CSSLength>(origin.x).unit, CSSLengthUnit::Px);
EXPECT_TRUE(std::holds_alternative<CSSPercentage>(origin.y));
EXPECT_EQ(std::get<CSSPercentage>(origin.y).value, 0.0f);
EXPECT_EQ(origin.z, CSSLength{});
}
TEST(CSSTransformOrigin, length_left) {
auto val = parseCSSProperty<CSSTransformOrigin>("500px left");
EXPECT_TRUE(std::holds_alternative<std::monostate>(val));
}
TEST(CSSTransformOrigin, length_bottom_length) {
auto val = parseCSSProperty<CSSTransformOrigin>("500px bottom 500px");
EXPECT_TRUE(std::holds_alternative<CSSTransformOrigin>(val));
auto& origin = std::get<CSSTransformOrigin>(val);
EXPECT_TRUE(std::holds_alternative<CSSLength>(origin.x));
EXPECT_EQ(std::get<CSSLength>(origin.x).value, 500.0f);
EXPECT_EQ(std::get<CSSLength>(origin.x).unit, CSSLengthUnit::Px);
EXPECT_TRUE(std::holds_alternative<CSSPercentage>(origin.y));
EXPECT_EQ(std::get<CSSPercentage>(origin.y).value, 100.0f);
EXPECT_EQ(origin.z.value, 500.0f);
EXPECT_EQ(origin.z.unit, CSSLengthUnit::Px);
}
TEST(CSSTransformOrigin, length_right_percentage) {
auto val = parseCSSProperty<CSSTransformOrigin>("500px right 9000%");
EXPECT_TRUE(std::holds_alternative<std::monostate>(val));
}
TEST(CSSTransformOrigin, length_length) {
auto val = parseCSSProperty<CSSTransformOrigin>("500px 600px");
EXPECT_TRUE(std::holds_alternative<CSSTransformOrigin>(val));
auto& origin = std::get<CSSTransformOrigin>(val);
EXPECT_TRUE(std::holds_alternative<CSSLength>(origin.x));
EXPECT_EQ(std::get<CSSLength>(origin.x).value, 500.0f);
EXPECT_EQ(std::get<CSSLength>(origin.x).unit, CSSLengthUnit::Px);
EXPECT_TRUE(std::holds_alternative<CSSLength>(origin.y));
EXPECT_EQ(std::get<CSSLength>(origin.y).value, 600.0f);
EXPECT_EQ(std::get<CSSLength>(origin.y).unit, CSSLengthUnit::Px);
EXPECT_EQ(origin.z, CSSLength{});
}
TEST(CSSTransformOrigin, length_percentage) {
auto val = parseCSSProperty<CSSTransformOrigin>("500px 9000%");
EXPECT_TRUE(std::holds_alternative<CSSTransformOrigin>(val));
auto& origin = std::get<CSSTransformOrigin>(val);
EXPECT_TRUE(std::holds_alternative<CSSLength>(origin.x));
EXPECT_EQ(std::get<CSSLength>(origin.x).value, 500.0f);
EXPECT_EQ(std::get<CSSLength>(origin.x).unit, CSSLengthUnit::Px);
EXPECT_TRUE(std::holds_alternative<CSSPercentage>(origin.y));
EXPECT_EQ(std::get<CSSPercentage>(origin.y).value, 9000.0f);
EXPECT_EQ(origin.z, CSSLength{});
}
TEST(CSSTransformOrigin, percentage_length) {
auto val = parseCSSProperty<CSSTransformOrigin>("9000% 500px");
EXPECT_TRUE(std::holds_alternative<CSSTransformOrigin>(val));
auto& origin = std::get<CSSTransformOrigin>(val);
EXPECT_TRUE(std::holds_alternative<CSSPercentage>(origin.x));
EXPECT_EQ(std::get<CSSPercentage>(origin.x).value, 9000.0f);
EXPECT_TRUE(std::holds_alternative<CSSLength>(origin.y));
EXPECT_EQ(std::get<CSSLength>(origin.y).value, 500.0f);
EXPECT_EQ(std::get<CSSLength>(origin.y).unit, CSSLengthUnit::Px);
EXPECT_EQ(origin.z, CSSLength{});
}
TEST(CSSTransformOrigin, percentage_right) {
auto val = parseCSSProperty<CSSTransformOrigin>("9000% right");
EXPECT_TRUE(std::holds_alternative<std::monostate>(val));
}
TEST(CSSTransformOrigin, percentage_bottom) {
auto val = parseCSSProperty<CSSTransformOrigin>("9000% bottom");
EXPECT_TRUE(std::holds_alternative<CSSTransformOrigin>(val));
auto& origin = std::get<CSSTransformOrigin>(val);
EXPECT_TRUE(std::holds_alternative<CSSPercentage>(origin.x));
EXPECT_EQ(std::get<CSSPercentage>(origin.x).value, 9000.0f);
EXPECT_TRUE(std::holds_alternative<CSSPercentage>(origin.y));
EXPECT_EQ(std::get<CSSPercentage>(origin.y).value, 100.0f);
EXPECT_EQ(origin.z, CSSLength{});
}
TEST(CSSTransformOrigin, percentage_left) {
auto val = parseCSSProperty<CSSTransformOrigin>("9000% left");
EXPECT_TRUE(std::holds_alternative<std::monostate>(val));
}
TEST(CSSTransformOrigin, bottom_percentage) {
auto val = parseCSSProperty<CSSTransformOrigin>("bottom 9000%");
EXPECT_TRUE(std::holds_alternative<std::monostate>(val));
}
TEST(CSSTransformOrigin, center_percentage) {
auto val = parseCSSProperty<CSSTransformOrigin>("center 9000%");
EXPECT_TRUE(std::holds_alternative<CSSTransformOrigin>(val));
auto& origin = std::get<CSSTransformOrigin>(val);
EXPECT_TRUE(std::holds_alternative<CSSPercentage>(origin.x));
EXPECT_EQ(std::get<CSSPercentage>(origin.x).value, 50.0f);
EXPECT_TRUE(std::holds_alternative<CSSPercentage>(origin.y));
EXPECT_EQ(std::get<CSSPercentage>(origin.y).value, 9000.0f);
EXPECT_EQ(origin.z, CSSLength{});
}
TEST(CSSTransformOrigin, right_percentage) {
auto val = parseCSSProperty<CSSTransformOrigin>("right 9000%");
EXPECT_TRUE(std::holds_alternative<CSSTransformOrigin>(val));
auto& origin = std::get<CSSTransformOrigin>(val);
EXPECT_TRUE(std::holds_alternative<CSSPercentage>(origin.x));
EXPECT_EQ(std::get<CSSPercentage>(origin.x).value, 100.0f);
EXPECT_TRUE(std::holds_alternative<CSSPercentage>(origin.y));
EXPECT_EQ(std::get<CSSPercentage>(origin.y).value, 9000.0f);
EXPECT_EQ(origin.z, CSSLength{});
}
TEST(CSSTransformOrigin, percentage_percentage) {
auto val = parseCSSProperty<CSSTransformOrigin>("9000% 9001%");
EXPECT_TRUE(std::holds_alternative<CSSTransformOrigin>(val));
auto& origin = std::get<CSSTransformOrigin>(val);
EXPECT_TRUE(std::holds_alternative<CSSPercentage>(origin.x));
EXPECT_EQ(std::get<CSSPercentage>(origin.x).value, 9000.0f);
EXPECT_TRUE(std::holds_alternative<CSSPercentage>(origin.y));
EXPECT_EQ(std::get<CSSPercentage>(origin.y).value, 9001.0f);
EXPECT_EQ(origin.z, CSSLength{});
}
} // namespace facebook::react

View File

@@ -0,0 +1,701 @@
/*
* 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 <gtest/gtest.h>
#include <react/renderer/css/CSSTransform.h>
#include <react/renderer/css/CSSValueParser.h>
namespace facebook::react {
TEST(CSSTransform, matrix_basic) {
auto val = parseCSSProperty<CSSTransformFunction>("matrix(1, 2, 3, 4, 5, 6)");
EXPECT_TRUE(std::holds_alternative<CSSMatrix>(val));
auto& matrix = std::get<CSSMatrix>(val);
EXPECT_EQ(matrix.values[0], 1.0f);
EXPECT_EQ(matrix.values[1], 2.0f);
EXPECT_EQ(matrix.values[2], 3.0f);
EXPECT_EQ(matrix.values[3], 4.0f);
EXPECT_EQ(matrix.values[4], 5.0f);
EXPECT_EQ(matrix.values[5], 6.0f);
}
TEST(CSSTransform, matrix_funky) {
auto val =
parseCSSProperty<CSSTransformFunction>("mAtRiX( 1 , \n2,3, 4, \t5, 6)");
EXPECT_TRUE(std::holds_alternative<CSSMatrix>(val));
auto& matrix = std::get<CSSMatrix>(val);
EXPECT_EQ(matrix.values[0], 1.0f);
EXPECT_EQ(matrix.values[1], 2.0f);
EXPECT_EQ(matrix.values[2], 3.0f);
EXPECT_EQ(matrix.values[3], 4.0f);
EXPECT_EQ(matrix.values[4], 5.0f);
EXPECT_EQ(matrix.values[5], 6.0f);
}
TEST(CSSTransform, matrix_missing_elements) {
auto val = parseCSSProperty<CSSTransformFunction>("matrix(1, 2, 3, 4, 5)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(val));
}
TEST(CSSTransform, matrix_extra_elements) {
auto val =
parseCSSProperty<CSSTransformFunction>("matrix(1, 2, 3, 4, 5, 6, 7)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(val));
}
TEST(CSSTransform, matrix_pct) {
auto val =
parseCSSProperty<CSSTransformFunction>("matrix(1, 2%, 3, 4, 5, 6)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(val));
}
TEST(CSSTransform, translate_basic) {
auto val = parseCSSProperty<CSSTransformFunction>("translate(4rem, 20%)");
EXPECT_TRUE(std::holds_alternative<CSSTranslate>(val));
auto& translate = std::get<CSSTranslate>(val);
EXPECT_TRUE(std::holds_alternative<CSSLength>(translate.x));
EXPECT_EQ(std::get<CSSLength>(translate.x).value, 4.0f);
EXPECT_EQ(std::get<CSSLength>(translate.x).unit, CSSLengthUnit::Rem);
EXPECT_TRUE(std::holds_alternative<CSSPercentage>(translate.y));
EXPECT_EQ(std::get<CSSPercentage>(translate.y).value, 20.0f);
}
TEST(CSSTransform, translate_funky) {
auto val =
parseCSSProperty<CSSTransformFunction>("traNslAte( 4rem, \n20% )");
EXPECT_TRUE(std::holds_alternative<CSSTranslate>(val));
auto& translate = std::get<CSSTranslate>(val);
EXPECT_TRUE(std::holds_alternative<CSSLength>(translate.x));
EXPECT_EQ(std::get<CSSLength>(translate.x).value, 4.0f);
EXPECT_EQ(std::get<CSSLength>(translate.x).unit, CSSLengthUnit::Rem);
EXPECT_TRUE(std::holds_alternative<CSSPercentage>(translate.y));
EXPECT_EQ(std::get<CSSPercentage>(translate.y).value, 20.0f);
}
TEST(CSSTransform, translate_default_y) {
auto val = parseCSSProperty<CSSTransformFunction>("translate(4rem)");
EXPECT_TRUE(std::holds_alternative<CSSTranslate>(val));
auto& translate = std::get<CSSTranslate>(val);
EXPECT_TRUE(std::holds_alternative<CSSLength>(translate.x));
EXPECT_EQ(std::get<CSSLength>(translate.x).value, 4.0f);
EXPECT_EQ(std::get<CSSLength>(translate.x).unit, CSSLengthUnit::Rem);
EXPECT_TRUE(std::holds_alternative<CSSLength>(translate.y));
EXPECT_EQ(std::get<CSSLength>(translate.y).value, 0.0f);
EXPECT_EQ(std::get<CSSLength>(translate.y).unit, CSSLengthUnit::Px);
}
TEST(CSSTransform, translate_missing_value) {
auto val = parseCSSProperty<CSSTransformFunction>("translate()");
EXPECT_TRUE(std::holds_alternative<std::monostate>(val));
}
TEST(CSSTransform, translate_extra_value) {
auto val =
parseCSSProperty<CSSTransformFunction>("translate(10px, 2px, 5px)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(val));
}
TEST(CSSTransform, translate_number) {
auto val = parseCSSProperty<CSSTransformFunction>("translate(5)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(val));
}
TEST(CSSTransform, translate3d_basic) {
auto val =
parseCSSProperty<CSSTransformFunction>("translate3d(4rem, 20%, 2px)");
EXPECT_TRUE(std::holds_alternative<CSSTranslate3D>(val));
auto& translate = std::get<CSSTranslate3D>(val);
EXPECT_TRUE(std::holds_alternative<CSSLength>(translate.x));
EXPECT_EQ(std::get<CSSLength>(translate.x).value, 4.0f);
EXPECT_EQ(std::get<CSSLength>(translate.x).unit, CSSLengthUnit::Rem);
EXPECT_TRUE(std::holds_alternative<CSSPercentage>(translate.y));
EXPECT_EQ(std::get<CSSPercentage>(translate.y).value, 20.0f);
EXPECT_EQ(translate.z.value, 2.0f);
EXPECT_EQ(translate.z.unit, CSSLengthUnit::Px);
}
TEST(CSSTransform, translate3d_funky) {
auto val = parseCSSProperty<CSSTransformFunction>(
"translAte3D( 4rem , 20% , 2px)");
EXPECT_TRUE(std::holds_alternative<CSSTranslate3D>(val));
auto& translate = std::get<CSSTranslate3D>(val);
EXPECT_TRUE(std::holds_alternative<CSSLength>(translate.x));
EXPECT_EQ(std::get<CSSLength>(translate.x).value, 4.0f);
EXPECT_EQ(std::get<CSSLength>(translate.x).unit, CSSLengthUnit::Rem);
EXPECT_TRUE(std::holds_alternative<CSSPercentage>(translate.y));
EXPECT_EQ(std::get<CSSPercentage>(translate.y).value, 20.0f);
EXPECT_EQ(translate.z.value, 2.0f);
EXPECT_EQ(translate.z.unit, CSSLengthUnit::Px);
}
TEST(CSSTransform, translate3d_missing_value) {
auto val = parseCSSProperty<CSSTransformFunction>("translate3d(4rem, 20%)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(val));
}
TEST(CSSTransform, translate3d_extra_value) {
auto val =
parseCSSProperty<CSSTransformFunction>("ranslate3d(4rem, 20%, 2px, 6in)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(val));
}
TEST(CSSTransform, translate3d_numbers) {
auto val = parseCSSProperty<CSSTransformFunction>("ranslate3d(4, 20, 2)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(val));
}
TEST(CSSTransform, translate_x_length) {
auto val = parseCSSProperty<CSSTransformFunction>("translateX(900pt)");
EXPECT_TRUE(std::holds_alternative<CSSTranslateX>(val));
auto& translate = std::get<CSSTranslateX>(val);
EXPECT_TRUE(std::holds_alternative<CSSLength>(translate.value));
EXPECT_EQ(std::get<CSSLength>(translate.value).value, 900.0f);
EXPECT_EQ(std::get<CSSLength>(translate.value).unit, CSSLengthUnit::Pt);
}
TEST(CSSTransform, translate_x_pct) {
auto val = parseCSSProperty<CSSTransformFunction>("translateX(420%)");
EXPECT_TRUE(std::holds_alternative<CSSTranslateX>(val));
auto& translate = std::get<CSSTranslateX>(val);
EXPECT_TRUE(std::holds_alternative<CSSPercentage>(translate.value));
EXPECT_EQ(std::get<CSSPercentage>(translate.value).value, 420.0f);
}
TEST(CSSTransform, translate_x_funky) {
auto val = parseCSSProperty<CSSTransformFunction>("transLaTeX(420%)");
EXPECT_TRUE(std::holds_alternative<CSSTranslateX>(val));
auto& translate = std::get<CSSTranslateX>(val);
EXPECT_TRUE(std::holds_alternative<CSSPercentage>(translate.value));
EXPECT_EQ(std::get<CSSPercentage>(translate.value).value, 420.0f);
}
TEST(CSSTransform, translate_x_missing_value) {
auto val = parseCSSProperty<CSSTransformFunction>("transLaTeX()");
EXPECT_TRUE(std::holds_alternative<std::monostate>(val));
}
TEST(CSSTransform, translate_x_extra_value) {
auto val = parseCSSProperty<CSSTransformFunction>("transLaTeX(123cm, 45px)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(val));
}
TEST(CSSTransform, translate_x_number) {
auto val = parseCSSProperty<CSSTransformFunction>("transLaTeX(456)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(val));
}
TEST(CSSTransform, translate_y_length) {
auto val = parseCSSProperty<CSSTransformFunction>("translateY(900pt)");
EXPECT_TRUE(std::holds_alternative<CSSTranslateY>(val));
auto& translate = std::get<CSSTranslateY>(val);
EXPECT_TRUE(std::holds_alternative<CSSLength>(translate.value));
EXPECT_EQ(std::get<CSSLength>(translate.value).value, 900.0f);
EXPECT_EQ(std::get<CSSLength>(translate.value).unit, CSSLengthUnit::Pt);
}
TEST(CSSTransform, translate_y_pct) {
auto val = parseCSSProperty<CSSTransformFunction>("translateY(420%)");
EXPECT_TRUE(std::holds_alternative<CSSTranslateY>(val));
auto& translate = std::get<CSSTranslateY>(val);
EXPECT_TRUE(std::holds_alternative<CSSPercentage>(translate.value));
EXPECT_EQ(std::get<CSSPercentage>(translate.value).value, 420.0f);
}
TEST(CSSTransform, translate_y_funky) {
auto val = parseCSSProperty<CSSTransformFunction>("transLaTeY(420%)");
EXPECT_TRUE(std::holds_alternative<CSSTranslateY>(val));
auto& translate = std::get<CSSTranslateY>(val);
EXPECT_TRUE(std::holds_alternative<CSSPercentage>(translate.value));
EXPECT_EQ(std::get<CSSPercentage>(translate.value).value, 420.0f);
}
TEST(CSSTransform, translate_y_missing_value) {
auto val = parseCSSProperty<CSSTransformFunction>("transLaTeY()");
EXPECT_TRUE(std::holds_alternative<std::monostate>(val));
}
TEST(CSSTransform, translate_y_eytra_value) {
auto val = parseCSSProperty<CSSTransformFunction>("transLaTeY(123cm, 45py)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(val));
}
TEST(CSSTransform, translate_y_number) {
auto val = parseCSSProperty<CSSTransformFunction>("transLaTeY(456)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(val));
}
TEST(CSSTransform, scale_basic) {
auto val = parseCSSProperty<CSSTransformFunction>("scale(0.9, 9001%)");
EXPECT_TRUE(std::holds_alternative<CSSScale>(val));
auto& scale = std::get<CSSScale>(val);
EXPECT_EQ(scale.x, 0.9f);
EXPECT_EQ(scale.y, 90.01f);
}
TEST(CSSTransform, scale_single_value) {
auto val = parseCSSProperty<CSSTransformFunction>("scale(2.0)");
EXPECT_TRUE(std::holds_alternative<CSSScale>(val));
auto& scale = std::get<CSSScale>(val);
EXPECT_EQ(scale.x, 2.0f);
EXPECT_EQ(scale.y, 2.0f);
}
TEST(CSSTransform, scale_funky) {
auto val = parseCSSProperty<CSSTransformFunction>("sCale( 0.9, 9001%)");
EXPECT_TRUE(std::holds_alternative<CSSScale>(val));
auto& scale = std::get<CSSScale>(val);
EXPECT_EQ(scale.x, 0.9f);
EXPECT_EQ(scale.y, 90.01f);
}
TEST(CSSTransform, scale_missing_value) {
auto val = parseCSSProperty<CSSTransformFunction>("scale()");
EXPECT_TRUE(std::holds_alternative<std::monostate>(val));
}
TEST(CSSTransform, scale_extra_value) {
auto val = parseCSSProperty<CSSTransformFunction>("scale(0.9, 9001%, 1.0)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(val));
}
TEST(CSSTransform, scale_length) {
auto val = parseCSSProperty<CSSTransformFunction>("scale(0.9, 9001pt)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(val));
}
TEST(CSSTransform, scale_x_number) {
auto val = parseCSSProperty<CSSTransformFunction>("scaleX(50)");
EXPECT_TRUE(std::holds_alternative<CSSScaleX>(val));
auto& scaleX = std::get<CSSScaleX>(val);
EXPECT_EQ(scaleX.value, 50.0f);
}
TEST(CSSTransform, scale_x_pct) {
auto val = parseCSSProperty<CSSTransformFunction>("scaleX(50%)");
EXPECT_TRUE(std::holds_alternative<CSSScaleX>(val));
auto& scaleX = std::get<CSSScaleX>(val);
EXPECT_EQ(scaleX.value, 0.5f);
}
TEST(CSSTransform, scale_x_funky) {
auto val = parseCSSProperty<CSSTransformFunction>("scaLeX(50%)");
EXPECT_TRUE(std::holds_alternative<CSSScaleX>(val));
auto& scaleX = std::get<CSSScaleX>(val);
EXPECT_EQ(scaleX.value, 0.5f);
}
TEST(CSSTransform, scale_x_missing_value) {
auto val = parseCSSProperty<CSSTransformFunction>("scaLeX()");
EXPECT_TRUE(std::holds_alternative<std::monostate>(val));
}
TEST(CSSTransform, scale_x_extra_value) {
auto val = parseCSSProperty<CSSTransformFunction>("scaLeX(50%, 50%)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(val));
}
TEST(CSSTransform, scale_x_length) {
auto val = parseCSSProperty<CSSTransformFunction>("scaLeX(50pt)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(val));
}
TEST(CSSTransform, scale_y_number) {
auto val = parseCSSProperty<CSSTransformFunction>("scaleY(50)");
EXPECT_TRUE(std::holds_alternative<CSSScaleY>(val));
auto& scaleY = std::get<CSSScaleY>(val);
EXPECT_EQ(scaleY.value, 50.0f);
}
TEST(CSSTransform, scale_y_pct) {
auto val = parseCSSProperty<CSSTransformFunction>("scaleY(50%)");
EXPECT_TRUE(std::holds_alternative<CSSScaleY>(val));
auto& scaleY = std::get<CSSScaleY>(val);
EXPECT_EQ(scaleY.value, 0.5f);
}
TEST(CSSTransform, scale_y_funky) {
auto val = parseCSSProperty<CSSTransformFunction>("scaLeY(50%)");
EXPECT_TRUE(std::holds_alternative<CSSScaleY>(val));
auto& scaleY = std::get<CSSScaleY>(val);
EXPECT_EQ(scaleY.value, 0.5f);
}
TEST(CSSTransform, scale_y_missing_value) {
auto val = parseCSSProperty<CSSTransformFunction>("scaLeY()");
EXPECT_TRUE(std::holds_alternative<std::monostate>(val));
}
TEST(CSSTransform, scale_y_eytra_value) {
auto val = parseCSSProperty<CSSTransformFunction>("scaLeY(50%, 50%)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(val));
}
TEST(CSSTransform, scale_y_length) {
auto val = parseCSSProperty<CSSTransformFunction>("scaLeY(50pt)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(val));
}
TEST(CSSTransform, rotate_basic) {
auto val = parseCSSProperty<CSSTransformFunction>("rotate(90deg)");
EXPECT_TRUE(std::holds_alternative<CSSRotate>(val));
auto& rotate = std::get<CSSRotate>(val);
EXPECT_EQ(rotate.degrees, 90.0f);
}
TEST(CSSTransform, rotate_turn) {
auto val = parseCSSProperty<CSSTransformFunction>("rotate(1turn)");
EXPECT_TRUE(std::holds_alternative<CSSRotate>(val));
auto& rotate = std::get<CSSRotate>(val);
EXPECT_EQ(rotate.degrees, 360.0f);
}
TEST(CSSTransform, rotate_zero) {
auto val = parseCSSProperty<CSSTransformFunction>("rotate(0)");
EXPECT_TRUE(std::holds_alternative<CSSRotate>(val));
auto& rotate = std::get<CSSRotate>(val);
EXPECT_EQ(rotate.degrees, 0.0f);
}
TEST(CSSTransform, rotate_z) {
auto val = parseCSSProperty<CSSTransformFunction>("rotateZ(90deg)");
EXPECT_TRUE(std::holds_alternative<CSSRotateZ>(val));
auto& rotate = std::get<CSSRotateZ>(val);
EXPECT_EQ(rotate.degrees, 90.0f);
}
TEST(CSSTransform, rotate_funky) {
auto val = parseCSSProperty<CSSTransformFunction>("roTate(90deg)");
EXPECT_TRUE(std::holds_alternative<CSSRotate>(val));
auto& rotate = std::get<CSSRotate>(val);
EXPECT_EQ(rotate.degrees, 90.0f);
}
TEST(CSSTransform, rotate_missing_value) {
auto val = parseCSSProperty<CSSTransformFunction>("roTate()");
EXPECT_TRUE(std::holds_alternative<std::monostate>(val));
}
TEST(CSSTransform, rotate_extra_value) {
auto val = parseCSSProperty<CSSTransformFunction>("roTate(90deg, 90deg)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(val));
}
TEST(CSSTransform, rotate_number) {
auto val = parseCSSProperty<CSSTransformFunction>("roTate(90)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(val));
}
TEST(CSSTransform, rotate_x_basic) {
auto val = parseCSSProperty<CSSTransformFunction>("rotateX(90deg)");
EXPECT_TRUE(std::holds_alternative<CSSRotateX>(val));
auto& rotate = std::get<CSSRotateX>(val);
EXPECT_EQ(rotate.degrees, 90.0f);
}
TEST(CSSTransform, rotate_x_turn) {
auto val = parseCSSProperty<CSSTransformFunction>("rotateX(1turn)");
EXPECT_TRUE(std::holds_alternative<CSSRotateX>(val));
auto& rotate = std::get<CSSRotateX>(val);
EXPECT_EQ(rotate.degrees, 360.0f);
}
TEST(CSSTransform, rotate_x_zero) {
auto val = parseCSSProperty<CSSTransformFunction>("rotateX(0)");
EXPECT_TRUE(std::holds_alternative<CSSRotateX>(val));
auto& rotate = std::get<CSSRotateX>(val);
EXPECT_EQ(rotate.degrees, 0.0f);
}
TEST(CSSTransform, rotate_x_funky) {
auto val = parseCSSProperty<CSSTransformFunction>("roTateX(90deg)");
EXPECT_TRUE(std::holds_alternative<CSSRotateX>(val));
auto& rotate = std::get<CSSRotateX>(val);
EXPECT_EQ(rotate.degrees, 90.0f);
}
TEST(CSSTransform, rotate_x_missing_value) {
auto val = parseCSSProperty<CSSTransformFunction>("roTateX()");
EXPECT_TRUE(std::holds_alternative<std::monostate>(val));
}
TEST(CSSTransform, rotate_x_extra_value) {
auto val = parseCSSProperty<CSSTransformFunction>("roTateX(90deg, 90deg)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(val));
}
TEST(CSSTransform, rotate_x_number) {
auto val = parseCSSProperty<CSSTransformFunction>("roTateX(90)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(val));
}
TEST(CSSTransform, rotate_y_basic) {
auto val = parseCSSProperty<CSSTransformFunction>("rotateY(90deg)");
EXPECT_TRUE(std::holds_alternative<CSSRotateY>(val));
auto& rotate = std::get<CSSRotateY>(val);
EXPECT_EQ(rotate.degrees, 90.0f);
}
TEST(CSSTransform, rotate_y_turn) {
auto val = parseCSSProperty<CSSTransformFunction>("rotateY(1turn)");
EXPECT_TRUE(std::holds_alternative<CSSRotateY>(val));
auto& rotate = std::get<CSSRotateY>(val);
EXPECT_EQ(rotate.degrees, 360.0f);
}
TEST(CSSTransform, rotate_y_zero) {
auto val = parseCSSProperty<CSSTransformFunction>("rotateY(0)");
EXPECT_TRUE(std::holds_alternative<CSSRotateY>(val));
auto& rotate = std::get<CSSRotateY>(val);
EXPECT_EQ(rotate.degrees, 0.0f);
}
TEST(CSSTransform, rotate_y_funky) {
auto val = parseCSSProperty<CSSTransformFunction>("roTateY(90deg)");
EXPECT_TRUE(std::holds_alternative<CSSRotateY>(val));
auto& rotate = std::get<CSSRotateY>(val);
EXPECT_EQ(rotate.degrees, 90.0f);
}
TEST(CSSTransform, rotate_y_missing_value) {
auto val = parseCSSProperty<CSSTransformFunction>("roTateY()");
EXPECT_TRUE(std::holds_alternative<std::monostate>(val));
}
TEST(CSSTransform, rotate_y_extra_value) {
auto val = parseCSSProperty<CSSTransformFunction>("roTateY(90deg, 90deg)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(val));
}
TEST(CSSTransform, rotate_y_number) {
auto val = parseCSSProperty<CSSTransformFunction>("roTateY(90)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(val));
}
TEST(CSSTransform, skew_x_basic) {
auto val = parseCSSProperty<CSSTransformFunction>("skewX(90deg)");
EXPECT_TRUE(std::holds_alternative<CSSSkewX>(val));
auto& skew = std::get<CSSSkewX>(val);
EXPECT_EQ(skew.degrees, 90.0f);
}
TEST(CSSTransform, skew_x_turn) {
auto val = parseCSSProperty<CSSTransformFunction>("skewX(1turn)");
EXPECT_TRUE(std::holds_alternative<CSSSkewX>(val));
auto& skew = std::get<CSSSkewX>(val);
EXPECT_EQ(skew.degrees, 360.0f);
}
TEST(CSSTransform, skew_x_zero) {
auto val = parseCSSProperty<CSSTransformFunction>("skewX(0)");
EXPECT_TRUE(std::holds_alternative<CSSSkewX>(val));
auto& skew = std::get<CSSSkewX>(val);
EXPECT_EQ(skew.degrees, 0.0f);
}
TEST(CSSTransform, skew_x_funky) {
auto val = parseCSSProperty<CSSTransformFunction>("skeWx(90deg)");
EXPECT_TRUE(std::holds_alternative<CSSSkewX>(val));
auto& skew = std::get<CSSSkewX>(val);
EXPECT_EQ(skew.degrees, 90.0f);
}
TEST(CSSTransform, skew_x_missing_value) {
auto val = parseCSSProperty<CSSTransformFunction>("skeWx()");
EXPECT_TRUE(std::holds_alternative<std::monostate>(val));
}
TEST(CSSTransform, skew_x_extra_value) {
auto val = parseCSSProperty<CSSTransformFunction>("skeWx(90deg, 90deg)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(val));
}
TEST(CSSTransform, skew_x_number) {
auto val = parseCSSProperty<CSSTransformFunction>("skeWx(90)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(val));
}
TEST(CSSTransform, skew_y_basic) {
auto val = parseCSSProperty<CSSTransformFunction>("skewY(90deg)");
EXPECT_TRUE(std::holds_alternative<CSSSkewY>(val));
auto& skew = std::get<CSSSkewY>(val);
EXPECT_EQ(skew.degrees, 90.0f);
}
TEST(CSSTransform, skew_y_turn) {
auto val = parseCSSProperty<CSSTransformFunction>("skewY(1turn)");
EXPECT_TRUE(std::holds_alternative<CSSSkewY>(val));
auto& skew = std::get<CSSSkewY>(val);
EXPECT_EQ(skew.degrees, 360.0f);
}
TEST(CSSTransform, skew_y_zero) {
auto val = parseCSSProperty<CSSTransformFunction>("skewY(0)");
EXPECT_TRUE(std::holds_alternative<CSSSkewY>(val));
auto& skew = std::get<CSSSkewY>(val);
EXPECT_EQ(skew.degrees, 0.0f);
}
TEST(CSSTransform, skew_y_funky) {
auto val = parseCSSProperty<CSSTransformFunction>("skeWy(90deg)");
EXPECT_TRUE(std::holds_alternative<CSSSkewY>(val));
auto& skew = std::get<CSSSkewY>(val);
EXPECT_EQ(skew.degrees, 90.0f);
}
TEST(CSSTransform, skew_y_missing_value) {
auto val = parseCSSProperty<CSSTransformFunction>("skeWy()");
EXPECT_TRUE(std::holds_alternative<std::monostate>(val));
}
TEST(CSSTransform, skew_y_extra_value) {
auto val = parseCSSProperty<CSSTransformFunction>("skeWy(90deg, 90deg)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(val));
}
TEST(CSSTransform, skew_y_number) {
auto val = parseCSSProperty<CSSTransformFunction>("skeWy(90)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(val));
}
TEST(CSSTransform, perspective_basic) {
auto val = parseCSSProperty<CSSTransformFunction>("perspective(1000px)");
EXPECT_TRUE(std::holds_alternative<CSSPerspective>(val));
auto& perspective = std::get<CSSPerspective>(val);
EXPECT_EQ(perspective.length.value, 1000.0f);
EXPECT_EQ(perspective.length.unit, CSSLengthUnit::Px);
}
TEST(CSSTransform, perspective_zero) {
auto val = parseCSSProperty<CSSTransformFunction>("perspective(0)");
EXPECT_TRUE(std::holds_alternative<CSSPerspective>(val));
auto& perspective = std::get<CSSPerspective>(val);
EXPECT_EQ(perspective.length.value, 0.0f);
EXPECT_EQ(perspective.length.unit, CSSLengthUnit::Px);
}
TEST(CSSTransform, perspective_negative) {
auto val = parseCSSProperty<CSSTransformFunction>("perspective(-1000px)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(val));
}
TEST(CSSTransform, perspective_funky) {
auto val = parseCSSProperty<CSSTransformFunction>("perspectivE(1000px)");
EXPECT_TRUE(std::holds_alternative<CSSPerspective>(val));
auto& perspective = std::get<CSSPerspective>(val);
EXPECT_EQ(perspective.length.value, 1000.0f);
EXPECT_EQ(perspective.length.unit, CSSLengthUnit::Px);
}
TEST(CSSTransform, perspective_missing_value) {
auto val = parseCSSProperty<CSSTransformFunction>("perspectivE()");
EXPECT_TRUE(std::holds_alternative<std::monostate>(val));
}
TEST(CSSTransform, perspective_extra_value) {
auto val =
parseCSSProperty<CSSTransformFunction>("perspectivE(1000px, 1000px)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(val));
}
TEST(CSSTransform, perspective_number) {
auto val = parseCSSProperty<CSSTransformFunction>("perspectivE(1000)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(val));
}
TEST(CSSTransform, transform_list) {
auto val = parseCSSProperty<CSSTransformList>(
"translate(100px, 200px) rotate(90deg) scale(2)");
EXPECT_TRUE(std::holds_alternative<CSSTransformList>(val));
auto& transformList = std::get<CSSTransformList>(val);
EXPECT_EQ(transformList.size(), 3);
EXPECT_TRUE(std::holds_alternative<CSSTranslate>(transformList[0]));
EXPECT_TRUE(std::holds_alternative<CSSRotate>(transformList[1]));
EXPECT_TRUE(std::holds_alternative<CSSScale>(transformList[2]));
auto& translate = std::get<CSSTranslate>(transformList[0]);
EXPECT_TRUE(std::holds_alternative<CSSLength>(translate.x));
EXPECT_TRUE(std::holds_alternative<CSSLength>(translate.y));
EXPECT_EQ(std::get<CSSLength>(translate.x).value, 100.0f);
EXPECT_EQ(std::get<CSSLength>(translate.y).value, 200.0f);
EXPECT_EQ(std::get<CSSLength>(translate.x).unit, CSSLengthUnit::Px);
EXPECT_EQ(std::get<CSSLength>(translate.y).unit, CSSLengthUnit::Px);
auto& rotate = std::get<CSSRotate>(transformList[1]);
EXPECT_EQ(rotate.degrees, 90.0f);
auto& scale = std::get<CSSScale>(transformList[2]);
EXPECT_EQ(scale.x, 2.0f);
EXPECT_EQ(scale.y, 2.0f);
}
TEST(CSSTransform, transform_list_comma_delimeter) {
auto val = parseCSSProperty<CSSTransformList>(
"translate(100px, 200px), rotate(90deg), scale(2)");
EXPECT_TRUE(std::holds_alternative<std::monostate>(val));
}
TEST(CSSTransform, transform_list_empty) {
auto val = parseCSSProperty<CSSTransformList>("");
EXPECT_TRUE(std::holds_alternative<std::monostate>(val));
}
} // namespace facebook::react

View File

@@ -0,0 +1,66 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include <gtest/gtest.h>
#include <react/renderer/css/CSSDataType.h>
#include <react/renderer/css/CSSNumber.h>
#include <react/renderer/css/CSSValueParser.h>
namespace facebook::react {
struct ConsumeDataType {
float number{};
constexpr bool operator==(const ConsumeDataType& other) const = default;
};
template <>
struct CSSDataTypeParser<ConsumeDataType> {
constexpr static std::optional<ConsumeDataType> consume(
CSSSyntaxParser& parser) {
auto val = parseNextCSSValue<CSSNumber>(parser);
if (std::holds_alternative<CSSNumber>(val)) {
return ConsumeDataType{std::get<CSSNumber>(val).value};
}
return {};
}
};
static_assert(CSSDataType<ConsumeDataType>);
TEST(CSSValueParser, consume_multiple_with_delimeter) {
CSSSyntaxParser parser{"1 2, 3, 4 / 5"};
auto next = parseNextCSSValue<ConsumeDataType>(parser);
EXPECT_TRUE(std::holds_alternative<ConsumeDataType>(next));
EXPECT_EQ(std::get<ConsumeDataType>(next).number, 1);
next = parseNextCSSValue<ConsumeDataType>(parser, CSSDelimiter::None);
EXPECT_FALSE(std::holds_alternative<ConsumeDataType>(next));
next = parseNextCSSValue<ConsumeDataType>(parser, CSSDelimiter::Whitespace);
EXPECT_TRUE(std::holds_alternative<ConsumeDataType>(next));
EXPECT_EQ(std::get<ConsumeDataType>(next).number, 2);
next = parseNextCSSValue<ConsumeDataType>(parser, CSSDelimiter::Comma);
EXPECT_TRUE(std::holds_alternative<ConsumeDataType>(next));
EXPECT_EQ(std::get<ConsumeDataType>(next).number, 3);
next = parseNextCSSValue<ConsumeDataType>(parser, CSSDelimiter::Comma);
EXPECT_TRUE(std::holds_alternative<ConsumeDataType>(next));
EXPECT_EQ(std::get<ConsumeDataType>(next).number, 4);
next = parseNextCSSValue<ConsumeDataType>(parser, CSSDelimiter::Solidus);
EXPECT_TRUE(std::holds_alternative<ConsumeDataType>(next));
EXPECT_EQ(std::get<ConsumeDataType>(next).number, 5);
next = parseNextCSSValue<ConsumeDataType>(parser);
EXPECT_FALSE(std::holds_alternative<ConsumeDataType>(next));
}
} // namespace facebook::react