From 033cb8c72b653a51519c0a74df9f90de9a7afdf4 Mon Sep 17 00:00:00 2001 From: Tom Klein Date: Tue, 1 Sep 2015 06:17:00 -0400 Subject: [PATCH] Bug 1092125 - Part 1: Add non-scaling-stroke support to nsSVGPathGeometryElement::GetGeometryBounds (except line). r=jwatt --- dom/svg/SVGCircleElement.cpp | 22 +++++++++++---- dom/svg/SVGCircleElement.h | 3 ++- dom/svg/SVGContentUtils.cpp | 39 +++++++++++++++++++++++++++ dom/svg/SVGContentUtils.h | 24 ++++++++++++++--- dom/svg/SVGEllipseElement.cpp | 22 +++++++++++---- dom/svg/SVGEllipseElement.h | 3 ++- dom/svg/SVGImageElement.cpp | 8 +++--- dom/svg/SVGImageElement.h | 3 ++- dom/svg/SVGLineElement.cpp | 22 +++++++++------ dom/svg/SVGLineElement.h | 3 ++- dom/svg/SVGRectElement.cpp | 30 +++++++++++++++++---- dom/svg/SVGRectElement.h | 3 ++- dom/svg/nsSVGPathGeometryElement.h | 11 +++++++- dom/svg/nsSVGPolyElement.cpp | 18 +++++++------ dom/svg/nsSVGPolyElement.h | 3 ++- dom/svg/test/bounds-helper.svg | 8 ++++++ dom/svg/test/test_bounds.html | 33 ++++++++++++++++++----- layout/svg/nsSVGPathGeometryFrame.cpp | 37 ++++++++++++++++--------- 18 files changed, 228 insertions(+), 64 deletions(-) diff --git a/dom/svg/SVGCircleElement.cpp b/dom/svg/SVGCircleElement.cpp index 9a39f213acb3..336ae0a1b887 100644 --- a/dom/svg/SVGCircleElement.cpp +++ b/dom/svg/SVGCircleElement.cpp @@ -83,26 +83,38 @@ SVGCircleElement::GetLengthInfo() // nsSVGPathGeometryElement methods bool -SVGCircleElement::GetGeometryBounds( - Rect* aBounds, const StrokeOptions& aStrokeOptions, const Matrix& aTransform) +SVGCircleElement::GetGeometryBounds(Rect* aBounds, + const StrokeOptions& aStrokeOptions, + const Matrix& aToBoundsSpace, + const Matrix* aToNonScalingStrokeSpace) { float x, y, r; GetAnimatedLengthValues(&x, &y, &r, nullptr); if (r <= 0.f) { // Rendering of the element is disabled - *aBounds = Rect(aTransform * Point(x, y), Size()); + *aBounds = Rect(aToBoundsSpace * Point(x, y), Size()); return true; } - if (aTransform.IsRectilinear()) { + if (aToBoundsSpace.IsRectilinear()) { // Optimize the case where we can treat the circle as a rectangle and // still get tight bounds. if (aStrokeOptions.mLineWidth > 0.f) { + if (aToNonScalingStrokeSpace) { + if (aToNonScalingStrokeSpace->IsRectilinear()) { + Rect userBounds(x - r, y - r, 2 * r, 2 * r); + SVGContentUtils::RectilinearGetStrokeBounds( + userBounds, aToBoundsSpace, *aToNonScalingStrokeSpace, + aStrokeOptions.mLineWidth, aBounds); + return true; + } + return false; + } r += aStrokeOptions.mLineWidth / 2.f; } Rect rect(x - r, y - r, 2 * r, 2 * r); - *aBounds = aTransform.TransformBounds(rect); + *aBounds = aToBoundsSpace.TransformBounds(rect); return true; } diff --git a/dom/svg/SVGCircleElement.h b/dom/svg/SVGCircleElement.h index d0968a7eca89..ff7bfe9024ad 100644 --- a/dom/svg/SVGCircleElement.h +++ b/dom/svg/SVGCircleElement.h @@ -32,7 +32,8 @@ public: // nsSVGPathGeometryElement methods: virtual bool GetGeometryBounds(Rect* aBounds, const StrokeOptions& aStrokeOptions, - const Matrix& aTransform) override; + const Matrix& aToBoundsSpace, + const Matrix* aToNonScalingStrokeSpace = nullptr) override; virtual already_AddRefed BuildPath(PathBuilder* aBuilder) override; virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override; diff --git a/dom/svg/SVGContentUtils.cpp b/dom/svg/SVGContentUtils.cpp index 0689e4798a01..b29bedb66e8d 100644 --- a/dom/svg/SVGContentUtils.cpp +++ b/dom/svg/SVGContentUtils.cpp @@ -464,6 +464,45 @@ SVGContentUtils::GetCTM(nsSVGElement *aElement, bool aScreenCTM) return GetCTMInternal(aElement, aScreenCTM, false); } +void +SVGContentUtils::RectilinearGetStrokeBounds(const Rect& aRect, + const Matrix& aToBoundsSpace, + const Matrix& aToNonScalingStrokeSpace, + float aStrokeWidth, + Rect* aBounds) +{ + MOZ_ASSERT(aToBoundsSpace.IsRectilinear(), + "aToBoundsSpace must be rectilinear"); + MOZ_ASSERT(aToNonScalingStrokeSpace.IsRectilinear(), + "aToNonScalingStrokeSpace must be rectilinear"); + + Matrix nonScalingToSource = aToNonScalingStrokeSpace.Inverse(); + Matrix nonScalingToBounds = nonScalingToSource * aToBoundsSpace; + + *aBounds = aToBoundsSpace.TransformBounds(aRect); + + // Compute the amounts dx and dy that nonScalingToBounds scales a half-width + // stroke in the x and y directions, and then inflate aBounds by those amounts + // so that when aBounds is transformed back to non-scaling-stroke space + // it will map onto the correct stroked bounds. + + Float dx = 0.0f; + Float dy = 0.0f; + // nonScalingToBounds is rectilinear, so either _12 and _21 are zero or _11 + // and _22 are zero, and in each case the non-zero entries (from among _11, + // _12, _21, _22) simply scale the stroke width in the x and y directions. + if (FuzzyEqual(nonScalingToBounds._12, 0) && + FuzzyEqual(nonScalingToBounds._21, 0)) { + dx = (aStrokeWidth / 2.0f) * std::abs(nonScalingToBounds._11); + dy = (aStrokeWidth / 2.0f) * std::abs(nonScalingToBounds._22); + } else { + dx = (aStrokeWidth / 2.0f) * std::abs(nonScalingToBounds._21); + dy = (aStrokeWidth / 2.0f) * std::abs(nonScalingToBounds._12); + } + + aBounds->Inflate(dx, dy); +} + double SVGContentUtils::ComputeNormalizedHypotenuse(double aWidth, double aHeight) { diff --git a/dom/svg/SVGContentUtils.h b/dom/svg/SVGContentUtils.h index 26f96a7a5c52..2136c5e6e780 100644 --- a/dom/svg/SVGContentUtils.h +++ b/dom/svg/SVGContentUtils.h @@ -63,6 +63,8 @@ class SVGContentUtils { public: typedef mozilla::gfx::Float Float; + typedef mozilla::gfx::Matrix Matrix; + typedef mozilla::gfx::Rect Rect; typedef mozilla::gfx::StrokeOptions StrokeOptions; typedef mozilla::SVGAnimatedPreserveAspectRatio SVGAnimatedPreserveAspectRatio; typedef mozilla::SVGPreserveAspectRatio SVGPreserveAspectRatio; @@ -180,7 +182,23 @@ public: const char16_t **aParams, uint32_t aParamsLength); - static mozilla::gfx::Matrix GetCTM(nsSVGElement *aElement, bool aScreenCTM); + static Matrix GetCTM(nsSVGElement *aElement, bool aScreenCTM); + + /** + * Gets the tight bounds-space stroke bounds of the non-scaling-stroked rect + * aRect. + * @param aToBoundsSpace transforms from source space to the space aBounds + * should be computed in. Must be rectilinear. + * @param aToNonScalingStrokeSpace transforms from source + * space to the space in which non-scaling stroke should be applied. + * Must be rectilinear. + */ + static void + RectilinearGetStrokeBounds(const Rect& aRect, + const Matrix& aToBoundsSpace, + const Matrix& aToNonScalingStrokeSpace, + float aStrokeWidth, + Rect* aBounds); /** * Check if this is one of the SVG elements that SVG 1.1 Full says @@ -205,13 +223,13 @@ public: /* Generate a viewbox to viewport tranformation matrix */ - static mozilla::gfx::Matrix + static Matrix GetViewBoxTransform(float aViewportWidth, float aViewportHeight, float aViewboxX, float aViewboxY, float aViewboxWidth, float aViewboxHeight, const SVGAnimatedPreserveAspectRatio &aPreserveAspectRatio); - static mozilla::gfx::Matrix + static Matrix GetViewBoxTransform(float aViewportWidth, float aViewportHeight, float aViewboxX, float aViewboxY, float aViewboxWidth, float aViewboxHeight, diff --git a/dom/svg/SVGEllipseElement.cpp b/dom/svg/SVGEllipseElement.cpp index e0e5c9bb552e..5a5b483ba0c5 100644 --- a/dom/svg/SVGEllipseElement.cpp +++ b/dom/svg/SVGEllipseElement.cpp @@ -94,27 +94,39 @@ SVGEllipseElement::GetLengthInfo() // nsSVGPathGeometryElement methods bool -SVGEllipseElement::GetGeometryBounds( - Rect* aBounds, const StrokeOptions& aStrokeOptions, const Matrix& aTransform) +SVGEllipseElement::GetGeometryBounds(Rect* aBounds, + const StrokeOptions& aStrokeOptions, + const Matrix& aToBoundsSpace, + const Matrix* aToNonScalingStrokeSpace) { float x, y, rx, ry; GetAnimatedLengthValues(&x, &y, &rx, &ry, nullptr); if (rx <= 0.f || ry <= 0.f) { // Rendering of the element is disabled - *aBounds = Rect(aTransform * Point(x, y), Size()); + *aBounds = Rect(aToBoundsSpace * Point(x, y), Size()); return true; } - if (aTransform.IsRectilinear()) { + if (aToBoundsSpace.IsRectilinear()) { // Optimize the case where we can treat the ellipse as a rectangle and // still get tight bounds. if (aStrokeOptions.mLineWidth > 0.f) { + if (aToNonScalingStrokeSpace) { + if (aToNonScalingStrokeSpace->IsRectilinear()) { + Rect userBounds(x - rx, y - ry, 2 * rx, 2 * ry); + SVGContentUtils::RectilinearGetStrokeBounds( + userBounds, aToBoundsSpace, *aToNonScalingStrokeSpace, + aStrokeOptions.mLineWidth, aBounds); + return true; + } + return false; + } rx += aStrokeOptions.mLineWidth / 2.f; ry += aStrokeOptions.mLineWidth / 2.f; } Rect rect(x - rx, y - ry, 2 * rx, 2 * ry); - *aBounds = aTransform.TransformBounds(rect); + *aBounds = aToBoundsSpace.TransformBounds(rect); return true; } diff --git a/dom/svg/SVGEllipseElement.h b/dom/svg/SVGEllipseElement.h index b9efe7f9bf84..27c132fd9674 100644 --- a/dom/svg/SVGEllipseElement.h +++ b/dom/svg/SVGEllipseElement.h @@ -32,7 +32,8 @@ public: // nsSVGPathGeometryElement methods: virtual bool GetGeometryBounds(Rect* aBounds, const StrokeOptions& aStrokeOptions, - const Matrix& aTransform) override; + const Matrix& aToBoundsSpace, + const Matrix* aToNonScalingStrokeSpace = nullptr) override; virtual already_AddRefed BuildPath(PathBuilder* aBuilder) override; virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override; diff --git a/dom/svg/SVGImageElement.cpp b/dom/svg/SVGImageElement.cpp index b05a756703d0..a2224e326d80 100644 --- a/dom/svg/SVGImageElement.cpp +++ b/dom/svg/SVGImageElement.cpp @@ -226,8 +226,10 @@ SVGImageElement::IsAttributeMapped(const nsIAtom* name) const /* For the purposes of the update/invalidation logic pretend to be a rectangle. */ bool -SVGImageElement::GetGeometryBounds( - Rect* aBounds, const StrokeOptions& aStrokeOptions, const Matrix& aTransform) +SVGImageElement::GetGeometryBounds(Rect* aBounds, + const StrokeOptions& aStrokeOptions, + const Matrix& aToBoundsSpace, + const Matrix* aToNonScalingStrokeSpace) { Rect rect; GetAnimatedLengthValues(&rect.x, &rect.y, &rect.width, @@ -238,7 +240,7 @@ SVGImageElement::GetGeometryBounds( rect.SetEmpty(); // Make sure width/height are zero and not negative } - *aBounds = aTransform.TransformBounds(rect); + *aBounds = aToBoundsSpace.TransformBounds(rect); return true; } diff --git a/dom/svg/SVGImageElement.h b/dom/svg/SVGImageElement.h index f8e2882da3ad..69ef9c7d2e10 100644 --- a/dom/svg/SVGImageElement.h +++ b/dom/svg/SVGImageElement.h @@ -55,7 +55,8 @@ public: // nsSVGPathGeometryElement methods: virtual bool GetGeometryBounds(Rect* aBounds, const StrokeOptions& aStrokeOptions, - const Matrix& aTransform) override; + const Matrix& aToBoundsSpace, + const Matrix* aToNonScalingStrokeSpace = nullptr) override; virtual already_AddRefed BuildPath(PathBuilder* aBuilder) override; // nsSVGSVGElement methods: diff --git a/dom/svg/SVGLineElement.cpp b/dom/svg/SVGLineElement.cpp index 8e481567f8f6..eb6d4a3b8005 100644 --- a/dom/svg/SVGLineElement.cpp +++ b/dom/svg/SVGLineElement.cpp @@ -148,27 +148,33 @@ SVGLineElement::BuildPath(PathBuilder* aBuilder) } bool -SVGLineElement::GetGeometryBounds( - Rect* aBounds, const StrokeOptions& aStrokeOptions, const Matrix& aTransform) +SVGLineElement::GetGeometryBounds(Rect* aBounds, + const StrokeOptions& aStrokeOptions, + const Matrix& aToBoundsSpace, + const Matrix* aToNonScalingStrokeSpace) { + if (aToNonScalingStrokeSpace) { + return false; + } + float x1, y1, x2, y2; GetAnimatedLengthValues(&x1, &y1, &x2, &y2, nullptr); if (aStrokeOptions.mLineWidth <= 0) { - *aBounds = Rect(aTransform * Point(x1, y1), Size()); - aBounds->ExpandToEnclose(aTransform * Point(x2, y2)); + *aBounds = Rect(aToBoundsSpace * Point(x1, y1), Size()); + aBounds->ExpandToEnclose(aToBoundsSpace * Point(x2, y2)); return true; } if (aStrokeOptions.mLineCap == CapStyle::ROUND) { - if (!aTransform.IsRectilinear()) { + if (!aToBoundsSpace.IsRectilinear()) { // TODO: handle this case. return false; } Rect bounds(Point(x1, y1), Size()); bounds.ExpandToEnclose(Point(x2, y2)); bounds.Inflate(aStrokeOptions.mLineWidth / 2.f); - *aBounds = aTransform.TransformBounds(bounds); + *aBounds = aToBoundsSpace.TransformBounds(bounds); return true; } @@ -201,9 +207,9 @@ SVGLineElement::GetGeometryBounds( points[2] = Point(x2 + xDelta, y2 + yDelta); points[3] = Point(x2 - xDelta, y2 - yDelta); - *aBounds = Rect(aTransform * points[0], Size()); + *aBounds = Rect(aToBoundsSpace * points[0], Size()); for (uint32_t i = 1; i < 4; ++i) { - aBounds->ExpandToEnclose(aTransform * points[i]); + aBounds->ExpandToEnclose(aToBoundsSpace * points[i]); } return true; } diff --git a/dom/svg/SVGLineElement.h b/dom/svg/SVGLineElement.h index 93ae940130bf..f2bafa4a36b9 100644 --- a/dom/svg/SVGLineElement.h +++ b/dom/svg/SVGLineElement.h @@ -40,7 +40,8 @@ public: virtual void GetAsSimplePath(SimplePath* aSimplePath) override; virtual already_AddRefed BuildPath(PathBuilder* aBuilder) override; virtual bool GetGeometryBounds(Rect* aBounds, const StrokeOptions& aStrokeOptions, - const Matrix& aTransform) override; + const Matrix& aToBoundsSpace, + const Matrix* aToNonScalingStrokeSpace = nullptr) override; virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override; diff --git a/dom/svg/SVGRectElement.cpp b/dom/svg/SVGRectElement.cpp index 923ecbfbcf76..9fd31175eb5a 100644 --- a/dom/svg/SVGRectElement.cpp +++ b/dom/svg/SVGRectElement.cpp @@ -112,8 +112,10 @@ SVGRectElement::GetLengthInfo() // nsSVGPathGeometryElement methods bool -SVGRectElement::GetGeometryBounds( - Rect* aBounds, const StrokeOptions& aStrokeOptions, const Matrix& aTransform) +SVGRectElement::GetGeometryBounds(Rect* aBounds, + const StrokeOptions& aStrokeOptions, + const Matrix& aToBoundsSpace, + const Matrix* aToNonScalingStrokeSpace) { Rect rect; Float rx, ry; @@ -124,11 +126,11 @@ SVGRectElement::GetGeometryBounds( // Rendering of the element disabled rect.SetEmpty(); // Make sure width/height are zero and not negative // We still want the x/y position from 'rect' - *aBounds = aTransform.TransformBounds(rect); + *aBounds = aToBoundsSpace.TransformBounds(rect); return true; } - if (!aTransform.IsRectilinear()) { + if (!aToBoundsSpace.IsRectilinear()) { // We can't ignore the radii in this case if we want tight bounds rx = std::max(rx, 0.0f); ry = std::max(ry, 0.0f); @@ -139,10 +141,28 @@ SVGRectElement::GetGeometryBounds( } if (aStrokeOptions.mLineWidth > 0.f) { + if (aToNonScalingStrokeSpace) { + if (aToNonScalingStrokeSpace->IsRectilinear()) { + rect = aToNonScalingStrokeSpace->TransformBounds(rect); + // Note that, in principle, an author could cause the corners of the + // rect to be beveled by specifying stroke-linejoin or setting + // stroke-miterlimit to be less than sqrt(2). In that very unlikely + // event the bounds that we calculate here may be too big if + // aToBoundsSpace is non-rectilinear. This is likely to be so rare it's + // not worth handling though. + rect.Inflate(aStrokeOptions.mLineWidth / 2.f); + Matrix nonScalingToBounds = + aToNonScalingStrokeSpace->Inverse() * aToBoundsSpace; + *aBounds = nonScalingToBounds.TransformBounds(rect); + return true; + } + return false; + } + // The "beveled" comment above applies here too rect.Inflate(aStrokeOptions.mLineWidth / 2.f); } - *aBounds = aTransform.TransformBounds(rect); + *aBounds = aToBoundsSpace.TransformBounds(rect); return true; } diff --git a/dom/svg/SVGRectElement.h b/dom/svg/SVGRectElement.h index 67d5c1384e50..fe32414eec5f 100644 --- a/dom/svg/SVGRectElement.h +++ b/dom/svg/SVGRectElement.h @@ -32,7 +32,8 @@ public: // nsSVGPathGeometryElement methods: virtual bool GetGeometryBounds(Rect* aBounds, const StrokeOptions& aStrokeOptions, - const Matrix& aTransform) override; + const Matrix& aToBoundsSpace, + const Matrix* aToNonScalingStrokeSpace = nullptr) override; virtual void GetAsSimplePath(SimplePath* aSimplePath) override; virtual already_AddRefed BuildPath(PathBuilder* aBuilder = nullptr) override; diff --git a/dom/svg/nsSVGPathGeometryElement.h b/dom/svg/nsSVGPathGeometryElement.h index e112b108613d..ae45374896eb 100644 --- a/dom/svg/nsSVGPathGeometryElement.h +++ b/dom/svg/nsSVGPathGeometryElement.h @@ -76,9 +76,18 @@ public: * GetStrokedBounds on it. It also helps us avoid rounding error for simple * shapes and simple transforms where the Moz2D Path backends can fail to * produce the clean integer bounds that content authors expect in some cases. + * + * If |aToNonScalingStrokeSpace| is non-null then |aBounds|, which is computed + * in bounds space, has the property that it's the smallest (axis-aligned) + * rectangular bound containing the image of this shape as stroked in + * non-scaling-stroke space. (When all transforms involved are rectilinear + * the bounds of the image of |aBounds| in non-scaling-stroke space will be + * tight, but if there are non-rectilinear transforms involved then that may + * be impossible and this method will return false). */ virtual bool GetGeometryBounds(Rect* aBounds, const StrokeOptions& aStrokeOptions, - const Matrix& aTransform) { + const Matrix& aToBoundsSpace, + const Matrix* aToNonScalingStrokeSpace = nullptr) { return false; } diff --git a/dom/svg/nsSVGPolyElement.cpp b/dom/svg/nsSVGPolyElement.cpp index 0061a35df1f9..5686a0cbd5f7 100644 --- a/dom/svg/nsSVGPolyElement.cpp +++ b/dom/svg/nsSVGPolyElement.cpp @@ -122,8 +122,10 @@ nsSVGPolyElement::GetMarkPoints(nsTArray *aMarks) } bool -nsSVGPolyElement::GetGeometryBounds( - Rect* aBounds, const StrokeOptions& aStrokeOptions, const Matrix& aTransform) +nsSVGPolyElement::GetGeometryBounds(Rect* aBounds, + const StrokeOptions& aStrokeOptions, + const Matrix& aToBoundsSpace, + const Matrix* aToNonScalingStrokeSpace) { const SVGPointList &points = mPoints.GetAnimValue(); @@ -133,23 +135,23 @@ nsSVGPolyElement::GetGeometryBounds( return true; } - if (aStrokeOptions.mLineWidth > 0) { - // We don't handle stroke-miterlimit etc. yet + if (aStrokeOptions.mLineWidth > 0 || aToNonScalingStrokeSpace) { + // We don't handle non-scaling-stroke or stroke-miterlimit etc. yet return false; } - if (aTransform.IsRectilinear()) { + if (aToBoundsSpace.IsRectilinear()) { // We can avoid transforming each point and just transform the result. // Important for large point lists. Rect bounds(points[0], Size()); for (uint32_t i = 1; i < points.Length(); ++i) { bounds.ExpandToEnclose(points[i]); } - *aBounds = aTransform.TransformBounds(bounds); + *aBounds = aToBoundsSpace.TransformBounds(bounds); } else { - *aBounds = Rect(aTransform * points[0], Size()); + *aBounds = Rect(aToBoundsSpace * points[0], Size()); for (uint32_t i = 1; i < points.Length(); ++i) { - aBounds->ExpandToEnclose(aTransform * points[i]); + aBounds->ExpandToEnclose(aToBoundsSpace * points[i]); } } return true; diff --git a/dom/svg/nsSVGPolyElement.h b/dom/svg/nsSVGPolyElement.h index ffcb145c7384..1dcde0078450 100644 --- a/dom/svg/nsSVGPolyElement.h +++ b/dom/svg/nsSVGPolyElement.h @@ -47,7 +47,8 @@ public: virtual bool IsMarkable() override { return true; } virtual void GetMarkPoints(nsTArray *aMarks) override; virtual bool GetGeometryBounds(Rect* aBounds, const StrokeOptions& aStrokeOptions, - const Matrix& aTransform) override; + const Matrix& aToBoundsSpace, + const Matrix* aToNonScalingStrokeSpace = nullptr) override; // WebIDL already_AddRefed Points(); diff --git a/dom/svg/test/bounds-helper.svg b/dom/svg/test/bounds-helper.svg index 6e08ba8f6f87..98100ee0cc37 100644 --- a/dom/svg/test/bounds-helper.svg +++ b/dom/svg/test/bounds-helper.svg @@ -39,5 +39,13 @@ text { font: 20px monospace; } + + diff --git a/dom/svg/test/test_bounds.html b/dom/svg/test/test_bounds.html index e615dfd6de37..7128a97011b8 100644 --- a/dom/svg/test/test_bounds.html +++ b/dom/svg/test/test_bounds.html @@ -159,13 +159,10 @@ function runTest() is(rect3aBounds.width, 108, "rect3a.getBoundingClientRect().width"); is(rect3aBounds.height, 108, "rect3a.getBoundingClientRect().height"); - // Our PathExtentsToMaxStrokeExtents implementation considers the stroke - // width to be sqrt(2)*stroke-width in case the rect is rotated 45 degrees, - // so unfortunately we get slightly large results currently. Bug 1092125. - isWithAbsTolerance(rect3bBounds.left, 198, 1, "rect3b.getBoundingClientRect().left"); - isWithAbsTolerance(rect3bBounds.top, 198, 1, "rect3b.getBoundingClientRect().top"); - isWithAbsTolerance(rect3bBounds.width, 54, 2, "rect3b.getBoundingClientRect().width"); - isWithAbsTolerance(rect3bBounds.height, 54, 2, "rect3b.getBoundingClientRect().height"); + isWithAbsTolerance(rect3bBounds.left, 198, 0.1, "rect3b.getBoundingClientRect().left"); + isWithAbsTolerance(rect3bBounds.top, 198, 0.1, "rect3b.getBoundingClientRect().top"); + isWithAbsTolerance(rect3bBounds.width, 54, 0.1, "rect3b.getBoundingClientRect().width"); + isWithAbsTolerance(rect3bBounds.height, 54, 0.1, "rect3b.getBoundingClientRect().height"); rect = new Rect(350 - 108 * sin45, 150 - 108 * sin45, 108 * sin45 * 2, 108 * sin45 * 2); isWithAbsTolerance(rect4aBounds.left, rect.left, 0.1, "rect4a.getBoundingClientRect().left"); @@ -203,6 +200,28 @@ function runTest() is(gBounds.width, 50, "g2.getBoundingClientRect().width"); is(gBounds.height, 50, "g2.getBoundingClientRect().height"); + var nonScalingStrokedCircle1Bounds = + doc.getElementById("nonScalingStrokedCircle1").getBoundingClientRect(); + isWithAbsTolerance(nonScalingStrokedCircle1Bounds.left, 10, 0.15, + "nonScalingStrokedCircle1.getBoundingClientRect().left"); + isWithAbsTolerance(nonScalingStrokedCircle1Bounds.top, 105, 0.15, + "nonScalingStrokedCircle1.getBoundingClientRect().top"); + isWithAbsTolerance(nonScalingStrokedCircle1Bounds.width, 70, 0.15, + "nonScalingStrokedCircle1.getBoundingClientRect().width"); + isWithAbsTolerance(nonScalingStrokedCircle1Bounds.height, 50, 0.15, + "nonScalingStrokedCircle1.getBoundingClientRect().height"); + + var nonScalingStrokedEllipse1Bounds = + doc.getElementById("nonScalingStrokedEllipse1").getBoundingClientRect(); + isWithAbsTolerance(nonScalingStrokedEllipse1Bounds.left, 5, 0.15, + "nonScalingStrokedEllipse1.getBoundingClientRect().left"); + isWithAbsTolerance(nonScalingStrokedEllipse1Bounds.top, 40, 0.15, + "nonScalingStrokedEllipse1.getBoundingClientRect().top"); + isWithAbsTolerance(nonScalingStrokedEllipse1Bounds.width, 30, 0.15, + "nonScalingStrokedEllipse1.getBoundingClientRect().width"); + isWithAbsTolerance(nonScalingStrokedEllipse1Bounds.height, 40, 0.15, + "nonScalingStrokedEllipse1.getBoundingClientRect().height"); + SimpleTest.finish(); } diff --git a/layout/svg/nsSVGPathGeometryFrame.cpp b/layout/svg/nsSVGPathGeometryFrame.cpp index 83eb1346d502..1448ed236001 100644 --- a/layout/svg/nsSVGPathGeometryFrame.cpp +++ b/layout/svg/nsSVGPathGeometryFrame.cpp @@ -470,25 +470,36 @@ nsSVGPathGeometryFrame::GetBBoxContribution(const Matrix &aToBBoxUserspace, ((aFlags & nsSVGUtils::eBBoxIncludeStroke) && nsSVGUtils::HasStroke(this)); - bool gotSimpleBounds = false; - if (!StyleSVGReset()->HasNonScalingStroke()) { - SVGContentUtils::AutoStrokeOptions strokeOptions; + SVGContentUtils::AutoStrokeOptions strokeOptions; + if (getStroke) { + SVGContentUtils::GetStrokeOptions(&strokeOptions, element, + StyleContext(), nullptr, + SVGContentUtils::eIgnoreStrokeDashing); + } else { + // Override the default line width of 1.f so that when we call + // GetGeometryBounds below the result doesn't include stroke bounds. strokeOptions.mLineWidth = 0.f; - if (getStroke) { - SVGContentUtils::GetStrokeOptions(&strokeOptions, element, - StyleContext(), nullptr, - SVGContentUtils::eIgnoreStrokeDashing); - } - Rect simpleBounds; + } + + Rect simpleBounds; + bool gotSimpleBounds = false; + gfxMatrix userToOuterSVG; + if (getStroke && + nsSVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG)) { + Matrix moz2dUserToOuterSVG = ToMatrix(userToOuterSVG); + gotSimpleBounds = element->GetGeometryBounds(&simpleBounds, + strokeOptions, + aToBBoxUserspace, + &moz2dUserToOuterSVG); + } else { gotSimpleBounds = element->GetGeometryBounds(&simpleBounds, strokeOptions, aToBBoxUserspace); - if (gotSimpleBounds) { - bbox = simpleBounds; - } } - if (!gotSimpleBounds) { + if (gotSimpleBounds) { + bbox = simpleBounds; + } else { // Get the bounds using a Moz2D Path object (more expensive): RefPtr tmpDT; #ifdef XP_WIN