blob: 53fc59107437cca3686de2ab3953b9aae15e9682 [file] [log] [blame]
/*
* Copyright (C) 2011, 2012 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "third_party/blink/renderer/core/css/css_math_expression_node.h"
#include "third_party/blink/renderer/core/css/css_numeric_literal_value.h"
#include "third_party/blink/renderer/core/css/css_primitive_value_mappings.h"
#include "third_party/blink/renderer/core/css/css_value_clamping_utils.h"
#include "third_party/blink/renderer/core/css/properties/css_parsing_utils.h"
#include "third_party/blink/renderer/core/css/resolver/style_resolver.h"
#include "third_party/blink/renderer/platform/geometry/calculation_expression_node.h"
#include "third_party/blink/renderer/platform/wtf/math_extras.h"
#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
static const int maxExpressionDepth = 100;
enum ParseState { OK, TooDeep, NoMoreTokens };
namespace blink {
static CalculationCategory UnitCategory(CSSPrimitiveValue::UnitType type) {
switch (type) {
case CSSPrimitiveValue::UnitType::kNumber:
case CSSPrimitiveValue::UnitType::kInteger:
return kCalcNumber;
case CSSPrimitiveValue::UnitType::kPercentage:
return kCalcPercent;
case CSSPrimitiveValue::UnitType::kEms:
case CSSPrimitiveValue::UnitType::kExs:
case CSSPrimitiveValue::UnitType::kPixels:
case CSSPrimitiveValue::UnitType::kCentimeters:
case CSSPrimitiveValue::UnitType::kMillimeters:
case CSSPrimitiveValue::UnitType::kQuarterMillimeters:
case CSSPrimitiveValue::UnitType::kInches:
case CSSPrimitiveValue::UnitType::kPoints:
case CSSPrimitiveValue::UnitType::kPicas:
case CSSPrimitiveValue::UnitType::kUserUnits:
case CSSPrimitiveValue::UnitType::kRems:
case CSSPrimitiveValue::UnitType::kChs:
case CSSPrimitiveValue::UnitType::kViewportWidth:
case CSSPrimitiveValue::UnitType::kViewportHeight:
case CSSPrimitiveValue::UnitType::kViewportMin:
case CSSPrimitiveValue::UnitType::kViewportMax:
return kCalcLength;
case CSSPrimitiveValue::UnitType::kDegrees:
case CSSPrimitiveValue::UnitType::kGradians:
case CSSPrimitiveValue::UnitType::kRadians:
case CSSPrimitiveValue::UnitType::kTurns:
return kCalcAngle;
case CSSPrimitiveValue::UnitType::kMilliseconds:
case CSSPrimitiveValue::UnitType::kSeconds:
return kCalcTime;
case CSSPrimitiveValue::UnitType::kHertz:
case CSSPrimitiveValue::UnitType::kKilohertz:
return kCalcFrequency;
default:
return kCalcOther;
}
}
static bool HasDoubleValue(CSSPrimitiveValue::UnitType type) {
switch (type) {
case CSSPrimitiveValue::UnitType::kNumber:
case CSSPrimitiveValue::UnitType::kPercentage:
case CSSPrimitiveValue::UnitType::kEms:
case CSSPrimitiveValue::UnitType::kExs:
case CSSPrimitiveValue::UnitType::kChs:
case CSSPrimitiveValue::UnitType::kRems:
case CSSPrimitiveValue::UnitType::kPixels:
case CSSPrimitiveValue::UnitType::kCentimeters:
case CSSPrimitiveValue::UnitType::kMillimeters:
case CSSPrimitiveValue::UnitType::kQuarterMillimeters:
case CSSPrimitiveValue::UnitType::kInches:
case CSSPrimitiveValue::UnitType::kPoints:
case CSSPrimitiveValue::UnitType::kPicas:
case CSSPrimitiveValue::UnitType::kUserUnits:
case CSSPrimitiveValue::UnitType::kDegrees:
case CSSPrimitiveValue::UnitType::kRadians:
case CSSPrimitiveValue::UnitType::kGradians:
case CSSPrimitiveValue::UnitType::kTurns:
case CSSPrimitiveValue::UnitType::kMilliseconds:
case CSSPrimitiveValue::UnitType::kSeconds:
case CSSPrimitiveValue::UnitType::kHertz:
case CSSPrimitiveValue::UnitType::kKilohertz:
case CSSPrimitiveValue::UnitType::kViewportWidth:
case CSSPrimitiveValue::UnitType::kViewportHeight:
case CSSPrimitiveValue::UnitType::kViewportMin:
case CSSPrimitiveValue::UnitType::kViewportMax:
case CSSPrimitiveValue::UnitType::kDotsPerPixel:
case CSSPrimitiveValue::UnitType::kDotsPerInch:
case CSSPrimitiveValue::UnitType::kDotsPerCentimeter:
case CSSPrimitiveValue::UnitType::kFraction:
case CSSPrimitiveValue::UnitType::kInteger:
return true;
default:
return false;
}
}
namespace {
const PixelsAndPercent CreateClampedSamePixelsAndPercent(float value) {
return PixelsAndPercent(CSSValueClampingUtils::ClampLength(value),
CSSValueClampingUtils::ClampLength(value));
}
bool IsNaN(PixelsAndPercent value, bool allows_negative_percentage_reference) {
if (std::isnan(value.pixels + value.percent) ||
(allows_negative_percentage_reference && std::isinf(value.percent))) {
return true;
}
return false;
}
base::Optional<PixelsAndPercent> EvaluateValueIfNaNorInfinity(
scoped_refptr<const blink::CalculationExpressionNode> value,
bool allows_negative_percentage_reference) {
float evaluated_value = value->Evaluate(1);
if (std::isnan(evaluated_value) || std::isinf(evaluated_value)) {
return CreateClampedSamePixelsAndPercent(evaluated_value);
}
if (allows_negative_percentage_reference) {
evaluated_value = value->Evaluate(-1);
if (std::isnan(evaluated_value) || std::isinf(evaluated_value)) {
return CreateClampedSamePixelsAndPercent(evaluated_value);
}
}
return base::nullopt;
}
} // namespace
// ------ Start of CSSMathExpressionNumericLiteral member functions ------
// static
CSSMathExpressionNumericLiteral* CSSMathExpressionNumericLiteral::Create(
const CSSNumericLiteralValue* value,
bool is_integer) {
return MakeGarbageCollected<CSSMathExpressionNumericLiteral>(value,
is_integer);
}
// static
CSSMathExpressionNumericLiteral* CSSMathExpressionNumericLiteral::Create(
double value,
CSSPrimitiveValue::UnitType type,
bool is_integer) {
if (!RuntimeEnabledFeatures::CSSCalcInfinityAndNaNEnabled() &&
(std::isnan(value) || std::isinf(value)))
return nullptr;
return MakeGarbageCollected<CSSMathExpressionNumericLiteral>(
CSSNumericLiteralValue::Create(value, type), is_integer);
}
CSSMathExpressionNumericLiteral::CSSMathExpressionNumericLiteral(
const CSSNumericLiteralValue* value,
bool is_integer)
: CSSMathExpressionNode(UnitCategory(value->GetType()),
is_integer,
false /* has_comparisons*/),
value_(value) {}
bool CSSMathExpressionNumericLiteral::IsZero() const {
return !value_->GetDoubleValue();
}
String CSSMathExpressionNumericLiteral::CustomCSSText() const {
return value_->CssText();
}
base::Optional<PixelsAndPercent>
CSSMathExpressionNumericLiteral::ToPixelsAndPercent(
const CSSToLengthConversionData& conversion_data) const {
PixelsAndPercent value(0, 0);
switch (category_) {
case kCalcLength:
// When CSSCalcInfinityAndNaN is enabled, we allow infinity and NaN in
// PixelsAndPercent. Therefore, we need to use a function that doesn't
// internally clamp the result to the float range.
if (RuntimeEnabledFeatures::CSSCalcInfinityAndNaNEnabled())
value.pixels = value_->ComputeLengthPx(conversion_data);
else
value.pixels = value_->ComputeLength<float>(conversion_data);
break;
case kCalcPercent:
DCHECK(value_->IsPercentage());
// When CSSCalcInfinityAndNaN is enabled, we allow infinity and NaN in
// PixelsAndPercent. Therefore, we need to use a function that doesn't
// internally clamp the result to the float range.
if (RuntimeEnabledFeatures::CSSCalcInfinityAndNaNEnabled())
value.percent = value_->GetDoubleValue();
else
value.percent = value_->GetFloatValue();
break;
case kCalcNumber:
// TODO(alancutter): Stop treating numbers like pixels unconditionally
// in calcs to be able to accomodate border-image-width
// https://drafts.csswg.org/css-backgrounds-3/#the-border-image-width
value.pixels = value_->GetFloatValue() * conversion_data.Zoom();
break;
default:
NOTREACHED();
}
return value;
}
scoped_refptr<const CalculationExpressionNode>
CSSMathExpressionNumericLiteral::ToCalculationExpression(
const CSSToLengthConversionData& conversion_data) const {
return base::MakeRefCounted<CalculationExpressionLeafNode>(
*ToPixelsAndPercent(conversion_data));
}
double CSSMathExpressionNumericLiteral::DoubleValue() const {
if (HasDoubleValue(ResolvedUnitType()))
return value_->GetDoubleValue();
NOTREACHED();
return 0;
}
base::Optional<double>
CSSMathExpressionNumericLiteral::ComputeValueInCanonicalUnit() const {
switch (category_) {
case kCalcNumber:
case kCalcPercent:
return value_->DoubleValue();
case kCalcLength:
if (CSSPrimitiveValue::IsRelativeUnit(value_->GetType()))
return base::nullopt;
U_FALLTHROUGH;
case kCalcAngle:
case kCalcTime:
case kCalcFrequency:
return value_->DoubleValue() *
CSSPrimitiveValue::ConversionToCanonicalUnitsScaleFactor(
value_->GetType());
default:
return base::nullopt;
}
}
double CSSMathExpressionNumericLiteral::ComputeLengthPx(
const CSSToLengthConversionData& conversion_data) const {
switch (category_) {
case kCalcLength:
// When CSSCalcInfinityAndNaN is enabled, we allow infinity and NaN in
// PixelsAndPercent. Therefore, we need to use a function that doesn't
// internally clamp the result to the float range.
if (RuntimeEnabledFeatures::CSSCalcInfinityAndNaNEnabled())
return value_->ComputeLengthPx(conversion_data);
return value_->ComputeLength<double>(conversion_data);
case kCalcNumber:
case kCalcPercent:
case kCalcAngle:
case kCalcFrequency:
case kCalcPercentLength:
case kCalcTime:
case kCalcOther:
NOTREACHED();
break;
}
NOTREACHED();
return 0;
}
bool CSSMathExpressionNumericLiteral::AccumulateLengthArray(
CSSLengthArray& length_array,
double multiplier) const {
DCHECK_NE(Category(), kCalcNumber);
return value_->AccumulateLengthArray(length_array, multiplier);
}
void CSSMathExpressionNumericLiteral::AccumulateLengthUnitTypes(
CSSPrimitiveValue::LengthTypeFlags& types) const {
value_->AccumulateLengthUnitTypes(types);
}
bool CSSMathExpressionNumericLiteral::operator==(
const CSSMathExpressionNode& other) const {
if (!other.IsNumericLiteral())
return false;
return DataEquivalent(value_,
To<CSSMathExpressionNumericLiteral>(other).value_);
}
CSSPrimitiveValue::UnitType CSSMathExpressionNumericLiteral::ResolvedUnitType()
const {
return value_->GetType();
}
bool CSSMathExpressionNumericLiteral::IsComputationallyIndependent() const {
return value_->IsComputationallyIndependent();
}
void CSSMathExpressionNumericLiteral::Trace(Visitor* visitor) const {
visitor->Trace(value_);
CSSMathExpressionNode::Trace(visitor);
}
#if DCHECK_IS_ON()
bool CSSMathExpressionNumericLiteral::InvolvesPercentageComparisons() const {
return false;
}
#endif
// ------ End of CSSMathExpressionNumericLiteral member functions
static const CalculationCategory kAddSubtractResult[kCalcOther][kCalcOther] = {
/* CalcNumber */ {kCalcNumber, kCalcOther, kCalcOther, kCalcOther,
kCalcOther, kCalcOther, kCalcOther},
/* CalcLength */
{kCalcOther, kCalcLength, kCalcPercentLength, kCalcPercentLength,
kCalcOther, kCalcOther, kCalcOther},
/* CalcPercent */
{kCalcOther, kCalcPercentLength, kCalcPercent, kCalcPercentLength,
kCalcOther, kCalcOther, kCalcOther},
/* CalcPercentLength */
{kCalcOther, kCalcPercentLength, kCalcPercentLength, kCalcPercentLength,
kCalcOther, kCalcOther, kCalcOther},
/* CalcAngle */
{kCalcOther, kCalcOther, kCalcOther, kCalcOther, kCalcAngle, kCalcOther,
kCalcOther},
/* CalcTime */
{kCalcOther, kCalcOther, kCalcOther, kCalcOther, kCalcOther, kCalcTime,
kCalcOther},
/* CalcFrequency */
{kCalcOther, kCalcOther, kCalcOther, kCalcOther, kCalcOther, kCalcOther,
kCalcFrequency}};
static CalculationCategory DetermineCategory(
const CSSMathExpressionNode& left_side,
const CSSMathExpressionNode& right_side,
CSSMathOperator op) {
CalculationCategory left_category = left_side.Category();
CalculationCategory right_category = right_side.Category();
if (left_category == kCalcOther || right_category == kCalcOther)
return kCalcOther;
switch (op) {
case CSSMathOperator::kAdd:
case CSSMathOperator::kSubtract:
return kAddSubtractResult[left_category][right_category];
case CSSMathOperator::kMultiply:
if (left_category != kCalcNumber && right_category != kCalcNumber)
return kCalcOther;
return left_category == kCalcNumber ? right_category : left_category;
case CSSMathOperator::kDivide:
if (right_category != kCalcNumber || right_side.IsZero())
return kCalcOther;
return left_category;
default:
break;
}
NOTREACHED();
return kCalcOther;
}
static bool IsIntegerResult(const CSSMathExpressionNode* left_side,
const CSSMathExpressionNode* right_side,
CSSMathOperator op) {
// Not testing for actual integer values.
// Performs W3C spec's type checking for calc integers.
// http://www.w3.org/TR/css3-values/#calc-type-checking
return op != CSSMathOperator::kDivide && left_side->IsInteger() &&
right_side->IsInteger();
}
// ------ Start of CSSMathExpressionBinaryOperation member functions ------
// static
CSSMathExpressionNode* CSSMathExpressionBinaryOperation::Create(
const CSSMathExpressionNode* left_side,
const CSSMathExpressionNode* right_side,
CSSMathOperator op) {
DCHECK_NE(left_side->Category(), kCalcOther);
DCHECK_NE(right_side->Category(), kCalcOther);
CalculationCategory new_category =
DetermineCategory(*left_side, *right_side, op);
if (new_category == kCalcOther)
return nullptr;
return MakeGarbageCollected<CSSMathExpressionBinaryOperation>(
left_side, right_side, op, new_category);
}
// static
CSSMathExpressionNode* CSSMathExpressionBinaryOperation::CreateSimplified(
const CSSMathExpressionNode* left_side,
const CSSMathExpressionNode* right_side,
CSSMathOperator op) {
if (left_side->IsMathFunction() || right_side->IsMathFunction())
return Create(left_side, right_side, op);
CalculationCategory left_category = left_side->Category();
CalculationCategory right_category = right_side->Category();
DCHECK_NE(left_category, kCalcOther);
DCHECK_NE(right_category, kCalcOther);
bool is_integer = IsIntegerResult(left_side, right_side, op);
// Simplify numbers.
if (left_category == kCalcNumber && right_category == kCalcNumber) {
return CSSMathExpressionNumericLiteral::Create(
EvaluateOperator(left_side->DoubleValue(), right_side->DoubleValue(),
op),
CSSPrimitiveValue::UnitType::kNumber, is_integer);
}
// Simplify addition and subtraction between same types.
if (op == CSSMathOperator::kAdd || op == CSSMathOperator::kSubtract) {
if (left_category == right_side->Category()) {
CSSPrimitiveValue::UnitType left_type = left_side->ResolvedUnitType();
if (HasDoubleValue(left_type)) {
CSSPrimitiveValue::UnitType right_type = right_side->ResolvedUnitType();
if (left_type == right_type) {
return CSSMathExpressionNumericLiteral::Create(
EvaluateOperator(left_side->DoubleValue(),
right_side->DoubleValue(), op),
left_type, is_integer);
}
CSSPrimitiveValue::UnitCategory left_unit_category =
CSSPrimitiveValue::UnitTypeToUnitCategory(left_type);
if (left_unit_category != CSSPrimitiveValue::kUOther &&
left_unit_category ==
CSSPrimitiveValue::UnitTypeToUnitCategory(right_type)) {
CSSPrimitiveValue::UnitType canonical_type =
CSSPrimitiveValue::CanonicalUnitTypeForCategory(
left_unit_category);
if (canonical_type != CSSPrimitiveValue::UnitType::kUnknown) {
double left_value = clampTo<double>(
left_side->DoubleValue() *
CSSPrimitiveValue::ConversionToCanonicalUnitsScaleFactor(
left_type));
double right_value = clampTo<double>(
right_side->DoubleValue() *
CSSPrimitiveValue::ConversionToCanonicalUnitsScaleFactor(
right_type));
return CSSMathExpressionNumericLiteral::Create(
EvaluateOperator(left_value, right_value, op), canonical_type,
is_integer);
}
}
}
}
} else {
// Simplify multiplying or dividing by a number for simplifiable types.
DCHECK(op == CSSMathOperator::kMultiply || op == CSSMathOperator::kDivide);
const CSSMathExpressionNode* number_side =
GetNumberSide(left_side, right_side);
if (!number_side)
return Create(left_side, right_side, op);
if (number_side == left_side && op == CSSMathOperator::kDivide)
return nullptr;
const CSSMathExpressionNode* other_side =
left_side == number_side ? right_side : left_side;
double number = number_side->DoubleValue();
if (!RuntimeEnabledFeatures::CSSCalcInfinityAndNaNEnabled()) {
if (std::isnan(number) || std::isinf(number))
return nullptr;
if (op == CSSMathOperator::kDivide && !number)
return nullptr;
}
CSSPrimitiveValue::UnitType other_type = other_side->ResolvedUnitType();
if (HasDoubleValue(other_type)) {
return CSSMathExpressionNumericLiteral::Create(
EvaluateOperator(other_side->DoubleValue(), number, op), other_type,
is_integer);
}
}
return Create(left_side, right_side, op);
}
CSSMathExpressionBinaryOperation::CSSMathExpressionBinaryOperation(
const CSSMathExpressionNode* left_side,
const CSSMathExpressionNode* right_side,
CSSMathOperator op,
CalculationCategory category)
: CSSMathExpressionNode(
category,
IsIntegerResult(left_side, right_side, op),
left_side->HasComparisons() || right_side->HasComparisons()),
left_side_(left_side),
right_side_(right_side),
operator_(op) {}
bool CSSMathExpressionBinaryOperation::IsZero() const {
return !DoubleValue();
}
base::Optional<PixelsAndPercent>
CSSMathExpressionBinaryOperation::ToPixelsAndPercent(
const CSSToLengthConversionData& conversion_data) const {
base::Optional<PixelsAndPercent> result;
switch (operator_) {
case CSSMathOperator::kAdd:
case CSSMathOperator::kSubtract: {
result = left_side_->ToPixelsAndPercent(conversion_data);
if (!result)
return base::nullopt;
base::Optional<PixelsAndPercent> other_side =
right_side_->ToPixelsAndPercent(conversion_data);
if (!other_side)
return base::nullopt;
if (operator_ == CSSMathOperator::kAdd) {
result->pixels += other_side->pixels;
result->percent += other_side->percent;
} else {
result->pixels -= other_side->pixels;
result->percent -= other_side->percent;
}
break;
}
case CSSMathOperator::kMultiply:
case CSSMathOperator::kDivide: {
const CSSMathExpressionNode* number_side =
GetNumberSide(left_side_, right_side_);
const CSSMathExpressionNode* other_side =
left_side_ == number_side ? right_side_ : left_side_;
result = other_side->ToPixelsAndPercent(conversion_data);
if (!result)
return base::nullopt;
float number = number_side->DoubleValue();
if (operator_ == CSSMathOperator::kDivide)
number = 1.0 / number;
result->pixels *= number;
result->percent *= number;
break;
}
default:
NOTREACHED();
}
return result;
}
scoped_refptr<const CalculationExpressionNode>
CSSMathExpressionBinaryOperation::ToCalculationExpression(
const CSSToLengthConversionData& conversion_data) const {
switch (operator_) {
case CSSMathOperator::kAdd:
return CalculationExpressionAdditiveNode::CreateSimplified(
left_side_->ToCalculationExpression(conversion_data),
right_side_->ToCalculationExpression(conversion_data),
CalculationExpressionAdditiveNode::Type::kAdd);
case CSSMathOperator::kSubtract:
return CalculationExpressionAdditiveNode::CreateSimplified(
left_side_->ToCalculationExpression(conversion_data),
right_side_->ToCalculationExpression(conversion_data),
CalculationExpressionAdditiveNode::Type::kSubtract);
case CSSMathOperator::kMultiply:
DCHECK_NE((left_side_->Category() == kCalcNumber),
(right_side_->Category() == kCalcNumber));
if (left_side_->Category() == kCalcNumber) {
return CalculationExpressionMultiplicationNode::CreateSimplified(
right_side_->ToCalculationExpression(conversion_data),
left_side_->DoubleValue());
}
return CalculationExpressionMultiplicationNode::CreateSimplified(
left_side_->ToCalculationExpression(conversion_data),
right_side_->DoubleValue());
case CSSMathOperator::kDivide:
DCHECK_EQ(right_side_->Category(), kCalcNumber);
return CalculationExpressionMultiplicationNode::CreateSimplified(
left_side_->ToCalculationExpression(conversion_data),
1.0 / right_side_->DoubleValue());
default:
NOTREACHED();
return nullptr;
}
}
double CSSMathExpressionBinaryOperation::DoubleValue() const {
DCHECK(HasDoubleValue(ResolvedUnitType())) << CustomCSSText();
return Evaluate(left_side_->DoubleValue(), right_side_->DoubleValue());
}
static bool HasCanonicalUnit(CalculationCategory category) {
return category == kCalcNumber || category == kCalcLength ||
category == kCalcPercent || category == kCalcAngle ||
category == kCalcTime || category == kCalcFrequency;
}
base::Optional<double>
CSSMathExpressionBinaryOperation::ComputeValueInCanonicalUnit() const {
if (!HasCanonicalUnit(category_))
return base::nullopt;
base::Optional<double> left_value = left_side_->ComputeValueInCanonicalUnit();
if (!left_value)
return base::nullopt;
base::Optional<double> right_value =
right_side_->ComputeValueInCanonicalUnit();
if (!right_value)
return base::nullopt;
return Evaluate(*left_value, *right_value);
}
double CSSMathExpressionBinaryOperation::ComputeLengthPx(
const CSSToLengthConversionData& conversion_data) const {
DCHECK_EQ(kCalcLength, Category());
double left_value;
if (left_side_->Category() == kCalcLength) {
left_value = left_side_->ComputeLengthPx(conversion_data);
} else {
DCHECK_EQ(kCalcNumber, left_side_->Category());
left_value = left_side_->DoubleValue();
}
double right_value;
if (right_side_->Category() == kCalcLength) {
right_value = right_side_->ComputeLengthPx(conversion_data);
} else {
DCHECK_EQ(kCalcNumber, right_side_->Category());
right_value = right_side_->DoubleValue();
}
return Evaluate(left_value, right_value);
}
bool CSSMathExpressionBinaryOperation::AccumulateLengthArray(
CSSLengthArray& length_array,
double multiplier) const {
switch (operator_) {
case CSSMathOperator::kAdd:
if (!left_side_->AccumulateLengthArray(length_array, multiplier))
return false;
if (!right_side_->AccumulateLengthArray(length_array, multiplier))
return false;
return true;
case CSSMathOperator::kSubtract:
if (!left_side_->AccumulateLengthArray(length_array, multiplier))
return false;
if (!right_side_->AccumulateLengthArray(length_array, -multiplier))
return false;
return true;
case CSSMathOperator::kMultiply:
DCHECK_NE((left_side_->Category() == kCalcNumber),
(right_side_->Category() == kCalcNumber));
if (left_side_->Category() == kCalcNumber) {
return right_side_->AccumulateLengthArray(
length_array, multiplier * left_side_->DoubleValue());
} else {
return left_side_->AccumulateLengthArray(
length_array, multiplier * right_side_->DoubleValue());
}
case CSSMathOperator::kDivide:
DCHECK_EQ(right_side_->Category(), kCalcNumber);
return left_side_->AccumulateLengthArray(
length_array, multiplier / right_side_->DoubleValue());
default:
NOTREACHED();
return false;
}
}
void CSSMathExpressionBinaryOperation::AccumulateLengthUnitTypes(
CSSPrimitiveValue::LengthTypeFlags& types) const {
left_side_->AccumulateLengthUnitTypes(types);
right_side_->AccumulateLengthUnitTypes(types);
}
bool CSSMathExpressionBinaryOperation::IsComputationallyIndependent() const {
if (Category() != kCalcLength && Category() != kCalcPercentLength)
return true;
return left_side_->IsComputationallyIndependent() &&
right_side_->IsComputationallyIndependent();
}
String CSSMathExpressionBinaryOperation::CustomCSSText() const {
StringBuilder result;
const bool left_side_needs_parentheses =
left_side_->IsBinaryOperation() && operator_ != CSSMathOperator::kAdd;
if (left_side_needs_parentheses)
result.Append('(');
result.Append(left_side_->CustomCSSText());
if (left_side_needs_parentheses)
result.Append(')');
result.Append(' ');
result.Append(ToString(operator_));
result.Append(' ');
const bool right_side_needs_parentheses =
right_side_->IsBinaryOperation() && operator_ != CSSMathOperator::kAdd;
if (right_side_needs_parentheses)
result.Append('(');
result.Append(right_side_->CustomCSSText());
if (right_side_needs_parentheses)
result.Append(')');
return result.ToString();
}
bool CSSMathExpressionBinaryOperation::operator==(
const CSSMathExpressionNode& exp) const {
if (!exp.IsBinaryOperation())
return false;
const CSSMathExpressionBinaryOperation& other =
To<CSSMathExpressionBinaryOperation>(exp);
return DataEquivalent(left_side_, other.left_side_) &&
DataEquivalent(right_side_, other.right_side_) &&
operator_ == other.operator_;
}
CSSPrimitiveValue::UnitType CSSMathExpressionBinaryOperation::ResolvedUnitType()
const {
switch (category_) {
case kCalcNumber:
DCHECK_EQ(left_side_->Category(), kCalcNumber);
DCHECK_EQ(right_side_->Category(), kCalcNumber);
return CSSPrimitiveValue::UnitType::kNumber;
case kCalcLength:
case kCalcPercent: {
if (left_side_->Category() == kCalcNumber)
return right_side_->ResolvedUnitType();
if (right_side_->Category() == kCalcNumber)
return left_side_->ResolvedUnitType();
CSSPrimitiveValue::UnitType left_type = left_side_->ResolvedUnitType();
if (left_type == right_side_->ResolvedUnitType())
return left_type;
return CSSPrimitiveValue::UnitType::kUnknown;
}
case kCalcAngle:
return CSSPrimitiveValue::UnitType::kDegrees;
case kCalcTime:
return CSSPrimitiveValue::UnitType::kMilliseconds;
case kCalcFrequency:
return CSSPrimitiveValue::UnitType::kHertz;
case kCalcPercentLength:
case kCalcOther:
return CSSPrimitiveValue::UnitType::kUnknown;
}
NOTREACHED();
return CSSPrimitiveValue::UnitType::kUnknown;
}
void CSSMathExpressionBinaryOperation::Trace(Visitor* visitor) const {
visitor->Trace(left_side_);
visitor->Trace(right_side_);
CSSMathExpressionNode::Trace(visitor);
}
// static
const CSSMathExpressionNode* CSSMathExpressionBinaryOperation::GetNumberSide(
const CSSMathExpressionNode* left_side,
const CSSMathExpressionNode* right_side) {
if (left_side->Category() == kCalcNumber)
return left_side;
if (right_side->Category() == kCalcNumber)
return right_side;
return nullptr;
}
// static
double CSSMathExpressionBinaryOperation::EvaluateOperator(double left_value,
double right_value,
CSSMathOperator op) {
// Design doc for infinity and NaN: https://bit.ly/349gXjq
switch (op) {
case CSSMathOperator::kAdd:
if (RuntimeEnabledFeatures::CSSCalcInfinityAndNaNEnabled())
return left_value + right_value;
return clampTo<double>(left_value + right_value);
case CSSMathOperator::kSubtract:
if (RuntimeEnabledFeatures::CSSCalcInfinityAndNaNEnabled())
return left_value - right_value;
return clampTo<double>(left_value - right_value);
case CSSMathOperator::kMultiply:
if (RuntimeEnabledFeatures::CSSCalcInfinityAndNaNEnabled())
return left_value * right_value;
return clampTo<double>(left_value * right_value);
case CSSMathOperator::kDivide:
if (RuntimeEnabledFeatures::CSSCalcInfinityAndNaNEnabled())
return left_value / right_value;
if (right_value)
return clampTo<double>(left_value / right_value);
return std::numeric_limits<double>::quiet_NaN();
default:
NOTREACHED();
break;
}
return 0;
}
#if DCHECK_IS_ON()
bool CSSMathExpressionBinaryOperation::InvolvesPercentageComparisons() const {
return left_side_->InvolvesPercentageComparisons() ||
right_side_->InvolvesPercentageComparisons();
}
#endif
// ------ End of CSSMathExpressionBinaryOperation member functions ------
// ------ Start of CSSMathExpressionVariadicOperation member functions ------
// static
CSSMathExpressionVariadicOperation* CSSMathExpressionVariadicOperation::Create(
Operands&& operands,
CSSMathOperator op) {
DCHECK(op == CSSMathOperator::kMin || op == CSSMathOperator::kMax);
DCHECK(operands.size());
bool is_first = true;
CalculationCategory category;
bool is_integer;
for (const auto& operand : operands) {
if (is_first) {
category = operand->Category();
is_integer = operand->IsInteger();
} else {
category = kAddSubtractResult[category][operand->Category()];
if (!operand->IsInteger())
is_integer = false;
}
is_first = false;
if (category == kCalcOther)
return nullptr;
}
return MakeGarbageCollected<CSSMathExpressionVariadicOperation>(
category, is_integer, std::move(operands), op);
}
CSSMathExpressionVariadicOperation::CSSMathExpressionVariadicOperation(
CalculationCategory category,
bool is_integer_result,
Operands&& operands,
CSSMathOperator op)
: CSSMathExpressionNode(category,
is_integer_result,
true /* has_comparisons */),
operands_(std::move(operands)),
operator_(op) {}
void CSSMathExpressionVariadicOperation::Trace(Visitor* visitor) const {
visitor->Trace(operands_);
CSSMathExpressionNode::Trace(visitor);
}
bool CSSMathExpressionVariadicOperation::IsZero() const {
base::Optional<double> maybe_value = ComputeValueInCanonicalUnit();
return maybe_value && !*maybe_value;
}
double CSSMathExpressionVariadicOperation::EvaluateBinary(double lhs,
double rhs) const {
if (std::isnan(lhs) || std::isnan(rhs))
return std::numeric_limits<double>::quiet_NaN();
switch (operator_) {
case CSSMathOperator::kMin:
return std::min(lhs, rhs);
case CSSMathOperator::kMax:
return std::max(lhs, rhs);
default:
NOTREACHED();
return 0;
}
}
base::Optional<double>
CSSMathExpressionVariadicOperation::ComputeValueInCanonicalUnit() const {
base::Optional<double> first_value =
operands_.front()->ComputeValueInCanonicalUnit();
if (!first_value)
return base::nullopt;
double result = *first_value;
for (const auto& operand : SecondToLastOperands()) {
base::Optional<double> maybe_value = operand->ComputeValueInCanonicalUnit();
if (!maybe_value)
return base::nullopt;
result = EvaluateBinary(result, *maybe_value);
}
return result;
}
double CSSMathExpressionVariadicOperation::DoubleValue() const {
DCHECK(HasDoubleValue(ResolvedUnitType()));
double result = operands_.front()->DoubleValue();
for (const auto& operand : SecondToLastOperands())
result = EvaluateBinary(result, operand->DoubleValue());
return result;
}
double CSSMathExpressionVariadicOperation::ComputeLengthPx(
const CSSToLengthConversionData& data) const {
DCHECK_EQ(kCalcLength, Category());
double result = operands_.front()->ComputeLengthPx(data);
for (const auto& operand : SecondToLastOperands())
result = EvaluateBinary(result, operand->ComputeLengthPx(data));
return result;
}
String CSSMathExpressionVariadicOperation::CSSTextAsClamp() const {
DCHECK(is_clamp_);
DCHECK_EQ(CSSMathOperator::kMax, operator_);
DCHECK_EQ(2u, operands_.size());
DCHECK(operands_[1]->IsVariadicOperation());
const auto& nested = To<CSSMathExpressionVariadicOperation>(*operands_[1]);
DCHECK(!nested.is_clamp_);
DCHECK_EQ(CSSMathOperator::kMin, nested.operator_);
DCHECK_EQ(2u, nested.operands_.size());
StringBuilder result;
result.Append("clamp(");
result.Append(operands_[0]->CustomCSSText());
result.Append(", ");
result.Append(nested.operands_[0]->CustomCSSText());
result.Append(", ");
result.Append(nested.operands_[1]->CustomCSSText());
result.Append(")");
return result.ToString();
}
String CSSMathExpressionVariadicOperation::CustomCSSText() const {
if (is_clamp_)
return CSSTextAsClamp();
StringBuilder result;
result.Append(ToString(operator_));
result.Append('(');
result.Append(operands_.front()->CustomCSSText());
for (const auto& operand : SecondToLastOperands()) {
result.Append(", ");
result.Append(operand->CustomCSSText());
}
result.Append(')');
return result.ToString();
}
base::Optional<PixelsAndPercent>
CSSMathExpressionVariadicOperation::ToPixelsAndPercent(
const CSSToLengthConversionData& conversion_data) const {
return base::nullopt;
}
scoped_refptr<const CalculationExpressionNode>
CSSMathExpressionVariadicOperation::ToCalculationExpression(
const CSSToLengthConversionData& data) const {
Vector<scoped_refptr<const CalculationExpressionNode>> operands;
operands.ReserveCapacity(operands_.size());
for (const auto& operand : operands_)
operands.push_back(operand->ToCalculationExpression(data));
auto expression_type = operator_ == CSSMathOperator::kMin
? CalculationExpressionComparisonNode::Type::kMin
: CalculationExpressionComparisonNode::Type::kMax;
return CalculationExpressionComparisonNode::CreateSimplified(
std::move(operands), expression_type);
}
bool CSSMathExpressionVariadicOperation::AccumulateLengthArray(CSSLengthArray&,
double) const {
// When comparison function are involved, we can't resolve the expression into
// a length array.
// TODO(crbug.com/991672): We need a more general length interpolation
// implemetation that doesn't rely on CSSLengthArray.
return false;
}
void CSSMathExpressionVariadicOperation::AccumulateLengthUnitTypes(
CSSPrimitiveValue::LengthTypeFlags& types) const {
for (const auto& operand : operands_)
operand->AccumulateLengthUnitTypes(types);
}
bool CSSMathExpressionVariadicOperation::IsComputationallyIndependent() const {
for (const auto& operand : operands_) {
if (!operand->IsComputationallyIndependent())
return false;
}
return true;
}
bool CSSMathExpressionVariadicOperation::operator==(
const CSSMathExpressionNode& exp) const {
if (!exp.IsVariadicOperation())
return false;
const CSSMathExpressionVariadicOperation& other =
To<CSSMathExpressionVariadicOperation>(exp);
if (operator_ != other.operator_)
return false;
if (operands_.size() != other.operands_.size())
return false;
for (wtf_size_t i = 0; i < operands_.size(); ++i) {
if (!DataEquivalent(operands_[i], other.operands_[i]))
return false;
}
return true;
}
CSSPrimitiveValue::UnitType
CSSMathExpressionVariadicOperation::ResolvedUnitType() const {
if (Category() == kCalcNumber)
return CSSPrimitiveValue::UnitType::kNumber;
CSSPrimitiveValue::UnitType result = operands_.front()->ResolvedUnitType();
if (result == CSSPrimitiveValue::UnitType::kUnknown)
return CSSPrimitiveValue::UnitType::kUnknown;
for (const auto& operand : SecondToLastOperands()) {
CSSPrimitiveValue::UnitType next = operand->ResolvedUnitType();
if (next == CSSPrimitiveValue::UnitType::kUnknown || next != result)
return CSSPrimitiveValue::UnitType::kUnknown;
}
return result;
}
#if DCHECK_IS_ON()
bool CSSMathExpressionVariadicOperation::InvolvesPercentageComparisons() const {
if (Category() == kCalcPercent && operands_.size() > 1u)
return true;
for (const auto& operand : operands_) {
if (operand->InvolvesPercentageComparisons())
return true;
}
return false;
}
#endif
// ------ End of CSSMathExpressionVariadicOperation member functions
static ParseState CheckDepthAndIndex(int* depth, CSSParserTokenRange tokens) {
(*depth)++;
if (tokens.AtEnd())
return NoMoreTokens;
if (*depth > maxExpressionDepth)
return TooDeep;
return OK;
}
class CSSMathExpressionNodeParser {
STACK_ALLOCATED();
public:
CSSMathExpressionNodeParser() {}
CSSMathExpressionNode* ParseCalc(CSSParserTokenRange tokens) {
tokens.ConsumeWhitespace();
CSSMathExpressionNode* result = ParseValueExpression(tokens, 0);
if (!result || !tokens.AtEnd())
return nullptr;
return result;
}
CSSMathExpressionNode* ParseMinOrMax(CSSParserTokenRange tokens,
CSSMathOperator op,
int depth) {
DCHECK(op == CSSMathOperator::kMin || op == CSSMathOperator::kMax);
if (CheckDepthAndIndex(&depth, tokens) != OK)
return nullptr;
CSSMathExpressionVariadicOperation::Operands operands;
bool last_token_is_comma = false;
while (!tokens.AtEnd()) {
tokens.ConsumeWhitespace();
CSSMathExpressionNode* operand = ParseValueExpression(tokens, depth);
if (!operand)
return nullptr;
last_token_is_comma = false;
operands.push_back(operand);
if (!css_parsing_utils::ConsumeCommaIncludingWhitespace(tokens))
break;
last_token_is_comma = true;
}
if (operands.IsEmpty() || !tokens.AtEnd() || last_token_is_comma)
return nullptr;
return CSSMathExpressionVariadicOperation::Create(std::move(operands), op);
}
CSSMathExpressionNode* ParseClamp(CSSParserTokenRange tokens, int depth) {
if (CheckDepthAndIndex(&depth, tokens) != OK)
return nullptr;
CSSMathExpressionNode* min_operand = ParseValueExpression(tokens, depth);
if (!min_operand)
return nullptr;
if (!css_parsing_utils::ConsumeCommaIncludingWhitespace(tokens))
return nullptr;
CSSMathExpressionNode* val_operand = ParseValueExpression(tokens, depth);
if (!val_operand)
return nullptr;
if (!css_parsing_utils::ConsumeCommaIncludingWhitespace(tokens))
return nullptr;
CSSMathExpressionNode* max_operand = ParseValueExpression(tokens, depth);
if (!max_operand)
return nullptr;
if (!tokens.AtEnd())
return nullptr;
// clamp(MIN, VAL, MAX) is identical to max(MIN, min(VAL, MAX))
auto* nested = CSSMathExpressionVariadicOperation::Create(
{val_operand, max_operand}, CSSMathOperator::kMin);
if (!nested)
return nullptr;
auto* result = CSSMathExpressionVariadicOperation::Create(
{min_operand, nested}, CSSMathOperator::kMax);
if (!result)
return nullptr;
result->SetIsClamp();
return result;
}
private:
CSSMathExpressionNode* ParseValue(CSSParserTokenRange& tokens) {
CSSParserToken token = tokens.ConsumeIncludingWhitespace();
if (RuntimeEnabledFeatures::CSSCalcInfinityAndNaNEnabled()) {
if (token.Id() == CSSValueID::kInfinity) {
return CSSMathExpressionNumericLiteral::Create(
std::numeric_limits<double>::infinity(),
CSSPrimitiveValue::UnitType::kNumber, false);
}
if (token.Id() == CSSValueID::kNegativeInfinity) {
return CSSMathExpressionNumericLiteral::Create(
-std::numeric_limits<double>::infinity(),
CSSPrimitiveValue::UnitType::kNumber, false);
}
if (token.Id() == CSSValueID::kNan) {
return CSSMathExpressionNumericLiteral::Create(
std::numeric_limits<double>::quiet_NaN(),
CSSPrimitiveValue::UnitType::kNumber, false);
}
}
if (!(token.GetType() == kNumberToken ||
token.GetType() == kPercentageToken ||
token.GetType() == kDimensionToken))
return nullptr;
CSSPrimitiveValue::UnitType type = token.GetUnitType();
if (UnitCategory(type) == kCalcOther)
return nullptr;
return CSSMathExpressionNumericLiteral::Create(
CSSNumericLiteralValue::Create(token.NumericValue(), type),
token.GetNumericValueType() == kIntegerValueType);
}
CSSMathExpressionNode* ParseValueTerm(CSSParserTokenRange& tokens,
int depth) {
if (CheckDepthAndIndex(&depth, tokens) != OK)
return nullptr;
if (tokens.Peek().GetType() == kLeftParenthesisToken ||
tokens.Peek().FunctionId() == CSSValueID::kCalc) {
CSSParserTokenRange inner_range = tokens.ConsumeBlock();
tokens.ConsumeWhitespace();
inner_range.ConsumeWhitespace();
CSSMathExpressionNode* result = ParseValueExpression(inner_range, depth);
if (!result)
return nullptr;
result->SetIsNestedCalc();
return result;
}
if (tokens.Peek().GetType() == kFunctionToken) {
CSSValueID function_id = tokens.Peek().FunctionId();
CSSParserTokenRange inner_range = tokens.ConsumeBlock();
tokens.ConsumeWhitespace();
inner_range.ConsumeWhitespace();
switch (function_id) {
case CSSValueID::kMin:
return ParseMinOrMax(inner_range, CSSMathOperator::kMin, depth);
case CSSValueID::kMax:
return ParseMinOrMax(inner_range, CSSMathOperator::kMax, depth);
case CSSValueID::kClamp:
return ParseClamp(inner_range, depth);
default:
break;
}
}
return ParseValue(tokens);
}
CSSMathExpressionNode* ParseValueMultiplicativeExpression(
CSSParserTokenRange& tokens,
int depth) {
if (CheckDepthAndIndex(&depth, tokens) != OK)
return nullptr;
CSSMathExpressionNode* result = ParseValueTerm(tokens, depth);
if (!result)
return nullptr;
while (!tokens.AtEnd()) {
CSSMathOperator math_operator = ParseCSSArithmeticOperator(tokens.Peek());
if (math_operator != CSSMathOperator::kMultiply &&
math_operator != CSSMathOperator::kDivide)
break;
tokens.ConsumeIncludingWhitespace();
CSSMathExpressionNode* rhs = ParseValueTerm(tokens, depth);
if (!rhs)
return nullptr;
result = CSSMathExpressionBinaryOperation::CreateSimplified(
result, rhs, math_operator);
if (!result)
return nullptr;
}
return result;
}
CSSMathExpressionNode* ParseAdditiveValueExpression(
CSSParserTokenRange& tokens,
int depth) {
if (CheckDepthAndIndex(&depth, tokens) != OK)
return nullptr;
CSSMathExpressionNode* result =
ParseValueMultiplicativeExpression(tokens, depth);
if (!result)
return nullptr;
while (!tokens.AtEnd()) {
CSSMathOperator math_operator = ParseCSSArithmeticOperator(tokens.Peek());
if (math_operator != CSSMathOperator::kAdd &&
math_operator != CSSMathOperator::kSubtract)
break;
if ((&tokens.Peek() - 1)->GetType() != kWhitespaceToken)
return nullptr; // calc(1px+ 2px) is invalid
tokens.Consume();
if (tokens.Peek().GetType() != kWhitespaceToken)
return nullptr; // calc(1px +2px) is invalid
tokens.ConsumeIncludingWhitespace();
CSSMathExpressionNode* rhs =
ParseValueMultiplicativeExpression(tokens, depth);
if (!rhs)
return nullptr;
result = CSSMathExpressionBinaryOperation::CreateSimplified(
result, rhs, math_operator);
if (!result)
return nullptr;
}
return result;
}
CSSMathExpressionNode* ParseValueExpression(CSSParserTokenRange& tokens,
int depth) {
return ParseAdditiveValueExpression(tokens, depth);
}
};
scoped_refptr<CalculationValue> CSSMathExpressionNode::ToCalcValue(
const CSSToLengthConversionData& conversion_data,
ValueRange range,
bool allows_negative_percentage_reference) const {
if (auto maybe_pixels_and_percent = ToPixelsAndPercent(conversion_data)) {
// Clamping if pixels + percent could result in NaN. In special case,
// inf px + inf % could evaluate to nan when
// allows_negative_percentage_reference is true.
if (RuntimeEnabledFeatures::CSSCalcInfinityAndNaNEnabled()) {
if (IsNaN(*maybe_pixels_and_percent,
allows_negative_percentage_reference)) {
maybe_pixels_and_percent = CreateClampedSamePixelsAndPercent(
std::numeric_limits<float>::quiet_NaN());
} else {
maybe_pixels_and_percent->pixels = CSSValueClampingUtils::ClampLength(
maybe_pixels_and_percent->pixels);
maybe_pixels_and_percent->percent = CSSValueClampingUtils::ClampLength(
maybe_pixels_and_percent->percent);
}
}
return CalculationValue::Create(*maybe_pixels_and_percent, range);
}
auto value = ToCalculationExpression(conversion_data);
if (RuntimeEnabledFeatures::CSSCalcInfinityAndNaNEnabled()) {
base::Optional<PixelsAndPercent> evaluated_value =
EvaluateValueIfNaNorInfinity(value,
allows_negative_percentage_reference);
if (evaluated_value.has_value()) {
return CalculationValue::Create(evaluated_value.value(), range);
}
}
return CalculationValue::CreateSimplified(value, range);
}
// static
CSSMathExpressionNode* CSSMathExpressionNode::Create(
const CalculationValue& calc) {
if (calc.IsExpression())
return Create(*calc.GetOrCreateExpression());
return Create(calc.GetPixelsAndPercent());
}
// static
CSSMathExpressionNode* CSSMathExpressionNode::Create(PixelsAndPercent value) {
double percent = value.percent;
double pixels = value.pixels;
CSSMathOperator op = CSSMathOperator::kAdd;
if (pixels < 0) {
pixels = -pixels;
op = CSSMathOperator::kSubtract;
}
return CSSMathExpressionBinaryOperation::Create(
CSSMathExpressionNumericLiteral::Create(
CSSNumericLiteralValue::Create(
percent, CSSPrimitiveValue::UnitType::kPercentage),
percent == trunc(percent)),
CSSMathExpressionNumericLiteral::Create(
CSSNumericLiteralValue::Create(pixels,
CSSPrimitiveValue::UnitType::kPixels),
pixels == trunc(pixels)),
op);
}
// static
CSSMathExpressionNode* CSSMathExpressionNode::Create(
const CalculationExpressionNode& node) {
if (node.IsLeaf()) {
const auto& leaf = To<CalculationExpressionLeafNode>(node);
return Create(leaf.GetPixelsAndPercent());
}
if (node.IsMultiplication()) {
const auto& multiplication =
To<CalculationExpressionMultiplicationNode>(node);
double factor = multiplication.GetFactor();
return CSSMathExpressionBinaryOperation::Create(
Create(multiplication.GetChild()),
CSSMathExpressionNumericLiteral::Create(
CSSNumericLiteralValue::Create(
factor, CSSPrimitiveValue::UnitType::kNumber),
factor == trunc(factor)),
CSSMathOperator::kMultiply);
}
if (node.IsAdditive()) {
const auto& add_or_subtract = To<CalculationExpressionAdditiveNode>(node);
auto* lhs = Create(add_or_subtract.GetLeftSide());
auto* rhs = Create(add_or_subtract.GetRightSide());
CSSMathOperator op = add_or_subtract.IsAdd() ? CSSMathOperator::kAdd
: CSSMathOperator::kSubtract;
return CSSMathExpressionBinaryOperation::Create(lhs, rhs, op);
}
DCHECK(node.IsComparison());
const auto& comparison = To<CalculationExpressionComparisonNode>(node);
CSSMathExpressionVariadicOperation::Operands operands;
for (const auto& operand : comparison.GetOperands())
operands.push_back(Create(*operand));
CSSMathOperator op =
comparison.IsMin() ? CSSMathOperator::kMin : CSSMathOperator::kMax;
return CSSMathExpressionVariadicOperation::Create(std::move(operands), op);
}
// static
CSSMathExpressionNode* CSSMathExpressionNode::ParseCalc(
const CSSParserTokenRange& tokens) {
CSSMathExpressionNodeParser parser;
return parser.ParseCalc(tokens);
}
// static
CSSMathExpressionNode* CSSMathExpressionNode::ParseMin(
const CSSParserTokenRange& tokens) {
CSSMathExpressionNodeParser parser;
return parser.ParseMinOrMax(tokens, CSSMathOperator::kMin, 0);
}
// static
CSSMathExpressionNode* CSSMathExpressionNode::ParseMax(
const CSSParserTokenRange& tokens) {
CSSMathExpressionNodeParser parser;
return parser.ParseMinOrMax(tokens, CSSMathOperator::kMax, 0);
}
// static
CSSMathExpressionNode* CSSMathExpressionNode::ParseClamp(
const CSSParserTokenRange& tokens) {
CSSMathExpressionNodeParser parser;
return parser.ParseClamp(tokens, 0);
}
} // namespace blink