| /* |
| * 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 "core/svg/SVGAnimateMotionElement.h" |
| |
| #include "core/dom/ElementTraversal.h" |
| #include "core/layout/LayoutObject.h" |
| #include "core/svg/SVGMPathElement.h" |
| #include "core/svg/SVGParserUtilities.h" |
| #include "core/svg/SVGPathElement.h" |
| #include "core/svg/SVGPathUtilities.h" |
| #include "core/svg_names.h" |
| #include "platform/transforms/AffineTransform.h" |
| #include "platform/wtf/MathExtras.h" |
| #include "platform/wtf/StdLibExtras.h" |
| |
| namespace blink { |
| |
| namespace { |
| |
| bool TargetCanHaveMotionTransform(const SVGElement& target) { |
| // We don't have a special attribute name to verify the animation type. Check |
| // the element name instead. |
| if (!target.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. |
| return IsSVGGElement(target) || IsSVGDefsElement(target) || |
| IsSVGUseElement(target) || IsSVGImageElement(target) || |
| IsSVGSwitchElement(target) || IsSVGPathElement(target) || |
| IsSVGRectElement(target) || IsSVGCircleElement(target) || |
| IsSVGEllipseElement(target) || IsSVGLineElement(target) || |
| IsSVGPolylineElement(target) || IsSVGPolygonElement(target) || |
| IsSVGTextElement(target) || IsSVGClipPathElement(target) || |
| IsSVGMaskElement(target) || IsSVGAElement(target) || |
| IsSVGForeignObjectElement(target); |
| } |
| } |
| |
| inline SVGAnimateMotionElement::SVGAnimateMotionElement(Document& document) |
| : SVGAnimationElement(SVGNames::animateMotionTag, document), |
| has_to_point_at_end_of_duration_(false) { |
| SetCalcMode(kCalcModePaced); |
| } |
| |
| DEFINE_NODE_FACTORY(SVGAnimateMotionElement) |
| |
| SVGAnimateMotionElement::~SVGAnimateMotionElement() = default; |
| |
| bool SVGAnimateMotionElement::HasValidTarget() { |
| return SVGAnimationElement::HasValidTarget() && |
| TargetCanHaveMotionTransform(*targetElement()); |
| } |
| |
| void SVGAnimateMotionElement::ParseAttribute( |
| const AttributeModificationParams& params) { |
| if (params.name == SVGNames::pathAttr) { |
| path_ = Path(); |
| BuildPathFromString(params.new_value, path_); |
| UpdateAnimationPath(); |
| return; |
| } |
| |
| SVGAnimationElement::ParseAttribute(params); |
| } |
| |
| SVGAnimateMotionElement::RotateMode SVGAnimateMotionElement::GetRotateMode() |
| const { |
| DEFINE_STATIC_LOCAL(const AtomicString, auto_val, ("auto")); |
| DEFINE_STATIC_LOCAL(const AtomicString, auto_reverse, ("auto-reverse")); |
| const AtomicString& rotate = getAttribute(SVGNames::rotateAttr); |
| if (rotate == auto_val) |
| return kRotateAuto; |
| if (rotate == auto_reverse) |
| return kRotateAutoReverse; |
| return kRotateAngle; |
| } |
| |
| void SVGAnimateMotionElement::UpdateAnimationPath() { |
| animation_path_ = Path(); |
| bool found_m_path = false; |
| |
| for (SVGMPathElement* mpath = Traversal<SVGMPathElement>::FirstChild(*this); |
| mpath; mpath = Traversal<SVGMPathElement>::NextSibling(*mpath)) { |
| if (SVGPathElement* path_element = mpath->PathElement()) { |
| animation_path_ = path_element->AttributePath(); |
| found_m_path = true; |
| break; |
| } |
| } |
| |
| if (!found_m_path && FastHasAttribute(SVGNames::pathAttr)) |
| animation_path_ = path_; |
| |
| UpdateAnimationMode(); |
| } |
| |
| template <typename CharType> |
| static bool ParsePointInternal(const String& string, FloatPoint& point) { |
| const CharType* ptr = string.GetCharacters<CharType>(); |
| const CharType* end = ptr + string.length(); |
| |
| if (!SkipOptionalSVGSpaces(ptr, end)) |
| return false; |
| |
| float x = 0; |
| if (!ParseNumber(ptr, end, x)) |
| return false; |
| |
| float y = 0; |
| if (!ParseNumber(ptr, end, y)) |
| return false; |
| |
| point = FloatPoint(x, y); |
| |
| // disallow anything except spaces at the end |
| return !SkipOptionalSVGSpaces(ptr, end); |
| } |
| |
| static bool ParsePoint(const String& string, FloatPoint& point) { |
| if (string.IsEmpty()) |
| return false; |
| if (string.Is8Bit()) |
| return ParsePointInternal<LChar>(string, point); |
| return ParsePointInternal<UChar>(string, point); |
| } |
| |
| void SVGAnimateMotionElement::ResetAnimatedType() { |
| SVGElement* target_element = this->targetElement(); |
| if (!target_element || !TargetCanHaveMotionTransform(*target_element)) |
| return; |
| if (AffineTransform* transform = target_element->AnimateMotionTransform()) |
| transform->MakeIdentity(); |
| } |
| |
| void SVGAnimateMotionElement::ClearAnimatedType() { |
| SVGElement* target_element = this->targetElement(); |
| if (!target_element) |
| return; |
| |
| AffineTransform* transform = target_element->AnimateMotionTransform(); |
| if (!transform) |
| return; |
| |
| transform->MakeIdentity(); |
| |
| if (LayoutObject* target_layout_object = target_element->GetLayoutObject()) |
| InvalidateForAnimateMotionTransformChange(*target_layout_object); |
| } |
| |
| bool SVGAnimateMotionElement::CalculateToAtEndOfDurationValue( |
| const String& to_at_end_of_duration_string) { |
| ParsePoint(to_at_end_of_duration_string, to_point_at_end_of_duration_); |
| has_to_point_at_end_of_duration_ = true; |
| return true; |
| } |
| |
| bool SVGAnimateMotionElement::CalculateFromAndToValues( |
| const String& from_string, |
| const String& to_string) { |
| has_to_point_at_end_of_duration_ = false; |
| ParsePoint(from_string, from_point_); |
| ParsePoint(to_string, to_point_); |
| return true; |
| } |
| |
| bool SVGAnimateMotionElement::CalculateFromAndByValues( |
| const String& from_string, |
| const String& by_string) { |
| has_to_point_at_end_of_duration_ = false; |
| if (GetAnimationMode() == kByAnimation && !IsAdditive()) |
| return false; |
| ParsePoint(from_string, from_point_); |
| FloatPoint by_point; |
| ParsePoint(by_string, by_point); |
| to_point_ = FloatPoint(from_point_.X() + by_point.X(), |
| from_point_.Y() + by_point.Y()); |
| return true; |
| } |
| |
| void SVGAnimateMotionElement::CalculateAnimatedValue(float percentage, |
| unsigned repeat_count, |
| SVGSMILElement*) { |
| SVGElement* target_element = this->targetElement(); |
| DCHECK(target_element); |
| AffineTransform* transform = target_element->AnimateMotionTransform(); |
| if (!transform) |
| return; |
| |
| if (LayoutObject* target_layout_object = target_element->GetLayoutObject()) |
| InvalidateForAnimateMotionTransformChange(*target_layout_object); |
| |
| if (!IsAdditive()) |
| transform->MakeIdentity(); |
| |
| if (GetAnimationMode() != kPathAnimation) { |
| FloatPoint to_point_at_end_of_duration = to_point_; |
| if (IsAccumulated() && repeat_count && has_to_point_at_end_of_duration_) |
| to_point_at_end_of_duration = to_point_at_end_of_duration_; |
| |
| float animated_x = 0; |
| AnimateAdditiveNumber(percentage, repeat_count, from_point_.X(), |
| to_point_.X(), to_point_at_end_of_duration.X(), |
| animated_x); |
| |
| float animated_y = 0; |
| AnimateAdditiveNumber(percentage, repeat_count, from_point_.Y(), |
| to_point_.Y(), to_point_at_end_of_duration.Y(), |
| animated_y); |
| |
| transform->Translate(animated_x, animated_y); |
| return; |
| } |
| |
| DCHECK(!animation_path_.IsEmpty()); |
| |
| float position_on_path = animation_path_.length() * percentage; |
| FloatPoint position; |
| float angle; |
| animation_path_.PointAndNormalAtLength(position_on_path, position, angle); |
| |
| // Handle accumulate="sum". |
| if (IsAccumulated() && repeat_count) { |
| FloatPoint position_at_end_of_duration = |
| animation_path_.PointAtLength(animation_path_.length()); |
| position.Move(position_at_end_of_duration.X() * repeat_count, |
| position_at_end_of_duration.Y() * repeat_count); |
| } |
| |
| transform->Translate(position.X(), position.Y()); |
| RotateMode rotate_mode = this->GetRotateMode(); |
| if (rotate_mode != kRotateAuto && rotate_mode != kRotateAutoReverse) |
| return; |
| if (rotate_mode == kRotateAutoReverse) |
| angle += 180; |
| transform->Rotate(angle); |
| } |
| |
| void SVGAnimateMotionElement::ApplyResultsToTarget() { |
| // We accumulate to the target element transform list so there is not much to |
| // do here. |
| SVGElement* target_element = this->targetElement(); |
| if (!target_element) |
| return; |
| |
| AffineTransform* t = target_element->AnimateMotionTransform(); |
| if (!t) |
| return; |
| |
| // ...except in case where we have additional instances in <use> trees. |
| const HeapHashSet<WeakMember<SVGElement>>& instances = |
| target_element->InstancesForElement(); |
| for (SVGElement* shadow_tree_element : instances) { |
| DCHECK(shadow_tree_element); |
| AffineTransform* transform = shadow_tree_element->AnimateMotionTransform(); |
| if (!transform) |
| continue; |
| transform->SetMatrix(t->A(), t->B(), t->C(), t->D(), t->E(), t->F()); |
| if (LayoutObject* layout_object = shadow_tree_element->GetLayoutObject()) |
| InvalidateForAnimateMotionTransformChange(*layout_object); |
| } |
| } |
| |
| float SVGAnimateMotionElement::CalculateDistance(const String& from_string, |
| const String& to_string) { |
| FloatPoint from; |
| FloatPoint to; |
| if (!ParsePoint(from_string, from)) |
| return -1; |
| if (!ParsePoint(to_string, to)) |
| return -1; |
| FloatSize diff = to - from; |
| return sqrtf(diff.Width() * diff.Width() + diff.Height() * diff.Height()); |
| } |
| |
| void SVGAnimateMotionElement::UpdateAnimationMode() { |
| if (!animation_path_.IsEmpty()) |
| SetAnimationMode(kPathAnimation); |
| else |
| SVGAnimationElement::UpdateAnimationMode(); |
| } |
| |
| void SVGAnimateMotionElement::InvalidateForAnimateMotionTransformChange( |
| LayoutObject& object) { |
| object.SetNeedsTransformUpdate(); |
| // The transform paint property relies on the SVG transform value. |
| object.SetNeedsPaintPropertyUpdate(); |
| MarkForLayoutAndParentResourceInvalidation(&object); |
| } |
| |
| } // namespace blink |