SVGAnimatedViewBox.cpp (9117B)
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 "SVGAnimatedViewBox.h" 8 9 #include <utility> 10 11 #include "SVGViewBoxSMILType.h" 12 #include "mozAutoDocUpdate.h" 13 #include "mozilla/Maybe.h" 14 #include "mozilla/SMILValue.h" 15 #include "mozilla/SVGContentUtils.h" 16 #include "mozilla/dom/SVGRect.h" 17 #include "nsCharSeparatedTokenizer.h" 18 #include "nsTextFormatter.h" 19 20 using namespace mozilla::dom; 21 22 namespace mozilla { 23 24 #define NUM_VIEWBOX_COMPONENTS 4 25 26 /* Implementation of SVGViewBox methods */ 27 28 bool SVGViewBox::operator==(const SVGViewBox& aOther) const { 29 if (&aOther == this) return true; 30 31 return (none && aOther.none) || 32 (!none && !aOther.none && x == aOther.x && y == aOther.y && 33 width == aOther.width && height == aOther.height); 34 } 35 36 /* static */ 37 nsresult SVGViewBox::FromString(const nsAString& aStr, SVGViewBox* aViewBox) { 38 if (aStr.EqualsLiteral("none")) { 39 aViewBox->none = true; 40 return NS_OK; 41 } 42 43 nsCharSeparatedTokenizerTemplate<nsContentUtils::IsHTMLWhitespace, 44 nsTokenizerFlags::SeparatorOptional> 45 tokenizer(aStr, ','); 46 float vals[NUM_VIEWBOX_COMPONENTS]; 47 uint32_t i; 48 for (i = 0; i < NUM_VIEWBOX_COMPONENTS && tokenizer.hasMoreTokens(); ++i) { 49 if (!SVGContentUtils::ParseNumber(tokenizer.nextToken(), vals[i])) { 50 return NS_ERROR_DOM_SYNTAX_ERR; 51 } 52 } 53 54 if (i != NUM_VIEWBOX_COMPONENTS || // Too few values. 55 tokenizer.hasMoreTokens() || // Too many values. 56 tokenizer.separatorAfterCurrentToken()) { // Trailing comma. 57 return NS_ERROR_DOM_SYNTAX_ERR; 58 } 59 60 aViewBox->x = vals[0]; 61 aViewBox->y = vals[1]; 62 aViewBox->width = vals[2]; 63 aViewBox->height = vals[3]; 64 aViewBox->none = false; 65 66 return NS_OK; 67 } 68 69 constinit static SVGAttrTearoffTable<SVGAnimatedViewBox, SVGRect> 70 sBaseSVGViewBoxTearoffTable; 71 constinit static SVGAttrTearoffTable<SVGAnimatedViewBox, SVGRect> 72 sAnimSVGViewBoxTearoffTable; 73 constinit SVGAttrTearoffTable<SVGAnimatedViewBox, SVGAnimatedRect> 74 SVGAnimatedViewBox::sSVGAnimatedRectTearoffTable; 75 76 //---------------------------------------------------------------------- 77 // Helper class: AutoChangeViewBoxNotifier 78 // Stack-based helper class to pair calls to WillChangeViewBox and 79 // DidChangeViewBox. 80 class MOZ_RAII AutoChangeViewBoxNotifier { 81 public: 82 AutoChangeViewBoxNotifier(SVGAnimatedViewBox* aViewBox, 83 SVGElement* aSVGElement, bool aDoSetAttr = true) 84 : mViewBox(aViewBox), mSVGElement(aSVGElement), mDoSetAttr(aDoSetAttr) { 85 MOZ_ASSERT(mViewBox, "Expecting non-null viewBox"); 86 MOZ_ASSERT(mSVGElement, "Expecting non-null element"); 87 88 if (mDoSetAttr) { 89 mUpdateBatch.emplace(aSVGElement->GetComposedDoc(), true); 90 mSVGElement->WillChangeViewBox(mUpdateBatch.ref()); 91 } 92 } 93 94 ~AutoChangeViewBoxNotifier() { 95 if (mDoSetAttr) { 96 mSVGElement->DidChangeViewBox(mUpdateBatch.ref()); 97 } 98 if (mViewBox->mAnimVal) { 99 mSVGElement->AnimationNeedsResample(); 100 } 101 } 102 103 private: 104 SVGAnimatedViewBox* const mViewBox; 105 SVGElement* const mSVGElement; 106 Maybe<mozAutoDocUpdate> mUpdateBatch; 107 bool mDoSetAttr; 108 }; 109 110 /* Implementation of SVGAnimatedViewBox methods */ 111 112 void SVGAnimatedViewBox::Init() { 113 mHasBaseVal = false; 114 // We shouldn't use mBaseVal for rendering (its usages should be guarded with 115 // "mHasBaseVal" checks), but just in case we do by accident, this will 116 // ensure that we treat it as "none" and ignore its numeric values: 117 mBaseVal.none = true; 118 119 mAnimVal = nullptr; 120 } 121 122 bool SVGAnimatedViewBox::HasRect() const { 123 // Check mAnimVal if we have one; otherwise, check mBaseVal if we have one; 124 // otherwise, just return false (we clearly do not have a rect). 125 const SVGViewBox* rect = mAnimVal.get(); 126 if (!rect) { 127 if (!mHasBaseVal) { 128 // no anim val, no base val --> no viewbox rect 129 return false; 130 } 131 rect = &mBaseVal; 132 } 133 134 return !rect->none && rect->width >= 0 && rect->height >= 0; 135 } 136 137 void SVGAnimatedViewBox::SetAnimValue(const SVGViewBox& aRect, 138 SVGElement* aSVGElement) { 139 if (!mAnimVal) { 140 // it's okay if allocation fails - and no point in reporting that 141 mAnimVal = MakeUnique<SVGViewBox>(aRect); 142 } else { 143 if (aRect == *mAnimVal) { 144 return; 145 } 146 *mAnimVal = aRect; 147 } 148 aSVGElement->DidAnimateViewBox(); 149 } 150 151 void SVGAnimatedViewBox::SetBaseField(float aValue, SVGElement* aSVGElement, 152 float& aField) { 153 if (!mHasBaseVal) { 154 aField = aValue; 155 return; 156 } 157 if (aField == aValue) { 158 return; 159 } 160 AutoChangeViewBoxNotifier notifier(this, aSVGElement); 161 aField = aValue; 162 } 163 164 void SVGAnimatedViewBox::SetBaseValue(const SVGViewBox& aRect, 165 SVGElement* aSVGElement, 166 bool aDoSetAttr) { 167 // Comparison against mBaseVal is only valid if we currently have a base val. 168 if (mHasBaseVal && mBaseVal == aRect) { 169 return; 170 } 171 172 AutoChangeViewBoxNotifier notifier(this, aSVGElement, aDoSetAttr); 173 174 mBaseVal = aRect; 175 mHasBaseVal = true; 176 } 177 178 nsresult SVGAnimatedViewBox::SetBaseValueString(const nsAString& aValue, 179 SVGElement* aSVGElement, 180 bool aDoSetAttr) { 181 SVGViewBox viewBox; 182 183 nsresult rv = SVGViewBox::FromString(aValue, &viewBox); 184 if (NS_FAILED(rv)) { 185 return rv; 186 } 187 SetBaseValue(viewBox, aSVGElement, aDoSetAttr); 188 return NS_OK; 189 } 190 191 void SVGAnimatedViewBox::GetBaseValueString(nsAString& aValue) const { 192 if (mBaseVal.none) { 193 aValue.AssignLiteral("none"); 194 return; 195 } 196 nsTextFormatter::ssprintf(aValue, u"%g %g %g %g", (double)mBaseVal.x, 197 (double)mBaseVal.y, (double)mBaseVal.width, 198 (double)mBaseVal.height); 199 } 200 201 already_AddRefed<SVGAnimatedRect> SVGAnimatedViewBox::ToSVGAnimatedRect( 202 SVGElement* aSVGElement) { 203 RefPtr<SVGAnimatedRect> domAnimatedRect = 204 sSVGAnimatedRectTearoffTable.GetTearoff(this); 205 if (!domAnimatedRect) { 206 domAnimatedRect = new SVGAnimatedRect(this, aSVGElement); 207 sSVGAnimatedRectTearoffTable.AddTearoff(this, domAnimatedRect); 208 } 209 210 return domAnimatedRect.forget(); 211 } 212 213 already_AddRefed<SVGRect> SVGAnimatedViewBox::ToDOMBaseVal( 214 SVGElement* aSVGElement) { 215 if (!mHasBaseVal || mBaseVal.none) { 216 return nullptr; 217 } 218 219 RefPtr<SVGRect> domBaseVal = sBaseSVGViewBoxTearoffTable.GetTearoff(this); 220 if (!domBaseVal) { 221 domBaseVal = new SVGRect(this, aSVGElement, SVGRect::RectType::BaseValue); 222 sBaseSVGViewBoxTearoffTable.AddTearoff(this, domBaseVal); 223 } 224 225 return domBaseVal.forget(); 226 } 227 228 SVGRect::~SVGRect() { 229 switch (mType) { 230 case RectType::BaseValue: 231 sBaseSVGViewBoxTearoffTable.RemoveTearoff(mVal); 232 break; 233 case RectType::AnimValue: 234 sAnimSVGViewBoxTearoffTable.RemoveTearoff(mVal); 235 break; 236 default: 237 break; 238 } 239 } 240 241 already_AddRefed<SVGRect> SVGAnimatedViewBox::ToDOMAnimVal( 242 SVGElement* aSVGElement) { 243 if ((mAnimVal && mAnimVal->none) || 244 (!mAnimVal && (!mHasBaseVal || mBaseVal.none))) { 245 return nullptr; 246 } 247 248 RefPtr<SVGRect> domAnimVal = sAnimSVGViewBoxTearoffTable.GetTearoff(this); 249 if (!domAnimVal) { 250 domAnimVal = new SVGRect(this, aSVGElement, SVGRect::RectType::AnimValue); 251 sAnimSVGViewBoxTearoffTable.AddTearoff(this, domAnimVal); 252 } 253 254 return domAnimVal.forget(); 255 } 256 257 UniquePtr<SMILAttr> SVGAnimatedViewBox::ToSMILAttr(SVGElement* aSVGElement) { 258 return MakeUnique<SMILViewBox>(this, aSVGElement); 259 } 260 261 nsresult SVGAnimatedViewBox::SMILViewBox ::ValueFromString( 262 const nsAString& aStr, const SVGAnimationElement* /*aSrcElement*/, 263 SMILValue& aValue, bool& aPreventCachingOfSandwich) const { 264 SVGViewBox viewBox; 265 nsresult res = SVGViewBox::FromString(aStr, &viewBox); 266 if (NS_FAILED(res)) { 267 return res; 268 } 269 SMILValue val(&SVGViewBoxSMILType::sSingleton); 270 *static_cast<SVGViewBox*>(val.mU.mPtr) = viewBox; 271 aValue = std::move(val); 272 273 return NS_OK; 274 } 275 276 SMILValue SVGAnimatedViewBox::SMILViewBox::GetBaseValue() const { 277 SMILValue val(&SVGViewBoxSMILType::sSingleton); 278 *static_cast<SVGViewBox*>(val.mU.mPtr) = mVal->mBaseVal; 279 return val; 280 } 281 282 void SVGAnimatedViewBox::SMILViewBox::ClearAnimValue() { 283 if (mVal->mAnimVal) { 284 mVal->mAnimVal = nullptr; 285 mSVGElement->DidAnimateViewBox(); 286 } 287 } 288 289 nsresult SVGAnimatedViewBox::SMILViewBox::SetAnimValue( 290 const SMILValue& aValue) { 291 NS_ASSERTION(aValue.mType == &SVGViewBoxSMILType::sSingleton, 292 "Unexpected type to assign animated value"); 293 if (aValue.mType == &SVGViewBoxSMILType::sSingleton) { 294 SVGViewBox& vb = *static_cast<SVGViewBox*>(aValue.mU.mPtr); 295 mVal->SetAnimValue(vb, mSVGElement); 296 } 297 return NS_OK; 298 } 299 300 } // namespace mozilla