blob: 4e958c4de06a017194f8636fe8a14f655dcfc132 [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/core/animation/interpolable_length.h"
#include "third_party/blink/renderer/core/animation/underlying_value.h"
#include "third_party/blink/renderer/core/css/css_math_expression_node.h"
#include "third_party/blink/renderer/core/css/css_math_function_value.h"
#include "third_party/blink/renderer/core/css/css_numeric_literal_value.h"
#include "third_party/blink/renderer/core/css/css_to_length_conversion_data.h"
#include "third_party/blink/renderer/platform/geometry/blend.h"
#include "third_party/blink/renderer/platform/geometry/calculation_value.h"
namespace blink {
using UnitType = CSSPrimitiveValue::UnitType;
namespace {
CSSMathExpressionNode* NumberNode(double number) {
return CSSMathExpressionNumericLiteral::Create(
CSSNumericLiteralValue::Create(number, UnitType::kNumber));
}
CSSMathExpressionNode* PercentageNode(double number) {
return CSSMathExpressionNumericLiteral::Create(
CSSNumericLiteralValue::Create(number, UnitType::kPercentage));
}
} // namespace
// static
std::unique_ptr<InterpolableLength> InterpolableLength::CreatePixels(
double pixels) {
CSSLengthArray length_array;
length_array.values[CSSPrimitiveValue::kUnitTypePixels] = pixels;
length_array.type_flags.set(CSSPrimitiveValue::kUnitTypePixels);
return std::make_unique<InterpolableLength>(std::move(length_array));
}
// static
std::unique_ptr<InterpolableLength> InterpolableLength::CreatePercent(
double percent) {
CSSLengthArray length_array;
length_array.values[CSSPrimitiveValue::kUnitTypePercentage] = percent;
length_array.type_flags.set(CSSPrimitiveValue::kUnitTypePercentage);
return std::make_unique<InterpolableLength>(std::move(length_array));
}
// static
std::unique_ptr<InterpolableLength> InterpolableLength::CreateNeutral() {
return std::make_unique<InterpolableLength>(CSSLengthArray());
}
// static
std::unique_ptr<InterpolableLength> InterpolableLength::MaybeConvertCSSValue(
const CSSValue& value) {
const auto* primitive_value = DynamicTo<CSSPrimitiveValue>(value);
if (!primitive_value)
return nullptr;
if (!primitive_value->IsLength() && !primitive_value->IsPercentage() &&
!primitive_value->IsCalculatedPercentageWithLength())
return nullptr;
CSSLengthArray length_array;
if (primitive_value->AccumulateLengthArray(length_array))
return std::make_unique<InterpolableLength>(std::move(length_array));
DCHECK(primitive_value->IsMathFunctionValue());
return std::make_unique<InterpolableLength>(
*To<CSSMathFunctionValue>(primitive_value)->ExpressionNode());
}
// static
std::unique_ptr<InterpolableLength> InterpolableLength::MaybeConvertLength(
const Length& length,
float zoom) {
if (!length.IsSpecified())
return nullptr;
if (length.IsCalculated() && length.GetCalculationValue().IsExpression()) {
auto unzoomed_calc = length.GetCalculationValue().Zoom(1.0 / zoom);
return std::make_unique<InterpolableLength>(
*CSSMathExpressionNode::Create(*unzoomed_calc));
}
PixelsAndPercent pixels_and_percent = length.GetPixelsAndPercent();
CSSLengthArray length_array;
length_array.values[CSSPrimitiveValue::kUnitTypePixels] =
pixels_and_percent.pixels / zoom;
length_array.type_flags[CSSPrimitiveValue::kUnitTypePixels] =
pixels_and_percent.pixels != 0;
length_array.values[CSSPrimitiveValue::kUnitTypePercentage] =
pixels_and_percent.percent;
length_array.type_flags[CSSPrimitiveValue::kUnitTypePercentage] =
length.IsPercentOrCalc();
return std::make_unique<InterpolableLength>(std::move(length_array));
}
// static
PairwiseInterpolationValue InterpolableLength::MergeSingles(
std::unique_ptr<InterpolableValue> start,
std::unique_ptr<InterpolableValue> end) {
// TODO(crbug.com/991672): We currently have a lot of "fast paths" that do not
// go through here, and hence, do not merge the percentage info of two
// lengths. We should stop doing that.
auto& start_length = To<InterpolableLength>(*start);
auto& end_length = To<InterpolableLength>(*end);
if (start_length.HasPercentage() || end_length.HasPercentage()) {
start_length.SetHasPercentage();
end_length.SetHasPercentage();
}
if (start_length.IsExpression() || end_length.IsExpression()) {
start_length.SetExpression(start_length.AsExpression());
end_length.SetExpression(end_length.AsExpression());
}
return PairwiseInterpolationValue(std::move(start), std::move(end));
}
InterpolableLength::InterpolableLength(CSSLengthArray&& length_array) {
SetLengthArray(std::move(length_array));
}
void InterpolableLength::SetLengthArray(CSSLengthArray&& length_array) {
type_ = Type::kLengthArray;
length_array_ = std::move(length_array);
expression_.Clear();
}
InterpolableLength::InterpolableLength(
const CSSMathExpressionNode& expression) {
SetExpression(expression);
}
void InterpolableLength::SetExpression(
const CSSMathExpressionNode& expression) {
type_ = Type::kExpression;
expression_ = &expression;
}
InterpolableLength* InterpolableLength::RawClone() const {
return new InterpolableLength(*this);
}
bool InterpolableLength::HasPercentage() const {
if (IsLengthArray()) {
return length_array_.type_flags.test(
CSSPrimitiveValue::kUnitTypePercentage);
}
return expression_->HasPercentage();
}
void InterpolableLength::SetHasPercentage() {
if (HasPercentage())
return;
if (IsLengthArray()) {
length_array_.type_flags.set(CSSPrimitiveValue::kUnitTypePercentage);
return;
}
DEFINE_STATIC_LOCAL(Persistent<CSSMathExpressionNode>, zero_percent,
{PercentageNode(0)});
SetExpression(*CSSMathExpressionBinaryOperation::Create(
expression_, zero_percent, CSSMathOperator::kAdd));
}
void InterpolableLength::SubtractFromOneHundredPercent() {
if (IsLengthArray()) {
for (double& value : length_array_.values)
value *= -1;
length_array_.values[CSSPrimitiveValue::kUnitTypePercentage] += 100;
length_array_.type_flags.set(CSSPrimitiveValue::kUnitTypePercentage);
return;
}
DEFINE_STATIC_LOCAL(Persistent<CSSMathExpressionNode>, hundred_percent,
{PercentageNode(100)});
SetExpression(*CSSMathExpressionBinaryOperation::CreateSimplified(
hundred_percent, expression_, CSSMathOperator::kSubtract));
}
static double ClampToRange(double x, ValueRange range) {
return (range == kValueRangeNonNegative && x < 0) ? 0 : x;
}
static const CSSNumericLiteralValue& ClampNumericLiteralValueToRange(
const CSSNumericLiteralValue& value,
ValueRange range) {
if (range == kValueRangeAll || value.DoubleValue() >= 0)
return value;
return *CSSNumericLiteralValue::Create(0, value.GetType());
}
static UnitType IndexToUnitType(wtf_size_t index) {
return CSSPrimitiveValue::LengthUnitTypeToUnitType(
static_cast<CSSPrimitiveValue::LengthUnitType>(index));
}
Length InterpolableLength::CreateLength(
const CSSToLengthConversionData& conversion_data,
ValueRange range) const {
// Passing true for ToCalcValue is a dirty hack to ensure that we don't create
// a degenerate value when animating 'background-position', while we know it
// may cause some minor animation glitches for the other properties.
if (IsExpression())
return Length(expression_->ToCalcValue(conversion_data, range, true));
bool has_percentage = HasPercentage();
double pixels = 0;
double percentage = 0;
for (wtf_size_t i = 0; i < length_array_.values.size(); ++i) {
double value = length_array_.values[i];
if (value == 0)
continue;
if (i == CSSPrimitiveValue::kUnitTypePercentage) {
percentage = value;
} else {
pixels += conversion_data.ZoomedComputedPixels(value, IndexToUnitType(i));
}
}
if (percentage != 0)
has_percentage = true;
if (pixels != 0 && has_percentage) {
return Length(CalculationValue::Create(
PixelsAndPercent(clampTo<float>(pixels), clampTo<float>(percentage)),
range));
}
if (has_percentage)
return Length::Percent(ClampToRange(percentage, range));
return Length::Fixed(
CSSPrimitiveValue::ClampToCSSLengthRange(ClampToRange(pixels, range)));
}
const CSSPrimitiveValue* InterpolableLength::CreateCSSValue(
ValueRange range) const {
if (IsExpression())
return CSSMathFunctionValue::Create(expression_, range);
DCHECK(IsLengthArray());
if (length_array_.type_flags.count() > 1u) {
const CSSMathExpressionNode& expression = AsExpression();
if (!expression.IsNumericLiteral())
return CSSMathFunctionValue::Create(&AsExpression(), range);
// This creates a temporary CSSMathExpressionNode. Eliminate it if this
// results in significant performance regression.
return &ClampNumericLiteralValueToRange(
To<CSSMathExpressionNumericLiteral>(expression).GetValue(), range);
}
for (wtf_size_t i = 0; i < length_array_.values.size(); ++i) {
if (length_array_.type_flags.test(i)) {
double value = ClampToRange(length_array_.values[i], range);
UnitType unit_type = IndexToUnitType(i);
return CSSNumericLiteralValue::Create(value, unit_type);
}
}
return CSSNumericLiteralValue::Create(0, UnitType::kPixels);
}
const CSSMathExpressionNode& InterpolableLength::AsExpression() const {
if (IsExpression())
return *expression_;
DCHECK(IsLengthArray());
bool has_percentage = HasPercentage();
CSSMathExpressionNode* root_node = nullptr;
for (wtf_size_t i = 0; i < length_array_.values.size(); ++i) {
double value = length_array_.values[i];
if (value == 0 &&
(i != CSSPrimitiveValue::kUnitTypePercentage || !has_percentage)) {
continue;
}
CSSNumericLiteralValue* current_value =
CSSNumericLiteralValue::Create(value, IndexToUnitType(i));
CSSMathExpressionNode* current_node =
CSSMathExpressionNumericLiteral::Create(current_value);
if (!root_node) {
root_node = current_node;
} else {
root_node = CSSMathExpressionBinaryOperation::Create(
root_node, current_node, CSSMathOperator::kAdd);
}
}
if (root_node)
return *root_node;
return *CSSMathExpressionNumericLiteral::Create(
CSSNumericLiteralValue::Create(0, UnitType::kPixels));
}
void InterpolableLength::Scale(double scale) {
if (IsLengthArray()) {
for (auto& value : length_array_.values)
value *= scale;
return;
}
DCHECK(IsExpression());
SetExpression(*CSSMathExpressionBinaryOperation::CreateSimplified(
expression_, NumberNode(scale), CSSMathOperator::kMultiply));
}
void InterpolableLength::Add(const InterpolableValue& other) {
const InterpolableLength& other_length = To<InterpolableLength>(other);
if (IsLengthArray() && other_length.IsLengthArray()) {
for (wtf_size_t i = 0; i < length_array_.values.size(); ++i) {
length_array_.values[i] =
length_array_.values[i] + other_length.length_array_.values[i];
}
length_array_.type_flags |= other_length.length_array_.type_flags;
return;
}
CSSMathExpressionNode* result =
CSSMathExpressionBinaryOperation::CreateSimplified(
&AsExpression(), &other_length.AsExpression(), CSSMathOperator::kAdd);
SetExpression(*result);
}
void InterpolableLength::ScaleAndAdd(double scale,
const InterpolableValue& other) {
const InterpolableLength& other_length = To<InterpolableLength>(other);
if (IsLengthArray() && other_length.IsLengthArray()) {
for (wtf_size_t i = 0; i < length_array_.values.size(); ++i) {
length_array_.values[i] = length_array_.values[i] * scale +
other_length.length_array_.values[i];
}
length_array_.type_flags |= other_length.length_array_.type_flags;
return;
}
CSSMathExpressionNode* scaled =
CSSMathExpressionBinaryOperation::CreateSimplified(
&AsExpression(), NumberNode(scale), CSSMathOperator::kMultiply);
CSSMathExpressionNode* result =
CSSMathExpressionBinaryOperation::CreateSimplified(
scaled, &other_length.AsExpression(), CSSMathOperator::kAdd);
SetExpression(*result);
}
void InterpolableLength::AssertCanInterpolateWith(
const InterpolableValue& other) const {
DCHECK(other.IsLength());
// TODO(crbug.com/991672): Ensure that all |MergeSingles| variants that merge
// two |InterpolableLength| objects should also assign them the same shape
// (i.e. type flags) after merging into a |PairwiseInterpolationValue|. We
// currently fail to do that, and hit the following DCHECK:
// DCHECK_EQ(HasPercentage(),
// To<InterpolableLength>(other).HasPercentage());
}
void InterpolableLength::Interpolate(const InterpolableValue& to,
const double progress,
InterpolableValue& result) const {
const auto& to_length = To<InterpolableLength>(to);
auto& result_length = To<InterpolableLength>(result);
if (IsLengthArray() && to_length.IsLengthArray()) {
if (!result_length.IsLengthArray())
result_length.SetLengthArray(CSSLengthArray());
const CSSLengthArray& to_length_array = to_length.length_array_;
CSSLengthArray& result_length_array =
To<InterpolableLength>(result).length_array_;
for (wtf_size_t i = 0; i < length_array_.values.size(); ++i) {
result_length_array.values[i] =
Blend(length_array_.values[i], to_length_array.values[i], progress);
}
result_length_array.type_flags =
length_array_.type_flags | to_length_array.type_flags;
return;
}
CSSMathExpressionNode* blended_from =
CSSMathExpressionBinaryOperation::CreateSimplified(
&AsExpression(), NumberNode(1 - progress),
CSSMathOperator::kMultiply);
CSSMathExpressionNode* blended_to =
CSSMathExpressionBinaryOperation::CreateSimplified(
&to_length.AsExpression(), NumberNode(progress),
CSSMathOperator::kMultiply);
CSSMathExpressionNode* result_expression =
CSSMathExpressionBinaryOperation::CreateSimplified(
blended_from, blended_to, CSSMathOperator::kAdd);
result_length.SetExpression(*result_expression);
DCHECK_EQ(result_length.HasPercentage(),
HasPercentage() || to_length.HasPercentage());
}
} // namespace blink