diff options
Diffstat (limited to 'dom/svg/SVGContentUtils.cpp')
-rw-r--r-- | dom/svg/SVGContentUtils.cpp | 865 |
1 files changed, 865 insertions, 0 deletions
diff --git a/dom/svg/SVGContentUtils.cpp b/dom/svg/SVGContentUtils.cpp new file mode 100644 index 0000000000..2b9d0120c9 --- /dev/null +++ b/dom/svg/SVGContentUtils.cpp @@ -0,0 +1,865 @@ +/* -*- 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/PresShell.h" +#include "mozilla/RefPtr.h" +#include "mozilla/SVGContextPaint.h" +#include "mozilla/SVGUtils.h" +#include "mozilla/TextUtils.h" +#include "nsComputedDOMStyle.h" +#include "nsContainerFrame.h" +#include "nsFontMetrics.h" +#include "nsIFrame.h" +#include "nsIScriptError.h" +#include "nsLayoutUtils.h" +#include "nsMathUtils.h" +#include "nsWhitespaceTokenizer.h" +#include "SVGAnimatedPreserveAspectRatio.h" +#include "SVGGeometryProperty.h" +#include "nsContentUtils.h" +#include "mozilla/gfx/Types.h" +#include "mozilla/FloatingPoint.h" +#include "mozilla/ComputedStyle.h" +#include "SVGPathDataParser.h" +#include "SVGPathData.h" +#include "SVGPathElement.h" + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::dom::SVGPreserveAspectRatio_Binding; +using namespace mozilla::gfx; + +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 (!mozilla::IsAsciiDigit(*aIter)) { + return false; + } + do { + intPart = 10.0 * intPart + mozilla::AsciiAlphanumericToNumber(*aIter); + ++aIter; + } while (aIter != aEnd && mozilla::IsAsciiDigit(*aIter)); + + if (aIter != aEnd) { + gotDot = *aIter == '.'; + } + } + + // Fractional part of the mantissa. + double fracPart = 0.0; + + if (gotDot) { + ++aIter; + if (aIter == aEnd || !mozilla::IsAsciiDigit(*aIter)) { + return false; + } + + // Power of ten by which we need to divide the fraction + double divisor = 1.0; + + do { + fracPart = 10.0 * fracPart + mozilla::AsciiAlphanumericToNumber(*aIter); + divisor *= 10.0; + ++aIter; + } while (aIter != aEnd && mozilla::IsAsciiDigit(*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 && mozilla::IsAsciiDigit(*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 + mozilla::AsciiAlphanumericToNumber(*aIter); + ++aIter; + } while (aIter != aEnd && mozilla::IsAsciiDigit(*aIter)); + } + } + + // Assemble the number + aValue = sign * (intPart + fracPart); + if (gotE) { + aValue *= pow(10.0, expSign * exponent); + } + return true; +} + +namespace mozilla { + +SVGSVGElement* SVGContentUtils::GetOuterSVGElement(SVGElement* aSVGElement) { + Element* element = nullptr; + Element* ancestor = aSVGElement->GetParentElementCrossingShadowRoot(); + + while (ancestor && ancestor->IsSVGElement() && + !ancestor->IsSVGElement(nsGkAtoms::foreignObject)) { + element = ancestor; + ancestor = element->GetParentElementCrossingShadowRoot(); + } + + if (element && element->IsSVGElement(nsGkAtoms::svg)) { + return static_cast<SVGSVGElement*>(element); + } + return nullptr; +} + +enum DashState { + eDashedStroke, + eContinuousStroke, //< all dashes, no gaps + eNoStroke //< all gaps, no dashes +}; + +static DashState GetStrokeDashData( + SVGContentUtils::AutoStrokeOptions* aStrokeOptions, SVGElement* aElement, + const nsStyleSVG* aStyleSVG, SVGContextPaint* aContextPaint) { + size_t dashArrayLength; + Float totalLengthOfDashes = 0.0, totalLengthOfGaps = 0.0; + Float pathScale = 1.0; + + if (aStyleSVG->mStrokeDasharray.IsContextValue()) { + if (!aContextPaint) { + return eContinuousStroke; + } + const FallibleTArray<Float>& 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 auto dasharray = aStyleSVG->mStrokeDasharray.AsValues().AsSpan(); + dashArrayLength = dasharray.Length(); + if (dashArrayLength <= 0) { + return eContinuousStroke; + } + if (aElement->IsNodeOfType(nsINode::eSHAPE)) { + pathScale = + static_cast<SVGGeometryElement*>(aElement)->GetPathLengthScale( + SVGGeometryElement::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 == StyleStrokeLinecap::Butt) { + return eNoStroke; + } + + if (aStyleSVG->mStrokeDashoffset.IsContextValue()) { + aStrokeOptions->mDashOffset = + Float(aContextPaint ? aContextPaint->GetStrokeDashOffset() : 0); + } else { + aStrokeOptions->mDashOffset = + SVGContentUtils::CoordToFloat( + aElement, aStyleSVG->mStrokeDashoffset.AsLengthPercentage()) * + pathScale; + } + + return eDashedStroke; +} + +void SVGContentUtils::GetStrokeOptions(AutoStrokeOptions* aStrokeOptions, + SVGElement* aElement, + const ComputedStyle* aComputedStyle, + SVGContextPaint* aContextPaint, + StrokeOptionFlags aFlags) { + auto doCompute = [&](const ComputedStyle* computedStyle) { + const nsStyleSVG* styleSVG = computedStyle->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, computedStyle, aContextPaint); + + aStrokeOptions->mMiterLimit = Float(styleSVG->mStrokeMiterlimit); + + switch (styleSVG->mStrokeLinejoin) { + case StyleStrokeLinejoin::Miter: + aStrokeOptions->mLineJoin = JoinStyle::MITER_OR_BEVEL; + break; + case StyleStrokeLinejoin::Round: + aStrokeOptions->mLineJoin = JoinStyle::ROUND; + break; + case StyleStrokeLinejoin::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 StyleStrokeLinecap::Butt: + aStrokeOptions->mLineCap = CapStyle::BUTT; + break; + case StyleStrokeLinecap::Round: + aStrokeOptions->mLineCap = CapStyle::ROUND; + break; + case StyleStrokeLinecap::Square: + aStrokeOptions->mLineCap = CapStyle::SQUARE; + break; + } + } + }; + + if (aComputedStyle) { + doCompute(aComputedStyle); + } else { + SVGGeometryProperty::DoForComputedStyle(aElement, doCompute); + } +} + +Float SVGContentUtils::GetStrokeWidth(SVGElement* aElement, + const ComputedStyle* aComputedStyle, + SVGContextPaint* aContextPaint) { + Float res = 0.0; + + auto doCompute = [&](const ComputedStyle* computedStyle) { + const nsStyleSVG* styleSVG = computedStyle->StyleSVG(); + + if (styleSVG->mStrokeWidth.IsContextValue()) { + res = aContextPaint ? aContextPaint->GetStrokeWidth() : 1.0; + } else { + auto& lp = styleSVG->mStrokeWidth.AsLengthPercentage(); + if (lp.HasPercent() && aElement) { + auto counter = + aElement->IsSVGElement(nsGkAtoms::text) + ? UseCounter::eUseCounter_custom_PercentageStrokeWidthInSVGText + : UseCounter::eUseCounter_custom_PercentageStrokeWidthInSVG; + aElement->OwnerDoc()->SetUseCounter(counter); + } + res = SVGContentUtils::CoordToFloat(aElement, lp); + } + }; + + if (aComputedStyle) { + doCompute(aComputedStyle); + } else { + SVGGeometryProperty::DoForComputedStyle(aElement, doCompute); + } + + return res; +} + +float SVGContentUtils::GetFontSize(Element* aElement) { + if (!aElement) { + return 1.0f; + } + + nsPresContext* pc = nsContentUtils::GetContextForContent(aElement); + if (!pc) { + return 1.0f; + } + + if (auto* f = aElement->GetPrimaryFrame()) { + return GetFontSize(f->Style(), pc); + } + + if (RefPtr<const ComputedStyle> style = + nsComputedDOMStyle::GetComputedStyleNoFlush(aElement)) { + return GetFontSize(style, pc); + } + + // ReportToConsole + NS_WARNING("Couldn't get ComputedStyle for content in GetFontStyle"); + return 1.0f; +} + +float SVGContentUtils::GetFontSize(nsIFrame* aFrame) { + MOZ_ASSERT(aFrame, "NULL frame in GetFontSize"); + return GetFontSize(aFrame->Style(), aFrame->PresContext()); +} + +float SVGContentUtils::GetFontSize(const ComputedStyle* aComputedStyle, + nsPresContext* aPresContext) { + MOZ_ASSERT(aComputedStyle); + MOZ_ASSERT(aPresContext); + + return aComputedStyle->StyleFont()->mSize.ToCSSPixels() / + aPresContext->EffectiveTextZoom(); +} + +float SVGContentUtils::GetFontXHeight(Element* aElement) { + if (!aElement) { + return 1.0f; + } + + nsPresContext* pc = nsContentUtils::GetContextForContent(aElement); + if (!pc) { + return 1.0f; + } + + if (auto* f = aElement->GetPrimaryFrame()) { + return GetFontXHeight(f->Style(), pc); + } + + if (RefPtr<const ComputedStyle> style = + nsComputedDOMStyle::GetComputedStyleNoFlush(aElement)) { + return GetFontXHeight(style, pc); + } + + // ReportToConsole + NS_WARNING("Couldn't get ComputedStyle for content in GetFontStyle"); + return 1.0f; +} + +float SVGContentUtils::GetFontXHeight(nsIFrame* aFrame) { + MOZ_ASSERT(aFrame, "NULL frame in GetFontXHeight"); + return GetFontXHeight(aFrame->Style(), aFrame->PresContext()); +} + +float SVGContentUtils::GetFontXHeight(const ComputedStyle* aComputedStyle, + nsPresContext* aPresContext) { + MOZ_ASSERT(aComputedStyle && aPresContext); + + RefPtr<nsFontMetrics> fontMetrics = + nsLayoutUtils::GetFontMetricsForComputedStyle(aComputedStyle, + aPresContext); + + if (!fontMetrics) { + // ReportToConsole + NS_WARNING("no FontMetrics in GetFontXHeight()"); + return 1.0f; + } + + nscoord xHeight = fontMetrics->XHeight(); + return nsPresContext::AppUnitsToFloatCSSPixels(xHeight) / + aPresContext->EffectiveTextZoom(); +} +nsresult SVGContentUtils::ReportToConsole(Document* doc, const char* aWarning, + const nsTArray<nsString>& aParams) { + return nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "SVG"_ns, + doc, nsContentUtils::eSVG_PROPERTIES, + aWarning, aParams); +} + +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); +} + +SVGViewportElement* SVGContentUtils::GetNearestViewportElement( + const nsIContent* aContent) { + nsIContent* element = aContent->GetFlattenedTreeParent(); + + while (element && element->IsSVGElement()) { + if (EstablishesViewport(element)) { + if (element->IsSVGElement(nsGkAtoms::foreignObject)) { + return nullptr; + } + MOZ_ASSERT(element->IsAnyOfSVGElements(nsGkAtoms::svg, nsGkAtoms::symbol), + "upcoming static_cast is only valid for " + "SVGViewportElement subclasses"); + return static_cast<SVGViewportElement*>(element); + } + element = element->GetFlattenedTreeParent(); + } + return nullptr; +} + +static gfx::Matrix GetCTMInternal(SVGElement* aElement, bool aScreenCTM, + bool aHaveRecursed) { + auto getLocalTransformHelper = + [](SVGElement const* e, bool shouldIncludeChildToUserSpace) -> gfxMatrix { + gfxMatrix ret; + + if (auto* f = e->GetPrimaryFrame()) { + ret = SVGUtils::GetTransformMatrixInUserSpace(f); + } else { + // FIXME: Ideally we should also return the correct matrix + // for display:none, but currently transform related code relies + // heavily on the present of a frame. + // For now we just fall back to |PrependLocalTransformsTo| which + // doesn't account for CSS transform. + ret = e->PrependLocalTransformsTo({}, eUserSpaceToParent); + } + + if (shouldIncludeChildToUserSpace) { + ret = e->PrependLocalTransformsTo({}, eChildToUserSpace) * ret; + } + + return ret; + }; + + gfxMatrix matrix = getLocalTransformHelper(aElement, aHaveRecursed); + + SVGElement* element = aElement; + nsIContent* ancestor = aElement->GetFlattenedTreeParent(); + + while (ancestor && ancestor->IsSVGElement() && + !ancestor->IsSVGElement(nsGkAtoms::foreignObject)) { + element = static_cast<SVGElement*>(ancestor); + matrix *= getLocalTransformHelper(element, true); + 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 = getLocalTransformHelper(aElement, true); + } + + if (auto* f = element->GetPrimaryFrame()) { + if (f->IsSVGOuterSVGFrame()) { + nsMargin bp = f->GetUsedBorderAndPadding(); + matrix.PostTranslate( + NSAppUnitsToFloatPixels(bp.left, AppUnitsPerCSSPixel()), + NSAppUnitsToFloatPixels(bp.top, AppUnitsPerCSSPixel())); + } + } + + if (!ancestor || !ancestor->IsElement()) { + return gfx::ToMatrix(matrix); + } + if (ancestor->IsSVGElement()) { + return gfx::ToMatrix(matrix) * + GetCTMInternal(static_cast<SVGElement*>(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 + Document* currentDoc = aElement->GetComposedDoc(); + float x = 0.0f, y = 0.0f; + if (currentDoc && + element->NodeInfo()->Equals(nsGkAtoms::svg, kNameSpaceID_SVG)) { + PresShell* presShell = currentDoc->GetPresShell(); + 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(SVGElement* 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 = std::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!"); + + uint16_t align = aPreserveAspectRatio.GetAlign(); + uint16_t 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: + MOZ_ASSERT_UNREACHABLE("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: + MOZ_ASSERT_UNREACHABLE("Unknown value for align"); + } + } else + MOZ_ASSERT_UNREACHABLE("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); +} + +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 (!mozilla::IsAsciiDigit(*iter)) { + return false; + } + + int64_t value = 0; + + do { + if (value <= std::numeric_limits<int32_t>::max()) { + value = 10 * value + mozilla::AsciiAlphanumericToNumber(*iter); + } + ++iter; + } while (iter != aEnd && mozilla::IsAsciiDigit(*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(SVGElement* aContent, + const LengthPercentage& aLength, + uint8_t aCtxType) { + float result = aLength.ResolveToCSSPixelsWith([&] { + SVGViewportElement* ctx = aContent->GetCtx(); + return CSSCoord(ctx ? ctx->GetLength(aCtxType) : 0.0f); + }); + if (aLength.IsCalc()) { + const auto& calc = aLength.AsCalc(); + if (calc.clamping_mode == StyleAllowedNumericType::NonNegative) { + result = std::max(result, 0.0f); + } else { + MOZ_ASSERT(calc.clamping_mode == StyleAllowedNumericType::All); + } + } + return result; +} + +already_AddRefed<gfx::Path> SVGContentUtils::GetPath( + const nsAString& aPathString) { + SVGPathData pathData; + SVGPathDataParser parser(aPathString, &pathData); + if (!parser.Parse()) { + return nullptr; + } + + RefPtr<DrawTarget> drawTarget = + gfxPlatform::ThreadLocalScreenReferenceDrawTarget(); + RefPtr<PathBuilder> builder = + drawTarget->CreatePathBuilder(FillRule::FILL_WINDING); + + return pathData.BuildPath(builder, StyleStrokeLinecap::Butt, 1); +} + +bool SVGContentUtils::ShapeTypeHasNoCorners(const nsIContent* aContent) { + return aContent && + aContent->IsAnyOfSVGElements(nsGkAtoms::circle, nsGkAtoms::ellipse); +} + +nsDependentSubstring SVGContentUtils::GetAndEnsureOneToken( + const nsAString& aString, bool& aSuccess) { + nsWhitespaceTokenizerTemplate<nsContentUtils::IsHTMLWhitespace> tokenizer( + aString); + + aSuccess = false; + if (!tokenizer.hasMoreTokens()) { + return {}; + } + auto token = tokenizer.nextToken(); + if (tokenizer.hasMoreTokens()) { + return {}; + } + + aSuccess = true; + return token; +} + +} // namespace mozilla |