1/*
2 * Copyright (C) 2004, 2005 Nikolas Zimmermann <zimmermann@kde.org>
3 * Copyright (C) 2004, 2005, 2006, 2007 Rob Buis <buis@kde.org>
4 * Copyright (C) 2007 Eric Seidel <eric@webkit.org>
5 * Copyright (C) 2008 Apple Inc. All rights reserved.
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., 51 Franklin Street, Fifth Floor,
20 * Boston, MA 02110-1301, USA.
21 */
22
23#include "config.h"
24
25#if ENABLE(SVG) && ENABLE(SVG_ANIMATION)
26#include "SVGAnimateTransformElement.h"
27
28#include "AffineTransform.h"
29#include "Attribute.h"
30#include "RenderObject.h"
31#include "RenderSVGResource.h"
32#include "SVGAngle.h"
33#include "SVGElementInstance.h"
34#include "SVGGradientElement.h"
35#include "SVGNames.h"
36#include "SVGParserUtilities.h"
37#include "SVGSVGElement.h"
38#include "SVGStyledTransformableElement.h"
39#include "SVGTextElement.h"
40#include "SVGTransform.h"
41#include "SVGTransformList.h"
42#include "SVGUseElement.h"
43#include <math.h>
44#include <wtf/MathExtras.h>
45
46using namespace std;
47
48namespace WebCore {
49
50inline SVGAnimateTransformElement::SVGAnimateTransformElement(const QualifiedName& tagName, Document* document)
51    : SVGAnimationElement(tagName, document)
52    , m_type(SVGTransform::SVG_TRANSFORM_UNKNOWN)
53    , m_baseIndexInTransformList(0)
54{
55}
56
57PassRefPtr<SVGAnimateTransformElement> SVGAnimateTransformElement::create(const QualifiedName& tagName, Document* document)
58{
59    return adoptRef(new SVGAnimateTransformElement(tagName, document));
60}
61
62bool SVGAnimateTransformElement::hasValidAttributeType() const
63{
64    SVGElement* targetElement = this->targetElement();
65    if (!targetElement)
66        return false;
67
68    return determineAnimatedAttributeType(targetElement) == AnimatedTransformList;
69}
70
71AnimatedAttributeType SVGAnimateTransformElement::determineAnimatedAttributeType(SVGElement* targetElement) const
72{
73    ASSERT(targetElement);
74
75    // Just transform lists can be animated with <animateTransform>
76    // http://www.w3.org/TR/SVG/animate.html#AnimationAttributesAndProperties
77    if (targetElement->animatedPropertyTypeForAttribute(attributeName()) != AnimatedTransformList)
78        return AnimatedUnknown;
79
80    return AnimatedTransformList;
81}
82
83void SVGAnimateTransformElement::parseMappedAttribute(Attribute* attr)
84{
85    if (attr->name() == SVGNames::typeAttr) {
86        if (attr->value() == "translate")
87            m_type = SVGTransform::SVG_TRANSFORM_TRANSLATE;
88        else if (attr->value() == "scale")
89            m_type = SVGTransform::SVG_TRANSFORM_SCALE;
90        else if (attr->value() == "rotate")
91            m_type = SVGTransform::SVG_TRANSFORM_ROTATE;
92        else if (attr->value() == "skewX")
93            m_type = SVGTransform::SVG_TRANSFORM_SKEWX;
94        else if (attr->value() == "skewY")
95            m_type = SVGTransform::SVG_TRANSFORM_SKEWY;
96    } else
97        SVGAnimationElement::parseMappedAttribute(attr);
98}
99
100
101static SVGTransformList* transformListFor(SVGElement* element)
102{
103    ASSERT(element);
104    if (element->isStyledTransformable())
105        return &static_cast<SVGStyledTransformableElement*>(element)->transform();
106    if (element->hasTagName(SVGNames::textTag))
107        return &static_cast<SVGTextElement*>(element)->transform();
108    if (element->hasTagName(SVGNames::linearGradientTag) || element->hasTagName(SVGNames::radialGradientTag))
109        return &static_cast<SVGGradientElement*>(element)->gradientTransform();
110    // FIXME: Handle patternTransform, which is obviously missing!
111    return 0;
112}
113
114void SVGAnimateTransformElement::resetToBaseValue(const String& baseValue)
115{
116    SVGElement* targetElement = this->targetElement();
117    if (!targetElement || determineAnimatedAttributeType(targetElement) == AnimatedUnknown)
118        return;
119
120    if (targetElement->hasTagName(SVGNames::linearGradientTag) || targetElement->hasTagName(SVGNames::radialGradientTag)) {
121        targetElement->setAttribute(SVGNames::gradientTransformAttr, baseValue.isEmpty() ? "matrix(1 0 0 1 0 0)" : baseValue);
122        return;
123    }
124
125    if (baseValue.isEmpty()) {
126        if (SVGTransformList* list = transformListFor(targetElement))
127            list->clear();
128    } else
129        targetElement->setAttribute(SVGNames::transformAttr, baseValue);
130}
131
132void SVGAnimateTransformElement::calculateAnimatedValue(float percentage, unsigned repeat, SVGSMILElement*)
133{
134    SVGElement* targetElement = this->targetElement();
135    if (!targetElement || determineAnimatedAttributeType(targetElement) == AnimatedUnknown)
136        return;
137    SVGTransformList* transformList = transformListFor(targetElement);
138    ASSERT(transformList);
139
140    if (!isAdditive())
141        transformList->clear();
142    if (isAccumulated() && repeat) {
143        SVGTransform accumulatedTransform = SVGTransformDistance(m_fromTransform, m_toTransform).scaledDistance(repeat).addToSVGTransform(SVGTransform());
144        transformList->append(accumulatedTransform);
145    }
146    SVGTransform transform = SVGTransformDistance(m_fromTransform, m_toTransform).scaledDistance(percentage).addToSVGTransform(m_fromTransform);
147    transformList->append(transform);
148}
149
150bool SVGAnimateTransformElement::calculateFromAndToValues(const String& fromString, const String& toString)
151{
152    m_fromTransform = parseTransformValue(fromString);
153    if (!m_fromTransform.isValid())
154        return false;
155    m_toTransform = parseTransformValue(toString);
156    return m_toTransform.isValid();
157}
158
159bool SVGAnimateTransformElement::calculateFromAndByValues(const String& fromString, const String& byString)
160{
161    m_fromTransform = parseTransformValue(fromString);
162    if (!m_fromTransform.isValid())
163        return false;
164    m_toTransform = SVGTransformDistance::addSVGTransforms(m_fromTransform, parseTransformValue(byString));
165    return m_toTransform.isValid();
166}
167
168SVGTransform SVGAnimateTransformElement::parseTransformValue(const String& value) const
169{
170    if (value.isEmpty())
171        return SVGTransform(m_type);
172    SVGTransform result;
173    // FIXME: This is pretty dumb but parseTransformValue() wants those parenthesis.
174    String parseString("(" + value + ")");
175    const UChar* ptr = parseString.characters();
176    SVGTransformable::parseTransformValue(m_type, ptr, ptr + parseString.length(), result); // ignoring return value
177    return result;
178}
179
180void SVGAnimateTransformElement::applyResultsToTarget()
181{
182    SVGElement* targetElement = this->targetElement();
183    if (!targetElement || determineAnimatedAttributeType(targetElement) == AnimatedUnknown)
184        return;
185
186    // We accumulate to the target element transform list so there is not much to do here.
187    if (RenderObject* renderer = targetElement->renderer()) {
188        renderer->setNeedsTransformUpdate();
189        RenderSVGResource::markForLayoutAndParentResourceInvalidation(renderer);
190    }
191
192    // ...except in case where we have additional instances in <use> trees.
193    SVGTransformList* transformList = transformListFor(targetElement);
194    if (!transformList)
195        return;
196
197    const HashSet<SVGElementInstance*>& instances = targetElement->instancesForElement();
198    const HashSet<SVGElementInstance*>::const_iterator end = instances.end();
199    for (HashSet<SVGElementInstance*>::const_iterator it = instances.begin(); it != end; ++it) {
200        SVGElement* shadowTreeElement = (*it)->shadowTreeElement();
201        ASSERT(shadowTreeElement);
202        if (shadowTreeElement->isStyledTransformable())
203            static_cast<SVGStyledTransformableElement*>(shadowTreeElement)->setTransformBaseValue(*transformList);
204        else if (shadowTreeElement->hasTagName(SVGNames::textTag))
205            static_cast<SVGTextElement*>(shadowTreeElement)->setTransformBaseValue(*transformList);
206        else if (shadowTreeElement->hasTagName(SVGNames::linearGradientTag) || shadowTreeElement->hasTagName(SVGNames::radialGradientTag))
207            static_cast<SVGGradientElement*>(shadowTreeElement)->setGradientTransformBaseValue(*transformList);
208        // FIXME: Handle patternTransform, obviously missing!
209        if (RenderObject* renderer = shadowTreeElement->renderer()) {
210            renderer->setNeedsTransformUpdate();
211            RenderSVGResource::markForLayoutAndParentResourceInvalidation(renderer);
212        }
213    }
214}
215
216float SVGAnimateTransformElement::calculateDistance(const String& fromString, const String& toString)
217{
218    // FIXME: This is not correct in all cases. The spec demands that each component (translate x and y for example)
219    // is paced separately. To implement this we need to treat each component as individual animation everywhere.
220    SVGTransform from = parseTransformValue(fromString);
221    if (!from.isValid())
222        return -1;
223    SVGTransform to = parseTransformValue(toString);
224    if (!to.isValid() || from.type() != to.type())
225        return -1;
226    if (to.type() == SVGTransform::SVG_TRANSFORM_TRANSLATE) {
227        FloatSize diff = to.translate() - from.translate();
228        return sqrtf(diff.width() * diff.width() + diff.height() * diff.height());
229    }
230    if (to.type() == SVGTransform::SVG_TRANSFORM_ROTATE)
231        return fabsf(to.angle() - from.angle());
232    if (to.type() == SVGTransform::SVG_TRANSFORM_SCALE) {
233        FloatSize diff = to.scale() - from.scale();
234        return sqrtf(diff.width() * diff.width() + diff.height() * diff.height());
235    }
236    return -1;
237}
238
239}
240#endif // ENABLE(SVG)
241