1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is Mozilla SVG Project code.
16 : *
17 : * The Initial Developer of the Original Code is the Mozilla Foundation.
18 : * Portions created by the Initial Developer are Copyright (C) 2011
19 : * the Initial Developer. All Rights Reserved.
20 : *
21 : * Contributor(s):
22 : *
23 : * Alternatively, the contents of this file may be used under the terms of
24 : * either the GNU General Public License Version 2 or later (the "GPL"), or
25 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
26 : * in which case the provisions of the GPL or the LGPL are applicable instead
27 : * of those above. If you wish to allow use of your version of this file only
28 : * under the terms of either the GPL or the LGPL, and not to allow others to
29 : * use your version of this file under the terms of the MPL, indicate your
30 : * decision by deleting the provisions above and replace them with the notice
31 : * and other provisions required by the GPL or the LGPL. If you do not delete
32 : * the provisions above, a recipient may use your version of this file under
33 : * the terms of any one of the MPL, the GPL or the LGPL.
34 : *
35 : * ***** END LICENSE BLOCK ***** */
36 :
37 : #include "SVGAnimatedTransformList.h"
38 : #include "DOMSVGAnimatedTransformList.h"
39 :
40 : #include "nsSMILValue.h"
41 : #include "SVGTransform.h"
42 : #include "SVGTransformListSMILType.h"
43 : #include "nsSVGUtils.h"
44 : #include "prdtoa.h"
45 :
46 : namespace mozilla {
47 :
48 : nsresult
49 0 : SVGAnimatedTransformList::SetBaseValueString(const nsAString& aValue)
50 : {
51 0 : SVGTransformList newBaseValue;
52 0 : nsresult rv = newBaseValue.SetValueFromString(aValue);
53 0 : if (NS_FAILED(rv)) {
54 0 : return rv;
55 : }
56 :
57 : DOMSVGAnimatedTransformList *domWrapper =
58 0 : DOMSVGAnimatedTransformList::GetDOMWrapperIfExists(this);
59 0 : if (domWrapper) {
60 : // We must send this notification *before* changing mBaseVal! If the length
61 : // of our baseVal is being reduced, our baseVal's DOM wrapper list may have
62 : // to remove DOM items from itself, and any removed DOM items need to copy
63 : // their internal counterpart values *before* we change them.
64 : //
65 0 : domWrapper->InternalBaseValListWillChangeLengthTo(newBaseValue.Length());
66 : }
67 :
68 : // We don't need to call DidChange* here - we're only called by
69 : // nsSVGElement::ParseAttribute under nsGenericElement::SetAttr,
70 : // which takes care of notifying.
71 :
72 0 : rv = mBaseVal.CopyFrom(newBaseValue);
73 0 : if (NS_FAILED(rv) && domWrapper) {
74 : // Attempting to increase mBaseVal's length failed - reduce domWrapper
75 : // back to the same length:
76 0 : domWrapper->InternalBaseValListWillChangeLengthTo(mBaseVal.Length());
77 : } else {
78 0 : mIsAttrSet = true;
79 : }
80 0 : return rv;
81 : }
82 :
83 : void
84 0 : SVGAnimatedTransformList::ClearBaseValue()
85 : {
86 : DOMSVGAnimatedTransformList *domWrapper =
87 0 : DOMSVGAnimatedTransformList::GetDOMWrapperIfExists(this);
88 0 : if (domWrapper) {
89 : // We must send this notification *before* changing mBaseVal! (See above.)
90 0 : domWrapper->InternalBaseValListWillChangeLengthTo(0);
91 : }
92 0 : mBaseVal.Clear();
93 0 : mIsAttrSet = false;
94 : // Caller notifies
95 0 : }
96 :
97 : nsresult
98 0 : SVGAnimatedTransformList::SetAnimValue(const SVGTransformList& aValue,
99 : nsSVGElement *aElement)
100 : {
101 : DOMSVGAnimatedTransformList *domWrapper =
102 0 : DOMSVGAnimatedTransformList::GetDOMWrapperIfExists(this);
103 0 : if (domWrapper) {
104 : // A new animation may totally change the number of items in the animVal
105 : // list, replacing what was essentially a mirror of the baseVal list, or
106 : // else replacing and overriding an existing animation. When this happens
107 : // we must try and keep our animVal's DOM wrapper in sync (see the comment
108 : // in DOMSVGAnimatedTransformList::InternalBaseValListWillChangeLengthTo).
109 : //
110 : // It's not possible for us to reliably distinguish between calls to this
111 : // method that are setting a new sample for an existing animation, and
112 : // calls that are setting the first sample of an animation that will
113 : // override an existing animation. Happily it's cheap to just blindly
114 : // notify our animVal's DOM wrapper of its internal counterpart's new value
115 : // each time this method is called, so that's what we do.
116 : //
117 : // Note that we must send this notification *before* setting or changing
118 : // mAnimVal! (See the comment in SetBaseValueString above.)
119 : //
120 0 : domWrapper->InternalAnimValListWillChangeLengthTo(aValue.Length());
121 : }
122 0 : if (!mAnimVal) {
123 0 : mAnimVal = new SVGTransformList();
124 : }
125 0 : nsresult rv = mAnimVal->CopyFrom(aValue);
126 0 : if (NS_FAILED(rv)) {
127 : // OOM. We clear the animation, and, importantly, ClearAnimValue() ensures
128 : // that mAnimVal and its DOM wrapper (if any) will have the same length!
129 0 : ClearAnimValue(aElement);
130 0 : return rv;
131 : }
132 0 : aElement->DidAnimateTransformList();
133 0 : return NS_OK;
134 : }
135 :
136 : void
137 0 : SVGAnimatedTransformList::ClearAnimValue(nsSVGElement *aElement)
138 : {
139 : DOMSVGAnimatedTransformList *domWrapper =
140 0 : DOMSVGAnimatedTransformList::GetDOMWrapperIfExists(this);
141 0 : if (domWrapper) {
142 : // When all animation ends, animVal simply mirrors baseVal, which may have
143 : // a different number of items to the last active animated value. We must
144 : // keep the length of our animVal's DOM wrapper list in sync, and again we
145 : // must do that before touching mAnimVal. See comments above.
146 : //
147 0 : domWrapper->InternalAnimValListWillChangeLengthTo(mBaseVal.Length());
148 : }
149 0 : mAnimVal = nsnull;
150 0 : aElement->DidAnimateTransformList();
151 0 : }
152 :
153 : bool
154 0 : SVGAnimatedTransformList::IsExplicitlySet() const
155 : {
156 : // Like other methods of this name, we need to know when a transform value has
157 : // been explicitly set.
158 : //
159 : // There are three ways an animated list can become set:
160 : // 1) Markup -- we set mIsAttrSet to true on any successful call to
161 : // SetBaseValueString and clear it on ClearBaseValue (as called by
162 : // nsSVGElement::UnsetAttr or a failed nsSVGElement::ParseAttribute)
163 : // 2) DOM call -- simply fetching the baseVal doesn't mean the transform value
164 : // has been set. It is set if that baseVal has one or more transforms in
165 : // the list.
166 : // 3) Animation -- which will cause the mAnimVal member to be allocated
167 0 : return mIsAttrSet || !mBaseVal.IsEmpty() || mAnimVal;
168 : }
169 :
170 : nsISMILAttr*
171 0 : SVGAnimatedTransformList::ToSMILAttr(nsSVGElement* aSVGElement)
172 : {
173 0 : return new SMILAnimatedTransformList(this, aSVGElement);
174 : }
175 :
176 : nsresult
177 0 : SVGAnimatedTransformList::SMILAnimatedTransformList::ValueFromString(
178 : const nsAString& aStr,
179 : const nsISMILAnimationElement* aSrcElement,
180 : nsSMILValue& aValue,
181 : bool& aPreventCachingOfSandwich) const
182 : {
183 0 : NS_ENSURE_TRUE(aSrcElement, NS_ERROR_FAILURE);
184 0 : NS_ABORT_IF_FALSE(aValue.IsNull(),
185 : "aValue should have been cleared before calling ValueFromString");
186 :
187 0 : const nsAttrValue* typeAttr = aSrcElement->GetAnimAttr(nsGkAtoms::type);
188 0 : const nsIAtom* transformType = nsGkAtoms::translate; // default val
189 0 : if (typeAttr) {
190 0 : if (typeAttr->Type() != nsAttrValue::eAtom) {
191 : // Recognized values of |type| are parsed as an atom -- so if we have
192 : // something other than an atom, then we know already our |type| is
193 : // invalid.
194 0 : return NS_ERROR_FAILURE;
195 : }
196 0 : transformType = typeAttr->GetAtomValue();
197 : }
198 :
199 0 : ParseValue(aStr, transformType, aValue);
200 0 : aPreventCachingOfSandwich = false;
201 0 : return aValue.IsNull() ? NS_ERROR_FAILURE : NS_OK;
202 : }
203 :
204 : void
205 0 : SVGAnimatedTransformList::SMILAnimatedTransformList::ParseValue(
206 : const nsAString& aSpec,
207 : const nsIAtom* aTransformType,
208 : nsSMILValue& aResult)
209 : {
210 0 : NS_ABORT_IF_FALSE(aResult.IsNull(), "Unexpected type for SMIL value");
211 :
212 : // nsSVGSMILTransform constructor should be expecting array with 3 params
213 : PR_STATIC_ASSERT(SVGTransformSMILData::NUM_SIMPLE_PARAMS == 3);
214 :
215 0 : float params[3] = { 0.f };
216 0 : PRInt32 numParsed = ParseParameterList(aSpec, params, 3);
217 : PRUint16 transformType;
218 :
219 0 : if (aTransformType == nsGkAtoms::translate) {
220 : // tx [ty=0]
221 0 : if (numParsed != 1 && numParsed != 2)
222 0 : return;
223 0 : transformType = nsIDOMSVGTransform::SVG_TRANSFORM_TRANSLATE;
224 0 : } else if (aTransformType == nsGkAtoms::scale) {
225 : // sx [sy=sx]
226 0 : if (numParsed != 1 && numParsed != 2)
227 0 : return;
228 0 : if (numParsed == 1) {
229 0 : params[1] = params[0];
230 : }
231 0 : transformType = nsIDOMSVGTransform::SVG_TRANSFORM_SCALE;
232 0 : } else if (aTransformType == nsGkAtoms::rotate) {
233 : // r [cx=0 cy=0]
234 0 : if (numParsed != 1 && numParsed != 3)
235 0 : return;
236 0 : transformType = nsIDOMSVGTransform::SVG_TRANSFORM_ROTATE;
237 0 : } else if (aTransformType == nsGkAtoms::skewX) {
238 : // x-angle
239 0 : if (numParsed != 1)
240 0 : return;
241 0 : transformType = nsIDOMSVGTransform::SVG_TRANSFORM_SKEWX;
242 0 : } else if (aTransformType == nsGkAtoms::skewY) {
243 : // y-angle
244 0 : if (numParsed != 1)
245 0 : return;
246 0 : transformType = nsIDOMSVGTransform::SVG_TRANSFORM_SKEWY;
247 : } else {
248 0 : return;
249 : }
250 :
251 0 : nsSMILValue val(&SVGTransformListSMILType::sSingleton);
252 0 : SVGTransformSMILData transform(transformType, params);
253 0 : if (NS_FAILED(SVGTransformListSMILType::AppendTransform(transform, val))) {
254 : return; // OOM
255 : }
256 :
257 : // Success! Populate our outparam with parsed value.
258 0 : aResult.Swap(val);
259 : }
260 :
261 : namespace
262 : {
263 : inline void
264 0 : SkipWsp(nsACString::const_iterator& aIter,
265 : const nsACString::const_iterator& aIterEnd)
266 : {
267 0 : while (aIter != aIterEnd && IsSVGWhitespace(*aIter))
268 0 : ++aIter;
269 0 : }
270 : } // end anonymous namespace block
271 :
272 : PRInt32
273 0 : SVGAnimatedTransformList::SMILAnimatedTransformList::ParseParameterList(
274 : const nsAString& aSpec,
275 : float* aVars,
276 : PRInt32 aNVars)
277 : {
278 0 : NS_ConvertUTF16toUTF8 spec(aSpec);
279 :
280 0 : nsACString::const_iterator start, end;
281 0 : spec.BeginReading(start);
282 0 : spec.EndReading(end);
283 :
284 0 : SkipWsp(start, end);
285 :
286 0 : int numArgsFound = 0;
287 :
288 0 : while (start != end) {
289 0 : char const *arg = start.get();
290 : char *argend;
291 0 : float f = float(PR_strtod(arg, &argend));
292 0 : if (arg == argend || argend > end.get() || !NS_finite(f))
293 0 : return -1;
294 :
295 0 : if (numArgsFound < aNVars) {
296 0 : aVars[numArgsFound] = f;
297 : }
298 :
299 0 : start.advance(argend - arg);
300 0 : numArgsFound++;
301 :
302 0 : SkipWsp(start, end);
303 0 : if (*start == ',') {
304 0 : ++start;
305 0 : SkipWsp(start, end);
306 : }
307 : }
308 :
309 0 : return numArgsFound;
310 : }
311 :
312 : nsSMILValue
313 0 : SVGAnimatedTransformList::SMILAnimatedTransformList::GetBaseValue() const
314 : {
315 : // To benefit from Return Value Optimization and avoid copy constructor calls
316 : // due to our use of return-by-value, we must return the exact same object
317 : // from ALL return points. This function must only return THIS variable:
318 0 : nsSMILValue val(&SVGTransformListSMILType::sSingleton);
319 0 : if (!SVGTransformListSMILType::AppendTransforms(mVal->mBaseVal, val)) {
320 0 : val = nsSMILValue();
321 : }
322 :
323 : return val;
324 : }
325 :
326 : nsresult
327 0 : SVGAnimatedTransformList::SMILAnimatedTransformList::SetAnimValue(
328 : const nsSMILValue& aNewAnimValue)
329 : {
330 0 : NS_ABORT_IF_FALSE(
331 : aNewAnimValue.mType == &SVGTransformListSMILType::sSingleton,
332 : "Unexpected type to assign animated value");
333 0 : SVGTransformList animVal;
334 0 : if (!SVGTransformListSMILType::GetTransforms(aNewAnimValue,
335 0 : animVal.mItems)) {
336 0 : return NS_ERROR_FAILURE;
337 : }
338 :
339 0 : return mVal->SetAnimValue(animVal, mElement);
340 : }
341 :
342 : void
343 0 : SVGAnimatedTransformList::SMILAnimatedTransformList::ClearAnimValue()
344 : {
345 0 : if (mVal->mAnimVal) {
346 0 : mVal->ClearAnimValue(mElement);
347 : }
348 0 : }
349 :
350 : } // namespace mozilla
|