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