calc expressions should support time, angle and frequency values.

According to the CSS3 standard calc() expressions can contain time,
angle and frequency values. (http://www.w3.org/TR/css3-values/#calc).
The patch implements this feature.

The patch is partially based on http://trac.webkit.org/changeset/168685
and http://trac.webkit.org/changeset/170544 (Patch by Martin Hodovan,
reviewers were: Darin Adler and Simon Fraser)

R=mikelawther@chromium.org,alancutter@chromium.org
BUG=390566

Review URL: https://codereview.chromium.org/345903005

git-svn-id: svn://svn.chromium.org/blink/trunk@179101 bbb929c8-8fbe-4397-9dbb-9b2b20218538
parent f9790474
All boxes below should be 100px * 100px and green. unclosed calc
unclosed calc with garbage
garbage
garbage
dpi
dpi / number
dpi + dpi
fr
zero division
mod10
1mod
70px+40px no whitespace around +
70px +40px no whitespace on right of +
70px+ 40px no whitespace on left of +
70px+-40px no whitespace around +
70px-40px no whitespace around -
70px -40px no whitespace on right of -
70px- 40px no whitespace on left of -
70px-+40px no whitespace around -
too many nests
end with operator
start with operator
no expressions
too many pluses
no binary operator
two binary operators
invalid operator '@'
invalid operator 'flim'
invalid operator '@'
invalid operator 'flim'
invalid operator 'flim' with parens
non length
number + length
length + number
percent + number
number + percent
angle + number
number + angle
angle + length
length + angle
angle + percent
percent + angle
angle + time
time + angle
angle + frequency
frequency + angle
time + number
number + time
time + length
length + time
time + percent
percent + time
time + angle
angle + time
time + frequency
frequency + time
length - number
number - length
percent - number
number - percent
angle - number
number - angle
angle - length
length - angle
angle - percent
percent - angle
angle - time
time - angle
angle - frequency
frequency - angle
time - number
number - time
time - length
length - time
time - percent
percent - time
time - angle
angle - time
time - frequency
frequency - time
length * length
length * percent
percent * length
percent * percent
angle * length
length * angle
angle * percent
percent * angle
angle * time
time * angle
angle * frequency
frequency * angle
time * length
length * time
time * percent
percent * time
time * angle
angle * time
time * frequency
frequency * time
number / length
number / percent
length / length
length / percent
percent / length
percent / percent
number / angle
angle / length
length / angle
angle / percent
percent / angle
angle / time
time / angle
angle / frequency
frequency / angle
number / time
time / length
length / time
time / percent
percent / time
time / angle
angle / time
time / frequency
frequency / time
This is a testharness.js-based test.
PASS Tests invalid calc() expression handling.
Harness: the test ran to completion.
unclosed calc => PASS
unclosed calc with garbage => PASS
garbage => PASS
dpi => PASS
dpi / number => PASS
dpi + dpi => PASS
zero division => PASS
non length => PASS
number + length => PASS
length + number => PASS
percent + number => PASS
number + percent => PASS
length - number => PASS
number - length => PASS
percent - number => PASS
number - percent => PASS
length * length => PASS
length * percent => PASS
percent * length => PASS
percent * percent => PASS
number / length => PASS
number / percent => PASS
length / length => PASS
length / percent => PASS
percent / length => PASS
percent / percent => PASS
number mod length => PASS
number mod percent => PASS
length mod length => PASS
length mod percent => PASS
percent mod length => PASS
percent mod percent => PASS
mod10 => PASS
1mod => PASS
70px+40px no whitespace around + => PASS
70px +40px no whitespace on right of + => PASS
70px+ 40px no whitespace on left of + => PASS
70px+-40px no whitespace around + => PASS
70px-40px no whitespace around - => PASS
70px -40px no whitespace on right of - => PASS
70px- 40px no whitespace on left of - => PASS
70px-+40px no whitespace around - => PASS
too many nests => PASS
end with operator => PASS
start with operator => PASS
no expressions => PASS
too many pluses => PASS
no binary operator => PASS
two binary operators => PASS
invalid operator '@' => PASS
invalid operator 'flim' => PASS
invalid operator '@' => PASS
invalid operator 'flim' => PASS
invalid operator 'flim' with parens => PASS
This is a testharness.js-based test.
PASS Tests calc() with time units.
PASS Tests calc() with angle units.
PASS Tests calc() with frequency units.
PASS Tests unit conversion of single values in calc().
Harness: the test ran to completion.
<!DOCTYPE html>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<div id="target"></div>
<script>
function compareValue(property, calcValue, expectedValue) {
target.style[property] = '';
target.style[property] = calcValue;
var computedCalcValue = getComputedStyle(target)[property];
target.style[property] = expectedValue;
var computedExpectedValue = getComputedStyle(target)[property];
assert_equals(computedCalcValue, computedExpectedValue, calcValue + ' should equal to ' + expectedValue);
}
test(function() {
compareValue("transition-delay", "calc(4s + 1s)", "5s");
compareValue("transition-delay", "calc(4s + 1ms)", "4.001s");
compareValue("transition-delay", "calc(4ms + 1ms)", "0.005s");
compareValue("transition-delay", "calc(4s - 1s)", "3s");
compareValue("transition-delay", "calc(4s - 1ms)", "3.999s");
compareValue("transition-delay", "calc(4 * 1s)", "4s");
compareValue("transition-delay", "calc(4 * 1ms)", "0.004s");
compareValue("transition-delay", "calc(4s / 2)", "2s");
compareValue("transition-delay", "calc(4ms / 2)", "0.002s");
}, "Tests calc() with time units.");
test(function() {
compareValue("transform", "rotate(calc(45deg + 45deg))", "rotate(90deg)");
compareValue("transform", "rotate(calc(45deg + 1rad))", "rotate(102.3deg)");
compareValue("transform", "rotate(calc(20deg + 200grad))", "rotate(200deg)");
compareValue("transform", "rotate(calc(200deg + 0.5turn))", "rotate(200deg)");
compareValue("transform", "rotate(calc(45rad + 45rad))", "rotate(90rad)");
compareValue("transform", "rotate(calc(1rad + 40grad))", "matrix(-0.0574904875548093, 0.998346054151921, -0.998346054151921, -0.0574904875548093, 0, 0)");
compareValue("transform", "rotate(calc(1rad + 0.5turn))", "matrix(-0.54030230586814, -0.841470984807896, 0.841470984807896, -0.54030230586814, 0, 0)");
compareValue("transform", "rotate(calc(45grad + 45grad))", "rotate(90grad)");
compareValue("transform", "rotate(calc(10grad + 0.5turn))", "rotate(189deg)");
compareValue("transform", "rotate(calc(45deg - 15deg))", "rotate(30deg)");
compareValue("transform", "rotate(calc(90deg - 1rad))", "matrix(0.841470984807897, 0.54030230586814, -0.54030230586814, 0.841470984807897, 0, 0)");
compareValue("transform", "rotate(calc(38deg - 20grad))", "rotate(20deg)");
compareValue("transform", "rotate(calc(360deg - 0.5turn))", "rotate(180deg)");
compareValue("transform", "rotate(calc(45rad - 15rad))", "rotate(30rad)");
compareValue("transform", "rotate(calc(30rad - 10grad))", "matrix(-0.955728013201613, 0.294251533184956, -0.294251533184956, -0.955728013201613, 0, 0)");
compareValue("transform", "rotate(calc(4rad - 0.1turn))", "matrix(-0.973646143183581, -0.228063999490797, 0.228063999490797, -0.973646143183581, 0, 0)");
compareValue("transform", "rotate(calc(45grad - 15grad))", "rotate(30grad)");
compareValue("transform", "rotate(calc(100grad - 0.25turn))", "rotate(0deg)");
compareValue("transform", "rotate(calc(45deg * 2))", "rotate(90deg)");
compareValue("transform", "rotate(calc(2 * 45rad))", "rotate(90rad)");
compareValue("transform", "rotate(calc(45grad * 2))", "rotate(90grad)");
compareValue("transform", "rotate(calc(2 * 45turn))", "rotate(90turn)");
compareValue("transform", "rotate(calc(90deg / 2))", "rotate(45deg)");
compareValue("transform", "rotate(calc(90rad / 2))", "rotate(45rad)");
compareValue("transform", "rotate(calc(90grad / 2))", "rotate(45grad)");
compareValue("transform", "rotate(calc(90turn / 2))", "rotate(45turn)");
}, "Tests calc() with angle units.");
test(function() {
// NOTE: Since there is no CSS property that uses frequency at the moment we only test the parsing.
compareValue("pitch", "calc(10Hz + 20Hz)", null);
compareValue("pitch", "calc(10kHz + 20kHz)", null);
compareValue("pitch", "calc(10kHz + 20Hz)", null);
compareValue("pitch", "calc(20Hz - 10Hz)", null);
compareValue("pitch", "calc(20kHz - 10kHz)", null);
compareValue("pitch", "calc(20kHz - 10Hz)", null);
compareValue("pitch", "calc(10Hz * 2)", null);
compareValue("pitch", "calc(10kHz * 2)", null);
compareValue("pitch", "calc(10Hz / 2)", null);
compareValue("pitch", "calc(10kHz / 2)", null);
}, "Tests calc() with frequency units.");
test(function() {
compareValue("transition-delay", "calc(4000ms)", "4s");
compareValue("transform", "rotate(calc(50grad)", "rotate(45deg)");
}, "Tests unit conversion of single values in calc().");
</script>
...@@ -70,8 +70,17 @@ static CalculationCategory unitCategory(CSSPrimitiveValue::UnitType type) ...@@ -70,8 +70,17 @@ static CalculationCategory unitCategory(CSSPrimitiveValue::UnitType type)
case CSSPrimitiveValue::CSS_VMIN: case CSSPrimitiveValue::CSS_VMIN:
case CSSPrimitiveValue::CSS_VMAX: case CSSPrimitiveValue::CSS_VMAX:
return CalcLength; return CalcLength;
// FIXME: Support angle, time and frequency units. case CSSPrimitiveValue::CSS_DEG:
// http://www.w3.org/TR/css3-values/#calc-notation case CSSPrimitiveValue::CSS_GRAD:
case CSSPrimitiveValue::CSS_RAD:
case CSSPrimitiveValue::CSS_TURN:
return CalcAngle;
case CSSPrimitiveValue::CSS_MS:
case CSSPrimitiveValue::CSS_S:
return CalcTime;
case CSSPrimitiveValue::CSS_HZ:
case CSSPrimitiveValue::CSS_KHZ:
return CalcFrequency;
default: default:
return CalcOther; return CalcOther;
} }
...@@ -96,6 +105,7 @@ static bool hasDoubleValue(CSSPrimitiveValue::UnitType type) ...@@ -96,6 +105,7 @@ static bool hasDoubleValue(CSSPrimitiveValue::UnitType type)
case CSSPrimitiveValue::CSS_DEG: case CSSPrimitiveValue::CSS_DEG:
case CSSPrimitiveValue::CSS_RAD: case CSSPrimitiveValue::CSS_RAD:
case CSSPrimitiveValue::CSS_GRAD: case CSSPrimitiveValue::CSS_GRAD:
case CSSPrimitiveValue::CSS_TURN:
case CSSPrimitiveValue::CSS_MS: case CSSPrimitiveValue::CSS_MS:
case CSSPrimitiveValue::CSS_S: case CSSPrimitiveValue::CSS_S:
case CSSPrimitiveValue::CSS_HZ: case CSSPrimitiveValue::CSS_HZ:
...@@ -123,7 +133,6 @@ static bool hasDoubleValue(CSSPrimitiveValue::UnitType type) ...@@ -123,7 +133,6 @@ static bool hasDoubleValue(CSSPrimitiveValue::UnitType type)
case CSSPrimitiveValue::CSS_PARSER_OPERATOR: case CSSPrimitiveValue::CSS_PARSER_OPERATOR:
case CSSPrimitiveValue::CSS_PARSER_HEXCOLOR: case CSSPrimitiveValue::CSS_PARSER_HEXCOLOR:
case CSSPrimitiveValue::CSS_PARSER_IDENTIFIER: case CSSPrimitiveValue::CSS_PARSER_IDENTIFIER:
case CSSPrimitiveValue::CSS_TURN:
case CSSPrimitiveValue::CSS_COUNTER_NAME: case CSSPrimitiveValue::CSS_COUNTER_NAME:
case CSSPrimitiveValue::CSS_SHAPE: case CSSPrimitiveValue::CSS_SHAPE:
case CSSPrimitiveValue::CSS_QUAD: case CSSPrimitiveValue::CSS_QUAD:
...@@ -232,11 +241,14 @@ public: ...@@ -232,11 +241,14 @@ public:
switch (m_category) { switch (m_category) {
case CalcLength: case CalcLength:
return m_value->computeLength<double>(conversionData); return m_value->computeLength<double>(conversionData);
case CalcPercent:
case CalcNumber: case CalcNumber:
case CalcPercent:
return m_value->getDoubleValue(); return m_value->getDoubleValue();
case CalcAngle:
case CalcFrequency:
case CalcPercentLength: case CalcPercentLength:
case CalcPercentNumber: case CalcPercentNumber:
case CalcTime:
case CalcOther: case CalcOther:
ASSERT_NOT_REACHED(); ASSERT_NOT_REACHED();
break; break;
...@@ -283,12 +295,15 @@ private: ...@@ -283,12 +295,15 @@ private:
}; };
static const CalculationCategory addSubtractResult[CalcOther][CalcOther] = { static const CalculationCategory addSubtractResult[CalcOther][CalcOther] = {
// CalcNumber CalcLength CalcPercent CalcPercentNumber CalcPercentLength // CalcNumber CalcLength CalcPercent CalcPercentNumber CalcPercentLength CalcAngle CalcTime CalcFrequency
/* CalcNumber */ { CalcNumber, CalcOther, CalcPercentNumber, CalcPercentNumber, CalcOther }, /* CalcNumber */ { CalcNumber, CalcOther, CalcPercentNumber, CalcPercentNumber, CalcOther, CalcOther, CalcOther, CalcOther },
/* CalcLength */ { CalcOther, CalcLength, CalcPercentLength, CalcOther, CalcPercentLength }, /* CalcLength */ { CalcOther, CalcLength, CalcPercentLength, CalcOther, CalcPercentLength, CalcOther, CalcOther, CalcOther },
/* CalcPercent */ { CalcPercentNumber, CalcPercentLength, CalcPercent, CalcPercentNumber, CalcPercentLength }, /* CalcPercent */ { CalcPercentNumber, CalcPercentLength, CalcPercent, CalcPercentNumber, CalcPercentLength, CalcOther, CalcOther, CalcOther },
/* CalcPercentNumber */ { CalcPercentNumber, CalcOther, CalcPercentNumber, CalcPercentNumber, CalcOther }, /* CalcPercentNumber */ { CalcPercentNumber, CalcOther, CalcPercentNumber, CalcPercentNumber, CalcOther, CalcOther, CalcOther, CalcOther },
/* CalcPercentLength */ { CalcOther, CalcPercentLength, CalcPercentLength, CalcOther, CalcPercentLength }, /* CalcPercentLength */ { CalcOther, CalcPercentLength, CalcPercentLength, CalcOther, CalcPercentLength, CalcOther, CalcOther, CalcOther },
/* CalcAngle */ { CalcOther, CalcOther, CalcOther, CalcOther, CalcOther, CalcAngle, CalcOther, CalcOther },
/* CalcTime */ { CalcOther, CalcOther, CalcOther, CalcOther, CalcOther, CalcOther, CalcTime, CalcOther },
/* CalcFrequency */ { CalcOther, CalcOther, CalcOther, CalcOther, CalcOther, CalcOther, CalcOther, CalcFrequency }
}; };
static CalculationCategory determineCategory(const CSSCalcExpressionNode& leftSide, const CSSCalcExpressionNode& rightSide, CalcOperator op) static CalculationCategory determineCategory(const CSSCalcExpressionNode& leftSide, const CSSCalcExpressionNode& rightSide, CalcOperator op)
...@@ -517,6 +532,12 @@ public: ...@@ -517,6 +532,12 @@ public:
return leftType; return leftType;
return CSSPrimitiveValue::CSS_UNKNOWN; return CSSPrimitiveValue::CSS_UNKNOWN;
} }
case CalcAngle:
return CSSPrimitiveValue::CSS_DEG;
case CalcTime:
return CSSPrimitiveValue::CSS_MS;
case CalcFrequency:
return CSSPrimitiveValue::CSS_HZ;
case CalcPercentLength: case CalcPercentLength:
case CalcPercentNumber: case CalcPercentNumber:
case CalcOther: case CalcOther:
......
...@@ -54,12 +54,17 @@ enum CalcOperator { ...@@ -54,12 +54,17 @@ enum CalcOperator {
CalcDivide = '/' CalcDivide = '/'
}; };
// The order of this enum should not change since its elements are used as indices
// in the addSubtractResult matrix.
enum CalculationCategory { enum CalculationCategory {
CalcNumber = 0, CalcNumber = 0,
CalcLength, CalcLength,
CalcPercent, CalcPercent,
CalcPercentNumber, CalcPercentNumber,
CalcPercentLength, CalcPercentLength,
CalcAngle,
CalcTime,
CalcFrequency,
CalcOther CalcOther
}; };
......
...@@ -143,7 +143,6 @@ StringToUnitTable createStringToUnitTable() ...@@ -143,7 +143,6 @@ StringToUnitTable createStringToUnitTable()
return table; return table;
} }
CSSPrimitiveValue::UnitType CSSPrimitiveValue::fromName(const String& unit) CSSPrimitiveValue::UnitType CSSPrimitiveValue::fromName(const String& unit)
{ {
DEFINE_STATIC_LOCAL(StringToUnitTable, unitTable, (createStringToUnitTable())); DEFINE_STATIC_LOCAL(StringToUnitTable, unitTable, (createStringToUnitTable()));
...@@ -216,6 +215,10 @@ CSSPrimitiveValue::UnitType CSSPrimitiveValue::primitiveType() const ...@@ -216,6 +215,10 @@ CSSPrimitiveValue::UnitType CSSPrimitiveValue::primitiveType() const
return static_cast<UnitType>(m_primitiveUnitType); return static_cast<UnitType>(m_primitiveUnitType);
switch (m_value.calc->category()) { switch (m_value.calc->category()) {
case CalcAngle:
return CSS_DEG;
case CalcFrequency:
return CSS_HZ;
case CalcNumber: case CalcNumber:
return CSS_NUMBER; return CSS_NUMBER;
case CalcPercent: case CalcPercent:
...@@ -226,6 +229,8 @@ CSSPrimitiveValue::UnitType CSSPrimitiveValue::primitiveType() const ...@@ -226,6 +229,8 @@ CSSPrimitiveValue::UnitType CSSPrimitiveValue::primitiveType() const
return CSS_CALC_PERCENTAGE_WITH_NUMBER; return CSS_CALC_PERCENTAGE_WITH_NUMBER;
case CalcPercentLength: case CalcPercentLength:
return CSS_CALC_PERCENTAGE_WITH_LENGTH; return CSS_CALC_PERCENTAGE_WITH_LENGTH;
case CalcTime:
return CSS_MS;
case CalcOther: case CalcOther:
return CSS_UNKNOWN; return CSS_UNKNOWN;
} }
...@@ -535,9 +540,23 @@ void CSSPrimitiveValue::cleanup() ...@@ -535,9 +540,23 @@ void CSSPrimitiveValue::cleanup()
} }
} }
double CSSPrimitiveValue::computeSeconds()
{
ASSERT(isTime() || (isCalculated() && cssCalcValue()->category() == CalcTime));
UnitType currentType = isCalculated() ? cssCalcValue()->expressionNode()->primitiveType() : static_cast<UnitType>(m_primitiveUnitType);
if (currentType == CSS_S)
return getDoubleValue();
if (currentType == CSS_MS)
return getDoubleValue() / 1000;
ASSERT_NOT_REACHED();
return 0;
}
double CSSPrimitiveValue::computeDegrees() double CSSPrimitiveValue::computeDegrees()
{ {
switch (m_primitiveUnitType) { ASSERT(isAngle() || (isCalculated() && cssCalcValue()->category() == CalcAngle));
UnitType currentType = isCalculated() ? cssCalcValue()->expressionNode()->primitiveType() : static_cast<UnitType>(m_primitiveUnitType);
switch (currentType) {
case CSS_DEG: case CSS_DEG:
return getDoubleValue(); return getDoubleValue();
case CSS_RAD: case CSS_RAD:
......
...@@ -273,21 +273,7 @@ public: ...@@ -273,21 +273,7 @@ public:
UnitType primitiveType() const; UnitType primitiveType() const;
double computeDegrees(); double computeDegrees();
double computeSeconds();
enum TimeUnit { Seconds, Milliseconds };
template <typename T, TimeUnit timeUnit> T computeTime()
{
if (timeUnit == Seconds && m_primitiveUnitType == CSS_S)
return getValue<T>();
if (timeUnit == Seconds && m_primitiveUnitType == CSS_MS)
return getValue<T>() / 1000;
if (timeUnit == Milliseconds && m_primitiveUnitType == CSS_MS)
return getValue<T>();
if (timeUnit == Milliseconds && m_primitiveUnitType == CSS_S)
return getValue<T>() * 1000;
ASSERT_NOT_REACHED();
return 0;
}
/* /*
* Computes a length in pixels out of the given CSSValue * Computes a length in pixels out of the given CSSValue
......
...@@ -225,6 +225,15 @@ bool CSSPropertyParser::validCalculationUnit(CSSParserValue* value, Units unitfl ...@@ -225,6 +225,15 @@ bool CSSPropertyParser::validCalculationUnit(CSSParserValue* value, Units unitfl
case CalcPercentNumber: case CalcPercentNumber:
b = (unitflags & FPercent) && (unitflags & FNumber); b = (unitflags & FPercent) && (unitflags & FNumber);
break; break;
case CalcAngle:
b = (unitflags & FAngle);
break;
case CalcTime:
b = (unitflags & FTime);
break;
case CalcFrequency:
b = (unitflags & FFrequency);
break;
case CalcOther: case CalcOther:
break; break;
} }
......
...@@ -291,7 +291,7 @@ double CSSToStyleMap::mapAnimationDelay(CSSValue* value) ...@@ -291,7 +291,7 @@ double CSSToStyleMap::mapAnimationDelay(CSSValue* value)
{ {
if (value->isInitialValue()) if (value->isInitialValue())
return CSSTimingData::initialDelay(); return CSSTimingData::initialDelay();
return toCSSPrimitiveValue(value)->computeTime<double, CSSPrimitiveValue::Seconds>(); return toCSSPrimitiveValue(value)->computeSeconds();
} }
Timing::PlaybackDirection CSSToStyleMap::mapAnimationDirection(CSSValue* value) Timing::PlaybackDirection CSSToStyleMap::mapAnimationDirection(CSSValue* value)
...@@ -318,7 +318,7 @@ double CSSToStyleMap::mapAnimationDuration(CSSValue* value) ...@@ -318,7 +318,7 @@ double CSSToStyleMap::mapAnimationDuration(CSSValue* value)
{ {
if (value->isInitialValue()) if (value->isInitialValue())
return CSSTimingData::initialDuration(); return CSSTimingData::initialDuration();
return toCSSPrimitiveValue(value)->computeTime<double, CSSPrimitiveValue::Seconds>(); return toCSSPrimitiveValue(value)->computeSeconds();
} }
Timing::FillMode CSSToStyleMap::mapAnimationFillMode(CSSValue* value) Timing::FillMode CSSToStyleMap::mapAnimationFillMode(CSSValue* value)
......
...@@ -1034,7 +1034,7 @@ void StyleBuilderFunctions::applyValueCSSPropertyInternalMarqueeSpeed(StyleResol ...@@ -1034,7 +1034,7 @@ void StyleBuilderFunctions::applyValueCSSPropertyInternalMarqueeSpeed(StyleResol
break; break;
} }
} else if (primitiveValue->isTime()) { } else if (primitiveValue->isTime()) {
state.style()->setMarqueeSpeed(primitiveValue->computeTime<int, CSSPrimitiveValue::Milliseconds>()); state.style()->setMarqueeSpeed(static_cast<int>(primitiveValue->computeSeconds()) * 1000);
} else if (primitiveValue->isNumber()) { // For scrollamount support. } else if (primitiveValue->isNumber()) { // For scrollamount support.
state.style()->setMarqueeSpeed(primitiveValue->getIntValue()); state.style()->setMarqueeSpeed(primitiveValue->getIntValue());
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment