Bug 934183, part 1 - Convert the bounds calculation code for SVG geometry to use Moz2D Path. r=longsonr
authorJonathan Watt <jwatt@jwatt.org>
Fri, 03 Oct 2014 09:50:43 +0100
changeset 208555 2f7b5e239d1e9d308e5635a7c337293babec8dfc
parent 208554 78b5cff9b699d6ba342d8ae2affc24e9ec95e71b
child 208556 7c26c6d5b2fb433fcc674d9939ed087843d78730
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewerslongsonr
bugs934183
milestone35.0a1
Bug 934183, part 1 - Convert the bounds calculation code for SVG geometry to use Moz2D Path. r=longsonr
content/svg/content/src/SVGContentUtils.cpp
content/svg/content/src/SVGContentUtils.h
layout/svg/nsSVGPathGeometryFrame.cpp
--- a/content/svg/content/src/SVGContentUtils.cpp
+++ b/content/svg/content/src/SVGContentUtils.cpp
@@ -154,44 +154,47 @@ GetStrokeDashData(SVGContentUtils::AutoS
 
   return eDashedStroke;
 }
 
 void
 SVGContentUtils::GetStrokeOptions(AutoStrokeOptions* aStrokeOptions,
                                   nsSVGElement* aElement,
                                   nsStyleContext* aStyleContext,
-                                  gfxTextContextPaint *aContextPaint)
+                                  gfxTextContextPaint *aContextPaint,
+                                  StrokeOptionFlags aFlags)
 {
   nsRefPtr<nsStyleContext> styleContext;
   if (aStyleContext) {
     styleContext = aStyleContext;
   } else {
     styleContext =
       nsComputedDOMStyle::GetStyleContextForElementNoFlush(aElement, nullptr,
                                                            nullptr);
   }
 
   if (!styleContext) {
     return;
   }
 
   const nsStyleSVG* styleSVG = styleContext->StyleSVG();
 
-  DashState dashState =
-    GetStrokeDashData(aStrokeOptions, aElement, styleSVG, aContextPaint);
+  if (aFlags != eIgnoreStrokeDashing) {
+    DashState dashState =
+      GetStrokeDashData(aStrokeOptions, aElement, styleSVG, aContextPaint);
 
-  if (dashState == eNoStroke) {
-    // Hopefully this will shortcircuit any stroke operations:
-    aStrokeOptions->mLineWidth = 0;
-    return;
-  }
-  if (dashState == eContinuousStroke && aStrokeOptions->mDashPattern) {
-    // Prevent our caller from wasting time looking at a pattern without gaps:
-    aStrokeOptions->DiscardDashPattern();
+    if (dashState == eNoStroke) {
+      // Hopefully this will shortcircuit any stroke operations:
+      aStrokeOptions->mLineWidth = 0;
+      return;
+    }
+    if (dashState == eContinuousStroke && aStrokeOptions->mDashPattern) {
+      // Prevent our caller from wasting time looking at a pattern without gaps:
+      aStrokeOptions->DiscardDashPattern();
+    }
   }
 
   aStrokeOptions->mLineWidth =
     GetStrokeWidth(aElement, styleContext, aContextPaint);
 
   aStrokeOptions->mMiterLimit = Float(styleSVG->mStrokeMiterlimit);
 
   switch (styleSVG->mStrokeLinejoin) {
--- a/content/svg/content/src/SVGContentUtils.h
+++ b/content/svg/content/src/SVGContentUtils.h
@@ -123,20 +123,25 @@ public:
       mDashLength = 0;
       mDashPattern = nullptr;
     }
   private:
     // Most dasharrays will fit in this and save us allocating
     Float mSmallArray[16];
   };
 
+  enum StrokeOptionFlags {
+    eAllStrokeOptions,
+    eIgnoreStrokeDashing
+  };
   static void GetStrokeOptions(AutoStrokeOptions* aStrokeOptions,
                                nsSVGElement* aElement,
                                nsStyleContext* aStyleContext,
-                               gfxTextContextPaint *aContextPaint);
+                               gfxTextContextPaint *aContextPaint,
+                               StrokeOptionFlags aFlags = eAllStrokeOptions);
 
   /**
    * Returns the current computed value of the CSS property 'stroke-width' for
    * the given element. aStyleContext may be provided as an optimization. 
    * aContextPaint is also optional.
    *
    * Note that this function does NOT take account of the value of the 'stroke'
    * and 'stroke-opacity' properties to, say, return zero if they are "none" or
--- a/layout/svg/nsSVGPathGeometryFrame.cpp
+++ b/layout/svg/nsSVGPathGeometryFrame.cpp
@@ -449,76 +449,128 @@ nsSVGPathGeometryFrame::GetBBoxContribut
 {
   SVGBBox bbox;
 
   if (aToBBoxUserspace.IsSingular()) {
     // XXX ReportToConsole
     return bbox;
   }
 
+  nsSVGPathGeometryElement* element =
+    static_cast<nsSVGPathGeometryElement*>(mContent);
+
   RefPtr<DrawTarget> tmpDT;
 #ifdef XP_WIN
   // Unfortunately D2D backed DrawTarget produces bounds with rounding errors
   // when whole number results are expected, even in the case of trivial
   // calculations. To avoid that and meet the expectations of web content we
   // have to use a CAIRO DrawTarget. The most efficient way to do that is to
   // wrap the cached cairo_surface_t from ScreenReferenceSurface():
   nsRefPtr<gfxASurface> refSurf =
     gfxPlatform::GetPlatform()->ScreenReferenceSurface();
   tmpDT = gfxPlatform::GetPlatform()->
     CreateDrawTargetForSurface(refSurf, IntSize(1, 1));
 #else
   tmpDT = gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
 #endif
-  nsRefPtr<gfxContext> tmpCtx = new gfxContext(tmpDT);
 
-  GeneratePath(tmpCtx, aToBBoxUserspace);
-  tmpCtx->SetMatrix(gfxMatrix());
+  FillRule fillRule = StyleSVG()->mFillRule == NS_STYLE_FILL_RULE_NONZERO
+                        ? FillRule::FILL_WINDING : FillRule::FILL_EVEN_ODD;
+  RefPtr<PathBuilder> builder = tmpDT->CreatePathBuilder(fillRule);
+  RefPtr<Path> pathInUserSpace = element->BuildPath(builder);
+  if (!pathInUserSpace) {
+    return bbox;
+  }
+  RefPtr<Path> pathInBBoxSpace;
+  if (aToBBoxUserspace.IsIdentity()) {
+    pathInBBoxSpace = pathInUserSpace;
+  } else {
+    builder =
+      pathInUserSpace->TransformedCopyToBuilder(aToBBoxUserspace, fillRule);
+    pathInBBoxSpace = builder->Finish();
+    if (!pathInBBoxSpace) {
+      return 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 = tmpCtx->GetUserPathExtent();
+  Rect pathBBoxExtents = pathInBBoxSpace->GetBounds();
+  if (!pathBBoxExtents.IsFinite()) {
+    // This can happen in the case that we only have a move-to command in the
+    // path commands, in which case we know nothing gets rendered.
+    return bbox;
+  }
 
   // Account for fill:
   if ((aFlags & nsSVGUtils::eBBoxIncludeFillGeometry) ||
       ((aFlags & nsSVGUtils::eBBoxIncludeFill) &&
        StyleSVG()->mFill.mType != eStyleSVGPaintType_None)) {
-    bbox = pathExtents;
+    bbox = pathBBoxExtents;
   }
 
   // Account for stroke:
   if ((aFlags & nsSVGUtils::eBBoxIncludeStrokeGeometry) ||
       ((aFlags & nsSVGUtils::eBBoxIncludeStroke) &&
        nsSVGUtils::HasStroke(this))) {
-    // We can't use tmpCtx->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 tmpCtx->GetUserStrokeExtent() to find
-      // the center point of the extents even though it gets the extents wrong.
-      pathExtents.MoveTo(tmpCtx->GetUserStrokeExtent().Center());
-      pathExtents.SizeTo(0, 0);
+#if 0
+    // This disabled code is how we would calculate the stroke bounds using
+    // Moz2D Path::GetStrokedBounds(). Unfortunately at the time of writing it
+    // there are two problems that prevent us from using it.
+    //
+    // First, it seems that some of the Moz2D backends are really dumb. Not
+    // only do some GetStrokeOptions() implementations sometimes significantly
+    // overestimate the stroke bounds, but if an argument is passed for the
+    // aTransform parameter then they just return bounds-of-transformed-bounds.
+    // These two things combined can lead the bounds to be unacceptably
+    // oversized, leading to massive over-invalidation.
+    //
+    // Second, the way we account for non-scaling-stroke by transforming the
+    // path using the transform to the outer-<svg> element is not compatible
+    // with the way that nsSVGPathGeometryFrame::Reflow() inserts a scale into
+    // aToBBoxUserspace and then scales the bounds that we return.
+    SVGContentUtils::AutoStrokeOptions strokeOptions;
+    SVGContentUtils::GetStrokeOptions(&strokeOptions, element, StyleContext(),
+                                      nullptr, SVGContentUtils::eIgnoreStrokeDashing);
+    Rect strokeBBoxExtents;
+    gfxMatrix userToOuterSVG;
+    if (nsSVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG)) {
+      Matrix outerSVGToUser = ToMatrix(userToOuterSVG);
+      outerSVGToUser.Invert();
+      Matrix outerSVGToBBox = aToBBoxUserspace * outerSVGToUser;
+      RefPtr<PathBuilder> builder =
+        pathInUserSpace->TransformedCopyToBuilder(ToMatrix(userToOuterSVG));
+      RefPtr<Path> pathInOuterSVGSpace = builder->Finish();
+      strokeBBoxExtents =
+        pathInOuterSVGSpace->GetStrokedBounds(strokeOptions, outerSVGToBBox);
+    } else {
+      strokeBBoxExtents =
+        pathInUserSpace->GetStrokedBounds(strokeOptions, aToBBoxUserspace);
     }
-    bbox.UnionEdges(nsSVGUtils::PathExtentsToMaxStrokeExtents(pathExtents,
-                                                              this,
-                                                              ThebesMatrix(aToBBoxUserspace)));
+    MOZ_ASSERT(strokeBBoxExtents.IsFinite(), "bbox is about to go bad");
+    bbox.UnionEdges(strokeBBoxExtents);
+#else
+    // For now we just use nsSVGUtils::PathExtentsToMaxStrokeExtents:
+    gfxRect strokeBBoxExtents =
+      nsSVGUtils::PathExtentsToMaxStrokeExtents(ThebesRect(pathBBoxExtents),
+                                                this,
+                                                ThebesMatrix(aToBBoxUserspace));
+    MOZ_ASSERT(ToRect(strokeBBoxExtents).IsFinite(), "bbox is about to go bad");
+    bbox.UnionEdges(strokeBBoxExtents);
+#endif
   }
 
   // Account for markers:
   if ((aFlags & nsSVGUtils::eBBoxIncludeMarkers) != 0 &&
       static_cast<nsSVGPathGeometryElement*>(mContent)->IsMarkable()) {
 
     float strokeWidth = nsSVGUtils::GetStrokeWidth(this);
     MarkerProperties properties = GetMarkerProperties(this);
@@ -538,16 +590,17 @@ nsSVGPathGeometryFrame::GetBBoxContribut
 
       for (uint32_t i = 0; i < num; i++) {
         nsSVGMark& mark = marks[i];
         nsSVGMarkerFrame* frame = markerFrames[mark.type];
         if (frame) {
           SVGBBox mbbox =
             frame->GetMarkBBoxContribution(aToBBoxUserspace, aFlags, this,
                                            &marks[i], strokeWidth);
+          MOZ_ASSERT(mbbox.IsFinite(), "bbox is about to go bad");
           bbox.UnionEdges(mbbox);
         }
       }
     }
   }
 
   return bbox;
 }