blob: 006a04090d05a16a4d828ac8a1cb0c7a18800ed6 [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) {
return std::make_unique<InterpolableLength>(pixels, UnitType::kPixels);
}
// static
std::unique_ptr<InterpolableLength> InterpolableLength::CreatePercent(
double percent) {
return std::make_unique<InterpolableLength>(percent, UnitType::kPercentage);
}
// static
std::unique_ptr<InterpolableLength> InterpolableLength::CreateNeutral() {
return std::make_unique<InterpolableLength>();
}
// 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;
if (const auto* numeric_literal =
DynamicTo<CSSNumericLiteralValue>(primitive_value)) {
return std::make_unique<InterpolableLength>(*numeric_literal);
}
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;
// Do not use CSSPrimitiveValue::CreateFromLength(), as it might drop 0% in
// calculated lengths.
// TODO(crbug.com/991672): Try not to drop 0% there.
if (length.IsFixed())
return CreatePixels(length.Pixels() / zoom);
if (length.IsPercent())
return CreatePercent(length.Percent());
return std::make_unique<InterpolableLength>(
*CSSMathExpressionNode::Create(length.GetCalculationValue()));
}
// 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 type flags 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();
}
return PairwiseInterpolationValue(std::move(start), std::move(end));
}
InterpolableLength::InterpolableLength(double value, UnitType unit_type) {
SetNumericLiteral(value, unit_type);
}
InterpolableLength::InterpolableLength()
: InterpolableLength(0, UnitType::kPixels) {}
InterpolableLength::InterpolableLength(const CSSNumericLiteralValue& value)
: InterpolableLength(value.DoubleValue(), value.GetType()) {}
InterpolableLength::InterpolableLength(
const CSSMathExpressionNode& expression) {
SetExpression(expression);
}
std::unique_ptr<InterpolableValue> InterpolableLength::Clone() const {
return std::make_unique<InterpolableLength>(*this);
}
void InterpolableLength::SetNumericLiteral(
double value,
CSSPrimitiveValue::UnitType unit_type) {
type_ = Type::kNumericLiteral;
single_value_ = value;
unit_type_ = unit_type;
expression_.Clear();
}
void InterpolableLength::SetNumericLiteral(
const CSSNumericLiteralValue& value) {
SetNumericLiteral(value.DoubleValue(), value.GetType());
}
void InterpolableLength::SetExpression(
const CSSMathExpressionNode& expression) {
if (expression.IsNumericLiteral()) {
return SetNumericLiteral(
*To<CSSMathExpressionNumericLiteral>(expression).GetValue());
}
type_ = Type::kExpression;
expression_ = &expression;
}
const CSSMathExpressionNode& InterpolableLength::AsExpression() const {
if (IsExpression())
return *expression_;
return *CSSMathExpressionNumericLiteral::Create(
CSSNumericLiteralValue::Create(single_value_, unit_type_));
}
bool InterpolableLength::HasPercentage() const {
if (IsNumericLiteral())
return unit_type_ == UnitType::kPercentage;
return expression_->HasPercentage();
}
void InterpolableLength::SetHasPercentage() {
DEFINE_STATIC_LOCAL(Persistent<CSSMathExpressionNode>, zero_percent,
{PercentageNode(0)});
if (HasPercentage())
return;
if (IsZeroLength())
return SetNumericLiteral(0, UnitType::kPercentage);
SetExpression(*CSSMathExpressionBinaryOperation::Create(
&AsExpression(), zero_percent, CSSMathOperator::kAdd));
}
void InterpolableLength::SubtractFromOneHundredPercent() {
DEFINE_STATIC_LOCAL(Persistent<CSSMathExpressionNode>, hundred_percent,
{PercentageNode(100)});
if (IsNumericLiteral()) {
if (unit_type_ == UnitType::kPercentage) {
single_value_ = 100 - single_value_;
return;
}
if (IsZeroLength())
return SetNumericLiteral(100, UnitType::kPercentage);
// Fall through, as the result requires an expression to represent
}
SetExpression(*CSSMathExpressionBinaryOperation::CreateSimplified(
hundred_percent, &AsExpression(), CSSMathOperator::kSubtract));
}
static double ClampToRange(double x, ValueRange range) {
return (range == kValueRangeNonNegative && x < 0) ? 0 : x;
}
Length InterpolableLength::CreateLength(
const CSSToLengthConversionData& conversion_data,
ValueRange range) const {
if (IsNumericLiteral()) {
const double value = ClampToRange(single_value_, range);
if (CSSPrimitiveValue::IsLength(unit_type_)) {
const double value_px =
conversion_data.ZoomedComputedPixels(value, unit_type_);
return Length::Fixed(CSSPrimitiveValue::ClampToCSSLengthRange(value_px));
}
DCHECK_EQ(UnitType::kPercentage, unit_type_);
return Length::Percent(value);
}
// In the uncommon case where |this| is a math expression, this implementation
// avoids a lot of code complexity at the cost of creating a temporary
// |CSSMathFunctionValue| object.
return CreateCSSValue(range)->ConvertToLength(conversion_data);
}
const CSSPrimitiveValue* InterpolableLength::CreateCSSValue(
ValueRange range) const {
if (IsExpression())
return CSSMathFunctionValue::Create(&AsExpression(), range);
DCHECK(IsNumericLiteral());
return CSSNumericLiteralValue::Create(ClampToRange(single_value_, range),
unit_type_);
}
void InterpolableLength::Scale(double scale) {
if (IsNumericLiteral()) {
single_value_ *= scale;
return;
}
DCHECK(IsExpression());
SetExpression(*CSSMathExpressionBinaryOperation::CreateSimplified(
&AsExpression(), NumberNode(scale), CSSMathOperator::kMultiply));
}
void InterpolableLength::ScaleAndAdd(double scale,
const InterpolableValue& other) {
const InterpolableLength& other_length = To<InterpolableLength>(other);
if (IsNumericLiteral() && other_length.IsNumericLiteral() &&
unit_type_ == other_length.unit_type_) {
single_value_ = single_value_ * scale + other_length.single_value_;
return;
}
// Avoid creating an addition expression with a zero-length operand.
if (IsZeroLength()) {
*this = other_length;
return;
}
if (other_length.IsZeroLength())
return Scale(scale);
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 (IsNumericLiteral() && to_length.IsNumericLiteral() &&
unit_type_ == to_length.unit_type_) {
return result_length.SetNumericLiteral(
Blend(single_value_, to_length.single_value_, progress), unit_type_);
}
// Avoid creating an addition expression with a zero-length operand.
if (IsZeroLength()) {
result_length = to_length;
return result_length.Scale(progress);
}
if (to_length.IsZeroLength()) {
result_length = *this;
return result_length.Scale(1 - progress);
}
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