mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-28 15:23:51 +00:00
Bug 1080688 - Calculate SVG rect bounds using a simple rect transform rather than using a Moz2D Path. r=longsonr
This commit is contained in:
parent
1d2c31c63d
commit
c2bb3314cb
@ -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)
|
||||
{
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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]
|
||||
|
@ -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:
|
||||
|
Loading…
Reference in New Issue
Block a user