Skip to content

Commit 417ff5f

Browse files
jorge-cabfacebook-github-bot
authored andcommitted
Add <hex-color> to CSSParser (#44624)
Summary: Pull Request resolved: #44624 The syntax for <color> is defined as (https://www.w3.org/TR/css-color-4/#color-syntax): ``` <color> = <color-base> | currentColor | <system-color> <color-base> = <hex-color> | <color-function> | <named-color> | transparent <color-function> = <rgb()> | <rgba()> | <hsl()> | <hsla()> | <hwb()> | <lab()> | <lch()> | <oklab()> | <oklch()> | <color()> ``` This diff implements in particular: ``` <color-base> = <hex-color> ``` We parse over a `<hash-token>` to extract a 3, 4, 6 or 8 digit long hexadecimal and parse it into a CSSColor struct representation: ``` struct Color { uint8_t r // 0-255 uint8_t g // 0-255 uint8_t b // 0-255 uint8_t a // 0.0 - 1.0 } ``` Changelog: [Internal] Reviewed By: NickGerleman Differential Revision: D57287309 fbshipit-source-id: 35ce82e52fdc8fc86425e03d7883ae09b429b9e4
1 parent 7349dab commit 417ff5f

File tree

4 files changed

+173
-1
lines changed

4 files changed

+173
-1
lines changed

packages/react-native/ReactCommon/react/renderer/css/CSSValue.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ enum class CSSValueType : uint8_t {
2727
Percentage,
2828
Ratio,
2929
Angle,
30+
Color,
3031
};
3132

3233
/**
@@ -81,4 +82,11 @@ struct CSSAngle {
8182
float degrees{};
8283
};
8384

85+
struct CSSColor {
86+
uint8_t r{};
87+
uint8_t g{};
88+
uint8_t b{};
89+
uint8_t a{};
90+
};
91+
8492
} // namespace facebook::react

packages/react-native/ReactCommon/react/renderer/css/CSSValueParser.h

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,12 @@ class CSSValueParser {
7676
return *percentage;
7777
}
7878
}
79-
79+
// <color>
80+
if constexpr (hasType<CSSColor>()) {
81+
if (auto colorValue = consumeColorToken(token)) {
82+
return *colorValue;
83+
}
84+
}
8085
return CSSValue{};
8186
});
8287
// TODO: support function component values and simple blocks
@@ -224,6 +229,93 @@ class CSSValueParser {
224229
value != -std::numeric_limits<float>::infinity();
225230
}
226231

232+
enum class HexColorType {
233+
Long,
234+
Short,
235+
};
236+
237+
constexpr std::optional<CSSValue> consumeColorToken(
238+
const CSSPreservedToken& token) {
239+
// https://www.w3.org/TR/css-color-4/#hex-color
240+
std::string_view hexColorValue = token.stringValue();
241+
if (isValidHexColor(hexColorValue)) {
242+
if (hexColorValue.length() == 3) {
243+
return CSSValue::color(
244+
hexToNumeric(hexColorValue.substr(0, 1), HexColorType::Short),
245+
hexToNumeric(hexColorValue.substr(1, 1), HexColorType::Short),
246+
hexToNumeric(hexColorValue.substr(2, 1), HexColorType::Short),
247+
255u);
248+
} else if (hexColorValue.length() == 4) {
249+
return CSSValue::color(
250+
hexToNumeric(hexColorValue.substr(0, 1), HexColorType::Short),
251+
hexToNumeric(hexColorValue.substr(1, 1), HexColorType::Short),
252+
hexToNumeric(hexColorValue.substr(2, 1), HexColorType::Short),
253+
hexToNumeric(hexColorValue.substr(3, 1), HexColorType::Short));
254+
} else if (hexColorValue.length() == 6) {
255+
return CSSValue::color(
256+
hexToNumeric(hexColorValue.substr(0, 2), HexColorType::Long),
257+
hexToNumeric(hexColorValue.substr(2, 2), HexColorType::Long),
258+
hexToNumeric(hexColorValue.substr(4, 2), HexColorType::Long),
259+
255u);
260+
} else if (hexColorValue.length() == 8) {
261+
return CSSValue::color(
262+
hexToNumeric(hexColorValue.substr(0, 2), HexColorType::Long),
263+
hexToNumeric(hexColorValue.substr(2, 2), HexColorType::Long),
264+
hexToNumeric(hexColorValue.substr(4, 2), HexColorType::Long),
265+
hexToNumeric(hexColorValue.substr(6, 2), HexColorType::Long));
266+
}
267+
}
268+
return {};
269+
}
270+
271+
constexpr char toLower(char c) {
272+
if (c >= 'A' && c <= 'Z') {
273+
return static_cast<char>(c + 32);
274+
}
275+
return c;
276+
}
277+
278+
constexpr uint8_t hexToNumeric(std::string_view hex, HexColorType hexType) {
279+
int result = 0;
280+
for (char c : hex) {
281+
int value = 0;
282+
if (c >= '0' && c <= '9') {
283+
value = c - '0';
284+
} else {
285+
value = toLower(c) - 'a' + 10;
286+
}
287+
result *= 16;
288+
result += value;
289+
}
290+
291+
if (hexType == HexColorType::Short) {
292+
return result * 16 + result;
293+
} else {
294+
return result;
295+
}
296+
}
297+
298+
constexpr bool isValidHexColor(std::string_view hex) {
299+
// The syntax of a <hex-color> is a <hash-token> token whose value consists
300+
// of 3, 4, 6, or 8 hexadecimal digits.
301+
if (hex.size() != 3 && hex.size() != 4 && hex.size() != 6 &&
302+
hex.size() != 8) {
303+
return false;
304+
}
305+
306+
for (auto c : hex) {
307+
if (!isHexDigit(c)) {
308+
return false;
309+
}
310+
}
311+
312+
return true;
313+
}
314+
315+
constexpr bool isHexDigit(char c) {
316+
return (c >= '0' && c <= '9') || (toLower(c) >= 'a' && toLower(c) <= 'f');
317+
}
318+
227319
CSSSyntaxParser parser_;
228320
};
229321

packages/react-native/ReactCommon/react/renderer/css/CSSValueVariant.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,13 @@ class CSSValueVariant {
109109
return CSSValueVariant(CSSValueType::Angle, CSSAngle{degrees});
110110
}
111111

112+
static constexpr CSSValueVariant
113+
color(uint8_t r, uint8_t g, uint8_t b, uint8_t a)
114+
requires(canRepresent<CSSColor>())
115+
{
116+
return CSSValueVariant(CSSValueType::Color, CSSColor{r, g, b, a});
117+
}
118+
112119
constexpr CSSValueType type() const {
113120
return type_;
114121
}
@@ -155,6 +162,12 @@ class CSSValueVariant {
155162
return getIf<CSSValueType::Angle, CSSAngle>();
156163
}
157164

165+
constexpr CSSColor getColor() const
166+
requires(canRepresent<CSSColor>())
167+
{
168+
return getIf<CSSValueType::Color, CSSColor>();
169+
}
170+
158171
constexpr bool hasValue() const
159172
requires(canRepresent<CSSWideKeyword>())
160173
{
@@ -185,6 +198,8 @@ class CSSValueVariant {
185198
return getRatio() == other.getRatio();
186199
case CSSValueType::Angle:
187200
return getAngle() == other.getAngle();
201+
case CSSValueType::Color:
202+
return getColor() == other.getColor();
188203
}
189204

190205
return false;

packages/react-native/ReactCommon/react/renderer/css/tests/CSSValueParserTest.cpp

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,4 +320,61 @@ TEST(CSSValueParser, parse_length_prop_constexpr) {
320320
EXPECT_EQ(pxValue.getLength().unit, CSSLengthUnit::Px);
321321
}
322322

323+
TEST(CSSValueParser, hex_color_values) {
324+
auto emptyValue = parseCSSValue<CSSWideKeyword, CSSColor>("");
325+
EXPECT_EQ(emptyValue.type(), CSSValueType::CSSWideKeyword);
326+
EXPECT_EQ(emptyValue.getCSSWideKeyword(), CSSWideKeyword::Unset);
327+
328+
auto hex3DigitColorValue = parseCSSValue<CSSWideKeyword, CSSColor>("#fff");
329+
EXPECT_EQ(hex3DigitColorValue.type(), CSSValueType::Color);
330+
EXPECT_EQ(hex3DigitColorValue.getColor().r, 255);
331+
EXPECT_EQ(hex3DigitColorValue.getColor().g, 255);
332+
EXPECT_EQ(hex3DigitColorValue.getColor().b, 255);
333+
EXPECT_EQ(hex3DigitColorValue.getColor().a, 255);
334+
335+
auto hex4DigitColorValue = parseCSSValue<CSSWideKeyword, CSSColor>("#ffff");
336+
EXPECT_EQ(hex4DigitColorValue.type(), CSSValueType::Color);
337+
EXPECT_EQ(hex4DigitColorValue.getColor().r, 255);
338+
EXPECT_EQ(hex4DigitColorValue.getColor().g, 255);
339+
EXPECT_EQ(hex4DigitColorValue.getColor().b, 255);
340+
EXPECT_EQ(hex4DigitColorValue.getColor().a, 255);
341+
342+
auto hex6DigitColorValue = parseCSSValue<CSSWideKeyword, CSSColor>("#ffffff");
343+
EXPECT_EQ(hex6DigitColorValue.type(), CSSValueType::Color);
344+
EXPECT_EQ(hex6DigitColorValue.getColor().r, 255);
345+
EXPECT_EQ(hex6DigitColorValue.getColor().g, 255);
346+
EXPECT_EQ(hex6DigitColorValue.getColor().b, 255);
347+
EXPECT_EQ(hex6DigitColorValue.getColor().a, 255);
348+
349+
auto hex8DigitColorValue =
350+
parseCSSValue<CSSWideKeyword, CSSColor>("#ffffffff");
351+
EXPECT_EQ(hex8DigitColorValue.type(), CSSValueType::Color);
352+
EXPECT_EQ(hex8DigitColorValue.getColor().r, 255);
353+
EXPECT_EQ(hex8DigitColorValue.getColor().g, 255);
354+
EXPECT_EQ(hex8DigitColorValue.getColor().b, 255);
355+
EXPECT_EQ(hex8DigitColorValue.getColor().a, 255);
356+
357+
auto hexMixedCaseColorValue =
358+
parseCSSValue<CSSWideKeyword, CSSColor>("#FFCc99");
359+
EXPECT_EQ(hexMixedCaseColorValue.type(), CSSValueType::Color);
360+
EXPECT_EQ(hexMixedCaseColorValue.getColor().r, 255);
361+
EXPECT_EQ(hexMixedCaseColorValue.getColor().g, 204);
362+
EXPECT_EQ(hexMixedCaseColorValue.getColor().b, 153);
363+
EXPECT_EQ(hexMixedCaseColorValue.getColor().a, 255);
364+
365+
auto hexDigitOnlyColorValue = parseCSSValue<CSSWideKeyword, CSSColor>("#369");
366+
EXPECT_EQ(hexDigitOnlyColorValue.type(), CSSValueType::Color);
367+
EXPECT_EQ(hexDigitOnlyColorValue.getColor().r, 51);
368+
EXPECT_EQ(hexDigitOnlyColorValue.getColor().g, 102);
369+
EXPECT_EQ(hexDigitOnlyColorValue.getColor().b, 153);
370+
EXPECT_EQ(hexDigitOnlyColorValue.getColor().a, 255);
371+
372+
auto hexAlphaTestValue = parseCSSValue<CSSWideKeyword, CSSColor>("#FFFFFFCC");
373+
EXPECT_EQ(hexAlphaTestValue.type(), CSSValueType::Color);
374+
EXPECT_EQ(hexAlphaTestValue.getColor().r, 255);
375+
EXPECT_EQ(hexAlphaTestValue.getColor().g, 255);
376+
EXPECT_EQ(hexAlphaTestValue.getColor().b, 255);
377+
EXPECT_EQ(hexAlphaTestValue.getColor().a, 204);
378+
}
379+
323380
} // namespace facebook::react

0 commit comments

Comments
 (0)