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)
case CSSPrimitiveValue::CSS_VMIN:
case CSSPrimitiveValue::CSS_VMAX:
return CalcLength;
// FIXME: Support angle, time and frequency units.
// http://www.w3.org/TR/css3-values/#calc-notation
case CSSPrimitiveValue::CSS_DEG:
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:
return CalcOther;
}
......@@ -96,6 +105,7 @@ static bool hasDoubleValue(CSSPrimitiveValue::UnitType type)
case CSSPrimitiveValue::CSS_DEG:
case CSSPrimitiveValue::CSS_RAD:
case CSSPrimitiveValue::CSS_GRAD:
case CSSPrimitiveValue::CSS_TURN:
case CSSPrimitiveValue::CSS_MS:
case CSSPrimitiveValue::CSS_S:
case CSSPrimitiveValue::CSS_HZ:
......@@ -123,7 +133,6 @@ static bool hasDoubleValue(CSSPrimitiveValue::UnitType type)
case CSSPrimitiveValue::CSS_PARSER_OPERATOR:
case CSSPrimitiveValue::CSS_PARSER_HEXCOLOR:
case CSSPrimitiveValue::CSS_PARSER_IDENTIFIER:
case CSSPrimitiveValue::CSS_TURN:
case CSSPrimitiveValue::CSS_COUNTER_NAME:
case CSSPrimitiveValue::CSS_SHAPE:
case CSSPrimitiveValue::CSS_QUAD:
......@@ -232,11 +241,14 @@ public:
switch (m_category) {
case CalcLength:
return m_value->computeLength<double>(conversionData);
case CalcPercent:
case CalcNumber:
case CalcPercent:
return m_value->getDoubleValue();
case CalcAngle:
case CalcFrequency:
case CalcPercentLength:
case CalcPercentNumber:
case CalcTime:
case CalcOther:
ASSERT_NOT_REACHED();
break;
......@@ -283,12 +295,15 @@ private:
};
static const CalculationCategory addSubtractResult[CalcOther][CalcOther] = {
// CalcNumber CalcLength CalcPercent CalcPercentNumber CalcPercentLength
/* CalcNumber */ { CalcNumber, CalcOther, CalcPercentNumber, CalcPercentNumber, CalcOther },
/* CalcLength */ { CalcOther, CalcLength, CalcPercentLength, CalcOther, CalcPercentLength },
/* CalcPercent */ { CalcPercentNumber, CalcPercentLength, CalcPercent, CalcPercentNumber, CalcPercentLength },
/* CalcPercentNumber */ { CalcPercentNumber, CalcOther, CalcPercentNumber, CalcPercentNumber, CalcOther },
/* CalcPercentLength */ { CalcOther, CalcPercentLength, CalcPercentLength, CalcOther, CalcPercentLength },
// CalcNumber CalcLength CalcPercent CalcPercentNumber CalcPercentLength CalcAngle CalcTime CalcFrequency
/* CalcNumber */ { CalcNumber, CalcOther, CalcPercentNumber, CalcPercentNumber, CalcOther, CalcOther, CalcOther, CalcOther },
/* CalcLength */ { CalcOther, CalcLength, CalcPercentLength, CalcOther, CalcPercentLength, CalcOther, CalcOther, CalcOther },
/* CalcPercent */ { CalcPercentNumber, CalcPercentLength, CalcPercent, CalcPercentNumber, CalcPercentLength, CalcOther, CalcOther, CalcOther },
/* CalcPercentNumber */ { CalcPercentNumber, CalcOther, CalcPercentNumber, CalcPercentNumber, CalcOther, CalcOther, CalcOther, CalcOther },
/* 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)
......@@ -517,6 +532,12 @@ public:
return leftType;
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 CalcPercentNumber:
case CalcOther:
......
......@@ -54,12 +54,17 @@ enum CalcOperator {
CalcDivide = '/'
};
// The order of this enum should not change since its elements are used as indices
// in the addSubtractResult matrix.
enum CalculationCategory {
CalcNumber = 0,
CalcLength,
CalcPercent,
CalcPercentNumber,
CalcPercentLength,
CalcAngle,
CalcTime,
CalcFrequency,
CalcOther
};
......
......@@ -143,7 +143,6 @@ StringToUnitTable createStringToUnitTable()
return table;
}
CSSPrimitiveValue::UnitType CSSPrimitiveValue::fromName(const String& unit)
{
DEFINE_STATIC_LOCAL(StringToUnitTable, unitTable, (createStringToUnitTable()));
......@@ -216,6 +215,10 @@ CSSPrimitiveValue::UnitType CSSPrimitiveValue::primitiveType() const
return static_cast<UnitType>(m_primitiveUnitType);
switch (m_value.calc->category()) {
case CalcAngle:
return CSS_DEG;
case CalcFrequency:
return CSS_HZ;
case CalcNumber:
return CSS_NUMBER;
case CalcPercent:
......@@ -226,6 +229,8 @@ CSSPrimitiveValue::UnitType CSSPrimitiveValue::primitiveType() const
return CSS_CALC_PERCENTAGE_WITH_NUMBER;
case CalcPercentLength:
return CSS_CALC_PERCENTAGE_WITH_LENGTH;
case CalcTime:
return CSS_MS;
case CalcOther:
return CSS_UNKNOWN;
}
......@@ -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()
{
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:
return getDoubleValue();
case CSS_RAD:
......
......@@ -273,21 +273,7 @@ public:
UnitType primitiveType() const;
double computeDegrees();
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;
}
double computeSeconds();
/*
* Computes a length in pixels out of the given CSSValue
......
......@@ -225,6 +225,15 @@ bool CSSPropertyParser::validCalculationUnit(CSSParserValue* value, Units unitfl
case CalcPercentNumber:
b = (unitflags & FPercent) && (unitflags & FNumber);
break;
case CalcAngle:
b = (unitflags & FAngle);
break;
case CalcTime:
b = (unitflags & FTime);
break;
case CalcFrequency:
b = (unitflags & FFrequency);
break;
case CalcOther:
break;
}
......
......@@ -291,7 +291,7 @@ double CSSToStyleMap::mapAnimationDelay(CSSValue* value)
{
if (value->isInitialValue())
return CSSTimingData::initialDelay();
return toCSSPrimitiveValue(value)->computeTime<double, CSSPrimitiveValue::Seconds>();
return toCSSPrimitiveValue(value)->computeSeconds();
}
Timing::PlaybackDirection CSSToStyleMap::mapAnimationDirection(CSSValue* value)
......@@ -318,7 +318,7 @@ double CSSToStyleMap::mapAnimationDuration(CSSValue* value)
{
if (value->isInitialValue())
return CSSTimingData::initialDuration();
return toCSSPrimitiveValue(value)->computeTime<double, CSSPrimitiveValue::Seconds>();
return toCSSPrimitiveValue(value)->computeSeconds();
}
Timing::FillMode CSSToStyleMap::mapAnimationFillMode(CSSValue* value)
......
......@@ -1034,7 +1034,7 @@ void StyleBuilderFunctions::applyValueCSSPropertyInternalMarqueeSpeed(StyleResol
break;
}
} 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.
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