mirror of
https://github.com/RGBCube/serenity
synced 2025-07-27 04:37:34 +00:00
LibWeb: Add FilterValueListStyleValue
This style value holds a list of CSS filter function calls e.g. blur(10px) invert() grayscale() It will be used to implement backdrop-filter, but the same style value can be used for the image filter property. (The name is a little awkward but it's referenced to as filter-value-list in the spec too).
This commit is contained in:
parent
776538a6c4
commit
980c92e9b5
3 changed files with 249 additions and 0 deletions
|
@ -96,6 +96,12 @@ ContentStyleValue const& StyleValue::as_content() const
|
|||
return static_cast<ContentStyleValue const&>(*this);
|
||||
}
|
||||
|
||||
FilterValueListStyleValue const& StyleValue::as_filter_value_list() const
|
||||
{
|
||||
VERIFY(is_filter_value_list());
|
||||
return static_cast<FilterValueListStyleValue const&>(*this);
|
||||
}
|
||||
|
||||
FlexStyleValue const& StyleValue::as_flex() const
|
||||
{
|
||||
VERIFY(is_flex());
|
||||
|
@ -1163,6 +1169,171 @@ bool ContentStyleValue::equals(StyleValue const& other) const
|
|||
return true;
|
||||
}
|
||||
|
||||
float Filter::Blur::resolved_radius(Layout::Node const& node) const
|
||||
{
|
||||
// Default value when omitted is 0px.
|
||||
auto sigma = 0;
|
||||
if (radius.has_value())
|
||||
sigma = radius->resolved(node).to_px(node);
|
||||
// Note: The radius/sigma of the blur needs to be doubled for LibGfx's blur functions.
|
||||
return sigma * 2;
|
||||
}
|
||||
|
||||
Filter::DropShadow::Resolved Filter::DropShadow::resolved(Layout::Node const& node) const
|
||||
{
|
||||
// The default value for omitted values is missing length values set to 0
|
||||
// and the missing used color is taken from the color property.
|
||||
return Resolved {
|
||||
offset_x.resolved(node).to_px(node),
|
||||
offset_y.resolved(node).to_px(node),
|
||||
radius.has_value() ? radius->resolved(node).to_px(node) : 0.0f,
|
||||
color.has_value() ? *color : node.computed_values().color()
|
||||
};
|
||||
}
|
||||
|
||||
float Filter::HueRotate::angle_degrees() const
|
||||
{
|
||||
// Default value when omitted is 0deg.
|
||||
if (!angle.has_value())
|
||||
return 0.0f;
|
||||
return angle->visit([&](Angle const& angle) { return angle.to_degrees(); }, [&](auto) { return 0.0f; });
|
||||
}
|
||||
|
||||
float Filter::Color::resolved_amount() const
|
||||
{
|
||||
if (amount.has_value()) {
|
||||
if (amount->is_percentage())
|
||||
return amount->percentage().as_fraction();
|
||||
return amount->number().value();
|
||||
}
|
||||
// All color filters (brightness, sepia, etc) have a default amount of 1.
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
String FilterValueListStyleValue::to_string() const
|
||||
{
|
||||
StringBuilder builder {};
|
||||
bool first = true;
|
||||
for (auto& filter_function : filter_value_list()) {
|
||||
if (!first)
|
||||
builder.append(' ');
|
||||
filter_function.visit(
|
||||
[&](Filter::Blur const& blur) {
|
||||
builder.append("blur("sv);
|
||||
if (blur.radius.has_value())
|
||||
builder.append(blur.radius->to_string());
|
||||
},
|
||||
[&](Filter::DropShadow const& drop_shadow) {
|
||||
builder.appendff("drop-shadow({} {}"sv,
|
||||
drop_shadow.offset_x, drop_shadow.offset_y);
|
||||
if (drop_shadow.radius.has_value())
|
||||
builder.appendff(" {}", drop_shadow.radius->to_string());
|
||||
if (drop_shadow.color.has_value()) {
|
||||
builder.append(' ');
|
||||
serialize_a_srgb_value(builder, *drop_shadow.color);
|
||||
}
|
||||
},
|
||||
[&](Filter::HueRotate const& hue_rotate) {
|
||||
builder.append("hue-rotate("sv);
|
||||
if (hue_rotate.angle.has_value()) {
|
||||
hue_rotate.angle->visit(
|
||||
[&](Angle const& angle) {
|
||||
builder.append(angle.to_string());
|
||||
},
|
||||
[&](auto&) {
|
||||
builder.append('0');
|
||||
});
|
||||
}
|
||||
},
|
||||
[&](Filter::Color const& color) {
|
||||
builder.appendff("{}(",
|
||||
[&] {
|
||||
switch (color.operation) {
|
||||
case Filter::Color::Operation::Brightness:
|
||||
return "brightness"sv;
|
||||
case Filter::Color::Operation::Contrast:
|
||||
return "contrast"sv;
|
||||
case Filter::Color::Operation::Grayscale:
|
||||
return "grayscale"sv;
|
||||
case Filter::Color::Operation::Invert:
|
||||
return "invert"sv;
|
||||
case Filter::Color::Operation::Opacity:
|
||||
return "opacity"sv;
|
||||
case Filter::Color::Operation::Saturate:
|
||||
return "saturate"sv;
|
||||
case Filter::Color::Operation::Sepia:
|
||||
return "sepia"sv;
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
}());
|
||||
if (color.amount.has_value())
|
||||
builder.append(color.amount->to_string());
|
||||
});
|
||||
builder.append(')');
|
||||
first = false;
|
||||
}
|
||||
return builder.to_string();
|
||||
}
|
||||
|
||||
static bool operator==(Filter::Blur const& a, Filter::Blur const& b)
|
||||
{
|
||||
return a.radius == b.radius;
|
||||
}
|
||||
|
||||
static bool operator==(Filter::DropShadow const& a, Filter::DropShadow const& b)
|
||||
{
|
||||
return a.offset_x == b.offset_x && a.offset_y == b.offset_y && a.radius == b.radius && a.color == b.color;
|
||||
}
|
||||
|
||||
static bool operator==(Filter::HueRotate::Zero const&, Filter::HueRotate::Zero const&)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool operator==(Filter::Color const& a, Filter::Color const& b)
|
||||
{
|
||||
return a.operation == b.operation && a.amount == b.amount;
|
||||
}
|
||||
|
||||
static bool operator==(Filter::HueRotate const& a, Filter::HueRotate const& b)
|
||||
{
|
||||
return a.angle == b.angle;
|
||||
}
|
||||
|
||||
static bool variant_equals(auto const& a, auto const& b)
|
||||
{
|
||||
return a.visit([&](auto const& held_value) {
|
||||
using HeldType = AK::Detail::Decay<decltype(held_value)>;
|
||||
bool other_holds_same_type = b.template has<HeldType>();
|
||||
return other_holds_same_type && held_value == b.template get<HeldType>();
|
||||
});
|
||||
}
|
||||
|
||||
static bool operator==(Filter::HueRotate::AngleOrZero const& a, Filter::HueRotate::AngleOrZero const& b)
|
||||
{
|
||||
return variant_equals(a, b);
|
||||
}
|
||||
|
||||
static bool operator==(FilterFunction const& a, FilterFunction const& b)
|
||||
{
|
||||
return variant_equals(a, b);
|
||||
}
|
||||
|
||||
bool FilterValueListStyleValue::equals(StyleValue const& other) const
|
||||
{
|
||||
if (type() != other.type())
|
||||
return false;
|
||||
auto const& typed_other = other.as_filter_value_list();
|
||||
if (m_filter_value_list.size() != typed_other.m_filter_value_list.size())
|
||||
return false;
|
||||
for (size_t i = 0; i < m_filter_value_list.size(); i++) {
|
||||
if (m_filter_value_list[i] != typed_other.m_filter_value_list[i])
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
String FlexStyleValue::to_string() const
|
||||
{
|
||||
return String::formatted("{} {} {}", m_grow->to_string(), m_shrink->to_string(), m_basis->to_string());
|
||||
|
|
|
@ -97,6 +97,52 @@ struct EdgeRect {
|
|||
Gfx::FloatRect resolved(Layout::Node const&, Gfx::FloatRect) const;
|
||||
};
|
||||
|
||||
namespace Filter {
|
||||
|
||||
struct Blur {
|
||||
Optional<Length> radius {};
|
||||
float resolved_radius(Layout::Node const&) const;
|
||||
};
|
||||
|
||||
struct DropShadow {
|
||||
Length offset_x;
|
||||
Length offset_y;
|
||||
Optional<Length> radius {};
|
||||
Optional<Color> color {};
|
||||
struct Resolved {
|
||||
float offset_x;
|
||||
float offset_y;
|
||||
float radius;
|
||||
Color color;
|
||||
};
|
||||
Resolved resolved(Layout::Node const&) const;
|
||||
};
|
||||
|
||||
struct HueRotate {
|
||||
struct Zero { };
|
||||
using AngleOrZero = Variant<Angle, Zero>;
|
||||
Optional<AngleOrZero> angle {};
|
||||
float angle_degrees() const;
|
||||
};
|
||||
|
||||
struct Color {
|
||||
enum class Operation {
|
||||
Brightness,
|
||||
Contrast,
|
||||
Grayscale,
|
||||
Invert,
|
||||
Opacity,
|
||||
Saturate,
|
||||
Sepia
|
||||
} operation;
|
||||
Optional<NumberPercentage> amount {};
|
||||
float resolved_amount() const;
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
using FilterFunction = Variant<Filter::Blur, Filter::DropShadow, Filter::HueRotate, Filter::Color>;
|
||||
|
||||
// FIXME: Find a better place for this helper.
|
||||
inline Gfx::Painter::ScalingMode to_gfx_scaling_mode(CSS::ImageRendering css_value)
|
||||
{
|
||||
|
@ -128,6 +174,7 @@ public:
|
|||
Calculated,
|
||||
Color,
|
||||
Content,
|
||||
FilterValueList,
|
||||
Flex,
|
||||
FlexFlow,
|
||||
Font,
|
||||
|
@ -172,6 +219,7 @@ public:
|
|||
bool is_calculated() const { return type() == Type::Calculated; }
|
||||
bool is_color() const { return type() == Type::Color; }
|
||||
bool is_content() const { return type() == Type::Content; }
|
||||
bool is_filter_value_list() const { return type() == Type::FilterValueList; }
|
||||
bool is_flex() const { return type() == Type::Flex; }
|
||||
bool is_flex_flow() const { return type() == Type::FlexFlow; }
|
||||
bool is_font() const { return type() == Type::Font; }
|
||||
|
@ -214,6 +262,7 @@ public:
|
|||
CalculatedStyleValue const& as_calculated() const;
|
||||
ColorStyleValue const& as_color() const;
|
||||
ContentStyleValue const& as_content() const;
|
||||
FilterValueListStyleValue const& as_filter_value_list() const;
|
||||
FlexFlowStyleValue const& as_flex_flow() const;
|
||||
FlexStyleValue const& as_flex() const;
|
||||
FontStyleValue const& as_font() const;
|
||||
|
@ -254,6 +303,7 @@ public:
|
|||
CalculatedStyleValue& as_calculated() { return const_cast<CalculatedStyleValue&>(const_cast<StyleValue const&>(*this).as_calculated()); }
|
||||
ColorStyleValue& as_color() { return const_cast<ColorStyleValue&>(const_cast<StyleValue const&>(*this).as_color()); }
|
||||
ContentStyleValue& as_content() { return const_cast<ContentStyleValue&>(const_cast<StyleValue const&>(*this).as_content()); }
|
||||
FilterValueListStyleValue& as_filter_value_list() { return const_cast<FilterValueListStyleValue&>(const_cast<StyleValue const&>(*this).as_filter_value_list()); }
|
||||
FlexFlowStyleValue& as_flex_flow() { return const_cast<FlexFlowStyleValue&>(const_cast<StyleValue const&>(*this).as_flex_flow()); }
|
||||
FlexStyleValue& as_flex() { return const_cast<FlexStyleValue&>(const_cast<StyleValue const&>(*this).as_flex()); }
|
||||
FontStyleValue& as_font() { return const_cast<FontStyleValue&>(const_cast<StyleValue const&>(*this).as_font()); }
|
||||
|
@ -803,6 +853,33 @@ private:
|
|||
RefPtr<StyleValueList> m_alt_text;
|
||||
};
|
||||
|
||||
class FilterValueListStyleValue final : public StyleValue {
|
||||
public:
|
||||
static NonnullRefPtr<FilterValueListStyleValue> create(
|
||||
Vector<FilterFunction> filter_value_list)
|
||||
{
|
||||
VERIFY(filter_value_list.size() >= 1);
|
||||
return adopt_ref(*new FilterValueListStyleValue(move(filter_value_list)));
|
||||
}
|
||||
|
||||
Vector<FilterFunction> const& filter_value_list() const { return m_filter_value_list; }
|
||||
|
||||
virtual String to_string() const override;
|
||||
virtual bool equals(StyleValue const& other) const override;
|
||||
|
||||
virtual ~FilterValueListStyleValue() override = default;
|
||||
|
||||
private:
|
||||
FilterValueListStyleValue(Vector<FilterFunction> filter_value_list)
|
||||
: StyleValue(Type::FilterValueList)
|
||||
, m_filter_value_list(move(filter_value_list))
|
||||
{
|
||||
}
|
||||
|
||||
// FIXME: No support for SVG filters yet
|
||||
Vector<FilterFunction> m_filter_value_list;
|
||||
};
|
||||
|
||||
class FlexStyleValue final : public StyleValue {
|
||||
public:
|
||||
static NonnullRefPtr<FlexStyleValue> create(
|
||||
|
|
|
@ -50,6 +50,7 @@ class CSSStyleSheet;
|
|||
class CSSSupportsRule;
|
||||
class Display;
|
||||
class ElementInlineCSSStyleDeclaration;
|
||||
class FilterValueListStyleValue;
|
||||
class FlexFlowStyleValue;
|
||||
class FlexStyleValue;
|
||||
class FontFace;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue