Bug 785606 - Support viewBox=none from SVG 1.2 Tiny r=jwatt

This commit is contained in:
Robert Longson 2013-02-26 16:58:06 +00:00
parent f12e7d74a0
commit 1216de8a05
13 changed files with 155 additions and 88 deletions

View File

@ -321,7 +321,7 @@ SVGMarkerElement::GetMarkerTransform(float aStrokeWidth,
nsSVGViewBoxRect
SVGMarkerElement::GetViewBoxRect()
{
if (mViewBox.IsExplicitlySet()) {
if (mViewBox.HasRect()) {
return mViewBox.GetAnimValue();
}
return nsSVGViewBoxRect(

View File

@ -847,12 +847,12 @@ nsSVGViewBoxRect
SVGSVGElement::GetViewBoxWithSynthesis(
float aViewportWidth, float aViewportHeight) const
{
// The logic here should match HasViewBox().
// The logic here should match HasViewBoxRect().
SVGViewElement* viewElement = GetCurrentViewElement();
if (viewElement && viewElement->mViewBox.IsExplicitlySet()) {
if (viewElement && viewElement->mViewBox.HasRect()) {
return viewElement->mViewBox.GetAnimValue();
}
if (mViewBox.IsExplicitlySet()) {
if (mViewBox.HasRect()) {
return mViewBox.GetAnimValue();
}
@ -885,11 +885,11 @@ SVGSVGElement::GetPreserveAspectRatioWithOverride() const
SVGViewElement* viewElement = GetCurrentViewElement();
// This check is equivalent to "!HasViewBox() && ShouldSynthesizeViewBox()".
// We're just holding onto the viewElement that HasViewBox() would look up,
// This check is equivalent to "!HasViewBoxRect() && ShouldSynthesizeViewBox()".
// We're just holding onto the viewElement that HasViewBoxRect() would look up,
// so that we don't have to look it up again later.
if (!((viewElement && viewElement->mViewBox.IsExplicitlySet()) ||
mViewBox.IsExplicitlySet()) &&
if (!((viewElement && viewElement->mViewBox.HasRect()) ||
mViewBox.HasRect()) &&
ShouldSynthesizeViewBox()) {
// If we're synthesizing a viewBox, use preserveAspectRatio="none";
return SVGPreserveAspectRatio(SVG_PRESERVEASPECTRATIO_NONE, SVG_MEETORSLICE_SLICE);
@ -912,10 +912,10 @@ SVGSVGElement::GetLength(uint8_t aCtxType)
SVGViewElement* viewElement = GetCurrentViewElement();
const nsSVGViewBoxRect* viewbox = nullptr;
// The logic here should match HasViewBox().
if (viewElement && viewElement->mViewBox.IsExplicitlySet()) {
// The logic here should match HasViewBoxRect().
if (viewElement && viewElement->mViewBox.HasRect()) {
viewbox = &viewElement->mViewBox.GetAnimValue();
} else if (mViewBox.IsExplicitlySet()) {
} else if (mViewBox.HasRect()) {
viewbox = &mViewBox.GetAnimValue();
}
@ -1029,19 +1029,19 @@ SVGSVGElement::GetPreserveAspectRatio()
}
bool
SVGSVGElement::HasViewBox() const
SVGSVGElement::HasViewBoxRect() const
{
SVGViewElement* viewElement = GetCurrentViewElement();
if (viewElement && viewElement->mViewBox.IsExplicitlySet()) {
if (viewElement && viewElement->mViewBox.HasRect()) {
return true;
}
return mViewBox.IsExplicitlySet();
return mViewBox.HasRect();
}
bool
SVGSVGElement::ShouldSynthesizeViewBox() const
{
NS_ABORT_IF_FALSE(!HasViewBox(),
NS_ABORT_IF_FALSE(!HasViewBoxRect(),
"Should only be called if we lack a viewBox");
nsIDocument* doc = GetCurrentDoc();
@ -1110,8 +1110,8 @@ SVGSVGElement::
"should only override preserveAspectRatio in images");
#endif
bool hasViewBox = HasViewBox();
if (!hasViewBox && ShouldSynthesizeViewBox()) {
bool hasViewBoxRect = HasViewBoxRect();
if (!hasViewBoxRect && ShouldSynthesizeViewBox()) {
// My non-<svg:image> clients will have been painting me with a synthesized
// viewBox, but my <svg:image> client that's about to paint me now does NOT
// want that. Need to tell ourselves to flush our transform.
@ -1119,7 +1119,7 @@ SVGSVGElement::
}
mIsPaintingSVGImageElement = true;
if (!hasViewBox) {
if (!hasViewBoxRect) {
return; // preserveAspectRatio irrelevant (only matters if we have viewBox)
}
@ -1141,7 +1141,7 @@ SVGSVGElement::ClearImageOverridePreserveAspectRatio()
#endif
mIsPaintingSVGImageElement = false;
if (!HasViewBox() && ShouldSynthesizeViewBox()) {
if (!HasViewBoxRect() && ShouldSynthesizeViewBox()) {
// My non-<svg:image> clients will want to paint me with a synthesized
// viewBox, but my <svg:image> client that just painted me did NOT
// use that. Need to tell ourselves to flush our transform.

View File

@ -154,19 +154,19 @@ public:
* Note also that this method does not pay attention to whether the width or
* height values of the viewBox rect are positive!
*/
bool HasViewBox() const;
bool HasViewBoxRect() const;
/**
* Returns true if we should synthesize a viewBox for ourselves (that is, if
* we're the root element in an image document, and we're not currently being
* painted for an <svg:image> element).
*
* Only call this method if HasViewBox() returns false.
* Only call this method if HasViewBoxRect() returns false.
*/
bool ShouldSynthesizeViewBox() const;
bool HasViewBoxOrSyntheticViewBox() const {
return HasViewBox() || ShouldSynthesizeViewBox();
return HasViewBoxRect() || ShouldSynthesizeViewBox();
}
gfxMatrix GetViewBoxTransform() const;

View File

@ -18,8 +18,7 @@ SVGViewBoxSMILType::Init(nsSMILValue& aValue) const
{
NS_ABORT_IF_FALSE(aValue.IsNull(), "Unexpected value type");
nsSVGViewBoxRect* viewBox = new nsSVGViewBoxRect();
aValue.mU.mPtr = viewBox;
aValue.mU.mPtr = new nsSVGViewBoxRect();
aValue.mType = this;
}
@ -84,6 +83,10 @@ SVGViewBoxSMILType::ComputeDistance(const nsSMILValue& aFrom,
const nsSVGViewBoxRect* from = static_cast<const nsSVGViewBoxRect*>(aFrom.mU.mPtr);
const nsSVGViewBoxRect* to = static_cast<const nsSVGViewBoxRect*>(aTo.mU.mPtr);
if (from->none || to->none) {
return NS_ERROR_FAILURE;
}
// We use the distances between the edges rather than the difference between
// the x, y, width and height for the "distance". This is necessary in
// order for the "distance" result that we calculate to be the same for a
@ -111,9 +114,14 @@ SVGViewBoxSMILType::Interpolate(const nsSMILValue& aStartVal,
NS_PRECONDITION(aStartVal.mType == this,
"Unexpected types for interpolation");
NS_PRECONDITION(aResult.mType == this, "Unexpected result type");
const nsSVGViewBoxRect* start = static_cast<const nsSVGViewBoxRect*>(aStartVal.mU.mPtr);
const nsSVGViewBoxRect* end = static_cast<const nsSVGViewBoxRect*>(aEndVal.mU.mPtr);
if (start->none || end->none) {
return NS_ERROR_FAILURE;
}
nsSVGViewBoxRect* current = static_cast<nsSVGViewBoxRect*>(aResult.mU.mPtr);
float x = (start->x + (end->x - start->x) * aUnitDistance);

View File

@ -25,10 +25,12 @@ nsSVGViewBoxRect::operator==(const nsSVGViewBoxRect& aOther) const
if (&aOther == this)
return true;
return x == aOther.x &&
y == aOther.y &&
width == aOther.width &&
height == aOther.height;
return (none && aOther.none) ||
(!none && !aOther.none &&
x == aOther.x &&
y == aOther.y &&
width == aOther.width &&
height == aOther.height);
}
/* Cycle collection macros for nsSVGViewBox */
@ -79,24 +81,22 @@ static nsSVGAttrTearoffTable<nsSVGViewBox, nsSVGViewBox::DOMAnimVal>
void
nsSVGViewBox::Init()
{
mBaseVal = nsSVGViewBoxRect();
mAnimVal = nullptr;
mHasBaseVal = false;
mAnimVal = nullptr;
}
void
nsSVGViewBox::SetAnimValue(float aX, float aY, float aWidth, float aHeight,
nsSVGViewBox::SetAnimValue(const nsSVGViewBoxRect& aRect,
nsSVGElement *aSVGElement)
{
if (!mAnimVal) {
// it's okay if allocation fails - and no point in reporting that
mAnimVal = new nsSVGViewBoxRect(aX, aY, aWidth, aHeight);
mAnimVal = new nsSVGViewBoxRect(aRect);
} else {
nsSVGViewBoxRect rect(aX, aY, aWidth, aHeight);
if (rect == *mAnimVal) {
if (aRect == *mAnimVal) {
return;
}
*mAnimVal = rect;
*mAnimVal = aRect;
}
aSVGElement->DidAnimateViewBox();
}
@ -105,7 +105,12 @@ void
nsSVGViewBox::SetBaseValue(const nsSVGViewBoxRect& aRect,
nsSVGElement *aSVGElement)
{
if (mHasBaseVal && mBaseVal == aRect) {
if (!mHasBaseVal || mBaseVal == aRect) {
// This method is used to set a single x, y, width
// or height value. It can't create a base value
// as the other components may be undefined. We record
// the new value though, so as not to lose data.
mBaseVal = aRect;
return;
}
@ -123,6 +128,11 @@ nsSVGViewBox::SetBaseValue(const nsSVGViewBoxRect& aRect,
static nsresult
ToSVGViewBoxRect(const nsAString& aStr, nsSVGViewBoxRect *aViewBox)
{
if (aStr.EqualsLiteral("none")) {
aViewBox->none = true;
return NS_OK;
}
nsCharSeparatedTokenizerTemplate<IsSVGWhitespace>
tokenizer(aStr, ',',
nsCharSeparatedTokenizer::SEPARATOR_OPTIONAL);
@ -152,6 +162,7 @@ ToSVGViewBoxRect(const nsAString& aStr, nsSVGViewBoxRect *aViewBox)
aViewBox->y = vals[1];
aViewBox->width = vals[2];
aViewBox->height = vals[3];
aViewBox->none = false;
return NS_OK;
}
@ -162,28 +173,38 @@ nsSVGViewBox::SetBaseValueString(const nsAString& aValue,
bool aDoSetAttr)
{
nsSVGViewBoxRect viewBox;
nsresult res = ToSVGViewBoxRect(aValue, &viewBox);
if (NS_SUCCEEDED(res)) {
nsAttrValue emptyOrOldValue;
if (aDoSetAttr) {
emptyOrOldValue = aSVGElement->WillChangeViewBox();
}
mBaseVal = nsSVGViewBoxRect(viewBox.x, viewBox.y, viewBox.width, viewBox.height);
mHasBaseVal = true;
if (aDoSetAttr) {
aSVGElement->DidChangeViewBox(emptyOrOldValue);
}
if (mAnimVal) {
aSVGElement->AnimationNeedsResample();
}
nsresult rv = ToSVGViewBoxRect(aValue, &viewBox);
if (NS_FAILED(rv)) {
return rv;
}
return res;
if (viewBox == mBaseVal) {
return NS_OK;
}
nsAttrValue emptyOrOldValue;
if (aDoSetAttr) {
emptyOrOldValue = aSVGElement->WillChangeViewBox();
}
mHasBaseVal = true;
mBaseVal = viewBox;
if (aDoSetAttr) {
aSVGElement->DidChangeViewBox(emptyOrOldValue);
}
if (mAnimVal) {
aSVGElement->AnimationNeedsResample();
}
return NS_OK;
}
void
nsSVGViewBox::GetBaseValueString(nsAString& aValue) const
{
if (mBaseVal.none) {
aValue.AssignLiteral("none");
return;
}
PRUnichar buf[200];
nsTextFormatter::snprintf(buf, sizeof(buf)/sizeof(PRUnichar),
NS_LITERAL_STRING("%g %g %g %g").get(),
@ -215,6 +236,10 @@ nsSVGViewBox::DOMAnimatedRect::~DOMAnimatedRect()
nsresult
nsSVGViewBox::ToDOMBaseVal(nsIDOMSVGRect **aResult, nsSVGElement *aSVGElement)
{
if (!mHasBaseVal || mBaseVal.none) {
*aResult = nullptr;
return NS_OK;
}
nsRefPtr<DOMBaseVal> domBaseVal =
sBaseSVGViewBoxTearoffTable.GetTearoff(this);
if (!domBaseVal) {
@ -234,6 +259,11 @@ nsSVGViewBox::DOMBaseVal::~DOMBaseVal()
nsresult
nsSVGViewBox::ToDOMAnimVal(nsIDOMSVGRect **aResult, nsSVGElement *aSVGElement)
{
if ((mAnimVal && mAnimVal->none) ||
(!mAnimVal && (!mHasBaseVal || mBaseVal.none))) {
*aResult = nullptr;
return NS_OK;
}
nsRefPtr<DOMAnimVal> domAnimVal =
sAnimSVGViewBoxTearoffTable.GetTearoff(this);
if (!domAnimVal) {
@ -255,8 +285,7 @@ nsSVGViewBox::DOMBaseVal::SetX(float aX)
{
nsSVGViewBoxRect rect = mVal->GetBaseValue();
rect.x = aX;
mVal->SetBaseValue(rect.x, rect.y, rect.width, rect.height,
mSVGElement);
mVal->SetBaseValue(rect, mSVGElement);
return NS_OK;
}
@ -265,8 +294,7 @@ nsSVGViewBox::DOMBaseVal::SetY(float aY)
{
nsSVGViewBoxRect rect = mVal->GetBaseValue();
rect.y = aY;
mVal->SetBaseValue(rect.x, rect.y, rect.width, rect.height,
mSVGElement);
mVal->SetBaseValue(rect, mSVGElement);
return NS_OK;
}
@ -275,8 +303,7 @@ nsSVGViewBox::DOMBaseVal::SetWidth(float aWidth)
{
nsSVGViewBoxRect rect = mVal->GetBaseValue();
rect.width = aWidth;
mVal->SetBaseValue(rect.x, rect.y, rect.width, rect.height,
mSVGElement);
mVal->SetBaseValue(rect, mSVGElement);
return NS_OK;
}
@ -285,8 +312,7 @@ nsSVGViewBox::DOMBaseVal::SetHeight(float aHeight)
{
nsSVGViewBoxRect rect = mVal->GetBaseValue();
rect.height = aHeight;
mVal->SetBaseValue(rect.x, rect.y, rect.width, rect.height,
mSVGElement);
mVal->SetBaseValue(rect, mSVGElement);
return NS_OK;
}
@ -340,7 +366,7 @@ nsSVGViewBox::SMILViewBox::SetAnimValue(const nsSMILValue& aValue)
"Unexpected type to assign animated value");
if (aValue.mType == &SVGViewBoxSMILType::sSingleton) {
nsSVGViewBoxRect &vb = *static_cast<nsSVGViewBoxRect*>(aValue.mU.mPtr);
mVal->SetAnimValue(vb.x, vb.y, vb.width, vb.height, mSVGElement);
mVal->SetAnimValue(vb, mSVGElement);
}
return NS_OK;
}

View File

@ -22,12 +22,13 @@ struct nsSVGViewBoxRect
{
float x, y;
float width, height;
bool none;
nsSVGViewBoxRect() : x(0), y(0), width(0), height(0) {}
nsSVGViewBoxRect() : none(true) {}
nsSVGViewBoxRect(float aX, float aY, float aWidth, float aHeight) :
x(aX), y(aY), width(aWidth), height(aHeight) {}
x(aX), y(aY), width(aWidth), height(aHeight), none(false) {}
nsSVGViewBoxRect(const nsSVGViewBoxRect& rhs) :
x(rhs.x), y(rhs.y), width(rhs.width), height(rhs.height) {}
x(rhs.x), y(rhs.y), width(rhs.width), height(rhs.height), none(rhs.none) {}
bool operator==(const nsSVGViewBoxRect& aOther) const;
};
@ -48,19 +49,24 @@ public:
* positive, so callers must check whether the viewBox rect is valid where
* necessary!
*/
bool HasRect() const
{ return (mAnimVal && !mAnimVal->none) ||
(!mAnimVal && mHasBaseVal && !mBaseVal.none); }
/**
* Returns true if the corresponding "viewBox" attribute either defined a
* rectangle with finite values or the special "none" value.
*/
bool IsExplicitlySet() const
{ return (mHasBaseVal || mAnimVal); }
{ return mAnimVal || mHasBaseVal; }
const nsSVGViewBoxRect& GetBaseValue() const
{ return mBaseVal; }
void SetBaseValue(const nsSVGViewBoxRect& aRect,
nsSVGElement *aSVGElement);
void SetBaseValue(float aX, float aY, float aWidth, float aHeight,
nsSVGElement *aSVGElement)
{ SetBaseValue(nsSVGViewBoxRect(aX, aY, aWidth, aHeight), aSVGElement); }
const nsSVGViewBoxRect& GetAnimValue() const
{ return mAnimVal ? *mAnimVal : mBaseVal; }
void SetAnimValue(float aX, float aY, float aWidth, float aHeight,
void SetAnimValue(const nsSVGViewBoxRect& aRect,
nsSVGElement *aSVGElement);
nsresult SetBaseValueString(const nsAString& aValue,

View File

@ -248,19 +248,8 @@ function runTests()
// viewBox attribute
var baseViewBox = marker.viewBox.baseVal;
var animViewBox = marker.viewBox.animVal;
is(baseViewBox.x, 0, "viewBox baseVal");
is(animViewBox.x, 0, "viewBox baseVal");
is(baseViewBox.y, 0, "viewBox baseVal");
is(animViewBox.y, 0, "viewBox baseVal");
is(baseViewBox.width, 0, "viewBox baseVal");
is(animViewBox.width, 0, "viewBox baseVal");
is(baseViewBox.height, 0, "viewBox baseVal");
is(animViewBox.height, 0, "viewBox baseVal");
baseViewBox.x = 10;
baseViewBox.y = 11;
baseViewBox.width = 12;
baseViewBox.height = 13;
is(marker.getAttribute("viewBox"), "10 11 12 13", "viewBox attribute");
is(baseViewBox, null, "viewBox baseVal");
is(animViewBox, null, "viewBox animVal");
marker.setAttribute("viewBox", "1 2 3 4");
is(marker.viewBox.baseVal.x, 1, "viewBox.x baseVal");
@ -280,11 +269,28 @@ function runTests()
marker.viewBox.baseVal.height = 8;
is(marker.viewBox.animVal.height, 8, "viewBox.height animVal");
is(marker.getAttribute("viewBox"), "5 6 7 8", "viewBox attribute");
var storedViewBox = marker.viewBox.baseVal;
marker.removeAttribute("viewBox");
is(marker.hasAttribute("viewBox"), false, "viewBox hasAttribute");
ok(marker.getAttribute("viewBox") === null, "removed viewBox attribute");
is(marker.viewBox.baseVal, null, "viewBox baseVal");
is(marker.viewBox.animVal, null, "viewBox animVal");
is(storedViewBox.width, 7, "Should not lose values");
storedViewBox.width = 200;
is(storedViewBox.width, 200, "Should be able to change detached viewBox rect");
is(marker.hasAttribute("viewBox"), false, "viewBox hasAttribute should still be false");
ok(marker.getAttribute("viewBox") === null, "viewBox attribute should still be null");
is(marker.viewBox.baseVal, null, "viewBox baseVal");
is(marker.viewBox.animVal, null, "viewBox animVal");
marker.setAttribute("viewBox", "none");
is(marker.hasAttribute("viewBox"), true, "viewBox hasAttribute");
is(marker.viewBox.baseVal, null, "viewBox baseVal");
is(marker.viewBox.animVal, null, "viewBox animVal");
marker.setAttribute("viewBox", "");
is(marker.hasAttribute("viewBox"), true, "viewBox hasAttribute");
ok(marker.getAttribute("viewBox") === "", "empty viewBox attribute");
SimpleTest.finish();

View File

@ -236,7 +236,8 @@ function runTests()
marker.viewBox.baseVal.height = 5;
marker.removeAttribute("viewBox");
marker.removeAttributeNS(null, "viewBox");
marker.viewBox.baseVal.height = 4;
marker.setAttribute("viewBox", "none");
marker.setAttribute("viewBox", "none");
eventChecker.ignoreEvents();
marker.setAttribute("viewBox", "1 2 3 4");

View File

@ -39,6 +39,7 @@ function runTests()
new Test("svgView(viewBox(0,0,200,200))", true, "0 0 200 200", null, null),
new Test("svgView(preserveAspectRatio(xMaxYMin slice))", true, null, "xMaxYMin slice", null),
new Test("svgView(viewBox(1,2,3,4);preserveAspectRatio(xMinYMax))", true, "1 2 3 4", "xMinYMax meet", null),
new Test("svgView(viewBox(none))", true, "none", null, null),
new Test("svgView(zoomAndPan(disable))", true, null, null, "disable"),
new Test("svgView(transform(translate(-10,-20) scale(2) rotate(45) translate(5,10)))", true, null, null, null),
// No duplicates allowed

View File

@ -0,0 +1,18 @@
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
class="reftest-wait"
onload="setTimeAndSnapshot(12, true)">
<title>Test discrete animation of the "viewBox" attribute on the "svg" element</title>
<script xlink:href="smil-util.js" type="text/javascript"/>
<rect width="100%" height="100%" fill="lime"/>
<svg width="200" height="200" viewBox="200 0 150 50">
<animate attributeName="viewBox"
calcMode="discrete"
begin="10s" dur="4s"
values="200 0 150 50; none; 200 0 150 50"
fill="freeze"/>
<rect x="-100" y="-100" width="1000" height="1000" fill="red"/>
<rect width="200" height="200" fill="lime"/>
</svg>
</svg>

After

Width:  |  Height:  |  Size: 735 B

View File

@ -139,6 +139,7 @@ skip-if(B2G) == anim-feConvolveMatrix-preserveAlpha-01.svg lime.svg # bug 773482
# animate some viewBox attributes
== anim-svg-viewBox-01.svg lime.svg
== anim-svg-viewBox-02.svg lime.svg
== anim-svg-viewBox-03.svg lime.svg
== anim-view-01.svg#view lime.svg
# animate some preserveAspectRatio attributes

View File

@ -136,11 +136,11 @@ nsSVGInnerSVGFrame::NotifySVGChanged(uint32_t aFlags)
if (!(aFlags & TRANSFORM_CHANGED) &&
(xOrYIsPercentage ||
(widthOrHeightIsPercentage && svg->HasViewBox()))) {
(widthOrHeightIsPercentage && svg->HasViewBoxRect()))) {
aFlags |= TRANSFORM_CHANGED;
}
if (svg->HasViewBox() || !widthOrHeightIsPercentage) {
if (svg->HasViewBoxRect() || !widthOrHeightIsPercentage) {
// Remove COORD_CONTEXT_CHANGED, since we establish the coordinate
// context for our descendants and this notification won't change its
// dimensions:

View File

@ -287,9 +287,9 @@ nsSVGOuterSVGFrame::GetIntrinsicRatio()
const nsSVGViewBoxRect* viewbox = nullptr;
// The logic here should match HasViewBox().
if (viewElement && viewElement->mViewBox.IsExplicitlySet()) {
if (viewElement && viewElement->mViewBox.HasRect()) {
viewbox = &viewElement->mViewBox.GetAnimValue();
} else if (content->mViewBox.IsExplicitlySet()) {
} else if (content->mViewBox.HasRect()) {
viewbox = &content->mViewBox.GetAnimValue();
}
@ -754,7 +754,7 @@ nsSVGOuterSVGFrame::NotifyViewportOrTransformChanged(uint32_t aFlags)
SVGSVGElement *content = static_cast<SVGSVGElement*>(mContent);
if (aFlags & COORD_CONTEXT_CHANGED) {
if (content->HasViewBox()) {
if (content->HasViewBoxRect()) {
// Percentage lengths on children resolve against the viewBox rect so we
// don't need to notify them of the viewport change, but the viewBox
// transform will have changed, so we need to notify them of that instead.