blob: 20d9460dbb689b18d42607091ae693a838773c73 [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// 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/platform/geometry/calculation_expression_node.h"
#include <cfloat>
#include <numeric>
#include "base/memory/scoped_refptr.h"
#include "base/notreached.h"
#include "base/ranges/algorithm.h"
#include "third_party/blink/renderer/platform/geometry/length.h"
#include "third_party/blink/renderer/platform/geometry/length_functions.h"
#include "third_party/blink/renderer/platform/geometry/math_functions.h"
namespace blink {
// ------ CalculationExpressionNumberNode ------
float CalculationExpressionNumberNode::Evaluate(
float max_value,
const Length::EvaluationInput&) const {
return value_;
}
bool CalculationExpressionNumberNode::Equals(
const CalculationExpressionNode& other) const {
if (!other.IsNumber())
return false;
const auto& other_number = To<CalculationExpressionNumberNode>(other);
return value_ == other_number.Value();
}
scoped_refptr<const CalculationExpressionNode>
CalculationExpressionNumberNode::Zoom(double) const {
return base::MakeRefCounted<CalculationExpressionNumberNode>(value_);
}
#if DCHECK_IS_ON()
CalculationExpressionNode::ResultType
CalculationExpressionNumberNode::ResolvedResultType() const {
return result_type_;
}
#endif
// ------ CalculationExpressionSizingKeywordNode ------
CalculationExpressionSizingKeywordNode::CalculationExpressionSizingKeywordNode(
Keyword keyword)
: keyword_(keyword) {
if (keyword != Keyword::kSize && keyword != Keyword::kAny) {
if (keyword == Keyword::kAuto) {
has_auto_ = true;
} else {
has_content_or_intrinsic_ = true;
}
}
#if DCHECK_IS_ON()
result_type_ = ResultType::kPixelsAndPercent;
#endif
}
float CalculationExpressionSizingKeywordNode::Evaluate(
float max_value,
const Length::EvaluationInput& input) const {
Length::Type intrinsic_type = Length::kFixed;
switch (keyword_) {
case Keyword::kSize:
CHECK(input.size_keyword_basis);
return *input.size_keyword_basis;
case Keyword::kAny:
return 0.0f;
case Keyword::kAuto:
intrinsic_type = Length::Type::kAuto;
break;
case Keyword::kMinContent:
case Keyword::kWebkitMinContent:
intrinsic_type = Length::Type::kMinContent;
break;
case Keyword::kMaxContent:
case Keyword::kWebkitMaxContent:
intrinsic_type = Length::Type::kMaxContent;
break;
case Keyword::kFitContent:
case Keyword::kWebkitFitContent:
intrinsic_type = Length::Type::kFitContent;
break;
case Keyword::kWebkitFillAvailable:
intrinsic_type = Length::Type::kFillAvailable;
break;
}
if (!input.intrinsic_evaluator) {
// TODO(https://crbug.com/313072): I'd like to be able to CHECK() this
// instead. However, we hit this code in three cases:
// * the code in ContentMinimumInlineSize, which passes max_value of 0
// * the (questionable) code in EvaluateValueIfNaNorInfinity(), which
// passes max_value of 1 or -1
// * the DCHECK()s in
// CSSLengthInterpolationType::ApplyStandardPropertyValue pass a max
// value of 100
// So we have to return something. Return 0 for now, though this may
// not be ideal.
CHECK(max_value == 1.0f || max_value == -1.0f || max_value == 0.0f ||
max_value == 100.0f);
return 0.0f;
}
CHECK(input.intrinsic_evaluator);
return (*input.intrinsic_evaluator)(Length(intrinsic_type));
}
// ------ CalculationExpressionPixelsAndPercentNode ------
float CalculationExpressionPixelsAndPercentNode::Evaluate(
float max_value,
const Length::EvaluationInput&) const {
return value_.pixels + value_.percent / 100 * max_value;
}
bool CalculationExpressionPixelsAndPercentNode::Equals(
const CalculationExpressionNode& other) const {
if (!other.IsPixelsAndPercent())
return false;
const auto& other_pixels_and_percent =
To<CalculationExpressionPixelsAndPercentNode>(other);
return value_.pixels == other_pixels_and_percent.value_.pixels &&
value_.percent == other_pixels_and_percent.value_.percent;
}
scoped_refptr<const CalculationExpressionNode>
CalculationExpressionPixelsAndPercentNode::Zoom(double factor) const {
PixelsAndPercent result(value_.pixels * factor, value_.percent,
value_.has_explicit_pixels,
value_.has_explicit_percent);
return base::MakeRefCounted<CalculationExpressionPixelsAndPercentNode>(
result);
}
#if DCHECK_IS_ON()
CalculationExpressionNode::ResultType
CalculationExpressionPixelsAndPercentNode::ResolvedResultType() const {
return result_type_;
}
#endif
// ------ CalculationExpressionOperationNode ------
// static
scoped_refptr<const CalculationExpressionNode>
CalculationExpressionOperationNode::CreateSimplified(Children&& children,
CalculationOperator op) {
switch (op) {
case CalculationOperator::kAdd:
case CalculationOperator::kSubtract: {
DCHECK_EQ(children.size(), 2u);
if (!children[0]->IsPixelsAndPercent() ||
!children[1]->IsPixelsAndPercent()) {
return base::MakeRefCounted<CalculationExpressionOperationNode>(
Children({std::move(children[0]), std::move(children[1])}), op);
}
const auto& left_pixels_and_percent =
To<CalculationExpressionPixelsAndPercentNode>(*children[0]);
PixelsAndPercent right_pixels_and_percent =
To<CalculationExpressionPixelsAndPercentNode>(*children[1])
.GetPixelsAndPercent();
PixelsAndPercent value = left_pixels_and_percent.GetPixelsAndPercent();
if (op == CalculationOperator::kAdd) {
value += right_pixels_and_percent;
} else {
value -= right_pixels_and_percent;
}
return base::MakeRefCounted<CalculationExpressionPixelsAndPercentNode>(
value);
}
case CalculationOperator::kMultiply: {
DCHECK_EQ(children.size(), 2u);
if (children.front()->IsOperation() || children.back()->IsOperation()) {
return base::MakeRefCounted<CalculationExpressionOperationNode>(
Children({std::move(children[0]), std::move(children[1])}), op);
}
auto& maybe_pixels_and_percent_node =
children[0]->IsNumber() ? children[1] : children[0];
if (!maybe_pixels_and_percent_node->IsPixelsAndPercent()) {
return base::MakeRefCounted<CalculationExpressionOperationNode>(
Children({std::move(children[0]), std::move(children[1])}), op);
}
auto& number_node = children[0]->IsNumber() ? children[0] : children[1];
const auto& number = To<CalculationExpressionNumberNode>(*number_node);
PixelsAndPercent pixels_and_percent =
To<CalculationExpressionPixelsAndPercentNode>(
*maybe_pixels_and_percent_node)
.GetPixelsAndPercent();
pixels_and_percent *= number.Value();
return base::MakeRefCounted<CalculationExpressionPixelsAndPercentNode>(
pixels_and_percent);
}
case CalculationOperator::kMin:
case CalculationOperator::kMax: {
DCHECK(children.size());
float simplified_px;
bool can_simplify = true;
for (wtf_size_t i = 0; i < children.size(); ++i) {
const auto* pixels_and_percent =
DynamicTo<CalculationExpressionPixelsAndPercentNode>(*children[i]);
if (!pixels_and_percent || pixels_and_percent->Percent()) {
can_simplify = false;
break;
}
if (!i) {
simplified_px = pixels_and_percent->Pixels();
} else {
if (op == CalculationOperator::kMin) {
simplified_px =
std::min(simplified_px, pixels_and_percent->Pixels());
} else {
simplified_px =
std::max(simplified_px, pixels_and_percent->Pixels());
}
}
}
if (can_simplify) {
return base::MakeRefCounted<CalculationExpressionPixelsAndPercentNode>(
PixelsAndPercent(simplified_px));
}
return base::MakeRefCounted<CalculationExpressionOperationNode>(
std::move(children), op);
}
case CalculationOperator::kClamp: {
DCHECK_EQ(children.size(), 3u);
Vector<float> operand_pixels;
operand_pixels.reserve(children.size());
bool can_simplify = true;
for (auto& child : children) {
const auto* pixels_and_percent =
DynamicTo<CalculationExpressionPixelsAndPercentNode>(*child);
if (!pixels_and_percent || pixels_and_percent->Percent()) {
can_simplify = false;
break;
}
operand_pixels.push_back(pixels_and_percent->Pixels());
}
if (can_simplify) {
float min_px = operand_pixels[0];
float val_px = operand_pixels[1];
float max_px = operand_pixels[2];
// clamp(MIN, VAL, MAX) is identical to max(MIN, min(VAL, MAX))
// according to the spec,
// https://drafts.csswg.org/css-values-4/#funcdef-clamp.
float clamped_px = std::max(min_px, std::min(val_px, max_px));
return base::MakeRefCounted<CalculationExpressionPixelsAndPercentNode>(
PixelsAndPercent(clamped_px));
}
return base::MakeRefCounted<CalculationExpressionOperationNode>(
std::move(children), op);
}
case CalculationOperator::kRoundNearest:
case CalculationOperator::kRoundUp:
case CalculationOperator::kRoundDown:
case CalculationOperator::kRoundToZero:
case CalculationOperator::kMod:
case CalculationOperator::kRem: {
DCHECK_EQ(children.size(), 2u);
const auto* a =
DynamicTo<CalculationExpressionPixelsAndPercentNode>(*children[0]);
const auto* b =
DynamicTo<CalculationExpressionPixelsAndPercentNode>(*children[1]);
bool can_simplify = a && !a->Percent() && b && !b->Percent();
if (can_simplify) {
float value =
EvaluateSteppedValueFunction(op, a->Pixels(), b->Pixels());
return base::MakeRefCounted<CalculationExpressionPixelsAndPercentNode>(
PixelsAndPercent(value));
} else {
return base::MakeRefCounted<CalculationExpressionOperationNode>(
std::move(children), op);
}
}
case CalculationOperator::kHypot: {
DCHECK_GE(children.size(), 1u);
Vector<float> operand_pixels;
operand_pixels.reserve(children.size());
bool can_simplify = true;
for (auto& child : children) {
const auto* pixels_and_percent =
DynamicTo<CalculationExpressionPixelsAndPercentNode>(*child);
if (!pixels_and_percent || pixels_and_percent->Percent()) {
can_simplify = false;
break;
}
operand_pixels.push_back(pixels_and_percent->Pixels());
}
if (can_simplify) {
float value = 0;
for (float operand : operand_pixels) {
value = std::hypot(value, operand);
}
return base::MakeRefCounted<CalculationExpressionPixelsAndPercentNode>(
PixelsAndPercent(value));
}
return base::MakeRefCounted<CalculationExpressionOperationNode>(
std::move(children), op);
}
case CalculationOperator::kAbs:
case CalculationOperator::kSign: {
DCHECK_EQ(children.size(), 1u);
const auto* pixels_and_percent =
DynamicTo<CalculationExpressionPixelsAndPercentNode>(
*children.front());
if (!pixels_and_percent || pixels_and_percent->Percent()) {
return base::MakeRefCounted<CalculationExpressionOperationNode>(
std::move(children), op);
} else {
float value = pixels_and_percent->Pixels();
if (op == CalculationOperator::kAbs) {
return base::MakeRefCounted<
CalculationExpressionPixelsAndPercentNode>(
PixelsAndPercent(std::abs(value)));
} else {
if (value == 0 || std::isnan(value)) {
return base::MakeRefCounted<CalculationExpressionNumberNode>(value);
}
return base::MakeRefCounted<CalculationExpressionNumberNode>(
value > 0 ? 1 : -1);
}
}
}
case CalculationOperator::kProgress:
case CalculationOperator::kMediaProgress: {
DCHECK_EQ(children.size(), 3u);
Vector<float, 3> operand_pixels;
bool can_simplify = true;
for (scoped_refptr<const CalculationExpressionNode>& child : children) {
const auto* pixels_and_percent =
DynamicTo<CalculationExpressionPixelsAndPercentNode>(*child);
if (!pixels_and_percent || pixels_and_percent->Percent()) {
can_simplify = false;
break;
}
operand_pixels.push_back(pixels_and_percent->Pixels());
}
if (can_simplify) {
float progress_px = operand_pixels[0];
float from_px = operand_pixels[1];
float to_px = operand_pixels[2];
float progress = (progress_px - from_px) / (to_px - from_px);
return base::MakeRefCounted<CalculationExpressionPixelsAndPercentNode>(
PixelsAndPercent(progress));
}
return base::MakeRefCounted<CalculationExpressionOperationNode>(
std::move(children), op);
}
case CalculationOperator::kCalcSize: {
DCHECK_EQ(children.size(), 2u);
// TODO(https://crbug.com/313072): It may be worth implementing
// simplification for calc-size(), but it's not likely to be possible to
// simplify calc-size() in any of its real use cases.
return base::MakeRefCounted<CalculationExpressionOperationNode>(
std::move(children), op);
}
case CalculationOperator::kInvalid:
NOTREACHED();
return nullptr;
}
}
CalculationExpressionOperationNode::CalculationExpressionOperationNode(
Children&& children,
CalculationOperator op)
: children_(std::move(children)), operator_(op) {
#if DCHECK_IS_ON()
result_type_ = ResolvedResultType();
DCHECK_NE(result_type_, ResultType::kInvalid);
#endif
if (op == CalculationOperator::kCalcSize) {
// "A calc-size() is treated, in all respects, as if it were its
// calc-size basis." This is particularly relevant for ignoring the
// presence of percentages in the calculation.
DCHECK_EQ(children_.size(), 2u);
const auto& basis = children_[0];
has_content_or_intrinsic_ = basis->HasContentOrIntrinsicSize();
has_auto_ = basis->HasAuto();
has_percent_ = basis->HasPercent();
} else {
for (const auto& child : children_) {
if (child->HasContentOrIntrinsicSize()) {
has_content_or_intrinsic_ = true;
}
if (child->HasAuto()) {
has_auto_ = true;
}
if (child->HasPercent()) {
has_percent_ = true;
}
}
}
}
float CalculationExpressionOperationNode::Evaluate(
float max_value,
const Length::EvaluationInput& input) const {
switch (operator_) {
case CalculationOperator::kAdd: {
DCHECK_EQ(children_.size(), 2u);
float left = children_[0]->Evaluate(max_value, input);
float right = children_[1]->Evaluate(max_value, input);
return left + right;
}
case CalculationOperator::kSubtract: {
DCHECK_EQ(children_.size(), 2u);
float left = children_[0]->Evaluate(max_value, input);
float right = children_[1]->Evaluate(max_value, input);
return left - right;
}
case CalculationOperator::kMultiply: {
DCHECK_EQ(children_.size(), 2u);
float left = children_[0]->Evaluate(max_value, input);
float right = children_[1]->Evaluate(max_value, input);
return left * right;
}
case CalculationOperator::kMin: {
DCHECK(!children_.empty());
float minimum = children_[0]->Evaluate(max_value, input);
for (auto& child : children_) {
minimum = std::min(minimum, child->Evaluate(max_value, input));
}
return minimum;
}
case CalculationOperator::kMax: {
DCHECK(!children_.empty());
float maximum = children_[0]->Evaluate(max_value, input);
for (auto& child : children_) {
maximum = std::max(maximum, child->Evaluate(max_value, input));
}
return maximum;
}
case CalculationOperator::kClamp: {
DCHECK(!children_.empty());
float min = children_[0]->Evaluate(max_value, input);
float val = children_[1]->Evaluate(max_value, input);
float max = children_[2]->Evaluate(max_value, input);
// clamp(MIN, VAL, MAX) is identical to max(MIN, min(VAL, MAX))
return std::max(min, std::min(val, max));
}
case CalculationOperator::kRoundNearest:
case CalculationOperator::kRoundUp:
case CalculationOperator::kRoundDown:
case CalculationOperator::kRoundToZero:
case CalculationOperator::kMod:
case CalculationOperator::kRem: {
DCHECK_EQ(children_.size(), 2u);
float a = children_[0]->Evaluate(max_value, input);
float b = children_[1]->Evaluate(max_value, input);
return EvaluateSteppedValueFunction(operator_, a, b);
}
case CalculationOperator::kHypot: {
DCHECK_GE(children_.size(), 1u);
float value = 0;
for (scoped_refptr<const CalculationExpressionNode> operand : children_) {
float a = operand->Evaluate(max_value, input);
value = std::hypot(value, a);
}
return value;
}
case CalculationOperator::kAbs:
case CalculationOperator::kSign: {
DCHECK_EQ(children_.size(), 1u);
const float value = children_.front()->Evaluate(max_value, input);
if (operator_ == CalculationOperator::kAbs) {
return std::abs(value);
} else {
if (value == 0 || std::isnan(value)) {
return value;
}
return value > 0 ? 1 : -1;
}
}
case CalculationOperator::kCalcSize: {
DCHECK_EQ(children_.size(), 2u);
Length::EvaluationInput calculation_input(input);
calculation_input.size_keyword_basis =
children_[0]->Evaluate(max_value, input);
if (max_value == kIndefiniteSize) {
// "When evaluating the calc-size calculation, if percentages are not
// definite in the given context, the resolve to 0px. Otherwise, they
// resolve as normal."
// -- https://drafts.csswg.org/css-values-5/#resolving-calc-size
max_value = 0.0f;
}
return children_[1]->Evaluate(max_value, calculation_input);
}
case CalculationOperator::kProgress:
case CalculationOperator::kMediaProgress: {
DCHECK(!children_.empty());
float progress = children_[0]->Evaluate(max_value, input);
float from = children_[1]->Evaluate(max_value, input);
float to = children_[2]->Evaluate(max_value, input);
return (progress - from) / (to - from);
}
case CalculationOperator::kInvalid:
break;
// TODO(crbug.com/1284199): Support other math functions.
}
NOTREACHED();
return std::numeric_limits<float>::quiet_NaN();
}
bool CalculationExpressionOperationNode::Equals(
const CalculationExpressionNode& other) const {
if (!other.IsOperation())
return false;
const auto& other_operation = To<CalculationExpressionOperationNode>(other);
if (operator_ != other_operation.GetOperator())
return false;
using ValueType = Children::value_type;
return base::ranges::equal(
children_, other_operation.GetChildren(),
[](const ValueType& a, const ValueType& b) { return *a == *b; });
}
scoped_refptr<const CalculationExpressionNode>
CalculationExpressionOperationNode::Zoom(double factor) const {
switch (operator_) {
case CalculationOperator::kAdd:
case CalculationOperator::kSubtract:
DCHECK_EQ(children_.size(), 2u);
return CreateSimplified(
Children({children_[0]->Zoom(factor), children_[1]->Zoom(factor)}),
operator_);
case CalculationOperator::kMultiply: {
DCHECK_EQ(children_.size(), 2u);
auto& number = children_[0]->IsNumber() ? children_[0] : children_[1];
auto& pixels_and_percent =
children_[0]->IsNumber() ? children_[1] : children_[0];
return CreateSimplified(
Children({pixels_and_percent->Zoom(factor), number}), operator_);
}
case CalculationOperator::kCalcSize: {
DCHECK_EQ(children_.size(), 2u);
return CreateSimplified(
Children({children_[0], children_[1]->Zoom(factor)}), operator_);
}
case CalculationOperator::kMin:
case CalculationOperator::kMax:
case CalculationOperator::kClamp:
case CalculationOperator::kRoundNearest:
case CalculationOperator::kRoundUp:
case CalculationOperator::kRoundDown:
case CalculationOperator::kRoundToZero:
case CalculationOperator::kMod:
case CalculationOperator::kRem:
case CalculationOperator::kHypot:
case CalculationOperator::kAbs:
case CalculationOperator::kSign:
case CalculationOperator::kProgress:
case CalculationOperator::kMediaProgress: {
DCHECK(children_.size());
Vector<scoped_refptr<const CalculationExpressionNode>> cloned_operands;
cloned_operands.reserve(children_.size());
for (const auto& child : children_)
cloned_operands.push_back(child->Zoom(factor));
return CreateSimplified(std::move(cloned_operands), operator_);
}
case CalculationOperator::kInvalid:
NOTREACHED();
return nullptr;
}
}
#if DCHECK_IS_ON()
CalculationExpressionNode::ResultType
CalculationExpressionOperationNode::ResolvedResultType() const {
switch (operator_) {
case CalculationOperator::kAdd:
case CalculationOperator::kSubtract: {
DCHECK_EQ(children_.size(), 2u);
auto left_type = children_[0]->ResolvedResultType();
auto right_type = children_[1]->ResolvedResultType();
if (left_type == ResultType::kInvalid ||
right_type == ResultType::kInvalid || left_type != right_type)
return ResultType::kInvalid;
return left_type;
}
case CalculationOperator::kMultiply: {
DCHECK_EQ(children_.size(), 2u);
auto left_type = children_[0]->ResolvedResultType();
auto right_type = children_[1]->ResolvedResultType();
if (left_type == ResultType::kInvalid ||
right_type == ResultType::kInvalid ||
(left_type == ResultType::kPixelsAndPercent &&
right_type == ResultType::kPixelsAndPercent))
return ResultType::kInvalid;
if ((left_type == ResultType::kPixelsAndPercent &&
right_type == ResultType::kNumber) ||
(left_type == ResultType::kNumber &&
right_type == ResultType::kPixelsAndPercent))
return ResultType::kPixelsAndPercent;
return ResultType::kNumber;
}
case CalculationOperator::kCalcSize: {
DCHECK_EQ(children_.size(), 2u);
auto basis_type = children_[0]->ResolvedResultType();
auto calculation_type = children_[1]->ResolvedResultType();
if (basis_type != ResultType::kPixelsAndPercent ||
calculation_type != ResultType::kPixelsAndPercent) {
return ResultType::kInvalid;
}
return ResultType::kPixelsAndPercent;
}
case CalculationOperator::kMin:
case CalculationOperator::kMax:
case CalculationOperator::kClamp:
case CalculationOperator::kRoundNearest:
case CalculationOperator::kRoundUp:
case CalculationOperator::kRoundDown:
case CalculationOperator::kRoundToZero:
case CalculationOperator::kMod:
case CalculationOperator::kRem:
case CalculationOperator::kHypot:
case CalculationOperator::kAbs: {
DCHECK(children_.size());
auto first_child_type = children_.front()->ResolvedResultType();
for (const auto& child : children_) {
if (first_child_type != child->ResolvedResultType())
return ResultType::kInvalid;
}
return first_child_type;
}
case CalculationOperator::kSign:
case CalculationOperator::kProgress:
case CalculationOperator::kMediaProgress:
return ResultType::kNumber;
case CalculationOperator::kInvalid:
NOTREACHED();
return result_type_;
}
}
#endif
} // namespace blink