Bug 614649, patch 1: In SVG-as-an-image with no viewBox, use height & width attrs on <svg> to dynamically synthesize a viewBox. r=jwatt a=roc
authorDaniel Holbert <dholbert@cs.stanford.edu>
Wed, 09 Feb 2011 12:13:18 -0800
changeset 62244 78ca7c8ec7bd0c5d782dd2be6a35d2a56ecea9a4
parent 62243 36eb30154ca184d53b037a740e8c0c066dbe2d54
child 62245 21ee3d8dfada1e24fcc1490ae4b8e0a9fcb1db3d
push id1
push userroot
push dateTue, 10 Dec 2013 15:46:25 +0000
reviewersjwatt, roc
bugs614649
milestone2.0b12pre
Bug 614649, patch 1: In SVG-as-an-image with no viewBox, use height & width attrs on <svg> to dynamically synthesize a viewBox. r=jwatt a=roc
content/svg/content/src/nsSVGSVGElement.cpp
content/svg/content/src/nsSVGSVGElement.h
layout/svg/base/src/nsSVGOuterSVGFrame.cpp
layout/svg/base/src/nsSVGOuterSVGFrame.h
--- a/content/svg/content/src/nsSVGSVGElement.cpp
+++ b/content/svg/content/src/nsSVGSVGElement.cpp
@@ -209,16 +209,17 @@ nsSVGSVGElement::nsSVGSVGElement(already
     mCurrentScale(1.0f),
     mPreviousTranslate(0.0f, 0.0f),
     mPreviousScale(1.0f),
     mRedrawSuspendCount(0)
 #ifdef MOZ_SMIL
   , mStartAnimationOnBindToTree(!aFromParser)
 #endif // MOZ_SMIL
   , mImageNeedsTransformInvalidation(PR_FALSE)
+  , mIsPaintingSVGImageElement(PR_FALSE)
 {
 }
 
 //----------------------------------------------------------------------
 // nsIDOMNode methods
 
 // From NS_IMPL_ELEMENT_CLONE_WITH_INIT(nsSVGSVGElement)
 nsresult
@@ -957,49 +958,96 @@ nsSVGSVGElement::IsEventName(nsIAtom* aN
      'svg' element in case we're not inserted into the document yet, but since
      the target of the events in question will always be the outermost 'svg'
      element, this shouldn't cause any real problems.
   */
   return nsContentUtils::IsEventAttributeName(aName,
          (EventNameType_SVGGraphic | EventNameType_SVGSVG));
 }
 
+// Helper for GetViewBoxTransform on root <svg> node
+// * aLength: internal value for our <svg> width or height attribute.
+// * aViewportLength: length of the corresponding dimension of the viewport.
+// * aSelf: the outermost <svg> node itself.
+// NOTE: aSelf is not an ancestor viewport element, so it can't be used to
+// resolve percentage lengths. (It can only be used to resolve
+// 'em'/'ex'-valued units).
+inline float
+ComputeSynthesizedViewBoxDimension(nsSVGLength2& aLength,
+                                   float aViewportLength,
+                                   nsSVGSVGElement* aSelf)
+{
+  if (aLength.IsPercentage()) {
+    return aViewportLength * aLength.GetAnimValInSpecifiedUnits() / 100.0f;
+  }
+
+  return aLength.GetAnimValue(aSelf);
+}
+
 //----------------------------------------------------------------------
 // public helpers:
 
 gfxMatrix
 nsSVGSVGElement::GetViewBoxTransform()
 {
+  // Do we have an override preserveAspectRatio value?
+  const SVGPreserveAspectRatio* overridePARPtr =
+    GetImageOverridePreserveAspectRatio();
+
+  // May assign this to overridePARPtr if we have no viewBox but are faking one:
+  SVGPreserveAspectRatio tmpPAR;
+
   float viewportWidth, viewportHeight;
   if (nsSVGUtils::IsInnerSVG(this)) {
     nsSVGSVGElement *ctx = GetCtx();
     viewportWidth = mLengthAttributes[WIDTH].GetAnimValue(ctx);
     viewportHeight = mLengthAttributes[HEIGHT].GetAnimValue(ctx);
   } else {
     viewportWidth = mViewportWidth;
     viewportHeight = mViewportHeight;
   }
 
   nsSVGViewBoxRect viewBox;
   if (mViewBox.IsValid()) {
     viewBox = mViewBox.GetAnimValue();
   } else {
     viewBox.x = viewBox.y = 0.0f;
-    viewBox.width  = viewportWidth;
-    viewBox.height = viewportHeight;
+    if (ShouldSynthesizeViewBox()) {
+      // Special case -- fake a viewBox, using height & width attrs.
+      // (Use |this| as context, since if we get here, we're outermost <svg>.)
+      viewBox.width =
+        ComputeSynthesizedViewBoxDimension(mLengthAttributes[WIDTH],
+                                           mViewportWidth, this);
+      viewBox.height =
+        ComputeSynthesizedViewBoxDimension(mLengthAttributes[HEIGHT],
+                                           mViewportHeight, this);
+      NS_ABORT_IF_FALSE(!overridePARPtr,
+                        "shouldn't have overridePAR if we're "
+                        "synthesizing a viewBox");
+
+      // If we're synthesizing a viewBox, use preserveAspectRatio="none";
+      tmpPAR.SetAlign(nsIDOMSVGPreserveAspectRatio::SVG_PRESERVEASPECTRATIO_NONE);
+
+      // (set the other pAR attributes too, just so they're initialized):
+      tmpPAR.SetDefer(PR_FALSE);
+      tmpPAR.SetMeetOrSlice(nsIDOMSVGPreserveAspectRatio::SVG_MEETORSLICE_SLICE);
+
+      overridePARPtr = &tmpPAR;
+    } else {
+      // No viewBox attribute, so we shouldn't auto-scale. This is equivalent
+      // to having a viewBox that exactly matches our viewport size.
+      viewBox.width  = viewportWidth;
+      viewBox.height = viewportHeight;
+    }
   }
 
   if (viewBox.width <= 0.0f || viewBox.height <= 0.0f) {
     return gfxMatrix(0.0, 0.0, 0.0, 0.0, 0.0, 0.0); // singular
   }
 
-  // Do we have an override preserveAspectRatio value?
-  const SVGPreserveAspectRatio* overridePARPtr =
-    GetImageOverridePreserveAspectRatio();
-
   return nsSVGUtils::GetViewBoxTransform(this,
                                          viewportWidth, viewportHeight,
                                          viewBox.x, viewBox.y,
                                          viewBox.width, viewBox.height,
                                          overridePARPtr ? *overridePARPtr :
                                          mPreserveAspectRatio.GetAnimValue());
 }
 
@@ -1116,25 +1164,28 @@ float
 nsSVGSVGElement::GetLength(PRUint8 aCtxType)
 {
   float h, w;
 
   if (mViewBox.IsValid()) {
     const nsSVGViewBoxRect& viewbox = mViewBox.GetAnimValue();
     w = viewbox.width;
     h = viewbox.height;
+  } else if (nsSVGUtils::IsInnerSVG(this)) {
+    nsSVGSVGElement *ctx = GetCtx();
+    w = mLengthAttributes[WIDTH].GetAnimValue(ctx);
+    h = mLengthAttributes[HEIGHT].GetAnimValue(ctx);
+  } else if (ShouldSynthesizeViewBox()) {
+    w = ComputeSynthesizedViewBoxDimension(mLengthAttributes[WIDTH],
+                                           mViewportWidth, this);
+    h = ComputeSynthesizedViewBoxDimension(mLengthAttributes[HEIGHT],
+                                           mViewportHeight, this);
   } else {
-    if (nsSVGUtils::IsInnerSVG(this)) {
-      nsSVGSVGElement *ctx = GetCtx();
-      w = mLengthAttributes[WIDTH].GetAnimValue(ctx);
-      h = mLengthAttributes[HEIGHT].GetAnimValue(ctx);
-    } else {
-      w = mViewportWidth;
-      h = mViewportHeight;
-    }
+    w = mViewportWidth;
+    h = mViewportHeight;
   }
 
   w = NS_MAX(w, 0.0f);
   h = NS_MAX(h, 0.0f);
 
   switch (aCtxType) {
   case nsSVGUtils::X:
     return w;
@@ -1247,16 +1298,30 @@ nsSVGSVGElement::GetPreserveAspectRatio(
 // XXXdholbert HACK -- see comment w/ this method's declaration in header file.
 void
 nsSVGSVGElement::RemoveAllRenderingObservers()
 {
   nsSVGEffects::RemoveAllRenderingObservers(this);
 }
 #endif // !MOZ_LIBXUL
 
+PRBool
+nsSVGSVGElement::ShouldSynthesizeViewBox()
+{
+  NS_ABORT_IF_FALSE(!HasValidViewbox(),
+                    "Should only be called if we lack a viewBox");
+
+  nsIDocument* doc = GetCurrentDoc();
+  return doc &&
+    doc->IsBeingUsedAsImage() &&
+    !mIsPaintingSVGImageElement &&
+    !GetParent();
+}
+
+
 // Callback function, for freeing PRUint64 values stored in property table
 static void
 ReleasePreserveAspectRatioPropertyValue(void*    aObject,       /* unused */
                                         nsIAtom* aPropertyName, /* unused */
                                         void*    aPropertyValue,
                                         void*    aData          /* unused */)
 {
   SVGPreserveAspectRatio* valPtr =
@@ -1268,16 +1333,24 @@ void
 nsSVGSVGElement::
   SetImageOverridePreserveAspectRatio(const SVGPreserveAspectRatio& aPAR)
 {
 #ifdef DEBUG
   NS_ABORT_IF_FALSE(GetCurrentDoc()->IsBeingUsedAsImage(),
                     "should only override preserveAspectRatio in images");
 #endif
 
+  if (!HasValidViewbox() && 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.
+    mImageNeedsTransformInvalidation = PR_TRUE;
+  }
+  mIsPaintingSVGImageElement = PR_TRUE;
+
   if (!mViewBox.IsValid()) {
     return; // preserveAspectRatio irrelevant (only matters if we have viewBox)
   }
 
   if (aPAR.GetDefer() && HasPreserveAspectRatio()) {
     return; // Referring element defers to my own preserveAspectRatio value.
   }
 
@@ -1299,16 +1372,24 @@ nsSVGSVGElement::
 void
 nsSVGSVGElement::ClearImageOverridePreserveAspectRatio()
 {
 #ifdef DEBUG
   NS_ABORT_IF_FALSE(GetCurrentDoc()->IsBeingUsedAsImage(),
                     "should only override preserveAspectRatio in images");
 #endif
 
+  mIsPaintingSVGImageElement = PR_FALSE;
+  if (!HasValidViewbox() && 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.
+    mImageNeedsTransformInvalidation = PR_TRUE;
+  }
+
   void* valPtr = UnsetProperty(nsGkAtoms::overridePreserveAspectRatio);
   if (valPtr) {
     mImageNeedsTransformInvalidation = PR_TRUE;
     delete static_cast<SVGPreserveAspectRatio*>(valPtr);
   }
 }
 
 const SVGPreserveAspectRatio*
--- a/content/svg/content/src/nsSVGSVGElement.h
+++ b/content/svg/content/src/nsSVGSVGElement.h
@@ -240,16 +240,22 @@ public:
 private:
   // Methods for <image> elements to override my "PreserveAspectRatio" value.
   // These are private so that only our friends (nsSVGImageFrame in
   // particular) have access.
   void SetImageOverridePreserveAspectRatio(const SVGPreserveAspectRatio& aPAR);
   void ClearImageOverridePreserveAspectRatio();
   const SVGPreserveAspectRatio* GetImageOverridePreserveAspectRatio();
 
+  // Returns PR_TRUE if we should synthesize a viewBox for ourselves (that is,
+  // if we're the outermost <svg> in an image document, and we're not currently
+  // being painted by an <svg:image> element). This method also assumes that we
+  // lack a valid viewBox attribute.
+  PRBool ShouldSynthesizeViewBox();
+
 protected:
   // nsSVGElement overrides
   PRBool IsEventName(nsIAtom* aName);
 
 #ifdef MOZ_SMIL
   virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
                               nsIContent* aBindingParent,
                               PRBool aCompileEventHandlers);
@@ -336,11 +342,12 @@ protected:
 #ifdef MOZ_SMIL
   // For outermost <svg> elements created from parsing, animation is started by
   // the onload event in accordance with the SVG spec, but for <svg> elements
   // created by script or promoted from inner <svg> to outermost <svg> we need
   // to manually kick off animation when they are bound to the tree.
   PRPackedBool                      mStartAnimationOnBindToTree;
 #endif // MOZ_SMIL
   PRPackedBool                      mImageNeedsTransformInvalidation;
+  PRPackedBool                      mIsPaintingSVGImageElement;
 };
 
 #endif
--- a/layout/svg/base/src/nsSVGOuterSVGFrame.cpp
+++ b/layout/svg/base/src/nsSVGOuterSVGFrame.cpp
@@ -311,18 +311,20 @@ nsSVGOuterSVGFrame::GetIntrinsicRatio()
 }
 
 /* virtual */ nsSize
 nsSVGOuterSVGFrame::ComputeSize(nsIRenderingContext *aRenderingContext,
                                 nsSize aCBSize, nscoord aAvailableWidth,
                                 nsSize aMargin, nsSize aBorder, nsSize aPadding,
                                 PRBool aShrinkWrap)
 {
-  if (mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::viewBox) &&
-      (EmbeddedByReference() || IsRootOfImage())) {
+  nsSVGSVGElement* content = static_cast<nsSVGSVGElement*>(mContent);
+
+  if ((content->HasValidViewbox() || content->ShouldSynthesizeViewBox()) &&
+      (IsRootOfImage() || IsRootOfReplacedElementSubDoc())) {
     // The embedding element has done the replaced element sizing, using our
     // intrinsic dimensions as necessary. We just need to fill the viewport.
     return aCBSize;
   }
 
   return nsLayoutUtils::ComputeSizeWithIntrinsicDimensions(
                             aRenderingContext, this,
                             GetIntrinsicSize(), GetIntrinsicRatio(), aCBSize,
@@ -497,18 +499,17 @@ NS_IMETHODIMP
 nsSVGOuterSVGFrame::AttributeChanged(PRInt32  aNameSpaceID,
                                      nsIAtom* aAttribute,
                                      PRInt32  aModType)
 {
   if (aNameSpaceID == kNameSpaceID_None &&
       !(GetStateBits() & NS_FRAME_FIRST_REFLOW) &&
       (aAttribute == nsGkAtoms::width || aAttribute == nsGkAtoms::height)) {
     nsIFrame* embeddingFrame;
-    EmbeddedByReference(&embeddingFrame);
-    if (embeddingFrame) {
+    if (IsRootOfReplacedElementSubDoc(&embeddingFrame) && embeddingFrame) {
       if (DependsOnIntrinsicSize(embeddingFrame)) {
         // Tell embeddingFrame's presShell it needs to be reflowed (which takes
         // care of reflowing us too).
         embeddingFrame->PresContext()->PresShell()->
           FrameNeedsReflow(embeddingFrame, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY);
       }
       // else our width and height is overridden - don't reflow anything
     } else {
@@ -803,17 +804,17 @@ nsSVGOuterSVGFrame::UnregisterForeignObj
 {
   NS_ASSERTION(aFrame, "Who on earth is calling us?!");
   NS_ASSERTION(mForeignObjectHash.GetEntry(aFrame),
                "nsSVGForeignObjectFrame not in registry!");
   return mForeignObjectHash.RemoveEntry(aFrame);
 }
 
 PRBool
-nsSVGOuterSVGFrame::EmbeddedByReference(nsIFrame **aEmbeddingFrame)
+nsSVGOuterSVGFrame::IsRootOfReplacedElementSubDoc(nsIFrame **aEmbeddingFrame)
 {
   if (!mContent->GetParent()) {
     // Our content is the document element
     nsCOMPtr<nsISupports> container = PresContext()->GetContainer();
     nsCOMPtr<nsIDOMWindowInternal> window = do_GetInterface(container);
     if (window) {
       nsCOMPtr<nsIDOMElement> frameElement;
       window->GetFrameElement(getter_AddRefs(frameElement));
--- a/layout/svg/base/src/nsSVGOuterSVGFrame.h
+++ b/layout/svg/base/src/nsSVGOuterSVGFrame.h
@@ -153,17 +153,17 @@ public:
   void UnregisterForeignObject(nsSVGForeignObjectFrame* aFrame);
 
 protected:
 
   /* Returns true if our content is the document element and our document is
    * embedded in an HTML 'object', 'embed' or 'applet' element. Set
    * aEmbeddingFrame to obtain the nsIFrame for the embedding HTML element.
    */
-  PRBool EmbeddedByReference(nsIFrame **aEmbeddingFrame = nsnull);
+  PRBool IsRootOfReplacedElementSubDoc(nsIFrame **aEmbeddingFrame = nsnull);
 
   /* Returns true if our content is the document element and our document is
    * being used as an image.
    */
   PRBool IsRootOfImage();
 
   // A hash-set containing our nsSVGForeignObjectFrame descendants. Note we use
   // a hash-set to avoid the O(N^2) behavior we'd get tearing down an SVG frame