| /* |
| * 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 "core/css/CSSCalculationValue.h" |
| |
| #include "core/css/CSSPrimitiveValueMappings.h" |
| #include "core/css/resolver/StyleResolver.h" |
| #include "wtf/MathExtras.h" |
| #include "wtf/text/StringBuilder.h" |
| |
| static const int maxExpressionDepth = 100; |
| |
| enum ParseState { OK, TooDeep, NoMoreTokens }; |
| |
| namespace blink { |
| |
| static CalculationCategory unitCategory(CSSPrimitiveValue::UnitType type) { |
| switch (type) { |
| case CSSPrimitiveValue::UnitType::Number: |
| case CSSPrimitiveValue::UnitType::Integer: |
| return CalcNumber; |
| case CSSPrimitiveValue::UnitType::Percentage: |
| return CalcPercent; |
| case CSSPrimitiveValue::UnitType::Ems: |
| case CSSPrimitiveValue::UnitType::Exs: |
| case CSSPrimitiveValue::UnitType::Pixels: |
| case CSSPrimitiveValue::UnitType::Centimeters: |
| case CSSPrimitiveValue::UnitType::Millimeters: |
| case CSSPrimitiveValue::UnitType::Inches: |
| case CSSPrimitiveValue::UnitType::Points: |
| case CSSPrimitiveValue::UnitType::Picas: |
| case CSSPrimitiveValue::UnitType::UserUnits: |
| case CSSPrimitiveValue::UnitType::Rems: |
| case CSSPrimitiveValue::UnitType::Chs: |
| case CSSPrimitiveValue::UnitType::ViewportWidth: |
| case CSSPrimitiveValue::UnitType::ViewportHeight: |
| case CSSPrimitiveValue::UnitType::ViewportMin: |
| case CSSPrimitiveValue::UnitType::ViewportMax: |
| return CalcLength; |
| case CSSPrimitiveValue::UnitType::Degrees: |
| case CSSPrimitiveValue::UnitType::Gradians: |
| case CSSPrimitiveValue::UnitType::Radians: |
| case CSSPrimitiveValue::UnitType::Turns: |
| return CalcAngle; |
| case CSSPrimitiveValue::UnitType::Milliseconds: |
| case CSSPrimitiveValue::UnitType::Seconds: |
| return CalcTime; |
| case CSSPrimitiveValue::UnitType::Hertz: |
| case CSSPrimitiveValue::UnitType::Kilohertz: |
| return CalcFrequency; |
| default: |
| return CalcOther; |
| } |
| } |
| |
| static bool hasDoubleValue(CSSPrimitiveValue::UnitType type) { |
| switch (type) { |
| case CSSPrimitiveValue::UnitType::Number: |
| case CSSPrimitiveValue::UnitType::Percentage: |
| case CSSPrimitiveValue::UnitType::Ems: |
| case CSSPrimitiveValue::UnitType::Exs: |
| case CSSPrimitiveValue::UnitType::Chs: |
| case CSSPrimitiveValue::UnitType::Rems: |
| case CSSPrimitiveValue::UnitType::Pixels: |
| case CSSPrimitiveValue::UnitType::Centimeters: |
| case CSSPrimitiveValue::UnitType::Millimeters: |
| case CSSPrimitiveValue::UnitType::Inches: |
| case CSSPrimitiveValue::UnitType::Points: |
| case CSSPrimitiveValue::UnitType::Picas: |
| case CSSPrimitiveValue::UnitType::UserUnits: |
| case CSSPrimitiveValue::UnitType::Degrees: |
| case CSSPrimitiveValue::UnitType::Radians: |
| case CSSPrimitiveValue::UnitType::Gradians: |
| case CSSPrimitiveValue::UnitType::Turns: |
| case CSSPrimitiveValue::UnitType::Milliseconds: |
| case CSSPrimitiveValue::UnitType::Seconds: |
| case CSSPrimitiveValue::UnitType::Hertz: |
| case CSSPrimitiveValue::UnitType::Kilohertz: |
| case CSSPrimitiveValue::UnitType::ViewportWidth: |
| case CSSPrimitiveValue::UnitType::ViewportHeight: |
| case CSSPrimitiveValue::UnitType::ViewportMin: |
| case CSSPrimitiveValue::UnitType::ViewportMax: |
| case CSSPrimitiveValue::UnitType::DotsPerPixel: |
| case CSSPrimitiveValue::UnitType::DotsPerInch: |
| case CSSPrimitiveValue::UnitType::DotsPerCentimeter: |
| case CSSPrimitiveValue::UnitType::Fraction: |
| case CSSPrimitiveValue::UnitType::Integer: |
| return true; |
| case CSSPrimitiveValue::UnitType::Unknown: |
| case CSSPrimitiveValue::UnitType::Calc: |
| case CSSPrimitiveValue::UnitType::CalcPercentageWithNumber: |
| case CSSPrimitiveValue::UnitType::CalcPercentageWithLength: |
| case CSSPrimitiveValue::UnitType::CalcLengthWithNumber: |
| case CSSPrimitiveValue::UnitType::CalcPercentageWithLengthAndNumber: |
| case CSSPrimitiveValue::UnitType::QuirkyEms: |
| return false; |
| }; |
| ASSERT_NOT_REACHED(); |
| return false; |
| } |
| |
| static String buildCSSText(const String& expression) { |
| StringBuilder result; |
| result.append("calc"); |
| bool expressionHasSingleTerm = expression[0] != '('; |
| if (expressionHasSingleTerm) |
| result.append('('); |
| result.append(expression); |
| if (expressionHasSingleTerm) |
| result.append(')'); |
| return result.toString(); |
| } |
| |
| String CSSCalcValue::customCSSText() const { |
| return buildCSSText(m_expression->customCSSText()); |
| } |
| |
| bool CSSCalcValue::equals(const CSSCalcValue& other) const { |
| return compareCSSValuePtr(m_expression, other.m_expression); |
| } |
| |
| double CSSCalcValue::clampToPermittedRange(double value) const { |
| return m_nonNegative && value < 0 ? 0 : value; |
| } |
| |
| double CSSCalcValue::doubleValue() const { |
| return clampToPermittedRange(m_expression->doubleValue()); |
| } |
| |
| double CSSCalcValue::computeLengthPx( |
| const CSSToLengthConversionData& conversionData) const { |
| return clampToPermittedRange(m_expression->computeLengthPx(conversionData)); |
| } |
| |
| class CSSCalcPrimitiveValue final : public CSSCalcExpressionNode { |
| public: |
| static CSSCalcPrimitiveValue* create(CSSPrimitiveValue* value, |
| bool isInteger) { |
| return new CSSCalcPrimitiveValue(value, isInteger); |
| } |
| |
| static CSSCalcPrimitiveValue* create(double value, |
| CSSPrimitiveValue::UnitType type, |
| bool isInteger) { |
| if (std::isnan(value) || std::isinf(value)) |
| return nullptr; |
| return new CSSCalcPrimitiveValue(CSSPrimitiveValue::create(value, type), |
| isInteger); |
| } |
| |
| bool isZero() const override { return !m_value->getDoubleValue(); } |
| |
| String customCSSText() const override { return m_value->cssText(); } |
| |
| void accumulatePixelsAndPercent( |
| const CSSToLengthConversionData& conversionData, |
| PixelsAndPercent& value, |
| float multiplier) const override { |
| switch (m_category) { |
| case CalcLength: |
| value.pixels += |
| m_value->computeLength<float>(conversionData) * multiplier; |
| break; |
| case CalcPercent: |
| ASSERT(m_value->isPercentage()); |
| value.percent += m_value->getDoubleValue() * multiplier; |
| break; |
| case CalcNumber: |
| // 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 += |
| m_value->getDoubleValue() * conversionData.zoom() * multiplier; |
| break; |
| default: |
| ASSERT_NOT_REACHED(); |
| } |
| } |
| |
| double doubleValue() const override { |
| if (hasDoubleValue(typeWithCalcResolved())) |
| return m_value->getDoubleValue(); |
| ASSERT_NOT_REACHED(); |
| return 0; |
| } |
| |
| double computeLengthPx( |
| const CSSToLengthConversionData& conversionData) const override { |
| switch (m_category) { |
| case CalcLength: |
| return m_value->computeLength<double>(conversionData); |
| case CalcNumber: |
| case CalcPercent: |
| return m_value->getDoubleValue(); |
| case CalcAngle: |
| case CalcFrequency: |
| case CalcPercentLength: |
| case CalcPercentNumber: |
| case CalcTime: |
| case CalcLengthNumber: |
| case CalcPercentLengthNumber: |
| case CalcOther: |
| ASSERT_NOT_REACHED(); |
| break; |
| } |
| ASSERT_NOT_REACHED(); |
| return 0; |
| } |
| |
| void accumulateLengthArray(CSSLengthArray& lengthArray, |
| double multiplier) const override { |
| ASSERT(category() != CalcNumber); |
| m_value->accumulateLengthArray(lengthArray, multiplier); |
| } |
| |
| bool equals(const CSSCalcExpressionNode& other) const override { |
| if (getType() != other.getType()) |
| return false; |
| |
| return compareCSSValuePtr( |
| m_value, static_cast<const CSSCalcPrimitiveValue&>(other).m_value); |
| } |
| |
| Type getType() const override { return CssCalcPrimitiveValue; } |
| CSSPrimitiveValue::UnitType typeWithCalcResolved() const override { |
| return m_value->typeWithCalcResolved(); |
| } |
| |
| DEFINE_INLINE_VIRTUAL_TRACE() { |
| visitor->trace(m_value); |
| CSSCalcExpressionNode::trace(visitor); |
| } |
| |
| private: |
| CSSCalcPrimitiveValue(CSSPrimitiveValue* value, bool isInteger) |
| : CSSCalcExpressionNode(unitCategory(value->typeWithCalcResolved()), |
| isInteger), |
| m_value(value) {} |
| |
| Member<CSSPrimitiveValue> m_value; |
| }; |
| |
| static const CalculationCategory addSubtractResult[CalcOther][CalcOther] = { |
| /* CalcNumber */ {CalcNumber, CalcLengthNumber, CalcPercentNumber, |
| CalcPercentNumber, CalcOther, CalcOther, CalcOther, |
| CalcOther, CalcLengthNumber, CalcPercentLengthNumber}, |
| /* CalcLength */ {CalcLengthNumber, CalcLength, CalcPercentLength, |
| CalcOther, CalcPercentLength, CalcOther, CalcOther, |
| CalcOther, CalcLengthNumber, CalcPercentLengthNumber}, |
| /* CalcPercent */ {CalcPercentNumber, CalcPercentLength, CalcPercent, |
| CalcPercentNumber, CalcPercentLength, CalcOther, |
| CalcOther, CalcOther, CalcPercentLengthNumber, |
| CalcPercentLengthNumber}, |
| /* CalcPercentNumber */ {CalcPercentNumber, CalcPercentLengthNumber, |
| CalcPercentNumber, CalcPercentNumber, |
| CalcPercentLengthNumber, CalcOther, CalcOther, |
| CalcOther, CalcOther, CalcPercentLengthNumber}, |
| /* CalcPercentLength */ {CalcPercentLengthNumber, CalcPercentLength, |
| CalcPercentLength, CalcPercentLengthNumber, |
| CalcPercentLength, CalcOther, CalcOther, CalcOther, |
| CalcOther, CalcPercentLengthNumber}, |
| /* CalcAngle */ {CalcOther, CalcOther, CalcOther, CalcOther, CalcOther, |
| CalcAngle, CalcOther, CalcOther, CalcOther, CalcOther}, |
| /* CalcTime */ {CalcOther, CalcOther, CalcOther, CalcOther, CalcOther, |
| CalcOther, CalcTime, CalcOther, CalcOther, CalcOther}, |
| /* CalcFrequency */ {CalcOther, CalcOther, CalcOther, CalcOther, CalcOther, |
| CalcOther, CalcOther, CalcFrequency, CalcOther, |
| CalcOther}, |
| /* CalcLengthNumber */ {CalcLengthNumber, CalcLengthNumber, |
| CalcPercentLengthNumber, CalcPercentLengthNumber, |
| CalcPercentLengthNumber, CalcOther, CalcOther, |
| CalcOther, CalcLengthNumber, |
| CalcPercentLengthNumber}, |
| /* CalcPercentLengthNumber */ { |
| CalcPercentLengthNumber, CalcPercentLengthNumber, |
| CalcPercentLengthNumber, CalcPercentLengthNumber, |
| CalcPercentLengthNumber, CalcOther, CalcOther, CalcOther, |
| CalcPercentLengthNumber, CalcPercentLengthNumber}}; |
| |
| static CalculationCategory determineCategory( |
| const CSSCalcExpressionNode& leftSide, |
| const CSSCalcExpressionNode& rightSide, |
| CalcOperator op) { |
| CalculationCategory leftCategory = leftSide.category(); |
| CalculationCategory rightCategory = rightSide.category(); |
| |
| if (leftCategory == CalcOther || rightCategory == CalcOther) |
| return CalcOther; |
| |
| switch (op) { |
| case CalcAdd: |
| case CalcSubtract: |
| return addSubtractResult[leftCategory][rightCategory]; |
| case CalcMultiply: |
| if (leftCategory != CalcNumber && rightCategory != CalcNumber) |
| return CalcOther; |
| return leftCategory == CalcNumber ? rightCategory : leftCategory; |
| case CalcDivide: |
| if (rightCategory != CalcNumber || rightSide.isZero()) |
| return CalcOther; |
| return leftCategory; |
| } |
| |
| ASSERT_NOT_REACHED(); |
| return CalcOther; |
| } |
| |
| static bool isIntegerResult(const CSSCalcExpressionNode* leftSide, |
| const CSSCalcExpressionNode* rightSide, |
| CalcOperator 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 != CalcDivide && leftSide->isInteger() && rightSide->isInteger(); |
| } |
| |
| class CSSCalcBinaryOperation final : public CSSCalcExpressionNode { |
| public: |
| static CSSCalcExpressionNode* create(CSSCalcExpressionNode* leftSide, |
| CSSCalcExpressionNode* rightSide, |
| CalcOperator op) { |
| ASSERT(leftSide->category() != CalcOther && |
| rightSide->category() != CalcOther); |
| |
| CalculationCategory newCategory = |
| determineCategory(*leftSide, *rightSide, op); |
| if (newCategory == CalcOther) |
| return nullptr; |
| |
| return new CSSCalcBinaryOperation(leftSide, rightSide, op, newCategory); |
| } |
| |
| static CSSCalcExpressionNode* createSimplified( |
| CSSCalcExpressionNode* leftSide, |
| CSSCalcExpressionNode* rightSide, |
| CalcOperator op) { |
| CalculationCategory leftCategory = leftSide->category(); |
| CalculationCategory rightCategory = rightSide->category(); |
| ASSERT(leftCategory != CalcOther && rightCategory != CalcOther); |
| |
| bool isInteger = isIntegerResult(leftSide, rightSide, op); |
| |
| // Simplify numbers. |
| if (leftCategory == CalcNumber && rightCategory == CalcNumber) { |
| return CSSCalcPrimitiveValue::create( |
| evaluateOperator(leftSide->doubleValue(), rightSide->doubleValue(), |
| op), |
| CSSPrimitiveValue::UnitType::Number, isInteger); |
| } |
| |
| // Simplify addition and subtraction between same types. |
| if (op == CalcAdd || op == CalcSubtract) { |
| if (leftCategory == rightSide->category()) { |
| CSSPrimitiveValue::UnitType leftType = leftSide->typeWithCalcResolved(); |
| if (hasDoubleValue(leftType)) { |
| CSSPrimitiveValue::UnitType rightType = |
| rightSide->typeWithCalcResolved(); |
| if (leftType == rightType) |
| return CSSCalcPrimitiveValue::create( |
| evaluateOperator(leftSide->doubleValue(), |
| rightSide->doubleValue(), op), |
| leftType, isInteger); |
| CSSPrimitiveValue::UnitCategory leftUnitCategory = |
| CSSPrimitiveValue::unitTypeToUnitCategory(leftType); |
| if (leftUnitCategory != CSSPrimitiveValue::UOther && |
| leftUnitCategory == |
| CSSPrimitiveValue::unitTypeToUnitCategory(rightType)) { |
| CSSPrimitiveValue::UnitType canonicalType = |
| CSSPrimitiveValue::canonicalUnitTypeForCategory( |
| leftUnitCategory); |
| if (canonicalType != CSSPrimitiveValue::UnitType::Unknown) { |
| double leftValue = |
| leftSide->doubleValue() * |
| CSSPrimitiveValue::conversionToCanonicalUnitsScaleFactor( |
| leftType); |
| double rightValue = |
| rightSide->doubleValue() * |
| CSSPrimitiveValue::conversionToCanonicalUnitsScaleFactor( |
| rightType); |
| return CSSCalcPrimitiveValue::create( |
| evaluateOperator(leftValue, rightValue, op), canonicalType, |
| isInteger); |
| } |
| } |
| } |
| } |
| } else { |
| // Simplify multiplying or dividing by a number for simplifiable types. |
| ASSERT(op == CalcMultiply || op == CalcDivide); |
| CSSCalcExpressionNode* numberSide = getNumberSide(leftSide, rightSide); |
| if (!numberSide) |
| return create(leftSide, rightSide, op); |
| if (numberSide == leftSide && op == CalcDivide) |
| return nullptr; |
| CSSCalcExpressionNode* otherSide = |
| leftSide == numberSide ? rightSide : leftSide; |
| |
| double number = numberSide->doubleValue(); |
| if (std::isnan(number) || std::isinf(number)) |
| return nullptr; |
| if (op == CalcDivide && !number) |
| return nullptr; |
| |
| CSSPrimitiveValue::UnitType otherType = otherSide->typeWithCalcResolved(); |
| if (hasDoubleValue(otherType)) |
| return CSSCalcPrimitiveValue::create( |
| evaluateOperator(otherSide->doubleValue(), number, op), otherType, |
| isInteger); |
| } |
| |
| return create(leftSide, rightSide, op); |
| } |
| |
| bool isZero() const override { return !doubleValue(); } |
| |
| void accumulatePixelsAndPercent( |
| const CSSToLengthConversionData& conversionData, |
| PixelsAndPercent& value, |
| float multiplier) const override { |
| switch (m_operator) { |
| case CalcAdd: |
| m_leftSide->accumulatePixelsAndPercent(conversionData, value, |
| multiplier); |
| m_rightSide->accumulatePixelsAndPercent(conversionData, value, |
| multiplier); |
| break; |
| case CalcSubtract: |
| m_leftSide->accumulatePixelsAndPercent(conversionData, value, |
| multiplier); |
| m_rightSide->accumulatePixelsAndPercent(conversionData, value, |
| -multiplier); |
| break; |
| case CalcMultiply: |
| ASSERT((m_leftSide->category() == CalcNumber) != |
| (m_rightSide->category() == CalcNumber)); |
| if (m_leftSide->category() == CalcNumber) |
| m_rightSide->accumulatePixelsAndPercent( |
| conversionData, value, multiplier * m_leftSide->doubleValue()); |
| else |
| m_leftSide->accumulatePixelsAndPercent( |
| conversionData, value, multiplier * m_rightSide->doubleValue()); |
| break; |
| case CalcDivide: |
| ASSERT(m_rightSide->category() == CalcNumber); |
| m_leftSide->accumulatePixelsAndPercent( |
| conversionData, value, multiplier / m_rightSide->doubleValue()); |
| break; |
| default: |
| ASSERT_NOT_REACHED(); |
| } |
| } |
| |
| double doubleValue() const override { |
| return evaluate(m_leftSide->doubleValue(), m_rightSide->doubleValue()); |
| } |
| |
| double computeLengthPx( |
| const CSSToLengthConversionData& conversionData) const override { |
| const double leftValue = m_leftSide->computeLengthPx(conversionData); |
| const double rightValue = m_rightSide->computeLengthPx(conversionData); |
| return evaluate(leftValue, rightValue); |
| } |
| |
| void accumulateLengthArray(CSSLengthArray& lengthArray, |
| double multiplier) const override { |
| switch (m_operator) { |
| case CalcAdd: |
| m_leftSide->accumulateLengthArray(lengthArray, multiplier); |
| m_rightSide->accumulateLengthArray(lengthArray, multiplier); |
| break; |
| case CalcSubtract: |
| m_leftSide->accumulateLengthArray(lengthArray, multiplier); |
| m_rightSide->accumulateLengthArray(lengthArray, -multiplier); |
| break; |
| case CalcMultiply: |
| ASSERT((m_leftSide->category() == CalcNumber) != |
| (m_rightSide->category() == CalcNumber)); |
| if (m_leftSide->category() == CalcNumber) |
| m_rightSide->accumulateLengthArray( |
| lengthArray, multiplier * m_leftSide->doubleValue()); |
| else |
| m_leftSide->accumulateLengthArray( |
| lengthArray, multiplier * m_rightSide->doubleValue()); |
| break; |
| case CalcDivide: |
| ASSERT(m_rightSide->category() == CalcNumber); |
| m_leftSide->accumulateLengthArray( |
| lengthArray, multiplier / m_rightSide->doubleValue()); |
| break; |
| default: |
| ASSERT_NOT_REACHED(); |
| } |
| } |
| |
| static String buildCSSText(const String& leftExpression, |
| const String& rightExpression, |
| CalcOperator op) { |
| StringBuilder result; |
| result.append('('); |
| result.append(leftExpression); |
| result.append(' '); |
| result.append(static_cast<char>(op)); |
| result.append(' '); |
| result.append(rightExpression); |
| result.append(')'); |
| |
| return result.toString(); |
| } |
| |
| String customCSSText() const override { |
| return buildCSSText(m_leftSide->customCSSText(), |
| m_rightSide->customCSSText(), m_operator); |
| } |
| |
| bool equals(const CSSCalcExpressionNode& exp) const override { |
| if (getType() != exp.getType()) |
| return false; |
| |
| const CSSCalcBinaryOperation& other = |
| static_cast<const CSSCalcBinaryOperation&>(exp); |
| return compareCSSValuePtr(m_leftSide, other.m_leftSide) && |
| compareCSSValuePtr(m_rightSide, other.m_rightSide) && |
| m_operator == other.m_operator; |
| } |
| |
| Type getType() const override { return CssCalcBinaryOperation; } |
| |
| CSSPrimitiveValue::UnitType typeWithCalcResolved() const override { |
| switch (m_category) { |
| case CalcNumber: |
| ASSERT(m_leftSide->category() == CalcNumber && |
| m_rightSide->category() == CalcNumber); |
| return CSSPrimitiveValue::UnitType::Number; |
| case CalcLength: |
| case CalcPercent: { |
| if (m_leftSide->category() == CalcNumber) |
| return m_rightSide->typeWithCalcResolved(); |
| if (m_rightSide->category() == CalcNumber) |
| return m_leftSide->typeWithCalcResolved(); |
| CSSPrimitiveValue::UnitType leftType = |
| m_leftSide->typeWithCalcResolved(); |
| if (leftType == m_rightSide->typeWithCalcResolved()) |
| return leftType; |
| return CSSPrimitiveValue::UnitType::Unknown; |
| } |
| case CalcAngle: |
| return CSSPrimitiveValue::UnitType::Degrees; |
| case CalcTime: |
| return CSSPrimitiveValue::UnitType::Milliseconds; |
| case CalcFrequency: |
| return CSSPrimitiveValue::UnitType::Hertz; |
| case CalcPercentLength: |
| case CalcPercentNumber: |
| case CalcLengthNumber: |
| case CalcPercentLengthNumber: |
| case CalcOther: |
| return CSSPrimitiveValue::UnitType::Unknown; |
| } |
| ASSERT_NOT_REACHED(); |
| return CSSPrimitiveValue::UnitType::Unknown; |
| } |
| |
| DEFINE_INLINE_VIRTUAL_TRACE() { |
| visitor->trace(m_leftSide); |
| visitor->trace(m_rightSide); |
| CSSCalcExpressionNode::trace(visitor); |
| } |
| |
| private: |
| CSSCalcBinaryOperation(CSSCalcExpressionNode* leftSide, |
| CSSCalcExpressionNode* rightSide, |
| CalcOperator op, |
| CalculationCategory category) |
| : CSSCalcExpressionNode(category, |
| isIntegerResult(leftSide, rightSide, op)), |
| m_leftSide(leftSide), |
| m_rightSide(rightSide), |
| m_operator(op) {} |
| |
| static CSSCalcExpressionNode* getNumberSide( |
| CSSCalcExpressionNode* leftSide, |
| CSSCalcExpressionNode* rightSide) { |
| if (leftSide->category() == CalcNumber) |
| return leftSide; |
| if (rightSide->category() == CalcNumber) |
| return rightSide; |
| return nullptr; |
| } |
| |
| double evaluate(double leftSide, double rightSide) const { |
| return evaluateOperator(leftSide, rightSide, m_operator); |
| } |
| |
| static double evaluateOperator(double leftValue, |
| double rightValue, |
| CalcOperator op) { |
| switch (op) { |
| case CalcAdd: |
| return leftValue + rightValue; |
| case CalcSubtract: |
| return leftValue - rightValue; |
| case CalcMultiply: |
| return leftValue * rightValue; |
| case CalcDivide: |
| if (rightValue) |
| return leftValue / rightValue; |
| return std::numeric_limits<double>::quiet_NaN(); |
| } |
| return 0; |
| } |
| |
| const Member<CSSCalcExpressionNode> m_leftSide; |
| const Member<CSSCalcExpressionNode> m_rightSide; |
| const CalcOperator m_operator; |
| }; |
| |
| static ParseState checkDepthAndIndex(int* depth, CSSParserTokenRange tokens) { |
| (*depth)++; |
| if (tokens.atEnd()) |
| return NoMoreTokens; |
| if (*depth > maxExpressionDepth) |
| return TooDeep; |
| return OK; |
| } |
| |
| class CSSCalcExpressionNodeParser { |
| STACK_ALLOCATED(); |
| |
| public: |
| CSSCalcExpressionNode* parseCalc(CSSParserTokenRange tokens) { |
| Value result; |
| tokens.consumeWhitespace(); |
| bool ok = parseValueExpression(tokens, 0, &result); |
| if (!ok || !tokens.atEnd()) |
| return nullptr; |
| return result.value; |
| } |
| |
| private: |
| struct Value { |
| STACK_ALLOCATED(); |
| |
| public: |
| Member<CSSCalcExpressionNode> value; |
| }; |
| |
| char operatorValue(const CSSParserToken& token) { |
| if (token.type() == DelimiterToken) |
| return token.delimiter(); |
| return 0; |
| } |
| |
| bool parseValue(CSSParserTokenRange& tokens, Value* result) { |
| CSSParserToken token = tokens.consumeIncludingWhitespace(); |
| if (!(token.type() == NumberToken || token.type() == PercentageToken || |
| token.type() == DimensionToken)) |
| return false; |
| |
| CSSPrimitiveValue::UnitType type = token.unitType(); |
| if (unitCategory(type) == CalcOther) |
| return false; |
| |
| result->value = CSSCalcPrimitiveValue::create( |
| CSSPrimitiveValue::create(token.numericValue(), type), |
| token.numericValueType() == IntegerValueType); |
| |
| return true; |
| } |
| |
| bool parseValueTerm(CSSParserTokenRange& tokens, int depth, Value* result) { |
| if (checkDepthAndIndex(&depth, tokens) != OK) |
| return false; |
| |
| if (tokens.peek().type() == LeftParenthesisToken || |
| tokens.peek().functionId() == CSSValueCalc) { |
| CSSParserTokenRange innerRange = tokens.consumeBlock(); |
| tokens.consumeWhitespace(); |
| innerRange.consumeWhitespace(); |
| return parseValueExpression(innerRange, depth, result); |
| } |
| |
| return parseValue(tokens, result); |
| } |
| |
| bool parseValueMultiplicativeExpression(CSSParserTokenRange& tokens, |
| int depth, |
| Value* result) { |
| if (checkDepthAndIndex(&depth, tokens) != OK) |
| return false; |
| |
| if (!parseValueTerm(tokens, depth, result)) |
| return false; |
| |
| while (!tokens.atEnd()) { |
| char operatorCharacter = operatorValue(tokens.peek()); |
| if (operatorCharacter != CalcMultiply && operatorCharacter != CalcDivide) |
| break; |
| tokens.consumeIncludingWhitespace(); |
| |
| Value rhs; |
| if (!parseValueTerm(tokens, depth, &rhs)) |
| return false; |
| |
| result->value = CSSCalcBinaryOperation::createSimplified( |
| result->value, rhs.value, |
| static_cast<CalcOperator>(operatorCharacter)); |
| if (!result->value) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool parseAdditiveValueExpression(CSSParserTokenRange& tokens, |
| int depth, |
| Value* result) { |
| if (checkDepthAndIndex(&depth, tokens) != OK) |
| return false; |
| |
| if (!parseValueMultiplicativeExpression(tokens, depth, result)) |
| return false; |
| |
| while (!tokens.atEnd()) { |
| char operatorCharacter = operatorValue(tokens.peek()); |
| if (operatorCharacter != CalcAdd && operatorCharacter != CalcSubtract) |
| break; |
| if ((&tokens.peek() - 1)->type() != WhitespaceToken) |
| return false; // calc(1px+ 2px) is invalid |
| tokens.consume(); |
| if (tokens.peek().type() != WhitespaceToken) |
| return false; // calc(1px +2px) is invalid |
| tokens.consumeIncludingWhitespace(); |
| |
| Value rhs; |
| if (!parseValueMultiplicativeExpression(tokens, depth, &rhs)) |
| return false; |
| |
| result->value = CSSCalcBinaryOperation::createSimplified( |
| result->value, rhs.value, |
| static_cast<CalcOperator>(operatorCharacter)); |
| if (!result->value) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool parseValueExpression(CSSParserTokenRange& tokens, |
| int depth, |
| Value* result) { |
| return parseAdditiveValueExpression(tokens, depth, result); |
| } |
| }; |
| |
| CSSCalcExpressionNode* CSSCalcValue::createExpressionNode( |
| CSSPrimitiveValue* value, |
| bool isInteger) { |
| return CSSCalcPrimitiveValue::create(value, isInteger); |
| } |
| |
| CSSCalcExpressionNode* CSSCalcValue::createExpressionNode( |
| CSSCalcExpressionNode* leftSide, |
| CSSCalcExpressionNode* rightSide, |
| CalcOperator op) { |
| return CSSCalcBinaryOperation::create(leftSide, rightSide, op); |
| } |
| |
| CSSCalcExpressionNode* CSSCalcValue::createExpressionNode(double pixels, |
| double percent) { |
| return createExpressionNode( |
| createExpressionNode(CSSPrimitiveValue::create( |
| pixels, CSSPrimitiveValue::UnitType::Pixels), |
| pixels == trunc(pixels)), |
| createExpressionNode( |
| CSSPrimitiveValue::create(percent, |
| CSSPrimitiveValue::UnitType::Percentage), |
| percent == trunc(percent)), |
| CalcAdd); |
| } |
| |
| CSSCalcValue* CSSCalcValue::create(const CSSParserTokenRange& tokens, |
| ValueRange range) { |
| CSSCalcExpressionNodeParser parser; |
| CSSCalcExpressionNode* expression = parser.parseCalc(tokens); |
| |
| return expression ? new CSSCalcValue(expression, range) : nullptr; |
| } |
| |
| CSSCalcValue* CSSCalcValue::create(CSSCalcExpressionNode* expression, |
| ValueRange range) { |
| return new CSSCalcValue(expression, range); |
| } |
| |
| } // namespace blink |