Bug 1074522 - Implement ellipse()/circle() parsing and style computing. r=dbaron

This commit is contained in:
Dirk Schulze 2014-10-15 00:03:00 +02:00
parent 63ad03e0f0
commit 62628a69a2
11 changed files with 352 additions and 32 deletions

View File

@ -106,6 +106,9 @@ PEColorSaturationEOF=saturation
PEColorLightnessEOF=lightness
PEColorOpacityEOF=opacity in color value
PEExpectedNumber=Expected a number but found '%1$S'.
PEPositionEOF=<position>
PEExpectedPosition=Expected <position> but found '%1$S'.
PEExpectedRadius=Expected radius but found '%1$S'.
PEExpectedCloseParen=Expected ')' but found '%1$S'.
PEDeclEndEOF=';' or '}' to end declaration
PEParseDeclarationNoColon=Expected ':' but found '%1$S'.

View File

@ -965,6 +965,7 @@ protected:
/* Functions for basic shapes */
bool ParseBasicShape(nsCSSValue& aValue, bool* aConsumedTokens);
bool ParsePolygonFunction(nsCSSValue& aValue);
bool ParseCircleOrEllipseFunction(nsCSSKeyword, nsCSSValue& aValue);
/* Functions for transform Parsing */
bool ParseSingleTransform(bool aIsPrefixed, nsCSSValue& aValue);
@ -13843,6 +13844,63 @@ CSSParserImpl::ParsePolygonFunction(nsCSSValue& aValue)
return true;
}
bool
CSSParserImpl::ParseCircleOrEllipseFunction(nsCSSKeyword aKeyword,
nsCSSValue& aValue)
{
nsCSSValue radiusX, radiusY, position;
bool hasRadius = false, hasPosition = false;
int32_t mask = VARIANT_LPCALC | VARIANT_NONNEGATIVE_DIMENSION |
VARIANT_KEYWORD;
if (ParseVariant(radiusX, mask, nsCSSProps::kShapeRadiusKTable)) {
if (aKeyword == eCSSKeyword_ellipse) {
if (!ParseVariant(radiusY, mask, nsCSSProps::kShapeRadiusKTable)) {
REPORT_UNEXPECTED_TOKEN(PEExpectedRadius);
SkipUntil(')');
return false;
}
}
hasRadius = true;
}
if (!ExpectSymbol(')', true)) {
if (!GetToken(true)) {
REPORT_UNEXPECTED_EOF(PEPositionEOF);
return false;
}
if (mToken.mType != eCSSToken_Ident ||
!mToken.mIdent.LowerCaseEqualsLiteral("at") ||
!ParsePositionValue(position)) {
REPORT_UNEXPECTED_TOKEN(PEExpectedPosition);
SkipUntil(')');
return false;
}
if (!ExpectSymbol(')', true)) {
REPORT_UNEXPECTED_TOKEN(PEExpectedCloseParen);
SkipUntil(')');
return false;
}
hasPosition = true;
}
size_t count = aKeyword == eCSSKeyword_circle ? 2 : 3;
nsRefPtr<nsCSSValue::Array> functionArray =
aValue.InitFunction(aKeyword, count);
if (hasRadius) {
functionArray->Item(1) = radiusX;
if (aKeyword == eCSSKeyword_ellipse) {
functionArray->Item(2) = radiusY;
}
}
if (hasPosition) {
functionArray->Item(count) = position;
}
return true;
}
bool
CSSParserImpl::ParseBasicShape(nsCSSValue& aValue, bool* aConsumedTokens)
{
@ -13861,6 +13919,9 @@ CSSParserImpl::ParseBasicShape(nsCSSValue& aValue, bool* aConsumedTokens)
switch (keyword) {
case eCSSKeyword_polygon:
return ParsePolygonFunction(aValue);
case eCSSKeyword_circle:
case eCSSKeyword_ellipse:
return ParseCircleOrEllipseFunction(keyword, aValue);
default:
return false;
}

View File

@ -1891,6 +1891,12 @@ const KTableValue nsCSSProps::kClipShapeSizingKTable[] = {
eCSSKeyword_UNKNOWN,-1
};
const KTableValue nsCSSProps::kShapeRadiusKTable[] = {
eCSSKeyword_closest_side, NS_RADIUS_CLOSEST_SIDE,
eCSSKeyword_farthest_side, NS_RADIUS_FARTHEST_SIDE,
eCSSKeyword_UNKNOWN, -1
};
const KTableValue nsCSSProps::kFilterFunctionKTable[] = {
eCSSKeyword_blur, NS_STYLE_FILTER_BLUR,
eCSSKeyword_brightness, NS_STYLE_FILTER_BRIGHTNESS,

View File

@ -537,6 +537,7 @@ public:
static const KTableValue kBoxPackKTable[];
static const KTableValue kClipShapeSizingKTable[];
static const KTableValue kDominantBaselineKTable[];
static const KTableValue kShapeRadiusKTable[];
static const KTableValue kFillRuleKTable[];
static const KTableValue kFilterFunctionKTable[];
static const KTableValue kImageRenderingKTable[];

View File

@ -852,6 +852,56 @@ nsCSSValue::AppendPolygonToString(nsCSSProperty aProperty, nsAString& aResult,
array->Item(index).AppendToString(aProperty, aResult, aSerialization);
}
inline void
nsCSSValue::AppendPositionCoordinateToString(
const nsCSSValue& aValue, nsCSSProperty aProperty,
nsAString& aResult, Serialization aSerialization) const
{
if (aValue.GetUnit() == eCSSUnit_Enumerated) {
int32_t intValue = aValue.GetIntValue();
AppendASCIItoUTF16(nsCSSProps::ValueToKeyword(intValue,
nsCSSProps::kShapeRadiusKTable), aResult);
} else {
aValue.AppendToString(aProperty, aResult, aSerialization);
}
}
void
nsCSSValue::AppendCircleOrEllipseToString(nsCSSKeyword aFunctionId,
nsCSSProperty aProperty,
nsAString& aResult,
Serialization aSerialization) const
{
const nsCSSValue::Array* array = GetArrayValue();
size_t count = aFunctionId == eCSSKeyword_circle ? 2 : 3;
NS_ABORT_IF_FALSE(array->Count() == count + 1, "wrong number of arguments");
bool hasRadii = array->Item(1).GetUnit() != eCSSUnit_Null;
AppendPositionCoordinateToString(array->Item(1), aProperty,
aResult, aSerialization);
if (hasRadii && aFunctionId == eCSSKeyword_ellipse) {
aResult.Append(' ');
AppendPositionCoordinateToString(array->Item(2), aProperty,
aResult, aSerialization);
}
// Any position specified?
if (array->Item(count).GetUnit() != eCSSUnit_Array) {
NS_ABORT_IF_FALSE(array->Item(count).GetUnit() == eCSSUnit_Null,
"unexpected value");
return;
}
if (hasRadii) {
aResult.Append(' ');
}
aResult.AppendLiteral("at ");
array->Item(count).AppendToString(eCSSProperty_background_position,
aResult, aSerialization);
}
void
nsCSSValue::AppendToString(nsCSSProperty aProperty, nsAString& aResult,
Serialization aSerialization) const
@ -991,6 +1041,12 @@ nsCSSValue::AppendToString(nsCSSProperty aProperty, nsAString& aResult,
AppendPolygonToString(aProperty, aResult, aSerialization);
break;
case eCSSKeyword_circle:
case eCSSKeyword_ellipse:
AppendCircleOrEllipseToString(functionId, aProperty, aResult,
aSerialization);
break;
default: {
// Now, step through the function contents, writing each of
// them as we go.

View File

@ -721,7 +721,14 @@ private:
void AppendPolygonToString(nsCSSProperty aProperty, nsAString& aResult,
Serialization aValueSerialization) const;
void AppendPositionCoordinateToString(const nsCSSValue& aValue,
nsCSSProperty aProperty,
nsAString& aResult,
Serialization aSerialization) const;
void AppendCircleOrEllipseToString(
nsCSSKeyword aFunctionId,
nsCSSProperty aProperty, nsAString& aResult,
Serialization aValueSerialization) const;
protected:
nsCSSUnit mUnit;
union {

View File

@ -5175,41 +5175,93 @@ nsComputedDOMStyle::DoGetStopColor()
return val;
}
inline void AppendBasicShapeTypeToString(nsStyleBasicShape::Type aType,
nsAutoString& aString)
{
nsCSSKeyword functionName;
switch (aType) {
case nsStyleBasicShape::Type::ePolygon:
functionName = eCSSKeyword_polygon;
break;
case nsStyleBasicShape::Type::eCircle:
functionName = eCSSKeyword_circle;
break;
case nsStyleBasicShape::Type::eEllipse:
functionName = eCSSKeyword_ellipse;
break;
default:
NS_NOTREACHED("unexpected type");
}
AppendASCIItoUTF16(nsCSSKeywords::GetStringValue(functionName),
aString);
}
CSSValue*
nsComputedDOMStyle::CreatePrimitiveValueForClipPath(
const nsStyleBasicShape* aStyleBasicShape, uint8_t aSizingBox)
{
nsDOMCSSValueList* valueList = GetROCSSValueList(false);
if (aStyleBasicShape &&
aStyleBasicShape->GetShapeType() == nsStyleBasicShape::Type::ePolygon) {
if (aStyleBasicShape) {
nsStyleBasicShape::Type type = aStyleBasicShape->GetShapeType();
// Shape function name and opening parenthesis.
nsAutoString shapeFunctionString;
AppendASCIItoUTF16(nsCSSKeywords::GetStringValue(eCSSKeyword_polygon),
shapeFunctionString);
AppendBasicShapeTypeToString(type, shapeFunctionString);
shapeFunctionString.Append('(');
bool hasEvenOdd = aStyleBasicShape->GetFillRule() ==
NS_STYLE_FILL_RULE_EVENODD;
if (hasEvenOdd) {
shapeFunctionString.AppendLiteral("evenodd");
}
for (size_t i = 0; i < aStyleBasicShape->Coordinates().Length(); i += 2) {
nsAutoString coordString;
if (i > 0 || hasEvenOdd) {
shapeFunctionString.AppendLiteral(", ");
switch (type) {
case nsStyleBasicShape::Type::ePolygon: {
bool hasEvenOdd = aStyleBasicShape->GetFillRule() ==
NS_STYLE_FILL_RULE_EVENODD;
if (hasEvenOdd) {
shapeFunctionString.AppendLiteral("evenodd");
}
for (size_t i = 0;
i < aStyleBasicShape->Coordinates().Length(); i += 2) {
nsAutoString coordString;
if (i > 0 || hasEvenOdd) {
shapeFunctionString.AppendLiteral(", ");
}
SetCssTextToCoord(coordString,
aStyleBasicShape->Coordinates()[i]);
shapeFunctionString.Append(coordString);
shapeFunctionString.Append(' ');
SetCssTextToCoord(coordString,
aStyleBasicShape->Coordinates()[i + 1]);
shapeFunctionString.Append(coordString);
}
break;
}
SetCssTextToCoord(coordString,
aStyleBasicShape->Coordinates()[i]);
shapeFunctionString.Append(coordString);
shapeFunctionString.Append(' ');
SetCssTextToCoord(coordString,
aStyleBasicShape->Coordinates()[i + 1]);
shapeFunctionString.Append(coordString);
case nsStyleBasicShape::Type::eCircle:
case nsStyleBasicShape::Type::eEllipse: {
const nsTArray<nsStyleCoord>& radii = aStyleBasicShape->Coordinates();
NS_ABORT_IF_FALSE(radii.Length() ==
(nsStyleBasicShape::Type::eCircle ? 1 : 2),
"wrong number of radii");
for (size_t i = 0; i < radii.Length(); ++i) {
nsAutoString radius;
nsRefPtr<nsROCSSPrimitiveValue> value = new nsROCSSPrimitiveValue;
bool clampNegativeCalc = true;
SetValueToCoord(value, radii[i], clampNegativeCalc, nullptr,
nsCSSProps::kShapeRadiusKTable);
value->GetCssText(radius);
shapeFunctionString.Append(radius);
shapeFunctionString.Append(' ');
}
shapeFunctionString.AppendLiteral("at ");
nsRefPtr<nsDOMCSSValueList> position = GetROCSSValueList(false);
nsAutoString positionString;
SetValueToPosition(aStyleBasicShape->GetPosition(), position);
position->GetCssText(positionString);
shapeFunctionString.Append(positionString);
break;
}
default:
NS_NOTREACHED("unexpected type");
}
shapeFunctionString.Append(')');
nsROCSSPrimitiveValue* val = new nsROCSSPrimitiveValue;
val->SetString(shapeFunctionString);
valueList->AppendCSSValue(val);
nsROCSSPrimitiveValue* functionValue = new nsROCSSPrimitiveValue;
functionValue->SetString(shapeFunctionString);
valueList->AppendCSSValue(functionValue);
}
if (aSizingBox == NS_STYLE_CLIP_SHAPE_SIZING_NOBOX) {

View File

@ -8801,6 +8801,7 @@ nsRuleNode::SetStyleClipPathToCSSValue(nsStyleClipPath* aStyleClipPath,
nsCSSKeyword functionName =
(nsCSSKeyword)shapeFunction->Item(0).GetIntValue();
if (functionName == eCSSKeyword_polygon) {
NS_ABORT_IF_FALSE(!basicShape, "did not expect value");
basicShape = new nsStyleBasicShape(nsStyleBasicShape::ePolygon);
NS_ABORT_IF_FALSE(shapeFunction->Count() > 1,
"polygon has wrong number of arguments");
@ -8830,6 +8831,46 @@ nsRuleNode::SetStyleClipPathToCSSValue(nsStyleClipPath* aStyleClipPath,
NS_ABORT_IF_FALSE(didSetCoordY, "unexpected y coordinate unit");
curPair = curPair->mNext;
}
} else if (functionName == eCSSKeyword_circle ||
functionName == eCSSKeyword_ellipse) {
nsStyleBasicShape::Type type = functionName == eCSSKeyword_circle ?
nsStyleBasicShape::eCircle :
nsStyleBasicShape::eEllipse;
NS_ABORT_IF_FALSE(!basicShape, "did not expect value");
basicShape = new nsStyleBasicShape(type);
int32_t mask = SETCOORD_PERCENT | SETCOORD_LENGTH |
SETCOORD_STORE_CALC | SETCOORD_ENUMERATED;
size_t count = type == nsStyleBasicShape::eCircle ? 2 : 3;
NS_ABORT_IF_FALSE(shapeFunction->Count() == count + 1,
"unexpected arguments count");
NS_ABORT_IF_FALSE(type == nsStyleBasicShape::eCircle ||
(shapeFunction->Item(1).GetUnit() == eCSSUnit_Null) ==
(shapeFunction->Item(2).GetUnit() == eCSSUnit_Null),
"ellipse should have two radii or none");
for (size_t j = 1; j < count; ++j) {
const nsCSSValue& val = shapeFunction->Item(j);
nsStyleCoord radius;
if (val.GetUnit() != eCSSUnit_Null) {
DebugOnly<bool> didSetRadius = SetCoord(val, radius,
nsStyleCoord(), mask,
aStyleContext,
aPresContext,
aCanStoreInRuleTree);
NS_ABORT_IF_FALSE(didSetRadius, "unexpected radius unit");
} else {
radius.SetIntValue(NS_RADIUS_CLOSEST_SIDE, eStyleUnit_Enumerated);
}
basicShape->Coordinates().AppendElement(radius);
}
const nsCSSValue& positionVal = shapeFunction->Item(count);
if (positionVal.GetUnit() == eCSSUnit_Array) {
ComputePositionValue(aStyleContext, positionVal,
basicShape->GetPosition(),
aCanStoreInRuleTree);
} else {
NS_ABORT_IF_FALSE(positionVal.GetUnit() == eCSSUnit_Null,
"expected no value");
}
} else {
// XXX Handle more basic shape functions later.
NS_NOTREACHED("unexpected basic shape function");

View File

@ -77,8 +77,8 @@ static inline mozilla::css::Side operator++(mozilla::css::Side& side, int) {
// Basic Shapes
#define NS_STYLE_BASIC_SHAPE_POLYGON 0
//#define NS_STYLE_BASIC_SHAPE_CIRCLE 1
//#define NS_STYLE_BASIC_SHAPE_ELLIPSE 2
#define NS_STYLE_BASIC_SHAPE_CIRCLE 1
#define NS_STYLE_BASIC_SHAPE_ELLIPSE 2
//#define NS_STYLE_BASIC_SHAPE_INSET 3
// box-shadow
@ -155,6 +155,9 @@ static inline mozilla::css::Side operator++(mozilla::css::Side& side, int) {
#define NS_STYLE_ORIENT_VERTICAL 1
#define NS_STYLE_ORIENT_AUTO 2
#define NS_RADIUS_FARTHEST_SIDE 0
#define NS_RADIUS_CLOSEST_SIDE 1
// stack-sizing
#define NS_STYLE_STACK_SIZING_IGNORE 0
#define NS_STYLE_STACK_SIZING_STRETCH_TO_FIT 1

View File

@ -2831,14 +2831,15 @@ class nsStyleBasicShape MOZ_FINAL {
public:
enum Type {
// eInset,
// eCircle,
// eEllipse,
eCircle,
eEllipse,
ePolygon
};
explicit nsStyleBasicShape(Type type)
: mType(type)
{
mPosition.SetInitialPercentValues(0.5f);
}
Type GetShapeType() const { return mType; }
@ -2850,15 +2851,31 @@ public:
mFillRule = aFillRule;
}
typedef nsStyleBackground::Position Position;
Position& GetPosition() {
NS_ASSERTION(mType == eCircle || mType == eEllipse,
"expected circle or ellipse");
return mPosition;
}
const Position& GetPosition() const {
NS_ASSERTION(mType == eCircle || mType == eEllipse,
"expected circle or ellipse");
return mPosition;
}
// mCoordinates has coordinates for polygon or radii for
// ellipse and circle.
nsTArray<nsStyleCoord>& Coordinates()
{
NS_ASSERTION(mType == ePolygon, "expected polygon");
NS_ASSERTION(mType == ePolygon || mType == eCircle || mType == eEllipse,
"expected polygon, circle or ellipse");
return mCoordinates;
}
const nsTArray<nsStyleCoord>& Coordinates() const
{
NS_ASSERTION(mType == ePolygon, "expected polygon");
NS_ASSERTION(mType == ePolygon || mType == eCircle || mType == eEllipse,
"expected polygon, circle or ellipse");
return mCoordinates;
}
@ -2866,7 +2883,8 @@ public:
{
return mType == aOther.mType &&
mFillRule == aOther.mFillRule &&
mCoordinates == aOther.mCoordinates;
mCoordinates == aOther.mCoordinates &&
mPosition == aOther.mPosition;
}
bool operator!=(const nsStyleBasicShape& aOther) const {
return !(*this == aOther);
@ -2879,7 +2897,10 @@ private:
Type mType;
int32_t mFillRule;
// mCoordinates has coordinates for polygon or radii for
// ellipse and circle.
nsTArray<nsStyleCoord> mCoordinates;
Position mPosition;
};
struct nsStyleClipPath

View File

@ -4622,6 +4622,39 @@ if (SpecialPowers.getBoolPref("layout.css.clip-path-shapes.enabled")) {
"polygon(evenodd, 20pt 20cm) fill-box",
"polygon(evenodd, 20ex 20pc) stroke-box",
"polygon(evenodd, 20rem 20in) view-box",
"circle()",
"circle(at center)",
"circle(at top left 20px)",
"circle(at bottom right)",
"circle(20%)",
"circle(300px)",
"circle(calc(20px + 30px))",
"circle(farthest-side)",
"circle(closest-side)",
"circle(closest-side at center)",
"circle(farthest-side at top)",
"circle(20px at top right)",
"circle(40% at 50% 100%)",
"circle(calc(20% + 20%) at right bottom)",
"circle() padding-box",
"ellipse()",
"ellipse(at center)",
"ellipse(at top left 20px)",
"ellipse(at bottom right)",
"ellipse(20% 20%)",
"ellipse(300px 50%)",
"ellipse(calc(20px + 30px) 10%)",
"ellipse(farthest-side closest-side)",
"ellipse(closest-side farthest-side)",
"ellipse(farthest-side farthest-side)",
"ellipse(closest-side closest-side)",
"ellipse(closest-side closest-side at center)",
"ellipse(20% farthest-side at top)",
"ellipse(20px 50% at top right)",
"ellipse(closest-side 40% at 50% 100%)",
"ellipse(calc(20% + 20%) calc(20px + 20cm) at right bottom)",
],
invalid_values: [
"url(#test) url(#tes2)",
@ -4656,11 +4689,47 @@ if (SpecialPowers.getBoolPref("layout.css.clip-path-shapes.enabled")) {
"margin-box farthest-side",
"nonsense() border-box",
"border-box nonsense()",
"circle(at)",
"circle(at 20% 20% 30%)",
"circle(20px 2px at center)",
"circle(2at center)",
"circle(closest-corner)",
"circle(at center top closest-side)",
"circle(-20px)",
"circle(farthest-side closest-side)",
"circle(20% 20%)",
"circle(at farthest-side)",
"ellipse(at)",
"ellipse(at 20% 20% 30%)",
"ellipse(20px at center)",
"ellipse(-20px 20px)",
"ellipse(closest-corner farthest-corner)",
"ellipse(20px -20px)",
"ellipse(-20px -20px)",
"ellipse(farthest-side)",
"ellipse(20%)",
"ellipse(at farthest-side farthest-side)",
"polygon(at)",
"polygon(at 20% 20% 30%)",
"polygon(20px at center)",
"polygon(2px 2at center)",
"polygon(closest-corner farthest-corner)",
"polygon(at center top closest-side closest-side)",
"polygon(40% at 50% 100%)",
"polygon(40% farthest-side 20px at 50% 100%)",
],
unbalanced_values: [
"polygon(30% 30%",
"polygon(nonzero, 20% 20px",
"polygon(evenodd, 20px 20px",
"circle(",
"circle(40% at 50% 100%",
"ellipse(",
"ellipse(40% at 50% 100%",
]
};
}