| /* |
| * Copyright (C) 2021 Apple 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: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. 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. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS 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 APPLE INC. OR ITS 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 "config.h" |
| #include "CSSCalcOperationNode.h" |
| |
| #include "CSSCalcCategoryMapping.h" |
| #include "CSSCalcInvertNode.h" |
| #include "CSSCalcNegateNode.h" |
| #include "CSSCalcPrimitiveValueNode.h" |
| #include "CSSCalcValue.h" |
| #include "CSSPrimitiveValue.h" |
| #include "CSSUnits.h" |
| #include "CalcExpressionOperation.h" |
| #include "Logging.h" |
| #include <wtf/text/TextStream.h> |
| |
| namespace WebCore { |
| |
| // This is the result of the "To add two types type1 and type2, perform the following steps:" rules. |
| |
| static const CalculationCategory addSubtractResult[static_cast<unsigned>(CalculationCategory::Angle)][static_cast<unsigned>(CalculationCategory::Angle)] = { |
| // CalculationCategory::Number CalculationCategory::Length CalculationCategory::Percent CalculationCategory::PercentNumber CalculationCategory::PercentLength |
| { CalculationCategory::Number, CalculationCategory::Other, CalculationCategory::PercentNumber, CalculationCategory::PercentNumber, CalculationCategory::Other }, // CalculationCategory::Number |
| { CalculationCategory::Other, CalculationCategory::Length, CalculationCategory::PercentLength, CalculationCategory::Other, CalculationCategory::PercentLength }, // CalculationCategory::Length |
| { CalculationCategory::PercentNumber, CalculationCategory::PercentLength, CalculationCategory::Percent, CalculationCategory::PercentNumber, CalculationCategory::PercentLength }, // CalculationCategory::Percent |
| { CalculationCategory::PercentNumber, CalculationCategory::Other, CalculationCategory::PercentNumber, CalculationCategory::PercentNumber, CalculationCategory::Other }, // CalculationCategory::PercentNumber |
| { CalculationCategory::Other, CalculationCategory::PercentLength, CalculationCategory::PercentLength, CalculationCategory::Other, CalculationCategory::PercentLength }, // CalculationCategory::PercentLength |
| }; |
| |
| static CalculationCategory determineCategory(const CSSCalcExpressionNode& leftSide, const CSSCalcExpressionNode& rightSide, CalcOperator op) |
| { |
| CalculationCategory leftCategory = leftSide.category(); |
| CalculationCategory rightCategory = rightSide.category(); |
| ASSERT(leftCategory < CalculationCategory::Other); |
| ASSERT(rightCategory < CalculationCategory::Other); |
| |
| switch (op) { |
| case CalcOperator::Add: |
| case CalcOperator::Subtract: |
| if (leftCategory < CalculationCategory::Angle && rightCategory < CalculationCategory::Angle) |
| return addSubtractResult[static_cast<unsigned>(leftCategory)][static_cast<unsigned>(rightCategory)]; |
| if (leftCategory == rightCategory) |
| return leftCategory; |
| return CalculationCategory::Other; |
| case CalcOperator::Multiply: |
| if (leftCategory != CalculationCategory::Number && rightCategory != CalculationCategory::Number) |
| return CalculationCategory::Other; |
| return leftCategory == CalculationCategory::Number ? rightCategory : leftCategory; |
| case CalcOperator::Divide: |
| if (rightCategory != CalculationCategory::Number) |
| return CalculationCategory::Other; |
| return leftCategory; |
| case CalcOperator::Sin: |
| case CalcOperator::Cos: |
| case CalcOperator::Tan: |
| case CalcOperator::Min: |
| case CalcOperator::Max: |
| case CalcOperator::Clamp: |
| case CalcOperator::Log: |
| case CalcOperator::Exp: |
| case CalcOperator::Asin: |
| case CalcOperator::Acos: |
| case CalcOperator::Atan: |
| case CalcOperator::Atan2: |
| case CalcOperator::Abs: |
| case CalcOperator::Sign: |
| case CalcOperator::Mod: |
| case CalcOperator::Rem: |
| case CalcOperator::Round: |
| case CalcOperator::Up: |
| case CalcOperator::Down: |
| case CalcOperator::Nearest: |
| case CalcOperator::ToZero: |
| case CalcOperator::Pow: |
| case CalcOperator::Sqrt: |
| case CalcOperator::Hypot: |
| ASSERT_NOT_REACHED(); |
| return CalculationCategory::Other; |
| } |
| |
| ASSERT_NOT_REACHED(); |
| return CalculationCategory::Other; |
| } |
| |
| // FIXME: Need to implement correct category computation per: |
| // <https://drafts.css-houdini.org/css-typed-om-1/#cssnumericvalue-invert-a-type> |
| // To invert a type type, perform the following steps: |
| // Let result be a new type with an initially empty ordered map and an initially null percent hint |
| // For each unit → exponent of type, set result[unit] to (-1 * exponent). |
| static CalculationCategory categoryForInvert(CalculationCategory category) |
| { |
| return category; |
| } |
| |
| static CalculationCategory determineCategory(const Vector<Ref<CSSCalcExpressionNode>>& nodes, CalcOperator op) |
| { |
| if (nodes.isEmpty()) |
| return CalculationCategory::Other; |
| |
| auto currentCategory = nodes[0]->category(); |
| |
| for (unsigned i = 1; i < nodes.size(); ++i) { |
| const auto& node = nodes[i].get(); |
| |
| auto usedOperator = op; |
| if (node.type() == CSSCalcExpressionNode::Type::CssCalcInvert) |
| usedOperator = CalcOperator::Divide; |
| |
| auto nextCategory = node.category(); |
| |
| switch (usedOperator) { |
| case CalcOperator::Add: |
| case CalcOperator::Subtract: |
| // <https://drafts.css-houdini.org/css-typed-om-1/#cssnumericvalue-add-two-types> |
| // At a + or - sub-expression, attempt to add the types of the left and right arguments. |
| // If this returns failure, the entire calculation’s type is failure. Otherwise, the sub-expression’s type is the returned type. |
| if (currentCategory < CalculationCategory::Angle && nextCategory < CalculationCategory::Angle) |
| currentCategory = addSubtractResult[static_cast<unsigned>(currentCategory)][static_cast<unsigned>(nextCategory)]; |
| else if (currentCategory != nextCategory) |
| return CalculationCategory::Other; |
| break; |
| |
| case CalcOperator::Multiply: |
| // <https://drafts.css-houdini.org/css-typed-om-1/#cssnumericvalue-multiply-two-types> |
| // At a * sub-expression, multiply the types of the left and right arguments. The sub-expression’s type is the returned result. |
| if (currentCategory != CalculationCategory::Number && nextCategory != CalculationCategory::Number) |
| return CalculationCategory::Other; |
| |
| currentCategory = currentCategory == CalculationCategory::Number ? nextCategory : currentCategory; |
| break; |
| |
| case CalcOperator::Divide: { |
| auto invertCategory = categoryForInvert(nextCategory); |
| |
| // At a / sub-expression, let left type be the result of finding the types of its left argument, |
| // and right type be the result of finding the types of its right argument and then inverting it. |
| // The sub-expression’s type is the result of multiplying the left type and right type. |
| if (invertCategory != CalculationCategory::Number) |
| return CalculationCategory::Other; |
| break; |
| } |
| case CalcOperator::Sin: |
| case CalcOperator::Cos: |
| case CalcOperator::Tan: |
| case CalcOperator::Abs: |
| case CalcOperator::Sign: |
| case CalcOperator::Min: |
| case CalcOperator::Max: |
| case CalcOperator::Clamp: |
| case CalcOperator::Log: |
| case CalcOperator::Exp: |
| case CalcOperator::Asin: |
| case CalcOperator::Acos: |
| case CalcOperator::Atan: |
| case CalcOperator::Atan2: |
| case CalcOperator::Mod: |
| case CalcOperator::Rem: |
| case CalcOperator::Round: |
| case CalcOperator::Up: |
| case CalcOperator::Down: |
| case CalcOperator::Nearest: |
| case CalcOperator::ToZero: |
| case CalcOperator::Hypot: |
| return CalculationCategory::Other; |
| case CalcOperator::Pow: |
| case CalcOperator::Sqrt: |
| // The type of pow() and sqrt() functions must evaluate to a number. |
| return CalculationCategory::Number; |
| } |
| } |
| |
| return currentCategory; |
| } |
| |
| static CalculationCategory resolvedTypeForMinOrMaxOrClamp(CalculationCategory category, CalculationCategory destinationCategory) |
| { |
| switch (category) { |
| case CalculationCategory::Number: |
| case CalculationCategory::Length: |
| case CalculationCategory::PercentNumber: |
| case CalculationCategory::PercentLength: |
| case CalculationCategory::Angle: |
| case CalculationCategory::Time: |
| case CalculationCategory::Frequency: |
| case CalculationCategory::Other: |
| return category; |
| |
| case CalculationCategory::Percent: |
| if (destinationCategory == CalculationCategory::Length) |
| return CalculationCategory::PercentLength; |
| if (destinationCategory == CalculationCategory::Number) |
| return CalculationCategory::PercentNumber; |
| return category; |
| } |
| |
| return CalculationCategory::Other; |
| } |
| |
| static bool isSamePair(CalculationCategory a, CalculationCategory b, CalculationCategory x, CalculationCategory y) |
| { |
| return (a == x && b == y) || (a == y && b == x); |
| } |
| |
| enum class SortingCategory { |
| Number, |
| Percent, |
| Dimension, |
| Other |
| }; |
| |
| static SortingCategory sortingCategoryForType(CSSUnitType unitType) |
| { |
| static constexpr SortingCategory sortOrder[] = { |
| SortingCategory::Number, // CalculationCategory::Number, |
| SortingCategory::Dimension, // CalculationCategory::Length, |
| SortingCategory::Percent, // CalculationCategory::Percent, |
| SortingCategory::Number, // CalculationCategory::PercentNumber, |
| SortingCategory::Dimension, // CalculationCategory::PercentLength, |
| SortingCategory::Dimension, // CalculationCategory::Angle, |
| SortingCategory::Dimension, // CalculationCategory::Time, |
| SortingCategory::Dimension, // CalculationCategory::Frequency, |
| SortingCategory::Other, // UOther |
| }; |
| |
| COMPILE_ASSERT(ARRAY_SIZE(sortOrder) == static_cast<unsigned>(CalculationCategory::Other) + 1, sortOrder_size_should_match_UnitCategory); |
| return sortOrder[static_cast<unsigned>(calcUnitCategory(unitType))]; |
| } |
| |
| static SortingCategory sortingCategory(const CSSCalcExpressionNode& node) |
| { |
| if (is<CSSCalcPrimitiveValueNode>(node)) |
| return sortingCategoryForType(node.primitiveType()); |
| |
| return SortingCategory::Other; |
| } |
| |
| static CSSUnitType primitiveTypeForCombination(const CSSCalcExpressionNode& node) |
| { |
| if (is<CSSCalcPrimitiveValueNode>(node)) |
| return node.primitiveType(); |
| |
| return CSSUnitType::CSS_UNKNOWN; |
| } |
| |
| static CSSCalcPrimitiveValueNode::UnitConversion conversionToAddValuesWithTypes(CSSUnitType firstType, CSSUnitType secondType) |
| { |
| if (firstType == CSSUnitType::CSS_UNKNOWN || secondType == CSSUnitType::CSS_UNKNOWN) |
| return CSSCalcPrimitiveValueNode::UnitConversion::Invalid; |
| |
| auto firstCategory = calculationCategoryForCombination(firstType); |
| |
| // Compatible types. |
| if (firstCategory != CalculationCategory::Other && firstCategory == calculationCategoryForCombination(secondType)) |
| return CSSCalcPrimitiveValueNode::UnitConversion::Canonicalize; |
| |
| // Matching types. |
| if (firstType == secondType && hasDoubleValue(firstType)) |
| return CSSCalcPrimitiveValueNode::UnitConversion::Preserve; |
| |
| return CSSCalcPrimitiveValueNode::UnitConversion::Invalid; |
| } |
| |
| static CSSValueID functionFromOperator(CalcOperator op) |
| { |
| switch (op) { |
| case CalcOperator::Add: |
| case CalcOperator::Subtract: |
| case CalcOperator::Multiply: |
| case CalcOperator::Divide: |
| return CSSValueCalc; |
| case CalcOperator::Min: |
| return CSSValueMin; |
| case CalcOperator::Max: |
| return CSSValueMax; |
| case CalcOperator::Clamp: |
| return CSSValueClamp; |
| case CalcOperator::Pow: |
| return CSSValuePow; |
| case CalcOperator::Sqrt: |
| return CSSValueSqrt; |
| case CalcOperator::Hypot: |
| return CSSValueHypot; |
| case CalcOperator::Sin: |
| return CSSValueSin; |
| case CalcOperator::Cos: |
| return CSSValueCos; |
| case CalcOperator::Tan: |
| return CSSValueTan; |
| case CalcOperator::Exp: |
| return CSSValueExp; |
| case CalcOperator::Log: |
| return CSSValueLog; |
| case CalcOperator::Asin: |
| return CSSValueAsin; |
| case CalcOperator::Acos: |
| return CSSValueAcos; |
| case CalcOperator::Atan: |
| return CSSValueAtan; |
| case CalcOperator::Atan2: |
| return CSSValueAtan2; |
| case CalcOperator::Abs: |
| return CSSValueAbs; |
| case CalcOperator::Sign: |
| return CSSValueSign; |
| case CalcOperator::Mod: |
| return CSSValueMod; |
| case CalcOperator::Rem: |
| return CSSValueRem; |
| case CalcOperator::Round: |
| return CSSValueRound; |
| case CalcOperator::Up: |
| return CSSValueUp; |
| case CalcOperator::Down: |
| return CSSValueDown; |
| case CalcOperator::Nearest: |
| return CSSValueNearest; |
| case CalcOperator::ToZero: |
| return CSSValueToZero; |
| } |
| return CSSValueCalc; |
| } |
| |
| static std::optional<CalculationCategory> commonCategory(const Vector<Ref<CSSCalcExpressionNode>>& values) |
| { |
| if (values.isEmpty()) |
| return std::nullopt; |
| |
| auto expectedCategory = values[0]->category(); |
| for (size_t i = 1; i < values.size(); ++i) { |
| if (values[i]->category() != expectedCategory) |
| return std::nullopt; |
| } |
| |
| return expectedCategory; |
| } |
| |
| RefPtr<CSSCalcOperationNode> CSSCalcOperationNode::create(CalcOperator op, RefPtr<CSSCalcExpressionNode>&& leftSide, RefPtr<CSSCalcExpressionNode>&& rightSide) |
| { |
| if (!leftSide || !rightSide) |
| return nullptr; |
| |
| ASSERT(op == CalcOperator::Add || op == CalcOperator::Multiply); |
| |
| ASSERT(leftSide->category() < CalculationCategory::Other); |
| ASSERT(rightSide->category() < CalculationCategory::Other); |
| |
| auto newCategory = determineCategory(*leftSide, *rightSide, op); |
| if (newCategory == CalculationCategory::Other) { |
| LOG_WITH_STREAM(Calc, stream << "Failed to create CSSCalcOperationNode " << op << " node because unable to determine category from " << prettyPrintNode(*leftSide) << " and " << *rightSide); |
| return nullptr; |
| } |
| |
| return adoptRef(new CSSCalcOperationNode(newCategory, op, leftSide.releaseNonNull(), rightSide.releaseNonNull())); |
| } |
| |
| RefPtr<CSSCalcOperationNode> CSSCalcOperationNode::createSum(Vector<Ref<CSSCalcExpressionNode>>&& values) |
| { |
| if (values.isEmpty()) |
| return nullptr; |
| |
| auto newCategory = determineCategory(values, CalcOperator::Add); |
| if (newCategory == CalculationCategory::Other) { |
| LOG_WITH_STREAM(Calc, stream << "Failed to create sum node because unable to determine category from " << prettyPrintNodes(values)); |
| newCategory = determineCategory(values, CalcOperator::Add); |
| return nullptr; |
| } |
| |
| return adoptRef(new CSSCalcOperationNode(newCategory, CalcOperator::Add, WTFMove(values))); |
| } |
| |
| RefPtr<CSSCalcOperationNode> CSSCalcOperationNode::createInverseTrig(CalcOperator op, Vector<Ref<CSSCalcExpressionNode>>&& values) |
| { |
| if (values.size() != 1) |
| return nullptr; |
| |
| auto childCategory = values[0]->category(); |
| if (childCategory != CalculationCategory::Number) { |
| LOG_WITH_STREAM(Calc, stream << "Failed to create trig node because unable to determine category from " << prettyPrintNodes(values)); |
| return nullptr; |
| } |
| |
| return adoptRef(new CSSCalcOperationNode(CalculationCategory::Angle, op, WTFMove(values))); |
| } |
| |
| RefPtr<CSSCalcOperationNode> CSSCalcOperationNode::createAtan2(Vector<Ref<CSSCalcExpressionNode>>&& values) |
| { |
| if (values.size() != 2) |
| return nullptr; |
| |
| auto child1Category = values[0]->category(); |
| auto child2Category = values[1]->category(); |
| if (child1Category != child2Category) { |
| LOG_WITH_STREAM(Calc, stream << "Failed to create atan2 node because unable to determine category from " << prettyPrintNodes(values)); |
| return nullptr; |
| } |
| return adoptRef(new CSSCalcOperationNode(CalculationCategory::Angle, CalcOperator::Atan2, WTFMove(values))); |
| } |
| |
| RefPtr<CSSCalcOperationNode> CSSCalcOperationNode::createProduct(Vector<Ref<CSSCalcExpressionNode>>&& values) |
| { |
| if (values.isEmpty()) |
| return nullptr; |
| |
| auto newCategory = determineCategory(values, CalcOperator::Multiply); |
| if (newCategory == CalculationCategory::Other) { |
| LOG_WITH_STREAM(Calc, stream << "Failed to create product node because unable to determine category from " << prettyPrintNodes(values)); |
| return nullptr; |
| } |
| |
| return adoptRef(new CSSCalcOperationNode(newCategory, CalcOperator::Multiply, WTFMove(values))); |
| } |
| |
| RefPtr<CSSCalcOperationNode> CSSCalcOperationNode::createLog(Vector<Ref<CSSCalcExpressionNode>>&& values) |
| { |
| if (values.size() != 1 && values.size() != 2) |
| return nullptr; |
| for (auto& value : values) { |
| if (value->category() != CalculationCategory::Number) { |
| LOG_WITH_STREAM(Calc, stream << "Failed to create log node because unable to determine category from " << prettyPrintNodes(values)); |
| return nullptr; |
| } |
| } |
| |
| return adoptRef(new CSSCalcOperationNode(CalculationCategory::Number, CalcOperator::Log, WTFMove(values))); |
| } |
| |
| RefPtr<CSSCalcOperationNode> CSSCalcOperationNode::createExp(Vector<Ref<CSSCalcExpressionNode>>&& values) |
| { |
| if (values.size() != 1) |
| return nullptr; |
| |
| if (values[0]->category() != CalculationCategory::Number) { |
| LOG_WITH_STREAM(Calc, stream << "Failed to create exp node because unable to determine category from " << prettyPrintNodes(values)); |
| return nullptr; |
| } |
| |
| return adoptRef(new CSSCalcOperationNode(CalculationCategory::Number, CalcOperator::Exp, WTFMove(values))); |
| } |
| |
| RefPtr<CSSCalcOperationNode> CSSCalcOperationNode::createPowOrSqrt(CalcOperator op, Vector<Ref<CSSCalcExpressionNode>>&& values) |
| { |
| if (op == CalcOperator::Pow && values.size() != 2) |
| return nullptr; |
| |
| if (op == CalcOperator::Sqrt && values.size() != 1) |
| return nullptr; |
| |
| if (commonCategory(values) != CalculationCategory::Number) { |
| LOG_WITH_STREAM(Calc, stream << "Failed to create " << op << "node because unable to determine category from " << prettyPrintNodes(values)); |
| return nullptr; |
| } |
| |
| return adoptRef(new CSSCalcOperationNode(CalculationCategory::Number, op, WTFMove(values))); |
| } |
| |
| RefPtr<CSSCalcOperationNode> CSSCalcOperationNode::createHypot(Vector<Ref<CSSCalcExpressionNode>>&& values) |
| { |
| auto expectedCategory = commonCategory(values); |
| |
| if (expectedCategory == CalculationCategory::Other) { |
| LOG_WITH_STREAM(Calc, stream << "Failed to create hypot node because unable to determine category from " << prettyPrintNodes(values)); |
| return nullptr; |
| } |
| |
| return adoptRef(new CSSCalcOperationNode(*expectedCategory, CalcOperator::Hypot, WTFMove(values))); |
| } |
| |
| RefPtr<CSSCalcOperationNode> CSSCalcOperationNode::createMinOrMaxOrClamp(CalcOperator op, Vector<Ref<CSSCalcExpressionNode>>&& values, CalculationCategory destinationCategory) |
| { |
| ASSERT(op == CalcOperator::Min || op == CalcOperator::Max || op == CalcOperator::Clamp); |
| ASSERT_IMPLIES(op == CalcOperator::Clamp, values.size() == 3); |
| |
| std::optional<CalculationCategory> category = std::nullopt; |
| for (auto& value : values) { |
| auto valueCategory = resolvedTypeForMinOrMaxOrClamp(value->category(), destinationCategory); |
| |
| ASSERT(valueCategory < CalculationCategory::Other); |
| if (!category) { |
| if (valueCategory == CalculationCategory::Other) { |
| LOG_WITH_STREAM(Calc, stream << "Failed to create CSSCalcOperationNode " << op << " node because unable to determine category from " << prettyPrintNodes(values)); |
| return nullptr; |
| } |
| category = valueCategory; |
| } |
| |
| if (category != valueCategory) { |
| if (isSamePair(category.value(), valueCategory, CalculationCategory::Length, CalculationCategory::PercentLength)) { |
| category = CalculationCategory::PercentLength; |
| continue; |
| } |
| if (isSamePair(category.value(), valueCategory, CalculationCategory::Number, CalculationCategory::PercentNumber)) { |
| category = CalculationCategory::PercentNumber; |
| continue; |
| } |
| return nullptr; |
| } |
| } |
| |
| return adoptRef(new CSSCalcOperationNode(category.value(), op, WTFMove(values))); |
| } |
| |
| RefPtr<CSSCalcOperationNode> CSSCalcOperationNode::createTrig(CalcOperator op, Vector<Ref<CSSCalcExpressionNode>>&& values) |
| { |
| if (values.size() != 1) |
| return nullptr; |
| |
| auto childCategory = values[0]->category(); |
| if (childCategory != CalculationCategory::Number && childCategory != CalculationCategory::Angle) { |
| LOG_WITH_STREAM(Calc, stream << "Failed to create trig node because unable to determine category from " << prettyPrintNodes(values)); |
| return nullptr; |
| } |
| |
| return adoptRef(new CSSCalcOperationNode(CalculationCategory::Number, op, WTFMove(values))); |
| } |
| |
| RefPtr<CSSCalcOperationNode> CSSCalcOperationNode::createSign(CalcOperator op, Vector<Ref<CSSCalcExpressionNode>>&& values) |
| { |
| if (values.size() != 1) |
| return nullptr; |
| auto newCategory = determineCategory(values, op); |
| if (op == CalcOperator::Sign) |
| newCategory = CalculationCategory::Number; |
| |
| if (newCategory == CalculationCategory::Other) { |
| LOG_WITH_STREAM(Calc, stream << "Failed to create sign-related node because unable to determine category from " << prettyPrintNodes(values)); |
| return nullptr; |
| } |
| return adoptRef(new CSSCalcOperationNode(newCategory, op, WTFMove(values))); |
| } |
| |
| RefPtr<CSSCalcOperationNode> CSSCalcOperationNode::createStep(CalcOperator op, Vector<Ref<CSSCalcExpressionNode>>&& values) |
| { |
| if (values.size() != 2) |
| return nullptr; |
| |
| if (values[0]->category() != values[1]->category()) { |
| LOG_WITH_STREAM(Calc, stream << "Failed to create stepped value node because unable to determine category from " << prettyPrintNodes(values)); |
| return nullptr; |
| } |
| return adoptRef(new CSSCalcOperationNode(values[0]->category(), op, WTFMove(values))); |
| } |
| |
| static bool validateRoundChildren(Vector<Ref<CSSCalcExpressionNode>>& values) |
| { |
| // for 3 children 1st node must be round constant |
| if (values.size() == 3) { |
| if (!is<CSSCalcOperationNode>(values[0]) || !(downcast<CSSCalcOperationNode>(values[0].get()).isRoundOperation())) |
| return false; |
| } |
| // for 2 children should not have round constant anywhere but first node of 3 |
| for (size_t i = values.size() == 2 ? 0 : 1; i < values.size(); i++) { |
| if (is<CSSCalcOperationNode>(values[i])) { |
| if (downcast<CSSCalcOperationNode>(values[i].get()).isRoundConstant()) |
| return false; |
| } |
| } |
| // check that two categories of numerical values are the same |
| return values.rbegin()[1]->category() == values.rbegin()[0]->category(); |
| |
| } |
| |
| RefPtr<CSSCalcOperationNode> CSSCalcOperationNode::createRound(Vector<Ref<CSSCalcExpressionNode>>&& values) |
| { |
| if (values.size() != 2 && values.size() != 3) |
| return nullptr; |
| |
| if (!validateRoundChildren(values)) { |
| LOG_WITH_STREAM(Calc, stream << "Failed to create round node because unable to determine category from " << prettyPrintNodes(values)); |
| return nullptr; |
| } |
| |
| CalcOperator roundType = values.size() == 2 ? CalcOperator::Nearest : downcast<CSSCalcOperationNode>(values[0].get()).calcOperator(); |
| if (values.size() == 3) |
| values.remove(0); |
| return adoptRef(new CSSCalcOperationNode(values.rbegin()[0]->category(), roundType, WTFMove(values))); |
| } |
| |
| RefPtr<CSSCalcOperationNode> CSSCalcOperationNode::createRoundConstant(CalcOperator op) |
| { |
| return adoptRef(new CSSCalcOperationNode(CalculationCategory::Number, op, { })); |
| } |
| |
| void CSSCalcOperationNode::hoistChildrenWithOperator(CalcOperator op) |
| { |
| ASSERT(op == CalcOperator::Add || op == CalcOperator::Multiply); |
| |
| auto hasChildWithOperator = [&] (CalcOperator op) { |
| for (auto& child : m_children) { |
| if (is<CSSCalcOperationNode>(child) && downcast<CSSCalcOperationNode>(child.get()).calcOperator() == op) |
| return true; |
| } |
| return false; |
| }; |
| |
| if (!hasChildWithOperator(op)) |
| return; |
| |
| Vector<Ref<CSSCalcExpressionNode>> newChildren; |
| for (auto& child : m_children) { |
| if (is<CSSCalcOperationNode>(child) && downcast<CSSCalcOperationNode>(child.get()).calcOperator() == op) { |
| auto& children = downcast<CSSCalcOperationNode>(child.get()).children(); |
| for (auto& childToMove : children) |
| newChildren.append(WTFMove(childToMove)); |
| } else |
| newChildren.append(WTFMove(child)); |
| } |
| |
| newChildren.shrinkToFit(); |
| m_children = WTFMove(newChildren); |
| } |
| |
| bool CSSCalcOperationNode::canCombineAllChildren() const |
| { |
| if (isIdentity() || !m_children.size()) |
| return false; |
| |
| if (!is<CSSCalcPrimitiveValueNode>(m_children[0])) |
| return false; |
| |
| auto firstUnitType = m_children[0]->primitiveType(); |
| auto firstCategory = calculationCategoryForCombination(m_children[0]->primitiveType()); |
| |
| for (unsigned i = 1; i < m_children.size(); ++i) { |
| auto& node = m_children[i]; |
| |
| if (!is<CSSCalcPrimitiveValueNode>(node)) |
| return false; |
| |
| auto nodeUnitType = node->primitiveType(); |
| auto nodeCategory = calculationCategoryForCombination(nodeUnitType); |
| |
| if (nodeCategory != firstCategory) |
| return false; |
| |
| if (nodeCategory == CalculationCategory::Other && nodeUnitType != firstUnitType) |
| return false; |
| |
| if (!hasDoubleValue(nodeUnitType)) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void CSSCalcOperationNode::combineChildren() |
| { |
| if (isIdentity() || !m_children.size()) |
| return; |
| |
| if (m_children.size() < 2) { |
| if (m_children.size() == 1 && isTrigNode()) { |
| double resolvedValue = doubleValue(m_children[0]->primitiveType()); |
| auto newChild = CSSCalcPrimitiveValueNode::create(CSSPrimitiveValue::create(resolvedValue, CSSUnitType::CSS_NUMBER)); |
| m_children.clear(); |
| m_children.append(WTFMove(newChild)); |
| } |
| |
| if (isExpNode()) { |
| double resolvedValue = doubleValue(m_children[0]->primitiveType()); |
| auto newChild = CSSCalcPrimitiveValueNode::create(CSSPrimitiveValue::create(resolvedValue, CSSUnitType::CSS_NUMBER)); |
| m_children.clear(); |
| m_children.append(WTFMove(newChild)); |
| } |
| if (m_children.size() == 1 && isInverseTrigNode()) { |
| double resolvedValue = doubleValue(m_children[0]->primitiveType()); |
| auto newChild = CSSCalcPrimitiveValueNode::create(CSSPrimitiveValue::create(resolvedValue, CSSUnitType::CSS_DEG)); |
| m_children.clear(); |
| m_children.append(WTFMove(newChild)); |
| } |
| if (isSignNode() || isHypotNode()) { |
| auto combinedUnitType = m_children[0]->primitiveType(); |
| if (calcOperator() == CalcOperator::Sign) |
| combinedUnitType = CSSUnitType::CSS_NUMBER; |
| double resolvedValue = doubleValue(m_children[0]->primitiveType()); |
| auto newChild = CSSCalcPrimitiveValueNode::create(CSSPrimitiveValue::create(resolvedValue, combinedUnitType)); |
| m_children.clear(); |
| m_children.append(WTFMove(newChild)); |
| } |
| if (calcOperator() == CalcOperator::Sqrt) { |
| double resolvedValue = doubleValue(m_children[0]->primitiveType()); |
| auto newChild = CSSCalcPrimitiveValueNode::create(CSSPrimitiveValue::create(resolvedValue, CSSUnitType::CSS_NUMBER)); |
| m_children.clear(); |
| m_children.append(WTFMove(newChild)); |
| } |
| return; |
| } |
| |
| if (shouldSortChildren()) { |
| // <https://drafts.csswg.org/css-values-4/#sort-a-calculations-children> |
| std::stable_sort(m_children.begin(), m_children.end(), [](const auto& first, const auto& second) { |
| // Sort order: number, percentage, dimension, other. |
| SortingCategory firstCategory = sortingCategory(first.get()); |
| SortingCategory secondCategory = sortingCategory(second.get()); |
| |
| if (firstCategory == SortingCategory::Dimension && secondCategory == SortingCategory::Dimension) { |
| // If nodes contains any dimensions, remove them from nodes, sort them by their units, and append them to ret. |
| auto firstUnitString = CSSPrimitiveValue::unitTypeString(first->primitiveType()); |
| auto secondUnitString = CSSPrimitiveValue::unitTypeString(second->primitiveType()); |
| return codePointCompareLessThan(firstUnitString, secondUnitString); |
| } |
| |
| return static_cast<unsigned>(firstCategory) < static_cast<unsigned>(secondCategory); |
| }); |
| |
| LOG_WITH_STREAM(Calc, stream << "post-sort: " << *this); |
| } |
| |
| if (calcOperator() == CalcOperator::Add) { |
| // For each set of root’s children that are numeric values with identical units, |
| // remove those children and replace them with a single numeric value containing |
| // the sum of the removed nodes, and with the same unit. |
| Vector<Ref<CSSCalcExpressionNode>> newChildren; |
| newChildren.reserveInitialCapacity(m_children.size()); |
| newChildren.uncheckedAppend(m_children[0].copyRef()); |
| |
| CSSUnitType previousType = primitiveTypeForCombination(newChildren[0].get()); |
| |
| for (unsigned i = 1; i < m_children.size(); ++i) { |
| auto& currentNode = m_children[i]; |
| CSSUnitType currentType = primitiveTypeForCombination(currentNode.get()); |
| |
| auto conversionType = conversionToAddValuesWithTypes(previousType, currentType); |
| if (conversionType != CSSCalcPrimitiveValueNode::UnitConversion::Invalid) { |
| downcast<CSSCalcPrimitiveValueNode>(newChildren.last().get()).add(downcast<CSSCalcPrimitiveValueNode>(currentNode.get()), conversionType); |
| continue; |
| } |
| |
| previousType = primitiveTypeForCombination(currentNode); |
| newChildren.uncheckedAppend(currentNode.copyRef()); |
| } |
| |
| newChildren.shrinkToFit(); |
| m_children = WTFMove(newChildren); |
| return; |
| } |
| |
| if (calcOperator() == CalcOperator::Multiply) { |
| // If root has multiple children that are numbers (not percentages or dimensions), |
| // remove them and replace them with a single number containing the product of the removed nodes. |
| double multiplier = 1; |
| |
| // Sorting will have put the number nodes first. |
| unsigned leadingNumberNodeCount = 0; |
| for (auto& node : m_children) { |
| auto nodeType = primitiveTypeForCombination(node.get()); |
| if (nodeType != CSSUnitType::CSS_NUMBER) |
| break; |
| |
| multiplier *= node->doubleValue(CSSUnitType::CSS_NUMBER); |
| ++leadingNumberNodeCount; |
| } |
| |
| Vector<Ref<CSSCalcExpressionNode>> newChildren; |
| newChildren.reserveInitialCapacity(m_children.size()); |
| |
| // If root contains only two children, one of which is a number (not a percentage or dimension) and the other of |
| // which is a Sum whose children are all numeric values, multiply all of the Sum’s children by the number, then |
| // return the Sum. |
| // The Sum's children simplification will have happened already. |
| bool didMultiply = false; |
| if (leadingNumberNodeCount && m_children.size() - leadingNumberNodeCount == 1) { |
| auto multiplicandCategory = calcUnitCategory(primitiveTypeForCombination(m_children.last().get())); |
| if (multiplicandCategory != CalculationCategory::Other) { |
| newChildren.uncheckedAppend(m_children.last().copyRef()); |
| downcast<CSSCalcPrimitiveValueNode>(newChildren[0].get()).multiply(multiplier); |
| didMultiply = true; |
| } else if (is<CSSCalcOperationNode>(m_children.last()) && downcast<CSSCalcOperationNode>(m_children.last().get()).calcOperator() == CalcOperator::Add) { |
| // If we're multiplying with another operation that is an addition and all the added children |
| // are percentages or dimensions, we should multiply each child and make this expression an |
| // addition. |
| auto allChildrenArePrimitiveValues = [](const Vector<Ref<CSSCalcExpressionNode>>& children) -> bool |
| { |
| for (auto& child : children) { |
| if (!is<CSSCalcPrimitiveValueNode>(child)) |
| return false; |
| } |
| return true; |
| }; |
| |
| auto& children = downcast<CSSCalcOperationNode>(m_children.last().get()).children(); |
| if (allChildrenArePrimitiveValues(children)) { |
| for (auto& child : children) { |
| newChildren.append(child.copyRef()); |
| downcast<CSSCalcPrimitiveValueNode>(newChildren.last().get()).multiply(multiplier); |
| } |
| m_operator = CalcOperator::Add; |
| didMultiply = true; |
| } |
| } |
| } |
| |
| if (!didMultiply) { |
| if (leadingNumberNodeCount) { |
| auto multiplierNode = CSSCalcPrimitiveValueNode::create(CSSPrimitiveValue::create(multiplier, CSSUnitType::CSS_NUMBER)); |
| newChildren.uncheckedAppend(WTFMove(multiplierNode)); |
| } |
| |
| for (unsigned i = leadingNumberNodeCount; i < m_children.size(); ++i) |
| newChildren.uncheckedAppend(m_children[i].copyRef()); |
| } |
| |
| newChildren.shrinkToFit(); |
| m_children = WTFMove(newChildren); |
| } |
| |
| if ((isMinOrMaxNode() || isHypotNode()) && canCombineAllChildren()) { |
| auto combinedUnitType = m_children[0]->primitiveType(); |
| auto category = calculationCategoryForCombination(combinedUnitType); |
| if (category != CalculationCategory::Other) |
| combinedUnitType = canonicalUnitTypeForCalculationCategory(category); |
| |
| double resolvedValue = doubleValue(combinedUnitType); |
| auto newChild = CSSCalcPrimitiveValueNode::create(CSSPrimitiveValue::create(resolvedValue, combinedUnitType)); |
| |
| m_children.clear(); |
| m_children.append(WTFMove(newChild)); |
| } |
| |
| if (calcOperator() == CalcOperator::Pow) { |
| auto resolvedValue = doubleValue(m_children[0]->primitiveType()); |
| auto newChild = CSSCalcPrimitiveValueNode::create(CSSPrimitiveValue::create(resolvedValue, CSSUnitType::CSS_NUMBER)); |
| m_children.clear(); |
| m_children.append(WTFMove(newChild)); |
| } |
| |
| if (calcOperator() == CalcOperator::Atan2) { |
| double resolvedValue = doubleValue(m_children[0]->primitiveType()); |
| auto newChild = CSSCalcPrimitiveValueNode::create(CSSPrimitiveValue::create(resolvedValue, CSSUnitType::CSS_DEG)); |
| m_children.clear(); |
| m_children.append(WTFMove(newChild)); |
| } |
| if (isSteppedNode()) { |
| auto combinedUnitType = m_children[0]->primitiveType(); |
| double resolvedValue = doubleValue(combinedUnitType); |
| auto newChild = CSSCalcPrimitiveValueNode::create(CSSPrimitiveValue::create(resolvedValue, combinedUnitType)); |
| m_children.clear(); |
| m_children.append(WTFMove(newChild)); |
| } |
| if (isRoundOperation()) { |
| auto combinedUnitType = m_children[0]->primitiveType(); |
| double resolvedValue = doubleValue(combinedUnitType); |
| auto newChild = CSSCalcPrimitiveValueNode::create(CSSPrimitiveValue::create(resolvedValue, combinedUnitType)); |
| m_children.clear(); |
| m_children.append(WTFMove(newChild)); |
| } |
| } |
| |
| // https://drafts.csswg.org/css-values-4/#simplify-a-calculation-tree |
| |
| Ref<CSSCalcExpressionNode> CSSCalcOperationNode::simplify(Ref<CSSCalcExpressionNode>&& rootNode) |
| { |
| return simplifyRecursive(WTFMove(rootNode), 0); |
| } |
| |
| Ref<CSSCalcExpressionNode> CSSCalcOperationNode::simplifyRecursive(Ref<CSSCalcExpressionNode>&& rootNode, int depth) |
| { |
| if (is<CSSCalcOperationNode>(rootNode)) { |
| auto& operationNode = downcast<CSSCalcOperationNode>(rootNode.get()); |
| |
| auto& children = operationNode.children(); |
| for (unsigned i = 0; i < children.size(); ++i) { |
| auto child = children[i].copyRef(); |
| auto newNode = simplifyRecursive(WTFMove(child), depth + 1); |
| if (newNode.ptr() != children[i].ptr()) |
| children[i] = WTFMove(newNode); |
| } |
| } else if (is<CSSCalcNegateNode>(rootNode)) { |
| auto& negateNode = downcast<CSSCalcNegateNode>(rootNode.get()); |
| Ref<CSSCalcExpressionNode> child = negateNode.child(); |
| auto newNode = simplifyRecursive(WTFMove(child), depth + 1); |
| if (newNode.ptr() != &negateNode.child()) |
| negateNode.setChild(WTFMove(newNode)); |
| } else if (is<CSSCalcInvertNode>(rootNode)) { |
| auto& invertNode = downcast<CSSCalcInvertNode>(rootNode.get()); |
| Ref<CSSCalcExpressionNode> child = invertNode.child(); |
| auto newNode = simplifyRecursive(WTFMove(child), depth + 1); |
| if (newNode.ptr() != &invertNode.child()) |
| invertNode.setChild(WTFMove(newNode)); |
| } |
| |
| return simplifyNode(WTFMove(rootNode), depth); |
| } |
| |
| Ref<CSSCalcExpressionNode> CSSCalcOperationNode::simplifyNode(Ref<CSSCalcExpressionNode>&& rootNode, int depth) |
| { |
| if (is<CSSCalcPrimitiveValueNode>(rootNode)) { |
| // If root is a percentage that will be resolved against another value, and there is enough information |
| // available to resolve it, do so, and express the resulting numeric value in the appropriate canonical |
| // unit. Return the value. |
| |
| // If root is a dimension that is not expressed in its canonical unit, and there is enough information |
| // available to convert it to the canonical unit, do so, and return the value. |
| auto& primitiveValueNode = downcast<CSSCalcPrimitiveValueNode>(rootNode.get()); |
| primitiveValueNode.canonicalizeUnit(); |
| return WTFMove(rootNode); |
| } |
| |
| // If root is an operator node that’s not one of the calc-operator nodes, and all of its children are numeric values |
| // with enough information to computed the operation root represents, return the result of running root’s operation |
| // using its children, expressed in the result’s canonical unit. |
| if (is<CSSCalcOperationNode>(rootNode)) { |
| auto& calcOperationNode = downcast<CSSCalcOperationNode>(rootNode.get()); |
| // Identity nodes have only one child and perform no operation on their child. |
| if (calcOperationNode.isIdentity() && depth) |
| return WTFMove(calcOperationNode.children()[0]); |
| |
| if (calcOperationNode.isCalcSumNode()) { |
| calcOperationNode.hoistChildrenWithOperator(CalcOperator::Add); |
| calcOperationNode.combineChildren(); |
| } |
| |
| if (calcOperationNode.isCalcProductNode()) { |
| calcOperationNode.hoistChildrenWithOperator(CalcOperator::Multiply); |
| calcOperationNode.combineChildren(); |
| } |
| |
| if (calcOperationNode.isMinOrMaxNode()) |
| calcOperationNode.combineChildren(); |
| |
| if (calcOperationNode.isTrigNode() && depth) |
| calcOperationNode.combineChildren(); |
| |
| if (calcOperationNode.isExpNode() && depth) |
| calcOperationNode.combineChildren(); |
| |
| if (calcOperationNode.isInverseTrigNode() && depth) |
| calcOperationNode.combineChildren(); |
| |
| if (calcOperationNode.isAtan2Node() && depth) |
| calcOperationNode.combineChildren(); |
| if (calcOperationNode.isSignNode() && depth) |
| calcOperationNode.combineChildren(); |
| |
| if (calcOperationNode.isSteppedNode() && depth) |
| calcOperationNode.combineChildren(); |
| |
| if (calcOperationNode.isRoundOperation() && depth) |
| calcOperationNode.combineChildren(); |
| |
| if (calcOperationNode.isHypotNode()) |
| calcOperationNode.combineChildren(); |
| |
| if (calcOperationNode.isPowOrSqrtNode() && depth) |
| calcOperationNode.combineChildren(); |
| |
| // If only one child remains, return the child (except at the root). |
| auto shouldCombineParentWithOnlyChild = [](const CSSCalcOperationNode& parent, int depth) |
| { |
| if (parent.children().size() != 1) |
| return false; |
| |
| // Always simplify below the root. |
| if (depth) |
| return true; |
| |
| // At the root, preserve the root function by only merging nodes with the same function. |
| auto& child = parent.children().first(); |
| if (!is<CSSCalcOperationNode>(child)) |
| return false; |
| |
| auto parentFunction = functionFromOperator(parent.calcOperator()); |
| auto childFunction = functionFromOperator(downcast<CSSCalcOperationNode>(child.get()).calcOperator()); |
| return childFunction == parentFunction; |
| }; |
| |
| if (shouldCombineParentWithOnlyChild(calcOperationNode, depth)) |
| return WTFMove(calcOperationNode.children().first()); |
| |
| return WTFMove(rootNode); |
| } |
| |
| if (is<CSSCalcNegateNode>(rootNode)) { |
| auto& childNode = downcast<CSSCalcNegateNode>(rootNode.get()).child(); |
| // If root’s child is a numeric value, return an equivalent numeric value, but with the value negated (0 - value). |
| if (is<CSSCalcPrimitiveValueNode>(childNode) && downcast<CSSCalcPrimitiveValueNode>(childNode).isNumericValue()) { |
| downcast<CSSCalcPrimitiveValueNode>(childNode).negate(); |
| return childNode; |
| } |
| |
| // If root’s child is a Negate node, return the child’s child. |
| if (is<CSSCalcNegateNode>(childNode)) |
| return downcast<CSSCalcNegateNode>(childNode).child(); |
| |
| return WTFMove(rootNode); |
| } |
| |
| if (is<CSSCalcInvertNode>(rootNode)) { |
| auto& childNode = downcast<CSSCalcInvertNode>(rootNode.get()).child(); |
| // If root’s child is a number (not a percentage or dimension) return the reciprocal of the child’s value. |
| if (is<CSSCalcPrimitiveValueNode>(childNode) && downcast<CSSCalcPrimitiveValueNode>(childNode).isNumericValue()) { |
| downcast<CSSCalcPrimitiveValueNode>(childNode).invert(); |
| return childNode; |
| } |
| |
| // If root’s child is an Invert node, return the child’s child. |
| if (is<CSSCalcInvertNode>(childNode)) |
| return downcast<CSSCalcInvertNode>(childNode).child(); |
| |
| return WTFMove(rootNode); |
| } |
| |
| return WTFMove(rootNode); |
| } |
| |
| CSSUnitType CSSCalcOperationNode::primitiveType() const |
| { |
| auto unitCategory = category(); |
| switch (unitCategory) { |
| case CalculationCategory::Number: |
| return CSSUnitType::CSS_NUMBER; |
| case CalculationCategory::Percent: { |
| if (m_children.isEmpty()) |
| return CSSUnitType::CSS_UNKNOWN; |
| |
| if (m_children.size() == 2) { |
| if (m_children[0]->category() == CalculationCategory::Number) |
| return m_children[1]->primitiveType(); |
| if (m_children[1]->category() == CalculationCategory::Number) |
| return m_children[0]->primitiveType(); |
| } |
| CSSUnitType firstType = m_children[0]->primitiveType(); |
| for (auto& child : m_children) { |
| if (firstType != child->primitiveType()) |
| return CSSUnitType::CSS_UNKNOWN; |
| } |
| return firstType; |
| } |
| |
| case CalculationCategory::Length: |
| case CalculationCategory::Angle: |
| case CalculationCategory::Time: |
| case CalculationCategory::Frequency: |
| if (m_children.size() == 1 && !isInverseTrigNode()) |
| return m_children.first()->primitiveType(); |
| return canonicalUnitTypeForCalculationCategory(unitCategory); |
| |
| case CalculationCategory::PercentLength: |
| case CalculationCategory::PercentNumber: |
| case CalculationCategory::Other: |
| return CSSUnitType::CSS_UNKNOWN; |
| } |
| ASSERT_NOT_REACHED(); |
| return CSSUnitType::CSS_UNKNOWN; |
| } |
| |
| std::unique_ptr<CalcExpressionNode> CSSCalcOperationNode::createCalcExpression(const CSSToLengthConversionData& conversionData) const |
| { |
| Vector<std::unique_ptr<CalcExpressionNode>> nodes; |
| nodes.reserveInitialCapacity(m_children.size()); |
| |
| for (auto& child : m_children) { |
| auto node = child->createCalcExpression(conversionData); |
| if (!node) |
| return nullptr; |
| nodes.uncheckedAppend(WTFMove(node)); |
| } |
| |
| // Reverse the operation we did when creating this node, recovering a suitable destination category for otherwise-ambiguous min/max/clamp nodes. |
| // Note that this category is really only good enough for that purpose and is not accurate for other node types; we could use a boolean instead. |
| auto destinationCategory = CalculationCategory::Other; |
| if (category() == CalculationCategory::PercentLength) |
| destinationCategory = CalculationCategory::Length; |
| else if (category() == CalculationCategory::PercentNumber) |
| destinationCategory = CalculationCategory::Number; |
| |
| return makeUnique<CalcExpressionOperation>(WTFMove(nodes), m_operator, destinationCategory); |
| } |
| |
| double CSSCalcOperationNode::doubleValue(CSSUnitType unitType) const |
| { |
| bool allowNumbers = calcOperator() == CalcOperator::Multiply; |
| |
| return evaluate(m_children.map([&] (auto& child) { |
| CSSUnitType childType = unitType; |
| if (allowNumbers && unitType != CSSUnitType::CSS_NUMBER && child->primitiveType() == CSSUnitType::CSS_NUMBER) |
| childType = CSSUnitType::CSS_NUMBER; |
| if (isTrigNode() && unitType != CSSUnitType::CSS_NUMBER) |
| childType = CSSUnitType::CSS_RAD; |
| if (isInverseTrigNode()) |
| childType = CSSUnitType::CSS_NUMBER; |
| if (isAtan2Node()) |
| childType = child->primitiveType(); |
| if (isSignNode()) |
| childType = child->primitiveType(); |
| return child->doubleValue(childType); |
| })); |
| } |
| |
| double CSSCalcOperationNode::computeLengthPx(const CSSToLengthConversionData& conversionData) const |
| { |
| return evaluate(m_children.map([&] (auto& child) { |
| return child->computeLengthPx(conversionData); |
| })); |
| } |
| |
| void CSSCalcOperationNode::collectDirectComputationalDependencies(HashSet<CSSPropertyID>& values) const |
| { |
| for (auto& child : m_children) |
| child->collectDirectComputationalDependencies(values); |
| } |
| |
| void CSSCalcOperationNode::collectDirectRootComputationalDependencies(HashSet<CSSPropertyID>& values) const |
| { |
| for (auto& child : m_children) |
| child->collectDirectRootComputationalDependencies(values); |
| } |
| |
| void CSSCalcOperationNode::buildCSSText(const CSSCalcExpressionNode& node, StringBuilder& builder) |
| { |
| auto shouldOutputEnclosingCalc = [](const CSSCalcExpressionNode& rootNode) { |
| if (is<CSSCalcOperationNode>(rootNode)) { |
| auto& operationNode = downcast<CSSCalcOperationNode>(rootNode); |
| return operationNode.isCalcSumNode() || operationNode.isCalcProductNode(); |
| } |
| return !is<CSSCalcPrimitiveValueNode>(rootNode); |
| }; |
| |
| bool outputCalc = shouldOutputEnclosingCalc(node); |
| if (outputCalc) |
| builder.append("calc("); |
| |
| buildCSSTextRecursive(node, builder, GroupingParens::Omit); |
| |
| if (outputCalc) |
| builder.append(')'); |
| } |
| |
| static const char* functionPrefixForOperator(CalcOperator op) |
| { |
| switch (op) { |
| case CalcOperator::Add: |
| case CalcOperator::Subtract: |
| case CalcOperator::Multiply: |
| case CalcOperator::Divide: |
| ASSERT_NOT_REACHED(); |
| return ""; |
| case CalcOperator::Sin: return "sin("; |
| case CalcOperator::Cos: return "cos("; |
| case CalcOperator::Tan: return "tan("; |
| case CalcOperator::Min: return "min("; |
| case CalcOperator::Max: return "max("; |
| case CalcOperator::Clamp: return "clamp("; |
| case CalcOperator::Exp: return "exp("; |
| case CalcOperator::Log: return "log("; |
| case CalcOperator::Asin: return "asin("; |
| case CalcOperator::Acos: return "acos("; |
| case CalcOperator::Atan: return "atan("; |
| case CalcOperator::Atan2: return "atan2("; |
| case CalcOperator::Abs: return "abs("; |
| case CalcOperator::Sign: return "sign("; |
| case CalcOperator::Mod: return "mod("; |
| case CalcOperator::Rem: return "rem("; |
| case CalcOperator::Round: return "round("; |
| case CalcOperator::Up: return "round(up, "; |
| case CalcOperator::Down: return "round(down, "; |
| case CalcOperator::Nearest: return "round(nearest, "; |
| case CalcOperator::ToZero: return "round(to-zero, "; |
| case CalcOperator::Pow: return "pow("; |
| case CalcOperator::Sqrt: return "sqrt("; |
| case CalcOperator::Hypot: return "hypot("; |
| } |
| |
| return ""; |
| } |
| |
| // <https://drafts.csswg.org/css-values-4/#serialize-a-calculation-tree> |
| void CSSCalcOperationNode::buildCSSTextRecursive(const CSSCalcExpressionNode& node, StringBuilder& builder, GroupingParens parens) |
| { |
| // If root is a numeric value, or a non-math function, serialize root per the normal rules for it and return the result. |
| if (is<CSSCalcPrimitiveValueNode>(node)) { |
| auto& valueNode = downcast<CSSCalcPrimitiveValueNode>(node); |
| builder.append(valueNode.customCSSText()); |
| return; |
| } |
| |
| if (is<CSSCalcOperationNode>(node)) { |
| auto& operationNode = downcast<CSSCalcOperationNode>(node); |
| |
| if (operationNode.isCalcSumNode()) { |
| // If root is a Sum node, let s be a string initially containing "(". |
| if (parens == GroupingParens::Include) |
| builder.append('('); |
| |
| // Simplification already sorted children. |
| auto& children = operationNode.children(); |
| ASSERT(children.size()); |
| // Serialize root’s first child, and append it to s. |
| buildCSSTextRecursive(children.first(), builder); |
| |
| // For each child of root beyond the first: |
| // If child is a Negate node, append " - " to s, then serialize the Negate’s child and append the result to s. |
| // If child is a negative numeric value, append " - " to s, then serialize the negation of child as normal and append the result to s. |
| // Otherwise, append " + " to s, then serialize child and append the result to s. |
| for (unsigned i = 1; i < children.size(); ++i) { |
| auto& child = children[i]; |
| if (is<CSSCalcNegateNode>(child)) { |
| builder.append(" - "); |
| buildCSSTextRecursive(downcast<CSSCalcNegateNode>(child.get()).child(), builder); |
| continue; |
| } |
| |
| if (is<CSSCalcPrimitiveValueNode>(child)) { |
| auto& primitiveValueNode = downcast<CSSCalcPrimitiveValueNode>(child.get()); |
| if (primitiveValueNode.isNegative()) { |
| builder.append(" - "); |
| // Serialize the negation of child. |
| auto unitType = primitiveValueNode.value().primitiveType(); |
| builder.append(0 - primitiveValueNode.value().doubleValue(), CSSPrimitiveValue::unitTypeString(unitType)); |
| continue; |
| } |
| } |
| |
| builder.append(" + "); |
| buildCSSTextRecursive(child, builder); |
| } |
| |
| if (parens == GroupingParens::Include) |
| builder.append(')'); |
| return; |
| } |
| |
| if (operationNode.isCalcProductNode()) { |
| // If root is a Product node, let s be a string initially containing "(". |
| if (parens == GroupingParens::Include) |
| builder.append('('); |
| |
| // Simplification already sorted children. |
| auto& children = operationNode.children(); |
| ASSERT(children.size()); |
| // Serialize root’s first child, and append it to s. |
| buildCSSTextRecursive(children.first(), builder); |
| |
| // For each child of root beyond the first: |
| // If child is an Invert node, append " / " to s, then serialize the Invert’s child and append the result to s. |
| // Otherwise, append " * " to s, then serialize child and append the result to s. |
| for (unsigned i = 1; i < children.size(); ++i) { |
| auto& child = children[i]; |
| if (is<CSSCalcInvertNode>(child)) { |
| builder.append(" / "); |
| buildCSSTextRecursive(downcast<CSSCalcInvertNode>(child.get()).child(), builder); |
| continue; |
| } |
| |
| builder.append(" * "); |
| buildCSSTextRecursive(child, builder); |
| } |
| |
| if (parens == GroupingParens::Include) |
| builder.append(')'); |
| return; |
| } |
| |
| // If root is anything but a Sum, Negate, Product, or Invert node, serialize a math function for the |
| // function corresponding to the node type, treating the node’s children as the function’s |
| // comma-separated calculation arguments, and return the result. |
| builder.append(functionPrefixForOperator(operationNode.calcOperator())); |
| |
| auto& children = operationNode.children(); |
| ASSERT(children.size()); |
| buildCSSTextRecursive(children.first(), builder, GroupingParens::Omit); |
| |
| for (unsigned i = 1; i < children.size(); ++i) { |
| builder.append(", "); |
| buildCSSTextRecursive(children[i], builder, GroupingParens::Omit); |
| } |
| |
| builder.append(')'); |
| return; |
| } |
| |
| if (is<CSSCalcNegateNode>(node)) { |
| auto& negateNode = downcast<CSSCalcNegateNode>(node); |
| // If root is a Negate node, let s be a string initially containing "(-1 * ". |
| builder.append("-1 *"); |
| buildCSSTextRecursive(negateNode.child(), builder); |
| return; |
| } |
| |
| if (is<CSSCalcInvertNode>(node)) { |
| auto& invertNode = downcast<CSSCalcInvertNode>(node); |
| // If root is an Invert node, let s be a string initially containing "(1 / ". |
| builder.append("1 / "); |
| buildCSSTextRecursive(invertNode.child(), builder); |
| return; |
| } |
| } |
| |
| void CSSCalcOperationNode::dump(TextStream& ts) const |
| { |
| ts << "calc operation " << m_operator << " (category: " << category() << ", type " << primitiveType() << ")"; |
| |
| TextStream::GroupScope scope(ts); |
| ts << m_children.size() << " children"; |
| for (auto& child : m_children) |
| ts.dumpProperty("node", child); |
| } |
| |
| bool CSSCalcOperationNode::equals(const CSSCalcExpressionNode& exp) const |
| { |
| if (type() != exp.type()) |
| return false; |
| |
| const CSSCalcOperationNode& other = static_cast<const CSSCalcOperationNode&>(exp); |
| |
| if (m_children.size() != other.m_children.size() || m_operator != other.m_operator) |
| return false; |
| |
| for (size_t i = 0; i < m_children.size(); ++i) { |
| if (!compareCSSValue(m_children[i], other.m_children[i])) |
| return false; |
| } |
| return true; |
| } |
| |
| static std::pair<double, double> getNearestMultiples(double a, double b) |
| { |
| double lowerB = std::floor(a / std::abs(b))*std::abs(b); |
| double upperB = lowerB + std::abs(b); |
| return std::make_pair(lowerB, upperB); |
| } |
| |
| double CSSCalcOperationNode::evaluateOperator(CalcOperator op, const Vector<double>& children) |
| { |
| switch (op) { |
| case CalcOperator::Add: { |
| double sum = 0; |
| for (auto& child : children) |
| sum += child; |
| return sum; |
| } |
| case CalcOperator::Subtract: |
| ASSERT(children.size() == 2); |
| return children[0] - children[1]; |
| case CalcOperator::Multiply: { |
| double product = 1; |
| for (auto& child : children) |
| product *= child; |
| return product; |
| } |
| case CalcOperator::Divide: |
| ASSERT(children.size() == 1 || children.size() == 2); |
| if (children.size() == 1) |
| return std::numeric_limits<double>::quiet_NaN(); |
| return children[0] / children[1]; |
| case CalcOperator::Min: { |
| if (children.isEmpty()) |
| return std::numeric_limits<double>::quiet_NaN(); |
| double minimum = children[0]; |
| for (auto child : children) |
| minimum = std::min(minimum, child); |
| return minimum; |
| } |
| case CalcOperator::Max: { |
| if (children.isEmpty()) |
| return std::numeric_limits<double>::quiet_NaN(); |
| double maximum = children[0]; |
| for (auto child : children) |
| maximum = std::max(maximum, child); |
| return maximum; |
| } |
| case CalcOperator::Clamp: { |
| if (children.size() != 3) |
| return std::numeric_limits<double>::quiet_NaN(); |
| double min = children[0]; |
| double value = children[1]; |
| double max = children[2]; |
| return std::max(min, std::min(value, max)); |
| } |
| case CalcOperator::Pow: |
| if (children.size() != 2) |
| return std::numeric_limits<double>::quiet_NaN(); |
| return std::pow(children[0], children[1]); |
| case CalcOperator::Sqrt: { |
| if (children.size() != 1) |
| return std::numeric_limits<double>::quiet_NaN(); |
| return std::sqrt(children[0]); |
| } |
| case CalcOperator::Hypot: { |
| if (children.isEmpty()) |
| return std::numeric_limits<double>::quiet_NaN(); |
| if (children.size() == 1) |
| return std::abs(children[0]); |
| double sum = 0; |
| for (auto child : children) |
| sum += (child * child); |
| return std::sqrt(sum); |
| } |
| case CalcOperator::Sin: { |
| if (children.size() != 1) |
| return std::numeric_limits<double>::quiet_NaN(); |
| return std::sin(children[0]); |
| } |
| case CalcOperator::Cos: { |
| if (children.size() != 1) |
| return std::numeric_limits<double>::quiet_NaN(); |
| return std::cos(children[0]); |
| } |
| case CalcOperator::Tan: { |
| if (children.size() != 1) |
| return std::numeric_limits<double>::quiet_NaN(); |
| return std::tan(children[0]); |
| } |
| case CalcOperator::Log: { |
| if (children.size() != 1 && children.size() != 2) |
| return std::numeric_limits<double>::quiet_NaN(); |
| if (children.size() == 1) |
| return std::log(children[0]); |
| return std::log(children[0]) / std::log(children[1]); |
| } |
| case CalcOperator::Exp: { |
| if (children.size() != 1) |
| return std::numeric_limits<double>::quiet_NaN(); |
| return std::exp(children[0]); |
| } |
| case CalcOperator::Asin: { |
| if (children.size() != 1) |
| return std::numeric_limits<double>::quiet_NaN(); |
| return rad2deg(std::asin(children[0])); |
| } |
| case CalcOperator::Acos: { |
| if (children.size() != 1) |
| return std::numeric_limits<double>::quiet_NaN(); |
| return rad2deg(std::acos(children[0])); |
| } |
| case CalcOperator::Atan: { |
| if (children.size() != 1) |
| return std::numeric_limits<double>::quiet_NaN(); |
| return rad2deg(std::atan(children[0])); |
| } |
| case CalcOperator::Atan2: { |
| if (children.size() != 2) |
| return std::numeric_limits<double>::quiet_NaN(); |
| return rad2deg(atan2(children[0], children[1])); |
| } |
| case CalcOperator::Abs: { |
| if (children.size() != 1) |
| return std::numeric_limits<double>::quiet_NaN(); |
| return std::abs(children[0]); |
| } |
| case CalcOperator::Sign: { |
| if (children.size() != 1) |
| return std::numeric_limits<double>::quiet_NaN(); |
| if (children[0] > 0) |
| return 1; |
| if (children[0] < 0) |
| return -1; |
| return children[0]; |
| } |
| case CalcOperator::Mod: { |
| if (children.size() != 2) |
| return std::numeric_limits<double>::quiet_NaN(); |
| float left = children[0]; |
| float right = children[1]; |
| if (!right) |
| return std::numeric_limits<double>::quiet_NaN(); |
| if ((left < 0) == (right < 0)) |
| return std::fmod(left, right); |
| return std::remainder(left, right); |
| } |
| case CalcOperator::Rem: { |
| if (children.size() != 2) |
| return std::numeric_limits<double>::quiet_NaN(); |
| float left = children[0]; |
| float right = children[1]; |
| if (!right) |
| return std::numeric_limits<double>::quiet_NaN(); |
| return std::fmod(left, right); |
| } |
| case CalcOperator::Round: |
| return std::numeric_limits<double>::quiet_NaN(); |
| case CalcOperator::Up: { |
| if (children.size() != 2) |
| return std::numeric_limits<double>::quiet_NaN(); |
| auto ret = getNearestMultiples(children[0], children[1]); |
| return ret.second; |
| } |
| case CalcOperator::Down: { |
| if (children.size() != 2) |
| return std::numeric_limits<double>::quiet_NaN(); |
| auto ret = getNearestMultiples(children[0], children[1]); |
| return ret.first; |
| } |
| case CalcOperator::Nearest: { |
| if (children.size() != 2) |
| return std::numeric_limits<double>::quiet_NaN(); |
| auto ret = getNearestMultiples(children[0], children[1]); |
| auto upperB = ret.second; |
| auto lowerB = ret.first; |
| return std::abs(upperB - children[0]) <= std::abs(children[1]) / 2 ? upperB : lowerB; |
| } |
| case CalcOperator::ToZero: { |
| if (children.size() != 2) |
| return std::numeric_limits<double>::quiet_NaN(); |
| auto ret = getNearestMultiples(children[0], children[1]); |
| auto upperB = ret.second; |
| auto lowerB = ret.first; |
| return std::abs(upperB) < std::abs(lowerB) ? upperB : lowerB; |
| } |
| } |
| ASSERT_NOT_REACHED(); |
| return 0; |
| } |
| |
| |
| |
| } |