mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-21 23:20:20 +00:00
LibWeb: Allow calculated values in css filters
This commit is contained in:
parent
af3383df09
commit
4ecf56cadf
Notes:
github-actions[bot]
2024-10-31 07:20:39 +00:00
Author: https://github.com/Gingeh Commit: https://github.com/LadybirdBrowser/ladybird/commit/4ecf56cadf2 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/2072
11 changed files with 166 additions and 59 deletions
|
@ -20,3 +20,4 @@
|
|||
<div style="filter: drop-shadow(5px 5px 5px)"></div>
|
||||
<div style="filter: drop-shadow(red 5px 5px 5px)"></div>
|
||||
<div style="filter: drop-shadow(5px 5px 5px red)"></div>
|
||||
<div style="filter: drop-shadow(calc(5*1px) calc(2px + 3px))"></div>
|
||||
|
|
74
Tests/LibWeb/Screenshot/css-filter.html
Normal file
74
Tests/LibWeb/Screenshot/css-filter.html
Normal file
|
@ -0,0 +1,74 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<link rel="match" href="reference/css-filter-ref.html" />
|
||||
<style>
|
||||
body {
|
||||
font-size: 0;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 50px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- blur() -->
|
||||
<img src="assets/car.png" style="filter: blur()">
|
||||
<img src="assets/car.png" style="filter: blur(0)">
|
||||
<img src="assets/car.png" style="filter: blur(4px)">
|
||||
<img src="assets/car.png" style="filter: blur(calc(4 * 1px))">
|
||||
|
||||
<!-- drop-shadow() is tested in css-filter-drop-shadow.html -->
|
||||
|
||||
<!-- hue-rotate() -->
|
||||
<img src="assets/car.png" style="filter: hue-rotate()">
|
||||
<img src="assets/car.png" style="filter: hue-rotate(0)">
|
||||
<img src="assets/car.png" style="filter: hue-rotate(90)">
|
||||
<img src="assets/car.png" style="filter: hue-rotate(90deg)">
|
||||
<img src="assets/car.png" style="filter: hue-rotate(-90deg)">
|
||||
<img src="assets/car.png" style="filter: hue-rotate(calc(180 * 1deg))">
|
||||
|
||||
<!-- simple color filters -->
|
||||
<!-- omitted -->
|
||||
<img src="assets/car.png" style="filter: brightness()">
|
||||
<img src="assets/car.png" style="filter: contrast()">
|
||||
<img src="assets/car.png" style="filter: grayscale()">
|
||||
<img src="assets/car.png" style="filter: invert()">
|
||||
<img src="assets/car.png" style="filter: opacity()">
|
||||
<img src="assets/car.png" style="filter: sepia()">
|
||||
<img src="assets/car.png" style="filter: saturate()">
|
||||
|
||||
<!-- number -->
|
||||
<img src="assets/car.png" style="filter: brightness(0.75)">
|
||||
<img src="assets/car.png" style="filter: contrast(0.75)">
|
||||
<img src="assets/car.png" style="filter: grayscale(0.75)">
|
||||
<img src="assets/car.png" style="filter: invert(0.75)">
|
||||
<img src="assets/car.png" style="filter: opacity(0.75)">
|
||||
<img src="assets/car.png" style="filter: sepia(0.75)">
|
||||
<img src="assets/car.png" style="filter: saturate(0.75)">
|
||||
|
||||
<!-- percentage -->
|
||||
<img src="assets/car.png" style="filter: brightness(75%)">
|
||||
<img src="assets/car.png" style="filter: contrast(75%)">
|
||||
<img src="assets/car.png" style="filter: grayscale(75%)">
|
||||
<img src="assets/car.png" style="filter: invert(75%)">
|
||||
<img src="assets/car.png" style="filter: opacity(75%)">
|
||||
<img src="assets/car.png" style="filter: sepia(75%)">
|
||||
<img src="assets/car.png" style="filter: saturate(75%)">
|
||||
|
||||
<!-- calculated number -->
|
||||
<img src="assets/car.png" style="filter: brightness(calc(3 / 4))">
|
||||
<img src="assets/car.png" style="filter: contrast(calc(3 / 4))">
|
||||
<img src="assets/car.png" style="filter: grayscale(calc(3 / 4))">
|
||||
<img src="assets/car.png" style="filter: invert(calc(3 / 4))">
|
||||
<img src="assets/car.png" style="filter: opacity(calc(3 / 4))">
|
||||
<img src="assets/car.png" style="filter: sepia(calc(3 / 4))">
|
||||
<img src="assets/car.png" style="filter: saturate(calc(3 / 4))">
|
||||
|
||||
<!-- calculated percentage -->
|
||||
<img src="assets/car.png" style="filter: brightness(calc(3 * 25%))">
|
||||
<img src="assets/car.png" style="filter: contrast(calc(3 * 25%))">
|
||||
<img src="assets/car.png" style="filter: grayscale(calc(3 * 25%))">
|
||||
<img src="assets/car.png" style="filter: invert(calc(3 * 25%))">
|
||||
<img src="assets/car.png" style="filter: opacity(calc(3 * 25%))">
|
||||
<img src="assets/car.png" style="filter: sepia(calc(3 * 25%))">
|
||||
<img src="assets/car.png" style="filter: saturate(calc(3 * 25%))">
|
Binary file not shown.
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 6.5 KiB |
BIN
Tests/LibWeb/Screenshot/images/css-filter-ref.png
Normal file
BIN
Tests/LibWeb/Screenshot/images/css-filter-ref.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 113 KiB |
10
Tests/LibWeb/Screenshot/reference/css-filter-ref.html
Normal file
10
Tests/LibWeb/Screenshot/reference/css-filter-ref.html
Normal file
|
@ -0,0 +1,10 @@
|
|||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: white;
|
||||
}
|
||||
</style>
|
||||
<img src="../images/css-filter-ref.png">
|
|
@ -4,10 +4,10 @@ animation-duration: 'calc(2s)' -> 'calc(2s)'
|
|||
animation-duration: 'calc(2s * var(--n))' -> '4s'
|
||||
animation-iteration-count: 'calc(2)' -> 'calc(2)'
|
||||
animation-iteration-count: 'calc(2 * var(--n))' -> '4'
|
||||
backdrop-filter: 'grayscale(calc(2%))' -> 'none'
|
||||
backdrop-filter: 'grayscale(calc(2% * var(--n)))' -> 'none'
|
||||
backdrop-filter: 'grayscale(calc(0.02))' -> 'none'
|
||||
backdrop-filter: 'grayscale(calc(0.02 * var(--n)))' -> 'none'
|
||||
backdrop-filter: 'grayscale(calc(2%))' -> 'grayscale(calc(2%))'
|
||||
backdrop-filter: 'grayscale(calc(2% * var(--n)))' -> 'grayscale(calc(2% * 2))'
|
||||
backdrop-filter: 'grayscale(calc(0.02))' -> 'grayscale(calc(0.02))'
|
||||
backdrop-filter: 'grayscale(calc(0.02 * var(--n)))' -> 'grayscale(calc(0.02 * 2))'
|
||||
background-position-x: 'calc(2px)' -> 'left calc(2px)'
|
||||
background-position-x: 'calc(2px * var(--n))' -> 'left calc(2px * 2)'
|
||||
background-position-y: 'calc(2%)' -> 'top calc(2%)'
|
||||
|
@ -56,10 +56,10 @@ cy: 'calc(2%)' -> 'calc(2%)'
|
|||
cy: 'calc(2% * var(--n))' -> '4%'
|
||||
fill-opacity: 'calc(2)' -> 'calc(2)'
|
||||
fill-opacity: 'calc(2 * var(--n))' -> '4'
|
||||
filter: 'grayscale(calc(2%))' -> 'none'
|
||||
filter: 'grayscale(calc(2% * var(--n)))' -> 'none'
|
||||
filter: 'grayscale(calc(0.02))' -> 'none'
|
||||
filter: 'grayscale(calc(0.02 * var(--n)))' -> 'none'
|
||||
filter: 'grayscale(calc(2%))' -> 'grayscale(calc(2%))'
|
||||
filter: 'grayscale(calc(2% * var(--n)))' -> 'grayscale(calc(2% * 2))'
|
||||
filter: 'grayscale(calc(0.02))' -> 'grayscale(calc(0.02))'
|
||||
filter: 'grayscale(calc(0.02 * var(--n)))' -> 'grayscale(calc(0.02 * 2))'
|
||||
flex-basis: 'calc(2px)' -> 'calc(2px)'
|
||||
flex-basis: 'calc(2px * var(--n))' -> 'calc(2px * 2)'
|
||||
flex-grow: 'calc(2)' -> 'calc(2)'
|
||||
|
|
|
@ -2246,6 +2246,29 @@ Optional<NumberOrCalculated> Parser::parse_number(TokenStream<ComponentValue>& t
|
|||
return {};
|
||||
}
|
||||
|
||||
Optional<NumberPercentage> Parser::parse_number_percentage(TokenStream<ComponentValue>& tokens)
|
||||
{
|
||||
auto transaction = tokens.begin_transaction();
|
||||
auto& token = tokens.consume_a_token();
|
||||
|
||||
if (token.is(Token::Type::Number)) {
|
||||
transaction.commit();
|
||||
return token.token().number();
|
||||
}
|
||||
|
||||
if (token.is(Token::Type::Percentage)) {
|
||||
transaction.commit();
|
||||
return Percentage(token.token().percentage());
|
||||
}
|
||||
|
||||
if (auto calc = parse_calculated_value(token); calc && calc->resolves_to_number_percentage()) {
|
||||
transaction.commit();
|
||||
return calc.release_nonnull();
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
Optional<ResolutionOrCalculated> Parser::parse_resolution(TokenStream<ComponentValue>& tokens)
|
||||
{
|
||||
auto transaction = tokens.begin_transaction();
|
||||
|
@ -5395,14 +5418,6 @@ RefPtr<CSSStyleValue> Parser::parse_filter_value_list_value(TokenStream<Componen
|
|||
return static_cast<FilterOperation::Color::Type>(filter);
|
||||
};
|
||||
|
||||
auto parse_number_percentage = [&](auto& token) -> Optional<NumberPercentage> {
|
||||
if (token.is(Token::Type::Percentage))
|
||||
return NumberPercentage(Percentage(token.token().percentage()));
|
||||
if (token.is(Token::Type::Number))
|
||||
return NumberPercentage(Number(Number::Type::Number, token.token().number_value()));
|
||||
return {};
|
||||
};
|
||||
|
||||
auto parse_filter_function_name = [&](auto name) -> Optional<FilterToken> {
|
||||
if (name.equals_ignoring_ascii_case("blur"sv))
|
||||
return FilterToken::Blur;
|
||||
|
@ -5446,8 +5461,7 @@ RefPtr<CSSStyleValue> Parser::parse_filter_value_list_value(TokenStream<Componen
|
|||
tokens.discard_whitespace();
|
||||
if (!blur_radius.has_value())
|
||||
return {};
|
||||
// FIXME: Support calculated radius
|
||||
return if_no_more_tokens_return(FilterOperation::Blur { blur_radius->value() });
|
||||
return if_no_more_tokens_return(FilterOperation::Blur { blur_radius.value() });
|
||||
} else if (filter_token == FilterToken::DropShadow) {
|
||||
if (!tokens.has_next_token())
|
||||
return {};
|
||||
|
@ -5481,29 +5495,24 @@ RefPtr<CSSStyleValue> Parser::parse_filter_value_list_value(TokenStream<Componen
|
|||
if (maybe_color)
|
||||
color = maybe_color->to_color({});
|
||||
|
||||
// FIXME: Support calculated offsets and radius
|
||||
return if_no_more_tokens_return(FilterOperation::DropShadow { x_offset->value(), y_offset->value(), maybe_radius.map([](auto& it) { return it.value(); }), color });
|
||||
return if_no_more_tokens_return(FilterOperation::DropShadow { x_offset.value(), y_offset.value(), maybe_radius, color });
|
||||
} else if (filter_token == FilterToken::HueRotate) {
|
||||
// hue-rotate( [ <angle> | <zero> ]? )
|
||||
if (!tokens.has_next_token())
|
||||
return FilterOperation::HueRotate {};
|
||||
auto& token = tokens.consume_a_token();
|
||||
if (token.is(Token::Type::Number)) {
|
||||
|
||||
if (tokens.next_token().is(Token::Type::Number)) {
|
||||
// hue-rotate(0)
|
||||
auto number = token.token().number();
|
||||
auto number = tokens.consume_a_token().token().number();
|
||||
if (number.is_integer() && number.integer_value() == 0)
|
||||
return if_no_more_tokens_return(FilterOperation::HueRotate { FilterOperation::HueRotate::Zero {} });
|
||||
return {};
|
||||
}
|
||||
if (!token.is(Token::Type::Dimension))
|
||||
|
||||
if (auto angle = parse_angle(tokens); angle.has_value())
|
||||
return if_no_more_tokens_return(FilterOperation::HueRotate { angle.value() });
|
||||
|
||||
return {};
|
||||
auto angle_value = token.token().dimension_value();
|
||||
auto angle_unit_name = token.token().dimension_unit();
|
||||
auto angle_unit = Angle::unit_from_name(angle_unit_name);
|
||||
if (!angle_unit.has_value())
|
||||
return {};
|
||||
Angle angle { angle_value, angle_unit.release_value() };
|
||||
return if_no_more_tokens_return(FilterOperation::HueRotate { angle });
|
||||
} else {
|
||||
// Simple filters:
|
||||
// brightness( <number-percentage>? )
|
||||
|
@ -5515,10 +5524,8 @@ RefPtr<CSSStyleValue> Parser::parse_filter_value_list_value(TokenStream<Componen
|
|||
// saturate( <number-percentage>? )
|
||||
if (!tokens.has_next_token())
|
||||
return FilterOperation::Color { filter_token_to_operation(filter_token) };
|
||||
auto amount = parse_number_percentage(tokens.consume_a_token());
|
||||
if (!amount.has_value())
|
||||
return {};
|
||||
return if_no_more_tokens_return(FilterOperation::Color { filter_token_to_operation(filter_token), *amount });
|
||||
auto amount = parse_number_percentage(tokens);
|
||||
return if_no_more_tokens_return(FilterOperation::Color { filter_token_to_operation(filter_token), amount });
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -202,6 +202,7 @@ private:
|
|||
Optional<LengthOrCalculated> parse_length(TokenStream<ComponentValue>&);
|
||||
Optional<LengthPercentage> parse_length_percentage(TokenStream<ComponentValue>&);
|
||||
Optional<NumberOrCalculated> parse_number(TokenStream<ComponentValue>&);
|
||||
Optional<NumberPercentage> parse_number_percentage(TokenStream<ComponentValue>&);
|
||||
Optional<ResolutionOrCalculated> parse_resolution(TokenStream<ComponentValue>&);
|
||||
Optional<TimeOrCalculated> parse_time(TokenStream<ComponentValue>&);
|
||||
Optional<TimePercentage> parse_time_percentage(TokenStream<ComponentValue>&);
|
||||
|
|
|
@ -15,30 +15,42 @@ namespace Web::CSS {
|
|||
|
||||
float FilterOperation::Blur::resolved_radius(Layout::Node const& node) const
|
||||
{
|
||||
// Default value when omitted is 0px.
|
||||
auto sigma = 0;
|
||||
if (radius.has_value())
|
||||
sigma = radius->to_px(node).to_int();
|
||||
return sigma;
|
||||
return radius->resolved(Length::ResolutionContext::for_layout_node(node)).to_px(node).to_float();
|
||||
|
||||
// Default value when omitted is 0px.
|
||||
return 0;
|
||||
}
|
||||
|
||||
float FilterOperation::HueRotate::angle_degrees() const
|
||||
float FilterOperation::HueRotate::angle_degrees(Layout::Node const& node) const
|
||||
{
|
||||
// Default value when omitted is 0deg.
|
||||
if (!angle.has_value())
|
||||
return 0.0f;
|
||||
return angle->visit([&](Angle const& a) { return a.to_degrees(); }, [&](auto) { return 0.0; });
|
||||
return angle->visit([&](AngleOrCalculated const& a) { return a.resolved(node).to_degrees(); }, [&](Zero) { return 0.0; });
|
||||
}
|
||||
|
||||
float FilterOperation::Color::resolved_amount() const
|
||||
{
|
||||
if (amount.has_value()) {
|
||||
// Default value when omitted is 1.
|
||||
if (!amount.has_value())
|
||||
return 1;
|
||||
|
||||
if (amount->is_number())
|
||||
return amount->number().value();
|
||||
|
||||
if (amount->is_percentage())
|
||||
return amount->percentage().as_fraction();
|
||||
return amount->number().value();
|
||||
|
||||
if (amount->is_calculated()) {
|
||||
if (amount->calculated()->resolves_to_number())
|
||||
return amount->calculated()->resolve_number().value();
|
||||
|
||||
if (amount->calculated()->resolves_to_percentage())
|
||||
return amount->calculated()->resolve_percentage()->as_fraction();
|
||||
}
|
||||
// All color filters (brightness, sepia, etc) have a default amount of 1.
|
||||
return 1.0f;
|
||||
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
String FilterValueListStyleValue::to_string() const
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <LibWeb/CSS/Angle.h>
|
||||
#include <LibWeb/CSS/CalculatedOr.h>
|
||||
#include <LibWeb/CSS/Length.h>
|
||||
#include <LibWeb/CSS/Number.h>
|
||||
#include <LibWeb/CSS/PercentageOr.h>
|
||||
|
@ -19,16 +20,16 @@ namespace Web::CSS {
|
|||
namespace FilterOperation {
|
||||
|
||||
struct Blur {
|
||||
Optional<Length> radius {};
|
||||
Optional<LengthOrCalculated> radius;
|
||||
float resolved_radius(Layout::Node const&) const;
|
||||
bool operator==(Blur const&) const = default;
|
||||
};
|
||||
|
||||
struct DropShadow {
|
||||
Length offset_x;
|
||||
Length offset_y;
|
||||
Optional<Length> radius {};
|
||||
Optional<Color> color {};
|
||||
LengthOrCalculated offset_x;
|
||||
LengthOrCalculated offset_y;
|
||||
Optional<LengthOrCalculated> radius;
|
||||
Optional<Color> color;
|
||||
bool operator==(DropShadow const&) const = default;
|
||||
};
|
||||
|
||||
|
@ -36,9 +37,9 @@ struct HueRotate {
|
|||
struct Zero {
|
||||
bool operator==(Zero const&) const = default;
|
||||
};
|
||||
using AngleOrZero = Variant<Angle, Zero>;
|
||||
Optional<AngleOrZero> angle {};
|
||||
float angle_degrees() const;
|
||||
using AngleOrZero = Variant<AngleOrCalculated, Zero>;
|
||||
Optional<AngleOrZero> angle;
|
||||
float angle_degrees(Layout::Node const&) const;
|
||||
bool operator==(HueRotate const&) const = default;
|
||||
};
|
||||
|
||||
|
|
|
@ -523,12 +523,13 @@ void NodeWithStyle::apply_style(const CSS::StyleProperties& computed_style)
|
|||
.radius = blur.resolved_radius(*this) });
|
||||
},
|
||||
[&](CSS::FilterOperation::DropShadow const& drop_shadow) {
|
||||
auto context = CSS::Length::ResolutionContext::for_layout_node(*this);
|
||||
// The default value for omitted values is missing length values set to 0
|
||||
// and the missing used color is taken from the color property.
|
||||
resolved_filter.filters.append(CSS::ResolvedFilter::DropShadow {
|
||||
.offset_x = drop_shadow.offset_x.to_px(*this).to_double(),
|
||||
.offset_y = drop_shadow.offset_y.to_px(*this).to_double(),
|
||||
.radius = drop_shadow.radius.has_value() ? drop_shadow.radius->to_px(*this).to_double() : 0.0,
|
||||
.offset_x = drop_shadow.offset_x.resolved(context).to_px(*this).to_double(),
|
||||
.offset_y = drop_shadow.offset_y.resolved(context).to_px(*this).to_double(),
|
||||
.radius = drop_shadow.radius.has_value() ? drop_shadow.radius->resolved(context).to_px(*this).to_double() : 0.0,
|
||||
.color = drop_shadow.color.has_value() ? *drop_shadow.color : this->computed_values().color() });
|
||||
},
|
||||
[&](CSS::FilterOperation::Color const& color_operation) {
|
||||
|
@ -537,7 +538,7 @@ void NodeWithStyle::apply_style(const CSS::StyleProperties& computed_style)
|
|||
.amount = color_operation.resolved_amount() });
|
||||
},
|
||||
[&](CSS::FilterOperation::HueRotate const& hue_rotate) {
|
||||
resolved_filter.filters.append(CSS::ResolvedFilter::HueRotate { .angle_degrees = hue_rotate.angle_degrees() });
|
||||
resolved_filter.filters.append(CSS::ResolvedFilter::HueRotate { .angle_degrees = hue_rotate.angle_degrees(*this) });
|
||||
});
|
||||
}
|
||||
return resolved_filter;
|
||||
|
|
Loading…
Reference in a new issue