blob: cf00c7576bfe499798ba456a234ed6fc2656a4fa [file] [log] [blame]
/*
* 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