summaryrefslogtreecommitdiffstats
path: root/dom/svg/SVGContentUtils.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/svg/SVGContentUtils.cpp')
-rw-r--r--dom/svg/SVGContentUtils.cpp898
1 files changed, 898 insertions, 0 deletions
diff --git a/dom/svg/SVGContentUtils.cpp b/dom/svg/SVGContentUtils.cpp
new file mode 100644
index 000000000..ed3b6032e
--- /dev/null
+++ b/dom/svg/SVGContentUtils.cpp
@@ -0,0 +1,898 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Main header first:
+// This is also necessary to ensure our definition of M_SQRT1_2 is picked up
+#include "SVGContentUtils.h"
+
+// Keep others in (case-insensitive) order:
+#include "gfx2DGlue.h"
+#include "gfxMatrix.h"
+#include "gfxPlatform.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/dom/SVGSVGElement.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/SVGContextPaint.h"
+#include "nsComputedDOMStyle.h"
+#include "nsFontMetrics.h"
+#include "nsIFrame.h"
+#include "nsIScriptError.h"
+#include "nsLayoutUtils.h"
+#include "SVGAnimationElement.h"
+#include "SVGAnimatedPreserveAspectRatio.h"
+#include "nsContentUtils.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Types.h"
+#include "mozilla/FloatingPoint.h"
+#include "nsStyleContext.h"
+#include "nsSVGPathDataParser.h"
+#include "SVGPathData.h"
+#include "SVGPathElement.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::gfx;
+
+SVGSVGElement*
+SVGContentUtils::GetOuterSVGElement(nsSVGElement *aSVGElement)
+{
+ nsIContent *element = nullptr;
+ nsIContent *ancestor = aSVGElement->GetFlattenedTreeParent();
+
+ while (ancestor && ancestor->IsSVGElement() &&
+ !ancestor->IsSVGElement(nsGkAtoms::foreignObject)) {
+ element = ancestor;
+ ancestor = element->GetFlattenedTreeParent();
+ }
+
+ if (element && element->IsSVGElement(nsGkAtoms::svg)) {
+ return static_cast<SVGSVGElement*>(element);
+ }
+ return nullptr;
+}
+
+void
+SVGContentUtils::ActivateByHyperlink(nsIContent *aContent)
+{
+ MOZ_ASSERT(aContent->IsNodeOfType(nsINode::eANIMATION),
+ "Expecting an animation element");
+
+ static_cast<SVGAnimationElement*>(aContent)->ActivateByHyperlink();
+}
+
+enum DashState {
+ eDashedStroke,
+ eContinuousStroke, //< all dashes, no gaps
+ eNoStroke //< all gaps, no dashes
+};
+
+static DashState
+GetStrokeDashData(SVGContentUtils::AutoStrokeOptions* aStrokeOptions,
+ nsSVGElement* aElement,
+ const nsStyleSVG* aStyleSVG,
+ SVGContextPaint* aContextPaint)
+{
+ size_t dashArrayLength;
+ Float totalLengthOfDashes = 0.0, totalLengthOfGaps = 0.0;
+ Float pathScale = 1.0;
+
+ if (aContextPaint && aStyleSVG->StrokeDasharrayFromObject()) {
+ const FallibleTArray<gfxFloat>& dashSrc = aContextPaint->GetStrokeDashArray();
+ dashArrayLength = dashSrc.Length();
+ if (dashArrayLength <= 0) {
+ return eContinuousStroke;
+ }
+ Float* dashPattern = aStrokeOptions->InitDashPattern(dashArrayLength);
+ if (!dashPattern) {
+ return eContinuousStroke;
+ }
+ for (size_t i = 0; i < dashArrayLength; i++) {
+ if (dashSrc[i] < 0.0) {
+ return eContinuousStroke; // invalid
+ }
+ dashPattern[i] = Float(dashSrc[i]);
+ (i % 2 ? totalLengthOfGaps : totalLengthOfDashes) += dashSrc[i];
+ }
+ } else {
+ const nsTArray<nsStyleCoord>& dasharray = aStyleSVG->mStrokeDasharray;
+ dashArrayLength = aStyleSVG->mStrokeDasharray.Length();
+ if (dashArrayLength <= 0) {
+ return eContinuousStroke;
+ }
+ if (aElement->IsSVGElement(nsGkAtoms::path)) {
+ pathScale = static_cast<SVGPathElement*>(aElement)->
+ GetPathLengthScale(SVGPathElement::eForStroking);
+ if (pathScale <= 0) {
+ return eContinuousStroke;
+ }
+ }
+ Float* dashPattern = aStrokeOptions->InitDashPattern(dashArrayLength);
+ if (!dashPattern) {
+ return eContinuousStroke;
+ }
+ for (uint32_t i = 0; i < dashArrayLength; i++) {
+ Float dashLength =
+ SVGContentUtils::CoordToFloat(aElement, dasharray[i]) * pathScale;
+ if (dashLength < 0.0) {
+ return eContinuousStroke; // invalid
+ }
+ dashPattern[i] = dashLength;
+ (i % 2 ? totalLengthOfGaps : totalLengthOfDashes) += dashLength;
+ }
+ }
+
+ // Now that aStrokeOptions.mDashPattern is fully initialized (we didn't
+ // return early above) we can safely set mDashLength:
+ aStrokeOptions->mDashLength = dashArrayLength;
+
+ if ((dashArrayLength % 2) == 1) {
+ // If we have a dash pattern with an odd number of lengths the pattern
+ // repeats a second time, per the SVG spec., and as implemented by Moz2D.
+ // When deciding whether to return eNoStroke or eContinuousStroke below we
+ // need to take into account that in the repeat pattern the dashes become
+ // gaps, and the gaps become dashes.
+ Float origTotalLengthOfDashes = totalLengthOfDashes;
+ totalLengthOfDashes += totalLengthOfGaps;
+ totalLengthOfGaps += origTotalLengthOfDashes;
+ }
+
+ // Stroking using dashes is much slower than stroking a continuous line
+ // (see bug 609361 comment 40), and much, much slower than not stroking the
+ // line at all. Here we check for cases when the dash pattern causes the
+ // stroke to essentially be continuous or to be nonexistent in which case
+ // we can avoid expensive stroking operations (the underlying platform
+ // graphics libraries don't seem to optimize for this).
+ if (totalLengthOfGaps <= 0) {
+ return eContinuousStroke;
+ }
+ // We can only return eNoStroke if the value of stroke-linecap isn't
+ // adding caps to zero length dashes.
+ if (totalLengthOfDashes <= 0 &&
+ aStyleSVG->mStrokeLinecap == NS_STYLE_STROKE_LINECAP_BUTT) {
+ return eNoStroke;
+ }
+
+ if (aContextPaint && aStyleSVG->StrokeDashoffsetFromObject()) {
+ aStrokeOptions->mDashOffset = Float(aContextPaint->GetStrokeDashOffset());
+ } else {
+ aStrokeOptions->mDashOffset =
+ SVGContentUtils::CoordToFloat(aElement, aStyleSVG->mStrokeDashoffset) *
+ pathScale;
+ }
+
+ return eDashedStroke;
+}
+
+void
+SVGContentUtils::GetStrokeOptions(AutoStrokeOptions* aStrokeOptions,
+ nsSVGElement* aElement,
+ nsStyleContext* aStyleContext,
+ SVGContextPaint* aContextPaint,
+ StrokeOptionFlags aFlags)
+{
+ RefPtr<nsStyleContext> styleContext;
+ if (aStyleContext) {
+ styleContext = aStyleContext;
+ } else {
+ styleContext =
+ nsComputedDOMStyle::GetStyleContextForElementNoFlush(aElement, nullptr,
+ nullptr);
+ }
+
+ if (!styleContext) {
+ return;
+ }
+
+ const nsStyleSVG* styleSVG = styleContext->StyleSVG();
+
+ bool checkedDashAndStrokeIsDashed = false;
+ 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();
+ }
+ checkedDashAndStrokeIsDashed = (dashState == eDashedStroke);
+ }
+
+ aStrokeOptions->mLineWidth =
+ GetStrokeWidth(aElement, styleContext, aContextPaint);
+
+ aStrokeOptions->mMiterLimit = Float(styleSVG->mStrokeMiterlimit);
+
+ switch (styleSVG->mStrokeLinejoin) {
+ case NS_STYLE_STROKE_LINEJOIN_MITER:
+ aStrokeOptions->mLineJoin = JoinStyle::MITER_OR_BEVEL;
+ break;
+ case NS_STYLE_STROKE_LINEJOIN_ROUND:
+ aStrokeOptions->mLineJoin = JoinStyle::ROUND;
+ break;
+ case NS_STYLE_STROKE_LINEJOIN_BEVEL:
+ aStrokeOptions->mLineJoin = JoinStyle::BEVEL;
+ break;
+ }
+
+ if (ShapeTypeHasNoCorners(aElement) && !checkedDashAndStrokeIsDashed) {
+ // Note: if aFlags == eIgnoreStrokeDashing then we may be returning the
+ // wrong linecap value here, since the actual linecap used on render in this
+ // case depends on whether the stroke is dashed or not.
+ aStrokeOptions->mLineCap = CapStyle::BUTT;
+ } else {
+ switch (styleSVG->mStrokeLinecap) {
+ case NS_STYLE_STROKE_LINECAP_BUTT:
+ aStrokeOptions->mLineCap = CapStyle::BUTT;
+ break;
+ case NS_STYLE_STROKE_LINECAP_ROUND:
+ aStrokeOptions->mLineCap = CapStyle::ROUND;
+ break;
+ case NS_STYLE_STROKE_LINECAP_SQUARE:
+ aStrokeOptions->mLineCap = CapStyle::SQUARE;
+ break;
+ }
+ }
+}
+
+Float
+SVGContentUtils::GetStrokeWidth(nsSVGElement* aElement,
+ nsStyleContext* aStyleContext,
+ SVGContextPaint* aContextPaint)
+{
+ RefPtr<nsStyleContext> styleContext;
+ if (aStyleContext) {
+ styleContext = aStyleContext;
+ } else {
+ styleContext =
+ nsComputedDOMStyle::GetStyleContextForElementNoFlush(aElement, nullptr,
+ nullptr);
+ }
+
+ if (!styleContext) {
+ return 0.0f;
+ }
+
+ const nsStyleSVG* styleSVG = styleContext->StyleSVG();
+
+ if (aContextPaint && styleSVG->StrokeWidthFromObject()) {
+ return aContextPaint->GetStrokeWidth();
+ }
+
+ return SVGContentUtils::CoordToFloat(aElement, styleSVG->mStrokeWidth);
+}
+
+float
+SVGContentUtils::GetFontSize(Element *aElement)
+{
+ if (!aElement)
+ return 1.0f;
+
+ RefPtr<nsStyleContext> styleContext =
+ nsComputedDOMStyle::GetStyleContextForElementNoFlush(aElement,
+ nullptr, nullptr);
+ if (!styleContext) {
+ // ReportToConsole
+ NS_WARNING("Couldn't get style context for content in GetFontStyle");
+ return 1.0f;
+ }
+
+ return GetFontSize(styleContext);
+}
+
+float
+SVGContentUtils::GetFontSize(nsIFrame *aFrame)
+{
+ MOZ_ASSERT(aFrame, "NULL frame in GetFontSize");
+ return GetFontSize(aFrame->StyleContext());
+}
+
+float
+SVGContentUtils::GetFontSize(nsStyleContext *aStyleContext)
+{
+ MOZ_ASSERT(aStyleContext, "NULL style context in GetFontSize");
+
+ nsPresContext *presContext = aStyleContext->PresContext();
+ MOZ_ASSERT(presContext, "NULL pres context in GetFontSize");
+
+ nscoord fontSize = aStyleContext->StyleFont()->mSize;
+ return nsPresContext::AppUnitsToFloatCSSPixels(fontSize) /
+ presContext->TextZoom();
+}
+
+float
+SVGContentUtils::GetFontXHeight(Element *aElement)
+{
+ if (!aElement)
+ return 1.0f;
+
+ RefPtr<nsStyleContext> styleContext =
+ nsComputedDOMStyle::GetStyleContextForElementNoFlush(aElement,
+ nullptr, nullptr);
+ if (!styleContext) {
+ // ReportToConsole
+ NS_WARNING("Couldn't get style context for content in GetFontStyle");
+ return 1.0f;
+ }
+
+ return GetFontXHeight(styleContext);
+}
+
+float
+SVGContentUtils::GetFontXHeight(nsIFrame *aFrame)
+{
+ MOZ_ASSERT(aFrame, "NULL frame in GetFontXHeight");
+ return GetFontXHeight(aFrame->StyleContext());
+}
+
+float
+SVGContentUtils::GetFontXHeight(nsStyleContext *aStyleContext)
+{
+ MOZ_ASSERT(aStyleContext, "NULL style context in GetFontXHeight");
+
+ nsPresContext *presContext = aStyleContext->PresContext();
+ MOZ_ASSERT(presContext, "NULL pres context in GetFontXHeight");
+
+ RefPtr<nsFontMetrics> fontMetrics =
+ nsLayoutUtils::GetFontMetricsForStyleContext(aStyleContext);
+
+ if (!fontMetrics) {
+ // ReportToConsole
+ NS_WARNING("no FontMetrics in GetFontXHeight()");
+ return 1.0f;
+ }
+
+ nscoord xHeight = fontMetrics->XHeight();
+ return nsPresContext::AppUnitsToFloatCSSPixels(xHeight) /
+ presContext->TextZoom();
+}
+nsresult
+SVGContentUtils::ReportToConsole(nsIDocument* doc,
+ const char* aWarning,
+ const char16_t **aParams,
+ uint32_t aParamsLength)
+{
+ return nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("SVG"), doc,
+ nsContentUtils::eSVG_PROPERTIES,
+ aWarning,
+ aParams, aParamsLength);
+}
+
+bool
+SVGContentUtils::EstablishesViewport(nsIContent *aContent)
+{
+ // Although SVG 1.1 states that <image> is an element that establishes a
+ // viewport, this is really only for the document it references, not
+ // for any child content, which is what this function is used for.
+ return aContent && aContent->IsAnyOfSVGElements(nsGkAtoms::svg,
+ nsGkAtoms::foreignObject,
+ nsGkAtoms::symbol);
+}
+
+nsSVGElement*
+SVGContentUtils::GetNearestViewportElement(nsIContent *aContent)
+{
+ nsIContent *element = aContent->GetFlattenedTreeParent();
+
+ while (element && element->IsSVGElement()) {
+ if (EstablishesViewport(element)) {
+ if (element->IsSVGElement(nsGkAtoms::foreignObject)) {
+ return nullptr;
+ }
+ return static_cast<nsSVGElement*>(element);
+ }
+ element = element->GetFlattenedTreeParent();
+ }
+ return nullptr;
+}
+
+static gfx::Matrix
+GetCTMInternal(nsSVGElement *aElement, bool aScreenCTM, bool aHaveRecursed)
+{
+ gfxMatrix matrix = aElement->PrependLocalTransformsTo(gfxMatrix(),
+ aHaveRecursed ? eAllTransforms : eUserSpaceToParent);
+ nsSVGElement *element = aElement;
+ nsIContent *ancestor = aElement->GetFlattenedTreeParent();
+
+ while (ancestor && ancestor->IsSVGElement() &&
+ !ancestor->IsSVGElement(nsGkAtoms::foreignObject)) {
+ element = static_cast<nsSVGElement*>(ancestor);
+ matrix *= element->PrependLocalTransformsTo(gfxMatrix()); // i.e. *A*ppend
+ if (!aScreenCTM && SVGContentUtils::EstablishesViewport(element)) {
+ if (!element->NodeInfo()->Equals(nsGkAtoms::svg, kNameSpaceID_SVG) &&
+ !element->NodeInfo()->Equals(nsGkAtoms::symbol, kNameSpaceID_SVG)) {
+ NS_ERROR("New (SVG > 1.1) SVG viewport establishing element?");
+ return gfx::Matrix(0.0, 0.0, 0.0, 0.0, 0.0, 0.0); // singular
+ }
+ // XXX spec seems to say x,y translation should be undone for IsInnerSVG
+ return gfx::ToMatrix(matrix);
+ }
+ ancestor = ancestor->GetFlattenedTreeParent();
+ }
+ if (!aScreenCTM) {
+ // didn't find a nearestViewportElement
+ return gfx::Matrix(0.0, 0.0, 0.0, 0.0, 0.0, 0.0); // singular
+ }
+ if (!element->IsSVGElement(nsGkAtoms::svg)) {
+ // Not a valid SVG fragment
+ return gfx::Matrix(0.0, 0.0, 0.0, 0.0, 0.0, 0.0); // singular
+ }
+ if (element == aElement && !aHaveRecursed) {
+ // We get here when getScreenCTM() is called on an outer-<svg>.
+ // Consistency with other elements would have us include only the
+ // eFromUserSpace transforms, but we include the eAllTransforms
+ // transforms in this case since that's what we've been doing for
+ // a while, and it keeps us consistent with WebKit and Opera (if not
+ // really with the ambiguous spec).
+ matrix = aElement->PrependLocalTransformsTo(gfxMatrix());
+ }
+ if (!ancestor || !ancestor->IsElement()) {
+ return gfx::ToMatrix(matrix);
+ }
+ if (ancestor->IsSVGElement()) {
+ return
+ gfx::ToMatrix(matrix) * GetCTMInternal(static_cast<nsSVGElement*>(ancestor), true, true);
+ }
+
+ // XXX this does not take into account CSS transform, or that the non-SVG
+ // content that we've hit may itself be inside an SVG foreignObject higher up
+ nsIDocument* currentDoc = aElement->GetComposedDoc();
+ float x = 0.0f, y = 0.0f;
+ if (currentDoc && element->NodeInfo()->Equals(nsGkAtoms::svg, kNameSpaceID_SVG)) {
+ nsIPresShell *presShell = currentDoc->GetShell();
+ if (presShell) {
+ nsIFrame* frame = element->GetPrimaryFrame();
+ nsIFrame* ancestorFrame = presShell->GetRootFrame();
+ if (frame && ancestorFrame) {
+ nsPoint point = frame->GetOffsetTo(ancestorFrame);
+ x = nsPresContext::AppUnitsToFloatCSSPixels(point.x);
+ y = nsPresContext::AppUnitsToFloatCSSPixels(point.y);
+ }
+ }
+ }
+ return ToMatrix(matrix).PostTranslate(x, y);
+}
+
+gfx::Matrix
+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)
+{
+ return sqrt((aWidth*aWidth + aHeight*aHeight)/2);
+}
+
+float
+SVGContentUtils::AngleBisect(float a1, float a2)
+{
+ float delta = fmod(a2 - a1, static_cast<float>(2*M_PI));
+ if (delta < 0) {
+ delta += static_cast<float>(2*M_PI);
+ }
+ /* delta is now the angle from a1 around to a2, in the range [0, 2*M_PI) */
+ float r = a1 + delta/2;
+ if (delta >= M_PI) {
+ /* the arc from a2 to a1 is smaller, so use the ray on that side */
+ r += static_cast<float>(M_PI);
+ }
+ return r;
+}
+
+gfx::Matrix
+SVGContentUtils::GetViewBoxTransform(float aViewportWidth, float aViewportHeight,
+ float aViewboxX, float aViewboxY,
+ float aViewboxWidth, float aViewboxHeight,
+ const SVGAnimatedPreserveAspectRatio &aPreserveAspectRatio)
+{
+ return GetViewBoxTransform(aViewportWidth, aViewportHeight,
+ aViewboxX, aViewboxY,
+ aViewboxWidth, aViewboxHeight,
+ aPreserveAspectRatio.GetAnimValue());
+}
+
+gfx::Matrix
+SVGContentUtils::GetViewBoxTransform(float aViewportWidth, float aViewportHeight,
+ float aViewboxX, float aViewboxY,
+ float aViewboxWidth, float aViewboxHeight,
+ const SVGPreserveAspectRatio &aPreserveAspectRatio)
+{
+ NS_ASSERTION(aViewportWidth >= 0, "viewport width must be nonnegative!");
+ NS_ASSERTION(aViewportHeight >= 0, "viewport height must be nonnegative!");
+ NS_ASSERTION(aViewboxWidth > 0, "viewBox width must be greater than zero!");
+ NS_ASSERTION(aViewboxHeight > 0, "viewBox height must be greater than zero!");
+
+ SVGAlign align = aPreserveAspectRatio.GetAlign();
+ SVGMeetOrSlice meetOrSlice = aPreserveAspectRatio.GetMeetOrSlice();
+
+ // default to the defaults
+ if (align == SVG_PRESERVEASPECTRATIO_UNKNOWN)
+ align = SVG_PRESERVEASPECTRATIO_XMIDYMID;
+ if (meetOrSlice == SVG_MEETORSLICE_UNKNOWN)
+ meetOrSlice = SVG_MEETORSLICE_MEET;
+
+ float a, d, e, f;
+ a = aViewportWidth / aViewboxWidth;
+ d = aViewportHeight / aViewboxHeight;
+ e = 0.0f;
+ f = 0.0f;
+
+ if (align != SVG_PRESERVEASPECTRATIO_NONE &&
+ a != d) {
+ if ((meetOrSlice == SVG_MEETORSLICE_MEET && a < d) ||
+ (meetOrSlice == SVG_MEETORSLICE_SLICE && d < a)) {
+ d = a;
+ switch (align) {
+ case SVG_PRESERVEASPECTRATIO_XMINYMIN:
+ case SVG_PRESERVEASPECTRATIO_XMIDYMIN:
+ case SVG_PRESERVEASPECTRATIO_XMAXYMIN:
+ break;
+ case SVG_PRESERVEASPECTRATIO_XMINYMID:
+ case SVG_PRESERVEASPECTRATIO_XMIDYMID:
+ case SVG_PRESERVEASPECTRATIO_XMAXYMID:
+ f = (aViewportHeight - a * aViewboxHeight) / 2.0f;
+ break;
+ case SVG_PRESERVEASPECTRATIO_XMINYMAX:
+ case SVG_PRESERVEASPECTRATIO_XMIDYMAX:
+ case SVG_PRESERVEASPECTRATIO_XMAXYMAX:
+ f = aViewportHeight - a * aViewboxHeight;
+ break;
+ default:
+ NS_NOTREACHED("Unknown value for align");
+ }
+ }
+ else if (
+ (meetOrSlice == SVG_MEETORSLICE_MEET &&
+ d < a) ||
+ (meetOrSlice == SVG_MEETORSLICE_SLICE &&
+ a < d)) {
+ a = d;
+ switch (align) {
+ case SVG_PRESERVEASPECTRATIO_XMINYMIN:
+ case SVG_PRESERVEASPECTRATIO_XMINYMID:
+ case SVG_PRESERVEASPECTRATIO_XMINYMAX:
+ break;
+ case SVG_PRESERVEASPECTRATIO_XMIDYMIN:
+ case SVG_PRESERVEASPECTRATIO_XMIDYMID:
+ case SVG_PRESERVEASPECTRATIO_XMIDYMAX:
+ e = (aViewportWidth - a * aViewboxWidth) / 2.0f;
+ break;
+ case SVG_PRESERVEASPECTRATIO_XMAXYMIN:
+ case SVG_PRESERVEASPECTRATIO_XMAXYMID:
+ case SVG_PRESERVEASPECTRATIO_XMAXYMAX:
+ e = aViewportWidth - a * aViewboxWidth;
+ break;
+ default:
+ NS_NOTREACHED("Unknown value for align");
+ }
+ }
+ else NS_NOTREACHED("Unknown value for meetOrSlice");
+ }
+
+ if (aViewboxX) e += -a * aViewboxX;
+ if (aViewboxY) f += -d * aViewboxY;
+
+ return gfx::Matrix(a, 0.0f, 0.0f, d, e, f);
+}
+
+static bool
+ParseNumber(RangedPtr<const char16_t>& aIter,
+ const RangedPtr<const char16_t>& aEnd,
+ double& aValue)
+{
+ int32_t sign;
+ if (!SVGContentUtils::ParseOptionalSign(aIter, aEnd, sign)) {
+ return false;
+ }
+
+ // Absolute value of the integer part of the mantissa.
+ double intPart = 0.0;
+
+ bool gotDot = *aIter == '.';
+
+ if (!gotDot) {
+ if (!SVGContentUtils::IsDigit(*aIter)) {
+ return false;
+ }
+ do {
+ intPart = 10.0 * intPart + SVGContentUtils::DecimalDigitValue(*aIter);
+ ++aIter;
+ } while (aIter != aEnd && SVGContentUtils::IsDigit(*aIter));
+
+ if (aIter != aEnd) {
+ gotDot = *aIter == '.';
+ }
+ }
+
+ // Fractional part of the mantissa.
+ double fracPart = 0.0;
+
+ if (gotDot) {
+ ++aIter;
+ if (aIter == aEnd || !SVGContentUtils::IsDigit(*aIter)) {
+ return false;
+ }
+
+ // Power of ten by which we need to divide the fraction
+ double divisor = 1.0;
+
+ do {
+ fracPart = 10.0 * fracPart + SVGContentUtils::DecimalDigitValue(*aIter);
+ divisor *= 10.0;
+ ++aIter;
+ } while (aIter != aEnd && SVGContentUtils::IsDigit(*aIter));
+
+ fracPart /= divisor;
+ }
+
+ bool gotE = false;
+ int32_t exponent = 0;
+ int32_t expSign;
+
+ if (aIter != aEnd && (*aIter == 'e' || *aIter == 'E')) {
+
+ RangedPtr<const char16_t> expIter(aIter);
+
+ ++expIter;
+ if (expIter != aEnd) {
+ expSign = *expIter == '-' ? -1 : 1;
+ if (*expIter == '-' || *expIter == '+') {
+ ++expIter;
+ }
+ if (expIter != aEnd && SVGContentUtils::IsDigit(*expIter)) {
+ // At this point we're sure this is an exponent
+ // and not the start of a unit such as em or ex.
+ gotE = true;
+ }
+ }
+
+ if (gotE) {
+ aIter = expIter;
+ do {
+ exponent = 10.0 * exponent + SVGContentUtils::DecimalDigitValue(*aIter);
+ ++aIter;
+ } while (aIter != aEnd && SVGContentUtils::IsDigit(*aIter));
+ }
+ }
+
+ // Assemble the number
+ aValue = sign * (intPart + fracPart);
+ if (gotE) {
+ aValue *= pow(10.0, expSign * exponent);
+ }
+ return true;
+}
+
+template<class floatType>
+bool
+SVGContentUtils::ParseNumber(RangedPtr<const char16_t>& aIter,
+ const RangedPtr<const char16_t>& aEnd,
+ floatType& aValue)
+{
+ RangedPtr<const char16_t> iter(aIter);
+
+ double value;
+ if (!::ParseNumber(iter, aEnd, value)) {
+ return false;
+ }
+ floatType floatValue = floatType(value);
+ if (!IsFinite(floatValue)) {
+ return false;
+ }
+ aValue = floatValue;
+ aIter = iter;
+ return true;
+}
+
+template bool
+SVGContentUtils::ParseNumber<float>(RangedPtr<const char16_t>& aIter,
+ const RangedPtr<const char16_t>& aEnd,
+ float& aValue);
+
+template bool
+SVGContentUtils::ParseNumber<double>(RangedPtr<const char16_t>& aIter,
+ const RangedPtr<const char16_t>& aEnd,
+ double& aValue);
+
+RangedPtr<const char16_t>
+SVGContentUtils::GetStartRangedPtr(const nsAString& aString)
+{
+ return RangedPtr<const char16_t>(aString.Data(), aString.Length());
+}
+
+RangedPtr<const char16_t>
+SVGContentUtils::GetEndRangedPtr(const nsAString& aString)
+{
+ return RangedPtr<const char16_t>(aString.Data() + aString.Length(),
+ aString.Data(), aString.Length());
+}
+
+template<class floatType>
+bool
+SVGContentUtils::ParseNumber(const nsAString& aString,
+ floatType& aValue)
+{
+ RangedPtr<const char16_t> iter = GetStartRangedPtr(aString);
+ const RangedPtr<const char16_t> end = GetEndRangedPtr(aString);
+
+ return ParseNumber(iter, end, aValue) && iter == end;
+}
+
+template bool
+SVGContentUtils::ParseNumber<float>(const nsAString& aString,
+ float& aValue);
+template bool
+SVGContentUtils::ParseNumber<double>(const nsAString& aString,
+ double& aValue);
+
+/* static */
+bool
+SVGContentUtils::ParseInteger(RangedPtr<const char16_t>& aIter,
+ const RangedPtr<const char16_t>& aEnd,
+ int32_t& aValue)
+{
+ RangedPtr<const char16_t> iter(aIter);
+
+ int32_t sign;
+ if (!ParseOptionalSign(iter, aEnd, sign)) {
+ return false;
+ }
+
+ if (!IsDigit(*iter)) {
+ return false;
+ }
+
+ int64_t value = 0;
+
+ do {
+ if (value <= std::numeric_limits<int32_t>::max()) {
+ value = 10 * value + DecimalDigitValue(*iter);
+ }
+ ++iter;
+ } while (iter != aEnd && IsDigit(*iter));
+
+ aIter = iter;
+ aValue = int32_t(clamped(sign * value,
+ int64_t(std::numeric_limits<int32_t>::min()),
+ int64_t(std::numeric_limits<int32_t>::max())));
+ return true;
+}
+
+/* static */
+bool
+SVGContentUtils::ParseInteger(const nsAString& aString,
+ int32_t& aValue)
+{
+ RangedPtr<const char16_t> iter = GetStartRangedPtr(aString);
+ const RangedPtr<const char16_t> end = GetEndRangedPtr(aString);
+
+ return ParseInteger(iter, end, aValue) && iter == end;
+}
+
+float
+SVGContentUtils::CoordToFloat(nsSVGElement *aContent,
+ const nsStyleCoord &aCoord)
+{
+ switch (aCoord.GetUnit()) {
+ case eStyleUnit_Factor:
+ // user units
+ return aCoord.GetFactorValue();
+
+ case eStyleUnit_Coord:
+ return nsPresContext::AppUnitsToFloatCSSPixels(aCoord.GetCoordValue());
+
+ case eStyleUnit_Percent: {
+ SVGSVGElement* ctx = aContent->GetCtx();
+ return ctx ? aCoord.GetPercentValue() * ctx->GetLength(SVGContentUtils::XY) : 0.0f;
+ }
+ default:
+ return 0.0f;
+ }
+}
+
+already_AddRefed<gfx::Path>
+SVGContentUtils::GetPath(const nsAString& aPathString)
+{
+ SVGPathData pathData;
+ nsSVGPathDataParser parser(aPathString, &pathData);
+ if (!parser.Parse()) {
+ return NULL;
+ }
+
+ RefPtr<DrawTarget> drawTarget =
+ gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
+ RefPtr<PathBuilder> builder =
+ drawTarget->CreatePathBuilder(FillRule::FILL_WINDING);
+
+ return pathData.BuildPath(builder, NS_STYLE_STROKE_LINECAP_BUTT, 1);
+}
+
+bool
+SVGContentUtils::ShapeTypeHasNoCorners(const nsIContent* aContent) {
+ return aContent && aContent->IsAnyOfSVGElements(nsGkAtoms::circle,
+ nsGkAtoms::ellipse);
+}
+
+gfxMatrix
+SVGContentUtils::PrependLocalTransformsTo(
+ const gfxMatrix &aMatrix,
+ SVGTransformTypes aWhich,
+ const gfx::Matrix* aAnimateMotionTransform,
+ const nsSVGAnimatedTransformList* aTransforms)
+{
+ gfxMatrix result(aMatrix);
+
+ if (aWhich == eChildToUserSpace) {
+ // We don't have anything to prepend.
+ // eChildToUserSpace is not the common case, which is why we return
+ // 'result' to benefit from NRVO rather than returning aMatrix before
+ // creating 'result'.
+ return result;
+ }
+
+ MOZ_ASSERT(aWhich == eAllTransforms || aWhich == eUserSpaceToParent,
+ "Unknown TransformTypes");
+
+ // animateMotion's resulting transform is supposed to apply *on top of*
+ // any transformations from the |transform| attribute. So since we're
+ // PRE-multiplying, we need to apply the animateMotion transform *first*.
+ if (aAnimateMotionTransform) {
+ result.PreMultiply(ThebesMatrix(*aAnimateMotionTransform));
+ }
+
+ if (aTransforms) {
+ result.PreMultiply(
+ aTransforms->GetAnimValue().GetConsolidationMatrix());
+ }
+
+ return result;
+}