Bug 614732 - Add an argument to nsSVGUtils::GetBBox() to allow callers to specify whether stroke or markers should be included in the bounds returned. r=roc.
authorJonathan Watt <jwatt@jwatt.org>
Fri, 30 Sep 2011 10:25:37 +0100
changeset 77910 8ddd09649e13
parent 77909 d74000e4f76a
child 77911 743ed92f9332
push id3
push userfelipc@gmail.com
push dateFri, 30 Sep 2011 20:09:13 +0000
reviewersroc
bugs614732
milestone10.0a1
Bug 614732 - Add an argument to nsSVGUtils::GetBBox() to allow callers to specify whether stroke or markers should be included in the bounds returned. r=roc.
layout/svg/base/src/nsISVGChildFrame.h
layout/svg/base/src/nsSVGContainerFrame.cpp
layout/svg/base/src/nsSVGContainerFrame.h
layout/svg/base/src/nsSVGForeignObjectFrame.cpp
layout/svg/base/src/nsSVGForeignObjectFrame.h
layout/svg/base/src/nsSVGGlyphFrame.cpp
layout/svg/base/src/nsSVGGlyphFrame.h
layout/svg/base/src/nsSVGMarkerFrame.cpp
layout/svg/base/src/nsSVGMarkerFrame.h
layout/svg/base/src/nsSVGPathGeometryFrame.cpp
layout/svg/base/src/nsSVGPathGeometryFrame.h
layout/svg/base/src/nsSVGSwitchFrame.cpp
layout/svg/base/src/nsSVGTextFrame.cpp
layout/svg/base/src/nsSVGTextFrame.h
layout/svg/base/src/nsSVGUtils.cpp
layout/svg/base/src/nsSVGUtils.h
--- a/layout/svg/base/src/nsISVGChildFrame.h
+++ b/layout/svg/base/src/nsISVGChildFrame.h
@@ -117,18 +117,22 @@ public:
    * instead. In that case, since we return the bounds in a different userspace
    * (the ancestor's), the bounds we return are not this element's bbox, but
    * rather this element's contribution to the bbox of the ancestor.
    *
    * @param aToBBoxUserspace The transform from the userspace established by
    *   this element to the userspace established by the ancestor on which
    *   getBBox was called. This will be the identity matrix if we are the
    *   element on which getBBox was called.
+   *
+   * @param aFlags Flags indicating whether, stroke, for example, should be
+   *   included in the bbox calculation.
    */
-  virtual gfxRect GetBBoxContribution(const gfxMatrix &aToBBoxUserspace) = 0;
+  virtual gfxRect GetBBoxContribution(const gfxMatrix &aToBBoxUserspace,
+                                      PRUint32 aFlags) = 0;
 
   // Are we a container frame?
   NS_IMETHOD_(bool) IsDisplayContainer()=0;
 
   // Does this frame have an current covered region in mRect (aka GetRect())?
   NS_IMETHOD_(bool) HasValidCoveredRect()=0;
 };
 
--- a/layout/svg/base/src/nsSVGContainerFrame.cpp
+++ b/layout/svg/base/src/nsSVGContainerFrame.cpp
@@ -256,29 +256,32 @@ nsSVGDisplayContainerFrame::NotifyRedraw
     if (SVGFrame) {
       SVGFrame->NotifyRedrawUnsuspended();
     }
   }
   return NS_OK;
 }
 
 gfxRect
-nsSVGDisplayContainerFrame::GetBBoxContribution(const gfxMatrix &aToBBoxUserspace)
+nsSVGDisplayContainerFrame::GetBBoxContribution(
+  const gfxMatrix &aToBBoxUserspace,
+  PRUint32 aFlags)
 {
   gfxRect bboxUnion(0.0, 0.0, 0.0, 0.0);
 
   nsIFrame* kid = mFrames.FirstChild();
   while (kid) {
     nsISVGChildFrame* svgKid = do_QueryFrame(kid);
     if (svgKid) {
       gfxMatrix transform = aToBBoxUserspace;
       nsIContent *content = kid->GetContent();
       if (content->IsSVG() && !content->IsNodeOfType(nsINode::eTEXT)) {
         transform = static_cast<nsSVGElement*>(content)->
                       PrependLocalTransformTo(aToBBoxUserspace);
       }
-      bboxUnion = bboxUnion.Union(svgKid->GetBBoxContribution(transform));
+      bboxUnion =
+        bboxUnion.Union(svgKid->GetBBoxContribution(transform, aFlags));
     }
     kid = kid->GetNextSibling();
   }
 
   return bboxUnion;
 }
--- a/layout/svg/base/src/nsSVGContainerFrame.h
+++ b/layout/svg/base/src/nsSVGContainerFrame.h
@@ -107,14 +107,15 @@ public:
                       const nsIntRect *aDirtyRect);
   NS_IMETHOD_(nsIFrame*) GetFrameForPoint(const nsPoint &aPoint);
   NS_IMETHOD_(nsRect) GetCoveredRegion();
   NS_IMETHOD UpdateCoveredRegion();
   NS_IMETHOD InitialUpdate();
   virtual void NotifySVGChanged(PRUint32 aFlags);
   NS_IMETHOD NotifyRedrawSuspended();
   NS_IMETHOD NotifyRedrawUnsuspended();
-  virtual gfxRect GetBBoxContribution(const gfxMatrix &aToBBoxUserspace);
+  virtual gfxRect GetBBoxContribution(const gfxMatrix &aToBBoxUserspace,
+                                      PRUint32 aFlags);
   NS_IMETHOD_(bool) IsDisplayContainer() { return true; }
   NS_IMETHOD_(bool) HasValidCoveredRect() { return false; }
 };
 
 #endif
--- a/layout/svg/base/src/nsSVGForeignObjectFrame.cpp
+++ b/layout/svg/base/src/nsSVGForeignObjectFrame.cpp
@@ -428,17 +428,18 @@ nsSVGForeignObjectFrame::NotifyRedrawUns
     } else {
       FlushDirtyRegion(0); // only invalidate areas dirtied by our descendants
     }
   }
   return NS_OK;
 }
 
 gfxRect
-nsSVGForeignObjectFrame::GetBBoxContribution(const gfxMatrix &aToBBoxUserspace)
+nsSVGForeignObjectFrame::GetBBoxContribution(const gfxMatrix &aToBBoxUserspace,
+                                             PRUint32 aFlags)
 {
   NS_ASSERTION(!(GetStateBits() & NS_STATE_SVG_NONDISPLAY_CHILD),
                "Should not be calling this on a non-display child");
 
   nsSVGForeignObjectElement *content =
     static_cast<nsSVGForeignObjectElement*>(mContent);
 
   float x, y, w, h;
--- a/layout/svg/base/src/nsSVGForeignObjectFrame.h
+++ b/layout/svg/base/src/nsSVGForeignObjectFrame.h
@@ -122,17 +122,18 @@ public:
                       const nsIntRect *aDirtyRect);
   NS_IMETHOD_(nsIFrame*) GetFrameForPoint(const nsPoint &aPoint);
   NS_IMETHOD_(nsRect) GetCoveredRegion();
   NS_IMETHOD UpdateCoveredRegion();
   NS_IMETHOD InitialUpdate();
   virtual void NotifySVGChanged(PRUint32 aFlags);
   NS_IMETHOD NotifyRedrawSuspended();
   NS_IMETHOD NotifyRedrawUnsuspended();
-  virtual gfxRect GetBBoxContribution(const gfxMatrix &aToBBoxUserspace);
+  virtual gfxRect GetBBoxContribution(const gfxMatrix &aToBBoxUserspace,
+                                      PRUint32 aFlags);
   NS_IMETHOD_(bool) IsDisplayContainer() { return true; }
   NS_IMETHOD_(bool) HasValidCoveredRect() { return true; }
 
   gfxMatrix GetCanvasTM();
 
   // This method allows our nsSVGOuterSVGFrame to reflow us as necessary.
   void MaybeReflowFromOuterSVGFrame();
 
--- a/layout/svg/base/src/nsSVGGlyphFrame.cpp
+++ b/layout/svg/base/src/nsSVGGlyphFrame.cpp
@@ -632,34 +632,53 @@ nsSVGGlyphFrame::FillCharacters(Characte
   while ((i = aIter->NextCluster()) != aIter->InvalidCluster()) {
     aIter->SetupForDrawing(aContext);
     mTextRun->Draw(aContext, gfxPoint(0, 0), i, aIter->ClusterLength(),
                    nsnull, nsnull);
   }
 }
 
 gfxRect
-nsSVGGlyphFrame::GetBBoxContribution(const gfxMatrix &aToBBoxUserspace)
+nsSVGGlyphFrame::GetBBoxContribution(const gfxMatrix &aToBBoxUserspace,
+                                     PRUint32 aFlags)
 {
   if (mOverrideCanvasTM) {
     *mOverrideCanvasTM = aToBBoxUserspace;
   } else {
     mOverrideCanvasTM = new gfxMatrix(aToBBoxUserspace);
   }
 
   nsRefPtr<gfxContext> tmpCtx = MakeTmpCtx();
   SetupGlobalTransform(tmpCtx);
   CharacterIterator iter(this, PR_TRUE);
   iter.SetInitialMatrix(tmpCtx);
   AddBoundingBoxesToPath(&iter, tmpCtx);
   tmpCtx->IdentityMatrix();
 
   mOverrideCanvasTM = nsnull;
 
-  return tmpCtx->GetUserPathExtent();
+  gfxRect bbox;
+
+  gfxRect pathExtents = tmpCtx->GetUserPathExtent();
+
+  // Account for fill:
+  if ((aFlags & nsSVGUtils::eBBoxIncludeFill) != 0 &&
+      ((aFlags & nsSVGUtils::eBBoxIgnoreFillIfNone) == 0 ||
+       GetStyleSVG()->mFill.mType != eStyleSVGPaintType_None)) {
+    bbox = pathExtents;
+  }
+
+  // Account for stroke:
+  if ((aFlags & nsSVGUtils::eBBoxIncludeStroke) != 0 &&
+      ((aFlags & nsSVGUtils::eBBoxIgnoreStrokeIfNone) == 0 || HasStroke())) {
+    bbox =
+      bbox.Union(nsSVGUtils::PathExtentsToMaxStrokeExtents(pathExtents, this));
+  }
+
+  return bbox;
 }
 
 //----------------------------------------------------------------------
 // nsSVGGeometryFrame methods:
 
 gfxMatrix
 nsSVGGlyphFrame::GetCanvasTM()
 {
--- a/layout/svg/base/src/nsSVGGlyphFrame.h
+++ b/layout/svg/base/src/nsSVGGlyphFrame.h
@@ -168,17 +168,18 @@ public:
 #endif
 
   // nsISVGChildFrame interface:
   // These four always use the global transform, even if NS_STATE_NONDISPLAY_CHILD
   NS_IMETHOD PaintSVG(nsSVGRenderState *aContext,
                       const nsIntRect *aDirtyRect);
   NS_IMETHOD_(nsIFrame*) GetFrameForPoint(const nsPoint &aPoint);
   NS_IMETHOD UpdateCoveredRegion();
-  virtual gfxRect GetBBoxContribution(const gfxMatrix &aToBBoxUserspace);
+  virtual gfxRect GetBBoxContribution(const gfxMatrix &aToBBoxUserspace,
+                                      PRUint32 aFlags);
 
   NS_IMETHOD_(nsRect) GetCoveredRegion();
   NS_IMETHOD InitialUpdate();
   virtual void NotifySVGChanged(PRUint32 aFlags);
   NS_IMETHOD NotifyRedrawSuspended();
   NS_IMETHOD NotifyRedrawUnsuspended();
   NS_IMETHOD_(bool) IsDisplayContainer() { return false; }
   NS_IMETHOD_(bool) HasValidCoveredRect() { return true; }
--- a/layout/svg/base/src/nsSVGMarkerFrame.cpp
+++ b/layout/svg/base/src/nsSVGMarkerFrame.cpp
@@ -201,16 +201,61 @@ nsSVGMarkerFrame::RegionMark(nsSVGPathGe
     if (child)
       child->UpdateCoveredRegion();
   }
 
   // Now get the combined covered region
   return nsSVGUtils::GetCoveredRegion(mFrames);
 }
 
+gfxRect
+nsSVGMarkerFrame::GetMarkBBoxContribution(const gfxMatrix &aToBBoxUserspace,
+                                          PRUint32 aFlags,
+                                          nsSVGPathGeometryFrame *aMarkedFrame,
+                                          const nsSVGMark *aMark,
+                                          float aStrokeWidth)
+{
+  // If the flag is set when we get here, it means this marker frame
+  // has already been used in calculating the current mark bbox, and
+  // the document has a marker reference loop.
+  if (mInUse)
+    return gfxRect();
+
+  AutoMarkerReferencer markerRef(this, aMarkedFrame);
+
+  mStrokeWidth = aStrokeWidth;
+  mX = aMark->x;
+  mY = aMark->y;
+  mAutoAngle = aMark->angle;
+
+  gfxRect bbox;
+
+  nsSVGMarkerElement *content = static_cast<nsSVGMarkerElement*>(mContent);
+
+  gfxMatrix markerTM =
+    content->GetMarkerTransform(mStrokeWidth, mX, mY, mAutoAngle);
+  gfxMatrix viewBoxTM = content->GetViewBoxTransform();
+
+  gfxMatrix tm = viewBoxTM * markerTM * aToBBoxUserspace;
+
+  for (nsIFrame* kid = mFrames.FirstChild();
+       kid;
+       kid = kid->GetNextSibling()) {
+    nsISVGChildFrame* child = do_QueryFrame(kid);
+    if (child) {
+      // When we're being called to obtain the invalidation area, we need to
+      // pass down all the flags so that stroke is included. However, once DOM
+      // getBBox() accepts flags, maybe we should strip some of those here?
+      bbox.UnionRect(bbox, child->GetBBoxContribution(tm, aFlags));
+    }
+  }
+
+  return bbox;
+}
+
 void
 nsSVGMarkerFrame::SetParentCoordCtxProvider(nsSVGSVGElement *aContext)
 {
   nsSVGMarkerElement *marker = static_cast<nsSVGMarkerElement*>(mContent);
   marker->SetParentCoordCtxProvider(aContext);
 }
 
 //----------------------------------------------------------------------
--- a/layout/svg/base/src/nsSVGMarkerFrame.h
+++ b/layout/svg/base/src/nsSVGMarkerFrame.h
@@ -90,16 +90,22 @@ public:
   nsresult PaintMark(nsSVGRenderState *aContext,
                      nsSVGPathGeometryFrame *aMarkedFrame,
                      nsSVGMark *aMark,
                      float aStrokeWidth);
 
   nsRect RegionMark(nsSVGPathGeometryFrame *aMarkedFrame,
                     const nsSVGMark *aMark, float aStrokeWidth);
 
+  gfxRect GetMarkBBoxContribution(const gfxMatrix &aToBBoxUserspace,
+                                  PRUint32 aFlags,
+                                  nsSVGPathGeometryFrame *aMarkedFrame,
+                                  const nsSVGMark *aMark,
+                                  float aStrokeWidth);
+
 private:
   // stuff needed for callback
   nsSVGPathGeometryFrame *mMarkedFrame;
   float mStrokeWidth, mX, mY, mAutoAngle;
 
   // nsSVGContainerFrame methods:
   virtual gfxMatrix GetCanvasTM();
 
--- a/layout/svg/base/src/nsSVGPathGeometryFrame.cpp
+++ b/layout/svg/base/src/nsSVGPathGeometryFrame.cpp
@@ -197,92 +197,21 @@ NS_IMETHODIMP_(nsRect)
 nsSVGPathGeometryFrame::GetCoveredRegion()
 {
   return mRect;
 }
 
 NS_IMETHODIMP
 nsSVGPathGeometryFrame::UpdateCoveredRegion()
 {
-  mRect.SetEmpty();
-
-  nsRefPtr<gfxContext> context =
-    new gfxContext(gfxPlatform::GetPlatform()->ScreenReferenceSurface());
-
-  GeneratePath(context);
-  context->IdentityMatrix();
-
-  gfxRect extent = context->GetUserPathExtent();
-
-  // Be careful when replacing the following logic to get the fill and stroke
-  // extents independently (instead of computing the stroke extents from the
-  // path extents). You may think that you can just use the stroke extents if
-  // there is both a fill and a stroke. In reality it's necessary to calculate
-  // both the fill and stroke extents, and take the union of the two. There are
-  // two reasons for this:
-  //
-  // # Due to stroke dashing, in certain cases the fill extents could actually
-  //   extend outside the stroke extents.
-  // # If the stroke is very thin, cairo won't paint any stroke, and so the
-  //   stroke bounds that it will return will be empty.
-
-  if (HasStroke()) {
-    SetupCairoStrokeGeometry(context);
-    if (extent.Width() <= 0 && extent.Height() <= 0) {
-      // If 'extent' is empty, its position will not be set. Although
-      // GetUserStrokeExtent gets the extents wrong we can still use it
-      // to get the device space position of zero length stroked paths.
-      extent = context->GetUserStrokeExtent();
-      extent += gfxPoint(extent.width, extent.height)/2;
-      extent.SizeTo(gfxSize(0, 0));
-    }
-    extent = nsSVGUtils::PathExtentsToMaxStrokeExtents(extent, this);
-  } else if (GetStyleSVG()->mFill.mType == eStyleSVGPaintType_None) {
-    extent = gfxRect(0, 0, 0, 0);
-  }
-
-  if (!extent.IsEmpty()) {
-    mRect = nsSVGUtils::ToAppPixelRect(PresContext(), extent);
-  }
-
-  // Add in markers
-  if (static_cast<nsSVGPathGeometryElement*>(mContent)->IsMarkable()) {
-
-    float strokeWidth = GetStrokeWidth();
-    MarkerProperties properties = GetMarkerProperties(this);
-
-    if (properties.MarkersExist()) {
-      nsTArray<nsSVGMark> marks;
-      static_cast<nsSVGPathGeometryElement*>(mContent)->GetMarkPoints(&marks);
-      PRUint32 num = marks.Length();
-
-      if (num) {
-        nsSVGMarkerFrame *frame = properties.GetMarkerStartFrame();
-        if (frame) {
-          nsRect rect = frame->RegionMark(this, &marks[0], strokeWidth);
-          mRect.UnionRect(mRect, rect);
-        }
-
-        frame = properties.GetMarkerMidFrame();
-        if (frame) {
-          for (PRUint32 i = 1; i < num - 1; i++) {
-            nsRect rect = frame->RegionMark(this, &marks[i], strokeWidth);
-            mRect.UnionRect(mRect, rect);
-          }
-        }
-
-        frame = properties.GetMarkerEndFrame();
-        if (frame) {
-          nsRect rect = frame->RegionMark(this, &marks[num-1], strokeWidth);
-          mRect.UnionRect(mRect, rect);
-        }
-      }
-    }
-  }
-
+  gfxRect extent = GetBBoxContribution(GetCanvasTM(),
+    nsSVGUtils::eBBoxIncludeFill | nsSVGUtils::eBBoxIgnoreFillIfNone |
+    nsSVGUtils::eBBoxIncludeStroke | nsSVGUtils::eBBoxIgnoreStrokeIfNone |
+    nsSVGUtils::eBBoxIncludeMarkers);
+  mRect = nsSVGUtils::ToAppPixelRect(PresContext(), extent);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSVGPathGeometryFrame::InitialUpdate()
 {
   NS_ASSERTION(GetStateBits() & NS_FRAME_FIRST_REFLOW,
                "Yikes! We've been called already! Hopefully we weren't called "
@@ -319,27 +248,117 @@ nsSVGPathGeometryFrame::NotifyRedrawUnsu
 {
   if (GetStateBits() & NS_STATE_SVG_DIRTY)
     nsSVGUtils::UpdateGraphic(this);
 
   return NS_OK;
 }
 
 gfxRect
-nsSVGPathGeometryFrame::GetBBoxContribution(const gfxMatrix &aToBBoxUserspace)
+nsSVGPathGeometryFrame::GetBBoxContribution(const gfxMatrix &aToBBoxUserspace,
+                                            PRUint32 aFlags)
 {
   if (aToBBoxUserspace.IsSingular()) {
     // XXX ReportToConsole
     return gfxRect(0.0, 0.0, 0.0, 0.0);
   }
+
   nsRefPtr<gfxContext> context =
     new gfxContext(gfxPlatform::GetPlatform()->ScreenReferenceSurface());
+
   GeneratePath(context, &aToBBoxUserspace);
   context->IdentityMatrix();
-  return context->GetUserPathExtent();
+
+  gfxRect bbox;
+
+  // Be careful when replacing the following logic to get the fill and stroke
+  // extents independently (instead of computing the stroke extents from the
+  // path extents). You may think that you can just use the stroke extents if
+  // there is both a fill and a stroke. In reality it's necessary to calculate
+  // both the fill and stroke extents, and take the union of the two. There are
+  // two reasons for this:
+  //
+  // # Due to stroke dashing, in certain cases the fill extents could actually
+  //   extend outside the stroke extents.
+  // # If the stroke is very thin, cairo won't paint any stroke, and so the
+  //   stroke bounds that it will return will be empty.
+
+  gfxRect pathExtents = context->GetUserPathExtent();
+
+  // Account for fill:
+  if ((aFlags & nsSVGUtils::eBBoxIncludeFill) != 0 &&
+      ((aFlags & nsSVGUtils::eBBoxIgnoreFillIfNone) == 0 ||
+       GetStyleSVG()->mFill.mType != eStyleSVGPaintType_None)) {
+    bbox = pathExtents;
+  }
+
+  // Account for stroke:
+  if ((aFlags & nsSVGUtils::eBBoxIncludeStroke) != 0 &&
+      ((aFlags & nsSVGUtils::eBBoxIgnoreStrokeIfNone) == 0 || HasStroke())) {
+    // We can't use context->GetUserStrokeExtent() since it doesn't work for
+    // device space extents. Instead we approximate the stroke extents from
+    // pathExtents using PathExtentsToMaxStrokeExtents.
+    if (pathExtents.Width() <= 0 && pathExtents.Height() <= 0) {
+      // We have a zero length path, but it may still have non-empty stroke
+      // bounds depending on the value of stroke-linecap. We need to fix up
+      // pathExtents before it can be used with PathExtentsToMaxStrokeExtents
+      // though, because if pathExtents is empty, its position will not have
+      // been set. Happily we can use context->GetUserStrokeExtent() to find
+      // the center point of the extents even though it gets the extents wrong.
+      SetupCairoStrokeGeometry(context);
+      pathExtents.MoveTo(context->GetUserStrokeExtent().Center());
+      pathExtents.SizeTo(0, 0);
+    }
+    bbox =
+      bbox.Union(nsSVGUtils::PathExtentsToMaxStrokeExtents(pathExtents, this));
+  }
+
+  // Account for markers:
+  if ((aFlags & nsSVGUtils::eBBoxIncludeMarkers) != 0 &&
+      static_cast<nsSVGPathGeometryElement*>(mContent)->IsMarkable()) {
+
+    float strokeWidth = GetStrokeWidth();
+    MarkerProperties properties = GetMarkerProperties(this);
+
+    if (properties.MarkersExist()) {
+      nsTArray<nsSVGMark> marks;
+      static_cast<nsSVGPathGeometryElement*>(mContent)->GetMarkPoints(&marks);
+      PRUint32 num = marks.Length();
+
+      if (num) {
+        nsSVGMarkerFrame *frame = properties.GetMarkerStartFrame();
+        if (frame) {
+          gfxRect mbbox =
+            frame->GetMarkBBoxContribution(aToBBoxUserspace, aFlags, this,
+                                           &marks[0], strokeWidth);
+          bbox.UnionRect(bbox, mbbox);
+        }
+
+        frame = properties.GetMarkerMidFrame();
+        if (frame) {
+          for (PRUint32 i = 1; i < num - 1; i++) {
+            gfxRect mbbox =
+              frame->GetMarkBBoxContribution(aToBBoxUserspace, aFlags, this,
+                                             &marks[i], strokeWidth);
+            bbox.UnionRect(bbox, mbbox);
+          }
+        }
+
+        frame = properties.GetMarkerEndFrame();
+        if (frame) {
+          gfxRect mbbox =
+            frame->GetMarkBBoxContribution(aToBBoxUserspace, aFlags, this,
+                                           &marks[num-1], strokeWidth);
+          bbox.UnionRect(bbox, mbbox);
+        }
+      }
+    }
+  }
+
+  return bbox;
 }
 
 //----------------------------------------------------------------------
 // nsSVGGeometryFrame methods:
 
 gfxMatrix
 nsSVGPathGeometryFrame::GetCanvasTM()
 {
--- a/layout/svg/base/src/nsSVGPathGeometryFrame.h
+++ b/layout/svg/base/src/nsSVGPathGeometryFrame.h
@@ -94,17 +94,18 @@ protected:
                       const nsIntRect *aDirtyRect);
   NS_IMETHOD_(nsIFrame*) GetFrameForPoint(const nsPoint &aPoint);
   NS_IMETHOD_(nsRect) GetCoveredRegion();
   NS_IMETHOD UpdateCoveredRegion();
   NS_IMETHOD InitialUpdate();
   virtual void NotifySVGChanged(PRUint32 aFlags);
   NS_IMETHOD NotifyRedrawSuspended();
   NS_IMETHOD NotifyRedrawUnsuspended();
-  virtual gfxRect GetBBoxContribution(const gfxMatrix &aToBBoxUserspace);
+  virtual gfxRect GetBBoxContribution(const gfxMatrix &aToBBoxUserspace,
+                                      PRUint32 aFlags);
   NS_IMETHOD_(bool) IsDisplayContainer() { return false; }
   NS_IMETHOD_(bool) HasValidCoveredRect() { return true; }
 
 protected:
   void GeneratePath(gfxContext *aContext,
                     const gfxMatrix *aOverrideTransform = nsnull);
 
 private:
--- a/layout/svg/base/src/nsSVGSwitchFrame.cpp
+++ b/layout/svg/base/src/nsSVGSwitchFrame.cpp
@@ -76,17 +76,18 @@ public:
 
   // nsISVGChildFrame interface:
   NS_IMETHOD PaintSVG(nsSVGRenderState* aContext, const nsIntRect *aDirtyRect);
   NS_IMETHODIMP_(nsIFrame*) GetFrameForPoint(const nsPoint &aPoint);
   NS_IMETHODIMP_(nsRect) GetCoveredRegion();
   NS_IMETHOD UpdateCoveredRegion();
   NS_IMETHOD InitialUpdate();
   NS_IMETHOD NotifyRedrawUnsuspended();
-  virtual gfxRect GetBBoxContribution(const gfxMatrix &aToBBoxUserspace);
+  virtual gfxRect GetBBoxContribution(const gfxMatrix &aToBBoxUserspace,
+                                      PRUint32 aFlags);
 
 private:
   nsIFrame *GetActiveChildFrame();
 };
 
 //----------------------------------------------------------------------
 // Implementation
 
@@ -190,28 +191,29 @@ nsSVGSwitchFrame::NotifyRedrawUnsuspende
 {
   if (GetStateBits() & NS_STATE_SVG_DIRTY)
     nsSVGUtils::UpdateGraphic(this);
 
   return nsSVGSwitchFrameBase::NotifyRedrawUnsuspended();
 }
 
 gfxRect
-nsSVGSwitchFrame::GetBBoxContribution(const gfxMatrix &aToBBoxUserspace)
+nsSVGSwitchFrame::GetBBoxContribution(const gfxMatrix &aToBBoxUserspace,
+                                      PRUint32 aFlags)
 {
   nsIFrame* kid = GetActiveChildFrame();
   nsISVGChildFrame* svgKid = do_QueryFrame(kid);
   if (svgKid) {
     nsIContent *content = kid->GetContent();
     gfxMatrix transform = aToBBoxUserspace;
     if (content->IsSVG()) {
       transform = static_cast<nsSVGElement*>(content)->
                     PrependLocalTransformTo(aToBBoxUserspace);
     }
-    return svgKid->GetBBoxContribution(transform);
+    return svgKid->GetBBoxContribution(transform, aFlags);
   }
   return gfxRect(0.0, 0.0, 0.0, 0.0);
 }
 
 nsIFrame *
 nsSVGSwitchFrame::GetActiveChildFrame()
 {
   nsIContent *activeChild =
--- a/layout/svg/base/src/nsSVGTextFrame.cpp
+++ b/layout/svg/base/src/nsSVGTextFrame.cpp
@@ -250,21 +250,22 @@ nsSVGTextFrame::InitialUpdate()
   nsresult rv = nsSVGTextFrameBase::InitialUpdate();
   
   UpdateGlyphPositioning(PR_FALSE);
 
   return rv;
 }  
 
 gfxRect
-nsSVGTextFrame::GetBBoxContribution(const gfxMatrix &aToBBoxUserspace)
+nsSVGTextFrame::GetBBoxContribution(const gfxMatrix &aToBBoxUserspace,
+                                    PRUint32 aFlags)
 {
   UpdateGlyphPositioning(PR_TRUE);
 
-  return nsSVGTextFrameBase::GetBBoxContribution(aToBBoxUserspace);
+  return nsSVGTextFrameBase::GetBBoxContribution(aToBBoxUserspace, aFlags);
 }
 
 //----------------------------------------------------------------------
 // nsSVGContainerFrame methods:
 
 gfxMatrix
 nsSVGTextFrame::GetCanvasTM()
 {
--- a/layout/svg/base/src/nsSVGTextFrame.h
+++ b/layout/svg/base/src/nsSVGTextFrame.h
@@ -89,17 +89,18 @@ public:
   NS_IMETHOD NotifyRedrawUnsuspended();
   // Override these four to ensure that UpdateGlyphPositioning is called
   // to bring glyph positions up to date
   NS_IMETHOD PaintSVG(nsSVGRenderState* aContext,
                       const nsIntRect *aDirtyRect);
   NS_IMETHOD_(nsIFrame*) GetFrameForPoint(const nsPoint & aPoint);
   NS_IMETHOD UpdateCoveredRegion();
   NS_IMETHOD InitialUpdate();
-  virtual gfxRect GetBBoxContribution(const gfxMatrix &aToBBoxUserspace);
+  virtual gfxRect GetBBoxContribution(const gfxMatrix &aToBBoxUserspace,
+                                      PRUint32 aFlags);
   
   // nsSVGContainerFrame methods:
   virtual gfxMatrix GetCanvasTM();
   
   // nsSVGTextContainerFrame methods:
   virtual PRUint32 GetNumberOfChars();
   virtual float GetComputedTextLength();
   virtual float GetSubStringLength(PRUint32 charnum, PRUint32 nchars);
--- a/layout/svg/base/src/nsSVGUtils.cpp
+++ b/layout/svg/base/src/nsSVGUtils.cpp
@@ -1268,17 +1268,17 @@ nsSVGUtils::ClipToGfxRect(nsIntRect* aRe
   r.RoundOut();
   gfxRect r2(aRect->x, aRect->y, aRect->width, aRect->height);
   r = r.Intersect(r2);
   *aRect = nsIntRect(PRInt32(r.X()), PRInt32(r.Y()),
                      PRInt32(r.Width()), PRInt32(r.Height()));
 }
 
 gfxRect
-nsSVGUtils::GetBBox(nsIFrame *aFrame)
+nsSVGUtils::GetBBox(nsIFrame *aFrame, PRUint32 aFlags)
 {
   if (aFrame->GetContent()->IsNodeOfType(nsINode::eTEXT)) {
     aFrame = aFrame->GetParent();
   }
   gfxRect bbox;
   nsISVGChildFrame *svg = do_QueryFrame(aFrame);
   if (svg) {
     // It is possible to apply a gradient, pattern, clipping path, mask or
@@ -1288,17 +1288,17 @@ nsSVGUtils::GetBBox(nsIFrame *aFrame)
     nsSVGTextContainerFrame* metrics = do_QueryFrame(
       GetFirstNonAAncestorFrame(aFrame));
     if (metrics) {
       while (aFrame->GetType() != nsGkAtoms::svgTextFrame) {
         aFrame = aFrame->GetParent();
       }
       svg = do_QueryFrame(aFrame);
     }
-    bbox = svg->GetBBoxContribution(gfxMatrix());
+    bbox = svg->GetBBoxContribution(gfxMatrix(), aFlags);
   } else {
     bbox = nsSVGIntegrationUtils::GetSVGBBoxForNonSVGFrame(aFrame);
   }
   NS_ASSERTION(bbox.Width() >= 0.0 && bbox.Height() >= 0.0, "Invalid bbox!");
   return bbox;
 }
 
 gfxRect
--- a/layout/svg/base/src/nsSVGUtils.h
+++ b/layout/svg/base/src/nsSVGUtils.h
@@ -509,21 +509,29 @@ public:
    *
    * If the bbox is empty, this will return a singular matrix.
    */
   static gfxMatrix
   AdjustMatrixForUnits(const gfxMatrix &aMatrix,
                        nsSVGEnum *aUnits,
                        nsIFrame *aFrame);
 
+  enum BBoxFlags {
+    eBBoxIncludeFill          = 1 << 0,
+    eBBoxIgnoreFillIfNone     = 1 << 1,
+    eBBoxIncludeStroke        = 1 << 2,
+    eBBoxIgnoreStrokeIfNone   = 1 << 3,
+    eBBoxIncludeMarkers       = 1 << 4
+  };
   /**
-   * Get bounding-box for aFrame. Matrix propagation is disabled so the
-   * bounding box is computed in terms of aFrame's own user space.
+   * Get the SVG bbox (the SVG spec's simplified idea of bounds) of aFrame in
+   * aFrame's userspace.
    */
-  static gfxRect GetBBox(nsIFrame *aFrame);
+  static gfxRect GetBBox(nsIFrame *aFrame, PRUint32 aFlags = eBBoxIncludeFill);
+
   /**
    * Compute a rectangle in userSpaceOnUse or objectBoundingBoxUnits.
    * @param aXYWH pointer to 4 consecutive nsSVGLength2 objects containing
    * the x, y, width and height values in that order
    * @param aBBox the bounding box of the object the rect is relative to;
    * may be null if aUnits is not SVG_UNIT_TYPE_OBJECTBOUNDINGBOX
    * @param aFrame the object in which to interpret user-space units;
    * may be null if aUnits is SVG_UNIT_TYPE_OBJECTBOUNDINGBOX