blob: e036fc13de915d25cb7b8c738729803928381ce4 [file] [log] [blame]
// Copyright 2017 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/css/cssom/css_unit_value.h"
#include "third_party/blink/renderer/core/animation/length_property_functions.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_resolution_units.h"
#include "third_party/blink/renderer/core/css/css_syntax_definition.h"
#include "third_party/blink/renderer/core/css/cssom/css_math_invert.h"
#include "third_party/blink/renderer/core/css/cssom/css_math_max.h"
#include "third_party/blink/renderer/core/css/cssom/css_math_min.h"
#include "third_party/blink/renderer/core/css/cssom/css_math_product.h"
#include "third_party/blink/renderer/core/css/cssom/css_math_sum.h"
#include "third_party/blink/renderer/core/css/cssom/css_numeric_sum_value.h"
#include "third_party/blink/renderer/core/css/properties/css_property.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/wtf/math_extras.h"
#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
namespace blink {
namespace {
CSSPrimitiveValue::UnitType ToCanonicalUnit(CSSPrimitiveValue::UnitType unit) {
return CSSPrimitiveValue::CanonicalUnitTypeForCategory(
CSSPrimitiveValue::UnitTypeToUnitCategory(unit));
}
CSSPrimitiveValue::UnitType ToCanonicalUnitIfPossible(
CSSPrimitiveValue::UnitType unit) {
const auto canonical_unit = ToCanonicalUnit(unit);
if (canonical_unit == CSSPrimitiveValue::UnitType::kUnknown)
return unit;
return canonical_unit;
}
bool IsValueOutOfRangeForProperty(CSSPropertyID property_id,
double value,
CSSPrimitiveValue::UnitType unit) {
// FIXME: Avoid this CSSProperty::Get call as it can be costly.
// The caller often has a CSSProperty already, so we can just pass it here.
if (LengthPropertyFunctions::GetValueRange(CSSProperty::Get(property_id)) ==
kValueRangeNonNegative &&
value < 0)
return true;
// For non-length properties and special cases.
switch (property_id) {
case CSSPropertyID::kOrder:
case CSSPropertyID::kZIndex:
case CSSPropertyID::kMathDepth:
return round(value) != value;
case CSSPropertyID::kTabSize:
return value < 0 || (unit == CSSPrimitiveValue::UnitType::kNumber &&
round(value) != value);
case CSSPropertyID::kOrphans:
case CSSPropertyID::kWidows:
case CSSPropertyID::kColumnCount:
return round(value) != value || value < 1;
case CSSPropertyID::kBlockSize:
case CSSPropertyID::kColumnRuleWidth:
case CSSPropertyID::kFlexGrow:
case CSSPropertyID::kFlexShrink:
case CSSPropertyID::kFontSize:
case CSSPropertyID::kFontSizeAdjust:
case CSSPropertyID::kFontStretch:
case CSSPropertyID::kInlineSize:
case CSSPropertyID::kLineHeightStep:
case CSSPropertyID::kMaxBlockSize:
case CSSPropertyID::kMaxInlineSize:
case CSSPropertyID::kMinBlockSize:
case CSSPropertyID::kMinInlineSize:
case CSSPropertyID::kR:
case CSSPropertyID::kRx:
case CSSPropertyID::kRy:
return value < 0;
case CSSPropertyID::kFontWeight:
return value < 0 || value > 1000;
default:
return false;
}
}
} // namespace
CSSUnitValue* CSSUnitValue::Create(double value,
const String& unit_name,
ExceptionState& exception_state) {
CSSPrimitiveValue::UnitType unit = UnitFromName(unit_name);
if (!IsValidUnit(unit)) {
exception_state.ThrowTypeError("Invalid unit: " + unit_name);
return nullptr;
}
return MakeGarbageCollected<CSSUnitValue>(value, unit);
}
CSSUnitValue* CSSUnitValue::Create(double value,
CSSPrimitiveValue::UnitType unit) {
DCHECK(IsValidUnit(unit));
return MakeGarbageCollected<CSSUnitValue>(value, unit);
}
CSSUnitValue* CSSUnitValue::FromCSSValue(const CSSNumericLiteralValue& value) {
CSSPrimitiveValue::UnitType unit = value.GetType();
if (unit == CSSPrimitiveValue::UnitType::kInteger)
unit = CSSPrimitiveValue::UnitType::kNumber;
if (!IsValidUnit(unit))
return nullptr;
return MakeGarbageCollected<CSSUnitValue>(value.GetDoubleValue(), unit);
}
String CSSUnitValue::unit() const {
if (unit_ == CSSPrimitiveValue::UnitType::kNumber)
return "number";
if (unit_ == CSSPrimitiveValue::UnitType::kPercentage)
return "percent";
return CSSPrimitiveValue::UnitTypeToString(unit_);
}
CSSStyleValue::StyleValueType CSSUnitValue::GetType() const {
return StyleValueType::kUnitType;
}
CSSUnitValue* CSSUnitValue::ConvertTo(
CSSPrimitiveValue::UnitType target_unit) const {
if (unit_ == target_unit)
return Create(value_, unit_);
// Instead of defining the scale factors for every unit to every other unit,
// we simply convert to the canonical unit and back since we already have
// the scale factors for canonical units.
const auto canonical_unit = ToCanonicalUnit(unit_);
if (canonical_unit != ToCanonicalUnit(target_unit) ||
canonical_unit == CSSPrimitiveValue::UnitType::kUnknown)
return nullptr;
const double scale_factor =
CSSPrimitiveValue::ConversionToCanonicalUnitsScaleFactor(unit_) /
CSSPrimitiveValue::ConversionToCanonicalUnitsScaleFactor(target_unit);
return CSSUnitValue::Create(value_ * scale_factor, target_unit);
}
base::Optional<CSSNumericSumValue> CSSUnitValue::SumValue() const {
CSSNumericSumValue sum;
CSSNumericSumValue::UnitMap unit_map;
if (unit_ != CSSPrimitiveValue::UnitType::kNumber)
unit_map.insert(ToCanonicalUnitIfPossible(unit_), 1);
sum.terms.emplace_back(
value_ * CSSPrimitiveValue::ConversionToCanonicalUnitsScaleFactor(unit_),
std::move(unit_map));
return sum;
}
bool CSSUnitValue::Equals(const CSSNumericValue& other) const {
auto* other_unit_value = DynamicTo<CSSUnitValue>(other);
if (!other_unit_value)
return false;
return value_ == other_unit_value->value_ && unit_ == other_unit_value->unit_;
}
const CSSNumericLiteralValue* CSSUnitValue::ToCSSValue() const {
return CSSNumericLiteralValue::Create(value_, unit_);
}
const CSSPrimitiveValue* CSSUnitValue::ToCSSValueWithProperty(
CSSPropertyID property_id) const {
if (IsValueOutOfRangeForProperty(property_id, value_, unit_)) {
// Wrap out of range values with a calc.
CSSMathExpressionNode* node = ToCalcExpressionNode();
node->SetIsNestedCalc();
return CSSMathFunctionValue::Create(node);
}
return CSSNumericLiteralValue::Create(value_, unit_);
}
CSSMathExpressionNode* CSSUnitValue::ToCalcExpressionNode() const {
return CSSMathExpressionNumericLiteral::Create(
CSSNumericLiteralValue::Create(value_, unit_));
}
CSSNumericValue* CSSUnitValue::Negate() {
return CSSUnitValue::Create(-value_, unit_);
}
CSSNumericValue* CSSUnitValue::Invert() {
if (unit_ == CSSPrimitiveValue::UnitType::kNumber) {
if (value_ == 0)
return nullptr;
return CSSUnitValue::Create(1.0 / value_, unit_);
}
return CSSMathInvert::Create(this);
}
void CSSUnitValue::BuildCSSText(Nested,
ParenLess,
StringBuilder& result) const {
result.Append(ToCSSValue()->CssText());
}
} // namespace blink