mirror of
https://github.com/RGBCube/serenity
synced 2025-07-23 09:17:36 +00:00
LibWeb: Implement CSS color parsing from Tokens
This was broken when we switched away from using StringStyleValues. While I was at it, I have implemented hsl/a() and the percentage syntax for rgb/a(). As a bonus, added `colors.html` as a test page for the various CSS color syntaxes, since nothing was testing rgb() or rgba() before. Much of the parsing code in LibGFX/Color.h seems to be centered around CSS color values, but this is not used by the new Parser. (And can't be used, because it requires a String value and we have a list of Tokens of some kind instead.) Maybe that should be removed from there when the new CSS parser is operational.
This commit is contained in:
parent
fabbd06de4
commit
7abfb18656
4 changed files with 195 additions and 9 deletions
29
Base/res/html/misc/colors.html
Normal file
29
Base/res/html/misc/colors.html
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>CSS test</title>
|
||||||
|
<style type="text/css">
|
||||||
|
#a { background-color: lime; }
|
||||||
|
#b { background-color: #0F0; }
|
||||||
|
#c { background-color: #00ff00; }
|
||||||
|
#d { background-color: rgb(0, 255, 0); }
|
||||||
|
#e { background-color: rgb(0%, 100%, 0%); }
|
||||||
|
#e2 { background-color: rgb(-5%, 120%, -42%); }
|
||||||
|
#f { background-color: rgba(0, 255, 0, 1); }
|
||||||
|
#g { background-color: rgba(0%, 100%, 0%, 1); }
|
||||||
|
#h { background-color: hsl(120, 100%, 50%); }
|
||||||
|
#i { background-color: hsla(120, 100%, 50%, 1); }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="a">This is green, using a named color.</div>
|
||||||
|
<div id="b">This is green, using #0F0.</div>
|
||||||
|
<div id="c">This is green, using #00ff00.</div>
|
||||||
|
<div id="d">This is green, using rgb(0, 255, 0).</div>
|
||||||
|
<div id="e">This is green, using rgb(0%, 100%, 0%).</div>
|
||||||
|
<div id="e2">This is green, using rgb(-5%, 120%, -42%).</div>
|
||||||
|
<div id="f">This is green, using rgba(0, 255, 0, 1).</div>
|
||||||
|
<div id="g">This is green, using rgba(0%, 100%, 0%, 1).</div>
|
||||||
|
<div id="h">This is green, using hsl(120, 100%, 50%).</div>
|
||||||
|
<div id="i">This is green, using hsla(120, 100%, 50%, 1).</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -114,6 +114,7 @@
|
||||||
<li><a href="form.html">form</a></li>
|
<li><a href="form.html">form</a></li>
|
||||||
<li><a href="borders.html">borders</a></li>
|
<li><a href="borders.html">borders</a></li>
|
||||||
<li><a href="css.html">css</a></li>
|
<li><a href="css.html">css</a></li>
|
||||||
|
<li><a href="colors.html">css colors</a></li>
|
||||||
<li><a href="acid1.html">acid1</a></li>
|
<li><a href="acid1.html">acid1</a></li>
|
||||||
<li><a href="acid2.html">acid2</a></li>
|
<li><a href="acid2.html">acid2</a></li>
|
||||||
<li><a href="attrselectors.html">attribute selectors</a></li>
|
<li><a href="attrselectors.html">attribute selectors</a></li>
|
||||||
|
|
|
@ -1405,17 +1405,171 @@ RefPtr<StyleValue> Parser::parse_identifier_value(ParsingContext const&, StyleCo
|
||||||
|
|
||||||
RefPtr<StyleValue> Parser::parse_color_value(ParsingContext const&, StyleComponentValueRule const& component_value)
|
RefPtr<StyleValue> Parser::parse_color_value(ParsingContext const&, StyleComponentValueRule const& component_value)
|
||||||
{
|
{
|
||||||
|
// https://www.w3.org/TR/css-color-3/
|
||||||
auto parse_css_color = [&]() -> Optional<Color> {
|
auto parse_css_color = [&]() -> Optional<Color> {
|
||||||
if (component_value.is(Token::Type::Ident) && component_value.token().ident().equals_ignoring_case("transparent"))
|
if (component_value.is(Token::Type::Ident)) {
|
||||||
return Color::from_rgba(0x00000000);
|
auto ident = component_value.token().ident();
|
||||||
|
if (ident.equals_ignoring_case("transparent"))
|
||||||
|
return Color::from_rgba(0x00000000);
|
||||||
|
|
||||||
// FIXME: Handle all the different color notations.
|
auto color = Color::from_string(ident.to_string().to_lowercase());
|
||||||
// https://www.w3.org/TR/css-color-3/
|
if (color.has_value())
|
||||||
// Right now, this uses non-CSS-specific parsing, and assumes the whole color value is one token,
|
return color;
|
||||||
// which is isn't if it's a function-style syntax.
|
|
||||||
auto color = Color::from_string(component_value.token().m_value.to_string().to_lowercase());
|
} else if (component_value.is(Token::Type::Hash)) {
|
||||||
if (color.has_value())
|
// FIXME: Read it directly
|
||||||
return color;
|
auto color = Color::from_string(String::formatted("#{}", component_value.token().m_value.to_string().to_lowercase()));
|
||||||
|
if (color.has_value())
|
||||||
|
return color;
|
||||||
|
|
||||||
|
} else if (component_value.is_function()) {
|
||||||
|
auto& function = component_value.function();
|
||||||
|
auto& values = function.values();
|
||||||
|
|
||||||
|
Vector<Token> params;
|
||||||
|
for (size_t i = 0; i < values.size(); ++i) {
|
||||||
|
auto& value = values.at(i);
|
||||||
|
if (value.is(Token::Type::Whitespace))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (value.is(Token::Type::Percentage) || value.is(Token::Type::Number)) {
|
||||||
|
params.append(value.token());
|
||||||
|
// Eat following comma and whitespace
|
||||||
|
while ((i + 1) < values.size()) {
|
||||||
|
auto& next = values.at(i + 1);
|
||||||
|
if (next.is(Token::Type::Whitespace))
|
||||||
|
i++;
|
||||||
|
else if (next.is(Token::Type::Comma))
|
||||||
|
break;
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (function.name().equals_ignoring_case("rgb")) {
|
||||||
|
if (params.size() != 3)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
auto r_val = params[0];
|
||||||
|
auto g_val = params[1];
|
||||||
|
auto b_val = params[2];
|
||||||
|
|
||||||
|
if (r_val.is(Token::NumberType::Integer)
|
||||||
|
&& g_val.is(Token::NumberType::Integer)
|
||||||
|
&& b_val.is(Token::NumberType::Integer)) {
|
||||||
|
|
||||||
|
auto maybe_r = r_val.m_value.string_view().to_uint<u8>();
|
||||||
|
auto maybe_g = g_val.m_value.string_view().to_uint<u8>();
|
||||||
|
auto maybe_b = b_val.m_value.string_view().to_uint<u8>();
|
||||||
|
if (maybe_r.has_value() && maybe_g.has_value() && maybe_b.has_value())
|
||||||
|
return Color(maybe_r.value(), maybe_g.value(), maybe_b.value());
|
||||||
|
|
||||||
|
} else if (r_val.is(Token::Type::Percentage)
|
||||||
|
&& g_val.is(Token::Type::Percentage)
|
||||||
|
&& b_val.is(Token::Type::Percentage)) {
|
||||||
|
|
||||||
|
auto maybe_r = try_parse_float(r_val.m_value.string_view());
|
||||||
|
auto maybe_g = try_parse_float(g_val.m_value.string_view());
|
||||||
|
auto maybe_b = try_parse_float(b_val.m_value.string_view());
|
||||||
|
if (maybe_r.has_value() && maybe_g.has_value() && maybe_b.has_value()) {
|
||||||
|
u8 r = clamp(lroundf(maybe_r.value() * 2.55f), 0, 255);
|
||||||
|
u8 g = clamp(lroundf(maybe_g.value() * 2.55f), 0, 255);
|
||||||
|
u8 b = clamp(lroundf(maybe_b.value() * 2.55f), 0, 255);
|
||||||
|
return Color(r, g, b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (function.name().equals_ignoring_case("rgba")) {
|
||||||
|
if (params.size() != 4)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
auto r_val = params[0];
|
||||||
|
auto g_val = params[1];
|
||||||
|
auto b_val = params[2];
|
||||||
|
auto a_val = params[3];
|
||||||
|
|
||||||
|
if (r_val.is(Token::NumberType::Integer)
|
||||||
|
&& g_val.is(Token::NumberType::Integer)
|
||||||
|
&& b_val.is(Token::NumberType::Integer)
|
||||||
|
&& a_val.is(Token::Type::Number)) {
|
||||||
|
|
||||||
|
auto maybe_r = r_val.m_value.string_view().to_uint<u8>();
|
||||||
|
auto maybe_g = g_val.m_value.string_view().to_uint<u8>();
|
||||||
|
auto maybe_b = b_val.m_value.string_view().to_uint<u8>();
|
||||||
|
auto maybe_a = try_parse_float(a_val.m_value.string_view());
|
||||||
|
if (maybe_r.has_value() && maybe_g.has_value() && maybe_b.has_value() && maybe_a.has_value()) {
|
||||||
|
u8 a = clamp(lroundf(maybe_a.value() * 255.0f), 0, 255);
|
||||||
|
return Color(maybe_r.value(), maybe_g.value(), maybe_b.value(), a);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (r_val.is(Token::Type::Percentage)
|
||||||
|
&& g_val.is(Token::Type::Percentage)
|
||||||
|
&& b_val.is(Token::Type::Percentage)
|
||||||
|
&& a_val.is(Token::Type::Number)) {
|
||||||
|
|
||||||
|
auto maybe_r = try_parse_float(r_val.m_value.string_view());
|
||||||
|
auto maybe_g = try_parse_float(g_val.m_value.string_view());
|
||||||
|
auto maybe_b = try_parse_float(b_val.m_value.string_view());
|
||||||
|
auto maybe_a = try_parse_float(a_val.m_value.string_view());
|
||||||
|
if (maybe_r.has_value() && maybe_g.has_value() && maybe_b.has_value() && maybe_a.has_value()) {
|
||||||
|
u8 r = clamp(lroundf(maybe_r.value() * 2.55f), 0, 255);
|
||||||
|
u8 g = clamp(lroundf(maybe_g.value() * 2.55f), 0, 255);
|
||||||
|
u8 b = clamp(lroundf(maybe_b.value() * 2.55f), 0, 255);
|
||||||
|
u8 a = clamp(lroundf(maybe_a.value() * 255.0f), 0, 255);
|
||||||
|
return Color(r, g, b, a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (function.name().equals_ignoring_case("hsl")) {
|
||||||
|
if (params.size() != 3)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
auto h_val = params[0];
|
||||||
|
auto s_val = params[1];
|
||||||
|
auto l_val = params[2];
|
||||||
|
|
||||||
|
if (h_val.is(Token::Type::Number)
|
||||||
|
&& s_val.is(Token::Type::Percentage)
|
||||||
|
&& l_val.is(Token::Type::Percentage)) {
|
||||||
|
|
||||||
|
auto maybe_h = try_parse_float(h_val.m_value.string_view());
|
||||||
|
auto maybe_s = try_parse_float(s_val.m_value.string_view());
|
||||||
|
auto maybe_l = try_parse_float(l_val.m_value.string_view());
|
||||||
|
if (maybe_h.has_value() && maybe_s.has_value() && maybe_l.has_value()) {
|
||||||
|
float h = maybe_h.value();
|
||||||
|
float s = maybe_s.value() / 100.0f;
|
||||||
|
float l = maybe_l.value() / 100.0f;
|
||||||
|
return Color::from_hsl(h, s, l);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (function.name().equals_ignoring_case("hsla")) {
|
||||||
|
if (params.size() != 4)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
auto h_val = params[0];
|
||||||
|
auto s_val = params[1];
|
||||||
|
auto l_val = params[2];
|
||||||
|
auto a_val = params[3];
|
||||||
|
|
||||||
|
if (h_val.is(Token::Type::Number)
|
||||||
|
&& s_val.is(Token::Type::Percentage)
|
||||||
|
&& l_val.is(Token::Type::Percentage)
|
||||||
|
&& a_val.is(Token::Type::Number)) {
|
||||||
|
|
||||||
|
auto maybe_h = try_parse_float(h_val.m_value.string_view());
|
||||||
|
auto maybe_s = try_parse_float(s_val.m_value.string_view());
|
||||||
|
auto maybe_l = try_parse_float(l_val.m_value.string_view());
|
||||||
|
auto maybe_a = try_parse_float(a_val.m_value.string_view());
|
||||||
|
if (maybe_h.has_value() && maybe_s.has_value() && maybe_l.has_value() && maybe_a.has_value()) {
|
||||||
|
float h = maybe_h.value();
|
||||||
|
float s = maybe_s.value() / 100.0f;
|
||||||
|
float l = maybe_l.value() / 100.0f;
|
||||||
|
float a = maybe_a.value();
|
||||||
|
return Color::from_hsla(h, s, l, a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
};
|
};
|
||||||
|
|
|
@ -76,6 +76,8 @@ public:
|
||||||
return m_value.string_view();
|
return m_value.string_view();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool is(NumberType number_type) const { return is(Token::Type::Number) && m_number_type == number_type; }
|
||||||
|
|
||||||
int integer() const
|
int integer() const
|
||||||
{
|
{
|
||||||
VERIFY(m_type == Type::Number && m_number_type == NumberType::Integer);
|
VERIFY(m_type == Type::Number && m_number_type == NumberType::Integer);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue