summaryrefslogtreecommitdiffstats
path: root/dom/svg/SVGAnimatedLength.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/svg/SVGAnimatedLength.cpp')
-rw-r--r--dom/svg/SVGAnimatedLength.cpp649
1 files changed, 649 insertions, 0 deletions
diff --git a/dom/svg/SVGAnimatedLength.cpp b/dom/svg/SVGAnimatedLength.cpp
new file mode 100644
index 0000000000..081db5704a
--- /dev/null
+++ b/dom/svg/SVGAnimatedLength.cpp
@@ -0,0 +1,649 @@
+/* -*- 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/. */
+
+#include "SVGAnimatedLength.h"
+
+#include "mozAutoDocUpdate.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/GeckoBindings.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/SMILValue.h"
+#include "mozilla/StaticPresData.h"
+#include "mozilla/SVGIntegrationUtils.h"
+#include "mozilla/dom/SVGViewportElement.h"
+#include "DOMSVGAnimatedLength.h"
+#include "DOMSVGLength.h"
+#include "LayoutLogging.h"
+#include "nsContentUtils.h"
+#include "nsIFrame.h"
+#include "nsLayoutUtils.h"
+#include "nsTextFormatter.h"
+#include "SVGLengthSMILType.h"
+#include "SVGAttrTearoffTable.h"
+#include "SVGGeometryProperty.h"
+
+using namespace mozilla::dom;
+
+namespace mozilla {
+
+//----------------------------------------------------------------------
+// Helper class: AutoChangeLengthNotifier
+// Stack-based helper class to pair calls to WillChangeLength and
+// DidChangeLength.
+class MOZ_RAII AutoChangeLengthNotifier {
+ public:
+ AutoChangeLengthNotifier(SVGAnimatedLength* aLength, SVGElement* aSVGElement,
+ bool aDoSetAttr = true)
+ : mLength(aLength), mSVGElement(aSVGElement), mDoSetAttr(aDoSetAttr) {
+ MOZ_ASSERT(mLength, "Expecting non-null length");
+ MOZ_ASSERT(mSVGElement, "Expecting non-null element");
+
+ if (mDoSetAttr) {
+ mUpdateBatch.emplace(aSVGElement->GetComposedDoc(), true);
+ mEmptyOrOldValue =
+ mSVGElement->WillChangeLength(mLength->mAttrEnum, mUpdateBatch.ref());
+ }
+ }
+
+ ~AutoChangeLengthNotifier() {
+ if (mDoSetAttr) {
+ mSVGElement->DidChangeLength(mLength->mAttrEnum, mEmptyOrOldValue,
+ mUpdateBatch.ref());
+ }
+ if (mLength->mIsAnimated) {
+ mSVGElement->AnimationNeedsResample();
+ }
+ }
+
+ private:
+ SVGAnimatedLength* const mLength;
+ SVGElement* const mSVGElement;
+ Maybe<mozAutoDocUpdate> mUpdateBatch;
+ nsAttrValue mEmptyOrOldValue;
+ bool mDoSetAttr;
+};
+
+static SVGAttrTearoffTable<SVGAnimatedLength, DOMSVGAnimatedLength>
+ sSVGAnimatedLengthTearoffTable;
+
+/* Helper functions */
+
+static void GetValueString(nsAString& aValueAsString, float aValue,
+ uint16_t aUnitType) {
+ nsTextFormatter::ssprintf(aValueAsString, u"%g", (double)aValue);
+
+ nsAutoString unitString;
+ SVGLength::GetUnitString(unitString, aUnitType);
+ aValueAsString.Append(unitString);
+}
+
+static bool GetValueFromString(const nsAString& aString, float& aValue,
+ uint16_t* aUnitType) {
+ bool success;
+ auto token = SVGContentUtils::GetAndEnsureOneToken(aString, success);
+
+ if (!success) {
+ return false;
+ }
+
+ RangedPtr<const char16_t> iter = SVGContentUtils::GetStartRangedPtr(token);
+ const RangedPtr<const char16_t> end = SVGContentUtils::GetEndRangedPtr(token);
+
+ if (!SVGContentUtils::ParseNumber(iter, end, aValue)) {
+ return false;
+ }
+ const nsAString& units = Substring(iter.get(), end.get());
+ *aUnitType = SVGLength::GetUnitTypeForString(units);
+ return *aUnitType != SVGLength_Binding::SVG_LENGTHTYPE_UNKNOWN;
+}
+
+static float FixAxisLength(float aLength) {
+ if (aLength == 0.0f) {
+ LAYOUT_WARNING("zero axis length");
+ return 1e-20f;
+ }
+ return aLength;
+}
+
+GeckoFontMetrics UserSpaceMetrics::DefaultFontMetrics() {
+ return {StyleLength::FromPixels(16.0f),
+ StyleLength::FromPixels(-1),
+ StyleLength::FromPixels(-1),
+ StyleLength::FromPixels(-1),
+ StyleLength::FromPixels(-1),
+ StyleLength::FromPixels(16.0f),
+ 0.0f,
+ 0.0f};
+}
+
+GeckoFontMetrics UserSpaceMetrics::GetFontMetrics(const Element* aElement) {
+ GeckoFontMetrics metrics = DefaultFontMetrics();
+ auto* presContext =
+ aElement ? nsContentUtils::GetContextForContent(aElement) : nullptr;
+ if (presContext) {
+ SVGGeometryProperty::DoForComputedStyle(
+ aElement, [&](const ComputedStyle* style) {
+ metrics = Gecko_GetFontMetrics(
+ presContext, WritingMode(style).IsVertical(), style->StyleFont(),
+ style->StyleFont()->mFont.size,
+ /* aUseUserFontSet = */ true,
+ /* aRetrieveMathScales */ false);
+ });
+ }
+ return metrics;
+}
+
+WritingMode UserSpaceMetrics::GetWritingMode(const Element* aElement) {
+ WritingMode writingMode;
+ SVGGeometryProperty::DoForComputedStyle(
+ aElement,
+ [&](const ComputedStyle* style) { writingMode = WritingMode(style); });
+ return writingMode;
+}
+
+float UserSpaceMetrics::GetExLength(Type aType) const {
+ return GetFontMetricsForType(aType).mXSize.ToCSSPixels();
+}
+
+float UserSpaceMetrics::GetChSize(Type aType) const {
+ auto metrics = GetFontMetricsForType(aType);
+ if (metrics.mChSize.ToCSSPixels() > 0.0) {
+ return metrics.mChSize.ToCSSPixels();
+ }
+ auto emLength = GetEmLength(aType);
+ WritingMode writingMode = GetWritingModeForType(aType);
+ return writingMode.IsVertical() && !writingMode.IsSideways()
+ ? emLength
+ : emLength * 0.5f;
+}
+
+float UserSpaceMetrics::GetIcWidth(Type aType) const {
+ auto metrics = GetFontMetricsForType(aType);
+ if (metrics.mIcWidth.ToCSSPixels() > 0.0) {
+ return metrics.mIcWidth.ToCSSPixels();
+ }
+ return GetEmLength(aType);
+}
+
+float UserSpaceMetrics::GetCapHeight(Type aType) const {
+ auto metrics = GetFontMetricsForType(aType);
+ if (metrics.mCapHeight.ToCSSPixels() > 0.0) {
+ return metrics.mCapHeight.ToCSSPixels();
+ }
+ return GetEmLength(aType);
+}
+
+CSSSize UserSpaceMetrics::GetCSSViewportSizeFromContext(
+ const nsPresContext* aContext) {
+ return CSSPixel::FromAppUnits(aContext->GetSizeForViewportUnits());
+}
+
+SVGElementMetrics::SVGElementMetrics(const SVGElement* aSVGElement,
+ const SVGViewportElement* aCtx)
+ : mSVGElement(aSVGElement), mCtx(aCtx) {}
+
+const Element* SVGElementMetrics::GetElementForType(Type aType) const {
+ switch (aType) {
+ case Type::This:
+ return mSVGElement;
+ case Type::Root:
+ return mSVGElement ? mSVGElement->OwnerDoc()->GetRootElement() : nullptr;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Was a new value added to the enumeration?");
+ return nullptr;
+ }
+}
+
+GeckoFontMetrics SVGElementMetrics::GetFontMetricsForType(Type aType) const {
+ return GetFontMetrics(GetElementForType(aType));
+}
+
+WritingMode SVGElementMetrics::GetWritingModeForType(Type aType) const {
+ return GetWritingMode(GetElementForType(aType));
+}
+
+float SVGElementMetrics::GetAxisLength(uint8_t aCtxType) const {
+ if (!EnsureCtx()) {
+ return 1.0f;
+ }
+
+ return FixAxisLength(mCtx->GetLength(aCtxType));
+}
+
+CSSSize SVGElementMetrics::GetCSSViewportSize() const {
+ if (!mSVGElement) {
+ return {0.0f, 0.0f};
+ }
+ nsPresContext* context = nsContentUtils::GetContextForContent(mSVGElement);
+ if (!context) {
+ return {0.0f, 0.0f};
+ }
+ return GetCSSViewportSizeFromContext(context);
+}
+
+bool SVGElementMetrics::EnsureCtx() const {
+ if (!mCtx && mSVGElement) {
+ mCtx = mSVGElement->GetCtx();
+ if (!mCtx && mSVGElement->IsSVGElement(nsGkAtoms::svg)) {
+ const auto* e = static_cast<const SVGViewportElement*>(mSVGElement);
+
+ if (!e->IsInner()) {
+ // mSVGElement must be the outer svg element
+ mCtx = e;
+ }
+ }
+ }
+ return mCtx != nullptr;
+}
+
+NonSVGFrameUserSpaceMetrics::NonSVGFrameUserSpaceMetrics(nsIFrame* aFrame)
+ : mFrame(aFrame) {
+ MOZ_ASSERT(mFrame, "Need a frame");
+}
+
+float NonSVGFrameUserSpaceMetrics::GetEmLength(Type aType) const {
+ switch (aType) {
+ case Type::This:
+ return SVGContentUtils::GetFontSize(mFrame);
+ case Type::Root:
+ return SVGContentUtils::GetFontSize(
+ mFrame->PresContext()->Document()->GetRootElement());
+ default:
+ MOZ_ASSERT_UNREACHABLE("Was a new value added to the enumeration?");
+ return 1.0f;
+ }
+}
+
+GeckoFontMetrics NonSVGFrameUserSpaceMetrics::GetFontMetricsForType(
+ Type aType) const {
+ switch (aType) {
+ case Type::This:
+ return Gecko_GetFontMetrics(
+ mFrame->PresContext(), mFrame->GetWritingMode().IsVertical(),
+ mFrame->StyleFont(), mFrame->StyleFont()->mFont.size,
+ /* aUseUserFontSet = */ true,
+ /* aRetrieveMathScales */ false);
+ case Type::Root:
+ return GetFontMetrics(
+ mFrame->PresContext()->Document()->GetRootElement());
+ default:
+ MOZ_ASSERT_UNREACHABLE("Was a new value added to the enumeration?");
+ return DefaultFontMetrics();
+ }
+}
+
+WritingMode NonSVGFrameUserSpaceMetrics::GetWritingModeForType(
+ Type aType) const {
+ switch (aType) {
+ case Type::This:
+ return mFrame->GetWritingMode();
+ case Type::Root:
+ return GetWritingMode(
+ mFrame->PresContext()->Document()->GetRootElement());
+ default:
+ MOZ_ASSERT_UNREACHABLE("Was a new value added to the enumeration?");
+ return WritingMode();
+ }
+}
+
+gfx::Size NonSVGFrameUserSpaceMetrics::GetSize() const {
+ return SVGIntegrationUtils::GetSVGCoordContextForNonSVGFrame(mFrame);
+}
+
+CSSSize NonSVGFrameUserSpaceMetrics::GetCSSViewportSize() const {
+ return GetCSSViewportSizeFromContext(mFrame->PresContext());
+}
+
+float UserSpaceMetricsWithSize::GetAxisLength(uint8_t aCtxType) const {
+ gfx::Size size = GetSize();
+ float length;
+ switch (aCtxType) {
+ case SVGContentUtils::X:
+ length = size.width;
+ break;
+ case SVGContentUtils::Y:
+ length = size.height;
+ break;
+ case SVGContentUtils::XY:
+ length =
+ SVGContentUtils::ComputeNormalizedHypotenuse(size.width, size.height);
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown axis type");
+ length = 1;
+ break;
+ }
+ return FixAxisLength(length);
+}
+
+float SVGAnimatedLength::GetPixelsPerUnit(const SVGElement* aSVGElement,
+ uint8_t aUnitType) const {
+ return SVGLength::GetPixelsPerUnit(SVGElementMetrics(aSVGElement), aUnitType,
+ mCtxType);
+}
+
+float SVGAnimatedLength::GetPixelsPerUnit(const SVGViewportElement* aCtx,
+ uint8_t aUnitType) const {
+ return SVGLength::GetPixelsPerUnit(SVGElementMetrics(aCtx, aCtx), aUnitType,
+ mCtxType);
+}
+
+float SVGAnimatedLength::GetPixelsPerUnit(nsIFrame* aFrame,
+ uint8_t aUnitType) const {
+ const nsIContent* content = aFrame->GetContent();
+ MOZ_ASSERT(!content->IsText(), "Not expecting text content");
+ if (content->IsSVGElement()) {
+ return SVGLength::GetPixelsPerUnit(
+ SVGElementMetrics(static_cast<const SVGElement*>(content)), aUnitType,
+ mCtxType);
+ }
+ return SVGLength::GetPixelsPerUnit(NonSVGFrameUserSpaceMetrics(aFrame),
+ aUnitType, mCtxType);
+}
+
+float SVGAnimatedLength::GetPixelsPerUnit(const UserSpaceMetrics& aMetrics,
+ uint8_t aUnitType) const {
+ return SVGLength::GetPixelsPerUnit(aMetrics, aUnitType, mCtxType);
+}
+
+void SVGAnimatedLength::SetBaseValueInSpecifiedUnits(float aValue,
+ SVGElement* aSVGElement,
+ bool aDoSetAttr) {
+ if (mIsBaseSet && mBaseVal == aValue) {
+ return;
+ }
+
+ AutoChangeLengthNotifier notifier(this, aSVGElement, aDoSetAttr);
+
+ mBaseVal = aValue;
+ mIsBaseSet = true;
+ if (!mIsAnimated) {
+ mAnimVal = mBaseVal;
+ }
+}
+
+nsresult SVGAnimatedLength::ConvertToSpecifiedUnits(uint16_t unitType,
+ SVGElement* aSVGElement) {
+ if (!SVGLength::IsValidUnitType(unitType)) {
+ return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ }
+
+ if (mIsBaseSet && mBaseUnitType == uint8_t(unitType)) return NS_OK;
+
+ float valueInSpecifiedUnits;
+
+ if (SVGLength::IsAbsoluteUnit(unitType) &&
+ SVGLength::IsAbsoluteUnit(mBaseUnitType)) {
+ valueInSpecifiedUnits =
+ mBaseVal * SVGLength::GetAbsUnitsPerAbsUnit(unitType, mBaseUnitType);
+ } else {
+ float pixelsPerUnit = GetPixelsPerUnit(aSVGElement, unitType);
+ if (pixelsPerUnit == 0.0f) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ float valueInPixels =
+ mBaseVal * GetPixelsPerUnit(aSVGElement, mBaseUnitType);
+ valueInSpecifiedUnits = valueInPixels / pixelsPerUnit;
+ }
+
+ if (!std::isfinite(valueInSpecifiedUnits)) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ // Even though we're not changing the visual effect this length will have
+ // on the document, we still need to send out notifications in case we have
+ // mutation listeners, since the actual string value of the attribute will
+ // change.
+ AutoChangeLengthNotifier notifier(this, aSVGElement);
+
+ mBaseUnitType = uint8_t(unitType);
+ if (!mIsAnimated) {
+ mAnimUnitType = mBaseUnitType;
+ }
+ // Setting aDoSetAttr to false here will ensure we don't call
+ // Will/DidChangeAngle a second time (and dispatch duplicate notifications).
+ SetBaseValueInSpecifiedUnits(valueInSpecifiedUnits, aSVGElement, false);
+
+ return NS_OK;
+}
+
+nsresult SVGAnimatedLength::NewValueSpecifiedUnits(uint16_t aUnitType,
+ float aValueInSpecifiedUnits,
+ SVGElement* aSVGElement) {
+ NS_ENSURE_FINITE(aValueInSpecifiedUnits, NS_ERROR_ILLEGAL_VALUE);
+
+ if (!SVGLength::IsValidUnitType(aUnitType)) {
+ return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ }
+
+ if (mIsBaseSet && mBaseVal == aValueInSpecifiedUnits &&
+ mBaseUnitType == uint8_t(aUnitType)) {
+ return NS_OK;
+ }
+
+ AutoChangeLengthNotifier notifier(this, aSVGElement);
+
+ mBaseVal = aValueInSpecifiedUnits;
+ mIsBaseSet = true;
+ mBaseUnitType = uint8_t(aUnitType);
+ if (!mIsAnimated) {
+ mAnimVal = mBaseVal;
+ mAnimUnitType = mBaseUnitType;
+ }
+ return NS_OK;
+}
+
+already_AddRefed<DOMSVGLength> SVGAnimatedLength::ToDOMBaseVal(
+ SVGElement* aSVGElement) {
+ return DOMSVGLength::GetTearOff(this, aSVGElement, false);
+}
+
+already_AddRefed<DOMSVGLength> SVGAnimatedLength::ToDOMAnimVal(
+ SVGElement* aSVGElement) {
+ return DOMSVGLength::GetTearOff(this, aSVGElement, true);
+}
+
+/* Implementation */
+
+nsresult SVGAnimatedLength::SetBaseValueString(const nsAString& aValueAsString,
+ SVGElement* aSVGElement,
+ bool aDoSetAttr) {
+ float value;
+ uint16_t unitType;
+
+ if (!GetValueFromString(aValueAsString, value, &unitType)) {
+ return NS_ERROR_DOM_SYNTAX_ERR;
+ }
+
+ if (mIsBaseSet && mBaseVal == float(value) &&
+ mBaseUnitType == uint8_t(unitType)) {
+ return NS_OK;
+ }
+
+ AutoChangeLengthNotifier notifier(this, aSVGElement, aDoSetAttr);
+
+ mBaseVal = value;
+ mIsBaseSet = true;
+ mBaseUnitType = uint8_t(unitType);
+ if (!mIsAnimated) {
+ mAnimVal = mBaseVal;
+ mAnimUnitType = mBaseUnitType;
+ }
+
+ return NS_OK;
+}
+
+void SVGAnimatedLength::GetBaseValueString(nsAString& aValueAsString) const {
+ GetValueString(aValueAsString, mBaseVal, mBaseUnitType);
+}
+
+void SVGAnimatedLength::GetAnimValueString(nsAString& aValueAsString) const {
+ GetValueString(aValueAsString, mAnimVal, mAnimUnitType);
+}
+
+nsresult SVGAnimatedLength::SetBaseValue(float aValue, SVGElement* aSVGElement,
+ bool aDoSetAttr) {
+ float pixelsPerUnit = GetPixelsPerUnit(aSVGElement, mBaseUnitType);
+ if (pixelsPerUnit == 0.0f) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ float valueInSpecifiedUnits = aValue / pixelsPerUnit;
+ if (!std::isfinite(valueInSpecifiedUnits)) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ SetBaseValueInSpecifiedUnits(valueInSpecifiedUnits, aSVGElement, aDoSetAttr);
+ return NS_OK;
+}
+
+void SVGAnimatedLength::SetAnimValueInSpecifiedUnits(float aValue,
+ SVGElement* aSVGElement) {
+ if (mAnimVal == aValue && mIsAnimated) {
+ return;
+ }
+ mAnimVal = aValue;
+ mIsAnimated = true;
+ aSVGElement->DidAnimateLength(mAttrEnum);
+}
+
+void SVGAnimatedLength::SetAnimValue(float aValue, uint16_t aUnitType,
+ SVGElement* aSVGElement) {
+ if (mIsAnimated && mAnimVal == aValue && mAnimUnitType == aUnitType) {
+ return;
+ }
+ mAnimVal = aValue;
+ mAnimUnitType = aUnitType;
+ mIsAnimated = true;
+ aSVGElement->DidAnimateLength(mAttrEnum);
+}
+
+already_AddRefed<DOMSVGAnimatedLength> SVGAnimatedLength::ToDOMAnimatedLength(
+ SVGElement* aSVGElement) {
+ RefPtr<DOMSVGAnimatedLength> svgAnimatedLength =
+ sSVGAnimatedLengthTearoffTable.GetTearoff(this);
+ if (!svgAnimatedLength) {
+ svgAnimatedLength = new DOMSVGAnimatedLength(this, aSVGElement);
+ sSVGAnimatedLengthTearoffTable.AddTearoff(this, svgAnimatedLength);
+ }
+
+ return svgAnimatedLength.forget();
+}
+
+DOMSVGAnimatedLength::~DOMSVGAnimatedLength() {
+ sSVGAnimatedLengthTearoffTable.RemoveTearoff(mVal);
+}
+
+UniquePtr<SMILAttr> SVGAnimatedLength::ToSMILAttr(SVGElement* aSVGElement) {
+ return MakeUnique<SMILLength>(this, aSVGElement);
+}
+
+nsresult SVGAnimatedLength::SMILLength::ValueFromString(
+ const nsAString& aStr, const SVGAnimationElement* /*aSrcElement*/,
+ SMILValue& aValue, bool& aPreventCachingOfSandwich) const {
+ float value;
+ uint16_t unitType;
+
+ if (!GetValueFromString(aStr, value, &unitType)) {
+ return NS_ERROR_DOM_SYNTAX_ERR;
+ }
+
+ SMILValue val(SVGLengthSMILType::Singleton());
+ SVGLengthAndInfo* lai = static_cast<SVGLengthAndInfo*>(val.mU.mPtr);
+ lai->Set(value, unitType, mVal->GetCtxType());
+ lai->SetInfo(mSVGElement);
+ aValue = std::move(val);
+ aPreventCachingOfSandwich = !SVGLength::IsAbsoluteUnit(unitType);
+
+ return NS_OK;
+}
+
+SMILValue SVGAnimatedLength::SMILLength::GetBaseValue() const {
+ SMILValue val(SVGLengthSMILType::Singleton());
+ auto* lai = static_cast<SVGLengthAndInfo*>(val.mU.mPtr);
+ lai->CopyBaseFrom(*mVal);
+ lai->SetInfo(mSVGElement);
+ return val;
+}
+
+void SVGAnimatedLength::SMILLength::ClearAnimValue() {
+ if (mVal->mIsAnimated) {
+ mVal->mIsAnimated = false;
+ mVal->mAnimVal = mVal->mBaseVal;
+ mVal->mAnimUnitType = mVal->mBaseUnitType;
+ mSVGElement->DidAnimateLength(mVal->mAttrEnum);
+ }
+}
+
+nsresult SVGAnimatedLength::SMILLength::SetAnimValue(const SMILValue& aValue) {
+ NS_ASSERTION(aValue.mType == SVGLengthSMILType::Singleton(),
+ "Unexpected type to assign animated value");
+ if (aValue.mType == SVGLengthSMILType::Singleton()) {
+ SVGLengthAndInfo* lai = static_cast<SVGLengthAndInfo*>(aValue.mU.mPtr);
+ mVal->SetAnimValue(lai->Value(), lai->UnitType(), mSVGElement);
+ }
+ return NS_OK;
+}
+
+float SVGLengthAndInfo::ConvertUnits(const SVGLengthAndInfo& aTo) const {
+ if (aTo.mUnitType == mUnitType) {
+ return mValue;
+ }
+ return SVGLength(mValue, mUnitType)
+ .GetValueInSpecifiedUnit(aTo.mUnitType, aTo.Element(), aTo.mCtxType);
+}
+
+float SVGLengthAndInfo::ValueInPixels(const UserSpaceMetrics& aMetrics) const {
+ return mValue == 0.0f ? 0.0f
+ : mValue * SVGLength::GetPixelsPerUnit(
+ aMetrics, mUnitType, mCtxType);
+}
+
+void SVGLengthAndInfo::Add(const SVGLengthAndInfo& aValueToAdd,
+ uint32_t aCount) {
+ mElement = aValueToAdd.mElement;
+
+ SVGElementMetrics metrics(Element());
+
+ // We may be dealing with two different length units, so we normalize to
+ // pixels for the add:
+
+ float currentLength = ValueInPixels(metrics);
+ float lengthToAdd = aValueToAdd.ValueInPixels(metrics) * aCount;
+
+ // And then we give the resulting value the same units as the value
+ // that we're animating to/by (i.e. the same as aValueToAdd):
+ mUnitType = aValueToAdd.mUnitType;
+ mCtxType = aValueToAdd.mCtxType;
+ mValue = (currentLength + lengthToAdd) /
+ SVGLength::GetPixelsPerUnit(metrics, mUnitType, mCtxType);
+}
+
+void SVGLengthAndInfo::Interpolate(const SVGLengthAndInfo& aStart,
+ const SVGLengthAndInfo& aEnd,
+ double aUnitDistance,
+ SVGLengthAndInfo& aResult) {
+ MOZ_ASSERT(!aStart.mElement || aStart.mElement == aEnd.mElement,
+ "Should not be interpolating between different elements");
+
+ float startValue, endValue;
+ if (!aStart.mElement || aUnitDistance > 0.5) {
+ aResult.mUnitType = aEnd.mUnitType;
+ aResult.mCtxType = aEnd.mCtxType;
+ startValue = aStart.ConvertUnits(aEnd);
+ endValue = aEnd.mValue;
+ } else {
+ aResult.mUnitType = aStart.mUnitType;
+ aResult.mCtxType = aStart.mCtxType;
+ startValue = aStart.mValue;
+ endValue = aEnd.ConvertUnits(aStart);
+ }
+ aResult.mElement = aEnd.mElement;
+ aResult.mValue = startValue + (endValue - startValue) * aUnitDistance;
+}
+
+} // namespace mozilla