blob: 1ffb3792f33ef17d6936efecb204b9ce8db1e331 [file] [log] [blame]
/*
* 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 "third_party/blink/renderer/core/svg/svg_animate_motion_element.h"
#include "third_party/blink/renderer/core/dom/element_traversal.h"
#include "third_party/blink/renderer/core/layout/layout_object.h"
#include "third_party/blink/renderer/core/svg/svg_mpath_element.h"
#include "third_party/blink/renderer/core/svg/svg_parser_utilities.h"
#include "third_party/blink/renderer/core/svg/svg_path_element.h"
#include "third_party/blink/renderer/core/svg/svg_path_utilities.h"
#include "third_party/blink/renderer/core/svg_names.h"
#include "third_party/blink/renderer/platform/transforms/affine_transform.h"
#include "third_party/blink/renderer/platform/wtf/math_extras.h"
#include "third_party/blink/renderer/platform/wtf/std_lib_extras.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 IsA<SVGGElement>(target) || IsSVGDefsElement(target) ||
IsA<SVGUseElement>(target) || IsA<SVGImageElement>(target) ||
IsSVGSwitchElement(target) || IsA<SVGPathElement>(target) ||
IsA<SVGRectElement>(target) || IsSVGCircleElement(target) ||
IsA<SVGEllipseElement>(target) || IsA<SVGLineElement>(target) ||
IsA<SVGPolylineElement>(target) || IsA<SVGPolygonElement>(target) ||
IsA<SVGTextElement>(target) || IsA<SVGClipPathElement>(target) ||
IsA<SVGMaskElement>(target) || IsSVGAElement(target) ||
IsA<SVGForeignObjectElement>(target);
}
}
SVGAnimateMotionElement::SVGAnimateMotionElement(Document& document)
: SVGAnimationElement(svg_names::kAnimateMotionTag, document),
has_to_point_at_end_of_duration_(false) {
SetCalcMode(kCalcModePaced);
}
SVGAnimateMotionElement::~SVGAnimateMotionElement() = default;
bool SVGAnimateMotionElement::HasValidAnimation() const {
return TargetCanHaveMotionTransform(*targetElement());
}
void SVGAnimateMotionElement::WillChangeAnimationTarget() {
SVGAnimationElement::WillChangeAnimationTarget();
UnregisterAnimation(svg_names::kAnimateMotionTag);
}
void SVGAnimateMotionElement::DidChangeAnimationTarget() {
// Use our QName as the key to RegisterAnimation to get a separate sandwich
// for animateMotion.
RegisterAnimation(svg_names::kAnimateMotionTag);
SVGAnimationElement::DidChangeAnimationTarget();
}
void SVGAnimateMotionElement::ParseAttribute(
const AttributeModificationParams& params) {
if (params.name == svg_names::kPathAttr) {
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(svg_names::kRotateAttr);
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(svg_names::kPathAttr))
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 = targetElement();
DCHECK(target_element);
DCHECK(TargetCanHaveMotionTransform(*target_element));
AffineTransform* transform = target_element->AnimateMotionTransform();
DCHECK(transform);
transform->MakeIdentity();
}
void SVGAnimateMotionElement::ClearAnimatedType() {
SVGElement* target_element = targetElement();
DCHECK(target_element);
AffineTransform* transform = target_element->AnimateMotionTransform();
DCHECK(transform);
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*) const {
SVGElement* target_element = targetElement();
DCHECK(target_element);
AffineTransform* transform = target_element->AnimateMotionTransform();
DCHECK(transform);
if (!IsAdditive())
transform->MakeIdentity();
if (GetAnimationMode() != kPathAnimation) {
FloatPoint to_point_at_end_of_duration = to_point_;
if (GetAnimationMode() != kToAnimation) {
if (repeat_count && IsAccumulated() && 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 (repeat_count && IsAccumulated()) {
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 = 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 = targetElement();
DCHECK(target_element);
AffineTransform* target_transform = target_element->AnimateMotionTransform();
DCHECK(target_transform);
if (LayoutObject* target_layout_object = target_element->GetLayoutObject())
InvalidateForAnimateMotionTransformChange(*target_layout_object);
// ...except in case where we have additional instances in <use> trees.
const auto& instances = target_element->InstancesForElement();
for (SVGElement* shadow_tree_element : instances) {
DCHECK(shadow_tree_element);
AffineTransform* shadow_transform =
shadow_tree_element->AnimateMotionTransform();
DCHECK(shadow_transform);
shadow_transform->SetTransform(*target_transform);
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