blob: f79696d8956d2b9421fb796a125eb50162981af4 [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 "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