2023-07-06 02:29:36 +03:30
|
|
|
|
/*
|
2024-10-04 13:19:50 +02:00
|
|
|
|
* Copyright (c) 2018-2020, Andreas Kling <andreas@ladybird.org>
|
2023-07-06 02:29:36 +03:30
|
|
|
|
* Copyright (c) 2021, Tobias Christiansen <tobyase@serenityos.org>
|
|
|
|
|
|
* Copyright (c) 2021-2023, Sam Atkins <atkinssj@serenityos.org>
|
|
|
|
|
|
* Copyright (c) 2022-2023, MacDue <macdue@dueutil.tech>
|
|
|
|
|
|
* Copyright (c) 2023, Ali Mohammad Pur <mpfard@serenityos.org>
|
|
|
|
|
|
*
|
|
|
|
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
#include "EasingStyleValue.h"
|
2024-06-14 21:50:25 -07:00
|
|
|
|
#include <AK/BinarySearch.h>
|
2023-07-06 02:29:36 +03:30
|
|
|
|
#include <AK/StringBuilder.h>
|
2025-06-17 09:46:51 +01:00
|
|
|
|
#include <LibWeb/CSS/StyleValues/IntegerStyleValue.h>
|
2023-07-06 02:29:36 +03:30
|
|
|
|
|
|
|
|
|
|
namespace Web::CSS {
|
|
|
|
|
|
|
2024-11-03 14:56:16 +11:00
|
|
|
|
// https://drafts.csswg.org/css-easing-1/#valdef-easing-function-linear
|
|
|
|
|
|
EasingStyleValue::Linear EasingStyleValue::Linear::identity()
|
|
|
|
|
|
{
|
|
|
|
|
|
static Linear linear { { { 0, {}, false }, { 1, {}, false } } };
|
|
|
|
|
|
return linear;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-06-14 21:27:23 -07:00
|
|
|
|
// NOTE: Magic cubic bezier values from https://www.w3.org/TR/css-easing-1/#valdef-cubic-bezier-easing-function-ease
|
|
|
|
|
|
|
|
|
|
|
|
EasingStyleValue::CubicBezier EasingStyleValue::CubicBezier::ease()
|
2023-07-06 02:29:36 +03:30
|
|
|
|
{
|
2024-06-14 21:27:23 -07:00
|
|
|
|
static CubicBezier bezier { 0.25, 0.1, 0.25, 1.0 };
|
|
|
|
|
|
return bezier;
|
|
|
|
|
|
}
|
2023-07-06 02:29:36 +03:30
|
|
|
|
|
2024-06-14 21:27:23 -07:00
|
|
|
|
EasingStyleValue::CubicBezier EasingStyleValue::CubicBezier::ease_in()
|
|
|
|
|
|
{
|
|
|
|
|
|
static CubicBezier bezier { 0.42, 0.0, 1.0, 1.0 };
|
|
|
|
|
|
return bezier;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
EasingStyleValue::CubicBezier EasingStyleValue::CubicBezier::ease_out()
|
|
|
|
|
|
{
|
|
|
|
|
|
static CubicBezier bezier { 0.0, 0.0, 0.58, 1.0 };
|
|
|
|
|
|
return bezier;
|
|
|
|
|
|
}
|
2023-07-06 02:29:36 +03:30
|
|
|
|
|
2024-06-14 21:27:23 -07:00
|
|
|
|
EasingStyleValue::CubicBezier EasingStyleValue::CubicBezier::ease_in_out()
|
|
|
|
|
|
{
|
|
|
|
|
|
static CubicBezier bezier { 0.42, 0.0, 0.58, 1.0 };
|
|
|
|
|
|
return bezier;
|
|
|
|
|
|
}
|
2023-07-06 02:29:36 +03:30
|
|
|
|
|
2024-06-16 12:47:35 -04:00
|
|
|
|
EasingStyleValue::Steps EasingStyleValue::Steps::step_start()
|
|
|
|
|
|
{
|
2025-10-10 19:42:28 +13:00
|
|
|
|
static Steps steps { 1, StepPosition::Start };
|
2024-06-14 21:27:23 -07:00
|
|
|
|
return steps;
|
|
|
|
|
|
}
|
2023-07-06 02:29:36 +03:30
|
|
|
|
|
2024-06-16 12:47:35 -04:00
|
|
|
|
EasingStyleValue::Steps EasingStyleValue::Steps::step_end()
|
|
|
|
|
|
{
|
2025-10-10 19:42:28 +13:00
|
|
|
|
static Steps steps { 1, StepPosition::End };
|
2024-06-14 21:27:23 -07:00
|
|
|
|
return steps;
|
2023-07-06 02:29:36 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-03 14:56:16 +11:00
|
|
|
|
// https://drafts.csswg.org/css-easing/#linear-canonicalization
|
|
|
|
|
|
EasingStyleValue::Linear::Linear(Vector<EasingStyleValue::Linear::Stop> stops)
|
2024-11-03 10:36:44 +11:00
|
|
|
|
{
|
2024-11-03 14:56:16 +11:00
|
|
|
|
// To canonicalize a linear() function’s control points, perform the following:
|
|
|
|
|
|
|
|
|
|
|
|
// 1. If the first control point lacks an input progress value, set its input progress value to 0.
|
|
|
|
|
|
if (!stops.first().input.has_value())
|
|
|
|
|
|
stops.first().input = 0;
|
|
|
|
|
|
|
|
|
|
|
|
// 2. If the last control point lacks an input progress value, set its input progress value to 1.
|
|
|
|
|
|
if (!stops.last().input.has_value())
|
|
|
|
|
|
stops.last().input = 1;
|
|
|
|
|
|
|
|
|
|
|
|
// 3. If any control point has an input progress value that is less than
|
|
|
|
|
|
// the input progress value of any preceding control point,
|
|
|
|
|
|
// set its input progress value to the largest input progress value of any preceding control point.
|
|
|
|
|
|
double largest_input = 0;
|
2025-06-18 01:08:35 +03:00
|
|
|
|
for (auto& stop : stops) {
|
2024-11-03 14:56:16 +11:00
|
|
|
|
if (stop.input.has_value()) {
|
|
|
|
|
|
if (stop.input.value() < largest_input) {
|
|
|
|
|
|
stop.input = largest_input;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
largest_input = stop.input.value();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 4. If any control point still lacks an input progress value,
|
|
|
|
|
|
// then for each contiguous run of such control points,
|
|
|
|
|
|
// set their input progress values so that they are evenly spaced
|
|
|
|
|
|
// between the preceding and following control points with input progress values.
|
|
|
|
|
|
Optional<size_t> run_start_idx;
|
|
|
|
|
|
for (size_t idx = 0; idx < stops.size(); idx++) {
|
2025-06-18 01:08:35 +03:00
|
|
|
|
auto& stop = stops[idx];
|
2024-11-03 14:56:16 +11:00
|
|
|
|
if (stop.input.has_value() && run_start_idx.has_value()) {
|
|
|
|
|
|
// Note: this stop is immediately after a run
|
|
|
|
|
|
// set inputs of [start, idx-1] stops to be evenly spaced between start-1 and idx
|
|
|
|
|
|
auto start_input = stops[run_start_idx.value() - 1].input.value();
|
|
|
|
|
|
auto end_input = stops[idx].input.value();
|
|
|
|
|
|
auto run_stop_count = idx - run_start_idx.value() + 1;
|
|
|
|
|
|
auto delta = (end_input - start_input) / run_stop_count;
|
|
|
|
|
|
for (size_t run_idx = 0; run_idx < run_stop_count; run_idx++) {
|
|
|
|
|
|
stops[run_idx + run_start_idx.value() - 1].input = start_input + delta * run_idx;
|
|
|
|
|
|
}
|
|
|
|
|
|
run_start_idx = {};
|
|
|
|
|
|
} else if (!stop.input.has_value() && !run_start_idx.has_value()) {
|
|
|
|
|
|
// Note: this stop is the start of a run
|
|
|
|
|
|
run_start_idx = idx;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this->stops = move(stops);
|
2024-11-03 10:36:44 +11:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-03 14:56:16 +11:00
|
|
|
|
// https://drafts.csswg.org/css-easing/#linear-easing-function-serializing
|
2025-06-17 09:46:51 +01:00
|
|
|
|
String EasingStyleValue::Linear::to_string(SerializationMode) const
|
2024-11-03 10:36:44 +11:00
|
|
|
|
{
|
2024-11-03 14:56:16 +11:00
|
|
|
|
// The linear keyword is serialized as itself.
|
|
|
|
|
|
if (*this == identity())
|
|
|
|
|
|
return "linear"_string;
|
|
|
|
|
|
|
|
|
|
|
|
// To serialize a linear() function:
|
|
|
|
|
|
// 1. Let s be the string "linear(".
|
2024-11-03 10:36:44 +11:00
|
|
|
|
StringBuilder builder;
|
2024-11-03 14:56:16 +11:00
|
|
|
|
builder.append("linear("sv);
|
|
|
|
|
|
|
|
|
|
|
|
// 2. Serialize each control point of the function,
|
|
|
|
|
|
// concatenate the results using the separator ", ",
|
|
|
|
|
|
// and append the result to s.
|
|
|
|
|
|
bool first = true;
|
|
|
|
|
|
for (auto stop : stops) {
|
|
|
|
|
|
if (first) {
|
2024-11-03 10:36:44 +11:00
|
|
|
|
first = false;
|
2024-11-03 14:56:16 +11:00
|
|
|
|
} else {
|
|
|
|
|
|
builder.append(", "sv);
|
2024-11-03 10:36:44 +11:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-03 14:56:16 +11:00
|
|
|
|
// To serialize a linear() control point:
|
|
|
|
|
|
// 1. Let s be the serialization, as a <number>, of the control point’s output progress value.
|
|
|
|
|
|
builder.appendff("{}", stop.output);
|
|
|
|
|
|
|
|
|
|
|
|
// 2. If the control point originally lacked an input progress value, return 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.
|
|
|
|
|
|
if (stop.had_explicit_input) {
|
|
|
|
|
|
builder.appendff(" {}%", stop.input.value() * 100);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 4. Return s.
|
2024-11-03 10:36:44 +11:00
|
|
|
|
}
|
2024-11-03 14:56:16 +11:00
|
|
|
|
|
|
|
|
|
|
// 4. Append ")" to s, and return it.
|
|
|
|
|
|
builder.append(')');
|
2024-11-03 10:36:44 +11:00
|
|
|
|
return MUST(builder.to_string());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-18 19:41:33 +04:00
|
|
|
|
// https://drafts.csswg.org/css-easing/#bezier-serialization
|
2025-06-17 22:51:54 +01:00
|
|
|
|
String EasingStyleValue::CubicBezier::to_string(SerializationMode mode) const
|
2024-11-03 10:36:44 +11:00
|
|
|
|
{
|
|
|
|
|
|
StringBuilder builder;
|
|
|
|
|
|
if (*this == CubicBezier::ease()) {
|
|
|
|
|
|
builder.append("ease"sv);
|
|
|
|
|
|
} else if (*this == CubicBezier::ease_in()) {
|
|
|
|
|
|
builder.append("ease-in"sv);
|
|
|
|
|
|
} else if (*this == CubicBezier::ease_out()) {
|
|
|
|
|
|
builder.append("ease-out"sv);
|
|
|
|
|
|
} else if (*this == CubicBezier::ease_in_out()) {
|
|
|
|
|
|
builder.append("ease-in-out"sv);
|
|
|
|
|
|
} else {
|
2025-06-17 22:51:54 +01:00
|
|
|
|
auto x1_value = x1;
|
|
|
|
|
|
auto y1_value = y1;
|
|
|
|
|
|
auto x2_value = x2;
|
|
|
|
|
|
auto y2_value = y2;
|
|
|
|
|
|
if (mode == SerializationMode::ResolvedValue) {
|
|
|
|
|
|
x1_value = clamp(x1_value.resolved({}).value_or(0.0), 0.0, 1.0);
|
|
|
|
|
|
x2_value = clamp(x2_value.resolved({}).value_or(0.0), 0.0, 1.0);
|
|
|
|
|
|
y1_value = y1_value.resolved({}).value_or(0.0);
|
|
|
|
|
|
y2_value = y2_value.resolved({}).value_or(0.0);
|
|
|
|
|
|
}
|
|
|
|
|
|
builder.appendff("cubic-bezier({}, {}, {}, {})",
|
2025-08-04 22:04:18 +12:00
|
|
|
|
x1_value.to_string(mode), y1_value.to_string(mode), x2_value.to_string(mode), y2_value.to_string(mode));
|
2024-11-03 10:36:44 +11:00
|
|
|
|
}
|
|
|
|
|
|
return MUST(builder.to_string());
|
|
|
|
|
|
}
|
2024-06-14 21:50:25 -07:00
|
|
|
|
|
2024-11-18 19:41:33 +04:00
|
|
|
|
// https://drafts.csswg.org/css-easing/#steps-serialization
|
2025-06-17 09:46:51 +01:00
|
|
|
|
String EasingStyleValue::Steps::to_string(SerializationMode mode) const
|
2024-11-03 10:36:44 +11:00
|
|
|
|
{
|
|
|
|
|
|
StringBuilder builder;
|
2024-11-18 19:41:33 +04:00
|
|
|
|
// Unlike the other easing function keywords, step-start and step-end do not serialize as themselves.
|
|
|
|
|
|
// Instead, they serialize as "steps(1, start)" and "steps(1)", respectively.
|
2024-11-03 10:36:44 +11:00
|
|
|
|
if (*this == Steps::step_start()) {
|
2024-11-18 19:41:33 +04:00
|
|
|
|
builder.append("steps(1, start)"sv);
|
2024-11-03 10:36:44 +11:00
|
|
|
|
} else if (*this == Steps::step_end()) {
|
2024-11-18 19:41:33 +04:00
|
|
|
|
builder.append("steps(1)"sv);
|
2024-11-03 10:36:44 +11:00
|
|
|
|
} else {
|
|
|
|
|
|
auto position = [&] -> Optional<StringView> {
|
2025-10-10 19:42:28 +13:00
|
|
|
|
if (first_is_one_of(this->position, StepPosition::JumpEnd, StepPosition::End))
|
2024-11-03 10:36:44 +11:00
|
|
|
|
return {};
|
2025-10-10 19:42:28 +13:00
|
|
|
|
return CSS::to_string(this->position);
|
2024-11-03 10:36:44 +11:00
|
|
|
|
}();
|
2025-06-17 09:46:51 +01:00
|
|
|
|
auto intervals = number_of_intervals;
|
|
|
|
|
|
if (mode == SerializationMode::ResolvedValue) {
|
|
|
|
|
|
auto resolved_value = number_of_intervals.resolved({}).value_or(1);
|
2025-10-10 19:42:28 +13:00
|
|
|
|
intervals = max(resolved_value, this->position == StepPosition::JumpNone ? 2 : 1);
|
2025-06-17 09:46:51 +01:00
|
|
|
|
}
|
2024-11-03 10:36:44 +11:00
|
|
|
|
if (position.has_value()) {
|
2025-08-04 22:04:18 +12:00
|
|
|
|
builder.appendff("steps({}, {})", intervals.to_string(mode), position.value());
|
2024-11-03 10:36:44 +11:00
|
|
|
|
} else {
|
2025-08-04 22:04:18 +12:00
|
|
|
|
builder.appendff("steps({})", intervals.to_string(mode));
|
2024-11-03 10:36:44 +11:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return MUST(builder.to_string());
|
|
|
|
|
|
}
|
2024-06-14 21:50:25 -07:00
|
|
|
|
|
2025-06-17 09:46:51 +01:00
|
|
|
|
String EasingStyleValue::Function::to_string(SerializationMode mode) const
|
2023-07-06 02:29:36 +03:30
|
|
|
|
{
|
2024-11-03 10:36:44 +11:00
|
|
|
|
return visit(
|
|
|
|
|
|
[&](auto const& curve) {
|
2025-06-17 09:46:51 +01:00
|
|
|
|
return curve.to_string(mode);
|
2024-06-14 21:27:23 -07:00
|
|
|
|
});
|
2023-07-06 02:29:36 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|