summaryrefslogtreecommitdiffstats
path: root/dom/svg/SVGAnimatedLength.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--dom/svg/SVGAnimatedLength.cpp474
1 files changed, 474 insertions, 0 deletions
diff --git a/dom/svg/SVGAnimatedLength.cpp b/dom/svg/SVGAnimatedLength.cpp
new file mode 100644
index 0000000000..0b799c79d8
--- /dev/null
+++ b/dom/svg/SVGAnimatedLength.cpp
@@ -0,0 +1,474 @@
+/* -*- 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/Maybe.h"
+#include "mozilla/SMILValue.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 "nsTextFormatter.h"
+#include "SMILFloatType.h"
+#include "SVGAttrTearoffTable.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;
+}
+
+SVGElementMetrics::SVGElementMetrics(SVGElement* aSVGElement,
+ SVGViewportElement* aCtx)
+ : mSVGElement(aSVGElement), mCtx(aCtx) {}
+
+float SVGElementMetrics::GetEmLength() const {
+ return SVGContentUtils::GetFontSize(mSVGElement);
+}
+
+float SVGElementMetrics::GetExLength() const {
+ return SVGContentUtils::GetFontXHeight(mSVGElement);
+}
+
+float SVGElementMetrics::GetAxisLength(uint8_t aCtxType) const {
+ if (!EnsureCtx()) {
+ return 1;
+ }
+
+ return FixAxisLength(mCtx->GetLength(aCtxType));
+}
+
+bool SVGElementMetrics::EnsureCtx() const {
+ if (!mCtx && mSVGElement) {
+ mCtx = mSVGElement->GetCtx();
+ if (!mCtx && mSVGElement->IsSVGElement(nsGkAtoms::svg)) {
+ auto* e = static_cast<SVGViewportElement*>(mSVGElement);
+
+ if (!e->IsInner()) {
+ // mSVGElement must be the outer svg element
+ mCtx = e;
+ }
+ }
+ }
+ return mCtx != nullptr;
+}
+
+NonSVGFrameUserSpaceMetrics::NonSVGFrameUserSpaceMetrics(nsIFrame* aFrame)
+ : mFrame(aFrame) {}
+
+float NonSVGFrameUserSpaceMetrics::GetEmLength() const {
+ return SVGContentUtils::GetFontSize(mFrame);
+}
+
+float NonSVGFrameUserSpaceMetrics::GetExLength() const {
+ return SVGContentUtils::GetFontXHeight(mFrame);
+}
+
+gfx::Size NonSVGFrameUserSpaceMetrics::GetSize() const {
+ return SVGIntegrationUtils::GetSVGCoordContextForNonSVGFrame(mFrame);
+}
+
+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(SVGElement* aSVGElement,
+ uint8_t aUnitType) const {
+ return GetPixelsPerUnit(SVGElementMetrics(aSVGElement), aUnitType);
+}
+
+float SVGAnimatedLength::GetPixelsPerUnit(SVGViewportElement* aCtx,
+ uint8_t aUnitType) const {
+ return GetPixelsPerUnit(SVGElementMetrics(aCtx, aCtx), aUnitType);
+}
+
+float SVGAnimatedLength::GetPixelsPerUnit(nsIFrame* aFrame,
+ uint8_t aUnitType) const {
+ nsIContent* content = aFrame->GetContent();
+ MOZ_ASSERT(!content->IsText(), "Not expecting text content");
+ if (content->IsSVGElement()) {
+ return GetPixelsPerUnit(
+ SVGElementMetrics(static_cast<SVGElement*>(content)), aUnitType);
+ }
+ return GetPixelsPerUnit(NonSVGFrameUserSpaceMetrics(aFrame), aUnitType);
+}
+
+// See https://www.w3.org/TR/css-values-3/#absolute-lengths
+static const float DPI = 96.0f;
+
+bool UserSpaceMetrics::ResolveAbsoluteUnit(uint8_t aUnitType, float& aRes) {
+ switch (aUnitType) {
+ case SVGLength_Binding::SVG_LENGTHTYPE_NUMBER:
+ case SVGLength_Binding::SVG_LENGTHTYPE_PX:
+ aRes = 1;
+ break;
+ case SVGLength_Binding::SVG_LENGTHTYPE_MM:
+ aRes = DPI / MM_PER_INCH_FLOAT;
+ break;
+ case SVGLength_Binding::SVG_LENGTHTYPE_CM:
+ aRes = 10.0f * DPI / MM_PER_INCH_FLOAT;
+ break;
+ case SVGLength_Binding::SVG_LENGTHTYPE_IN:
+ aRes = DPI;
+ break;
+ case SVGLength_Binding::SVG_LENGTHTYPE_PT:
+ aRes = DPI / POINTS_PER_INCH_FLOAT;
+ break;
+ case SVGLength_Binding::SVG_LENGTHTYPE_PC:
+ aRes = 12.0f * DPI / POINTS_PER_INCH_FLOAT;
+ break;
+ default:
+ return false;
+ }
+ return true;
+}
+
+float SVGAnimatedLength::GetPixelsPerUnit(const UserSpaceMetrics& aMetrics,
+ uint8_t aUnitType) const {
+ float res;
+ if (UserSpaceMetrics::ResolveAbsoluteUnit(aUnitType, res)) {
+ return res;
+ }
+ switch (aUnitType) {
+ case SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE:
+ return aMetrics.GetAxisLength(mCtxType) / 100.0f;
+ case SVGLength_Binding::SVG_LENGTHTYPE_EMS:
+ return aMetrics.GetEmLength();
+ case SVGLength_Binding::SVG_LENGTHTYPE_EXS:
+ return aMetrics.GetExLength();
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown unit type");
+ return 0;
+ }
+}
+
+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 && mSpecifiedUnitType == uint8_t(unitType)) return NS_OK;
+
+ float pixelsPerUnit = GetPixelsPerUnit(aSVGElement, unitType);
+ if (pixelsPerUnit == 0.0f) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ float valueInUserUnits =
+ mBaseVal * GetPixelsPerUnit(aSVGElement, mSpecifiedUnitType);
+ float valueInSpecifiedUnits = valueInUserUnits / 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);
+
+ mSpecifiedUnitType = uint8_t(unitType);
+ // 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 &&
+ mSpecifiedUnitType == uint8_t(aUnitType)) {
+ return NS_OK;
+ }
+
+ AutoChangeLengthNotifier notifier(this, aSVGElement);
+
+ mBaseVal = aValueInSpecifiedUnits;
+ mIsBaseSet = true;
+ mSpecifiedUnitType = uint8_t(aUnitType);
+ if (!mIsAnimated) {
+ mAnimVal = mBaseVal;
+ }
+ 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) &&
+ mSpecifiedUnitType == uint8_t(unitType)) {
+ return NS_OK;
+ }
+
+ AutoChangeLengthNotifier notifier(this, aSVGElement, aDoSetAttr);
+
+ mBaseVal = value;
+ mIsBaseSet = true;
+ mSpecifiedUnitType = uint8_t(unitType);
+ if (!mIsAnimated) {
+ mAnimVal = mBaseVal;
+ }
+
+ return NS_OK;
+}
+
+void SVGAnimatedLength::GetBaseValueString(nsAString& aValueAsString) const {
+ GetValueString(aValueAsString, mBaseVal, mSpecifiedUnitType);
+}
+
+void SVGAnimatedLength::GetAnimValueString(nsAString& aValueAsString) const {
+ GetValueString(aValueAsString, mAnimVal, mSpecifiedUnitType);
+}
+
+nsresult SVGAnimatedLength::SetBaseValue(float aValue, SVGElement* aSVGElement,
+ bool aDoSetAttr) {
+ float pixelsPerUnit = GetPixelsPerUnit(aSVGElement, mSpecifiedUnitType);
+ 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);
+}
+
+nsresult SVGAnimatedLength::SetAnimValue(float aValue,
+ SVGElement* aSVGElement) {
+ float valueInSpecifiedUnits =
+ aValue / GetPixelsPerUnit(aSVGElement, mSpecifiedUnitType);
+
+ if (std::isfinite(valueInSpecifiedUnits)) {
+ SetAnimValueInSpecifiedUnits(valueInSpecifiedUnits, aSVGElement);
+ return NS_OK;
+ }
+ return NS_ERROR_ILLEGAL_VALUE;
+}
+
+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(SMILFloatType::Singleton());
+ val.mU.mDouble = value * mVal->GetPixelsPerUnit(mSVGElement, unitType);
+ aValue = val;
+ aPreventCachingOfSandwich =
+ (unitType == SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE ||
+ unitType == SVGLength_Binding::SVG_LENGTHTYPE_EMS ||
+ unitType == SVGLength_Binding::SVG_LENGTHTYPE_EXS);
+
+ return NS_OK;
+}
+
+SMILValue SVGAnimatedLength::SMILLength::GetBaseValue() const {
+ SMILValue val(SMILFloatType::Singleton());
+ val.mU.mDouble = mVal->GetBaseValue(mSVGElement);
+ return val;
+}
+
+void SVGAnimatedLength::SMILLength::ClearAnimValue() {
+ if (mVal->mIsAnimated) {
+ mVal->mIsAnimated = false;
+ mVal->mAnimVal = mVal->mBaseVal;
+ mSVGElement->DidAnimateLength(mVal->mAttrEnum);
+ }
+}
+
+nsresult SVGAnimatedLength::SMILLength::SetAnimValue(const SMILValue& aValue) {
+ NS_ASSERTION(aValue.mType == SMILFloatType::Singleton(),
+ "Unexpected type to assign animated value");
+ if (aValue.mType == SMILFloatType::Singleton()) {
+ return mVal->SetAnimValue(float(aValue.mU.mDouble), mSVGElement);
+ }
+ return NS_OK;
+}
+
+} // namespace mozilla