| /* |
| * Copyright (C) 2007 Eric Seidel <eric@webkit.org> |
| * Copyright (C) 2007 Rob Buis <buis@kde.org> |
| * Copyright (C) 2008 Apple Inc. All rights reserved. |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public License |
| * along with this library; see the file COPYING.LIB. If not, write to |
| * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| |
| #include "config.h" |
| #include "SVGAnimateMotionElement.h" |
| |
| #include "AffineTransform.h" |
| #include "CommonAtomStrings.h" |
| #include "ElementIterator.h" |
| #include "PathTraversalState.h" |
| #include "RenderElement.h" |
| #include "RenderSVGResource.h" |
| #include "SVGElementTypeHelpers.h" |
| #include "SVGImageElement.h" |
| #include "SVGMPathElement.h" |
| #include "SVGNames.h" |
| #include "SVGParserUtilities.h" |
| #include "SVGPathData.h" |
| #include "SVGPathElement.h" |
| #include "SVGPathUtilities.h" |
| #include <wtf/IsoMallocInlines.h> |
| #include <wtf/MathExtras.h> |
| #include <wtf/StdLibExtras.h> |
| #include <wtf/text/StringView.h> |
| |
| namespace WebCore { |
| |
| WTF_MAKE_ISO_ALLOCATED_IMPL(SVGAnimateMotionElement); |
| |
| using namespace SVGNames; |
| |
| inline SVGAnimateMotionElement::SVGAnimateMotionElement(const QualifiedName& tagName, Document& document) |
| : SVGAnimationElement(tagName, document) |
| { |
| setCalcMode(CalcMode::Paced); |
| ASSERT(hasTagName(animateMotionTag)); |
| } |
| |
| Ref<SVGAnimateMotionElement> SVGAnimateMotionElement::create(const QualifiedName& tagName, Document& document) |
| { |
| return adoptRef(*new SVGAnimateMotionElement(tagName, document)); |
| } |
| |
| bool SVGAnimateMotionElement::hasValidAttributeType() const |
| { |
| RefPtr targetElement = this->targetElement(); |
| if (!targetElement) |
| return false; |
| |
| // We don't have a special attribute name to verify the animation type. Check the element name instead. |
| if (!targetElement->isSVGGraphicsElement()) |
| return false; |
| // Spec: SVG 1.1 section 19.2.15 |
| // FIXME: svgTag is missing. Needs to be checked, if transforming <svg> could cause problems. |
| if (targetElement->hasTagName(gTag) |
| || targetElement->hasTagName(defsTag) |
| || targetElement->hasTagName(useTag) |
| || is<SVGImageElement>(*targetElement) |
| || targetElement->hasTagName(switchTag) |
| || targetElement->hasTagName(pathTag) |
| || targetElement->hasTagName(rectTag) |
| || targetElement->hasTagName(circleTag) |
| || targetElement->hasTagName(ellipseTag) |
| || targetElement->hasTagName(lineTag) |
| || targetElement->hasTagName(polylineTag) |
| || targetElement->hasTagName(polygonTag) |
| || targetElement->hasTagName(textTag) |
| || targetElement->hasTagName(clipPathTag) |
| || targetElement->hasTagName(maskTag) |
| || targetElement->hasTagName(SVGNames::aTag) |
| || targetElement->hasTagName(foreignObjectTag) |
| ) |
| return true; |
| return false; |
| } |
| |
| bool SVGAnimateMotionElement::hasValidAttributeName() const |
| { |
| // AnimateMotion does not use attributeName so it is always valid. |
| return true; |
| } |
| |
| void SVGAnimateMotionElement::parseAttribute(const QualifiedName& name, const AtomString& value) |
| { |
| if (name == SVGNames::pathAttr) { |
| m_path = buildPathFromString(value); |
| updateAnimationPath(); |
| return; |
| } |
| |
| SVGAnimationElement::parseAttribute(name, value); |
| } |
| |
| SVGAnimateMotionElement::RotateMode SVGAnimateMotionElement::rotateMode() const |
| { |
| static MainThreadNeverDestroyed<const AtomString> autoReverse("auto-reverse"_s); |
| auto& rotate = getAttribute(SVGNames::rotateAttr); |
| if (rotate == autoAtom()) |
| return RotateAuto; |
| if (rotate == autoReverse) |
| return RotateAutoReverse; |
| return RotateAngle; |
| } |
| |
| void SVGAnimateMotionElement::updateAnimationPath() |
| { |
| m_animationPath = Path(); |
| bool foundMPath = false; |
| |
| for (auto& mPath : childrenOfType<SVGMPathElement>(*this)) { |
| auto pathElement = mPath.pathElement(); |
| if (pathElement) { |
| m_animationPath = pathFromGraphicsElement(pathElement.get()); |
| foundMPath = true; |
| break; |
| } |
| } |
| |
| if (!foundMPath && hasAttributeWithoutSynchronization(SVGNames::pathAttr)) |
| m_animationPath = m_path; |
| |
| updateAnimationMode(); |
| } |
| |
| void SVGAnimateMotionElement::startAnimation() |
| { |
| if (!hasValidAttributeType()) |
| return; |
| RefPtr targetElement = this->targetElement(); |
| if (!targetElement) |
| return; |
| if (AffineTransform* transform = targetElement->supplementalTransform()) |
| transform->makeIdentity(); |
| } |
| |
| void SVGAnimateMotionElement::stopAnimation(SVGElement* targetElement) |
| { |
| if (!targetElement) |
| return; |
| if (AffineTransform* transform = targetElement->supplementalTransform()) |
| transform->makeIdentity(); |
| applyResultsToTarget(); |
| } |
| |
| bool SVGAnimateMotionElement::calculateToAtEndOfDurationValue(const String& toAtEndOfDurationString) |
| { |
| m_toPointAtEndOfDuration = valueOrDefault(parsePoint(toAtEndOfDurationString)); |
| return true; |
| } |
| |
| bool SVGAnimateMotionElement::calculateFromAndToValues(const String& fromString, const String& toString) |
| { |
| m_toPointAtEndOfDuration = std::nullopt; |
| m_fromPoint = valueOrDefault(parsePoint(fromString)); |
| m_toPoint = valueOrDefault(parsePoint(toString)); |
| return true; |
| } |
| |
| bool SVGAnimateMotionElement::calculateFromAndByValues(const String& fromString, const String& byString) |
| { |
| m_toPointAtEndOfDuration = std::nullopt; |
| if (animationMode() == AnimationMode::By && !isAdditive()) |
| return false; |
| m_fromPoint = valueOrDefault(parsePoint(fromString)); |
| auto byPoint = valueOrDefault(parsePoint(byString)); |
| m_toPoint = FloatPoint(m_fromPoint.x() + byPoint.x(), m_fromPoint.y() + byPoint.y()); |
| return true; |
| } |
| |
| void SVGAnimateMotionElement::buildTransformForProgress(AffineTransform* transform, float percentage) |
| { |
| ASSERT(!m_animationPath.isEmpty()); |
| |
| float positionOnPath = m_animationPath.length() * percentage; |
| auto traversalState(m_animationPath.traversalStateAtLength(positionOnPath)); |
| if (!traversalState.success()) |
| return; |
| |
| FloatPoint position = traversalState.current(); |
| float angle = traversalState.normalAngle(); |
| |
| transform->translate(position); |
| RotateMode rotateMode = this->rotateMode(); |
| if (rotateMode != RotateAuto && rotateMode != RotateAutoReverse) |
| return; |
| if (rotateMode == RotateAutoReverse) |
| angle += 180; |
| transform->rotate(angle); |
| } |
| |
| void SVGAnimateMotionElement::calculateAnimatedValue(float percentage, unsigned repeatCount) |
| { |
| RefPtr targetElement = this->targetElement(); |
| if (!targetElement) |
| return; |
| |
| AffineTransform* transform = targetElement->supplementalTransform(); |
| if (!transform) |
| return; |
| |
| if (!isAdditive()) |
| transform->makeIdentity(); |
| |
| if (animationMode() != AnimationMode::Path) { |
| FloatPoint toPointAtEndOfDuration = m_toPoint; |
| if (isAccumulated() && repeatCount && m_toPointAtEndOfDuration) |
| toPointAtEndOfDuration = *m_toPointAtEndOfDuration; |
| |
| float animatedX = 0; |
| animateAdditiveNumber(percentage, repeatCount, m_fromPoint.x(), m_toPoint.x(), toPointAtEndOfDuration.x(), animatedX); |
| |
| float animatedY = 0; |
| animateAdditiveNumber(percentage, repeatCount, m_fromPoint.y(), m_toPoint.y(), toPointAtEndOfDuration.y(), animatedY); |
| |
| transform->translate(animatedX, animatedY); |
| return; |
| } |
| |
| buildTransformForProgress(transform, percentage); |
| |
| // Handle accumulate="sum". |
| if (isAccumulated() && repeatCount) { |
| for (unsigned i = 0; i < repeatCount; ++i) |
| buildTransformForProgress(transform, 1); |
| } |
| } |
| |
| void SVGAnimateMotionElement::applyResultsToTarget() |
| { |
| // We accumulate to the target element transform list so there is not much to do here. |
| RefPtr targetElement = this->targetElement(); |
| if (!targetElement) |
| return; |
| |
| auto updateTargetElement = [](SVGElement& element) { |
| if (auto renderer = element.renderer()) |
| renderer->setNeedsTransformUpdate(); |
| element.updateSVGRendererForElementChange(); |
| }; |
| |
| updateTargetElement(*targetElement); |
| |
| AffineTransform* targetSupplementalTransform = targetElement->supplementalTransform(); |
| if (!targetSupplementalTransform) |
| return; |
| |
| // ...except in case where we have additional instances in <use> trees. |
| for (auto& instance : copyToVectorOf<Ref<SVGElement>>(targetElement->instances())) { |
| AffineTransform* transform = instance->supplementalTransform(); |
| if (!transform || *transform == *targetSupplementalTransform) |
| continue; |
| *transform = *targetSupplementalTransform; |
| updateTargetElement(instance); |
| } |
| } |
| |
| std::optional<float> SVGAnimateMotionElement::calculateDistance(const String& fromString, const String& toString) |
| { |
| auto from = parsePoint(fromString); |
| if (!from) |
| return { }; |
| auto to = parsePoint(toString); |
| if (!to) |
| return { }; |
| auto diff = *to - *from; |
| return std::hypot(diff.width(), diff.height()); |
| } |
| |
| void SVGAnimateMotionElement::updateAnimationMode() |
| { |
| if (!m_animationPath.isEmpty()) |
| setAnimationMode(AnimationMode::Path); |
| else |
| SVGAnimationElement::updateAnimationMode(); |
| } |
| |
| } |