Reviewed by Mitz.
[WebKit-https.git] / WebCore / ksvg2 / svg / SVGAnimateTransformElement.cpp
1 /*
2     Copyright (C) 2004, 2005 Nikolas Zimmermann <wildfox@kde.org>
3                   2004, 2005, 2006 Rob Buis <buis@kde.org>
4
5     This file is part of the KDE project
6
7     This library is free software; you can redistribute it and/or
8     modify it under the terms of the GNU Library General Public
9     License as published by the Free Software Foundation; either
10     version 2 of the License, or (at your option) any later version.
11
12     This library is distributed in the hope that it will be useful,
13     but WITHOUT ANY WARRANTY; without even the implied warranty of
14     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15     Library General Public License for more details.
16
17     You should have received a copy of the GNU Library General Public License
18     along with this library; see the file COPYING.LIB.  If not, write to
19     the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20     Boston, MA 02111-1307, USA.
21 */
22
23 #include "config.h"
24 #ifdef SVG_SUPPORT
25 #include "SVGAnimateTransformElement.h"
26
27 #include "KSVGTimeScheduler.h"
28 #include "SVGAngle.h"
29 #include "SVGMatrix.h"
30 #include "SVGStyledTransformableElement.h"
31 #include "SVGSVGElement.h"
32 #include "SVGTransform.h"
33 #include "SVGTransformList.h"
34 #include <math.h>
35
36 using namespace std;
37
38 namespace WebCore {
39
40 SVGAnimateTransformElement::SVGAnimateTransformElement(const QualifiedName& tagName, Document* doc)
41     : SVGAnimationElement(tagName, doc)
42     , m_currentItem(-1)
43     , m_type(SVGTransform::SVG_TRANSFORM_UNKNOWN)
44     , m_rotateSpecialCase(false)
45     , m_toRotateSpecialCase(false)
46     , m_fromRotateSpecialCase(false)
47 {
48 }
49
50 SVGAnimateTransformElement::~SVGAnimateTransformElement()
51 {
52 }
53
54 void SVGAnimateTransformElement::parseMappedAttribute(MappedAttribute* attr)
55 {
56     if (attr->name() == SVGNames::typeAttr) {
57         const String& value = attr->value();
58         if (value == "translate")
59             m_type = SVGTransform::SVG_TRANSFORM_TRANSLATE;
60         else if (value == "scale")
61             m_type = SVGTransform::SVG_TRANSFORM_SCALE;
62         else if (value == "rotate")
63             m_type = SVGTransform::SVG_TRANSFORM_ROTATE;
64         else if (value == "skewX")
65             m_type = SVGTransform::SVG_TRANSFORM_SKEWX;
66         else if (value == "skewY")
67             m_type = SVGTransform::SVG_TRANSFORM_SKEWY;
68     } else
69         SVGAnimationElement::parseMappedAttribute(attr);
70 }
71
72 void SVGAnimateTransformElement::handleTimerEvent(double timePercentage)
73 {
74     // Start condition.
75     if (!m_connected) {
76         m_initialTransform = 0;
77         
78         // Save initial transform... (needed for fill="remove" or additve="sum")
79         if (targetElement()->isStyledTransformable()) {
80             SVGStyledTransformableElement* transform = static_cast<SVGStyledTransformableElement*>(targetElement());
81             RefPtr<SVGTransformList> transformList = transform->transformBaseValue();
82             if (transformList) {
83                 ExceptionCode ec = 0;
84                 for (unsigned long i = 0; i < transformList->numberOfItems(); i++) {
85                     SVGTransform* value = transformList->getItem(i, ec).get();;
86                     if (!value)
87                         continue;
88                         
89                     if (value->type() == m_type) {
90                         m_initialTransform = value;
91                         break;
92                     }
93                 }
94             }
95         }
96                 
97         // Animation mode handling
98         switch (detectAnimationMode()) {
99             case TO_ANIMATION:
100             case FROM_TO_ANIMATION:
101             {        
102                 m_toTransform = parseTransformValue(m_to);
103                 m_toRotateSpecialCase = m_rotateSpecialCase;
104
105                 if (!m_from.isEmpty()) // from-to animation
106                 {
107                     m_fromTransform = parseTransformValue(m_from);
108                     m_fromRotateSpecialCase = m_rotateSpecialCase;
109                 }
110                 else // to animation
111                 {
112                     m_fromTransform = m_initialTransform;
113                     m_fromRotateSpecialCase = false;
114                 }
115
116                 if (!m_fromTransform)
117                     m_fromTransform = new SVGTransform();
118                 
119                 break;
120             }
121             case BY_ANIMATION:
122             case FROM_BY_ANIMATION:
123             {
124                 m_toTransform = parseTransformValue(m_by);
125                 m_toRotateSpecialCase = m_rotateSpecialCase;
126
127                 if (!m_from.isEmpty()) // from-by animation
128                 {
129                     m_fromTransform = parseTransformValue(m_from);
130                     m_fromRotateSpecialCase = m_rotateSpecialCase;
131                 }
132                 else // by animation
133                 {
134                     m_fromTransform = m_initialTransform;
135                     m_fromRotateSpecialCase = false;
136                 }
137
138                 if (!m_fromTransform)
139                     m_fromTransform = new SVGTransform();
140
141                 SVGMatrix* byMatrix = m_toTransform->matrix();
142                 SVGMatrix* fromMatrix = m_fromTransform->matrix();
143
144                 byMatrix->multiply(fromMatrix);
145
146                 break;
147             }
148             case VALUES_ANIMATION:
149                 break;
150             default:
151             {
152                 //kdError() << k_funcinfo << " Unable to detect animation mode! Aborting creation!" << endl;
153                 return;
154             }
155         }
156         
157         ownerSVGElement()->timeScheduler()->connectIntervalTimer(this);
158         m_connected = true;
159
160         return;
161     }
162
163     // Calculations...
164     if (timePercentage >= 1.0)
165         timePercentage = 1.0;
166     
167     AffineTransform qToMatrix, qFromMatrix;
168     double useTimePercentage = timePercentage;
169
170     if (m_values) {
171         int itemByPercentage = calculateCurrentValueItem(timePercentage);
172
173         if (itemByPercentage == -1)
174             return;
175         
176         if (m_currentItem != itemByPercentage) // Item changed...
177         {
178             ExceptionCode ec = 0;
179
180             // Extract current 'from' / 'to' values
181             String value1 = String(m_values->getItem(itemByPercentage, ec));
182             String value2 = String(m_values->getItem(itemByPercentage + 1, ec));
183
184             // Calculate new from/to transform values...
185             if (!value1.isEmpty() && !value2.isEmpty()) {
186                 bool apply = false;
187                 if (m_toTransform && m_fromTransform) {    
188                     qToMatrix = m_toTransform->matrix()->matrix();
189                     qFromMatrix = m_fromTransform->matrix()->matrix();
190                     
191                     apply = true;
192                     useTimePercentage = 1.0;
193                 }
194
195                 m_toTransform = parseTransformValue(value2);
196                 m_toRotateSpecialCase = m_rotateSpecialCase;
197     
198                 m_fromTransform = parseTransformValue(value1);
199                 m_fromRotateSpecialCase = m_rotateSpecialCase;
200
201                 m_currentItem = itemByPercentage;
202
203                 if (!apply)
204                     return;
205             }
206         }
207         else if (m_toTransform && m_fromTransform)
208             useTimePercentage = calculateRelativeTimePercentage(timePercentage, m_currentItem);
209     }
210
211     if (m_toTransform && m_toTransform->matrix() && qToMatrix.isIdentity())
212         qToMatrix = m_toTransform->matrix()->matrix();
213
214     if (m_fromTransform && m_fromTransform->matrix() && qFromMatrix.isIdentity())
215         qFromMatrix = m_fromTransform->matrix()->matrix();
216
217     if (!m_transformMatrix)
218         m_transformMatrix = new SVGMatrix();
219     else {
220         m_transformMatrix->reset();
221
222         if (isAccumulated() && repeations() != 0.0 && m_lastMatrix)
223             m_transformMatrix->multiply(m_lastMatrix.get());
224     }
225     
226     switch (m_type) {
227         case SVGTransform::SVG_TRANSFORM_TRANSLATE:
228         {
229             double dx = ((qToMatrix.dx() - qFromMatrix.dx()) * useTimePercentage) + qFromMatrix.dx();
230             double dy = ((qToMatrix.dy() - qFromMatrix.dy()) * useTimePercentage) + qFromMatrix.dy();
231
232             m_transformMatrix->translate(dx, dy);
233             break;
234         }
235         case SVGTransform::SVG_TRANSFORM_SCALE:
236         {
237             double sx = ((qToMatrix.m11() - qFromMatrix.m11()) * useTimePercentage) + qFromMatrix.m11();
238             double sy = ((qToMatrix.m22() - qFromMatrix.m22()) * useTimePercentage) + qFromMatrix.m22();
239
240             m_transformMatrix->scaleNonUniform(sx, sy);
241             break;
242         }
243         case SVGTransform::SVG_TRANSFORM_ROTATE:
244         {
245             double toAngle, toCx, toCy, fromAngle, fromCx, fromCy;
246             calculateRotationFromMatrix(qToMatrix, toAngle, toCx, toCy);
247             calculateRotationFromMatrix(qFromMatrix, fromAngle, fromCx, fromCy);
248
249             if (m_toRotateSpecialCase) {
250                 if (lround(toAngle) == 1)
251                     toAngle = 0.0;
252                 else
253                     toAngle = 360.0;
254             }
255
256             if (m_fromRotateSpecialCase) {
257                 if (lround(fromAngle) == 1)
258                     fromAngle = 0.0;
259                 else
260                     fromAngle = 360.0;
261             }
262                     
263             double angle = ((toAngle - fromAngle) * useTimePercentage) + fromAngle;
264             double cx = (toCx - fromCx) * useTimePercentage + fromCx;
265             double cy = (toCy - fromCy) * useTimePercentage + fromCy;
266
267             m_transformMatrix->translate(cx, cy);
268             m_transformMatrix->rotate(angle);
269             m_transformMatrix->translate(-cx, -cy);
270             break;
271         }
272         case SVGTransform::SVG_TRANSFORM_SKEWX:
273         {
274             double sx = (SVGAngle::todeg(atan(qToMatrix.m21()) - atan(qFromMatrix.m21())) *
275                          useTimePercentage) + SVGAngle::todeg(atan(qFromMatrix.m21()));
276
277             m_transformMatrix->skewX(sx);
278             break;
279         }
280         case SVGTransform::SVG_TRANSFORM_SKEWY:
281         {
282             double sy = (SVGAngle::todeg(atan(qToMatrix.m12()) - atan(qFromMatrix.m12())) *
283                          useTimePercentage) + SVGAngle::todeg(atan(qFromMatrix.m12()));
284
285             m_transformMatrix->skewY(sy);
286             break;
287         }
288         default:
289             break;
290     }
291
292     // End condition.
293     if (timePercentage == 1.0)
294     {
295         if ((m_repeatCount > 0 && m_repeations < m_repeatCount - 1) || isIndefinite(m_repeatCount))
296         {
297             m_lastMatrix = new SVGMatrix();
298             
299             if (m_transformMatrix)
300                 m_lastMatrix->copy(m_transformMatrix.get());
301
302             m_repeations++;
303             return;
304         }
305
306         ownerSVGElement()->timeScheduler()->disconnectIntervalTimer(this);
307         m_connected = false;
308
309         // Reset...
310         m_currentItem = -1;
311         m_toTransform = 0;
312         m_fromTransform = 0;
313         m_initialTransform = 0;
314
315         if (!isFrozen()) {
316             SVGMatrix* initial = initialMatrix();
317             if (initial)
318                 m_transformMatrix = initial;
319             else
320                 m_transformMatrix = new SVGMatrix();
321         }
322     }
323 }
324
325 RefPtr<SVGTransform> SVGAnimateTransformElement::parseTransformValue(const String& data) const
326 {
327     DeprecatedString parse = data.deprecatedString().stripWhiteSpace();
328     if (parse.isEmpty())
329         return 0;
330     
331     int commaPos = parse.find(','); // In case two values are passed...
332
333     RefPtr<SVGTransform> parsedTransform = new SVGTransform();
334     
335     switch (m_type) {
336         case SVGTransform::SVG_TRANSFORM_TRANSLATE:
337         {
338             double tx = 0.0, ty = 0.0;
339             if (commaPos != - 1) {
340                 tx = parse.mid(0, commaPos).toDouble();
341                 ty = parse.mid(commaPos + 1).toDouble();
342             } else 
343                 tx = parse.toDouble();
344
345             parsedTransform->setTranslate(tx, ty);
346             break;    
347         }
348         case SVGTransform::SVG_TRANSFORM_SCALE:
349         {
350             double sx = 1.0, sy = 1.0;
351             if (commaPos != - 1) {
352                 sx = parse.mid(0, commaPos).toDouble();
353                 sy = parse.mid(commaPos + 1).toDouble();
354             } else {
355                 sx = parse.toDouble();
356                 sy = sx;
357             }
358
359             parsedTransform->setScale(sx, sy);
360             break;
361         }
362         case SVGTransform::SVG_TRANSFORM_ROTATE:
363         {
364             double angle = 0, cx = 0, cy = 0;
365             if (commaPos != - 1) {
366                 angle = parse.mid(0, commaPos).toDouble(); // TODO: probably needs it's own 'angle' parser
367     
368                 int commaPosTwo = parse.find(',', commaPos + 1); // In case three values are passed...
369                 if (commaPosTwo != -1) {
370                     cx = parse.mid(commaPos + 1, commaPosTwo - commaPos - 1).toDouble();
371                     cy = parse.mid(commaPosTwo + 1).toDouble();
372                 }
373             }
374             else 
375                 angle = parse.toDouble();
376
377             // Ok now here's a hack to make it possible to calculate cx/cy values, if angle = 0 
378             // or angle=360 -> for angle=0 our matrix is m11: 1 m12: 0 m21: 0 m22: 1 dx: 0 dy: 0
379             // As you can see there is no way to retrieve the cx/cy values for these angles.
380             // -> set 'm_rotateSpecialCase' to true, and save angle = 1/359 -> this way we can calculate
381             //    the cx/cy values, while keeping this uber-optimized way of handling <animateTransform>!
382             m_rotateSpecialCase = false;
383             
384             if (angle == 0.0) {
385                 angle = angle + 1.0;
386                 m_rotateSpecialCase = true;
387             } else if (angle == 360.0) {
388                 angle = angle - 1.0;
389                 m_rotateSpecialCase = true;
390             }
391             
392             parsedTransform->setRotate(angle, cx, cy);
393             break;    
394         }
395         case SVGTransform::SVG_TRANSFORM_SKEWX:
396         case SVGTransform::SVG_TRANSFORM_SKEWY:
397         {
398             double angle = parse.toDouble(); // TODO: probably needs it's own 'angle' parser
399             
400             if (m_type == SVGTransform::SVG_TRANSFORM_SKEWX)
401                 parsedTransform->setSkewX(angle);
402             else
403                 parsedTransform->setSkewY(angle);
404
405             break;
406         }
407         default:
408             return 0;
409     }
410     
411     return parsedTransform;
412 }
413
414 void SVGAnimateTransformElement::calculateRotationFromMatrix(const AffineTransform& matrix, double& angle, double& cx, double& cy) const
415 {
416     double cosa = matrix.m11();
417     double sina = -matrix.m21();
418
419     if (cosa != 1.0)
420     {
421         // Calculate the angle via magic ;)
422         double temp = SVGAngle::todeg(asin(sina));
423         angle = SVGAngle::todeg(acos(cosa));
424         if (temp < 0)
425             angle = 360.0 - angle;
426         
427         double res = (1 - cosa) + ((sina * sina) / (1 - cosa));
428         
429         cx = (matrix.dx() - ((sina * matrix.dy()) / (1 - cosa))) / res;
430         cy = (matrix.dy() + ((sina * matrix.dx()) / (1 - cosa))) / res;
431
432         return;
433     }
434
435     cx = 0.0;
436     cy = 0.0;
437     angle = 0.0;
438 }
439
440 SVGMatrix* SVGAnimateTransformElement::initialMatrix() const
441 {
442     if (!targetElement()->isStyledTransformable())
443         return 0;
444     SVGStyledTransformableElement* transform = static_cast<SVGStyledTransformableElement*>(targetElement());
445     SVGTransformList* transformList = (transform ? transform->transformBaseValue() : 0);
446     if (!transformList)
447         return 0;
448     
449     RefPtr<SVGTransform> result = transformList->concatenate();
450     if (!result)
451         return 0;
452
453     SVGMatrix* ret = result->matrix();
454     ret->ref();
455
456     return ret;
457 }
458
459 SVGMatrix* SVGAnimateTransformElement::transformMatrix() const
460 {
461     return m_transformMatrix.get();
462 }
463
464 }
465
466 // vim:ts=4:noet
467 #endif // SVG_SUPPORT
468