Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
337 changes: 283 additions & 54 deletions packages/react-native/ReactCommon/react/renderer/css/CSSColorFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,18 @@
#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 {
Expand All @@ -33,81 +36,307 @@ constexpr uint8_t clamp255Component(float f) {
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)),
};
}

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.
* Some invalid syntax (like mixing commas and whitespace) are allowed for
* backwards compatibility with normalize-color.
* https://www.w3.org/TR/css-color-4/#funcdef-rgb
*/
template <typename CSSColor>
constexpr std::optional<CSSColor> parseRgbFunction(CSSSyntaxParser& parser) {
auto firstValue = parseNextCSSValue<CSSNumber, CSSPercentage>(parser);
if (std::holds_alternative<std::monostate>(firstValue)) {
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 {};
}

float redNumber = 0;
float greenNumber = 0;
float blueNumber = 0;
auto s = normalizeComponent(
parseNextCSSValue<CSSPercentage>(parser, CSSDelimiter::Comma), 100.0f);
if (!s.has_value()) {
return {};
}

if (std::holds_alternative<CSSNumber>(firstValue)) {
redNumber = std::get<CSSNumber>(firstValue).value;
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 green = parseNextCSSValue<CSSNumber>(
parser, CSSComponentValueDelimiter::CommaOrWhitespace);
if (!std::holds_alternative<CSSNumber>(green)) {
return {};
}
greenNumber = std::get<CSSNumber>(green).value;
auto [r, g, b] = hslToRgb(*h, *s, *l);

auto blue = parseNextCSSValue<CSSNumber>(
parser, CSSComponentValueDelimiter::CommaOrWhitespace);
if (!std::holds_alternative<CSSNumber>(blue)) {
return {};
}
blueNumber = std::get<CSSNumber>(blue).value;
} else {
redNumber = std::get<CSSPercentage>(firstValue).value * 2.55f;
return CSSColor{
.r = r,
.g = g,
.b = b,
.a = clampAlpha(a),
};
}

auto green = parseNextCSSValue<CSSPercentage>(
parser, CSSComponentValueDelimiter::CommaOrWhitespace);
if (!std::holds_alternative<CSSPercentage>(green)) {
return {};
}
greenNumber = std::get<CSSPercentage>(green).value * 2.55f;
/**
* 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 blue = parseNextCSSValue<CSSPercentage>(
parser, CSSComponentValueDelimiter::CommaOrWhitespace);
if (!std::holds_alternative<CSSPercentage>(blue)) {
return {};
}
blueNumber = std::get<CSSPercentage>(blue).value * 2.55f;
auto s = normalizeComponent(
parseNextCSSValue<CSSNumber, CSSPercentage>(
parser, CSSDelimiter::Whitespace),
100.0f);
if (!s.has_value()) {
return {};
}

auto alphaValue = peekNextCSSValue<CSSNumber, CSSPercentage>(
parser, CSSComponentValueDelimiter::CommaOrWhitespace);
if (!std::holds_alternative<std::monostate>(alphaValue)) {
parser.consumeComponentValue(CSSComponentValueDelimiter::CommaOrWhitespace);
} else {
alphaValue = peekNextCSSValue<CSSNumber, CSSPercentage>(
parser, CSSComponentValueDelimiter::Solidus);
if (!std::holds_alternative<std::monostate>(alphaValue)) {
parser.consumeComponentValue(CSSComponentValueDelimiter::Solidus);
}
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);

float alphaNumber = std::holds_alternative<std::monostate>(alphaValue) ? 1.0f
: std::holds_alternative<CSSNumber>(alphaValue)
? std::get<CSSNumber>(alphaValue).value
: std::get<CSSPercentage>(alphaValue).value / 100.0f;
auto [r, g, b] = hslToRgb(*h, *s, *l);

return CSSColor{
.r = clamp255Component(redNumber),
.g = clamp255Component(greenNumber),
.b = clamp255Component(blueNumber),
.a = clamp255Component(alphaNumber * 255.0f),
.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);
}
}
} // namespace detail

/**
Expand All @@ -128,7 +357,7 @@ constexpr std::optional<CSSColor> parseCSSColorFunction(
break;
case fnv1a("hsl"):
case fnv1a("hsla"):
// TODO
return detail::parseHslFunction<CSSColor>(parser);
break;
case fnv1a("hwb"):
case fnv1a("hwba"):
Expand Down
Loading