gecko-dev/dom/svg/SVGContentUtils.cpp
Brian Birtles 33041d1214 Bug 1315874 - Drop 'ForElement' from GetStyleContextForElementXXX methods; r=heycam
In the next patch we want to add a method called
GetUnanimatedStyleContextForElementNoFlush but that's much too long. Instead it
seems better to just drop 'ForElement' from all these methods since it should be
fairly obvious we are getting the style context for an element given that the
first argument is an element.

MozReview-Commit-ID: JQKaEuCKV2F

--HG--
extra : rebase_source : 3ba51f3b00d1ec7bc91102629d9c0abb88992fef
2017-04-05 14:39:23 +09:00

861 lines
27 KiB
C++

/* -*- 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 "nsMathUtils.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::GetStyleContextNoFlush(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::GetStyleContextNoFlush(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::GetStyleContextNoFlush(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->EffectiveTextZoom();
}
float
SVGContentUtils::GetFontXHeight(Element *aElement)
{
if (!aElement)
return 1.0f;
RefPtr<nsStyleContext> styleContext =
nsComputedDOMStyle::GetStyleContextNoFlush(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->EffectiveTextZoom();
}
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 NS_hypot(aWidth, aHeight) / M_SQRT2;
}
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);
}