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 "SVGNames.h"
27#include "core/rendering/RenderObject.h"
28#include "core/rendering/svg/RenderSVGResource.h"
29#include "core/rendering/svg/SVGPathData.h"
30#include "core/svg/SVGElementInstance.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 WebCore {
40
41using namespace SVGNames;
42
43inline SVGAnimateMotionElement::SVGAnimateMotionElement(Document& document)
44    : SVGAnimationElement(animateMotionTag, document)
45    , m_hasToPointAtEndOfDuration(false)
46{
47    setCalcMode(CalcModePaced);
48    ScriptWrappable::init(this);
49}
50
51SVGAnimateMotionElement::~SVGAnimateMotionElement()
52{
53    if (targetElement())
54        clearAnimatedType(targetElement());
55}
56
57PassRefPtr<SVGAnimateMotionElement> SVGAnimateMotionElement::create(Document& document)
58{
59    return adoptRef(new SVGAnimateMotionElement(document));
60}
61
62bool SVGAnimateMotionElement::hasValidAttributeType()
63{
64    SVGElement* targetElement = this->targetElement();
65    if (!targetElement)
66        return false;
67
68    // We don't have a special attribute name to verify the animation type. Check the element name instead.
69    if (!targetElement->isSVGGraphicsElement())
70        return false;
71    // Spec: SVG 1.1 section 19.2.15
72    // FIXME: svgTag is missing. Needs to be checked, if transforming <svg> could cause problems.
73    if (targetElement->hasTagName(gTag)
74        || targetElement->hasTagName(defsTag)
75        || targetElement->hasTagName(useTag)
76        || targetElement->hasTagName(SVGNames::imageTag)
77        || targetElement->hasTagName(switchTag)
78        || targetElement->hasTagName(pathTag)
79        || targetElement->hasTagName(rectTag)
80        || targetElement->hasTagName(circleTag)
81        || targetElement->hasTagName(ellipseTag)
82        || targetElement->hasTagName(lineTag)
83        || targetElement->hasTagName(polylineTag)
84        || targetElement->hasTagName(polygonTag)
85        || targetElement->hasTagName(textTag)
86        || targetElement->hasTagName(clipPathTag)
87        || targetElement->hasTagName(maskTag)
88        || targetElement->hasTagName(SVGNames::aTag)
89        || targetElement->hasTagName(foreignObjectTag)
90        )
91        return true;
92    return false;
93}
94
95bool SVGAnimateMotionElement::hasValidAttributeName()
96{
97    // AnimateMotion does not use attributeName so it is always valid.
98    return true;
99}
100
101bool SVGAnimateMotionElement::isSupportedAttribute(const QualifiedName& attrName)
102{
103    DEFINE_STATIC_LOCAL(HashSet<QualifiedName>, supportedAttributes, ());
104    if (supportedAttributes.isEmpty())
105        supportedAttributes.add(SVGNames::pathAttr);
106    return supportedAttributes.contains<SVGAttributeHashTranslator>(attrName);
107}
108
109void SVGAnimateMotionElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
110{
111    if (!isSupportedAttribute(name)) {
112        SVGAnimationElement::parseAttribute(name, value);
113        return;
114    }
115
116    if (name == SVGNames::pathAttr) {
117        m_path = Path();
118        buildPathFromString(value, m_path);
119        updateAnimationPath();
120        return;
121    }
122
123    ASSERT_NOT_REACHED();
124}
125
126SVGAnimateMotionElement::RotateMode SVGAnimateMotionElement::rotateMode() const
127{
128    DEFINE_STATIC_LOCAL(const AtomicString, autoVal, ("auto", AtomicString::ConstructFromLiteral));
129    DEFINE_STATIC_LOCAL(const AtomicString, autoReverse, ("auto-reverse", AtomicString::ConstructFromLiteral));
130    const AtomicString& rotate = getAttribute(SVGNames::rotateAttr);
131    if (rotate == autoVal)
132        return RotateAuto;
133    if (rotate == autoReverse)
134        return RotateAutoReverse;
135    return RotateAngle;
136}
137
138void SVGAnimateMotionElement::updateAnimationPath()
139{
140    m_animationPath = Path();
141    bool foundMPath = false;
142
143    for (Node* child = firstChild(); child; child = child->nextSibling()) {
144        if (child->hasTagName(SVGNames::mpathTag)) {
145            SVGMPathElement* mPath = toSVGMPathElement(child);
146            SVGPathElement* pathElement = mPath->pathElement();
147            if (pathElement) {
148                updatePathFromGraphicsElement(pathElement, m_animationPath);
149                foundMPath = true;
150                break;
151            }
152        }
153    }
154
155    if (!foundMPath && fastHasAttribute(SVGNames::pathAttr))
156        m_animationPath = m_path;
157
158    updateAnimationMode();
159}
160
161template<typename CharType>
162static bool parsePointInternal(const String& string, FloatPoint& point)
163{
164    const CharType* ptr = string.getCharacters<CharType>();
165    const CharType* end = ptr + string.length();
166
167    if (!skipOptionalSVGSpaces(ptr, end))
168        return false;
169
170    float x = 0;
171    if (!parseNumber(ptr, end, x))
172        return false;
173
174    float y = 0;
175    if (!parseNumber(ptr, end, y))
176        return false;
177
178    point = FloatPoint(x, y);
179
180    // disallow anything except spaces at the end
181    return !skipOptionalSVGSpaces(ptr, end);
182}
183
184static bool parsePoint(const String& string, FloatPoint& point)
185{
186    if (string.isEmpty())
187        return false;
188    if (string.is8Bit())
189        return parsePointInternal<LChar>(string, point);
190    return parsePointInternal<UChar>(string, point);
191}
192
193void SVGAnimateMotionElement::resetAnimatedType()
194{
195    if (!hasValidAttributeType())
196        return;
197    SVGElement* targetElement = this->targetElement();
198    if (!targetElement)
199        return;
200    if (AffineTransform* transform = targetElement->supplementalTransform())
201        transform->makeIdentity();
202}
203
204void SVGAnimateMotionElement::clearAnimatedType(SVGElement* targetElement)
205{
206    if (!targetElement)
207        return;
208
209    AffineTransform* transform = targetElement->supplementalTransform();
210    if (!transform)
211        return;
212
213    transform->makeIdentity();
214
215    if (RenderObject* targetRenderer = targetElement->renderer()) {
216        targetRenderer->setNeedsTransformUpdate();
217        RenderSVGResource::markForLayoutAndParentResourceInvalidation(targetRenderer);
218    }
219}
220
221bool SVGAnimateMotionElement::calculateToAtEndOfDurationValue(const String& toAtEndOfDurationString)
222{
223    parsePoint(toAtEndOfDurationString, m_toPointAtEndOfDuration);
224    m_hasToPointAtEndOfDuration = true;
225    return true;
226}
227
228bool SVGAnimateMotionElement::calculateFromAndToValues(const String& fromString, const String& toString)
229{
230    m_hasToPointAtEndOfDuration = false;
231    parsePoint(fromString, m_fromPoint);
232    parsePoint(toString, m_toPoint);
233    return true;
234}
235
236bool SVGAnimateMotionElement::calculateFromAndByValues(const String& fromString, const String& byString)
237{
238    m_hasToPointAtEndOfDuration = false;
239    if (animationMode() == ByAnimation && !isAdditive())
240        return false;
241    parsePoint(fromString, m_fromPoint);
242    FloatPoint byPoint;
243    parsePoint(byString, byPoint);
244    m_toPoint = FloatPoint(m_fromPoint.x() + byPoint.x(), m_fromPoint.y() + byPoint.y());
245    return true;
246}
247
248void SVGAnimateMotionElement::calculateAnimatedValue(float percentage, unsigned repeatCount, SVGSMILElement*)
249{
250    SVGElement* targetElement = this->targetElement();
251    if (!targetElement)
252        return;
253    AffineTransform* transform = targetElement->supplementalTransform();
254    if (!transform)
255        return;
256
257    if (RenderObject* targetRenderer = targetElement->renderer())
258        targetRenderer->setNeedsTransformUpdate();
259
260    if (!isAdditive())
261        transform->makeIdentity();
262
263    if (animationMode() != PathAnimation) {
264        FloatPoint toPointAtEndOfDuration = m_toPoint;
265        if (isAccumulated() && repeatCount && m_hasToPointAtEndOfDuration)
266            toPointAtEndOfDuration = m_toPointAtEndOfDuration;
267
268        float animatedX = 0;
269        animateAdditiveNumber(percentage, repeatCount, m_fromPoint.x(), m_toPoint.x(), toPointAtEndOfDuration.x(), animatedX);
270
271        float animatedY = 0;
272        animateAdditiveNumber(percentage, repeatCount, m_fromPoint.y(), m_toPoint.y(), toPointAtEndOfDuration.y(), animatedY);
273
274        transform->translate(animatedX, animatedY);
275        return;
276    }
277
278    ASSERT(!m_animationPath.isEmpty());
279
280    bool ok = false;
281    float positionOnPath = m_animationPath.length() * percentage;
282    FloatPoint position;
283    float angle;
284    ok = m_animationPath.pointAndNormalAtLength(positionOnPath, position, angle);
285    if (!ok)
286        return;
287
288    // Handle accumulate="sum".
289    if (isAccumulated() && repeatCount) {
290        FloatPoint positionAtEndOfDuration = m_animationPath.pointAtLength(m_animationPath.length(), ok);
291        if (ok)
292            position.move(positionAtEndOfDuration.x() * repeatCount, positionAtEndOfDuration.y() * repeatCount);
293    }
294
295    transform->translate(position.x(), position.y());
296    RotateMode rotateMode = this->rotateMode();
297    if (rotateMode != RotateAuto && rotateMode != RotateAutoReverse)
298        return;
299    if (rotateMode == RotateAutoReverse)
300        angle += 180;
301    transform->rotate(angle);
302}
303
304void SVGAnimateMotionElement::applyResultsToTarget()
305{
306    // We accumulate to the target element transform list so there is not much to do here.
307    SVGElement* targetElement = this->targetElement();
308    if (!targetElement)
309        return;
310
311    if (RenderObject* renderer = targetElement->renderer())
312        RenderSVGResource::markForLayoutAndParentResourceInvalidation(renderer);
313
314    AffineTransform* t = targetElement->supplementalTransform();
315    if (!t)
316        return;
317
318    // ...except in case where we have additional instances in <use> trees.
319    const HashSet<SVGElementInstance*>& instances = targetElement->instancesForElement();
320    const HashSet<SVGElementInstance*>::const_iterator end = instances.end();
321    for (HashSet<SVGElementInstance*>::const_iterator it = instances.begin(); it != end; ++it) {
322        SVGElement* shadowTreeElement = (*it)->shadowTreeElement();
323        ASSERT(shadowTreeElement);
324        AffineTransform* transform = shadowTreeElement->supplementalTransform();
325        if (!transform)
326            continue;
327        transform->setMatrix(t->a(), t->b(), t->c(), t->d(), t->e(), t->f());
328        if (RenderObject* renderer = shadowTreeElement->renderer()) {
329            renderer->setNeedsTransformUpdate();
330            RenderSVGResource::markForLayoutAndParentResourceInvalidation(renderer);
331        }
332    }
333}
334
335float SVGAnimateMotionElement::calculateDistance(const String& fromString, const String& toString)
336{
337    FloatPoint from;
338    FloatPoint to;
339    if (!parsePoint(fromString, from))
340        return -1;
341    if (!parsePoint(toString, to))
342        return -1;
343    FloatSize diff = to - from;
344    return sqrtf(diff.width() * diff.width() + diff.height() * diff.height());
345}
346
347void SVGAnimateMotionElement::updateAnimationMode()
348{
349    if (!m_animationPath.isEmpty())
350        setAnimationMode(PathAnimation);
351    else
352        SVGAnimationElement::updateAnimationMode();
353}
354
355}
356