SVGAnimatedOrient.cpp (15410B)
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/. */ 6 7 #include "SVGAnimatedOrient.h" 8 9 #include <utility> 10 11 #include "DOMSVGAngle.h" 12 #include "DOMSVGAnimatedAngle.h" 13 #include "SVGAttrTearoffTable.h" 14 #include "SVGOrientSMILType.h" 15 #include "mozAutoDocUpdate.h" 16 #include "mozilla/Maybe.h" 17 #include "mozilla/SMILValue.h" 18 #include "mozilla/dom/SVGMarkerElement.h" 19 #include "nsContentUtils.h" 20 #include "nsPrintfCString.h" 21 #include "nsTextFormatter.h" 22 23 using namespace mozilla::dom; 24 using namespace mozilla::dom::SVGAngle_Binding; 25 using namespace mozilla::dom::SVGMarkerElement_Binding; 26 27 namespace mozilla { 28 29 constinit static SVGAttrTearoffTable<SVGAnimatedOrient, 30 DOMSVGAnimatedEnumeration> 31 sSVGAnimatedEnumTearoffTable; 32 constinit static SVGAttrTearoffTable<SVGAnimatedOrient, DOMSVGAnimatedAngle> 33 sSVGAnimatedAngleTearoffTable; 34 constinit static SVGAttrTearoffTable<SVGAnimatedOrient, DOMSVGAngle> 35 sBaseSVGAngleTearoffTable; 36 constinit static SVGAttrTearoffTable<SVGAnimatedOrient, DOMSVGAngle> 37 sAnimSVGAngleTearoffTable; 38 39 /* Helper functions */ 40 41 //---------------------------------------------------------------------- 42 // Helper class: AutoChangeOrientNotifier 43 // Stack-based helper class to pair calls to WillChangeOrient and 44 // DidChangeOrient with mozAutoDocUpdate. 45 class MOZ_RAII AutoChangeOrientNotifier { 46 public: 47 AutoChangeOrientNotifier(SVGAnimatedOrient* aOrient, SVGElement* aSVGElement, 48 bool aDoSetAttr = true) 49 : mOrient(aOrient), mSVGElement(aSVGElement), mDoSetAttr(aDoSetAttr) { 50 MOZ_ASSERT(mOrient, "Expecting non-null orient"); 51 if (mSVGElement && mDoSetAttr) { 52 mUpdateBatch.emplace(mSVGElement->GetComposedDoc(), true); 53 mSVGElement->WillChangeOrient(mUpdateBatch.ref()); 54 } 55 } 56 57 ~AutoChangeOrientNotifier() { 58 if (mSVGElement) { 59 if (mDoSetAttr) { 60 mSVGElement->DidChangeOrient(mUpdateBatch.ref()); 61 } 62 if (mOrient->mIsAnimated) { 63 mSVGElement->AnimationNeedsResample(); 64 } 65 } 66 } 67 68 private: 69 Maybe<mozAutoDocUpdate> mUpdateBatch; 70 SVGAnimatedOrient* const mOrient; 71 SVGElement* const mSVGElement; 72 bool mDoSetAttr; 73 }; 74 75 const unsigned short SVG_ANGLETYPE_TURN = 5; 76 77 static void GetAngleUnitString(nsAString& aUnit, uint16_t aUnitType) { 78 switch (aUnitType) { 79 case SVG_ANGLETYPE_UNSPECIFIED: 80 aUnit.Truncate(); 81 return; 82 case SVG_ANGLETYPE_DEG: 83 aUnit.AssignLiteral("deg"); 84 return; 85 case SVG_ANGLETYPE_RAD: 86 aUnit.AssignLiteral("rad"); 87 return; 88 case SVG_ANGLETYPE_GRAD: 89 aUnit.AssignLiteral("grad"); 90 return; 91 case SVG_ANGLETYPE_TURN: 92 aUnit.AssignLiteral("turn"); 93 return; 94 } 95 96 MOZ_ASSERT_UNREACHABLE("Unknown unit type"); 97 } 98 99 static uint16_t GetAngleUnitTypeForString(const nsAString& aUnit) { 100 if (aUnit.IsEmpty()) { 101 return SVG_ANGLETYPE_UNSPECIFIED; 102 } 103 if (aUnit.LowerCaseEqualsLiteral("deg")) { 104 return SVG_ANGLETYPE_DEG; 105 } 106 if (aUnit.LowerCaseEqualsLiteral("rad")) { 107 return SVG_ANGLETYPE_RAD; 108 } 109 if (aUnit.LowerCaseEqualsLiteral("grad")) { 110 return SVG_ANGLETYPE_GRAD; 111 } 112 if (aUnit.LowerCaseEqualsLiteral("turn")) { 113 return SVG_ANGLETYPE_TURN; 114 } 115 return SVG_ANGLETYPE_UNKNOWN; 116 } 117 118 static void GetAngleValueString(nsAString& aValueAsString, float aValue, 119 uint16_t aUnitType) { 120 nsTextFormatter::ssprintf(aValueAsString, u"%g", (double)aValue); 121 122 nsAutoString unitString; 123 GetAngleUnitString(unitString, aUnitType); 124 aValueAsString.Append(unitString); 125 } 126 127 /*static*/ 128 bool SVGAnimatedOrient::IsValidUnitType(uint16_t aUnitType) { 129 return aUnitType > SVG_ANGLETYPE_UNKNOWN && aUnitType <= SVG_ANGLETYPE_GRAD; 130 } 131 132 /* static */ 133 bool SVGAnimatedOrient::GetValueFromString(const nsAString& aString, 134 float& aValue, uint16_t* aUnitType) { 135 bool success; 136 auto token = SVGContentUtils::GetAndEnsureOneToken(aString, success); 137 138 if (!success) { 139 return false; 140 } 141 142 nsAString::const_iterator iter, end; 143 token.BeginReading(iter); 144 token.EndReading(end); 145 146 if (!SVGContentUtils::ParseNumber(iter, end, aValue)) { 147 return false; 148 } 149 150 const nsAString& units = Substring(iter, end); 151 *aUnitType = GetAngleUnitTypeForString(units); 152 return *aUnitType != SVG_ANGLETYPE_UNKNOWN; 153 } 154 155 /* static */ 156 float SVGAnimatedOrient::GetDegreesPerUnit(uint8_t aUnit) { 157 switch (aUnit) { 158 case SVG_ANGLETYPE_UNSPECIFIED: 159 case SVG_ANGLETYPE_DEG: 160 return 1; 161 case SVG_ANGLETYPE_RAD: 162 return static_cast<float>(180.0 / M_PI); 163 case SVG_ANGLETYPE_GRAD: 164 return 90.0f / 100.0f; 165 case SVG_ANGLETYPE_TURN: 166 return 360.0f; 167 default: 168 MOZ_ASSERT_UNREACHABLE("Unknown unit type"); 169 return 0; 170 } 171 } 172 173 void SVGAnimatedOrient::SetBaseValueInSpecifiedUnits(float aValue, 174 SVGElement* aSVGElement) { 175 if (mBaseVal == aValue && mBaseType == SVG_MARKER_ORIENT_ANGLE) { 176 return; 177 } 178 179 AutoChangeOrientNotifier notifier(this, aSVGElement); 180 181 mBaseVal = aValue; 182 mBaseType = SVG_MARKER_ORIENT_ANGLE; 183 if (!mIsAnimated) { 184 mAnimVal = mBaseVal; 185 mAnimType = mBaseType; 186 } 187 } 188 189 void SVGAnimatedOrient::ConvertToSpecifiedUnits(uint16_t unitType, 190 SVGElement* aSVGElement) { 191 if (mBaseValUnit == uint8_t(unitType) && 192 mBaseType == SVG_MARKER_ORIENT_ANGLE) { 193 return; 194 } 195 196 float valueInUserUnits = mBaseVal * GetDegreesPerUnit(mBaseValUnit); 197 SetBaseValue(valueInUserUnits, unitType, aSVGElement, true); 198 } 199 200 void SVGAnimatedOrient::NewValueSpecifiedUnits(uint16_t aUnitType, 201 float aValueInSpecifiedUnits, 202 SVGElement* aSVGElement) { 203 MOZ_ASSERT(IsValidUnitType(aUnitType), "Unexpected unit type"); 204 205 if (mBaseVal == aValueInSpecifiedUnits && 206 mBaseValUnit == uint8_t(aUnitType) && 207 mBaseType == SVG_MARKER_ORIENT_ANGLE) { 208 return; 209 } 210 211 AutoChangeOrientNotifier notifier(this, aSVGElement); 212 213 mBaseVal = aValueInSpecifiedUnits; 214 mBaseValUnit = uint8_t(aUnitType); 215 mBaseType = SVG_MARKER_ORIENT_ANGLE; 216 if (!mIsAnimated) { 217 mAnimVal = mBaseVal; 218 mAnimValUnit = mBaseValUnit; 219 mAnimType = mBaseType; 220 } 221 } 222 223 already_AddRefed<DOMSVGAngle> SVGAnimatedOrient::ToDOMBaseVal( 224 SVGElement* aSVGElement) { 225 RefPtr<DOMSVGAngle> domBaseVal = sBaseSVGAngleTearoffTable.GetTearoff(this); 226 if (!domBaseVal) { 227 domBaseVal = 228 new DOMSVGAngle(this, aSVGElement, DOMSVGAngle::AngleType::BaseValue); 229 sBaseSVGAngleTearoffTable.AddTearoff(this, domBaseVal); 230 } 231 232 return domBaseVal.forget(); 233 } 234 235 already_AddRefed<DOMSVGAngle> SVGAnimatedOrient::ToDOMAnimVal( 236 SVGElement* aSVGElement) { 237 RefPtr<DOMSVGAngle> domAnimVal = sAnimSVGAngleTearoffTable.GetTearoff(this); 238 if (!domAnimVal) { 239 domAnimVal = 240 new DOMSVGAngle(this, aSVGElement, DOMSVGAngle::AngleType::AnimValue); 241 sAnimSVGAngleTearoffTable.AddTearoff(this, domAnimVal); 242 } 243 244 return domAnimVal.forget(); 245 } 246 247 DOMSVGAngle::~DOMSVGAngle() { 248 switch (mType) { 249 case AngleType::BaseValue: 250 sBaseSVGAngleTearoffTable.RemoveTearoff(mVal); 251 break; 252 case AngleType::AnimValue: 253 sAnimSVGAngleTearoffTable.RemoveTearoff(mVal); 254 break; 255 default: 256 delete mVal; 257 } 258 } 259 260 /* Implementation */ 261 262 nsresult SVGAnimatedOrient::SetBaseValueString(const nsAString& aValueAsString, 263 SVGElement* aSVGElement, 264 bool aDoSetAttr) { 265 uint8_t type; 266 float value; 267 uint16_t unitType; 268 bool valueChanged = false; 269 270 if (aValueAsString.EqualsLiteral("auto")) { 271 type = SVG_MARKER_ORIENT_AUTO; 272 if (type == mBaseType) { 273 return NS_OK; 274 } 275 } else if (aValueAsString.EqualsLiteral("auto-start-reverse")) { 276 type = SVG_MARKER_ORIENT_AUTO_START_REVERSE; 277 if (type == mBaseType) { 278 return NS_OK; 279 } 280 } else { 281 if (!GetValueFromString(aValueAsString, value, &unitType)) { 282 return NS_ERROR_DOM_SYNTAX_ERR; 283 } 284 if (mBaseVal == value && mBaseValUnit == uint8_t(unitType) && 285 mBaseType == SVG_MARKER_ORIENT_ANGLE) { 286 return NS_OK; 287 } 288 valueChanged = true; 289 } 290 291 AutoChangeOrientNotifier notifier(this, aSVGElement, aDoSetAttr); 292 293 if (valueChanged) { 294 mBaseVal = value; 295 mBaseValUnit = uint8_t(unitType); 296 mBaseType = SVG_MARKER_ORIENT_ANGLE; 297 } else { 298 mBaseVal = .0f; 299 mBaseValUnit = SVG_ANGLETYPE_UNSPECIFIED; 300 mBaseType = type; 301 } 302 303 if (!mIsAnimated) { 304 mAnimVal = mBaseVal; 305 mAnimValUnit = mBaseValUnit; 306 mAnimType = mBaseType; 307 } 308 309 return NS_OK; 310 } 311 312 void SVGAnimatedOrient::GetBaseValueString(nsAString& aValueAsString) const { 313 switch (mBaseType) { 314 case SVG_MARKER_ORIENT_AUTO: 315 aValueAsString.AssignLiteral("auto"); 316 return; 317 case SVG_MARKER_ORIENT_AUTO_START_REVERSE: 318 aValueAsString.AssignLiteral("auto-start-reverse"); 319 return; 320 } 321 GetAngleValueString(aValueAsString, mBaseVal, mBaseValUnit); 322 } 323 324 void SVGAnimatedOrient::GetBaseAngleValueString( 325 nsAString& aValueAsString) const { 326 GetAngleValueString(aValueAsString, mBaseVal, mBaseValUnit); 327 } 328 329 void SVGAnimatedOrient::GetAnimAngleValueString( 330 nsAString& aValueAsString) const { 331 GetAngleValueString(aValueAsString, mAnimVal, mAnimValUnit); 332 } 333 334 void SVGAnimatedOrient::SetBaseValue(float aValue, uint8_t aUnit, 335 SVGElement* aSVGElement, bool aDoSetAttr) { 336 float valueInSpecifiedUnits = aValue / GetDegreesPerUnit(aUnit); 337 if (aUnit == mBaseValUnit && mBaseVal == valueInSpecifiedUnits && 338 mBaseType == SVG_MARKER_ORIENT_ANGLE) { 339 return; 340 } 341 342 AutoChangeOrientNotifier notifier(this, aSVGElement, aDoSetAttr); 343 344 mBaseValUnit = aUnit; 345 mBaseVal = valueInSpecifiedUnits; 346 mBaseType = SVG_MARKER_ORIENT_ANGLE; 347 if (!mIsAnimated) { 348 mAnimValUnit = mBaseValUnit; 349 mAnimVal = mBaseVal; 350 mAnimType = mBaseType; 351 } 352 } 353 354 void SVGAnimatedOrient::SetBaseType(SVGEnumValue aValue, 355 SVGElement* aSVGElement, ErrorResult& aRv) { 356 if (mBaseType == aValue) { 357 return; 358 } 359 if (aValue >= SVG_MARKER_ORIENT_AUTO && 360 aValue <= SVG_MARKER_ORIENT_AUTO_START_REVERSE) { 361 AutoChangeOrientNotifier notifier(this, aSVGElement); 362 363 mBaseVal = .0f; 364 mBaseValUnit = SVG_ANGLETYPE_UNSPECIFIED; 365 mBaseType = aValue; 366 if (!mIsAnimated) { 367 mAnimVal = mBaseVal; 368 mAnimValUnit = mBaseValUnit; 369 mAnimType = mBaseType; 370 } 371 return; 372 } 373 nsPrintfCString err("Invalid base value %u for marker orient", aValue); 374 aRv.ThrowTypeError(err); 375 } 376 377 void SVGAnimatedOrient::SetAnimValue(float aValue, uint8_t aUnit, 378 SVGElement* aSVGElement) { 379 if (mIsAnimated && mAnimVal == aValue && mAnimValUnit == aUnit && 380 mAnimType == SVG_MARKER_ORIENT_ANGLE) { 381 return; 382 } 383 mAnimVal = aValue; 384 mAnimValUnit = aUnit; 385 mAnimType = SVG_MARKER_ORIENT_ANGLE; 386 mIsAnimated = true; 387 aSVGElement->DidAnimateOrient(); 388 } 389 390 void SVGAnimatedOrient::SetAnimType(SVGEnumValue aValue, 391 SVGElement* aSVGElement) { 392 if (mIsAnimated && mAnimType == aValue) { 393 return; 394 } 395 mAnimVal = .0f; 396 mAnimValUnit = SVG_ANGLETYPE_UNSPECIFIED; 397 mAnimType = aValue; 398 mIsAnimated = true; 399 aSVGElement->DidAnimateOrient(); 400 } 401 402 already_AddRefed<DOMSVGAnimatedAngle> SVGAnimatedOrient::ToDOMAnimatedAngle( 403 SVGElement* aSVGElement) { 404 RefPtr<DOMSVGAnimatedAngle> domAnimatedAngle = 405 sSVGAnimatedAngleTearoffTable.GetTearoff(this); 406 if (!domAnimatedAngle) { 407 domAnimatedAngle = new DOMSVGAnimatedAngle(this, aSVGElement); 408 sSVGAnimatedAngleTearoffTable.AddTearoff(this, domAnimatedAngle); 409 } 410 411 return domAnimatedAngle.forget(); 412 } 413 414 already_AddRefed<DOMSVGAnimatedEnumeration> 415 SVGAnimatedOrient::ToDOMAnimatedEnum(SVGElement* aSVGElement) { 416 RefPtr<DOMSVGAnimatedEnumeration> domAnimatedEnum = 417 sSVGAnimatedEnumTearoffTable.GetTearoff(this); 418 if (!domAnimatedEnum) { 419 domAnimatedEnum = new DOMAnimatedEnum(this, aSVGElement); 420 sSVGAnimatedEnumTearoffTable.AddTearoff(this, domAnimatedEnum); 421 } 422 423 return domAnimatedEnum.forget(); 424 } 425 426 DOMSVGAnimatedAngle::~DOMSVGAnimatedAngle() { 427 sSVGAnimatedAngleTearoffTable.RemoveTearoff(mVal); 428 } 429 430 SVGAnimatedOrient::DOMAnimatedEnum::~DOMAnimatedEnum() { 431 sSVGAnimatedEnumTearoffTable.RemoveTearoff(mVal); 432 } 433 434 UniquePtr<SMILAttr> SVGAnimatedOrient::ToSMILAttr(SVGElement* aSVGElement) { 435 if (aSVGElement->IsSVGElement(nsGkAtoms::marker)) { 436 return MakeUnique<SMILOrient>(this, aSVGElement); 437 } 438 // SMILOrient would not be useful for general angle attributes (also, 439 // "orient" is the only animatable <angle>-valued attribute in SVG 1.1). 440 MOZ_ASSERT_UNREACHABLE("Trying to animate unknown angle attribute."); 441 return nullptr; 442 } 443 444 nsresult SVGAnimatedOrient::SMILOrient::ValueFromString( 445 const nsAString& aStr, const SVGAnimationElement* /*aSrcElement*/, 446 SMILValue& aValue, bool& aPreventCachingOfSandwich) const { 447 SMILValue val(&SVGOrientSMILType::sSingleton); 448 if (aStr.EqualsLiteral("auto")) { 449 val.mU.mOrient.mOrientType = SVG_MARKER_ORIENT_AUTO; 450 val.mU.mOrient.mAngle = .0f; 451 val.mU.mOrient.mUnit = SVG_ANGLETYPE_UNSPECIFIED; 452 } else if (aStr.EqualsLiteral("auto-start-reverse")) { 453 val.mU.mOrient.mOrientType = SVG_MARKER_ORIENT_AUTO_START_REVERSE; 454 val.mU.mOrient.mAngle = .0f; 455 val.mU.mOrient.mUnit = SVG_ANGLETYPE_UNSPECIFIED; 456 } else { 457 float value; 458 uint16_t unitType; 459 if (!GetValueFromString(aStr, value, &unitType)) { 460 return NS_ERROR_DOM_SYNTAX_ERR; 461 } 462 val.mU.mOrient.mAngle = value; 463 val.mU.mOrient.mUnit = unitType; 464 val.mU.mOrient.mOrientType = SVG_MARKER_ORIENT_ANGLE; 465 } 466 aValue = std::move(val); 467 468 return NS_OK; 469 } 470 471 SMILValue SVGAnimatedOrient::SMILOrient::GetBaseValue() const { 472 SMILValue val(&SVGOrientSMILType::sSingleton); 473 val.mU.mOrient.mAngle = mOrient->GetBaseValInSpecifiedUnits(); 474 val.mU.mOrient.mUnit = mOrient->GetBaseValueUnit(); 475 val.mU.mOrient.mOrientType = mOrient->mBaseType; 476 return val; 477 } 478 479 void SVGAnimatedOrient::SMILOrient::ClearAnimValue() { 480 if (mOrient->mIsAnimated) { 481 mOrient->mIsAnimated = false; 482 mOrient->mAnimVal = mOrient->mBaseVal; 483 mOrient->mAnimValUnit = mOrient->mBaseValUnit; 484 mOrient->mAnimType = mOrient->mBaseType; 485 mSVGElement->DidAnimateOrient(); 486 } 487 } 488 489 nsresult SVGAnimatedOrient::SMILOrient::SetAnimValue(const SMILValue& aValue) { 490 NS_ASSERTION(aValue.mType == &SVGOrientSMILType::sSingleton, 491 "Unexpected type to assign animated value"); 492 493 if (aValue.mType == &SVGOrientSMILType::sSingleton) { 494 if (aValue.mU.mOrient.mOrientType == SVG_MARKER_ORIENT_AUTO || 495 aValue.mU.mOrient.mOrientType == SVG_MARKER_ORIENT_AUTO_START_REVERSE) { 496 mOrient->SetAnimType(aValue.mU.mOrient.mOrientType, mSVGElement); 497 } else { 498 mOrient->SetAnimValue(aValue.mU.mOrient.mAngle, aValue.mU.mOrient.mUnit, 499 mSVGElement); 500 } 501 } 502 return NS_OK; 503 } 504 505 } // namespace mozilla