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 "SVGAnimatedViewBox.h"
9 #include "mozAutoDocUpdate.h"
10 #include "mozilla/Maybe.h"
13 #include "SVGViewBoxSMILType.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"
20 using namespace mozilla::dom
;
24 #define NUM_VIEWBOX_COMPONENTS 4
26 /* Implementation of SVGViewBox methods */
28 bool SVGViewBox::operator==(const SVGViewBox
& aOther
) const {
29 if (&aOther
== this) return true;
31 return (none
&& aOther
.none
) ||
32 (!none
&& !aOther
.none
&& x
== aOther
.x
&& y
== aOther
.y
&&
33 width
== aOther
.width
&& height
== aOther
.height
);
37 nsresult
SVGViewBox::FromString(const nsAString
& aStr
, SVGViewBox
* aViewBox
) {
38 if (aStr
.EqualsLiteral("none")) {
39 aViewBox
->none
= true;
43 nsCharSeparatedTokenizerTemplate
<nsContentUtils::IsHTMLWhitespace
,
44 nsTokenizerFlags::SeparatorOptional
>
46 float vals
[NUM_VIEWBOX_COMPONENTS
];
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
;
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
;
60 aViewBox
->x
= vals
[0];
61 aViewBox
->y
= vals
[1];
62 aViewBox
->width
= vals
[2];
63 aViewBox
->height
= vals
[3];
64 aViewBox
->none
= false;
69 MOZ_CONSTINIT
static SVGAttrTearoffTable
<SVGAnimatedViewBox
, SVGRect
>
70 sBaseSVGViewBoxTearoffTable
;
71 MOZ_CONSTINIT
static SVGAttrTearoffTable
<SVGAnimatedViewBox
, SVGRect
>
72 sAnimSVGViewBoxTearoffTable
;
73 MOZ_CONSTINIT SVGAttrTearoffTable
<SVGAnimatedViewBox
, SVGAnimatedRect
>
74 SVGAnimatedViewBox::sSVGAnimatedRectTearoffTable
;
76 //----------------------------------------------------------------------
77 // Helper class: AutoChangeViewBoxNotifier
78 // Stack-based helper class to pair calls to WillChangeViewBox and
80 class MOZ_RAII AutoChangeViewBoxNotifier
{
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");
89 mUpdateBatch
.emplace(aSVGElement
->GetComposedDoc(), true);
90 mEmptyOrOldValue
= mSVGElement
->WillChangeViewBox(mUpdateBatch
.ref());
94 ~AutoChangeViewBoxNotifier() {
96 mSVGElement
->DidChangeViewBox(mEmptyOrOldValue
, mUpdateBatch
.ref());
98 if (mViewBox
->mAnimVal
) {
99 mSVGElement
->AnimationNeedsResample();
104 SVGAnimatedViewBox
* const mViewBox
;
105 SVGElement
* const mSVGElement
;
106 Maybe
<mozAutoDocUpdate
> mUpdateBatch
;
107 nsAttrValue mEmptyOrOldValue
;
111 /* Implementation of SVGAnimatedViewBox methods */
113 void SVGAnimatedViewBox::Init() {
115 // We shouldn't use mBaseVal for rendering (its usages should be guarded with
116 // "mHasBaseVal" checks), but just in case we do by accident, this will
117 // ensure that we treat it as "none" and ignore its numeric values:
118 mBaseVal
.none
= true;
123 bool SVGAnimatedViewBox::HasRect() const {
124 // Check mAnimVal if we have one; otherwise, check mBaseVal if we have one;
125 // otherwise, just return false (we clearly do not have a rect).
126 const SVGViewBox
* rect
= mAnimVal
.get();
129 // no anim val, no base val --> no viewbox rect
135 return !rect
->none
&& rect
->width
>= 0 && rect
->height
>= 0;
138 void SVGAnimatedViewBox::SetAnimValue(const SVGViewBox
& aRect
,
139 SVGElement
* aSVGElement
) {
141 // it's okay if allocation fails - and no point in reporting that
142 mAnimVal
= MakeUnique
<SVGViewBox
>(aRect
);
144 if (aRect
== *mAnimVal
) {
149 aSVGElement
->DidAnimateViewBox();
152 void SVGAnimatedViewBox::SetBaseValue(const SVGViewBox
& aRect
,
153 SVGElement
* aSVGElement
) {
154 if (!mHasBaseVal
|| mBaseVal
== aRect
) {
155 // This method is used to set a single x, y, width
156 // or height value. It can't create a base value
157 // as the other components may be undefined. We record
158 // the new value though, so as not to lose data.
163 AutoChangeViewBoxNotifier
notifier(this, aSVGElement
);
169 nsresult
SVGAnimatedViewBox::SetBaseValueString(const nsAString
& aValue
,
170 SVGElement
* aSVGElement
,
174 nsresult rv
= SVGViewBox::FromString(aValue
, &viewBox
);
178 // Comparison against mBaseVal is only valid if we currently have a base val.
179 if (mHasBaseVal
&& viewBox
== mBaseVal
) {
183 AutoChangeViewBoxNotifier
notifier(this, aSVGElement
, aDoSetAttr
);
190 void SVGAnimatedViewBox::GetBaseValueString(nsAString
& aValue
) const {
192 aValue
.AssignLiteral("none");
195 nsTextFormatter::ssprintf(aValue
, u
"%g %g %g %g", (double)mBaseVal
.x
,
196 (double)mBaseVal
.y
, (double)mBaseVal
.width
,
197 (double)mBaseVal
.height
);
200 already_AddRefed
<SVGAnimatedRect
> SVGAnimatedViewBox::ToSVGAnimatedRect(
201 SVGElement
* aSVGElement
) {
202 RefPtr
<SVGAnimatedRect
> domAnimatedRect
=
203 sSVGAnimatedRectTearoffTable
.GetTearoff(this);
204 if (!domAnimatedRect
) {
205 domAnimatedRect
= new SVGAnimatedRect(this, aSVGElement
);
206 sSVGAnimatedRectTearoffTable
.AddTearoff(this, domAnimatedRect
);
209 return domAnimatedRect
.forget();
212 already_AddRefed
<SVGRect
> SVGAnimatedViewBox::ToDOMBaseVal(
213 SVGElement
* aSVGElement
) {
214 if (!mHasBaseVal
|| mBaseVal
.none
) {
218 RefPtr
<SVGRect
> domBaseVal
= sBaseSVGViewBoxTearoffTable
.GetTearoff(this);
220 domBaseVal
= new SVGRect(this, aSVGElement
, SVGRect::RectType::BaseValue
);
221 sBaseSVGViewBoxTearoffTable
.AddTearoff(this, domBaseVal
);
224 return domBaseVal
.forget();
227 SVGRect::~SVGRect() {
229 case RectType::BaseValue
:
230 sBaseSVGViewBoxTearoffTable
.RemoveTearoff(mVal
);
232 case RectType::AnimValue
:
233 sAnimSVGViewBoxTearoffTable
.RemoveTearoff(mVal
);
240 already_AddRefed
<SVGRect
> SVGAnimatedViewBox::ToDOMAnimVal(
241 SVGElement
* aSVGElement
) {
242 if ((mAnimVal
&& mAnimVal
->none
) ||
243 (!mAnimVal
&& (!mHasBaseVal
|| mBaseVal
.none
))) {
247 RefPtr
<SVGRect
> domAnimVal
= sAnimSVGViewBoxTearoffTable
.GetTearoff(this);
249 domAnimVal
= new SVGRect(this, aSVGElement
, SVGRect::RectType::AnimValue
);
250 sAnimSVGViewBoxTearoffTable
.AddTearoff(this, domAnimVal
);
253 return domAnimVal
.forget();
256 UniquePtr
<SMILAttr
> SVGAnimatedViewBox::ToSMILAttr(SVGElement
* aSVGElement
) {
257 return MakeUnique
<SMILViewBox
>(this, aSVGElement
);
260 nsresult
SVGAnimatedViewBox::SMILViewBox ::ValueFromString(
261 const nsAString
& aStr
, const SVGAnimationElement
* /*aSrcElement*/,
262 SMILValue
& aValue
, bool& aPreventCachingOfSandwich
) const {
264 nsresult res
= SVGViewBox::FromString(aStr
, &viewBox
);
265 if (NS_FAILED(res
)) {
268 SMILValue
val(&SVGViewBoxSMILType::sSingleton
);
269 *static_cast<SVGViewBox
*>(val
.mU
.mPtr
) = viewBox
;
270 aValue
= std::move(val
);
275 SMILValue
SVGAnimatedViewBox::SMILViewBox::GetBaseValue() const {
276 SMILValue
val(&SVGViewBoxSMILType::sSingleton
);
277 *static_cast<SVGViewBox
*>(val
.mU
.mPtr
) = mVal
->mBaseVal
;
281 void SVGAnimatedViewBox::SMILViewBox::ClearAnimValue() {
282 if (mVal
->mAnimVal
) {
283 mVal
->mAnimVal
= nullptr;
284 mSVGElement
->DidAnimateViewBox();
288 nsresult
SVGAnimatedViewBox::SMILViewBox::SetAnimValue(
289 const SMILValue
& aValue
) {
290 NS_ASSERTION(aValue
.mType
== &SVGViewBoxSMILType::sSingleton
,
291 "Unexpected type to assign animated value");
292 if (aValue
.mType
== &SVGViewBoxSMILType::sSingleton
) {
293 SVGViewBox
& vb
= *static_cast<SVGViewBox
*>(aValue
.mU
.mPtr
);
294 mVal
->SetAnimValue(vb
, mSVGElement
);
299 } // namespace mozilla