LibWeb: Allow calc() values in steps() easing functions

This commit is contained in:
Tim Ledbetter 2025-06-17 09:46:51 +01:00 committed by Jelle Raaijmakers
commit c5a3eaaf45
Notes: github-actions[bot] 2025-06-18 06:58:22 +00:00
8 changed files with 177 additions and 27 deletions

View file

@ -2651,11 +2651,10 @@ RefPtr<CSSStyleValue const> Parser::parse_easing_value(TokenStream<ComponentValu
EasingStyleValue::Steps steps;
auto const& intervals_argument = comma_separated_arguments[0][0];
if (!intervals_argument.is(Token::Type::Number))
auto intervals_token = TokenStream<ComponentValue>::of_single_token(intervals_argument);
auto intervals = parse_integer(intervals_token);
if (!intervals.has_value())
return nullptr;
if (!intervals_argument.token().number().is_integer())
return nullptr;
auto intervals = intervals_argument.token().to_integer();
if (comma_separated_arguments.size() == 2) {
TokenStream identifier_stream { comma_separated_arguments[1] };
@ -2690,14 +2689,16 @@ RefPtr<CSSStyleValue const> Parser::parse_easing_value(TokenStream<ComponentValu
// https://drafts.csswg.org/css-easing/#step-easing-functions
// If the <step-position> is jump-none, the <integer> must be at least 2, or the function is invalid.
// Otherwise, the <integer> must be at least 1, or the function is invalid.
if (steps.position == EasingStyleValue::Steps::Position::JumpNone) {
if (intervals <= 1)
if (!intervals->is_calculated()) {
if (steps.position == EasingStyleValue::Steps::Position::JumpNone) {
if (intervals->value() <= 1)
return nullptr;
} else if (intervals->value() <= 0) {
return nullptr;
} else if (intervals <= 0) {
return nullptr;
}
}
steps.number_of_intervals = intervals;
steps.number_of_intervals = *intervals;
transaction.commit();
return EasingStyleValue::create(steps);
}

View file

@ -11,6 +11,7 @@
#include "EasingStyleValue.h"
#include <AK/BinarySearch.h>
#include <AK/StringBuilder.h>
#include <LibWeb/CSS/StyleValues/IntegerStyleValue.h>
namespace Web::CSS {
@ -174,7 +175,7 @@ double EasingStyleValue::Linear::evaluate_at(double input_progress, bool before_
}
// https://drafts.csswg.org/css-easing/#linear-easing-function-serializing
String EasingStyleValue::Linear::to_string() const
String EasingStyleValue::Linear::to_string(SerializationMode) const
{
// The linear keyword is serialized as itself.
if (*this == identity())
@ -316,7 +317,7 @@ double EasingStyleValue::CubicBezier::evaluate_at(double input_progress, bool) c
}
// https://drafts.csswg.org/css-easing/#bezier-serialization
String EasingStyleValue::CubicBezier::to_string() const
String EasingStyleValue::CubicBezier::to_string(SerializationMode) const
{
StringBuilder builder;
if (*this == CubicBezier::ease()) {
@ -337,7 +338,10 @@ double EasingStyleValue::Steps::evaluate_at(double input_progress, bool before_f
{
// https://www.w3.org/TR/css-easing-1/#step-easing-algo
// 1. Calculate the current step as floor(input progress value × steps).
auto current_step = floor(input_progress * number_of_intervals);
auto resolved_number_of_intervals = number_of_intervals.resolved({}).value_or(1);
resolved_number_of_intervals = max(resolved_number_of_intervals, position == Steps::Position::JumpNone ? 2 : 1);
auto current_step = floor(input_progress * resolved_number_of_intervals);
// 2. If the step position property is one of:
// - jump-start,
@ -350,7 +354,7 @@ double EasingStyleValue::Steps::evaluate_at(double input_progress, bool before_f
// - the before flag is set, and
// - input progress value × steps mod 1 equals zero (that is, if input progress value × steps is integral), then
// decrement current step by one.
auto step_progress = input_progress * number_of_intervals;
auto step_progress = input_progress * resolved_number_of_intervals;
if (before_flag && trunc(step_progress) == step_progress)
current_step -= 1;
@ -363,7 +367,7 @@ double EasingStyleValue::Steps::evaluate_at(double input_progress, bool before_f
// jump-start or jump-end -> steps
// jump-none -> steps - 1
// jump-both -> steps + 1
auto jumps = number_of_intervals;
auto jumps = resolved_number_of_intervals;
if (position == Steps::Position::JumpNone) {
jumps--;
} else if (position == Steps::Position::JumpBoth) {
@ -379,7 +383,7 @@ double EasingStyleValue::Steps::evaluate_at(double input_progress, bool before_f
}
// https://drafts.csswg.org/css-easing/#steps-serialization
String EasingStyleValue::Steps::to_string() const
String EasingStyleValue::Steps::to_string(SerializationMode mode) const
{
StringBuilder builder;
// Unlike the other easing function keywords, step-start and step-end do not serialize as themselves.
@ -403,10 +407,15 @@ String EasingStyleValue::Steps::to_string() const
return {};
}
}();
auto intervals = number_of_intervals;
if (mode == SerializationMode::ResolvedValue) {
auto resolved_value = number_of_intervals.resolved({}).value_or(1);
intervals = max(resolved_value, this->position == Steps::Position::JumpNone ? 2 : 1);
}
if (position.has_value()) {
builder.appendff("steps({}, {})", number_of_intervals, position.value());
builder.appendff("steps({}, {})", intervals.to_string(), position.value());
} else {
builder.appendff("steps({})", number_of_intervals);
builder.appendff("steps({})", intervals.to_string());
}
}
return MUST(builder.to_string());
@ -420,11 +429,11 @@ double EasingStyleValue::Function::evaluate_at(double input_progress, bool befor
});
}
String EasingStyleValue::Function::to_string() const
String EasingStyleValue::Function::to_string(SerializationMode mode) const
{
return visit(
[&](auto const& curve) {
return curve.to_string();
return curve.to_string(mode);
});
}

View file

@ -11,6 +11,7 @@
#pragma once
#include <LibWeb/CSS/CSSStyleValue.h>
#include <LibWeb/CSS/CalculatedOr.h>
namespace Web::CSS {
@ -35,7 +36,7 @@ public:
bool operator==(Linear const&) const = default;
double evaluate_at(double input_progress, bool before_flag) const;
String to_string() const;
String to_string(SerializationMode) const;
Linear(Vector<Stop> stops);
};
@ -62,7 +63,7 @@ public:
bool operator==(CubicBezier const&) const;
double evaluate_at(double input_progress, bool before_flag) const;
String to_string() const;
String to_string(SerializationMode) const;
};
struct Steps {
@ -78,20 +79,20 @@ public:
static Steps step_start();
static Steps step_end();
unsigned int number_of_intervals;
IntegerOrCalculated number_of_intervals { 0 };
Position position { Position::End };
bool operator==(Steps const&) const = default;
double evaluate_at(double input_progress, bool before_flag) const;
String to_string() const;
String to_string(SerializationMode) const;
};
struct Function : public Variant<Linear, CubicBezier, Steps> {
using Variant::Variant;
double evaluate_at(double input_progress, bool before_flag) const;
String to_string() const;
String to_string(SerializationMode) const;
};
static ValueComparingNonnullRefPtr<EasingStyleValue const> create(Function const& function)
@ -102,7 +103,7 @@ public:
Function const& function() const { return m_function; }
virtual String to_string(SerializationMode) const override { return m_function.to_string(); }
virtual String to_string(SerializationMode mode) const override { return m_function.to_string(mode); }
bool properties_equal(EasingStyleValue const& other) const { return m_function == other.m_function; }