LibWeb: Implement css gradient-interpolation-method

This commit is contained in:
Gingeh 2025-02-19 21:02:12 +11:00 committed by Sam Atkins
parent 02a642b87b
commit 31853c13d3
Notes: github-actions[bot] 2025-03-06 11:34:14 +00:00
35 changed files with 499 additions and 101 deletions

View file

@ -465,7 +465,7 @@ NonnullRefPtr<CSSStyleValue const> interpolate_box_shadow(DOM::Element& element,
values.ensure_capacity(other.size());
for (size_t i = values.size(); i < other.size(); i++) {
values.unchecked_append(ShadowStyleValue::create(
CSSColorValue::create_from_color(Color::Transparent),
CSSColorValue::create_from_color(Color::Transparent, ColorSyntax::Legacy),
LengthStyleValue::create(Length::make_px(0)),
LengthStyleValue::create(Length::make_px(0)),
LengthStyleValue::create(Length::make_px(0)),
@ -488,7 +488,7 @@ NonnullRefPtr<CSSStyleValue const> interpolate_box_shadow(DOM::Element& element,
auto const& from_shadow = from_shadows[i]->as_shadow();
auto const& to_shadow = to_shadows[i]->as_shadow();
auto result_shadow = ShadowStyleValue::create(
CSSColorValue::create_from_color(interpolate_color(from_shadow.color()->to_color({}), to_shadow.color()->to_color({}), delta)),
CSSColorValue::create_from_color(interpolate_color(from_shadow.color()->to_color({}), to_shadow.color()->to_color({}), delta), ColorSyntax::Modern),
interpolate_value(element, calculation_context, from_shadow.offset_x(), to_shadow.offset_x(), delta),
interpolate_value(element, calculation_context, from_shadow.offset_y(), to_shadow.offset_y(), delta),
interpolate_value(element, calculation_context, from_shadow.blur_radius(), to_shadow.blur_radius(), delta),
@ -584,7 +584,7 @@ NonnullRefPtr<CSSStyleValue const> interpolate_value(DOM::Element& element, Calc
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));
return CSSColorValue::create_from_color(interpolate_color(from.to_color(layout_node), to.to_color(layout_node), delta), ColorSyntax::Modern);
}
case CSSStyleValue::Type::Integer:
return IntegerStyleValue::create(interpolate_raw(from.as_integer().integer(), to.as_integer().integer(), delta));

View file

@ -128,6 +128,101 @@ Optional<Vector<AngularColorStopListElement>> Parser::parse_angular_color_stop_l
[&](auto& it) { return parse_angle_percentage(it); });
}
Optional<InterpolationMethod> Parser::parse_interpolation_method(TokenStream<ComponentValue>& tokens)
{
// <color-interpolation-method> = in [ <rectangular-color-space> | <polar-color-space> <hue-interpolation-method>? ]
auto transaction = tokens.begin_transaction();
if (!tokens.consume_a_token().is_ident("in"sv))
return {};
tokens.discard_whitespace();
auto first_value = tokens.consume_a_token();
if (!first_value.is(Token::Type::Ident))
return {};
auto color_space_name = first_value.token().ident();
GradientSpace color_space;
bool polar_space = false;
if (color_space_name.equals_ignoring_ascii_case("srgb"sv)) {
color_space = GradientSpace::sRGB;
} else if (color_space_name.equals_ignoring_ascii_case("srgb-linear"sv)) {
color_space = GradientSpace::sRGBLinear;
} else if (color_space_name.equals_ignoring_ascii_case("display-p3"sv)) {
color_space = GradientSpace::DisplayP3;
} else if (color_space_name.equals_ignoring_ascii_case("a98-rgb"sv)) {
color_space = GradientSpace::A98RGB;
} else if (color_space_name.equals_ignoring_ascii_case("prophoto-rgb"sv)) {
color_space = GradientSpace::ProPhotoRGB;
} else if (color_space_name.equals_ignoring_ascii_case("rec2020"sv)) {
color_space = GradientSpace::Rec2020;
} else if (color_space_name.equals_ignoring_ascii_case("lab"sv)) {
color_space = GradientSpace::Lab;
} else if (color_space_name.equals_ignoring_ascii_case("oklab"sv)) {
color_space = GradientSpace::OKLab;
} else if (color_space_name.equals_ignoring_ascii_case("xyz-d50"sv)) {
color_space = GradientSpace::XYZD50;
} else if (color_space_name.equals_ignoring_ascii_case("xyz-d65"sv)
|| color_space_name.equals_ignoring_ascii_case("xyz"sv)) {
color_space = GradientSpace::XYZD65;
} else {
polar_space = true;
if (color_space_name.equals_ignoring_ascii_case("hsl"sv)) {
color_space = GradientSpace::HSL;
} else if (color_space_name.equals_ignoring_ascii_case("hwb"sv)) {
color_space = GradientSpace::HWB;
} else if (color_space_name.equals_ignoring_ascii_case("lch"sv)) {
color_space = GradientSpace::LCH;
} else if (color_space_name.equals_ignoring_ascii_case("oklch"sv)) {
color_space = GradientSpace::OKLCH;
} else {
return {};
}
}
Optional<HueMethod> hue_method;
if (polar_space) {
[&]() {
auto hue_transaction = transaction.create_child();
tokens.discard_whitespace();
auto second_value = tokens.consume_a_token();
if (!second_value.is(Token::Type::Ident))
return;
auto hue_method_name = second_value.token().ident();
if (hue_method_name.equals_ignoring_ascii_case("shorter"sv)) {
hue_method = HueMethod::Shorter;
} else if (hue_method_name.equals_ignoring_ascii_case("longer"sv)) {
hue_method = HueMethod::Longer;
} else if (hue_method_name.equals_ignoring_ascii_case("increasing"sv)) {
hue_method = HueMethod::Increasing;
} else if (hue_method_name.equals_ignoring_ascii_case("decreasing"sv)) {
hue_method = HueMethod::Decreasing;
} else {
return;
}
tokens.discard_whitespace();
if (!tokens.consume_a_token().is_ident("hue"sv))
return;
hue_transaction.commit();
}();
}
transaction.commit();
InterpolationMethod interpolation_method;
interpolation_method.color_space = color_space;
if (hue_method.has_value())
interpolation_method.hue_method = hue_method.value();
return interpolation_method;
}
RefPtr<LinearGradientStyleValue> Parser::parse_linear_gradient_function(TokenStream<ComponentValue>& outer_tokens)
{
using GradientType = LinearGradientStyleValue::GradientType;
@ -156,7 +251,7 @@ RefPtr<LinearGradientStyleValue> Parser::parse_linear_gradient_function(TokenStr
if (!function_name.equals_ignoring_ascii_case("linear-gradient"sv))
return nullptr;
// linear-gradient() = linear-gradient([ <angle> | to <side-or-corner> ]?, <color-stop-list>)
// <linear-gradient-syntax> = [ [ <angle> | to <side-or-corner> ] || <color-interpolation-method> ]? , <color-stop-list>
TokenStream tokens { component_value.function().value };
tokens.discard_whitespace();
@ -189,6 +284,9 @@ RefPtr<LinearGradientStyleValue> Parser::parse_linear_gradient_function(TokenStr
return token.token().ident().equals_ignoring_ascii_case("to"sv);
};
auto maybe_interpolation_method = parse_interpolation_method(tokens);
tokens.discard_whitespace();
auto const& first_param = tokens.next_token();
if (first_param.is(Token::Type::Dimension)) {
// <angle>
@ -222,11 +320,12 @@ RefPtr<LinearGradientStyleValue> Parser::parse_linear_gradient_function(TokenStr
tokens.discard_whitespace();
Optional<SideOrCorner> side_b;
if (tokens.has_next_token() && tokens.next_token().is(Token::Type::Ident))
side_b = to_side(tokens.consume_a_token().token().ident());
side_b = to_side(tokens.next_token().token().ident());
if (side_a.has_value() && !side_b.has_value()) {
gradient_direction = *side_a;
} else if (side_a.has_value() && side_b.has_value()) {
tokens.discard_a_token();
// Convert two sides to a corner
if (to_underlying(*side_b) < to_underlying(*side_a))
swap(side_a, side_b);
@ -247,11 +346,16 @@ RefPtr<LinearGradientStyleValue> Parser::parse_linear_gradient_function(TokenStr
has_direction_param = false;
}
if (!maybe_interpolation_method.has_value()) {
tokens.discard_whitespace();
maybe_interpolation_method = parse_interpolation_method(tokens);
}
tokens.discard_whitespace();
if (!tokens.has_next_token())
return nullptr;
if (has_direction_param && !tokens.consume_a_token().is(Token::Type::Comma))
if ((has_direction_param || maybe_interpolation_method.has_value()) && !tokens.consume_a_token().is(Token::Type::Comma))
return nullptr;
auto color_stops = parse_linear_color_stop_list(tokens);
@ -259,7 +363,7 @@ RefPtr<LinearGradientStyleValue> Parser::parse_linear_gradient_function(TokenStr
return nullptr;
transaction.commit();
return LinearGradientStyleValue::create(gradient_direction, move(*color_stops), gradient_type, repeating_gradient);
return LinearGradientStyleValue::create(gradient_direction, move(*color_stops), gradient_type, repeating_gradient, maybe_interpolation_method);
}
RefPtr<ConicGradientStyleValue> Parser::parse_conic_gradient_function(TokenStream<ComponentValue>& outer_tokens)
@ -290,9 +394,9 @@ RefPtr<ConicGradientStyleValue> Parser::parse_conic_gradient_function(TokenStrea
Angle from_angle(0, Angle::Type::Deg);
RefPtr<PositionStyleValue> at_position;
Optional<InterpolationMethod> maybe_interpolation_method;
// conic-gradient( [ [ from <angle> ]? [ at <position> ]? ] ||
// <color-interpolation-method> , <angular-color-stop-list> )
// conic-gradient( [ [ [ from <angle> ]? [ at <position> ]? ] || <color-interpolation-method> ]? , <angular-color-stop-list> )
NonnullRawPtr<ComponentValue const> token = tokens.next_token();
bool got_from_angle = false;
bool got_color_interpolation_method = false;
@ -335,12 +439,15 @@ RefPtr<ConicGradientStyleValue> Parser::parse_conic_gradient_function(TokenStrea
return nullptr;
at_position = position;
got_at_position = true;
} else if (consume_identifier("in"sv)) {
} else if (token->token().ident().equals_ignoring_ascii_case("in"sv)) {
// <color-interpolation-method>
if (got_color_interpolation_method)
return nullptr;
dbgln("FIXME: Parse color interpolation method for conic-gradient()");
got_color_interpolation_method = true;
maybe_interpolation_method = parse_interpolation_method(tokens);
if (!maybe_interpolation_method.has_value())
return nullptr;
} else {
break;
}
@ -364,7 +471,7 @@ RefPtr<ConicGradientStyleValue> Parser::parse_conic_gradient_function(TokenStrea
at_position = PositionStyleValue::create_center();
transaction.commit();
return ConicGradientStyleValue::create(from_angle, at_position.release_nonnull(), move(*color_stops), repeating_gradient);
return ConicGradientStyleValue::create(from_angle, at_position.release_nonnull(), move(*color_stops), repeating_gradient, maybe_interpolation_method);
}
RefPtr<RadialGradientStyleValue> Parser::parse_radial_gradient_function(TokenStream<ComponentValue>& outer_tokens)
@ -405,7 +512,8 @@ RefPtr<RadialGradientStyleValue> Parser::parse_radial_gradient_function(TokenStr
return value;
};
// radial-gradient( [ <ending-shape> || <size> ]? [ at <position> ]? , <color-stop-list> )
// <radial-gradient-syntax> = [ [ [ <radial-shape> || <radial-size> ]? [ at <position> ]? ] || <color-interpolation-method> ]? , <color-stop-list>
// FIXME: Maybe rename ending-shape things to radial-shape
Size size = Extent::FarthestCorner;
EndingShape ending_shape = EndingShape::Circle;
@ -469,6 +577,9 @@ RefPtr<RadialGradientStyleValue> Parser::parse_radial_gradient_function(TokenStr
return {};
};
auto maybe_interpolation_method = parse_interpolation_method(tokens);
tokens.discard_whitespace();
{
// [ <ending-shape> || <size> ]?
auto maybe_ending_shape = parse_ending_shape();
@ -506,6 +617,14 @@ RefPtr<RadialGradientStyleValue> Parser::parse_radial_gradient_function(TokenStr
}
tokens.discard_whitespace();
if (!maybe_interpolation_method.has_value()) {
maybe_interpolation_method = parse_interpolation_method(tokens);
tokens.discard_whitespace();
}
if (maybe_interpolation_method.has_value())
expect_comma = true;
if (!tokens.has_next_token())
return nullptr;
if (expect_comma && !tokens.consume_a_token().is(Token::Type::Comma))
@ -520,7 +639,7 @@ RefPtr<RadialGradientStyleValue> Parser::parse_radial_gradient_function(TokenStr
at_position = PositionStyleValue::create_center();
transaction.commit();
return RadialGradientStyleValue::create(ending_shape, size, at_position.release_nonnull(), move(*color_stops), repeating_gradient);
return RadialGradientStyleValue::create(ending_shape, size, at_position.release_nonnull(), move(*color_stops), repeating_gradient, maybe_interpolation_method);
}
}

View file

@ -281,6 +281,7 @@ private:
Optional<Vector<TElement>> parse_color_stop_list(TokenStream<ComponentValue>& tokens, auto parse_position);
Optional<Vector<LinearColorStopListElement>> parse_linear_color_stop_list(TokenStream<ComponentValue>&);
Optional<Vector<AngularColorStopListElement>> parse_angular_color_stop_list(TokenStream<ComponentValue>&);
Optional<InterpolationMethod> parse_interpolation_method(TokenStream<ComponentValue>&);
RefPtr<LinearGradientStyleValue> parse_linear_gradient_function(TokenStream<ComponentValue>&);
RefPtr<ConicGradientStyleValue> parse_conic_gradient_function(TokenStream<ComponentValue>&);

View file

@ -1198,7 +1198,7 @@ RefPtr<CSSStyleValue> Parser::parse_rgb_color_value(TokenStream<ComponentValue>&
alpha = NumberStyleValue::create(1);
transaction.commit();
return CSSRGB::create(red.release_nonnull(), green.release_nonnull(), blue.release_nonnull(), alpha.release_nonnull());
return CSSRGB::create(red.release_nonnull(), green.release_nonnull(), blue.release_nonnull(), alpha.release_nonnull(), legacy_syntax ? ColorSyntax::Legacy : ColorSyntax::Modern);
}
// https://www.w3.org/TR/css-color-4/#funcdef-hsl
@ -1310,7 +1310,7 @@ RefPtr<CSSStyleValue> Parser::parse_hsl_color_value(TokenStream<ComponentValue>&
alpha = NumberStyleValue::create(1);
transaction.commit();
return CSSHSL::create(h.release_nonnull(), s.release_nonnull(), l.release_nonnull(), alpha.release_nonnull());
return CSSHSL::create(h.release_nonnull(), s.release_nonnull(), l.release_nonnull(), alpha.release_nonnull(), legacy_syntax ? ColorSyntax::Legacy : ColorSyntax::Modern);
}
// https://www.w3.org/TR/css-color-4/#funcdef-hwb
@ -1687,7 +1687,7 @@ RefPtr<CSSStyleValue> Parser::parse_color_value(TokenStream<ComponentValue>& tok
auto color = Color::from_string(ident);
if (color.has_value()) {
transaction.commit();
return CSSColorValue::create_from_color(color.release_value(), ident);
return CSSColorValue::create_from_color(color.release_value(), ColorSyntax::Legacy, ident);
}
// Otherwise, fall through to the hashless-hex-color case
}
@ -1696,7 +1696,7 @@ RefPtr<CSSStyleValue> Parser::parse_color_value(TokenStream<ComponentValue>& tok
auto color = Color::from_string(MUST(String::formatted("#{}", component_value.token().hash_value())));
if (color.has_value()) {
transaction.commit();
return CSSColorValue::create_from_color(color.release_value());
return CSSColorValue::create_from_color(color.release_value(), ColorSyntax::Legacy);
}
return {};
}
@ -1782,7 +1782,7 @@ RefPtr<CSSStyleValue> Parser::parse_color_value(TokenStream<ComponentValue>& tok
auto color = Color::from_string(MUST(String::formatted("#{}", serialization)));
if (color.has_value()) {
transaction.commit();
return CSSColorValue::create_from_color(color.release_value());
return CSSColorValue::create_from_color(color.release_value(), ColorSyntax::Legacy);
}
}
}

View file

@ -147,7 +147,7 @@ static RefPtr<CSSStyleValue const> style_value_for_shadow(Vector<ShadowData> con
auto make_shadow_style_value = [](ShadowData const& shadow) {
return ShadowStyleValue::create(
CSSColorValue::create_from_color(shadow.color),
CSSColorValue::create_from_color(shadow.color, ColorSyntax::Modern),
style_value_for_length_percentage(shadow.offset_x),
style_value_for_length_percentage(shadow.offset_y),
style_value_for_length_percentage(shadow.blur_radius),
@ -209,23 +209,23 @@ RefPtr<CSSStyleValue const> ResolvedCSSStyleDeclaration::style_value_for_propert
// -> A resolved value special case property like color defined in another specification
// The resolved value is the used value.
case PropertyID::BackgroundColor:
return CSSColorValue::create_from_color(layout_node.computed_values().background_color());
return CSSColorValue::create_from_color(layout_node.computed_values().background_color(), ColorSyntax::Modern);
case PropertyID::BorderBottomColor:
return CSSColorValue::create_from_color(layout_node.computed_values().border_bottom().color);
return CSSColorValue::create_from_color(layout_node.computed_values().border_bottom().color, ColorSyntax::Modern);
case PropertyID::BorderLeftColor:
return CSSColorValue::create_from_color(layout_node.computed_values().border_left().color);
return CSSColorValue::create_from_color(layout_node.computed_values().border_left().color, ColorSyntax::Modern);
case PropertyID::BorderRightColor:
return CSSColorValue::create_from_color(layout_node.computed_values().border_right().color);
return CSSColorValue::create_from_color(layout_node.computed_values().border_right().color, ColorSyntax::Modern);
case PropertyID::BorderTopColor:
return CSSColorValue::create_from_color(layout_node.computed_values().border_top().color);
return CSSColorValue::create_from_color(layout_node.computed_values().border_top().color, ColorSyntax::Modern);
case PropertyID::BoxShadow:
return style_value_for_shadow(layout_node.computed_values().box_shadow());
case PropertyID::Color:
return CSSColorValue::create_from_color(layout_node.computed_values().color());
return CSSColorValue::create_from_color(layout_node.computed_values().color(), ColorSyntax::Modern);
case PropertyID::OutlineColor:
return CSSColorValue::create_from_color(layout_node.computed_values().outline_color());
return CSSColorValue::create_from_color(layout_node.computed_values().outline_color(), ColorSyntax::Modern);
case PropertyID::TextDecorationColor:
return CSSColorValue::create_from_color(layout_node.computed_values().text_decoration_color());
return CSSColorValue::create_from_color(layout_node.computed_values().text_decoration_color(), ColorSyntax::Modern);
// NOTE: text-shadow isn't listed, but is computed the same as box-shadow.
case PropertyID::TextShadow:
return style_value_for_shadow(layout_node.computed_values().text_shadow());
@ -456,7 +456,7 @@ RefPtr<CSSStyleValue const> ResolvedCSSStyleDeclaration::style_value_for_propert
// The resolved value is the computed value.
// NOTE: This is handled inside the `default` case.
case PropertyID::WebkitTextFillColor:
return CSSColorValue::create_from_color(layout_node.computed_values().webkit_text_fill_color());
return CSSColorValue::create_from_color(layout_node.computed_values().webkit_text_fill_color(), ColorSyntax::Modern);
case PropertyID::Invalid:
return CSSKeywordValue::create(Keyword::Invalid);
case PropertyID::Custom:

View file

@ -13,6 +13,7 @@
#include <LibWeb/CSS/Enums.h>
#include <LibWeb/CSS/PercentageOr.h>
#include <LibWeb/CSS/Serialize.h>
#include <LibWeb/CSS/StyleValues/CSSColorValue.h>
namespace Web::CSS {
@ -48,6 +49,112 @@ enum class GradientRepeating {
No
};
enum class GradientSpace : u8 {
sRGB,
sRGBLinear,
DisplayP3,
A98RGB,
ProPhotoRGB,
Rec2020,
Lab,
OKLab,
XYZD50,
XYZD65,
HSL,
HWB,
LCH,
OKLCH,
};
enum class HueMethod : u8 {
Shorter,
Longer,
Increasing,
Decreasing,
};
struct InterpolationMethod {
GradientSpace color_space;
HueMethod hue_method = HueMethod::Shorter;
String to_string() const
{
StringBuilder builder;
switch (color_space) {
case GradientSpace::OKLab:
builder.append("in oklab"sv);
break;
case GradientSpace::sRGB:
builder.append("in srgb"sv);
break;
case GradientSpace::sRGBLinear:
builder.append("in srgb-linear"sv);
break;
case GradientSpace::DisplayP3:
builder.append("in display-p3"sv);
break;
case GradientSpace::A98RGB:
builder.append("in a98-rgb"sv);
break;
case GradientSpace::ProPhotoRGB:
builder.append("in prophoto-rgb"sv);
break;
case GradientSpace::Rec2020:
builder.append("in rec2020"sv);
break;
case GradientSpace::Lab:
builder.append("in lab"sv);
break;
case GradientSpace::XYZD50:
builder.append("in xyz-d50"sv);
break;
case GradientSpace::XYZD65:
builder.append("in xyz-d65"sv);
break;
case GradientSpace::HSL:
builder.append("in hsl"sv);
break;
case GradientSpace::HWB:
builder.append("in hwb"sv);
break;
case GradientSpace::LCH:
builder.append("in lch"sv);
break;
case GradientSpace::OKLCH:
builder.append("in oklch"sv);
break;
}
switch (hue_method) {
case HueMethod::Shorter:
// "shorter" is the default value and isn't serialized
break;
case HueMethod::Longer:
builder.append(" longer hue"sv);
break;
case HueMethod::Increasing:
builder.append(" increasing hue"sv);
break;
case HueMethod::Decreasing:
builder.append(" decreasing hue"sv);
break;
}
return MUST(builder.to_string());
}
static GradientSpace default_color_space(ColorSyntax color_syntax)
{
if (color_syntax == ColorSyntax::Legacy)
return GradientSpace::sRGB;
return GradientSpace::OKLab;
}
bool operator==(InterpolationMethod const&) const = default;
};
template<typename TPosition>
struct ColorStopListElement {
using PositionType = TPosition;

View file

@ -25,7 +25,7 @@ public:
private:
CSSColor(ColorType color_type, ValueComparingNonnullRefPtr<CSSStyleValue> c1, ValueComparingNonnullRefPtr<CSSStyleValue> c2, ValueComparingNonnullRefPtr<CSSStyleValue> c3, ValueComparingNonnullRefPtr<CSSStyleValue> alpha)
: CSSColorValue(color_type)
: CSSColorValue(color_type, ColorSyntax::Modern)
, m_properties { .channels = { move(c1), move(c2), move(c3) }, .alpha = move(alpha) }
{
}

View file

@ -18,13 +18,14 @@
namespace Web::CSS {
ValueComparingNonnullRefPtr<CSSColorValue> CSSColorValue::create_from_color(Color color, Optional<FlyString> name)
ValueComparingNonnullRefPtr<CSSColorValue> CSSColorValue::create_from_color(Color color, ColorSyntax color_syntax, Optional<FlyString> name)
{
return CSSRGB::create(
NumberStyleValue::create(color.red()),
NumberStyleValue::create(color.green()),
NumberStyleValue::create(color.blue()),
NumberStyleValue::create(color.alpha() / 255.0),
color_syntax,
name);
}

View file

@ -15,10 +15,15 @@
namespace Web::CSS {
enum class ColorSyntax : u8 {
Legacy,
Modern,
};
// https://drafts.css-houdini.org/css-typed-om-1/#csscolorvalue
class CSSColorValue : public CSSStyleValue {
public:
static ValueComparingNonnullRefPtr<CSSColorValue> create_from_color(Color color, Optional<FlyString> name = {});
static ValueComparingNonnullRefPtr<CSSColorValue> create_from_color(Color color, ColorSyntax color_syntax, Optional<FlyString> name = {});
virtual ~CSSColorValue() override = default;
virtual bool has_color() const override { return true; }
@ -42,11 +47,13 @@ public:
LightDark, // This is used by CSSLightDark for light-dark(..., ...).
};
ColorType color_type() const { return m_color_type; }
ColorSyntax color_syntax() const { return m_color_syntax; }
protected:
explicit CSSColorValue(ColorType color_type)
explicit CSSColorValue(ColorType color_type, ColorSyntax color_syntax)
: CSSStyleValue(Type::Color)
, m_color_type(color_type)
, m_color_syntax(color_syntax)
{
}
@ -55,6 +62,7 @@ protected:
static Optional<double> resolve_alpha(CSSStyleValue const&);
ColorType m_color_type;
ColorSyntax m_color_syntax;
};
}

View file

@ -14,13 +14,13 @@ 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 = {})
static ValueComparingNonnullRefPtr<CSSHSL> create(ValueComparingNonnullRefPtr<CSSStyleValue> h, ValueComparingNonnullRefPtr<CSSStyleValue> s, ValueComparingNonnullRefPtr<CSSStyleValue> l, ValueComparingRefPtr<CSSStyleValue> alpha, ColorSyntax color_syntax)
{
// 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), NumberStyleValue::create(1), color_syntax));
return adopt_ref(*new (nothrow) CSSHSL(move(h), move(s), move(l), alpha.release_nonnull()));
return adopt_ref(*new (nothrow) CSSHSL(move(h), move(s), move(l), alpha.release_nonnull(), color_syntax));
}
virtual ~CSSHSL() override = default;
@ -36,8 +36,8 @@ public:
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)
CSSHSL(ValueComparingNonnullRefPtr<CSSStyleValue> h, ValueComparingNonnullRefPtr<CSSStyleValue> s, ValueComparingNonnullRefPtr<CSSStyleValue> l, ValueComparingNonnullRefPtr<CSSStyleValue> alpha, ColorSyntax color_syntax)
: CSSColorValue(ColorType::HSL, color_syntax)
, m_properties { .h = move(h), .s = move(s), .l = move(l), .alpha = move(alpha) }
{
}

View file

@ -37,7 +37,7 @@ public:
private:
CSSHWB(ValueComparingNonnullRefPtr<CSSStyleValue> h, ValueComparingNonnullRefPtr<CSSStyleValue> w, ValueComparingNonnullRefPtr<CSSStyleValue> b, ValueComparingNonnullRefPtr<CSSStyleValue> alpha)
: CSSColorValue(ColorType::HWB)
: CSSColorValue(ColorType::HWB, ColorSyntax::Modern)
, m_properties { .h = move(h), .w = move(w), .b = move(b), .alpha = move(alpha) }
{
}

View file

@ -33,7 +33,7 @@ public:
protected:
CSSLCHLike(ColorType color_type, ValueComparingNonnullRefPtr<CSSStyleValue> l, ValueComparingNonnullRefPtr<CSSStyleValue> c, ValueComparingNonnullRefPtr<CSSStyleValue> h, ValueComparingNonnullRefPtr<CSSStyleValue> alpha)
: CSSColorValue(color_type)
: CSSColorValue(color_type, ColorSyntax::Modern)
, m_properties { .l = move(l), .c = move(c), .h = move(h), .alpha = move(alpha) }
{
}

View file

@ -34,7 +34,7 @@ public:
protected:
CSSLabLike(ColorType color_type, ValueComparingNonnullRefPtr<CSSStyleValue> l, ValueComparingNonnullRefPtr<CSSStyleValue> a, ValueComparingNonnullRefPtr<CSSStyleValue> b, ValueComparingNonnullRefPtr<CSSStyleValue> alpha)
: CSSColorValue(color_type)
: CSSColorValue(color_type, ColorSyntax::Modern)
, m_properties { .l = move(l), .a = move(a), .b = move(b), .alpha = move(alpha) }
{
}

View file

@ -26,7 +26,7 @@ public:
private:
CSSLightDark(ValueComparingNonnullRefPtr<CSSStyleValue> light, ValueComparingNonnullRefPtr<CSSStyleValue> dark)
: CSSColorValue(CSSColorValue::ColorType::LightDark)
: CSSColorValue(CSSColorValue::ColorType::LightDark, ColorSyntax::Modern)
, m_properties { .light = move(light), .dark = move(dark) }
{
}

View file

@ -14,13 +14,13 @@ 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 = {}, Optional<FlyString> name = {})
static ValueComparingNonnullRefPtr<CSSRGB> create(ValueComparingNonnullRefPtr<CSSStyleValue> r, ValueComparingNonnullRefPtr<CSSStyleValue> g, ValueComparingNonnullRefPtr<CSSStyleValue> b, ValueComparingRefPtr<CSSStyleValue> alpha, ColorSyntax color_syntax, Optional<FlyString> name = {})
{
// alpha defaults to 1
if (!alpha)
return adopt_ref(*new (nothrow) CSSRGB(move(r), move(g), move(b), NumberStyleValue::create(1), name));
return adopt_ref(*new (nothrow) CSSRGB(move(r), move(g), move(b), NumberStyleValue::create(1), color_syntax, name));
return adopt_ref(*new (nothrow) CSSRGB(move(r), move(g), move(b), alpha.release_nonnull(), name));
return adopt_ref(*new (nothrow) CSSRGB(move(r), move(g), move(b), alpha.release_nonnull(), color_syntax, name));
}
virtual ~CSSRGB() override = default;
@ -36,8 +36,8 @@ public:
virtual bool equals(CSSStyleValue const& other) const override;
private:
CSSRGB(ValueComparingNonnullRefPtr<CSSStyleValue> r, ValueComparingNonnullRefPtr<CSSStyleValue> g, ValueComparingNonnullRefPtr<CSSStyleValue> b, ValueComparingNonnullRefPtr<CSSStyleValue> alpha, Optional<FlyString> name = {})
: CSSColorValue(ColorType::RGB)
CSSRGB(ValueComparingNonnullRefPtr<CSSStyleValue> r, ValueComparingNonnullRefPtr<CSSStyleValue> g, ValueComparingNonnullRefPtr<CSSStyleValue> b, ValueComparingNonnullRefPtr<CSSStyleValue> alpha, ColorSyntax color_syntax, Optional<FlyString> name = {})
: CSSColorValue(ColorType::RGB, color_syntax)
, m_properties { .r = move(r), .g = move(g), .b = move(b), .alpha = move(alpha), .name = name }
{
}

View file

@ -22,6 +22,8 @@ String ConicGradientStyleValue::to_string(SerializationMode mode) const
builder.append("conic-gradient("sv);
bool has_from_angle = m_properties.from_angle.to_degrees() != 0;
bool has_at_position = !m_properties.position->is_center();
bool has_color_space = m_properties.interpolation_method.has_value() && m_properties.interpolation_method.value().color_space != InterpolationMethod::default_color_space(m_properties.color_syntax);
if (has_from_angle)
builder.appendff("from {}", m_properties.from_angle.to_string());
if (has_at_position) {
@ -29,7 +31,12 @@ String ConicGradientStyleValue::to_string(SerializationMode mode) const
builder.append(' ');
builder.appendff("at {}"sv, m_properties.position->to_string(mode));
}
if (has_from_angle || has_at_position)
if (has_color_space) {
if (has_from_angle || has_at_position)
builder.append(' ');
builder.append(m_properties.interpolation_method.value().to_string());
}
if (has_from_angle || has_at_position || has_color_space)
builder.append(", "sv);
serialize_color_stop_list(builder, m_properties.color_stop_list, mode);
builder.append(')');

View file

@ -17,10 +17,11 @@ namespace Web::CSS {
class ConicGradientStyleValue final : public AbstractImageStyleValue {
public:
static ValueComparingNonnullRefPtr<ConicGradientStyleValue> create(Angle from_angle, ValueComparingNonnullRefPtr<PositionStyleValue> position, Vector<AngularColorStopListElement> color_stop_list, GradientRepeating repeating)
static ValueComparingNonnullRefPtr<ConicGradientStyleValue> create(Angle from_angle, ValueComparingNonnullRefPtr<PositionStyleValue> position, Vector<AngularColorStopListElement> color_stop_list, GradientRepeating repeating, Optional<InterpolationMethod> interpolation_method)
{
VERIFY(!color_stop_list.is_empty());
return adopt_ref(*new (nothrow) ConicGradientStyleValue(from_angle, move(position), move(color_stop_list), repeating));
bool any_non_legacy = color_stop_list.find_first_index_if([](auto const& stop) { return !stop.color_stop.color->is_keyword() && stop.color_stop.color->as_color().color_syntax() == ColorSyntax::Modern; }).has_value();
return adopt_ref(*new (nothrow) ConicGradientStyleValue(from_angle, move(position), move(color_stop_list), repeating, interpolation_method, any_non_legacy ? ColorSyntax::Modern : ColorSyntax::Legacy));
}
virtual String to_string(SerializationMode) const override;
@ -34,6 +35,14 @@ public:
return m_properties.color_stop_list;
}
InterpolationMethod interpolation_method() const
{
if (m_properties.interpolation_method.has_value())
return m_properties.interpolation_method.value();
return InterpolationMethod { .color_space = InterpolationMethod::default_color_space(m_properties.color_syntax) };
}
float angle_degrees() const;
bool is_paintable() const override { return true; }
@ -45,18 +54,19 @@ public:
bool is_repeating() const { return m_properties.repeating == GradientRepeating::Yes; }
private:
ConicGradientStyleValue(Angle from_angle, ValueComparingNonnullRefPtr<PositionStyleValue> position, Vector<AngularColorStopListElement> color_stop_list, GradientRepeating repeating)
ConicGradientStyleValue(Angle from_angle, ValueComparingNonnullRefPtr<PositionStyleValue> position, Vector<AngularColorStopListElement> color_stop_list, GradientRepeating repeating, Optional<InterpolationMethod> interpolation_method, ColorSyntax color_syntax)
: AbstractImageStyleValue(Type::ConicGradient)
, m_properties { .from_angle = from_angle, .position = move(position), .color_stop_list = move(color_stop_list), .repeating = repeating }
, m_properties { .from_angle = from_angle, .position = move(position), .color_stop_list = move(color_stop_list), .repeating = repeating, .interpolation_method = interpolation_method, .color_syntax = color_syntax }
{
}
struct Properties {
// FIXME: Support <color-interpolation-method>
Angle from_angle;
ValueComparingNonnullRefPtr<PositionStyleValue> position;
Vector<AngularColorStopListElement> color_stop_list;
GradientRepeating repeating;
Optional<InterpolationMethod> interpolation_method;
ColorSyntax color_syntax;
bool operator==(Properties const&) const = default;
} m_properties;

View file

@ -27,30 +27,45 @@ String LinearGradientStyleValue::to_string(SerializationMode mode) const
case SideOrCorner::Right:
return "right"sv;
case SideOrCorner::TopLeft:
return "top left"sv;
return "left top"sv;
case SideOrCorner::TopRight:
return "top right"sv;
return "right top"sv;
case SideOrCorner::BottomLeft:
return "bottom left"sv;
return "left bottom"sv;
case SideOrCorner::BottomRight:
return "bottom right"sv;
return "right bottom"sv;
default:
VERIFY_NOT_REACHED();
}
};
auto default_direction = m_properties.gradient_type == GradientType::WebKit ? SideOrCorner::Top : SideOrCorner::Bottom;
bool has_direction = m_properties.direction != default_direction;
bool has_color_space = m_properties.interpolation_method.has_value() && m_properties.interpolation_method.value().color_space != InterpolationMethod::default_color_space(m_properties.color_syntax);
if (m_properties.gradient_type == GradientType::WebKit)
builder.append("-webkit-"sv);
if (is_repeating())
builder.append("repeating-"sv);
builder.append("linear-gradient("sv);
m_properties.direction.visit(
[&](SideOrCorner side_or_corner) {
return builder.appendff("{}{}, "sv, m_properties.gradient_type == GradientType::Standard ? "to "sv : ""sv, side_or_corner_to_string(side_or_corner));
},
[&](Angle const& angle) {
return builder.appendff("{}, "sv, angle.to_string());
});
if (has_direction) {
m_properties.direction.visit(
[&](SideOrCorner side_or_corner) {
builder.appendff("{}{}"sv, m_properties.gradient_type == GradientType::Standard ? "to "sv : ""sv, side_or_corner_to_string(side_or_corner));
},
[&](Angle const& angle) {
builder.append(angle.to_string());
});
if (has_color_space)
builder.append(' ');
}
if (has_color_space)
builder.append(m_properties.interpolation_method.value().to_string());
if (has_direction || has_color_space)
builder.append(", "sv);
serialize_color_stop_list(builder, m_properties.color_stop_list, mode);
builder.append(")"sv);

View file

@ -13,6 +13,7 @@
#include <LibWeb/CSS/Angle.h>
#include <LibWeb/CSS/Percentage.h>
#include <LibWeb/CSS/StyleValues/AbstractImageStyleValue.h>
#include <LibWeb/CSS/StyleValues/CSSColorValue.h>
#include <LibWeb/Painting/GradientPainting.h>
namespace Web::CSS {
@ -38,10 +39,11 @@ public:
WebKit
};
static ValueComparingNonnullRefPtr<LinearGradientStyleValue> create(GradientDirection direction, Vector<LinearColorStopListElement> color_stop_list, GradientType type, GradientRepeating repeating)
static ValueComparingNonnullRefPtr<LinearGradientStyleValue> create(GradientDirection direction, Vector<LinearColorStopListElement> color_stop_list, GradientType type, GradientRepeating repeating, Optional<InterpolationMethod> interpolation_method)
{
VERIFY(!color_stop_list.is_empty());
return adopt_ref(*new (nothrow) LinearGradientStyleValue(direction, move(color_stop_list), type, repeating));
bool any_non_legacy = color_stop_list.find_first_index_if([](auto const& stop) { return !stop.color_stop.color->is_keyword() && stop.color_stop.color->as_color().color_syntax() == ColorSyntax::Modern; }).has_value();
return adopt_ref(*new (nothrow) LinearGradientStyleValue(direction, move(color_stop_list), type, repeating, interpolation_method, any_non_legacy ? ColorSyntax::Modern : ColorSyntax::Legacy));
}
virtual String to_string(SerializationMode) const override;
@ -53,6 +55,17 @@ public:
return m_properties.color_stop_list;
}
// FIXME: This (and the any_non_legacy code in the constructor) is duplicated in the separate gradient classes,
// should this logic be pulled into some kind of GradientStyleValue superclass?
// It could also contain the "gradient related things" currently in AbstractImageStyleValue.h
InterpolationMethod interpolation_method() const
{
if (m_properties.interpolation_method.has_value())
return m_properties.interpolation_method.value();
return InterpolationMethod { .color_space = InterpolationMethod::default_color_space(m_properties.color_syntax) };
}
bool is_repeating() const { return m_properties.repeating == GradientRepeating::Yes; }
float angle_degrees(CSSPixelSize gradient_size) const;
@ -63,9 +76,9 @@ public:
void paint(PaintContext& context, DevicePixelRect const& dest_rect, CSS::ImageRendering image_rendering) const override;
private:
LinearGradientStyleValue(GradientDirection direction, Vector<LinearColorStopListElement> color_stop_list, GradientType type, GradientRepeating repeating)
LinearGradientStyleValue(GradientDirection direction, Vector<LinearColorStopListElement> color_stop_list, GradientType type, GradientRepeating repeating, Optional<InterpolationMethod> interpolation_method, ColorSyntax color_syntax)
: AbstractImageStyleValue(Type::LinearGradient)
, m_properties { .direction = direction, .color_stop_list = move(color_stop_list), .gradient_type = type, .repeating = repeating }
, m_properties { .direction = direction, .color_stop_list = move(color_stop_list), .gradient_type = type, .repeating = repeating, .interpolation_method = interpolation_method, .color_syntax = color_syntax }
{
}
@ -74,6 +87,8 @@ private:
Vector<LinearColorStopListElement> color_stop_list;
GradientType gradient_type;
GradientRepeating repeating;
Optional<InterpolationMethod> interpolation_method;
ColorSyntax color_syntax;
bool operator==(Properties const&) const = default;
} m_properties;

View file

@ -19,8 +19,11 @@ String RadialGradientStyleValue::to_string(SerializationMode mode) const
StringBuilder builder;
if (is_repeating())
builder.append("repeating-"sv);
builder.appendff("radial-gradient({} "sv,
m_properties.ending_shape == EndingShape::Circle ? "circle"sv : "ellipse"sv);
builder.appendff("radial-gradient("sv);
bool has_size = !m_properties.size.has<Extent>() || m_properties.size.get<Extent>() != Extent::FarthestCorner;
bool has_position = !m_properties.position->is_center();
bool has_color_space = m_properties.interpolation_method.has_value() && m_properties.interpolation_method.value().color_space != InterpolationMethod::default_color_space(m_properties.color_syntax);
m_properties.size.visit(
[&](Extent extent) {
@ -31,7 +34,8 @@ String RadialGradientStyleValue::to_string(SerializationMode mode) const
case Extent::ClosestSide:
return "closest-side"sv;
case Extent::FarthestCorner:
return "farthest-corner"sv;
// "farthest-corner" is the default value and isn't serialized
return ""sv;
case Extent::FarthestSide:
return "farthest-side"sv;
default:
@ -46,10 +50,23 @@ String RadialGradientStyleValue::to_string(SerializationMode mode) const
builder.appendff("{} {}", ellipse_size.radius_a.to_string(), ellipse_size.radius_b.to_string());
});
if (!m_properties.position->is_center())
builder.appendff(" at {}"sv, m_properties.position->to_string(mode));
if (has_position) {
if (has_size)
builder.append(' ');
builder.appendff("at {}"sv, m_properties.position->to_string(mode));
}
if (has_color_space) {
if (has_size || has_position)
builder.append(' ');
builder.append(m_properties.interpolation_method.value().to_string());
}
if (has_size || has_position || has_color_space)
builder.append(", "sv);
builder.append(", "sv);
serialize_color_stop_list(builder, m_properties.color_stop_list, mode);
builder.append(')');
return MUST(builder.to_string());

View file

@ -43,10 +43,11 @@ public:
using Size = Variant<Extent, CircleSize, EllipseSize>;
static ValueComparingNonnullRefPtr<RadialGradientStyleValue> create(EndingShape ending_shape, Size size, ValueComparingNonnullRefPtr<PositionStyleValue> position, Vector<LinearColorStopListElement> color_stop_list, GradientRepeating repeating)
static ValueComparingNonnullRefPtr<RadialGradientStyleValue> create(EndingShape ending_shape, Size size, ValueComparingNonnullRefPtr<PositionStyleValue> position, Vector<LinearColorStopListElement> color_stop_list, GradientRepeating repeating, Optional<InterpolationMethod> interpolation_method)
{
VERIFY(!color_stop_list.is_empty());
return adopt_ref(*new (nothrow) RadialGradientStyleValue(ending_shape, size, move(position), move(color_stop_list), repeating));
bool any_non_legacy = color_stop_list.find_first_index_if([](auto const& stop) { return !stop.color_stop.color->is_keyword() && stop.color_stop.color->as_color().color_syntax() == ColorSyntax::Modern; }).has_value();
return adopt_ref(*new (nothrow) RadialGradientStyleValue(ending_shape, size, move(position), move(color_stop_list), repeating, interpolation_method, any_non_legacy ? ColorSyntax::Modern : ColorSyntax::Legacy));
}
virtual String to_string(SerializationMode) const override;
@ -60,6 +61,14 @@ public:
return m_properties.color_stop_list;
}
InterpolationMethod interpolation_method() const
{
if (m_properties.interpolation_method.has_value())
return m_properties.interpolation_method.value();
return InterpolationMethod { .color_space = InterpolationMethod::default_color_space(m_properties.color_syntax) };
}
bool is_paintable() const override { return true; }
void resolve_for_size(Layout::NodeWithStyle const&, CSSPixelSize) const override;
@ -71,9 +80,9 @@ public:
virtual ~RadialGradientStyleValue() override = default;
private:
RadialGradientStyleValue(EndingShape ending_shape, Size size, ValueComparingNonnullRefPtr<PositionStyleValue> position, Vector<LinearColorStopListElement> color_stop_list, GradientRepeating repeating)
RadialGradientStyleValue(EndingShape ending_shape, Size size, ValueComparingNonnullRefPtr<PositionStyleValue> position, Vector<LinearColorStopListElement> color_stop_list, GradientRepeating repeating, Optional<InterpolationMethod> interpolation_method, ColorSyntax color_syntax)
: AbstractImageStyleValue(Type::RadialGradient)
, m_properties { .ending_shape = ending_shape, .size = size, .position = move(position), .color_stop_list = move(color_stop_list), .repeating = repeating }
, m_properties { .ending_shape = ending_shape, .size = size, .position = move(position), .color_stop_list = move(color_stop_list), .repeating = repeating, .interpolation_method = interpolation_method, .color_syntax = color_syntax }
{
}
@ -83,6 +92,8 @@ private:
ValueComparingNonnullRefPtr<PositionStyleValue> position;
Vector<LinearColorStopListElement> color_stop_list;
GradientRepeating repeating;
Optional<InterpolationMethod> interpolation_method;
ColorSyntax color_syntax;
bool operator==(Properties const&) const = default;
} m_properties;

View file

@ -65,12 +65,12 @@ void HTMLBodyElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties
// https://html.spec.whatwg.org/multipage/rendering.html#the-page:rules-for-parsing-a-legacy-colour-value
auto color = parse_legacy_color_value(value);
if (color.has_value())
cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::BackgroundColor, CSS::CSSColorValue::create_from_color(color.value()));
cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::BackgroundColor, CSS::CSSColorValue::create_from_color(color.value(), CSS::ColorSyntax::Legacy));
} else if (name.equals_ignoring_ascii_case("text"sv)) {
// https://html.spec.whatwg.org/multipage/rendering.html#the-page:rules-for-parsing-a-legacy-colour-value-2
auto color = parse_legacy_color_value(value);
if (color.has_value())
cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::Color, CSS::CSSColorValue::create_from_color(color.value()));
cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::Color, CSS::CSSColorValue::create_from_color(color.value(), CSS::ColorSyntax::Legacy));
} else if (name.equals_ignoring_ascii_case("background"sv)) {
VERIFY(m_background_style_value);
cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::BackgroundImage, *m_background_style_value);

View file

@ -129,7 +129,7 @@ void HTMLFontElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties
// https://html.spec.whatwg.org/multipage/rendering.html#phrasing-content-3:rules-for-parsing-a-legacy-colour-value
auto color = parse_legacy_color_value(value);
if (color.has_value())
cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::Color, CSS::CSSColorValue::create_from_color(color.value()));
cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::Color, CSS::CSSColorValue::create_from_color(color.value(), CSS::ColorSyntax::Legacy));
} else if (name.equals_ignoring_ascii_case("size"sv)) {
// When a font element has a size attribute, the user agent is expected to use the following steps, known as the rules for parsing a legacy font size, to treat the attribute as a presentational hint setting the element's 'font-size' property:
auto font_size_or_empty = parse_legacy_font_size(value);

View file

@ -68,7 +68,7 @@ void HTMLHRElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties>
// the user agent is expected to treat the attribute as a presentational hint setting the element's 'color' property to the resulting color.
if (name == HTML::AttributeNames::color) {
if (auto parsed_value = parse_legacy_color_value(value); parsed_value.has_value()) {
cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::Color, CSS::CSSColorValue::create_from_color(*parsed_value));
cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::Color, CSS::CSSColorValue::create_from_color(*parsed_value, CSS::ColorSyntax::Legacy));
}
}
// https://html.spec.whatwg.org/multipage/rendering.html#the-hr-element-2:maps-to-the-dimension-property

View file

@ -51,7 +51,7 @@ void HTMLMarqueeElement::apply_presentational_hints(GC::Ref<CSS::CascadedPropert
// https://html.spec.whatwg.org/multipage/rendering.html#the-marquee-element-2:rules-for-parsing-a-legacy-colour-value
auto color = parse_legacy_color_value(value);
if (color.has_value())
cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::BackgroundColor, CSS::CSSColorValue::create_from_color(color.value()));
cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::BackgroundColor, CSS::CSSColorValue::create_from_color(color.value(), CSS::ColorSyntax::Legacy));
} else if (name == HTML::AttributeNames::height) {
// https://html.spec.whatwg.org/multipage/rendering.html#the-marquee-element-2:maps-to-the-dimension-property
if (auto parsed_value = parse_dimension_value(value)) {

View file

@ -59,7 +59,7 @@ void HTMLTableCellElement::apply_presentational_hints(GC::Ref<CSS::CascadedPrope
// https://html.spec.whatwg.org/multipage/rendering.html#tables-2:rules-for-parsing-a-legacy-colour-value
auto color = parse_legacy_color_value(value);
if (color.has_value())
cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::BackgroundColor, CSS::CSSColorValue::create_from_color(color.value()));
cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::BackgroundColor, CSS::CSSColorValue::create_from_color(color.value(), CSS::ColorSyntax::Legacy));
return;
}
if (name == HTML::AttributeNames::valign) {

View file

@ -102,7 +102,7 @@ void HTMLTableElement::apply_presentational_hints(GC::Ref<CSS::CascadedPropertie
// https://html.spec.whatwg.org/multipage/rendering.html#tables-2:rules-for-parsing-a-legacy-colour-value
auto color = parse_legacy_color_value(value);
if (color.has_value())
cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::BackgroundColor, CSS::CSSColorValue::create_from_color(color.value()));
cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::BackgroundColor, CSS::CSSColorValue::create_from_color(color.value(), CSS::ColorSyntax::Legacy));
return;
}
if (name == HTML::AttributeNames::cellspacing) {
@ -118,7 +118,7 @@ void HTMLTableElement::apply_presentational_hints(GC::Ref<CSS::CascadedPropertie
auto legacy_line_style = CSS::CSSKeywordValue::create(CSS::Keyword::Outset);
cascaded_properties->set_property_from_presentational_hint(style_property, legacy_line_style);
cascaded_properties->set_property_from_presentational_hint(width_property, CSS::LengthStyleValue::create(CSS::Length::make_px(border)));
cascaded_properties->set_property_from_presentational_hint(color_property, CSS::CSSColorValue::create_from_color(Color(128, 128, 128)));
cascaded_properties->set_property_from_presentational_hint(color_property, CSS::CSSColorValue::create_from_color(Color(128, 128, 128), CSS::ColorSyntax::Legacy));
};
apply_border_style(CSS::PropertyID::BorderLeftStyle, CSS::PropertyID::BorderLeftWidth, CSS::PropertyID::BorderLeftColor);
apply_border_style(CSS::PropertyID::BorderTopStyle, CSS::PropertyID::BorderTopWidth, CSS::PropertyID::BorderTopColor);
@ -131,7 +131,7 @@ void HTMLTableElement::apply_presentational_hints(GC::Ref<CSS::CascadedPropertie
// and if that does not return failure, the user agent is expected to treat the attribute as a presentational hint setting the element's
// 'border-top-color', 'border-right-color', 'border-bottom-color', and 'border-left-color' properties to the resulting color.
if (auto parsed_color = parse_legacy_color_value(value); parsed_color.has_value()) {
auto color_value = CSS::CSSColorValue::create_from_color(parsed_color.value());
auto color_value = CSS::CSSColorValue::create_from_color(parsed_color.value(), CSS::ColorSyntax::Legacy);
cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::BorderTopColor, color_value);
cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::BorderRightColor, color_value);
cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::BorderBottomColor, color_value);

View file

@ -58,7 +58,7 @@ void HTMLTableRowElement::apply_presentational_hints(GC::Ref<CSS::CascadedProper
// https://html.spec.whatwg.org/multipage/rendering.html#tables-2:rules-for-parsing-a-legacy-colour-value
auto color = parse_legacy_color_value(value);
if (color.has_value())
cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::BackgroundColor, CSS::CSSColorValue::create_from_color(color.value()));
cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::BackgroundColor, CSS::CSSColorValue::create_from_color(color.value(), CSS::ColorSyntax::Legacy));
} else if (name == HTML::AttributeNames::background) {
// https://html.spec.whatwg.org/multipage/rendering.html#tables-2:encoding-parsing-and-serializing-a-url
if (auto parsed_value = document().encoding_parse_url(value); parsed_value.has_value())

View file

@ -122,7 +122,7 @@ void HTMLTableSectionElement::apply_presentational_hints(GC::Ref<CSS::CascadedPr
// https://html.spec.whatwg.org/multipage/rendering.html#tables-2:rules-for-parsing-a-legacy-colour-value
else if (name == HTML::AttributeNames::bgcolor) {
if (auto color = parse_legacy_color_value(value); color.has_value())
cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::BackgroundColor, CSS::CSSColorValue::create_from_color(color.value()));
cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::BackgroundColor, CSS::CSSColorValue::create_from_color(color.value(), CSS::ColorSyntax::Legacy));
} else if (name == HTML::AttributeNames::height) {
if (auto parsed_value = parse_dimension_value(value))
cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::Height, parsed_value.release_nonnull());

View file

@ -315,6 +315,66 @@ static ColorStopList expand_repeat_length(ColorStopList const& color_stop_list,
return color_stop_list_with_expanded_repeat;
}
static SkGradientShader::Interpolation to_skia_interpolation(CSS::InterpolationMethod interpolation_method)
{
SkGradientShader::Interpolation interpolation;
switch (interpolation_method.color_space) {
case CSS::GradientSpace::sRGB:
interpolation.fColorSpace = SkGradientShader::Interpolation::ColorSpace::kSRGB;
break;
case CSS::GradientSpace::sRGBLinear:
interpolation.fColorSpace = SkGradientShader::Interpolation::ColorSpace::kSRGBLinear;
break;
case CSS::GradientSpace::Lab:
interpolation.fColorSpace = SkGradientShader::Interpolation::ColorSpace::kLab;
break;
case CSS::GradientSpace::OKLab:
interpolation.fColorSpace = SkGradientShader::Interpolation::ColorSpace::kOKLab;
break;
case CSS::GradientSpace::HSL:
interpolation.fColorSpace = SkGradientShader::Interpolation::ColorSpace::kHSL;
break;
case CSS::GradientSpace::HWB:
interpolation.fColorSpace = SkGradientShader::Interpolation::ColorSpace::kHWB;
break;
case CSS::GradientSpace::LCH:
interpolation.fColorSpace = SkGradientShader::Interpolation::ColorSpace::kLCH;
break;
case CSS::GradientSpace::OKLCH:
interpolation.fColorSpace = SkGradientShader::Interpolation::ColorSpace::kOKLCH;
break;
case CSS::GradientSpace::DisplayP3:
case CSS::GradientSpace::A98RGB:
case CSS::GradientSpace::ProPhotoRGB:
case CSS::GradientSpace::Rec2020:
case CSS::GradientSpace::XYZD50:
case CSS::GradientSpace::XYZD65:
dbgln("FIXME: Unsupported gradient color space");
interpolation.fColorSpace = SkGradientShader::Interpolation::ColorSpace::kOKLab;
break;
}
switch (interpolation_method.hue_method) {
case CSS::HueMethod::Shorter:
interpolation.fHueMethod = SkGradientShader::Interpolation::HueMethod::kShorter;
break;
case CSS::HueMethod::Longer:
interpolation.fHueMethod = SkGradientShader::Interpolation::HueMethod::kLonger;
break;
case CSS::HueMethod::Increasing:
interpolation.fHueMethod = SkGradientShader::Interpolation::HueMethod::kIncreasing;
break;
case CSS::HueMethod::Decreasing:
interpolation.fHueMethod = SkGradientShader::Interpolation::HueMethod::kDecreasing;
break;
}
interpolation.fInPremul = SkGradientShader::Interpolation::InPremul::kYes;
return interpolation;
}
void DisplayListPlayerSkia::paint_linear_gradient(PaintLinearGradient const& command)
{
auto const& linear_gradient_data = command.linear_gradient_data;
@ -351,9 +411,7 @@ void DisplayListPlayerSkia::paint_linear_gradient(PaintLinearGradient const& com
matrix.setRotate(linear_gradient_data.gradient_angle, center.x(), center.y());
auto color_space = SkColorSpace::MakeSRGB();
SkGradientShader::Interpolation interpolation = {};
interpolation.fColorSpace = SkGradientShader::Interpolation::ColorSpace::kSRGB;
interpolation.fInPremul = SkGradientShader::Interpolation::InPremul::kYes;
auto interpolation = to_skia_interpolation(linear_gradient_data.interpolation_method);
auto shader = SkGradientShader::MakeLinear(points.data(), colors.data(), color_space, positions.data(), positions.size(), SkTileMode::kClamp, interpolation, &matrix);
SkPaint paint;
@ -766,9 +824,7 @@ void DisplayListPlayerSkia::paint_radial_gradient(PaintRadialGradient const& com
tile_mode = SkTileMode::kRepeat;
auto color_space = SkColorSpace::MakeSRGB();
SkGradientShader::Interpolation interpolation = {};
interpolation.fColorSpace = SkGradientShader::Interpolation::ColorSpace::kSRGB;
interpolation.fInPremul = SkGradientShader::Interpolation::InPremul::kYes;
auto interpolation = to_skia_interpolation(radial_gradient_data.interpolation_method);
auto shader = SkGradientShader::MakeRadial(center, size.height(), colors.data(), color_space, positions.data(), positions.size(), tile_mode, interpolation, &matrix);
SkPaint paint;
@ -805,9 +861,7 @@ void DisplayListPlayerSkia::paint_conic_gradient(PaintConicGradient const& comma
SkMatrix matrix;
matrix.setRotate(-90 + conic_gradient_data.start_angle, center.x(), center.y());
auto color_space = SkColorSpace::MakeSRGB();
SkGradientShader::Interpolation interpolation = {};
interpolation.fColorSpace = SkGradientShader::Interpolation::ColorSpace::kSRGB;
interpolation.fInPremul = SkGradientShader::Interpolation::InPremul::kYes;
auto interpolation = to_skia_interpolation(conic_gradient_data.interpolation_method);
auto shader = SkGradientShader::MakeSweep(center.x(), center.y(), colors.data(), color_space, positions.data(), positions.size(), SkTileMode::kRepeat, 0, 360, interpolation, &matrix);
SkPaint paint;

View file

@ -10,6 +10,7 @@
#include <AK/Vector.h>
#include <LibGfx/Color.h>
#include <LibGfx/Gradients.h>
#include <LibWeb/CSS/StyleValues/AbstractImageStyleValue.h>
#include <LibWeb/Forward.h>
namespace Web::Painting {
@ -24,15 +25,18 @@ struct ColorStopData {
struct LinearGradientData {
float gradient_angle;
ColorStopData color_stops;
CSS::InterpolationMethod interpolation_method;
};
struct ConicGradientData {
float start_angle;
ColorStopData color_stops;
CSS::InterpolationMethod interpolation_method;
};
struct RadialGradientData {
ColorStopData color_stops;
CSS::InterpolationMethod interpolation_method;
};
}

View file

@ -121,7 +121,7 @@ LinearGradientData resolve_linear_gradient_data(Layout::NodeWithStyle const& nod
},
linear_gradient.is_repeating());
return { gradient_angle, resolved_color_stops };
return { gradient_angle, resolved_color_stops, linear_gradient.interpolation_method() };
}
ConicGradientData resolve_conic_gradient_data(Layout::NodeWithStyle const& node, CSS::ConicGradientStyleValue const& conic_gradient)
@ -132,7 +132,7 @@ ConicGradientData resolve_conic_gradient_data(Layout::NodeWithStyle const& node,
return angle_percentage.resolved(node, one_turn).to_degrees() / one_turn.to_degrees();
},
conic_gradient.is_repeating());
return { conic_gradient.angle_degrees(), resolved_color_stops };
return { conic_gradient.angle_degrees(), resolved_color_stops, conic_gradient.interpolation_method() };
}
RadialGradientData resolve_radial_gradient_data(Layout::NodeWithStyle const& node, CSSPixelSize gradient_size, CSS::RadialGradientStyleValue const& radial_gradient)
@ -143,7 +143,7 @@ RadialGradientData resolve_radial_gradient_data(Layout::NodeWithStyle const& nod
return length_percentage.to_px(node, gradient_size.width()).to_float() / gradient_size.width().to_float();
},
radial_gradient.is_repeating());
return { resolved_color_stops };
return { resolved_color_stops, radial_gradient.interpolation_method() };
}
}

View file

@ -0,0 +1,10 @@
<style>
* {
margin: 0;
}
body {
background-color: white;
}
</style>
<img src="../images/gradient-interpolation-method-ref.png">

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

View file

@ -0,0 +1,19 @@
<!DOCTYPE html>
<link rel="match" href="../expected/gradient-interpolation-method-ref.html" />
<style>
div {
width: 480px;
height: 50px;
}
</style>
<div style="background: linear-gradient(to right in srgb, red, blue)"></div>
<div style="background: linear-gradient(to right in srgb-linear, red, blue)"></div>
<div style="background: linear-gradient(to right in lab, red, blue)"></div>
<div style="background: linear-gradient(to right in oklab, red, blue)"></div>
<div style="background: linear-gradient(to right in hsl, red, blue)"></div>
<div style="background: linear-gradient(to right in hwb, red, blue)"></div>
<div style="background: linear-gradient(to right in lch, red, blue)"></div>
<div style="background: linear-gradient(to right in oklch shorter hue, red, blue)"></div>
<div style="background: linear-gradient(to right in oklch longer hue, red, blue)"></div>
<div style="background: linear-gradient(to right in oklch increasing hue, red, blue)"></div>
<div style="background: linear-gradient(to right in oklch decreasing hue, red, blue)"></div>