Bug 1080688 - Calculate SVG rect bounds using a simple rect transform rather than using a Moz2D Path. r=longsonr

This commit is contained in:
Jonathan Watt 2014-10-26 18:00:03 +00:00
parent 1d2c31c63d
commit c2bb3314cb
5 changed files with 159 additions and 96 deletions

View File

@ -7,6 +7,8 @@
#include "nsGkAtoms.h"
#include "mozilla/dom/SVGRectElementBinding.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/gfx/Matrix.h"
#include "mozilla/gfx/Rect.h"
#include "mozilla/gfx/PathHelpers.h"
#include <algorithm>
@ -108,6 +110,36 @@ SVGRectElement::GetLengthInfo()
//----------------------------------------------------------------------
// nsSVGPathGeometryElement methods
bool
SVGRectElement::GetGeometryBounds(Rect* aBounds, Float aStrokeWidth,
const Matrix& aTransform)
{
Rect r;
Float rx, ry;
GetAnimatedLengthValues(&r.x, &r.y, &r.width, &r.height, &rx, &ry, nullptr);
if (r.IsEmpty()) {
// Rendering of the element disabled
r.SetEmpty(); // make sure width/height are actually zero
*aBounds = r;
return true;
}
rx = std::max(rx, 0.0f);
ry = std::max(ry, 0.0f);
if (rx != 0 || ry != 0) {
return false;
}
if (aStrokeWidth > 0.f) {
r.Inflate(aStrokeWidth / 2.f);
}
*aBounds = aTransform.TransformBounds(r);
return true;
}
void
SVGRectElement::GetAsSimplePath(SimplePath* aSimplePath)
{

View File

@ -30,6 +30,8 @@ public:
virtual bool HasValidDimensions() const MOZ_OVERRIDE;
// nsSVGPathGeometryElement methods:
virtual bool GetGeometryBounds(Rect* aBounds, Float aStrokeWidth,
const Matrix& aTransform) MOZ_OVERRIDE;
virtual void GetAsSimplePath(SimplePath* aSimplePath) MOZ_OVERRIDE;
virtual TemporaryRef<Path> BuildPath(PathBuilder* aBuilder = nullptr) MOZ_OVERRIDE;

View File

@ -34,6 +34,7 @@ protected:
typedef mozilla::gfx::DrawTarget DrawTarget;
typedef mozilla::gfx::FillRule FillRule;
typedef mozilla::gfx::Float Float;
typedef mozilla::gfx::Matrix Matrix;
typedef mozilla::gfx::Path Path;
typedef mozilla::gfx::Point Point;
typedef mozilla::gfx::PathBuilder PathBuilder;
@ -69,6 +70,11 @@ public:
virtual bool IsMarkable();
virtual void GetMarkPoints(nsTArray<nsSVGMark> *aMarks);
virtual bool GetGeometryBounds(Rect* aBounds, Float aStrokeWidth,
const Matrix& aTransform) {
return false;
}
/**
* For use with GetAsSimplePath.
*/

View File

@ -3040,6 +3040,10 @@ struct nsStyleSVGReset {
return mFilters.Length() > 0;
}
bool HasNonScalingStroke() const {
return mVectorEffect == NS_STYLE_VECTOR_EFFECT_NON_SCALING_STROKE;
}
nsStyleClipPath mClipPath; // [reset]
nsTArray<nsStyleFilter> mFilters; // [reset]
nsCOMPtr<nsIURI> mMask; // [reset]

View File

@ -463,113 +463,132 @@ nsSVGPathGeometryFrame::GetBBoxContribution(const Matrix &aToBBoxUserspace,
nsSVGPathGeometryElement* element =
static_cast<nsSVGPathGeometryElement*>(mContent);
RefPtr<DrawTarget> tmpDT;
bool getFill = (aFlags & nsSVGUtils::eBBoxIncludeFillGeometry) ||
((aFlags & nsSVGUtils::eBBoxIncludeFill) &&
StyleSVG()->mFill.mType != eStyleSVGPaintType_None);
bool getStroke = (aFlags & nsSVGUtils::eBBoxIncludeStrokeGeometry) ||
((aFlags & nsSVGUtils::eBBoxIncludeStroke) &&
nsSVGUtils::HasStroke(this));
bool gotSimpleBounds = false;
if (!StyleSVGReset()->HasNonScalingStroke()) {
Float strokeWidth = getStroke ? nsSVGUtils::GetStrokeWidth(this) : 0.f;
Rect simpleBounds;
gotSimpleBounds = element->GetGeometryBounds(&simpleBounds, strokeWidth,
aToBBoxUserspace);
if (gotSimpleBounds) {
bbox = simpleBounds;
}
}
if (!gotSimpleBounds) {
// Get the bounds using a Moz2D Path object (more expensive):
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));
// 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();
tmpDT = gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
#endif
FillRule fillRule = nsSVGUtils::ToFillRule(StyleSVG()->mFillRule);
RefPtr<Path> pathInUserSpace = element->GetOrBuildPath(*tmpDT, fillRule);
if (!pathInUserSpace) {
return bbox;
}
RefPtr<Path> pathInBBoxSpace;
if (aToBBoxUserspace.IsIdentity()) {
pathInBBoxSpace = pathInUserSpace;
} else {
RefPtr<PathBuilder> builder =
pathInUserSpace->TransformedCopyToBuilder(aToBBoxUserspace, fillRule);
pathInBBoxSpace = builder->Finish();
if (!pathInBBoxSpace) {
FillRule fillRule = nsSVGUtils::ToFillRule(StyleSVG()->mFillRule);
RefPtr<Path> pathInUserSpace = element->GetOrBuildPath(*tmpDT, fillRule);
if (!pathInUserSpace) {
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.
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 = pathBBoxExtents;
}
// Account for stroke:
if ((aFlags & nsSVGUtils::eBBoxIncludeStrokeGeometry) ||
((aFlags & nsSVGUtils::eBBoxIncludeStroke) &&
nsSVGUtils::HasStroke(this))) {
#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);
RefPtr<Path> pathInBBoxSpace;
if (aToBBoxUserspace.IsIdentity()) {
pathInBBoxSpace = pathInUserSpace;
} else {
strokeBBoxExtents =
pathInUserSpace->GetStrokedBounds(strokeOptions, aToBBoxUserspace);
RefPtr<PathBuilder> builder =
pathInUserSpace->TransformedCopyToBuilder(aToBBoxUserspace, fillRule);
pathInBBoxSpace = builder->Finish();
if (!pathInBBoxSpace) {
return bbox;
}
}
MOZ_ASSERT(strokeBBoxExtents.IsFinite(), "bbox is about to go bad");
bbox.UnionEdges(strokeBBoxExtents);
// 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.
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 (getFill) {
bbox = pathBBoxExtents;
}
// Account for stroke:
if (getStroke) {
#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);
}
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);
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: