1/*
2 * Copyright (C) 2007 Eric Seidel <eric@webkit.org>
3 * Copyright (C) 2007 Rob Buis <buis@kde.org>
4 * Copyright (C) 2008 Apple Inc. All rights reserved.
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 * Library General Public License for more details.
15 *
16 * You should have received a copy of the GNU Library General Public License
17 * along with this library; see the file COPYING.LIB.  If not, write to
18 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 * Boston, MA 02110-1301, USA.
20 */
21
22#include "config.h"
23
24#include "core/svg/SVGAnimateMotionElement.h"
25
26#include "core/SVGNames.h"
27#include "core/dom/ElementTraversal.h"
28#include "core/rendering/RenderObject.h"
29#include "core/rendering/svg/RenderSVGResource.h"
30#include "core/rendering/svg/SVGPathData.h"
31#include "core/svg/SVGMPathElement.h"
32#include "core/svg/SVGParserUtilities.h"
33#include "core/svg/SVGPathElement.h"
34#include "core/svg/SVGPathUtilities.h"
35#include "platform/transforms/AffineTransform.h"
36#include "wtf/MathExtras.h"
37#include "wtf/StdLibExtras.h"
38
39namespace blink {
40
41using namespace SVGNames;
42
43inline SVGAnimateMotionElement::SVGAnimateMotionElement(Document& document)
44    : SVGAnimationElement(animateMotionTag, document)
45    , m_hasToPointAtEndOfDuration(false)
46{
47    setCalcMode(CalcModePaced);
48}
49
50DEFINE_NODE_FACTORY(SVGAnimateMotionElement)
51
52SVGAnimateMotionElement::~SVGAnimateMotionElement()
53{
54}
55
56bool SVGAnimateMotionElement::hasValidAttributeType()
57{
58    SVGElement* targetElement = this->targetElement();
59    if (!targetElement)
60        return false;
61
62    // We don't have a special attribute name to verify the animation type. Check the element name instead.
63    if (!targetElement->isSVGGraphicsElement())
64        return false;
65    // Spec: SVG 1.1 section 19.2.15
66    // FIXME: svgTag is missing. Needs to be checked, if transforming <svg> could cause problems.
67    return (isSVGGElement(*targetElement)
68        || isSVGDefsElement(*targetElement)
69        || isSVGUseElement(*targetElement)
70        || isSVGImageElement(*targetElement)
71        || isSVGSwitchElement(*targetElement)
72        || isSVGPathElement(*targetElement)
73        || isSVGRectElement(*targetElement)
74        || isSVGCircleElement(*targetElement)
75        || isSVGEllipseElement(*targetElement)
76        || isSVGLineElement(*targetElement)
77        || isSVGPolylineElement(*targetElement)
78        || isSVGPolygonElement(*targetElement)
79        || isSVGTextElement(*targetElement)
80        || isSVGClipPathElement(*targetElement)
81        || isSVGMaskElement(*targetElement)
82        || isSVGAElement(*targetElement)
83        || isSVGForeignObjectElement(*targetElement)
84        );
85}
86
87bool SVGAnimateMotionElement::hasValidAttributeName()
88{
89    // AnimateMotion does not use attributeName so it is always valid.
90    return true;
91}
92
93void SVGAnimateMotionElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
94{
95    if (name == SVGNames::pathAttr) {
96        m_path = Path();
97        buildPathFromString(value, m_path);
98        updateAnimationPath();
99        return;
100    }
101
102    SVGAnimationElement::parseAttribute(name, value);
103}
104
105SVGAnimateMotionElement::RotateMode SVGAnimateMotionElement::rotateMode() const
106{
107    DEFINE_STATIC_LOCAL(const AtomicString, autoVal, ("auto", AtomicString::ConstructFromLiteral));
108    DEFINE_STATIC_LOCAL(const AtomicString, autoReverse, ("auto-reverse", AtomicString::ConstructFromLiteral));
109    const AtomicString& rotate = getAttribute(SVGNames::rotateAttr);
110    if (rotate == autoVal)
111        return RotateAuto;
112    if (rotate == autoReverse)
113        return RotateAutoReverse;
114    return RotateAngle;
115}
116
117void SVGAnimateMotionElement::updateAnimationPath()
118{
119    m_animationPath = Path();
120    bool foundMPath = false;
121
122    for (SVGMPathElement* mpath = Traversal<SVGMPathElement>::firstChild(*this); mpath; mpath = Traversal<SVGMPathElement>::nextSibling(*mpath)) {
123        if (SVGPathElement* pathElement = mpath->pathElement()) {
124            updatePathFromGraphicsElement(pathElement, m_animationPath);
125            foundMPath = true;
126            break;
127        }
128    }
129
130    if (!foundMPath && fastHasAttribute(SVGNames::pathAttr))
131        m_animationPath = m_path;
132
133    updateAnimationMode();
134}
135
136template<typename CharType>
137static bool parsePointInternal(const String& string, FloatPoint& point)
138{
139    const CharType* ptr = string.getCharacters<CharType>();
140    const CharType* end = ptr + string.length();
141
142    if (!skipOptionalSVGSpaces(ptr, end))
143        return false;
144
145    float x = 0;
146    if (!parseNumber(ptr, end, x))
147        return false;
148
149    float y = 0;
150    if (!parseNumber(ptr, end, y))
151        return false;
152
153    point = FloatPoint(x, y);
154
155    // disallow anything except spaces at the end
156    return !skipOptionalSVGSpaces(ptr, end);
157}
158
159static bool parsePoint(const String& string, FloatPoint& point)
160{
161    if (string.isEmpty())
162        return false;
163    if (string.is8Bit())
164        return parsePointInternal<LChar>(string, point);
165    return parsePointInternal<UChar>(string, point);
166}
167
168void SVGAnimateMotionElement::resetAnimatedType()
169{
170    if (!hasValidAttributeType())
171        return;
172    SVGElement* targetElement = this->targetElement();
173    if (!targetElement)
174        return;
175    if (AffineTransform* transform = targetElement->supplementalTransform())
176        transform->makeIdentity();
177}
178
179void SVGAnimateMotionElement::clearAnimatedType(SVGElement* targetElement)
180{
181    if (!targetElement)
182        return;
183
184    AffineTransform* transform = targetElement->supplementalTransform();
185    if (!transform)
186        return;
187
188    transform->makeIdentity();
189
190    if (RenderObject* targetRenderer = targetElement->renderer()) {
191        targetRenderer->setNeedsTransformUpdate();
192        RenderSVGResource::markForLayoutAndParentResourceInvalidation(targetRenderer);
193    }
194}
195
196bool SVGAnimateMotionElement::calculateToAtEndOfDurationValue(const String& toAtEndOfDurationString)
197{
198    parsePoint(toAtEndOfDurationString, m_toPointAtEndOfDuration);
199    m_hasToPointAtEndOfDuration = true;
200    return true;
201}
202
203bool SVGAnimateMotionElement::calculateFromAndToValues(const String& fromString, const String& toString)
204{
205    m_hasToPointAtEndOfDuration = false;
206    parsePoint(fromString, m_fromPoint);
207    parsePoint(toString, m_toPoint);
208    return true;
209}
210
211bool SVGAnimateMotionElement::calculateFromAndByValues(const String& fromString, const String& byString)
212{
213    m_hasToPointAtEndOfDuration = false;
214    if (animationMode() == ByAnimation && !isAdditive())
215        return false;
216    parsePoint(fromString, m_fromPoint);
217    FloatPoint byPoint;
218    parsePoint(byString, byPoint);
219    m_toPoint = FloatPoint(m_fromPoint.x() + byPoint.x(), m_fromPoint.y() + byPoint.y());
220    return true;
221}
222
223void SVGAnimateMotionElement::calculateAnimatedValue(float percentage, unsigned repeatCount, SVGSMILElement*)
224{
225    SVGElement* targetElement = this->targetElement();
226    if (!targetElement)
227        return;
228    AffineTransform* transform = targetElement->supplementalTransform();
229    if (!transform)
230        return;
231
232    if (RenderObject* targetRenderer = targetElement->renderer())
233        targetRenderer->setNeedsTransformUpdate();
234
235    if (!isAdditive())
236        transform->makeIdentity();
237
238    if (animationMode() != PathAnimation) {
239        FloatPoint toPointAtEndOfDuration = m_toPoint;
240        if (isAccumulated() && repeatCount && m_hasToPointAtEndOfDuration)
241            toPointAtEndOfDuration = m_toPointAtEndOfDuration;
242
243        float animatedX = 0;
244        animateAdditiveNumber(percentage, repeatCount, m_fromPoint.x(), m_toPoint.x(), toPointAtEndOfDuration.x(), animatedX);
245
246        float animatedY = 0;
247        animateAdditiveNumber(percentage, repeatCount, m_fromPoint.y(), m_toPoint.y(), toPointAtEndOfDuration.y(), animatedY);
248
249        transform->translate(animatedX, animatedY);
250        return;
251    }
252
253    ASSERT(!m_animationPath.isEmpty());
254
255    float positionOnPath = m_animationPath.length() * percentage;
256    FloatPoint position;
257    float angle;
258    bool ok = m_animationPath.pointAndNormalAtLength(positionOnPath, position, angle);
259    if (!ok)
260        return;
261
262    // Handle accumulate="sum".
263    if (isAccumulated() && repeatCount) {
264        FloatPoint positionAtEndOfDuration = m_animationPath.pointAtLength(m_animationPath.length(), ok);
265        if (ok)
266            position.move(positionAtEndOfDuration.x() * repeatCount, positionAtEndOfDuration.y() * repeatCount);
267    }
268
269    transform->translate(position.x(), position.y());
270    RotateMode rotateMode = this->rotateMode();
271    if (rotateMode != RotateAuto && rotateMode != RotateAutoReverse)
272        return;
273    if (rotateMode == RotateAutoReverse)
274        angle += 180;
275    transform->rotate(angle);
276}
277
278void SVGAnimateMotionElement::applyResultsToTarget()
279{
280    // We accumulate to the target element transform list so there is not much to do here.
281    SVGElement* targetElement = this->targetElement();
282    if (!targetElement)
283        return;
284
285    if (RenderObject* renderer = targetElement->renderer())
286        RenderSVGResource::markForLayoutAndParentResourceInvalidation(renderer);
287
288    AffineTransform* t = targetElement->supplementalTransform();
289    if (!t)
290        return;
291
292    // ...except in case where we have additional instances in <use> trees.
293    const WillBeHeapHashSet<RawPtrWillBeWeakMember<SVGElement> >& instances = targetElement->instancesForElement();
294    const WillBeHeapHashSet<RawPtrWillBeWeakMember<SVGElement> >::const_iterator end = instances.end();
295    for (WillBeHeapHashSet<RawPtrWillBeWeakMember<SVGElement> >::const_iterator it = instances.begin(); it != end; ++it) {
296        SVGElement* shadowTreeElement = *it;
297        ASSERT(shadowTreeElement);
298        AffineTransform* transform = shadowTreeElement->supplementalTransform();
299        if (!transform)
300            continue;
301        transform->setMatrix(t->a(), t->b(), t->c(), t->d(), t->e(), t->f());
302        if (RenderObject* renderer = shadowTreeElement->renderer()) {
303            renderer->setNeedsTransformUpdate();
304            RenderSVGResource::markForLayoutAndParentResourceInvalidation(renderer);
305        }
306    }
307}
308
309float SVGAnimateMotionElement::calculateDistance(const String& fromString, const String& toString)
310{
311    FloatPoint from;
312    FloatPoint to;
313    if (!parsePoint(fromString, from))
314        return -1;
315    if (!parsePoint(toString, to))
316        return -1;
317    FloatSize diff = to - from;
318    return sqrtf(diff.width() * diff.width() + diff.height() * diff.height());
319}
320
321void SVGAnimateMotionElement::updateAnimationMode()
322{
323    if (!m_animationPath.isEmpty())
324        setAnimationMode(PathAnimation);
325    else
326        SVGAnimationElement::updateAnimationMode();
327}
328
329}
330