mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-12-07 21:59:54 +00:00
LibWeb: Support calc within linear() easing function
This commit is contained in:
parent
91925db9ca
commit
2f83356c0f
Notes:
github-actions[bot]
2025-10-20 10:29:34 +00:00
Author: https://github.com/Calme1709
Commit: 2f83356c0f
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/6459
Reviewed-by: https://github.com/AtkinsSJ ✅
6 changed files with 94 additions and 34 deletions
|
|
@ -607,6 +607,7 @@ Optional<CSS::EasingFunction> AnimationEffect::parse_easing_string(StringView va
|
||||||
{
|
{
|
||||||
if (auto style_value = parse_css_value(CSS::Parser::ParsingParams(), value, CSS::PropertyID::AnimationTimingFunction)) {
|
if (auto style_value = parse_css_value(CSS::Parser::ParsingParams(), value, CSS::PropertyID::AnimationTimingFunction)) {
|
||||||
if (style_value->is_easing())
|
if (style_value->is_easing())
|
||||||
|
// FIXME: We should absolutize style_value to resolve relative lengths within calcs
|
||||||
return CSS::EasingFunction::from_style_value(*style_value);
|
return CSS::EasingFunction::from_style_value(*style_value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@
|
||||||
#include "EasingFunction.h"
|
#include "EasingFunction.h"
|
||||||
#include <AK/BinarySearch.h>
|
#include <AK/BinarySearch.h>
|
||||||
#include <LibWeb/CSS/StyleValues/EasingStyleValue.h>
|
#include <LibWeb/CSS/StyleValues/EasingStyleValue.h>
|
||||||
|
#include <LibWeb/CSS/StyleValues/NumberStyleValue.h>
|
||||||
|
#include <LibWeb/CSS/StyleValues/PercentageStyleValue.h>
|
||||||
|
|
||||||
namespace Web::CSS {
|
namespace Web::CSS {
|
||||||
|
|
||||||
|
|
@ -267,13 +269,40 @@ static Vector<LinearEasingFunction::ControlPoint> canonicalize_linear_easing_fun
|
||||||
|
|
||||||
EasingFunction EasingFunction::from_style_value(StyleValue const& style_value)
|
EasingFunction EasingFunction::from_style_value(StyleValue const& style_value)
|
||||||
{
|
{
|
||||||
|
auto const resolve_number = [](StyleValue const& style_value) {
|
||||||
|
if (style_value.is_number())
|
||||||
|
return style_value.as_number().number();
|
||||||
|
|
||||||
|
if (style_value.is_calculated())
|
||||||
|
return style_value.as_calculated().resolve_number({}).value();
|
||||||
|
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
|
};
|
||||||
|
|
||||||
|
auto const resolve_percentage = [](StyleValue const& style_value) {
|
||||||
|
if (style_value.is_percentage())
|
||||||
|
return style_value.as_percentage().percentage().as_fraction();
|
||||||
|
|
||||||
|
if (style_value.is_calculated())
|
||||||
|
return style_value.as_calculated().resolve_percentage({})->as_fraction();
|
||||||
|
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
|
};
|
||||||
|
|
||||||
if (style_value.is_easing()) {
|
if (style_value.is_easing()) {
|
||||||
return style_value.as_easing().function().visit(
|
return style_value.as_easing().function().visit(
|
||||||
[](EasingStyleValue::Linear const& linear) -> EasingFunction {
|
[&](EasingStyleValue::Linear const& linear) -> EasingFunction {
|
||||||
Vector<LinearEasingFunction::ControlPoint> resolved_control_points;
|
Vector<LinearEasingFunction::ControlPoint> resolved_control_points;
|
||||||
|
|
||||||
for (auto const& control_point : linear.stops)
|
for (auto const& control_point : linear.stops) {
|
||||||
resolved_control_points.append({ control_point.input, control_point.output });
|
double output = resolve_number(control_point.output);
|
||||||
|
|
||||||
|
Optional<double> input;
|
||||||
|
if (control_point.input)
|
||||||
|
input = resolve_percentage(*control_point.input);
|
||||||
|
|
||||||
|
resolved_control_points.append({ input, output });
|
||||||
|
}
|
||||||
|
|
||||||
// https://drafts.csswg.org/css-easing-2/#funcdef-linear
|
// https://drafts.csswg.org/css-easing-2/#funcdef-linear
|
||||||
// If an argument lacks a <percentage>, its input progress value is initially empty. This is corrected
|
// If an argument lacks a <percentage>, its input progress value is initially empty. This is corrected
|
||||||
|
|
|
||||||
|
|
@ -2908,32 +2908,32 @@ RefPtr<StyleValue const> Parser::parse_easing_value(TokenStream<ComponentValue>&
|
||||||
for (auto const& argument : comma_separated_arguments) {
|
for (auto const& argument : comma_separated_arguments) {
|
||||||
TokenStream argument_tokens { argument };
|
TokenStream argument_tokens { argument };
|
||||||
|
|
||||||
Optional<double> output;
|
RefPtr<StyleValue const> output;
|
||||||
Optional<double> first_input;
|
RefPtr<StyleValue const> first_input;
|
||||||
Optional<double> second_input;
|
RefPtr<StyleValue const> second_input;
|
||||||
|
|
||||||
if (argument_tokens.next_token().is(Token::Type::Number))
|
if (auto maybe_output = parse_number_value(argument_tokens))
|
||||||
output = argument_tokens.consume_a_token().token().number_value();
|
output = maybe_output;
|
||||||
|
|
||||||
if (argument_tokens.next_token().is(Token::Type::Percentage)) {
|
if (auto maybe_first_input = parse_percentage_value(argument_tokens)) {
|
||||||
first_input = argument_tokens.consume_a_token().token().percentage() / 100;
|
first_input = maybe_first_input;
|
||||||
if (argument_tokens.next_token().is(Token::Type::Percentage)) {
|
if (auto maybe_second_input = parse_percentage_value(argument_tokens)) {
|
||||||
second_input = argument_tokens.consume_a_token().token().percentage() / 100;
|
second_input = maybe_second_input;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argument_tokens.next_token().is(Token::Type::Number)) {
|
if (auto maybe_output = parse_number_value(argument_tokens)) {
|
||||||
if (output.has_value())
|
if (output)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
output = argument_tokens.consume_a_token().token().number_value();
|
output = maybe_output;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argument_tokens.has_next_token() || !output.has_value())
|
if (argument_tokens.has_next_token() || !output)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
stops.append({ output.value(), first_input });
|
stops.append({ *output, first_input });
|
||||||
if (second_input.has_value())
|
if (second_input)
|
||||||
stops.append({ output.value(), second_input });
|
stops.append({ *output, second_input });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stops.is_empty())
|
if (stops.is_empty())
|
||||||
|
|
|
||||||
|
|
@ -12,13 +12,14 @@
|
||||||
#include <AK/BinarySearch.h>
|
#include <AK/BinarySearch.h>
|
||||||
#include <AK/StringBuilder.h>
|
#include <AK/StringBuilder.h>
|
||||||
#include <LibWeb/CSS/StyleValues/IntegerStyleValue.h>
|
#include <LibWeb/CSS/StyleValues/IntegerStyleValue.h>
|
||||||
|
#include <LibWeb/CSS/StyleValues/NumberStyleValue.h>
|
||||||
|
|
||||||
namespace Web::CSS {
|
namespace Web::CSS {
|
||||||
|
|
||||||
// https://drafts.csswg.org/css-easing-1/#valdef-easing-function-linear
|
// https://drafts.csswg.org/css-easing-1/#valdef-easing-function-linear
|
||||||
EasingStyleValue::Linear EasingStyleValue::Linear::identity()
|
EasingStyleValue::Linear EasingStyleValue::Linear::identity()
|
||||||
{
|
{
|
||||||
static Linear linear { { { 0, {} }, { 1, {} } } };
|
static Linear linear { { { NumberStyleValue::create(0), {} }, { NumberStyleValue::create(1), {} } } };
|
||||||
return linear;
|
return linear;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -61,7 +62,7 @@ EasingStyleValue::Steps EasingStyleValue::Steps::step_end()
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://drafts.csswg.org/css-easing/#linear-easing-function-serializing
|
// https://drafts.csswg.org/css-easing/#linear-easing-function-serializing
|
||||||
String EasingStyleValue::Linear::to_string(SerializationMode) const
|
String EasingStyleValue::Linear::to_string(SerializationMode mode) const
|
||||||
{
|
{
|
||||||
// The linear keyword is serialized as itself.
|
// The linear keyword is serialized as itself.
|
||||||
if (*this == identity())
|
if (*this == identity())
|
||||||
|
|
@ -85,13 +86,13 @@ String EasingStyleValue::Linear::to_string(SerializationMode) const
|
||||||
|
|
||||||
// To serialize a linear() control point:
|
// To serialize a linear() control point:
|
||||||
// 1. Let s be the serialization, as a <number>, of the control point’s output progress value.
|
// 1. Let s be the serialization, as a <number>, of the control point’s output progress value.
|
||||||
builder.appendff("{}", stop.output);
|
builder.appendff(stop.output->to_string(mode));
|
||||||
|
|
||||||
// 2. If the control point originally lacked an input progress value, return s.
|
// 2. If the control point originally lacked an input progress value, return s.
|
||||||
// 3. Otherwise, append " " (U+0020 SPACE) to s,
|
// 3. Otherwise, append " " (U+0020 SPACE) to s,
|
||||||
// then serialize the control point’s input progress value as a <percentage> and append it to s.
|
// then serialize the control point’s input progress value as a <percentage> and append it to s.
|
||||||
if (stop.input.has_value()) {
|
if (stop.input) {
|
||||||
builder.appendff(" {}%", stop.input.value() * 100);
|
builder.appendff(" {}", stop.input->to_string(mode));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Return s.
|
// 4. Return s.
|
||||||
|
|
@ -169,4 +170,31 @@ String EasingStyleValue::Function::to_string(SerializationMode mode) const
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ValueComparingNonnullRefPtr<StyleValue const> EasingStyleValue::absolutized(ComputationContext const& computation_context) const
|
||||||
|
{
|
||||||
|
auto const& absolutized_function = m_function.visit(
|
||||||
|
[&](Linear const& linear) -> Function {
|
||||||
|
Vector<Linear::Stop> absolutized_stops;
|
||||||
|
|
||||||
|
for (auto stop : linear.stops) {
|
||||||
|
RefPtr<StyleValue const> absolutized_input;
|
||||||
|
|
||||||
|
if (stop.input)
|
||||||
|
absolutized_input = stop.input->absolutized(computation_context);
|
||||||
|
|
||||||
|
absolutized_stops.append({ stop.output->absolutized(computation_context), absolutized_input });
|
||||||
|
}
|
||||||
|
|
||||||
|
return Linear { absolutized_stops };
|
||||||
|
},
|
||||||
|
[&](CubicBezier const& cubic_bezier) -> Function {
|
||||||
|
return cubic_bezier;
|
||||||
|
},
|
||||||
|
[&](Steps const& steps) -> Function {
|
||||||
|
return steps;
|
||||||
|
});
|
||||||
|
|
||||||
|
return EasingStyleValue::create(absolutized_function);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,8 @@ public:
|
||||||
static Linear identity();
|
static Linear identity();
|
||||||
|
|
||||||
struct Stop {
|
struct Stop {
|
||||||
double output;
|
ValueComparingNonnullRefPtr<StyleValue const> output;
|
||||||
Optional<double> input;
|
ValueComparingRefPtr<StyleValue const> input;
|
||||||
|
|
||||||
bool operator==(Stop const&) const = default;
|
bool operator==(Stop const&) const = default;
|
||||||
};
|
};
|
||||||
|
|
@ -90,6 +90,8 @@ public:
|
||||||
|
|
||||||
virtual String to_string(SerializationMode mode) const override { return m_function.to_string(mode); }
|
virtual String to_string(SerializationMode mode) const override { return m_function.to_string(mode); }
|
||||||
|
|
||||||
|
virtual ValueComparingNonnullRefPtr<StyleValue const> absolutized(ComputationContext const&) const override;
|
||||||
|
|
||||||
bool properties_equal(EasingStyleValue const& other) const { return m_function == other.m_function; }
|
bool properties_equal(EasingStyleValue const& other) const { return m_function == other.m_function; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
||||||
|
|
@ -2,16 +2,16 @@ Harness status: OK
|
||||||
|
|
||||||
Found 35 tests
|
Found 35 tests
|
||||||
|
|
||||||
13 Pass
|
19 Pass
|
||||||
22 Fail
|
16 Fail
|
||||||
Pass e.style['animation-timing-function'] = "linear(0 0%, 1 100%)" should set the property value
|
Pass e.style['animation-timing-function'] = "linear(0 0%, 1 100%)" should set the property value
|
||||||
Pass e.style['animation-timing-function'] = "linear( 0 0%, 1 100% )" should set the property value
|
Pass e.style['animation-timing-function'] = "linear( 0 0%, 1 100% )" should set the property value
|
||||||
Fail e.style['animation-timing-function'] = "linear(0, 1)" should set the property value
|
Fail e.style['animation-timing-function'] = "linear(0, 1)" should set the property value
|
||||||
Pass e.style['animation-timing-function'] = "linear(-10, -5, 0, 5, 10)" should set the property value
|
Pass e.style['animation-timing-function'] = "linear(-10, -5, 0, 5, 10)" should set the property value
|
||||||
Pass e.style['animation-timing-function'] = "linear(-10 -10%, -5 -5%, 0, 5, 10)" should set the property value
|
Pass e.style['animation-timing-function'] = "linear(-10 -10%, -5 -5%, 0, 5, 10)" should set the property value
|
||||||
Fail e.style['animation-timing-function'] = "linear(0 calc(0%), 0 calc(100%))" should set the property value
|
Pass e.style['animation-timing-function'] = "linear(0 calc(0%), 0 calc(100%))" should set the property value
|
||||||
Fail e.style['animation-timing-function'] = "linear(0 calc(50% - 50%), 0 calc(50% + 50%))" should set the property value
|
Pass e.style['animation-timing-function'] = "linear(0 calc(50% - 50%), 0 calc(50% + 50%))" should set the property value
|
||||||
Fail e.style['animation-timing-function'] = "linear(0 calc(50%), 0 100%)" should set the property value
|
Pass e.style['animation-timing-function'] = "linear(0 calc(50%), 0 100%)" should set the property value
|
||||||
Fail e.style['animation-timing-function'] = "linear(0 0% 50%, 1 50% 100%)" should set the property value
|
Fail e.style['animation-timing-function'] = "linear(0 0% 50%, 1 50% 100%)" should set the property value
|
||||||
Fail e.style['animation-timing-function'] = "linear(0, 0.5 25% 75%, 1 100% 100%)" should set the property value
|
Fail e.style['animation-timing-function'] = "linear(0, 0.5 25% 75%, 1 100% 100%)" should set the property value
|
||||||
Fail e.style['animation-timing-function'] = "linear(0, 1.3, 1, 0.92, 1, 0.99, 1, 0.998, 1 100% 100%)" should set the property value
|
Fail e.style['animation-timing-function'] = "linear(0, 1.3, 1, 0.92, 1, 0.99, 1, 0.998, 1 100% 100%)" should set the property value
|
||||||
|
|
@ -31,9 +31,9 @@ Pass Property animation-timing-function value 'linear( 0 0%, 1 100% )'
|
||||||
Fail Property animation-timing-function value 'linear(0, 1)'
|
Fail Property animation-timing-function value 'linear(0, 1)'
|
||||||
Fail Property animation-timing-function value 'linear(-10, -5, 0, 5, 10)'
|
Fail Property animation-timing-function value 'linear(-10, -5, 0, 5, 10)'
|
||||||
Fail Property animation-timing-function value 'linear(-10 -10%, -5 -5%, 0, 5, 10)'
|
Fail Property animation-timing-function value 'linear(-10 -10%, -5 -5%, 0, 5, 10)'
|
||||||
Fail Property animation-timing-function value 'linear(0 calc(0%), 0 calc(100%))'
|
Pass Property animation-timing-function value 'linear(0 calc(0%), 0 calc(100%))'
|
||||||
Fail Property animation-timing-function value 'linear(0 calc(50% - 50%), 0 calc(50% + 50%))'
|
Pass Property animation-timing-function value 'linear(0 calc(50% - 50%), 0 calc(50% + 50%))'
|
||||||
Fail Property animation-timing-function value 'linear(0 calc(min(50%, 60%)), 0 100%)'
|
Pass Property animation-timing-function value 'linear(0 calc(min(50%, 60%)), 0 100%)'
|
||||||
Pass Property animation-timing-function value 'linear(0 0% 50%, 1 50% 100%)'
|
Pass Property animation-timing-function value 'linear(0 0% 50%, 1 50% 100%)'
|
||||||
Fail Property animation-timing-function value 'linear(0, 0.5 25% 75%, 1 100% 100%)'
|
Fail Property animation-timing-function value 'linear(0, 0.5 25% 75%, 1 100% 100%)'
|
||||||
Fail Property animation-timing-function value 'linear(0, 1.3, 1, 0.92, 1, 0.99, 1, 0.998, 1 100% 100%)'
|
Fail Property animation-timing-function value 'linear(0, 1.3, 1, 0.92, 1, 0.99, 1, 0.998, 1 100% 100%)'
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue