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