mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-12-02 04:20:28 +00:00
LibWeb: Introduce color-function-specific style values
Instead of CSSColorValue holding a Gfx::Color, make it an abstract class with subclasses for each different color function, to match the Typed-OM spec. This means moving the color calculations from the parsing code to the `to_color()` method on the style value. This lets us have calc() inside a color function, instead of having to fully resolve the color at parse time. The canvas fillStyle tests have been updated to reflect this. The other test change is Screenshot/css-color-functions.html: previously we produced slightly different colors for an alpha of 0.5 and one of 50%, and this incorrect behavior was baked into the test. So now it's more correct. :^)
This commit is contained in:
parent
27be8678c9
commit
3af6a69f1e
Notes:
github-actions[bot]
2024-08-21 09:52:45 +00:00
Author: https://github.com/AtkinsSJ Commit: https://github.com/LadybirdBrowser/ladybird/commit/3af6a69f1e1 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/1091
24 changed files with 1108 additions and 496 deletions
|
@ -8,7 +8,12 @@ source_set("StyleValues") {
|
|||
"BasicShapeStyleValue.cpp",
|
||||
"BorderRadiusStyleValue.cpp",
|
||||
"CSSColorValue.cpp",
|
||||
"CSSHSL.cpp",
|
||||
"CSSHWB.cpp",
|
||||
"CSSKeywordValue.cpp",
|
||||
"CSSOKLCH.cpp",
|
||||
"CSSOKLab.cpp",
|
||||
"CSSRGB.cpp",
|
||||
"CalculatedStyleValue.cpp",
|
||||
"ConicGradientStyleValue.cpp",
|
||||
"ContentStyleValue.cpp",
|
||||
|
|
|
@ -30,8 +30,6 @@
|
|||
ctx.fillRect(0, 300, 500, 100);
|
||||
|
||||
// Calc
|
||||
// FIXME: The CSS parser currently chokes on calc expressions, which will
|
||||
// leave the fillStyle as it was (green).
|
||||
ctx.fillStyle = "rgb(calc(infinity), 0, 0)";
|
||||
ctx.fillRect(0, 400, 500, 100);
|
||||
</script>
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 376 B |
Binary file not shown.
Before Width: | Height: | Size: 107 KiB After Width: | Height: | Size: 93 KiB |
|
@ -2,4 +2,4 @@
|
|||
2. "#ff0000ff"
|
||||
3. "#0000ffff"
|
||||
4. "#00ff00ff"
|
||||
5. "#00ff00ff"
|
||||
5. "#ff0000ff"
|
||||
|
|
|
@ -33,10 +33,8 @@
|
|||
return context.fillStyle;
|
||||
});
|
||||
|
||||
// 5. Percentages
|
||||
// 5. Calc, with out-of-range values
|
||||
testPart(() => {
|
||||
// FIXME: The CSS parser currently chokes on calc expressions, which will
|
||||
// leave the fillStyle as it was (green).
|
||||
context.fillStyle = "rgb(calc(infinity), 0, 0)";
|
||||
return context.fillStyle;
|
||||
});
|
||||
|
|
|
@ -113,7 +113,12 @@ set(SOURCES
|
|||
CSS/StyleValues/CounterDefinitionsStyleValue.cpp
|
||||
CSS/StyleValues/CounterStyleValue.cpp
|
||||
CSS/StyleValues/CSSColorValue.cpp
|
||||
CSS/StyleValues/CSSHSL.cpp
|
||||
CSS/StyleValues/CSSHWB.cpp
|
||||
CSS/StyleValues/CSSKeywordValue.cpp
|
||||
CSS/StyleValues/CSSOKLab.cpp
|
||||
CSS/StyleValues/CSSOKLCH.cpp
|
||||
CSS/StyleValues/CSSRGB.cpp
|
||||
CSS/StyleValues/DisplayStyleValue.cpp
|
||||
CSS/StyleValues/EasingStyleValue.cpp
|
||||
CSS/StyleValues/EdgeStyleValue.cpp
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2022, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2020-2021, the SerenityOS developers.
|
||||
* Copyright (c) 2021-2024, Sam Atkins <atkinssj@serenityos.org>
|
||||
* Copyright (c) 2021-2024, Sam Atkins <sam@ladybird.org>
|
||||
* Copyright (c) 2021, Tobias Christiansen <tobyase@serenityos.org>
|
||||
* Copyright (c) 2022, MacDue <macdue@dueutil.tech>
|
||||
* Copyright (c) 2024, Shannon Booth <shannon@serenityos.org>
|
||||
|
@ -43,7 +43,12 @@
|
|||
#include <LibWeb/CSS/StyleValues/BasicShapeStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/BorderRadiusStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/CSSColorValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/CSSHSL.h>
|
||||
#include <LibWeb/CSS/StyleValues/CSSHWB.h>
|
||||
#include <LibWeb/CSS/StyleValues/CSSKeywordValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/CSSOKLCH.h>
|
||||
#include <LibWeb/CSS/StyleValues/CSSOKLab.h>
|
||||
#include <LibWeb/CSS/StyleValues/CSSRGB.h>
|
||||
#include <LibWeb/CSS/StyleValues/ContentStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/CounterDefinitionsStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/CounterStyleValue.h>
|
||||
|
@ -2622,474 +2627,454 @@ RefPtr<CSSStyleValue> Parser::parse_rect_value(TokenStream<ComponentValue>& toke
|
|||
return RectStyleValue::create(EdgeRect { params[0], params[1], params[2], params[3] });
|
||||
}
|
||||
|
||||
Optional<Color> Parser::parse_rgb_color(Vector<ComponentValue> const& component_values)
|
||||
// https://www.w3.org/TR/css-color-4/#typedef-hue
|
||||
RefPtr<CSSStyleValue> Parser::parse_hue_value(TokenStream<ComponentValue>& tokens)
|
||||
{
|
||||
u8 r_val = 0;
|
||||
u8 g_val = 0;
|
||||
u8 b_val = 0;
|
||||
// <hue> = <number> | <angle>
|
||||
if (auto number = parse_number_value(tokens))
|
||||
return number;
|
||||
if (auto angle = parse_angle_value(tokens))
|
||||
return angle;
|
||||
|
||||
auto tokens = TokenStream { component_values };
|
||||
|
||||
tokens.skip_whitespace();
|
||||
auto const& red = tokens.next_token();
|
||||
|
||||
if (!red.is(Token::Type::Number)
|
||||
&& !red.is(Token::Type::Percentage))
|
||||
return {};
|
||||
|
||||
tokens.skip_whitespace();
|
||||
bool legacy_syntax = tokens.peek_token().is(Token::Type::Comma);
|
||||
if (legacy_syntax) {
|
||||
// Legacy syntax.
|
||||
tokens.next_token();
|
||||
tokens.skip_whitespace();
|
||||
|
||||
auto const& green = tokens.next_token();
|
||||
tokens.skip_whitespace();
|
||||
|
||||
tokens.next_token();
|
||||
tokens.skip_whitespace();
|
||||
|
||||
auto const& blue = tokens.next_token();
|
||||
|
||||
if (red.is(Token::Type::Percentage)) {
|
||||
// Percentage components.
|
||||
if (!green.is(Token::Type::Percentage) || !blue.is(Token::Type::Percentage))
|
||||
return {};
|
||||
|
||||
r_val = lround(clamp(red.token().percentage() * 2.55, 0, 255));
|
||||
g_val = lround(clamp(green.token().percentage() * 2.55, 0, 255));
|
||||
b_val = lround(clamp(blue.token().percentage() * 2.55, 0, 255));
|
||||
} else {
|
||||
// Number components.
|
||||
if (!green.is(Token::Type::Number) || !blue.is(Token::Type::Number))
|
||||
return {};
|
||||
|
||||
r_val = clamp(llroundf(red.token().number_value()), 0, 255);
|
||||
g_val = clamp(llroundf(green.token().number_value()), 0, 255);
|
||||
b_val = clamp(llroundf(blue.token().number_value()), 0, 255);
|
||||
}
|
||||
} else {
|
||||
// Modern syntax.
|
||||
|
||||
if (red.is(Token::Type::Number)) {
|
||||
r_val = clamp(llroundf(red.token().number_value()), 0, 255);
|
||||
} else {
|
||||
r_val = lround(clamp(red.token().percentage() * 2.55, 0, 255));
|
||||
}
|
||||
|
||||
auto const& green = tokens.next_token();
|
||||
if (green.is(Token::Type::Number)) {
|
||||
g_val = clamp(llroundf(green.token().number_value()), 0, 255);
|
||||
} else if (green.is(Token::Type::Percentage)) {
|
||||
g_val = lround(clamp(green.token().percentage() * 2.55, 0, 255));
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
|
||||
tokens.skip_whitespace();
|
||||
auto const& blue = tokens.next_token();
|
||||
if (blue.is(Token::Type::Number)) {
|
||||
b_val = clamp(llroundf(blue.token().number_value()), 0, 255);
|
||||
} else if (blue.is(Token::Type::Percentage)) {
|
||||
b_val = lround(clamp(blue.token().percentage() * 2.55, 0, 255));
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
u8 alpha_val = 255;
|
||||
tokens.skip_whitespace();
|
||||
if (tokens.has_next_token()) {
|
||||
auto const& separator = tokens.next_token();
|
||||
bool is_comma = separator.is(Token::Type::Comma);
|
||||
bool is_slash = separator.is_delim('/');
|
||||
if (legacy_syntax ? !is_comma : !is_slash)
|
||||
return {};
|
||||
|
||||
tokens.skip_whitespace();
|
||||
auto const& alpha = tokens.next_token();
|
||||
|
||||
if (alpha.is(Token::Type::Number))
|
||||
alpha_val = clamp(lround(alpha.token().number_value() * 255.0), 0, 255);
|
||||
else if (alpha.is(Token::Type::Percentage))
|
||||
alpha_val = clamp(lround(alpha.token().percentage() * 2.55), 0, 255);
|
||||
else
|
||||
return {};
|
||||
|
||||
tokens.skip_whitespace();
|
||||
if (tokens.has_next_token())
|
||||
return {}; // should have consumed all arguments.
|
||||
}
|
||||
|
||||
return Color(r_val, g_val, b_val, alpha_val);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Optional<Color> Parser::parse_hsl_color(Vector<ComponentValue> const& component_values)
|
||||
RefPtr<CSSStyleValue> Parser::parse_solidus_and_alpha_value(TokenStream<ComponentValue>& tokens)
|
||||
{
|
||||
float h_val = 0.0;
|
||||
float s_val = 0.0;
|
||||
float l_val = 0.0;
|
||||
// [ / [<alpha-value> | none] ]?
|
||||
// Common to the modern-syntax color functions.
|
||||
// TODO: Parse `none`
|
||||
|
||||
auto tokens = TokenStream { component_values };
|
||||
|
||||
tokens.skip_whitespace();
|
||||
auto const& hue = tokens.next_token();
|
||||
|
||||
if (!hue.is(Token::Type::Number)
|
||||
&& !hue.is(Token::Type::Dimension))
|
||||
return {};
|
||||
|
||||
if (hue.is(Token::Type::Number)) {
|
||||
h_val = fmod(hue.token().number_value(), 360.0);
|
||||
} else {
|
||||
auto numeric_value = hue.token().dimension_value();
|
||||
auto unit_string = hue.token().dimension_unit();
|
||||
auto angle_type = Angle::unit_from_name(unit_string);
|
||||
|
||||
if (!angle_type.has_value())
|
||||
return {};
|
||||
|
||||
auto angle = Angle { numeric_value, angle_type.release_value() };
|
||||
|
||||
h_val = fmod(angle.to_degrees(), 360.0);
|
||||
}
|
||||
|
||||
tokens.skip_whitespace();
|
||||
bool legacy_syntax = tokens.peek_token().is(Token::Type::Comma);
|
||||
if (legacy_syntax) {
|
||||
// legacy syntax.
|
||||
tokens.next_token();
|
||||
tokens.skip_whitespace();
|
||||
|
||||
auto const& saturation = tokens.next_token();
|
||||
if (!saturation.is(Token::Type::Percentage))
|
||||
return {};
|
||||
s_val = max(saturation.token().percentage() / 100.0, 0);
|
||||
|
||||
tokens.skip_whitespace();
|
||||
tokens.next_token();
|
||||
tokens.skip_whitespace();
|
||||
|
||||
auto const& lightness = tokens.next_token();
|
||||
if (!lightness.is(Token::Type::Percentage))
|
||||
return {};
|
||||
l_val = lightness.token().percentage() / 100.0;
|
||||
} else {
|
||||
// Modern syntax.
|
||||
|
||||
auto const& saturation = tokens.next_token();
|
||||
if (saturation.is(Token::Type::Number)) {
|
||||
s_val = saturation.token().number_value() / 100.0;
|
||||
} else if (saturation.is(Token::Type::Percentage)) {
|
||||
s_val = saturation.token().percentage() / 100.0;
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
s_val = max(s_val, 0);
|
||||
|
||||
tokens.skip_whitespace();
|
||||
auto const& lightness = tokens.next_token();
|
||||
if (lightness.is(Token::Type::Number)) {
|
||||
l_val = lightness.token().number_value() / 100.0;
|
||||
} else if (lightness.is(Token::Type::Percentage)) {
|
||||
l_val = lightness.token().percentage() / 100.0;
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
float alpha_val = 1.0;
|
||||
tokens.skip_whitespace();
|
||||
if (tokens.has_next_token()) {
|
||||
auto const& separator = tokens.next_token();
|
||||
bool is_comma = separator.is(Token::Type::Comma);
|
||||
bool is_slash = separator.is_delim('/');
|
||||
if (legacy_syntax ? !is_comma : !is_slash)
|
||||
return {};
|
||||
|
||||
tokens.skip_whitespace();
|
||||
auto const& alpha = tokens.next_token();
|
||||
|
||||
if (alpha.is(Token::Type::Number))
|
||||
alpha_val = alpha.token().number_value();
|
||||
else if (alpha.is(Token::Type::Percentage))
|
||||
alpha_val = alpha.token().percentage() / 100.0;
|
||||
else
|
||||
return {};
|
||||
|
||||
tokens.skip_whitespace();
|
||||
if (tokens.has_next_token())
|
||||
return {}; // should have consumed all arguments.
|
||||
}
|
||||
|
||||
return Color::from_hsla(h_val, s_val, l_val, alpha_val);
|
||||
}
|
||||
|
||||
Optional<Color> Parser::parse_hwb_color(Vector<ComponentValue> const& component_values)
|
||||
{
|
||||
float h_val = 0.0;
|
||||
float w_val = 0.0;
|
||||
float b_val = 0.0;
|
||||
|
||||
auto tokens = TokenStream { component_values };
|
||||
|
||||
tokens.skip_whitespace();
|
||||
auto const& hue = tokens.next_token();
|
||||
|
||||
if (!hue.is(Token::Type::Number)
|
||||
&& !hue.is(Token::Type::Dimension))
|
||||
return {};
|
||||
|
||||
if (hue.is(Token::Type::Number)) {
|
||||
h_val = fmod(hue.token().number_value(), 360.0);
|
||||
} else {
|
||||
auto numeric_value = hue.token().dimension_value();
|
||||
auto unit_string = hue.token().dimension_unit();
|
||||
auto angle_type = Angle::unit_from_name(unit_string);
|
||||
|
||||
if (!angle_type.has_value())
|
||||
return {};
|
||||
|
||||
auto angle = Angle { numeric_value, angle_type.release_value() };
|
||||
|
||||
h_val = fmod(angle.to_degrees(), 360);
|
||||
}
|
||||
|
||||
tokens.skip_whitespace();
|
||||
auto const& whiteness = tokens.next_token();
|
||||
if (whiteness.is(Token::Type::Number)) {
|
||||
w_val = whiteness.token().number_value() / 100.0;
|
||||
} else if (whiteness.is(Token::Type::Percentage)) {
|
||||
w_val = whiteness.token().percentage() / 100.0;
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
|
||||
tokens.skip_whitespace();
|
||||
auto const& blackness = tokens.next_token();
|
||||
if (blackness.is(Token::Type::Number)) {
|
||||
b_val = blackness.token().number_value() / 100.0;
|
||||
} else if (blackness.is(Token::Type::Percentage)) {
|
||||
b_val = blackness.token().percentage() / 100.0;
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
|
||||
float alpha_val = 1.0;
|
||||
tokens.skip_whitespace();
|
||||
if (tokens.has_next_token()) {
|
||||
auto const& separator = tokens.next_token();
|
||||
if (!separator.is_delim('/'))
|
||||
return {};
|
||||
|
||||
tokens.skip_whitespace();
|
||||
auto const& alpha = tokens.next_token();
|
||||
|
||||
if (alpha.is(Token::Type::Number))
|
||||
alpha_val = alpha.token().number_value();
|
||||
else if (alpha.is(Token::Type::Percentage))
|
||||
alpha_val = alpha.token().percentage() / 100.0;
|
||||
else
|
||||
return {};
|
||||
|
||||
tokens.skip_whitespace();
|
||||
if (tokens.has_next_token())
|
||||
return {}; // should have consumed all arguments.
|
||||
}
|
||||
|
||||
if (w_val + b_val >= 1.0) {
|
||||
u8 gray = clamp(llroundf((w_val / (w_val + b_val)) * 255), 0, 255);
|
||||
return Color(gray, gray, gray, clamp(llroundf(alpha_val * 255), 0, 255));
|
||||
}
|
||||
|
||||
float value = 1 - b_val;
|
||||
float saturation = 1 - (w_val / value);
|
||||
return Color::from_hsv(h_val, saturation, value).with_opacity(alpha_val);
|
||||
}
|
||||
|
||||
Optional<Color> Parser::parse_oklab_color(Vector<ComponentValue> const& component_values)
|
||||
{
|
||||
float L_val = 0.0;
|
||||
float a_val = 0.0;
|
||||
float b_val = 0.0;
|
||||
|
||||
auto tokens = TokenStream { component_values };
|
||||
|
||||
tokens.skip_whitespace();
|
||||
auto const& lightness = tokens.next_token();
|
||||
if (lightness.is(Token::Type::Number)) {
|
||||
L_val = lightness.token().number_value();
|
||||
} else if (lightness.is(Token::Type::Percentage)) {
|
||||
L_val = lightness.token().percentage() / 100.0;
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
L_val = clamp(L_val, 0.0, 1.0);
|
||||
|
||||
tokens.skip_whitespace();
|
||||
auto const& a = tokens.next_token();
|
||||
if (a.is(Token::Type::Number)) {
|
||||
a_val = a.token().number_value();
|
||||
} else if (a.is(Token::Type::Percentage)) {
|
||||
a_val = a.token().percentage() / 100.0 * 0.4;
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
|
||||
tokens.skip_whitespace();
|
||||
auto const& b = tokens.next_token();
|
||||
if (b.is(Token::Type::Number)) {
|
||||
b_val = b.token().number_value();
|
||||
} else if (a.is(Token::Type::Percentage)) {
|
||||
b_val = b.token().percentage() / 100.0 * 0.4;
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
|
||||
float alpha_val = 1.0;
|
||||
tokens.skip_whitespace();
|
||||
if (tokens.has_next_token()) {
|
||||
auto const& separator = tokens.next_token();
|
||||
if (!separator.is_delim('/'))
|
||||
return {};
|
||||
|
||||
tokens.skip_whitespace();
|
||||
auto const& alpha = tokens.next_token();
|
||||
|
||||
if (alpha.is(Token::Type::Number))
|
||||
alpha_val = alpha.token().number_value();
|
||||
else if (alpha.is(Token::Type::Percentage))
|
||||
alpha_val = alpha.token().percentage() / 100.0;
|
||||
else
|
||||
return {};
|
||||
|
||||
tokens.skip_whitespace();
|
||||
if (tokens.has_next_token())
|
||||
return {}; // should have consumed all arguments.
|
||||
}
|
||||
|
||||
return Color::from_oklab(L_val, a_val, b_val, alpha_val);
|
||||
}
|
||||
|
||||
Optional<Color> Parser::parse_oklch_color(Vector<ComponentValue> const& component_values)
|
||||
{
|
||||
float L_val = 0.0;
|
||||
float c_val = 0.0;
|
||||
float h_val = 0.0;
|
||||
|
||||
auto tokens = TokenStream { component_values };
|
||||
|
||||
tokens.skip_whitespace();
|
||||
auto const& lightness = tokens.next_token();
|
||||
if (lightness.is(Token::Type::Number)) {
|
||||
L_val = lightness.token().number_value();
|
||||
} else if (lightness.is(Token::Type::Percentage)) {
|
||||
L_val = lightness.token().percentage() / 100.0;
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
L_val = clamp(L_val, 0.0, 1.0);
|
||||
|
||||
tokens.skip_whitespace();
|
||||
auto const& chroma = tokens.next_token();
|
||||
if (chroma.is(Token::Type::Number)) {
|
||||
c_val = chroma.token().number_value();
|
||||
} else if (chroma.is(Token::Type::Percentage)) {
|
||||
c_val = chroma.token().percentage() / 100.0 * 0.4;
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
c_val = max(c_val, 0.0);
|
||||
|
||||
tokens.skip_whitespace();
|
||||
auto const& hue = tokens.next_token();
|
||||
|
||||
if (!hue.is(Token::Type::Number)
|
||||
&& !hue.is(Token::Type::Dimension))
|
||||
return {};
|
||||
|
||||
if (hue.is(Token::Type::Number)) {
|
||||
h_val = hue.token().number_value() * AK::Pi<float> / 180;
|
||||
} else {
|
||||
auto numeric_value = hue.token().dimension_value();
|
||||
auto unit_string = hue.token().dimension_unit();
|
||||
auto angle_type = Angle::unit_from_name(unit_string);
|
||||
|
||||
if (!angle_type.has_value())
|
||||
return {};
|
||||
|
||||
auto angle = Angle { numeric_value, angle_type.release_value() };
|
||||
|
||||
h_val = angle.to_radians();
|
||||
}
|
||||
|
||||
float alpha_val = 1.0;
|
||||
tokens.skip_whitespace();
|
||||
if (tokens.has_next_token()) {
|
||||
auto const& separator = tokens.next_token();
|
||||
if (!separator.is_delim('/'))
|
||||
return {};
|
||||
|
||||
tokens.skip_whitespace();
|
||||
auto const& alpha = tokens.next_token();
|
||||
|
||||
if (alpha.is(Token::Type::Number))
|
||||
alpha_val = alpha.token().number_value();
|
||||
else if (alpha.is(Token::Type::Percentage))
|
||||
alpha_val = alpha.token().percentage() / 100.0;
|
||||
else
|
||||
return {};
|
||||
|
||||
tokens.skip_whitespace();
|
||||
if (tokens.has_next_token())
|
||||
return {}; // should have consumed all arguments.
|
||||
}
|
||||
|
||||
return Color::from_oklab(L_val, c_val * cos(h_val), c_val * sin(h_val), alpha_val);
|
||||
}
|
||||
|
||||
Optional<Color> Parser::parse_color(TokenStream<ComponentValue>& tokens)
|
||||
{
|
||||
auto transaction = tokens.begin_transaction();
|
||||
auto commit_if_valid = [&](Optional<Color> color) {
|
||||
if (color.has_value())
|
||||
transaction.commit();
|
||||
return color;
|
||||
};
|
||||
tokens.skip_whitespace();
|
||||
if (!tokens.next_token().is_delim('/'))
|
||||
return {};
|
||||
tokens.skip_whitespace();
|
||||
auto alpha = parse_number_percentage_value(tokens);
|
||||
if (!alpha)
|
||||
return {};
|
||||
tokens.skip_whitespace();
|
||||
|
||||
transaction.commit();
|
||||
return alpha;
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/css-color-4/#funcdef-rgb
|
||||
RefPtr<CSSStyleValue> Parser::parse_rgb_color_value(TokenStream<ComponentValue>& outer_tokens)
|
||||
{
|
||||
// rgb() = [ <legacy-rgb-syntax> | <modern-rgb-syntax> ]
|
||||
// rgba() = [ <legacy-rgba-syntax> | <modern-rgba-syntax> ]
|
||||
// <legacy-rgb-syntax> = rgb( <percentage>#{3} , <alpha-value>? ) |
|
||||
// rgb( <number>#{3} , <alpha-value>? )
|
||||
// <legacy-rgba-syntax> = rgba( <percentage>#{3} , <alpha-value>? ) |
|
||||
// rgba( <number>#{3} , <alpha-value>? )
|
||||
// <modern-rgb-syntax> = rgb(
|
||||
// [ <number> | <percentage> | none]{3}
|
||||
// [ / [<alpha-value> | none] ]? )
|
||||
// <modern-rgba-syntax> = rgba(
|
||||
// [ <number> | <percentage> | none]{3}
|
||||
// [ / [<alpha-value> | none] ]? )
|
||||
// TODO: Handle none values
|
||||
|
||||
auto transaction = outer_tokens.begin_transaction();
|
||||
outer_tokens.skip_whitespace();
|
||||
|
||||
auto& function_token = outer_tokens.next_token();
|
||||
if (!function_token.is_function("rgb"sv) && !function_token.is_function("rgba"sv))
|
||||
return {};
|
||||
|
||||
RefPtr<CSSStyleValue> red;
|
||||
RefPtr<CSSStyleValue> green;
|
||||
RefPtr<CSSStyleValue> blue;
|
||||
RefPtr<CSSStyleValue> alpha;
|
||||
|
||||
auto inner_tokens = TokenStream { function_token.function().values() };
|
||||
inner_tokens.skip_whitespace();
|
||||
|
||||
red = parse_number_percentage_value(inner_tokens);
|
||||
if (!red)
|
||||
return {};
|
||||
|
||||
inner_tokens.skip_whitespace();
|
||||
bool legacy_syntax = inner_tokens.peek_token().is(Token::Type::Comma);
|
||||
if (legacy_syntax) {
|
||||
// Legacy syntax
|
||||
// <percentage>#{3} , <alpha-value>?
|
||||
// | <number>#{3} , <alpha-value>?
|
||||
// So, r/g/b can be numbers or percentages, as long as they're all the same type.
|
||||
|
||||
inner_tokens.next_token(); // comma
|
||||
inner_tokens.skip_whitespace();
|
||||
|
||||
green = parse_number_percentage_value(inner_tokens);
|
||||
if (!green)
|
||||
return {};
|
||||
inner_tokens.skip_whitespace();
|
||||
|
||||
if (!inner_tokens.next_token().is(Token::Type::Comma))
|
||||
return {};
|
||||
inner_tokens.skip_whitespace();
|
||||
|
||||
blue = parse_number_percentage_value(inner_tokens);
|
||||
if (!blue)
|
||||
return {};
|
||||
inner_tokens.skip_whitespace();
|
||||
|
||||
if (inner_tokens.has_next_token()) {
|
||||
// Try and read comma and alpha
|
||||
if (!inner_tokens.next_token().is(Token::Type::Comma))
|
||||
return {};
|
||||
inner_tokens.skip_whitespace();
|
||||
|
||||
alpha = parse_number_percentage_value(inner_tokens);
|
||||
inner_tokens.skip_whitespace();
|
||||
|
||||
if (inner_tokens.has_next_token())
|
||||
return {};
|
||||
}
|
||||
|
||||
// Verify we're all percentages or all numbers
|
||||
auto is_percentage = [](CSSStyleValue const& style_value) {
|
||||
return style_value.is_percentage()
|
||||
|| (style_value.is_calculated() && style_value.as_calculated().resolves_to_percentage());
|
||||
};
|
||||
bool red_is_percentage = is_percentage(*red);
|
||||
bool green_is_percentage = is_percentage(*green);
|
||||
bool blue_is_percentage = is_percentage(*blue);
|
||||
if (red_is_percentage != green_is_percentage || red_is_percentage != blue_is_percentage)
|
||||
return {};
|
||||
|
||||
} else {
|
||||
// Modern syntax
|
||||
// [ <number> | <percentage> | none]{3} [ / [<alpha-value> | none] ]?
|
||||
|
||||
green = parse_number_percentage_value(inner_tokens);
|
||||
if (!green)
|
||||
return {};
|
||||
inner_tokens.skip_whitespace();
|
||||
|
||||
blue = parse_number_percentage_value(inner_tokens);
|
||||
if (!blue)
|
||||
return {};
|
||||
inner_tokens.skip_whitespace();
|
||||
|
||||
if (inner_tokens.has_next_token()) {
|
||||
alpha = parse_solidus_and_alpha_value(inner_tokens);
|
||||
if (!alpha || inner_tokens.has_next_token())
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
if (!alpha)
|
||||
alpha = NumberStyleValue::create(1);
|
||||
|
||||
transaction.commit();
|
||||
return CSSRGB::create(red.release_nonnull(), green.release_nonnull(), blue.release_nonnull(), alpha.release_nonnull());
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/css-color-4/#funcdef-hsl
|
||||
RefPtr<CSSStyleValue> Parser::parse_hsl_color_value(TokenStream<ComponentValue>& outer_tokens)
|
||||
{
|
||||
// hsl() = [ <legacy-hsl-syntax> | <modern-hsl-syntax> ]
|
||||
// hsla() = [ <legacy-hsla-syntax> | <modern-hsla-syntax> ]
|
||||
// <modern-hsl-syntax> = hsl(
|
||||
// [<hue> | none]
|
||||
// [<percentage> | <number> | none]
|
||||
// [<percentage> | <number> | none]
|
||||
// [ / [<alpha-value> | none] ]? )
|
||||
// <modern-hsla-syntax> = hsla(
|
||||
// [<hue> | none]
|
||||
// [<percentage> | <number> | none]
|
||||
// [<percentage> | <number> | none]
|
||||
// [ / [<alpha-value> | none] ]? )
|
||||
// <legacy-hsl-syntax> = hsl( <hue>, <percentage>, <percentage>, <alpha-value>? )
|
||||
// <legacy-hsla-syntax> = hsla( <hue>, <percentage>, <percentage>, <alpha-value>? )
|
||||
// TODO: Handle none values
|
||||
|
||||
auto transaction = outer_tokens.begin_transaction();
|
||||
outer_tokens.skip_whitespace();
|
||||
|
||||
auto& function_token = outer_tokens.next_token();
|
||||
if (!function_token.is_function("hsl"sv) && !function_token.is_function("hsla"sv))
|
||||
return {};
|
||||
|
||||
RefPtr<CSSStyleValue> h;
|
||||
RefPtr<CSSStyleValue> s;
|
||||
RefPtr<CSSStyleValue> l;
|
||||
RefPtr<CSSStyleValue> alpha;
|
||||
|
||||
auto inner_tokens = TokenStream { function_token.function().values() };
|
||||
inner_tokens.skip_whitespace();
|
||||
|
||||
h = parse_hue_value(inner_tokens);
|
||||
if (!h)
|
||||
return {};
|
||||
|
||||
inner_tokens.skip_whitespace();
|
||||
bool legacy_syntax = inner_tokens.peek_token().is(Token::Type::Comma);
|
||||
if (legacy_syntax) {
|
||||
// Legacy syntax
|
||||
// <hue>, <percentage>, <percentage>, <alpha-value>?
|
||||
(void)inner_tokens.next_token(); // comma
|
||||
inner_tokens.skip_whitespace();
|
||||
|
||||
s = parse_percentage_value(inner_tokens);
|
||||
if (!s)
|
||||
return {};
|
||||
inner_tokens.skip_whitespace();
|
||||
|
||||
if (!inner_tokens.next_token().is(Token::Type::Comma))
|
||||
return {};
|
||||
inner_tokens.skip_whitespace();
|
||||
|
||||
l = parse_percentage_value(inner_tokens);
|
||||
if (!l)
|
||||
return {};
|
||||
inner_tokens.skip_whitespace();
|
||||
|
||||
if (inner_tokens.has_next_token()) {
|
||||
// Try and read comma and alpha
|
||||
if (!inner_tokens.next_token().is(Token::Type::Comma))
|
||||
return {};
|
||||
inner_tokens.skip_whitespace();
|
||||
|
||||
alpha = parse_number_percentage_value(inner_tokens);
|
||||
inner_tokens.skip_whitespace();
|
||||
|
||||
if (inner_tokens.has_next_token())
|
||||
return {};
|
||||
}
|
||||
} else {
|
||||
// Modern syntax
|
||||
// [<hue> | none]
|
||||
// [<percentage> | <number> | none]
|
||||
// [<percentage> | <number> | none]
|
||||
// [ / [<alpha-value> | none] ]?
|
||||
|
||||
s = parse_number_percentage_value(inner_tokens);
|
||||
if (!s)
|
||||
return {};
|
||||
inner_tokens.skip_whitespace();
|
||||
|
||||
l = parse_number_percentage_value(inner_tokens);
|
||||
if (!l)
|
||||
return {};
|
||||
inner_tokens.skip_whitespace();
|
||||
|
||||
if (inner_tokens.has_next_token()) {
|
||||
alpha = parse_solidus_and_alpha_value(inner_tokens);
|
||||
if (!alpha || inner_tokens.has_next_token())
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
if (!alpha)
|
||||
alpha = NumberStyleValue::create(1);
|
||||
|
||||
transaction.commit();
|
||||
return CSSHSL::create(h.release_nonnull(), s.release_nonnull(), l.release_nonnull(), alpha.release_nonnull());
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/css-color-4/#funcdef-hwb
|
||||
RefPtr<CSSStyleValue> Parser::parse_hwb_color_value(TokenStream<ComponentValue>& outer_tokens)
|
||||
{
|
||||
// hwb() = hwb(
|
||||
// [<hue> | none]
|
||||
// [<percentage> | <number> | none]
|
||||
// [<percentage> | <number> | none]
|
||||
// [ / [<alpha-value> | none] ]? )
|
||||
|
||||
auto transaction = outer_tokens.begin_transaction();
|
||||
outer_tokens.skip_whitespace();
|
||||
|
||||
auto& function_token = outer_tokens.next_token();
|
||||
if (!function_token.is_function("hwb"sv))
|
||||
return {};
|
||||
|
||||
RefPtr<CSSStyleValue> h;
|
||||
RefPtr<CSSStyleValue> w;
|
||||
RefPtr<CSSStyleValue> b;
|
||||
RefPtr<CSSStyleValue> alpha;
|
||||
|
||||
auto inner_tokens = TokenStream { function_token.function().values() };
|
||||
inner_tokens.skip_whitespace();
|
||||
|
||||
h = parse_hue_value(inner_tokens);
|
||||
if (!h)
|
||||
return {};
|
||||
inner_tokens.skip_whitespace();
|
||||
|
||||
w = parse_number_percentage_value(inner_tokens);
|
||||
if (!w)
|
||||
return {};
|
||||
inner_tokens.skip_whitespace();
|
||||
|
||||
b = parse_number_percentage_value(inner_tokens);
|
||||
if (!b)
|
||||
return {};
|
||||
inner_tokens.skip_whitespace();
|
||||
|
||||
if (inner_tokens.has_next_token()) {
|
||||
alpha = parse_solidus_and_alpha_value(inner_tokens);
|
||||
if (!alpha || inner_tokens.has_next_token())
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!alpha)
|
||||
alpha = NumberStyleValue::create(1);
|
||||
|
||||
transaction.commit();
|
||||
return CSSHWB::create(h.release_nonnull(), w.release_nonnull(), b.release_nonnull(), alpha.release_nonnull());
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/css-color-4/#funcdef-oklab
|
||||
RefPtr<CSSStyleValue> Parser::parse_oklab_color_value(TokenStream<ComponentValue>& outer_tokens)
|
||||
{
|
||||
// oklab() = oklab( [ <percentage> | <number> | none]
|
||||
// [ <percentage> | <number> | none]
|
||||
// [ <percentage> | <number> | none]
|
||||
// [ / [<alpha-value> | none] ]? )
|
||||
|
||||
auto transaction = outer_tokens.begin_transaction();
|
||||
outer_tokens.skip_whitespace();
|
||||
|
||||
auto& function_token = outer_tokens.next_token();
|
||||
if (!function_token.is_function("oklab"sv))
|
||||
return {};
|
||||
|
||||
RefPtr<CSSStyleValue> l;
|
||||
RefPtr<CSSStyleValue> a;
|
||||
RefPtr<CSSStyleValue> b;
|
||||
RefPtr<CSSStyleValue> alpha;
|
||||
|
||||
auto inner_tokens = TokenStream { function_token.function().values() };
|
||||
inner_tokens.skip_whitespace();
|
||||
|
||||
l = parse_number_percentage_value(inner_tokens);
|
||||
if (!l)
|
||||
return {};
|
||||
inner_tokens.skip_whitespace();
|
||||
|
||||
a = parse_number_percentage_value(inner_tokens);
|
||||
if (!a)
|
||||
return {};
|
||||
inner_tokens.skip_whitespace();
|
||||
|
||||
b = parse_number_percentage_value(inner_tokens);
|
||||
if (!b)
|
||||
return {};
|
||||
inner_tokens.skip_whitespace();
|
||||
|
||||
if (inner_tokens.has_next_token()) {
|
||||
alpha = parse_solidus_and_alpha_value(inner_tokens);
|
||||
if (!alpha || inner_tokens.has_next_token())
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!alpha)
|
||||
alpha = NumberStyleValue::create(1);
|
||||
|
||||
transaction.commit();
|
||||
return CSSOKLab::create(l.release_nonnull(), a.release_nonnull(), b.release_nonnull(), alpha.release_nonnull());
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/css-color-4/#funcdef-oklch
|
||||
RefPtr<CSSStyleValue> Parser::parse_oklch_color_value(TokenStream<ComponentValue>& outer_tokens)
|
||||
{
|
||||
// oklch() = oklch( [ <percentage> | <number> | none]
|
||||
// [ <percentage> | <number> | none]
|
||||
// [ <hue> | none]
|
||||
// [ / [<alpha-value> | none] ]? )
|
||||
|
||||
auto transaction = outer_tokens.begin_transaction();
|
||||
outer_tokens.skip_whitespace();
|
||||
|
||||
auto& function_token = outer_tokens.next_token();
|
||||
if (!function_token.is_function("oklch"sv))
|
||||
return {};
|
||||
|
||||
RefPtr<CSSStyleValue> l;
|
||||
RefPtr<CSSStyleValue> c;
|
||||
RefPtr<CSSStyleValue> h;
|
||||
RefPtr<CSSStyleValue> alpha;
|
||||
|
||||
auto inner_tokens = TokenStream { function_token.function().values() };
|
||||
inner_tokens.skip_whitespace();
|
||||
|
||||
l = parse_number_percentage_value(inner_tokens);
|
||||
if (!l)
|
||||
return {};
|
||||
inner_tokens.skip_whitespace();
|
||||
|
||||
c = parse_number_percentage_value(inner_tokens);
|
||||
if (!c)
|
||||
return {};
|
||||
inner_tokens.skip_whitespace();
|
||||
|
||||
h = parse_hue_value(inner_tokens);
|
||||
if (!h)
|
||||
return {};
|
||||
inner_tokens.skip_whitespace();
|
||||
|
||||
if (inner_tokens.has_next_token()) {
|
||||
alpha = parse_solidus_and_alpha_value(inner_tokens);
|
||||
if (!alpha || inner_tokens.has_next_token())
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!alpha)
|
||||
alpha = NumberStyleValue::create(1);
|
||||
|
||||
transaction.commit();
|
||||
return CSSOKLCH::create(l.release_nonnull(), c.release_nonnull(), h.release_nonnull(), alpha.release_nonnull());
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/css-color-4/#color-syntax
|
||||
RefPtr<CSSStyleValue> Parser::parse_color_value(TokenStream<ComponentValue>& tokens)
|
||||
{
|
||||
|
||||
// Keywords: <system-color> | <deprecated-color> | currentColor
|
||||
{
|
||||
auto transaction = tokens.begin_transaction();
|
||||
if (auto keyword = parse_keyword_value(tokens); keyword && keyword->has_color()) {
|
||||
transaction.commit();
|
||||
return keyword;
|
||||
}
|
||||
}
|
||||
|
||||
// Functions
|
||||
if (auto rgb = parse_rgb_color_value(tokens))
|
||||
return rgb;
|
||||
if (auto hsl = parse_hsl_color_value(tokens))
|
||||
return hsl;
|
||||
if (auto hwb = parse_hwb_color_value(tokens))
|
||||
return hwb;
|
||||
if (auto oklab = parse_oklab_color_value(tokens))
|
||||
return oklab;
|
||||
if (auto oklch = parse_oklch_color_value(tokens))
|
||||
return oklch;
|
||||
|
||||
auto transaction = tokens.begin_transaction();
|
||||
tokens.skip_whitespace();
|
||||
auto component_value = tokens.next_token();
|
||||
|
||||
// https://www.w3.org/TR/css-color-4/
|
||||
if (component_value.is(Token::Type::Ident)) {
|
||||
auto ident = component_value.token().ident();
|
||||
|
||||
auto color = Color::from_string(ident);
|
||||
if (color.has_value()) {
|
||||
transaction.commit();
|
||||
return color;
|
||||
return CSSColorValue::create_from_color(color.release_value());
|
||||
}
|
||||
// Otherwise, fall through to the hashless-hex-color case
|
||||
} else if (component_value.is(Token::Type::Hash)) {
|
||||
}
|
||||
|
||||
if (component_value.is(Token::Type::Hash)) {
|
||||
auto color = Color::from_string(MUST(String::formatted("#{}", component_value.token().hash_value())));
|
||||
return commit_if_valid(color);
|
||||
} else if (component_value.is_function()) {
|
||||
auto const& function = component_value.function();
|
||||
auto const& values = function.values();
|
||||
auto const function_name = function.name();
|
||||
|
||||
if (function_name.equals_ignoring_ascii_case("rgb"sv) || function_name.equals_ignoring_ascii_case("rgba"sv))
|
||||
return commit_if_valid(parse_rgb_color(values));
|
||||
if (function_name.equals_ignoring_ascii_case("hsl"sv) || function_name.equals_ignoring_ascii_case("hsla"sv))
|
||||
return commit_if_valid(parse_hsl_color(values));
|
||||
if (function_name.equals_ignoring_ascii_case("hwb"sv))
|
||||
return commit_if_valid(parse_hwb_color(values));
|
||||
if (function_name.equals_ignoring_ascii_case("oklab"sv))
|
||||
return commit_if_valid(parse_oklab_color(values));
|
||||
if (function_name.equals_ignoring_ascii_case("oklch"sv))
|
||||
return commit_if_valid(parse_oklch_color(values));
|
||||
|
||||
if (color.has_value()) {
|
||||
transaction.commit();
|
||||
return CSSColorValue::create_from_color(color.release_value());
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
|
@ -3149,26 +3134,16 @@ Optional<Color> Parser::parse_color(TokenStream<ComponentValue>& tokens)
|
|||
}
|
||||
|
||||
// 6. Return the concatenation of "#" (U+0023) and serialization.
|
||||
return commit_if_valid(Color::from_string(MUST(String::formatted("#{}", serialization))));
|
||||
auto color = Color::from_string(MUST(String::formatted("#{}", serialization)));
|
||||
if (color.has_value()) {
|
||||
transaction.commit();
|
||||
return CSSColorValue::create_from_color(color.release_value());
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
RefPtr<CSSStyleValue> Parser::parse_color_value(TokenStream<ComponentValue>& tokens)
|
||||
{
|
||||
if (auto color = parse_color(tokens); color.has_value())
|
||||
return CSSColorValue::create_from_color(color.value());
|
||||
|
||||
auto transaction = tokens.begin_transaction();
|
||||
if (auto keyword = parse_keyword_value(tokens); keyword && keyword->has_color()) {
|
||||
transaction.commit();
|
||||
return keyword;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// https://drafts.csswg.org/css-lists-3/#counter-functions
|
||||
RefPtr<CSSStyleValue> Parser::parse_counter_value(TokenStream<ComponentValue>& tokens)
|
||||
{
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Copyright (c) 2020-2021, the SerenityOS developers.
|
||||
* Copyright (c) 2021-2024, Sam Atkins <atkinssj@serenityos.org>
|
||||
* Copyright (c) 2021-2024, Sam Atkins <sam@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -194,12 +194,6 @@ private:
|
|||
Optional<TimeOrCalculated> parse_time(TokenStream<ComponentValue>&);
|
||||
Optional<TimePercentage> parse_time_percentage(TokenStream<ComponentValue>&);
|
||||
|
||||
Optional<Color> parse_rgb_color(Vector<ComponentValue> const&);
|
||||
Optional<Color> parse_hsl_color(Vector<ComponentValue> const&);
|
||||
Optional<Color> parse_hwb_color(Vector<ComponentValue> const&);
|
||||
Optional<Color> parse_oklab_color(Vector<ComponentValue> const&);
|
||||
Optional<Color> parse_oklch_color(Vector<ComponentValue> const&);
|
||||
Optional<Color> parse_color(TokenStream<ComponentValue>&);
|
||||
Optional<LengthOrCalculated> parse_source_size_value(TokenStream<ComponentValue>&);
|
||||
Optional<Ratio> parse_ratio(TokenStream<ComponentValue>&);
|
||||
Optional<Gfx::UnicodeRange> parse_unicode_range(TokenStream<ComponentValue>&);
|
||||
|
@ -239,6 +233,13 @@ private:
|
|||
OwnPtr<CalculationNode> parse_math_function(PropertyID, Function const&);
|
||||
OwnPtr<CalculationNode> parse_a_calc_function_node(Function const&);
|
||||
RefPtr<CSSStyleValue> parse_keyword_value(TokenStream<ComponentValue>&);
|
||||
RefPtr<CSSStyleValue> parse_hue_value(TokenStream<ComponentValue>&);
|
||||
RefPtr<CSSStyleValue> parse_solidus_and_alpha_value(TokenStream<ComponentValue>&);
|
||||
RefPtr<CSSStyleValue> parse_rgb_color_value(TokenStream<ComponentValue>&);
|
||||
RefPtr<CSSStyleValue> parse_hsl_color_value(TokenStream<ComponentValue>&);
|
||||
RefPtr<CSSStyleValue> parse_hwb_color_value(TokenStream<ComponentValue>&);
|
||||
RefPtr<CSSStyleValue> parse_oklab_color_value(TokenStream<ComponentValue>&);
|
||||
RefPtr<CSSStyleValue> parse_oklch_color_value(TokenStream<ComponentValue>&);
|
||||
RefPtr<CSSStyleValue> parse_color_value(TokenStream<ComponentValue>&);
|
||||
RefPtr<CSSStyleValue> parse_counter_value(TokenStream<ComponentValue>&);
|
||||
enum class AllowReversed {
|
||||
|
|
|
@ -1431,8 +1431,12 @@ static NonnullRefPtr<CSSStyleValue const> interpolate_value(DOM::Element& elemen
|
|||
switch (from.type()) {
|
||||
case CSSStyleValue::Type::Angle:
|
||||
return AngleStyleValue::create(Angle::make_degrees(interpolate_raw(from.as_angle().angle().to_degrees(), to.as_angle().angle().to_degrees(), delta)));
|
||||
case CSSStyleValue::Type::Color:
|
||||
return CSSColorValue::create_from_color(interpolate_color(from.as_color().color(), to.as_color().color(), delta));
|
||||
case CSSStyleValue::Type::Color: {
|
||||
Optional<Layout::NodeWithStyle const&> layout_node;
|
||||
if (auto node = element.layout_node())
|
||||
layout_node = *node;
|
||||
return CSSColorValue::create_from_color(interpolate_color(from.to_color(layout_node), to.to_color(layout_node), delta));
|
||||
}
|
||||
case CSSStyleValue::Type::Integer:
|
||||
return IntegerStyleValue::create(interpolate_raw(from.as_integer().integer(), to.as_integer().integer(), delta));
|
||||
case CSSStyleValue::Type::Length: {
|
||||
|
|
|
@ -9,32 +9,116 @@
|
|||
|
||||
#include "CSSColorValue.h"
|
||||
#include <LibWeb/CSS/Serialize.h>
|
||||
#include <LibWeb/CSS/StyleValues/AngleStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/CSSRGB.h>
|
||||
#include <LibWeb/CSS/StyleValues/CalculatedStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/NumberStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/PercentageStyleValue.h>
|
||||
|
||||
namespace Web::CSS {
|
||||
|
||||
ValueComparingNonnullRefPtr<CSSColorValue> CSSColorValue::create_from_color(Color color)
|
||||
{
|
||||
auto make_rgb_color = [](Color const& color) {
|
||||
return CSSRGB::create(
|
||||
NumberStyleValue::create(color.red()),
|
||||
NumberStyleValue::create(color.green()),
|
||||
NumberStyleValue::create(color.blue()),
|
||||
NumberStyleValue::create(color.alpha() / 255.0));
|
||||
};
|
||||
|
||||
if (color.value() == 0) {
|
||||
static auto transparent = adopt_ref(*new (nothrow) CSSColorValue(color));
|
||||
static auto transparent = make_rgb_color(color);
|
||||
return transparent;
|
||||
}
|
||||
|
||||
if (color == Color::from_rgb(0x000000)) {
|
||||
static auto black = adopt_ref(*new (nothrow) CSSColorValue(color));
|
||||
static auto black = make_rgb_color(color);
|
||||
return black;
|
||||
}
|
||||
|
||||
if (color == Color::from_rgb(0xffffff)) {
|
||||
static auto white = adopt_ref(*new (nothrow) CSSColorValue(color));
|
||||
static auto white = make_rgb_color(color);
|
||||
return white;
|
||||
}
|
||||
|
||||
return adopt_ref(*new (nothrow) CSSColorValue(color));
|
||||
return make_rgb_color(color);
|
||||
}
|
||||
|
||||
String CSSColorValue::to_string() const
|
||||
Optional<float> CSSColorValue::resolve_hue(CSSStyleValue const& style_value)
|
||||
{
|
||||
return serialize_a_srgb_value(m_color);
|
||||
// <number> | <angle> | none
|
||||
auto normalized = [](double number) {
|
||||
return fmod(number, 360.0);
|
||||
};
|
||||
|
||||
if (style_value.is_number())
|
||||
return normalized(style_value.as_number().number());
|
||||
|
||||
if (style_value.is_angle())
|
||||
return normalized(style_value.as_angle().angle().to_degrees());
|
||||
|
||||
if (style_value.is_calculated() && style_value.as_calculated().resolves_to_angle())
|
||||
return normalized(style_value.as_calculated().resolve_angle().value().to_degrees());
|
||||
|
||||
if (style_value.is_keyword() && style_value.to_keyword() == Keyword::None)
|
||||
return 0;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
Optional<float> CSSColorValue::resolve_with_reference_value(CSSStyleValue const& style_value, float one_hundred_percent_value)
|
||||
{
|
||||
// <percentage> | <number> | none
|
||||
auto normalize_percentage = [one_hundred_percent_value](Percentage const& percentage) {
|
||||
return percentage.as_fraction() * one_hundred_percent_value;
|
||||
};
|
||||
|
||||
if (style_value.is_percentage())
|
||||
return normalize_percentage(style_value.as_percentage().percentage());
|
||||
|
||||
if (style_value.is_number())
|
||||
return style_value.as_number().number();
|
||||
|
||||
if (style_value.is_calculated()) {
|
||||
auto const& calculated = style_value.as_calculated();
|
||||
if (calculated.resolves_to_number())
|
||||
return calculated.resolve_number().value();
|
||||
if (calculated.resolves_to_percentage())
|
||||
return normalize_percentage(calculated.resolve_percentage().value());
|
||||
}
|
||||
|
||||
if (style_value.is_keyword() && style_value.to_keyword() == Keyword::None)
|
||||
return 0;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
Optional<float> CSSColorValue::resolve_alpha(CSSStyleValue const& style_value)
|
||||
{
|
||||
// <number> | <percentage> | none
|
||||
auto normalized = [](double number) {
|
||||
return clamp(number, 0.0, 1.0);
|
||||
};
|
||||
|
||||
if (style_value.is_number())
|
||||
return normalized(style_value.as_number().number());
|
||||
|
||||
if (style_value.is_percentage())
|
||||
return normalized(style_value.as_percentage().percentage().as_fraction());
|
||||
|
||||
if (style_value.is_calculated()) {
|
||||
auto const& calculated = style_value.as_calculated();
|
||||
if (calculated.resolves_to_number())
|
||||
return normalized(calculated.resolve_number().value());
|
||||
if (calculated.resolves_to_percentage())
|
||||
return normalized(calculated.resolve_percentage().value().as_fraction());
|
||||
}
|
||||
|
||||
if (style_value.is_keyword() && style_value.to_keyword() == Keyword::None)
|
||||
return 0;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -15,26 +15,35 @@
|
|||
namespace Web::CSS {
|
||||
|
||||
// https://drafts.css-houdini.org/css-typed-om-1/#csscolorvalue
|
||||
class CSSColorValue : public StyleValueWithDefaultOperators<CSSColorValue> {
|
||||
class CSSColorValue : public CSSStyleValue {
|
||||
public:
|
||||
static ValueComparingNonnullRefPtr<CSSColorValue> create_from_color(Color color);
|
||||
virtual ~CSSColorValue() override = default;
|
||||
|
||||
Color color() const { return m_color; }
|
||||
virtual String to_string() const override;
|
||||
virtual bool has_color() const override { return true; }
|
||||
virtual Color to_color(Optional<Layout::NodeWithStyle const&>) const override { return m_color; }
|
||||
|
||||
bool properties_equal(CSSColorValue const& other) const { return m_color == other.m_color; }
|
||||
enum class ColorType {
|
||||
RGB,
|
||||
HSL,
|
||||
HWB,
|
||||
OKLab,
|
||||
OKLCH,
|
||||
};
|
||||
ColorType color_type() const { return m_color_type; }
|
||||
|
||||
private:
|
||||
explicit CSSColorValue(Color color)
|
||||
: StyleValueWithDefaultOperators(Type::Color)
|
||||
, m_color(color)
|
||||
protected:
|
||||
explicit CSSColorValue(ColorType color_type)
|
||||
: CSSStyleValue(Type::Color)
|
||||
, m_color_type(color_type)
|
||||
{
|
||||
}
|
||||
|
||||
Color m_color;
|
||||
static Optional<float> resolve_hue(CSSStyleValue const&);
|
||||
static Optional<float> resolve_with_reference_value(CSSStyleValue const&, float one_hundred_percent_value);
|
||||
static Optional<float> resolve_alpha(CSSStyleValue const&);
|
||||
|
||||
private:
|
||||
ColorType m_color_type;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
41
Userland/Libraries/LibWeb/CSS/StyleValues/CSSHSL.cpp
Normal file
41
Userland/Libraries/LibWeb/CSS/StyleValues/CSSHSL.cpp
Normal file
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Sam Atkins <sam@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "CSSHSL.h"
|
||||
#include <AK/TypeCasts.h>
|
||||
#include <LibWeb/CSS/Serialize.h>
|
||||
|
||||
namespace Web::CSS {
|
||||
|
||||
Color CSSHSL::to_color(Optional<Layout::NodeWithStyle const&>) const
|
||||
{
|
||||
auto const h_val = resolve_hue(m_properties.h).value_or(0);
|
||||
auto const s_val = resolve_with_reference_value(m_properties.s, 100.0).value_or(0);
|
||||
auto const l_val = resolve_with_reference_value(m_properties.l, 100.0).value_or(0);
|
||||
auto const alpha_val = resolve_alpha(m_properties.alpha).value_or(1);
|
||||
|
||||
return Color::from_hsla(h_val, s_val / 100.0f, l_val / 100.0f, alpha_val);
|
||||
}
|
||||
|
||||
bool CSSHSL::equals(CSSStyleValue const& other) const
|
||||
{
|
||||
if (type() != other.type())
|
||||
return false;
|
||||
auto const& other_color = other.as_color();
|
||||
if (color_type() != other_color.color_type())
|
||||
return false;
|
||||
auto const& other_hsl = verify_cast<CSSHSL>(other_color);
|
||||
return m_properties == other_hsl.m_properties;
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/css-color-4/#serializing-sRGB-values
|
||||
String CSSHSL::to_string() const
|
||||
{
|
||||
// FIXME: Do this properly, taking unresolved calculated values into account.
|
||||
return serialize_a_srgb_value(to_color({}));
|
||||
}
|
||||
|
||||
}
|
54
Userland/Libraries/LibWeb/CSS/StyleValues/CSSHSL.h
Normal file
54
Userland/Libraries/LibWeb/CSS/StyleValues/CSSHSL.h
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Sam Atkins <sam@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibWeb/CSS/StyleValues/CSSColorValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/NumberStyleValue.h>
|
||||
|
||||
namespace Web::CSS {
|
||||
|
||||
// https://drafts.css-houdini.org/css-typed-om-1/#csshsl
|
||||
class CSSHSL final : public CSSColorValue {
|
||||
public:
|
||||
static ValueComparingNonnullRefPtr<CSSHSL> create(ValueComparingNonnullRefPtr<CSSStyleValue> h, ValueComparingNonnullRefPtr<CSSStyleValue> s, ValueComparingNonnullRefPtr<CSSStyleValue> l, ValueComparingRefPtr<CSSStyleValue> alpha = {})
|
||||
{
|
||||
// alpha defaults to 1
|
||||
if (!alpha)
|
||||
return adopt_ref(*new (nothrow) CSSHSL(move(h), move(s), move(l), NumberStyleValue::create(1)));
|
||||
|
||||
return adopt_ref(*new (nothrow) CSSHSL(move(h), move(s), move(l), alpha.release_nonnull()));
|
||||
}
|
||||
virtual ~CSSHSL() override = default;
|
||||
|
||||
CSSStyleValue const& h() const { return *m_properties.h; }
|
||||
CSSStyleValue const& s() const { return *m_properties.s; }
|
||||
CSSStyleValue const& l() const { return *m_properties.l; }
|
||||
CSSStyleValue const& alpha() const { return *m_properties.alpha; }
|
||||
|
||||
virtual Color to_color(Optional<Layout::NodeWithStyle const&>) const override;
|
||||
|
||||
String to_string() const override;
|
||||
|
||||
virtual bool equals(CSSStyleValue const& other) const override;
|
||||
|
||||
private:
|
||||
CSSHSL(ValueComparingNonnullRefPtr<CSSStyleValue> h, ValueComparingNonnullRefPtr<CSSStyleValue> s, ValueComparingNonnullRefPtr<CSSStyleValue> l, ValueComparingNonnullRefPtr<CSSStyleValue> alpha)
|
||||
: CSSColorValue(ColorType::HSL)
|
||||
, m_properties { .h = move(h), .s = move(s), .l = move(l), .alpha = move(alpha) }
|
||||
{
|
||||
}
|
||||
|
||||
struct Properties {
|
||||
ValueComparingNonnullRefPtr<CSSStyleValue> h;
|
||||
ValueComparingNonnullRefPtr<CSSStyleValue> s;
|
||||
ValueComparingNonnullRefPtr<CSSStyleValue> l;
|
||||
ValueComparingNonnullRefPtr<CSSStyleValue> alpha;
|
||||
bool operator==(Properties const&) const = default;
|
||||
} m_properties;
|
||||
};
|
||||
|
||||
}
|
51
Userland/Libraries/LibWeb/CSS/StyleValues/CSSHWB.cpp
Normal file
51
Userland/Libraries/LibWeb/CSS/StyleValues/CSSHWB.cpp
Normal file
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Sam Atkins <sam@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "CSSHWB.h"
|
||||
#include <AK/TypeCasts.h>
|
||||
#include <LibWeb/CSS/Serialize.h>
|
||||
|
||||
namespace Web::CSS {
|
||||
|
||||
Color CSSHWB::to_color(Optional<Layout::NodeWithStyle const&>) const
|
||||
{
|
||||
auto const h_val = resolve_hue(m_properties.h).value_or(0);
|
||||
auto const w_val = clamp(resolve_with_reference_value(m_properties.w, 100.0).value_or(0), 0, 100) / 100.0;
|
||||
auto const b_val = clamp(resolve_with_reference_value(m_properties.b, 100.0).value_or(0), 0, 100) / 100.0;
|
||||
auto const alpha_val = resolve_alpha(m_properties.alpha).value_or(1);
|
||||
|
||||
if (w_val + b_val >= 1.0) {
|
||||
auto to_byte = [](double value) {
|
||||
return round_to<u8>(clamp(value * 255.0, 0.0, 255.0));
|
||||
};
|
||||
u8 gray = to_byte(w_val / (w_val + b_val));
|
||||
return Color(gray, gray, gray, to_byte(alpha_val));
|
||||
}
|
||||
|
||||
float value = 1 - b_val;
|
||||
float saturation = 1 - (w_val / value);
|
||||
return Color::from_hsv(h_val, saturation, value).with_opacity(alpha_val);
|
||||
}
|
||||
|
||||
bool CSSHWB::equals(CSSStyleValue const& other) const
|
||||
{
|
||||
if (type() != other.type())
|
||||
return false;
|
||||
auto const& other_color = other.as_color();
|
||||
if (color_type() != other_color.color_type())
|
||||
return false;
|
||||
auto const& other_hwb = verify_cast<CSSHWB>(other_color);
|
||||
return m_properties == other_hwb.m_properties;
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/css-color-4/#serializing-sRGB-values
|
||||
String CSSHWB::to_string() const
|
||||
{
|
||||
// FIXME: Do this properly, taking unresolved calculated values into account.
|
||||
return serialize_a_srgb_value(to_color({}));
|
||||
}
|
||||
|
||||
}
|
54
Userland/Libraries/LibWeb/CSS/StyleValues/CSSHWB.h
Normal file
54
Userland/Libraries/LibWeb/CSS/StyleValues/CSSHWB.h
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Sam Atkins <sam@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibWeb/CSS/StyleValues/CSSColorValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/NumberStyleValue.h>
|
||||
|
||||
namespace Web::CSS {
|
||||
|
||||
// https://drafts.css-houdini.org/css-typed-om-1/#csshwb
|
||||
class CSSHWB final : public CSSColorValue {
|
||||
public:
|
||||
static ValueComparingNonnullRefPtr<CSSHWB> create(ValueComparingNonnullRefPtr<CSSStyleValue> h, ValueComparingNonnullRefPtr<CSSStyleValue> w, ValueComparingNonnullRefPtr<CSSStyleValue> b, ValueComparingRefPtr<CSSStyleValue> alpha = {})
|
||||
{
|
||||
// alpha defaults to 1
|
||||
if (!alpha)
|
||||
return adopt_ref(*new (nothrow) CSSHWB(move(h), move(w), move(b), NumberStyleValue::create(1)));
|
||||
|
||||
return adopt_ref(*new (nothrow) CSSHWB(move(h), move(w), move(b), alpha.release_nonnull()));
|
||||
}
|
||||
virtual ~CSSHWB() override = default;
|
||||
|
||||
CSSStyleValue const& h() const { return *m_properties.h; }
|
||||
CSSStyleValue const& w() const { return *m_properties.w; }
|
||||
CSSStyleValue const& b() const { return *m_properties.b; }
|
||||
CSSStyleValue const& alpha() const { return *m_properties.alpha; }
|
||||
|
||||
virtual Color to_color(Optional<Layout::NodeWithStyle const&>) const override;
|
||||
|
||||
String to_string() const override;
|
||||
|
||||
virtual bool equals(CSSStyleValue const& other) const override;
|
||||
|
||||
private:
|
||||
CSSHWB(ValueComparingNonnullRefPtr<CSSStyleValue> h, ValueComparingNonnullRefPtr<CSSStyleValue> w, ValueComparingNonnullRefPtr<CSSStyleValue> b, ValueComparingNonnullRefPtr<CSSStyleValue> alpha)
|
||||
: CSSColorValue(ColorType::HWB)
|
||||
, m_properties { .h = move(h), .w = move(w), .b = move(b), .alpha = move(alpha) }
|
||||
{
|
||||
}
|
||||
|
||||
struct Properties {
|
||||
ValueComparingNonnullRefPtr<CSSStyleValue> h;
|
||||
ValueComparingNonnullRefPtr<CSSStyleValue> w;
|
||||
ValueComparingNonnullRefPtr<CSSStyleValue> b;
|
||||
ValueComparingNonnullRefPtr<CSSStyleValue> alpha;
|
||||
bool operator==(Properties const&) const = default;
|
||||
} m_properties;
|
||||
};
|
||||
|
||||
}
|
45
Userland/Libraries/LibWeb/CSS/StyleValues/CSSOKLCH.cpp
Normal file
45
Userland/Libraries/LibWeb/CSS/StyleValues/CSSOKLCH.cpp
Normal file
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Sam Atkins <sam@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "CSSOKLCH.h"
|
||||
#include <AK/Math.h>
|
||||
#include <AK/TypeCasts.h>
|
||||
#include <LibWeb/CSS/Serialize.h>
|
||||
#include <LibWeb/CSS/StyleValues/CalculatedStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/NumberStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/PercentageStyleValue.h>
|
||||
|
||||
namespace Web::CSS {
|
||||
|
||||
Color CSSOKLCH::to_color(Optional<Layout::NodeWithStyle const&>) const
|
||||
{
|
||||
auto const l_val = clamp(resolve_with_reference_value(m_properties.l, 1.0).value_or(0), 0, 1);
|
||||
auto const c_val = max(resolve_with_reference_value(m_properties.c, 0.4).value_or(0), 0);
|
||||
auto const h_val = AK::to_radians(resolve_hue(m_properties.h).value_or(0));
|
||||
auto const alpha_val = resolve_alpha(m_properties.alpha).value_or(1);
|
||||
|
||||
return Color::from_oklab(l_val, c_val * cos(h_val), c_val * sin(h_val), alpha_val);
|
||||
}
|
||||
|
||||
bool CSSOKLCH::equals(CSSStyleValue const& other) const
|
||||
{
|
||||
if (type() != other.type())
|
||||
return false;
|
||||
auto const& other_color = other.as_color();
|
||||
if (color_type() != other_color.color_type())
|
||||
return false;
|
||||
auto const& other_oklch = verify_cast<CSSOKLCH>(other_color);
|
||||
return m_properties == other_oklch.m_properties;
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/css-color-4/#serializing-oklab-oklch
|
||||
String CSSOKLCH::to_string() const
|
||||
{
|
||||
// FIXME: Do this properly, taking unresolved calculated values into account.
|
||||
return serialize_a_srgb_value(to_color({}));
|
||||
}
|
||||
|
||||
}
|
54
Userland/Libraries/LibWeb/CSS/StyleValues/CSSOKLCH.h
Normal file
54
Userland/Libraries/LibWeb/CSS/StyleValues/CSSOKLCH.h
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Sam Atkins <sam@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibWeb/CSS/StyleValues/CSSColorValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/NumberStyleValue.h>
|
||||
|
||||
namespace Web::CSS {
|
||||
|
||||
// https://drafts.css-houdini.org/css-typed-om-1/#cssoklch
|
||||
class CSSOKLCH final : public CSSColorValue {
|
||||
public:
|
||||
static ValueComparingNonnullRefPtr<CSSOKLCH> create(ValueComparingNonnullRefPtr<CSSStyleValue> l, ValueComparingNonnullRefPtr<CSSStyleValue> c, ValueComparingNonnullRefPtr<CSSStyleValue> h, ValueComparingRefPtr<CSSStyleValue> alpha = {})
|
||||
{
|
||||
// alpha defaults to 1
|
||||
if (!alpha)
|
||||
return adopt_ref(*new (nothrow) CSSOKLCH(move(l), move(c), move(h), NumberStyleValue::create(1)));
|
||||
|
||||
return adopt_ref(*new (nothrow) CSSOKLCH(move(l), move(c), move(h), alpha.release_nonnull()));
|
||||
}
|
||||
virtual ~CSSOKLCH() override = default;
|
||||
|
||||
CSSStyleValue const& l() const { return *m_properties.l; }
|
||||
CSSStyleValue const& c() const { return *m_properties.c; }
|
||||
CSSStyleValue const& h() const { return *m_properties.h; }
|
||||
CSSStyleValue const& alpha() const { return *m_properties.alpha; }
|
||||
|
||||
virtual Color to_color(Optional<Layout::NodeWithStyle const&>) const override;
|
||||
|
||||
String to_string() const override;
|
||||
|
||||
virtual bool equals(CSSStyleValue const& other) const override;
|
||||
|
||||
private:
|
||||
CSSOKLCH(ValueComparingNonnullRefPtr<CSSStyleValue> l, ValueComparingNonnullRefPtr<CSSStyleValue> c, ValueComparingNonnullRefPtr<CSSStyleValue> h, ValueComparingNonnullRefPtr<CSSStyleValue> alpha)
|
||||
: CSSColorValue(ColorType::OKLCH)
|
||||
, m_properties { .l = move(l), .c = move(c), .h = move(h), .alpha = move(alpha) }
|
||||
{
|
||||
}
|
||||
|
||||
struct Properties {
|
||||
ValueComparingNonnullRefPtr<CSSStyleValue> l;
|
||||
ValueComparingNonnullRefPtr<CSSStyleValue> c;
|
||||
ValueComparingNonnullRefPtr<CSSStyleValue> h;
|
||||
ValueComparingNonnullRefPtr<CSSStyleValue> alpha;
|
||||
bool operator==(Properties const&) const = default;
|
||||
} m_properties;
|
||||
};
|
||||
|
||||
}
|
44
Userland/Libraries/LibWeb/CSS/StyleValues/CSSOKLab.cpp
Normal file
44
Userland/Libraries/LibWeb/CSS/StyleValues/CSSOKLab.cpp
Normal file
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Sam Atkins <sam@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "CSSOKLab.h"
|
||||
#include <AK/TypeCasts.h>
|
||||
#include <LibWeb/CSS/Serialize.h>
|
||||
#include <LibWeb/CSS/StyleValues/CalculatedStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/NumberStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/PercentageStyleValue.h>
|
||||
|
||||
namespace Web::CSS {
|
||||
|
||||
Color CSSOKLab::to_color(Optional<Layout::NodeWithStyle const&>) const
|
||||
{
|
||||
auto const l_val = clamp(resolve_with_reference_value(m_properties.l, 1.0).value_or(0), 0, 1);
|
||||
auto const a_val = resolve_with_reference_value(m_properties.a, 0.4).value_or(0);
|
||||
auto const b_val = resolve_with_reference_value(m_properties.b, 0.4).value_or(0);
|
||||
auto const alpha_val = resolve_alpha(m_properties.alpha).value_or(1);
|
||||
|
||||
return Color::from_oklab(l_val, a_val, b_val, alpha_val);
|
||||
}
|
||||
|
||||
bool CSSOKLab::equals(CSSStyleValue const& other) const
|
||||
{
|
||||
if (type() != other.type())
|
||||
return false;
|
||||
auto const& other_color = other.as_color();
|
||||
if (color_type() != other_color.color_type())
|
||||
return false;
|
||||
auto const& other_oklab = verify_cast<CSSOKLab>(other_color);
|
||||
return m_properties == other_oklab.m_properties;
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/css-color-4/#serializing-oklab-oklch
|
||||
String CSSOKLab::to_string() const
|
||||
{
|
||||
// FIXME: Do this properly, taking unresolved calculated values into account.
|
||||
return serialize_a_srgb_value(to_color({}));
|
||||
}
|
||||
|
||||
}
|
54
Userland/Libraries/LibWeb/CSS/StyleValues/CSSOKLab.h
Normal file
54
Userland/Libraries/LibWeb/CSS/StyleValues/CSSOKLab.h
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Sam Atkins <sam@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibWeb/CSS/StyleValues/CSSColorValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/NumberStyleValue.h>
|
||||
|
||||
namespace Web::CSS {
|
||||
|
||||
// https://drafts.css-houdini.org/css-typed-om-1/#cssoklab
|
||||
class CSSOKLab final : public CSSColorValue {
|
||||
public:
|
||||
static ValueComparingNonnullRefPtr<CSSOKLab> create(ValueComparingNonnullRefPtr<CSSStyleValue> l, ValueComparingNonnullRefPtr<CSSStyleValue> a, ValueComparingNonnullRefPtr<CSSStyleValue> b, ValueComparingRefPtr<CSSStyleValue> alpha = {})
|
||||
{
|
||||
// alpha defaults to 1
|
||||
if (!alpha)
|
||||
return adopt_ref(*new (nothrow) CSSOKLab(move(l), move(a), move(b), NumberStyleValue::create(1)));
|
||||
|
||||
return adopt_ref(*new (nothrow) CSSOKLab(move(l), move(a), move(b), alpha.release_nonnull()));
|
||||
}
|
||||
virtual ~CSSOKLab() override = default;
|
||||
|
||||
CSSStyleValue const& l() const { return *m_properties.l; }
|
||||
CSSStyleValue const& a() const { return *m_properties.a; }
|
||||
CSSStyleValue const& b() const { return *m_properties.b; }
|
||||
CSSStyleValue const& alpha() const { return *m_properties.alpha; }
|
||||
|
||||
virtual Color to_color(Optional<Layout::NodeWithStyle const&>) const override;
|
||||
|
||||
String to_string() const override;
|
||||
|
||||
virtual bool equals(CSSStyleValue const& other) const override;
|
||||
|
||||
private:
|
||||
CSSOKLab(ValueComparingNonnullRefPtr<CSSStyleValue> l, ValueComparingNonnullRefPtr<CSSStyleValue> a, ValueComparingNonnullRefPtr<CSSStyleValue> b, ValueComparingNonnullRefPtr<CSSStyleValue> alpha)
|
||||
: CSSColorValue(ColorType::OKLab)
|
||||
, m_properties { .l = move(l), .a = move(a), .b = move(b), .alpha = move(alpha) }
|
||||
{
|
||||
}
|
||||
|
||||
struct Properties {
|
||||
ValueComparingNonnullRefPtr<CSSStyleValue> l;
|
||||
ValueComparingNonnullRefPtr<CSSStyleValue> a;
|
||||
ValueComparingNonnullRefPtr<CSSStyleValue> b;
|
||||
ValueComparingNonnullRefPtr<CSSStyleValue> alpha;
|
||||
bool operator==(Properties const&) const = default;
|
||||
} m_properties;
|
||||
};
|
||||
|
||||
}
|
77
Userland/Libraries/LibWeb/CSS/StyleValues/CSSRGB.cpp
Normal file
77
Userland/Libraries/LibWeb/CSS/StyleValues/CSSRGB.cpp
Normal file
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Sam Atkins <sam@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "CSSRGB.h"
|
||||
#include <AK/TypeCasts.h>
|
||||
#include <LibWeb/CSS/Serialize.h>
|
||||
#include <LibWeb/CSS/StyleValues/CalculatedStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/NumberStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/PercentageStyleValue.h>
|
||||
|
||||
namespace Web::CSS {
|
||||
|
||||
Color CSSRGB::to_color(Optional<Layout::NodeWithStyle const&>) const
|
||||
{
|
||||
auto resolve_rgb_to_u8 = [](CSSStyleValue const& style_value) -> Optional<u8> {
|
||||
// <number> | <percentage> | none
|
||||
auto normalized = [](double number) {
|
||||
return llround(clamp(number, 0.0, 255.0));
|
||||
};
|
||||
|
||||
if (style_value.is_number())
|
||||
return normalized(style_value.as_number().number());
|
||||
|
||||
if (style_value.is_percentage())
|
||||
return normalized(style_value.as_percentage().value() * 2.55);
|
||||
|
||||
if (style_value.is_calculated()) {
|
||||
auto const& calculated = style_value.as_calculated();
|
||||
if (calculated.resolves_to_number())
|
||||
return normalized(calculated.resolve_number().value());
|
||||
if (calculated.resolves_to_percentage())
|
||||
return normalized(calculated.resolve_percentage().value().value() * 2.55);
|
||||
}
|
||||
|
||||
if (style_value.is_keyword() && style_value.to_keyword() == Keyword::None)
|
||||
return 0;
|
||||
|
||||
return {};
|
||||
};
|
||||
|
||||
auto resolve_alpha_to_u8 = [](CSSStyleValue const& style_value) -> Optional<u8> {
|
||||
auto alpha_0_1 = resolve_alpha(style_value);
|
||||
if (alpha_0_1.has_value())
|
||||
return llround(clamp(alpha_0_1.value() * 255.0, 0.0, 255.0));
|
||||
return {};
|
||||
};
|
||||
|
||||
u8 const r_val = resolve_rgb_to_u8(m_properties.r).value_or(0);
|
||||
u8 const g_val = resolve_rgb_to_u8(m_properties.g).value_or(0);
|
||||
u8 const b_val = resolve_rgb_to_u8(m_properties.b).value_or(0);
|
||||
u8 const alpha_val = resolve_alpha_to_u8(m_properties.alpha).value_or(255);
|
||||
|
||||
return Color(r_val, g_val, b_val, alpha_val);
|
||||
}
|
||||
|
||||
bool CSSRGB::equals(CSSStyleValue const& other) const
|
||||
{
|
||||
if (type() != other.type())
|
||||
return false;
|
||||
auto const& other_color = other.as_color();
|
||||
if (color_type() != other_color.color_type())
|
||||
return false;
|
||||
auto const& other_rgb = verify_cast<CSSRGB>(other_color);
|
||||
return m_properties == other_rgb.m_properties;
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/css-color-4/#serializing-sRGB-values
|
||||
String CSSRGB::to_string() const
|
||||
{
|
||||
// FIXME: Do this properly, taking unresolved calculated values into account.
|
||||
return serialize_a_srgb_value(to_color({}));
|
||||
}
|
||||
|
||||
}
|
54
Userland/Libraries/LibWeb/CSS/StyleValues/CSSRGB.h
Normal file
54
Userland/Libraries/LibWeb/CSS/StyleValues/CSSRGB.h
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Sam Atkins <sam@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibWeb/CSS/StyleValues/CSSColorValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/NumberStyleValue.h>
|
||||
|
||||
namespace Web::CSS {
|
||||
|
||||
// https://drafts.css-houdini.org/css-typed-om-1/#cssrgb
|
||||
class CSSRGB final : public CSSColorValue {
|
||||
public:
|
||||
static ValueComparingNonnullRefPtr<CSSRGB> create(ValueComparingNonnullRefPtr<CSSStyleValue> r, ValueComparingNonnullRefPtr<CSSStyleValue> g, ValueComparingNonnullRefPtr<CSSStyleValue> b, ValueComparingRefPtr<CSSStyleValue> alpha = {})
|
||||
{
|
||||
// alpha defaults to 1
|
||||
if (!alpha)
|
||||
return adopt_ref(*new (nothrow) CSSRGB(move(r), move(g), move(b), NumberStyleValue::create(1)));
|
||||
|
||||
return adopt_ref(*new (nothrow) CSSRGB(move(r), move(g), move(b), alpha.release_nonnull()));
|
||||
}
|
||||
virtual ~CSSRGB() override = default;
|
||||
|
||||
CSSStyleValue const& r() const { return *m_properties.r; }
|
||||
CSSStyleValue const& g() const { return *m_properties.g; }
|
||||
CSSStyleValue const& b() const { return *m_properties.b; }
|
||||
CSSStyleValue const& alpha() const { return *m_properties.alpha; }
|
||||
|
||||
virtual Color to_color(Optional<Layout::NodeWithStyle const&>) const override;
|
||||
|
||||
String to_string() const override;
|
||||
|
||||
virtual bool equals(CSSStyleValue const& other) const override;
|
||||
|
||||
private:
|
||||
CSSRGB(ValueComparingNonnullRefPtr<CSSStyleValue> r, ValueComparingNonnullRefPtr<CSSStyleValue> g, ValueComparingNonnullRefPtr<CSSStyleValue> b, ValueComparingNonnullRefPtr<CSSStyleValue> alpha)
|
||||
: CSSColorValue(ColorType::RGB)
|
||||
, m_properties { .r = move(r), .g = move(g), .b = move(b), .alpha = move(alpha) }
|
||||
{
|
||||
}
|
||||
|
||||
struct Properties {
|
||||
ValueComparingNonnullRefPtr<CSSStyleValue> r;
|
||||
ValueComparingNonnullRefPtr<CSSStyleValue> g;
|
||||
ValueComparingNonnullRefPtr<CSSStyleValue> b;
|
||||
ValueComparingNonnullRefPtr<CSSStyleValue> alpha;
|
||||
bool operator==(Properties const&) const = default;
|
||||
} m_properties;
|
||||
};
|
||||
|
||||
}
|
|
@ -106,11 +106,16 @@ class CSSColorValue;
|
|||
class CSSConditionRule;
|
||||
class CSSFontFaceRule;
|
||||
class CSSGroupingRule;
|
||||
class CSSHSL;
|
||||
class CSSHWB;
|
||||
class CSSImportRule;
|
||||
class CSSKeyframeRule;
|
||||
class CSSKeyframesRule;
|
||||
class CSSKeywordValue;
|
||||
class CSSMediaRule;
|
||||
class CSSOKLab;
|
||||
class CSSOKLCH;
|
||||
class CSSRGB;
|
||||
class CSSRule;
|
||||
class CSSRuleList;
|
||||
class CSSStyleDeclaration;
|
||||
|
|
|
@ -76,7 +76,7 @@ void HTMLMetaElement::inserted()
|
|||
auto css_value = parse_css_value(context, value, CSS::PropertyID::Color);
|
||||
if (css_value.is_null() || !css_value->is_color())
|
||||
return;
|
||||
auto color = css_value->as_color().color();
|
||||
auto color = css_value->to_color({}); // TODO: Pass a layout node?
|
||||
|
||||
// 4. If color is not failure, then return color.
|
||||
document().page().client().page_did_change_theme_color(color);
|
||||
|
|
Loading…
Reference in a new issue