Bug 1700051: part 46) Const-qualify `mozInlineSpellStatus::mAnchorRange`. r=smaug
[gecko.git] / dom / svg / SVGAnimatedLength.cpp
blobae1d51d8137d56466ebaa8ee31717ef996341604
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "SVGAnimatedLength.h"
9 #include "mozAutoDocUpdate.h"
10 #include "mozilla/ArrayUtils.h"
11 #include "mozilla/Maybe.h"
12 #include "mozilla/SMILValue.h"
13 #include "mozilla/SVGIntegrationUtils.h"
14 #include "mozilla/dom/SVGViewportElement.h"
15 #include "DOMSVGAnimatedLength.h"
16 #include "DOMSVGLength.h"
17 #include "LayoutLogging.h"
18 #include "nsContentUtils.h"
19 #include "nsIFrame.h"
20 #include "nsTextFormatter.h"
21 #include "SMILFloatType.h"
22 #include "SVGAttrTearoffTable.h"
24 using namespace mozilla::dom;
26 namespace mozilla {
28 //----------------------------------------------------------------------
29 // Helper class: AutoChangeLengthNotifier
30 // Stack-based helper class to pair calls to WillChangeLength and
31 // DidChangeLength.
32 class MOZ_RAII AutoChangeLengthNotifier {
33 public:
34 AutoChangeLengthNotifier(SVGAnimatedLength* aLength, SVGElement* aSVGElement,
35 bool aDoSetAttr = true)
36 : mLength(aLength), mSVGElement(aSVGElement), mDoSetAttr(aDoSetAttr) {
37 MOZ_ASSERT(mLength, "Expecting non-null length");
38 MOZ_ASSERT(mSVGElement, "Expecting non-null element");
40 if (mDoSetAttr) {
41 mUpdateBatch.emplace(aSVGElement->GetComposedDoc(), true);
42 mEmptyOrOldValue =
43 mSVGElement->WillChangeLength(mLength->mAttrEnum, mUpdateBatch.ref());
47 ~AutoChangeLengthNotifier() {
48 if (mDoSetAttr) {
49 mSVGElement->DidChangeLength(mLength->mAttrEnum, mEmptyOrOldValue,
50 mUpdateBatch.ref());
52 if (mLength->mIsAnimated) {
53 mSVGElement->AnimationNeedsResample();
57 private:
58 SVGAnimatedLength* const mLength;
59 SVGElement* const mSVGElement;
60 Maybe<mozAutoDocUpdate> mUpdateBatch;
61 nsAttrValue mEmptyOrOldValue;
62 bool mDoSetAttr;
65 static const nsStaticAtom* const unitMap[] = {
66 nullptr, /* SVG_LENGTHTYPE_UNKNOWN */
67 nullptr, /* SVG_LENGTHTYPE_NUMBER */
68 nsGkAtoms::percentage,
69 nsGkAtoms::em,
70 nsGkAtoms::ex,
71 nsGkAtoms::px,
72 nsGkAtoms::cm,
73 nsGkAtoms::mm,
74 nsGkAtoms::in,
75 nsGkAtoms::pt,
76 nsGkAtoms::pc};
78 static SVGAttrTearoffTable<SVGAnimatedLength, DOMSVGAnimatedLength>
79 sSVGAnimatedLengthTearoffTable;
81 /* Helper functions */
83 static bool IsValidUnitType(uint16_t unit) {
84 return unit > SVGLength_Binding::SVG_LENGTHTYPE_UNKNOWN &&
85 unit <= SVGLength_Binding::SVG_LENGTHTYPE_PC;
88 static void GetUnitString(nsAString& unit, uint16_t unitType) {
89 if (IsValidUnitType(unitType)) {
90 if (unitMap[unitType]) {
91 unitMap[unitType]->ToString(unit);
93 return;
96 MOZ_ASSERT_UNREACHABLE("Unknown unit type");
99 static uint16_t GetUnitTypeForString(const nsAString& unitStr) {
100 if (unitStr.IsEmpty()) return SVGLength_Binding::SVG_LENGTHTYPE_NUMBER;
102 nsAtom* unitAtom = NS_GetStaticAtom(unitStr);
103 if (unitAtom) {
104 for (uint32_t i = 0; i < ArrayLength(unitMap); i++) {
105 if (unitMap[i] == unitAtom) {
106 return i;
111 return SVGLength_Binding::SVG_LENGTHTYPE_UNKNOWN;
114 static void GetValueString(nsAString& aValueAsString, float aValue,
115 uint16_t aUnitType) {
116 nsTextFormatter::ssprintf(aValueAsString, u"%g", (double)aValue);
118 nsAutoString unitString;
119 GetUnitString(unitString, aUnitType);
120 aValueAsString.Append(unitString);
123 static bool GetValueFromString(const nsAString& aString, float& aValue,
124 uint16_t* aUnitType) {
125 bool success;
126 auto token = SVGContentUtils::GetAndEnsureOneToken(aString, success);
128 if (!success) {
129 return false;
132 RangedPtr<const char16_t> iter = SVGContentUtils::GetStartRangedPtr(token);
133 const RangedPtr<const char16_t> end = SVGContentUtils::GetEndRangedPtr(token);
135 if (!SVGContentUtils::ParseNumber(iter, end, aValue)) {
136 return false;
138 const nsAString& units = Substring(iter.get(), end.get());
139 *aUnitType = GetUnitTypeForString(units);
140 return IsValidUnitType(*aUnitType);
143 static float FixAxisLength(float aLength) {
144 if (aLength == 0.0f) {
145 LAYOUT_WARNING("zero axis length");
146 return 1e-20f;
148 return aLength;
151 SVGElementMetrics::SVGElementMetrics(SVGElement* aSVGElement,
152 SVGViewportElement* aCtx)
153 : mSVGElement(aSVGElement), mCtx(aCtx) {}
155 float SVGElementMetrics::GetEmLength() const {
156 return SVGContentUtils::GetFontSize(mSVGElement);
159 float SVGElementMetrics::GetExLength() const {
160 return SVGContentUtils::GetFontXHeight(mSVGElement);
163 float SVGElementMetrics::GetAxisLength(uint8_t aCtxType) const {
164 if (!EnsureCtx()) {
165 return 1;
168 return FixAxisLength(mCtx->GetLength(aCtxType));
171 bool SVGElementMetrics::EnsureCtx() const {
172 if (!mCtx && mSVGElement) {
173 mCtx = mSVGElement->GetCtx();
174 if (!mCtx && mSVGElement->IsSVGElement(nsGkAtoms::svg)) {
175 auto* e = static_cast<SVGViewportElement*>(mSVGElement);
177 if (!e->IsInner()) {
178 // mSVGElement must be the outer svg element
179 mCtx = e;
183 return mCtx != nullptr;
186 NonSVGFrameUserSpaceMetrics::NonSVGFrameUserSpaceMetrics(nsIFrame* aFrame)
187 : mFrame(aFrame) {}
189 float NonSVGFrameUserSpaceMetrics::GetEmLength() const {
190 return SVGContentUtils::GetFontSize(mFrame);
193 float NonSVGFrameUserSpaceMetrics::GetExLength() const {
194 return SVGContentUtils::GetFontXHeight(mFrame);
197 gfx::Size NonSVGFrameUserSpaceMetrics::GetSize() const {
198 return SVGIntegrationUtils::GetSVGCoordContextForNonSVGFrame(mFrame);
201 float UserSpaceMetricsWithSize::GetAxisLength(uint8_t aCtxType) const {
202 gfx::Size size = GetSize();
203 float length;
204 switch (aCtxType) {
205 case SVGContentUtils::X:
206 length = size.width;
207 break;
208 case SVGContentUtils::Y:
209 length = size.height;
210 break;
211 case SVGContentUtils::XY:
212 length =
213 SVGContentUtils::ComputeNormalizedHypotenuse(size.width, size.height);
214 break;
215 default:
216 MOZ_ASSERT_UNREACHABLE("Unknown axis type");
217 length = 1;
218 break;
220 return FixAxisLength(length);
223 float SVGAnimatedLength::GetPixelsPerUnit(SVGElement* aSVGElement,
224 uint8_t aUnitType) const {
225 return GetPixelsPerUnit(SVGElementMetrics(aSVGElement), aUnitType);
228 float SVGAnimatedLength::GetPixelsPerUnit(SVGViewportElement* aCtx,
229 uint8_t aUnitType) const {
230 return GetPixelsPerUnit(SVGElementMetrics(aCtx, aCtx), aUnitType);
233 float SVGAnimatedLength::GetPixelsPerUnit(nsIFrame* aFrame,
234 uint8_t aUnitType) const {
235 nsIContent* content = aFrame->GetContent();
236 if (content->IsSVGElement()) {
237 return GetPixelsPerUnit(
238 SVGElementMetrics(static_cast<SVGElement*>(content)), aUnitType);
240 return GetPixelsPerUnit(NonSVGFrameUserSpaceMetrics(aFrame), aUnitType);
243 // See https://www.w3.org/TR/css-values-3/#absolute-lengths
244 static const float DPI = 96.0f;
246 bool UserSpaceMetrics::ResolveAbsoluteUnit(uint8_t aUnitType, float& aRes) {
247 switch (aUnitType) {
248 case SVGLength_Binding::SVG_LENGTHTYPE_NUMBER:
249 case SVGLength_Binding::SVG_LENGTHTYPE_PX:
250 aRes = 1;
251 break;
252 case SVGLength_Binding::SVG_LENGTHTYPE_MM:
253 aRes = DPI / MM_PER_INCH_FLOAT;
254 break;
255 case SVGLength_Binding::SVG_LENGTHTYPE_CM:
256 aRes = 10.0f * DPI / MM_PER_INCH_FLOAT;
257 break;
258 case SVGLength_Binding::SVG_LENGTHTYPE_IN:
259 aRes = DPI;
260 break;
261 case SVGLength_Binding::SVG_LENGTHTYPE_PT:
262 aRes = DPI / POINTS_PER_INCH_FLOAT;
263 break;
264 case SVGLength_Binding::SVG_LENGTHTYPE_PC:
265 aRes = 12.0f * DPI / POINTS_PER_INCH_FLOAT;
266 break;
267 default:
268 return false;
270 return true;
273 float SVGAnimatedLength::GetPixelsPerUnit(const UserSpaceMetrics& aMetrics,
274 uint8_t aUnitType) const {
275 float res;
276 if (UserSpaceMetrics::ResolveAbsoluteUnit(aUnitType, res)) {
277 return res;
279 switch (aUnitType) {
280 case SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE:
281 return aMetrics.GetAxisLength(mCtxType) / 100.0f;
282 case SVGLength_Binding::SVG_LENGTHTYPE_EMS:
283 return aMetrics.GetEmLength();
284 case SVGLength_Binding::SVG_LENGTHTYPE_EXS:
285 return aMetrics.GetExLength();
286 default:
287 MOZ_ASSERT_UNREACHABLE("Unknown unit type");
288 return 0;
292 void SVGAnimatedLength::SetBaseValueInSpecifiedUnits(float aValue,
293 SVGElement* aSVGElement,
294 bool aDoSetAttr) {
295 if (mIsBaseSet && mBaseVal == aValue) {
296 return;
299 AutoChangeLengthNotifier notifier(this, aSVGElement, aDoSetAttr);
301 mBaseVal = aValue;
302 mIsBaseSet = true;
303 if (!mIsAnimated) {
304 mAnimVal = mBaseVal;
308 nsresult SVGAnimatedLength::ConvertToSpecifiedUnits(uint16_t unitType,
309 SVGElement* aSVGElement) {
310 if (!IsValidUnitType(unitType)) return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
312 if (mIsBaseSet && mSpecifiedUnitType == uint8_t(unitType)) return NS_OK;
314 float pixelsPerUnit = GetPixelsPerUnit(aSVGElement, unitType);
315 if (pixelsPerUnit == 0.0f) {
316 return NS_ERROR_ILLEGAL_VALUE;
319 float valueInUserUnits =
320 mBaseVal * GetPixelsPerUnit(aSVGElement, mSpecifiedUnitType);
321 float valueInSpecifiedUnits = valueInUserUnits / pixelsPerUnit;
323 if (!IsFinite(valueInSpecifiedUnits)) {
324 return NS_ERROR_ILLEGAL_VALUE;
327 // Even though we're not changing the visual effect this length will have
328 // on the document, we still need to send out notifications in case we have
329 // mutation listeners, since the actual string value of the attribute will
330 // change.
331 AutoChangeLengthNotifier notifier(this, aSVGElement);
333 mSpecifiedUnitType = uint8_t(unitType);
334 // Setting aDoSetAttr to false here will ensure we don't call
335 // Will/DidChangeAngle a second time (and dispatch duplicate notifications).
336 SetBaseValueInSpecifiedUnits(valueInSpecifiedUnits, aSVGElement, false);
338 return NS_OK;
341 nsresult SVGAnimatedLength::NewValueSpecifiedUnits(uint16_t aUnitType,
342 float aValueInSpecifiedUnits,
343 SVGElement* aSVGElement) {
344 NS_ENSURE_FINITE(aValueInSpecifiedUnits, NS_ERROR_ILLEGAL_VALUE);
346 if (!IsValidUnitType(aUnitType)) return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
348 if (mIsBaseSet && mBaseVal == aValueInSpecifiedUnits &&
349 mSpecifiedUnitType == uint8_t(aUnitType)) {
350 return NS_OK;
353 AutoChangeLengthNotifier notifier(this, aSVGElement);
355 mBaseVal = aValueInSpecifiedUnits;
356 mIsBaseSet = true;
357 mSpecifiedUnitType = uint8_t(aUnitType);
358 if (!mIsAnimated) {
359 mAnimVal = mBaseVal;
361 return NS_OK;
364 already_AddRefed<DOMSVGLength> SVGAnimatedLength::ToDOMBaseVal(
365 SVGElement* aSVGElement) {
366 return DOMSVGLength::GetTearOff(this, aSVGElement, false);
369 already_AddRefed<DOMSVGLength> SVGAnimatedLength::ToDOMAnimVal(
370 SVGElement* aSVGElement) {
371 return DOMSVGLength::GetTearOff(this, aSVGElement, true);
374 /* Implementation */
376 nsresult SVGAnimatedLength::SetBaseValueString(const nsAString& aValueAsString,
377 SVGElement* aSVGElement,
378 bool aDoSetAttr) {
379 float value;
380 uint16_t unitType;
382 if (!GetValueFromString(aValueAsString, value, &unitType)) {
383 return NS_ERROR_DOM_SYNTAX_ERR;
386 if (mIsBaseSet && mBaseVal == float(value) &&
387 mSpecifiedUnitType == uint8_t(unitType)) {
388 return NS_OK;
391 AutoChangeLengthNotifier notifier(this, aSVGElement, aDoSetAttr);
393 mBaseVal = value;
394 mIsBaseSet = true;
395 mSpecifiedUnitType = uint8_t(unitType);
396 if (!mIsAnimated) {
397 mAnimVal = mBaseVal;
400 return NS_OK;
403 void SVGAnimatedLength::GetBaseValueString(nsAString& aValueAsString) const {
404 GetValueString(aValueAsString, mBaseVal, mSpecifiedUnitType);
407 void SVGAnimatedLength::GetAnimValueString(nsAString& aValueAsString) const {
408 GetValueString(aValueAsString, mAnimVal, mSpecifiedUnitType);
411 nsresult SVGAnimatedLength::SetBaseValue(float aValue, SVGElement* aSVGElement,
412 bool aDoSetAttr) {
413 float pixelsPerUnit = GetPixelsPerUnit(aSVGElement, mSpecifiedUnitType);
414 if (pixelsPerUnit == 0.0f) {
415 return NS_ERROR_ILLEGAL_VALUE;
418 float valueInSpecifiedUnits = aValue / pixelsPerUnit;
419 if (!IsFinite(valueInSpecifiedUnits)) {
420 return NS_ERROR_ILLEGAL_VALUE;
423 SetBaseValueInSpecifiedUnits(valueInSpecifiedUnits, aSVGElement, aDoSetAttr);
424 return NS_OK;
427 void SVGAnimatedLength::SetAnimValueInSpecifiedUnits(float aValue,
428 SVGElement* aSVGElement) {
429 if (mAnimVal == aValue && mIsAnimated) {
430 return;
432 mAnimVal = aValue;
433 mIsAnimated = true;
434 aSVGElement->DidAnimateLength(mAttrEnum);
437 nsresult SVGAnimatedLength::SetAnimValue(float aValue,
438 SVGElement* aSVGElement) {
439 float valueInSpecifiedUnits =
440 aValue / GetPixelsPerUnit(aSVGElement, mSpecifiedUnitType);
442 if (IsFinite(valueInSpecifiedUnits)) {
443 SetAnimValueInSpecifiedUnits(valueInSpecifiedUnits, aSVGElement);
444 return NS_OK;
446 return NS_ERROR_ILLEGAL_VALUE;
449 already_AddRefed<DOMSVGAnimatedLength> SVGAnimatedLength::ToDOMAnimatedLength(
450 SVGElement* aSVGElement) {
451 RefPtr<DOMSVGAnimatedLength> svgAnimatedLength =
452 sSVGAnimatedLengthTearoffTable.GetTearoff(this);
453 if (!svgAnimatedLength) {
454 svgAnimatedLength = new DOMSVGAnimatedLength(this, aSVGElement);
455 sSVGAnimatedLengthTearoffTable.AddTearoff(this, svgAnimatedLength);
458 return svgAnimatedLength.forget();
461 DOMSVGAnimatedLength::~DOMSVGAnimatedLength() {
462 sSVGAnimatedLengthTearoffTable.RemoveTearoff(mVal);
465 UniquePtr<SMILAttr> SVGAnimatedLength::ToSMILAttr(SVGElement* aSVGElement) {
466 return MakeUnique<SMILLength>(this, aSVGElement);
469 nsresult SVGAnimatedLength::SMILLength::ValueFromString(
470 const nsAString& aStr, const SVGAnimationElement* /*aSrcElement*/,
471 SMILValue& aValue, bool& aPreventCachingOfSandwich) const {
472 float value;
473 uint16_t unitType;
475 if (!GetValueFromString(aStr, value, &unitType)) {
476 return NS_ERROR_DOM_SYNTAX_ERR;
479 SMILValue val(SMILFloatType::Singleton());
480 val.mU.mDouble = value * mVal->GetPixelsPerUnit(mSVGElement, unitType);
481 aValue = val;
482 aPreventCachingOfSandwich =
483 (unitType == SVGLength_Binding::SVG_LENGTHTYPE_PERCENTAGE ||
484 unitType == SVGLength_Binding::SVG_LENGTHTYPE_EMS ||
485 unitType == SVGLength_Binding::SVG_LENGTHTYPE_EXS);
487 return NS_OK;
490 SMILValue SVGAnimatedLength::SMILLength::GetBaseValue() const {
491 SMILValue val(SMILFloatType::Singleton());
492 val.mU.mDouble = mVal->GetBaseValue(mSVGElement);
493 return val;
496 void SVGAnimatedLength::SMILLength::ClearAnimValue() {
497 if (mVal->mIsAnimated) {
498 mVal->mIsAnimated = false;
499 mVal->mAnimVal = mVal->mBaseVal;
500 mSVGElement->DidAnimateLength(mVal->mAttrEnum);
504 nsresult SVGAnimatedLength::SMILLength::SetAnimValue(const SMILValue& aValue) {
505 NS_ASSERTION(aValue.mType == SMILFloatType::Singleton(),
506 "Unexpected type to assign animated value");
507 if (aValue.mType == SMILFloatType::Singleton()) {
508 return mVal->SetAnimValue(float(aValue.mU.mDouble), mSVGElement);
510 return NS_OK;
513 } // namespace mozilla