| /* |
| * Copyright (C) 2011, 2012 Google Inc. All rights reserved. |
| * Copyright (C) 2014-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: |
| * |
| * * 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 "config.h" |
| #include "CSSCalcValue.h" |
| |
| #include "CSSCalcExpressionNodeParser.h" |
| #include "CSSCalcInvertNode.h" |
| #include "CSSCalcNegateNode.h" |
| #include "CSSCalcOperationNode.h" |
| #include "CSSCalcPrimitiveValueNode.h" |
| #include "CSSCalcSymbolTable.h" |
| #include "CSSParser.h" |
| #include "CSSParserTokenRange.h" |
| #include "CSSPrimitiveValueMappings.h" |
| #include "CalcExpressionBlendLength.h" |
| #include "CalcExpressionInversion.h" |
| #include "CalcExpressionLength.h" |
| #include "CalcExpressionNegation.h" |
| #include "CalcExpressionNumber.h" |
| #include "CalcExpressionOperation.h" |
| #include "Logging.h" |
| #include "StyleResolver.h" |
| #include <wtf/MathExtras.h> |
| #include <wtf/text/StringBuilder.h> |
| #include <wtf/text/TextStream.h> |
| |
| namespace WebCore { |
| |
| static RefPtr<CSSCalcExpressionNode> createCSS(const CalcExpressionNode&, const RenderStyle&); |
| static RefPtr<CSSCalcExpressionNode> createCSS(const Length&, const RenderStyle&); |
| |
| static inline RefPtr<CSSCalcOperationNode> createBlendHalf(const Length& length, const RenderStyle& style, float progress) |
| { |
| return CSSCalcOperationNode::create(CalcOperator::Multiply, createCSS(length, style), |
| CSSCalcPrimitiveValueNode::create(CSSPrimitiveValue::create(progress, CSSUnitType::CSS_NUMBER))); |
| } |
| |
| static Vector<Ref<CSSCalcExpressionNode>> createCSS(const Vector<std::unique_ptr<CalcExpressionNode>>& nodes, const RenderStyle& style) |
| { |
| return WTF::compactMap(nodes, [&](auto& node) -> RefPtr<CSSCalcExpressionNode> { |
| return createCSS(*node, style); |
| }); |
| } |
| |
| static RefPtr<CSSCalcExpressionNode> createCSS(const CalcExpressionNode& node, const RenderStyle& style) |
| { |
| switch (node.type()) { |
| case CalcExpressionNodeType::Number: { |
| float value = downcast<CalcExpressionNumber>(node).value(); // double? |
| return CSSCalcPrimitiveValueNode::create(CSSPrimitiveValue::create(value, CSSUnitType::CSS_NUMBER)); |
| } |
| case CalcExpressionNodeType::Length: { |
| auto& length = downcast<CalcExpressionLength>(node).length(); |
| if (!length.isPercent() && length.isZero()) |
| return nullptr; |
| return createCSS(length, style); |
| } |
| |
| case CalcExpressionNodeType::Negation: { |
| auto childNode = createCSS(*downcast<CalcExpressionNegation>(node).child(), style); |
| if (!childNode) |
| return nullptr; |
| return CSSCalcNegateNode::create(childNode.releaseNonNull()); |
| } |
| case CalcExpressionNodeType::Inversion: { |
| auto childNode = createCSS(*downcast<CalcExpressionInversion>(node).child(), style); |
| if (!childNode) |
| return nullptr; |
| return CSSCalcInvertNode::create(childNode.releaseNonNull()); |
| } |
| case CalcExpressionNodeType::Operation: { |
| auto& operationNode = downcast<CalcExpressionOperation>(node); |
| auto& operationChildren = operationNode.children(); |
| CalcOperator op = operationNode.getOperator(); |
| |
| switch (op) { |
| case CalcOperator::Add: { |
| auto children = createCSS(operationChildren, style); |
| if (children.isEmpty()) |
| return nullptr; |
| if (children.size() == 1) |
| return WTFMove(children[0]); |
| return CSSCalcOperationNode::createSum(WTFMove(children)); |
| } |
| case CalcOperator::Subtract: { |
| ASSERT(operationChildren.size() == 2); |
| |
| Vector<Ref<CSSCalcExpressionNode>> values; |
| values.reserveInitialCapacity(operationChildren.size()); |
| |
| auto firstChild = createCSS(*operationChildren[0], style); |
| auto secondChild = createCSS(*operationChildren[1], style); |
| |
| if (!secondChild) |
| return firstChild; |
| |
| auto negateNode = CSSCalcNegateNode::create(secondChild.releaseNonNull()); |
| if (!firstChild) |
| return negateNode; |
| |
| values.append(firstChild.releaseNonNull()); |
| values.append(WTFMove(negateNode)); |
| |
| return CSSCalcOperationNode::createSum(WTFMove(values)); |
| } |
| case CalcOperator::Multiply: { |
| auto children = createCSS(operationChildren, style); |
| if (children.isEmpty()) |
| return nullptr; |
| return CSSCalcOperationNode::createProduct(WTFMove(children)); |
| } |
| case CalcOperator::Divide: { |
| ASSERT(operationChildren.size() == 2); |
| |
| Vector<Ref<CSSCalcExpressionNode>> values; |
| values.reserveInitialCapacity(operationChildren.size()); |
| |
| auto firstChild = createCSS(*operationChildren[0], style); |
| if (!firstChild) |
| return nullptr; |
| |
| auto secondChild = createCSS(*operationChildren[1], style); |
| if (!secondChild) |
| return nullptr; |
| auto invertNode = CSSCalcInvertNode::create(secondChild.releaseNonNull()); |
| |
| values.append(firstChild.releaseNonNull()); |
| values.append(WTFMove(invertNode)); |
| |
| return CSSCalcOperationNode::createProduct(createCSS(operationChildren, style)); |
| } |
| case CalcOperator::Cos: |
| case CalcOperator::Tan: |
| case CalcOperator::Sin: { |
| auto children = createCSS(operationChildren, style); |
| if (children.size() != 1) |
| return nullptr; |
| return CSSCalcOperationNode::createTrig(op, WTFMove(children)); |
| } |
| case CalcOperator::Min: |
| case CalcOperator::Max: |
| case CalcOperator::Clamp: { |
| auto children = createCSS(operationChildren, style); |
| if (children.isEmpty()) |
| return nullptr; |
| return CSSCalcOperationNode::createMinOrMaxOrClamp(op, WTFMove(children), operationNode.destinationCategory()); |
| } |
| case CalcOperator::Log: { |
| auto children = createCSS(operationChildren, style); |
| if (children.size() != 1 && children.size() != 2) |
| return nullptr; |
| return CSSCalcOperationNode::createLog(WTFMove(children)); |
| } |
| case CalcOperator::Exp: { |
| auto children = createCSS(operationChildren, style); |
| if (children.size() != 1) |
| return nullptr; |
| return CSSCalcOperationNode::createExp(WTFMove(children)); |
| } |
| case CalcOperator::Asin: |
| case CalcOperator::Acos: |
| case CalcOperator::Atan: { |
| auto children = createCSS(operationChildren, style); |
| if (children.size() != 1) |
| return nullptr; |
| return CSSCalcOperationNode::createInverseTrig(op, WTFMove(children)); |
| } |
| case CalcOperator::Atan2: { |
| auto children = createCSS(operationChildren, style); |
| if (children.size() != 2) |
| return nullptr; |
| return CSSCalcOperationNode::createAtan2(WTFMove(children)); |
| } |
| case CalcOperator::Sign: |
| case CalcOperator::Abs: { |
| auto children = createCSS(operationChildren, style); |
| if (children.size() != 1) |
| return nullptr; |
| return CSSCalcOperationNode::createSign(op, WTFMove(children)); |
| } |
| case CalcOperator::Sqrt: |
| case CalcOperator::Pow: { |
| auto children = createCSS(operationChildren, style); |
| if (children.isEmpty()) |
| return nullptr; |
| return CSSCalcOperationNode::createPowOrSqrt(op, WTFMove(children)); |
| } |
| case CalcOperator::Hypot: { |
| auto children = createCSS(operationChildren, style); |
| if (children.isEmpty()) |
| return nullptr; |
| return CSSCalcOperationNode::createHypot(WTFMove(children)); |
| } |
| case CalcOperator::Mod: |
| case CalcOperator::Rem: |
| case CalcOperator::Round: { |
| auto children = createCSS(operationChildren, style); |
| if (children.size() != 2) |
| return nullptr; |
| return CSSCalcOperationNode::createStep(op, WTFMove(children)); |
| } |
| case CalcOperator::Nearest: |
| case CalcOperator::ToZero: |
| case CalcOperator::Up: |
| case CalcOperator::Down: { |
| return CSSCalcOperationNode::createRoundConstant(op); |
| } |
| } |
| return nullptr; |
| } |
| case CalcExpressionNodeType::BlendLength: { |
| // FIXME: (http://webkit.org/b/122036) Create a CSSCalcExpressionNode equivalent of CalcExpressionBlendLength. |
| auto& blend = downcast<CalcExpressionBlendLength>(node); |
| float progress = blend.progress(); |
| return CSSCalcOperationNode::create(CalcOperator::Add, createBlendHalf(blend.from(), style, 1 - progress), createBlendHalf(blend.to(), style, progress)); |
| } |
| case CalcExpressionNodeType::Undefined: |
| ASSERT_NOT_REACHED(); |
| } |
| return nullptr; |
| } |
| |
| static RefPtr<CSSCalcExpressionNode> createCSS(const Length& length, const RenderStyle& style) |
| { |
| switch (length.type()) { |
| case LengthType::Percent: |
| case LengthType::Fixed: |
| return CSSCalcPrimitiveValueNode::create(CSSPrimitiveValue::create(length, style)); |
| case LengthType::Calculated: |
| return createCSS(length.calculationValue().expression(), style); |
| case LengthType::Auto: |
| case LengthType::Content: |
| case LengthType::Intrinsic: |
| case LengthType::MinIntrinsic: |
| case LengthType::MinContent: |
| case LengthType::MaxContent: |
| case LengthType::FillAvailable: |
| case LengthType::FitContent: |
| case LengthType::Relative: |
| case LengthType::Undefined: |
| ASSERT_NOT_REACHED(); |
| } |
| return nullptr; |
| } |
| |
| CSSCalcValue::CSSCalcValue(Ref<CSSCalcExpressionNode>&& expression, bool shouldClampToNonNegative) |
| : CSSValue(CalculationClass) |
| , m_expression(WTFMove(expression)) |
| , m_shouldClampToNonNegative(shouldClampToNonNegative) |
| { |
| } |
| |
| CSSCalcValue::~CSSCalcValue() = default; |
| |
| CalculationCategory CSSCalcValue::category() const |
| { |
| return m_expression->category(); |
| } |
| |
| CSSUnitType CSSCalcValue::primitiveType() const |
| { |
| return m_expression->primitiveType(); |
| } |
| |
| Ref<CalculationValue> CSSCalcValue::createCalculationValue(const CSSToLengthConversionData& conversionData) const |
| { |
| return CalculationValue::create(m_expression->createCalcExpression(conversionData), m_shouldClampToNonNegative ? ValueRange::NonNegative : ValueRange::All); |
| } |
| |
| void CSSCalcValue::setPermittedValueRange(ValueRange range) |
| { |
| m_shouldClampToNonNegative = range != ValueRange::All; |
| } |
| |
| void CSSCalcValue::collectDirectComputationalDependencies(HashSet<CSSPropertyID>& values) const |
| { |
| m_expression->collectDirectComputationalDependencies(values); |
| } |
| |
| void CSSCalcValue::collectDirectRootComputationalDependencies(HashSet<CSSPropertyID>& values) const |
| { |
| m_expression->collectDirectRootComputationalDependencies(values); |
| } |
| |
| String CSSCalcValue::customCSSText() const |
| { |
| StringBuilder builder; |
| CSSCalcOperationNode::buildCSSText(m_expression.get(), builder); |
| return builder.toString(); |
| } |
| |
| bool CSSCalcValue::equals(const CSSCalcValue& other) const |
| { |
| return compareCSSValue(m_expression, other.m_expression); |
| } |
| |
| inline double CSSCalcValue::clampToPermittedRange(double value) const |
| { |
| if (primitiveType() == CSSUnitType::CSS_DEG && (isnan(value) || isinf(value))) |
| return 0; |
| return m_shouldClampToNonNegative && value < 0 ? 0 : value; |
| } |
| |
| double CSSCalcValue::doubleValue() const |
| { |
| return clampToPermittedRange(m_expression->doubleValue(primitiveType())); |
| } |
| |
| double CSSCalcValue::computeLengthPx(const CSSToLengthConversionData& conversionData) const |
| { |
| return clampToPermittedRange(m_expression->computeLengthPx(conversionData)); |
| } |
| |
| bool CSSCalcValue::convertingToLengthRequiresNonNullStyle(int lengthConversion) const |
| { |
| return m_expression->convertingToLengthRequiresNonNullStyle(lengthConversion); |
| } |
| |
| bool CSSCalcValue::isCalcFunction(CSSValueID functionId) |
| { |
| switch (functionId) { |
| case CSSValueCalc: |
| case CSSValueWebkitCalc: |
| case CSSValueMin: |
| case CSSValueMax: |
| case CSSValueClamp: |
| case CSSValuePow: |
| case CSSValueSqrt: |
| case CSSValueHypot: |
| case CSSValueSin: |
| case CSSValueCos: |
| case CSSValueTan: |
| case CSSValueExp: |
| case CSSValueLog: |
| case CSSValueAsin: |
| case CSSValueAcos: |
| case CSSValueAtan: |
| case CSSValueAtan2: |
| case CSSValueAbs: |
| case CSSValueSign: |
| case CSSValueRound: |
| case CSSValueMod: |
| case CSSValueRem: |
| return true; |
| default: |
| return false; |
| } |
| return false; |
| } |
| |
| void CSSCalcValue::dump(TextStream& ts) const |
| { |
| ts << indent << "(" << "CSSCalcValue"; |
| |
| TextStream multilineStream; |
| multilineStream.setIndent(ts.indent() + 2); |
| |
| multilineStream.dumpProperty("should clamp non-negative", m_shouldClampToNonNegative); |
| multilineStream.dumpProperty("expression", m_expression.get()); |
| |
| ts << multilineStream.release(); |
| ts << ")\n"; |
| } |
| |
| RefPtr<CSSCalcValue> CSSCalcValue::create(CSSValueID function, const CSSParserTokenRange& tokens, CalculationCategory destinationCategory, ValueRange range, const CSSCalcSymbolTable& symbolTable, bool allowsNegativePercentage) |
| { |
| CSSCalcExpressionNodeParser parser(destinationCategory, symbolTable); |
| auto expression = parser.parseCalc(tokens, function, allowsNegativePercentage); |
| if (!expression) |
| return nullptr; |
| auto result = adoptRef(new CSSCalcValue(expression.releaseNonNull(), range != ValueRange::All)); |
| LOG_WITH_STREAM(Calc, stream << "CSSCalcValue::create " << *result); |
| return result; |
| } |
| |
| RefPtr<CSSCalcValue> CSSCalcValue::create(CSSValueID function, const CSSParserTokenRange& tokens, CalculationCategory destinationCategory, ValueRange range) |
| { |
| return create(function, tokens, destinationCategory, range, { }); |
| } |
| |
| RefPtr<CSSCalcValue> CSSCalcValue::create(const CalculationValue& value, const RenderStyle& style) |
| { |
| auto expression = createCSS(value.expression(), style); |
| if (!expression) |
| return nullptr; |
| |
| auto simplifiedExpression = CSSCalcOperationNode::simplify(expression.releaseNonNull()); |
| |
| auto result = adoptRef(new CSSCalcValue(WTFMove(simplifiedExpression), value.shouldClampToNonNegative())); |
| LOG_WITH_STREAM(Calc, stream << "CSSCalcValue::create from CalculationValue: " << *result); |
| return result; |
| } |
| |
| TextStream& operator<<(TextStream& ts, const CSSCalcValue& value) |
| { |
| value.dump(ts); |
| return ts; |
| } |
| |
| } // namespace WebCore |