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#include "SVGAnimateMotionElement.h"
24
25#include "AffineTransform.h"
26#include "ElementIterator.h"
27#include "PathTraversalState.h"
28#include "RenderSVGResource.h"
29#include "SVGImageElement.h"
30#include "SVGMPathElement.h"
31#include "SVGNames.h"
32#include "SVGParserUtilities.h"
33#include "SVGPathData.h"
34#include "SVGPathElement.h"
35#include "SVGPathUtilities.h"
36#include <wtf/IsoMallocInlines.h>
37#include <wtf/MathExtras.h>
38#include <wtf/StdLibExtras.h>
39#include <wtf/text/StringView.h>
40
41namespace WebCore {
42
43WTF_MAKE_ISO_ALLOCATED_IMPL(SVGAnimateMotionElement);
44
45using namespace SVGNames;
46
47inline SVGAnimateMotionElement::SVGAnimateMotionElement(const QualifiedName& tagName, Document& document)
48 : SVGAnimationElement(tagName, document)
49 , m_hasToPointAtEndOfDuration(false)
50{
51 setCalcMode(CalcMode::Paced);
52 ASSERT(hasTagName(animateMotionTag));
53}
54
55Ref<SVGAnimateMotionElement> SVGAnimateMotionElement::create(const QualifiedName& tagName, Document& document)
56{
57 return adoptRef(*new SVGAnimateMotionElement(tagName, document));
58}
59
60bool SVGAnimateMotionElement::hasValidAttributeType() const
61{
62 auto targetElement = makeRefPtr(this->targetElement());
63 if (!targetElement)
64 return false;
65
66 // We don't have a special attribute name to verify the animation type. Check the element name instead.
67 if (!targetElement->isSVGGraphicsElement())
68 return false;
69 // Spec: SVG 1.1 section 19.2.15
70 // FIXME: svgTag is missing. Needs to be checked, if transforming <svg> could cause problems.
71 if (targetElement->hasTagName(gTag)
72 || targetElement->hasTagName(defsTag)
73 || targetElement->hasTagName(useTag)
74 || is<SVGImageElement>(*targetElement)
75 || targetElement->hasTagName(switchTag)
76 || targetElement->hasTagName(pathTag)
77 || targetElement->hasTagName(rectTag)
78 || targetElement->hasTagName(circleTag)
79 || targetElement->hasTagName(ellipseTag)
80 || targetElement->hasTagName(lineTag)
81 || targetElement->hasTagName(polylineTag)
82 || targetElement->hasTagName(polygonTag)
83 || targetElement->hasTagName(textTag)
84 || targetElement->hasTagName(clipPathTag)
85 || targetElement->hasTagName(maskTag)
86 || targetElement->hasTagName(SVGNames::aTag)
87 || targetElement->hasTagName(foreignObjectTag)
88 )
89 return true;
90 return false;
91}
92
93bool SVGAnimateMotionElement::hasValidAttributeName() const
94{
95 // AnimateMotion does not use attributeName so it is always valid.
96 return true;
97}
98
99void SVGAnimateMotionElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
100{
101 if (name == SVGNames::pathAttr) {
102 m_path = buildPathFromString(value);
103 updateAnimationPath();
104 return;
105 }
106
107 SVGAnimationElement::parseAttribute(name, value);
108}
109
110SVGAnimateMotionElement::RotateMode SVGAnimateMotionElement::rotateMode() const
111{
112 static NeverDestroyed<const AtomicString> autoVal("auto", AtomicString::ConstructFromLiteral);
113 static NeverDestroyed<const AtomicString> autoReverse("auto-reverse", AtomicString::ConstructFromLiteral);
114 const AtomicString& rotate = getAttribute(SVGNames::rotateAttr);
115 if (rotate == autoVal)
116 return RotateAuto;
117 if (rotate == autoReverse)
118 return RotateAutoReverse;
119 return RotateAngle;
120}
121
122void SVGAnimateMotionElement::updateAnimationPath()
123{
124 m_animationPath = Path();
125 bool foundMPath = false;
126
127 for (auto& mPath : childrenOfType<SVGMPathElement>(*this)) {
128 auto pathElement = mPath.pathElement();
129 if (pathElement) {
130 m_animationPath = pathFromGraphicsElement(pathElement.get());
131 foundMPath = true;
132 break;
133 }
134 }
135
136 if (!foundMPath && hasAttributeWithoutSynchronization(SVGNames::pathAttr))
137 m_animationPath = m_path;
138
139 updateAnimationMode();
140}
141
142void SVGAnimateMotionElement::resetAnimatedType()
143{
144 if (!hasValidAttributeType())
145 return;
146 auto targetElement = makeRefPtr(this->targetElement());
147 if (!targetElement)
148 return;
149 if (AffineTransform* transform = targetElement->supplementalTransform())
150 transform->makeIdentity();
151}
152
153void SVGAnimateMotionElement::clearAnimatedType(SVGElement* targetElement)
154{
155 if (!targetElement)
156 return;
157 if (AffineTransform* transform = targetElement->supplementalTransform())
158 transform->makeIdentity();
159}
160
161bool SVGAnimateMotionElement::calculateToAtEndOfDurationValue(const String& toAtEndOfDurationString)
162{
163 parsePoint(toAtEndOfDurationString, m_toPointAtEndOfDuration);
164 m_hasToPointAtEndOfDuration = true;
165 return true;
166}
167
168bool SVGAnimateMotionElement::calculateFromAndToValues(const String& fromString, const String& toString)
169{
170 m_hasToPointAtEndOfDuration = false;
171 parsePoint(fromString, m_fromPoint);
172 parsePoint(toString, m_toPoint);
173 return true;
174}
175
176bool SVGAnimateMotionElement::calculateFromAndByValues(const String& fromString, const String& byString)
177{
178 m_hasToPointAtEndOfDuration = false;
179 if (animationMode() == AnimationMode::By && !isAdditive())
180 return false;
181 parsePoint(fromString, m_fromPoint);
182 FloatPoint byPoint;
183 parsePoint(byString, byPoint);
184 m_toPoint = FloatPoint(m_fromPoint.x() + byPoint.x(), m_fromPoint.y() + byPoint.y());
185 return true;
186}
187
188void SVGAnimateMotionElement::buildTransformForProgress(AffineTransform* transform, float percentage)
189{
190 ASSERT(!m_animationPath.isEmpty());
191
192 bool success = false;
193 float positionOnPath = m_animationPath.length() * percentage;
194 auto traversalState(m_animationPath.traversalStateAtLength(positionOnPath, success));
195 if (!success)
196 return;
197
198 FloatPoint position = traversalState.current();
199 float angle = traversalState.normalAngle();
200
201 transform->translate(position);
202 RotateMode rotateMode = this->rotateMode();
203 if (rotateMode != RotateAuto && rotateMode != RotateAutoReverse)
204 return;
205 if (rotateMode == RotateAutoReverse)
206 angle += 180;
207 transform->rotate(angle);
208}
209
210void SVGAnimateMotionElement::calculateAnimatedValue(float percentage, unsigned repeatCount, SVGSMILElement*)
211{
212 auto targetElement = makeRefPtr(this->targetElement());
213 if (!targetElement)
214 return;
215 AffineTransform* transform = targetElement->supplementalTransform();
216 if (!transform)
217 return;
218
219 if (RenderObject* targetRenderer = targetElement->renderer())
220 targetRenderer->setNeedsTransformUpdate();
221
222 if (!isAdditive())
223 transform->makeIdentity();
224
225 if (animationMode() != AnimationMode::Path) {
226 FloatPoint toPointAtEndOfDuration = m_toPoint;
227 if (isAccumulated() && repeatCount && m_hasToPointAtEndOfDuration)
228 toPointAtEndOfDuration = m_toPointAtEndOfDuration;
229
230 float animatedX = 0;
231 animateAdditiveNumber(percentage, repeatCount, m_fromPoint.x(), m_toPoint.x(), toPointAtEndOfDuration.x(), animatedX);
232
233 float animatedY = 0;
234 animateAdditiveNumber(percentage, repeatCount, m_fromPoint.y(), m_toPoint.y(), toPointAtEndOfDuration.y(), animatedY);
235
236 transform->translate(animatedX, animatedY);
237 return;
238 }
239
240 buildTransformForProgress(transform, percentage);
241
242 // Handle accumulate="sum".
243 if (isAccumulated() && repeatCount) {
244 for (unsigned i = 0; i < repeatCount; ++i)
245 buildTransformForProgress(transform, 1);
246 }
247}
248
249void SVGAnimateMotionElement::applyResultsToTarget()
250{
251 // We accumulate to the target element transform list so there is not much to do here.
252 auto targetElement = makeRefPtr(this->targetElement());
253 if (!targetElement)
254 return;
255
256 if (RenderElement* renderer = targetElement->renderer())
257 RenderSVGResource::markForLayoutAndParentResourceInvalidation(*renderer);
258
259 AffineTransform* targetSupplementalTransform = targetElement->supplementalTransform();
260 if (!targetSupplementalTransform)
261 return;
262
263 // ...except in case where we have additional instances in <use> trees.
264 for (auto* instance : targetElement->instances()) {
265 AffineTransform* transform = instance->supplementalTransform();
266 if (!transform || *transform == *targetSupplementalTransform)
267 continue;
268 *transform = *targetSupplementalTransform;
269 if (RenderElement* renderer = instance->renderer()) {
270 renderer->setNeedsTransformUpdate();
271 RenderSVGResource::markForLayoutAndParentResourceInvalidation(*renderer);
272 }
273 }
274}
275
276Optional<float> SVGAnimateMotionElement::calculateDistance(const String& fromString, const String& toString)
277{
278 FloatPoint from;
279 FloatPoint to;
280 if (!parsePoint(fromString, from))
281 return { };
282 if (!parsePoint(toString, to))
283 return { };
284 FloatSize diff = to - from;
285 return sqrtf(diff.width() * diff.width() + diff.height() * diff.height());
286}
287
288void SVGAnimateMotionElement::updateAnimationMode()
289{
290 if (!m_animationPath.isEmpty())
291 setAnimationMode(AnimationMode::Path);
292 else
293 SVGAnimationElement::updateAnimationMode();
294}
295
296}
297