CSSCalculationValue.cpp [plain text]
#include "config.h"
#include "CSSCalculationValue.h"
#include "CSSValueList.h"
#include "Length.h"
#include "StyleResolver.h"
#include <wtf/OwnPtr.h>
#include <wtf/PassOwnPtr.h>
#include <wtf/text/StringBuilder.h>
static const int maxExpressionDepth = 100;
enum ParseState {
OK,
TooDeep,
NoMoreTokens
};
namespace WebCore {
static CalculationCategory unitCategory(CSSPrimitiveValue::UnitTypes type)
{
switch (type) {
case CSSPrimitiveValue::CSS_NUMBER:
case CSSPrimitiveValue::CSS_PARSER_INTEGER:
return CalcNumber;
case CSSPrimitiveValue::CSS_PERCENTAGE:
return CalcPercent;
case CSSPrimitiveValue::CSS_EMS:
case CSSPrimitiveValue::CSS_EXS:
case CSSPrimitiveValue::CSS_PX:
case CSSPrimitiveValue::CSS_CM:
case CSSPrimitiveValue::CSS_MM:
case CSSPrimitiveValue::CSS_IN:
case CSSPrimitiveValue::CSS_PT:
case CSSPrimitiveValue::CSS_PC:
case CSSPrimitiveValue::CSS_REMS:
return CalcLength;
default:
return CalcOther;
}
}
String CSSCalcValue::customCssText() const
{
return "";
}
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(RenderStyle* currentStyle, RenderStyle* rootStyle, double multiplier, bool computingFontSize) const
{
return clampToPermittedRange(m_expression->computeLengthPx(currentStyle, rootStyle, multiplier, computingFontSize));
}
CSSCalcExpressionNode::~CSSCalcExpressionNode()
{
}
class CSSCalcPrimitiveValue : public CSSCalcExpressionNode {
public:
static PassRefPtr<CSSCalcPrimitiveValue> create(CSSPrimitiveValue* value, bool isInteger)
{
return adoptRef(new CSSCalcPrimitiveValue(value, isInteger));
}
virtual bool isZero() const
{
return !m_value->getDoubleValue();
}
virtual String cssText() const
{
return m_value->cssText();
}
virtual PassOwnPtr<CalcExpressionNode> toCalcValue(RenderStyle* style, RenderStyle* rootStyle, double zoom) const
{
switch (m_category) {
case CalcNumber:
return adoptPtr(new CalcExpressionNumber(m_value->getFloatValue()));
case CalcLength:
return adoptPtr(new CalcExpressionNumber(m_value->computeLength<float>(style, rootStyle, zoom)));
case CalcPercent:
case CalcPercentLength:
return adoptPtr(new CalcExpressionLength(StyleResolver::convertToFloatLength(m_value.get(), style, rootStyle, zoom)));
case CalcPercentNumber:
case CalcOther:
ASSERT_NOT_REACHED();
}
return nullptr;
}
virtual double doubleValue() const
{
switch (m_category) {
case CalcNumber:
case CalcPercent:
return m_value->getDoubleValue();
case CalcLength:
case CalcPercentLength:
case CalcPercentNumber:
case CalcOther:
ASSERT_NOT_REACHED();
break;
}
return 0;
}
virtual double computeLengthPx(RenderStyle* currentStyle, RenderStyle* rootStyle, double multiplier, bool computingFontSize) const
{
switch (m_category) {
case CalcLength:
return m_value->computeLength<double>(currentStyle, rootStyle, multiplier, computingFontSize);
case CalcPercent:
case CalcNumber:
return m_value->getDoubleValue();
case CalcPercentLength:
case CalcPercentNumber:
case CalcOther:
ASSERT_NOT_REACHED();
break;
}
return 0;
}
private:
explicit CSSCalcPrimitiveValue(CSSPrimitiveValue* value, bool isInteger)
: CSSCalcExpressionNode(unitCategory((CSSPrimitiveValue::UnitTypes)value->primitiveType()), isInteger)
, m_value(value)
{
}
RefPtr<CSSPrimitiveValue> m_value;
};
static const CalculationCategory addSubtractResult[CalcOther][CalcOther] = {
{ CalcNumber, CalcOther, CalcPercentNumber, CalcPercentNumber, CalcOther },
{ CalcOther, CalcLength, CalcPercentLength, CalcOther, CalcPercentLength },
{ CalcPercentNumber, CalcPercentLength, CalcPercent, CalcPercentNumber, CalcPercentLength },
{ CalcPercentNumber, CalcOther, CalcPercentNumber, CalcPercentNumber, CalcOther },
{ CalcOther, CalcPercentLength, CalcPercentLength, CalcOther, CalcPercentLength },
};
class CSSCalcBinaryOperation : public CSSCalcExpressionNode {
public:
static PassRefPtr<CSSCalcBinaryOperation> create(PassRefPtr<CSSCalcExpressionNode> leftSide, PassRefPtr<CSSCalcExpressionNode> rightSide, CalcOperator op)
{
CalculationCategory leftCategory = leftSide->category();
CalculationCategory rightCategory = rightSide->category();
CalculationCategory newCategory = CalcOther;
ASSERT(leftCategory != CalcOther && rightCategory != CalcOther);
switch (op) {
case CalcAdd:
case CalcSubtract:
if (leftCategory == CalcOther || rightCategory == CalcOther)
return 0;
newCategory = addSubtractResult[leftCategory][rightCategory];
break;
case CalcMultiply:
if (leftCategory != CalcNumber && rightCategory != CalcNumber)
return 0;
newCategory = leftCategory == CalcNumber ? rightCategory : leftCategory;
break;
case CalcDivide:
if (rightCategory != CalcNumber || rightSide->isZero())
return 0;
newCategory = leftCategory;
break;
}
if (newCategory == CalcOther)
return 0;
return adoptRef(new CSSCalcBinaryOperation(leftSide, rightSide, op, newCategory));
}
virtual bool isZero() const
{
return !doubleValue();
}
virtual PassOwnPtr<CalcExpressionNode> toCalcValue(RenderStyle* style, RenderStyle* rootStyle, double zoom) const
{
OwnPtr<CalcExpressionNode> left(m_leftSide->toCalcValue(style, rootStyle, zoom));
if (!left)
return nullptr;
OwnPtr<CalcExpressionNode> right(m_rightSide->toCalcValue(style, rootStyle, zoom));
if (!right)
return nullptr;
return adoptPtr(new CalcExpressionBinaryOperation(left.release(), right.release(), m_operator));
}
virtual double doubleValue() const
{
return evaluate(m_leftSide->doubleValue(), m_rightSide->doubleValue());
}
virtual double computeLengthPx(RenderStyle* currentStyle, RenderStyle* rootStyle, double multiplier, bool computingFontSize) const
{
const double leftValue = m_leftSide->computeLengthPx(currentStyle, rootStyle, multiplier, computingFontSize);
const double rightValue = m_rightSide->computeLengthPx(currentStyle, rootStyle, multiplier, computingFontSize);
return evaluate(leftValue, rightValue);
}
private:
CSSCalcBinaryOperation(PassRefPtr<CSSCalcExpressionNode> leftSide, PassRefPtr<CSSCalcExpressionNode> rightSide, CalcOperator op, CalculationCategory category)
: CSSCalcExpressionNode(category, leftSide->isInteger() && rightSide->isInteger())
, m_leftSide(leftSide)
, m_rightSide(rightSide)
, m_operator(op)
{
}
double evaluate(double leftValue, double rightValue) const
{
switch (m_operator) {
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 RefPtr<CSSCalcExpressionNode> m_leftSide;
const RefPtr<CSSCalcExpressionNode> m_rightSide;
const CalcOperator m_operator;
};
static ParseState checkDepthAndIndex(int* depth, unsigned index, CSSParserValueList* tokens)
{
(*depth)++;
if (*depth > maxExpressionDepth)
return TooDeep;
if (index >= tokens->size())
return NoMoreTokens;
return OK;
}
class CSSCalcExpressionNodeParser {
public:
PassRefPtr<CSSCalcExpressionNode> parseCalc(CSSParserValueList* tokens)
{
unsigned index = 0;
Value result;
bool ok = parseValueExpression(tokens, 0, &index, &result);
ASSERT(index <= tokens->size());
if (!ok || index != tokens->size())
return 0;
return result.value;
}
private:
struct Value {
RefPtr<CSSCalcExpressionNode> value;
};
char operatorValue(CSSParserValueList* tokens, unsigned index)
{
if (index >= tokens->size())
return 0;
CSSParserValue* value = tokens->valueAt(index);
if (value->unit != CSSParserValue::Operator)
return 0;
return value->iValue;
}
bool parseValue(CSSParserValueList* tokens, unsigned* index, Value* result)
{
CSSParserValue* parserValue = tokens->valueAt(*index);
if (parserValue->unit == CSSParserValue::Operator || parserValue->unit == CSSParserValue::Function)
return false;
RefPtr<CSSValue> value = parserValue->createCSSValue();
if (!value || !value->isPrimitiveValue())
return false;
CSSPrimitiveValue* primitiveValue = static_cast<CSSPrimitiveValue*>(value.get());
result->value = CSSCalcPrimitiveValue::create(primitiveValue, parserValue->isInt);
++*index;
return true;
}
bool parseValueTerm(CSSParserValueList* tokens, int depth, unsigned* index, Value* result)
{
if (checkDepthAndIndex(&depth, *index, tokens) != OK)
return false;
if (operatorValue(tokens, *index) == '(') {
unsigned currentIndex = *index + 1;
if (!parseValueExpression(tokens, depth, ¤tIndex, result))
return false;
if (operatorValue(tokens, currentIndex) != ')')
return false;
*index = currentIndex + 1;
return true;
}
return parseValue(tokens, index, result);
}
bool parseValueMultiplicativeExpression(CSSParserValueList* tokens, int depth, unsigned* index, Value* result)
{
if (checkDepthAndIndex(&depth, *index, tokens) != OK)
return false;
if (!parseValueTerm(tokens, depth, index, result))
return false;
while (*index < tokens->size() - 1) {
char operatorCharacter = operatorValue(tokens, *index);
if (operatorCharacter != CalcMultiply && operatorCharacter != CalcDivide)
break;
++*index;
Value rhs;
if (!parseValueTerm(tokens, depth, index, &rhs))
return false;
result->value = CSSCalcBinaryOperation::create(result->value, rhs.value, static_cast<CalcOperator>(operatorCharacter));
if (!result->value)
return false;
}
ASSERT(*index <= tokens->size());
return true;
}
bool parseAdditiveValueExpression(CSSParserValueList* tokens, int depth, unsigned* index, Value* result)
{
if (checkDepthAndIndex(&depth, *index, tokens) != OK)
return false;
if (!parseValueMultiplicativeExpression(tokens, depth, index, result))
return false;
while (*index < tokens->size() - 1) {
char operatorCharacter = operatorValue(tokens, *index);
if (operatorCharacter != CalcAdd && operatorCharacter != CalcSubtract)
break;
++*index;
Value rhs;
if (!parseValueMultiplicativeExpression(tokens, depth, index, &rhs))
return false;
result->value = CSSCalcBinaryOperation::create(result->value, rhs.value, static_cast<CalcOperator>(operatorCharacter));
if (!result->value)
return false;
}
ASSERT(*index <= tokens->size());
return true;
}
bool parseValueExpression(CSSParserValueList* tokens, int depth, unsigned* index, Value* result)
{
return parseAdditiveValueExpression(tokens, depth, index, result);
}
};
PassRefPtr<CSSCalcValue> CSSCalcValue::create(CSSParserString name, CSSParserValueList* parserValueList, CalculationPermittedValueRange range)
{
CSSCalcExpressionNodeParser parser;
RefPtr<CSSCalcExpressionNode> expression;
if (equalIgnoringCase(name, "-webkit-calc("))
expression = parser.parseCalc(parserValueList);
return expression ? adoptRef(new CSSCalcValue(expression, range)) : 0;
}
}