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 | |
41 | namespace WebCore { |
42 | |
43 | WTF_MAKE_ISO_ALLOCATED_IMPL(SVGAnimateMotionElement); |
44 | |
45 | using namespace SVGNames; |
46 | |
47 | inline 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 | |
55 | Ref<SVGAnimateMotionElement> SVGAnimateMotionElement::create(const QualifiedName& tagName, Document& document) |
56 | { |
57 | return adoptRef(*new SVGAnimateMotionElement(tagName, document)); |
58 | } |
59 | |
60 | bool 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 | |
93 | bool SVGAnimateMotionElement::hasValidAttributeName() const |
94 | { |
95 | // AnimateMotion does not use attributeName so it is always valid. |
96 | return true; |
97 | } |
98 | |
99 | void 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 | |
110 | SVGAnimateMotionElement::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 | |
122 | void 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 | |
142 | void 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 | |
153 | void SVGAnimateMotionElement::clearAnimatedType(SVGElement* targetElement) |
154 | { |
155 | if (!targetElement) |
156 | return; |
157 | if (AffineTransform* transform = targetElement->supplementalTransform()) |
158 | transform->makeIdentity(); |
159 | } |
160 | |
161 | bool SVGAnimateMotionElement::calculateToAtEndOfDurationValue(const String& toAtEndOfDurationString) |
162 | { |
163 | parsePoint(toAtEndOfDurationString, m_toPointAtEndOfDuration); |
164 | m_hasToPointAtEndOfDuration = true; |
165 | return true; |
166 | } |
167 | |
168 | bool 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 | |
176 | bool 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 | |
188 | void 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 | |
210 | void 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 | |
249 | void 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 | |
276 | Optional<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 | |
288 | void SVGAnimateMotionElement::updateAnimationMode() |
289 | { |
290 | if (!m_animationPath.isEmpty()) |
291 | setAnimationMode(AnimationMode::Path); |
292 | else |
293 | SVGAnimationElement::updateAnimationMode(); |
294 | } |
295 | |
296 | } |
297 | |