| /* |
| * Copyright (C) 2004, 2005 Nikolas Zimmermann <zimmermann@kde.org> |
| * Copyright (C) 2004, 2005, 2006 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" |
| |
| #if ENABLE(SVG) && ENABLE(SVG_ANIMATION) |
| #include "SVGAnimateElement.h" |
| |
| #include "CSSComputedStyleDeclaration.h" |
| #include "CSSParser.h" |
| #include "CSSPropertyNames.h" |
| #include "ColorDistance.h" |
| #include "FloatConversion.h" |
| #include "QualifiedName.h" |
| #include "RenderObject.h" |
| #include "SVGColor.h" |
| #include "SVGNames.h" |
| #include "SVGParserUtilities.h" |
| #include "SVGPathParserFactory.h" |
| #include "SVGPathSegList.h" |
| #include "SVGPointList.h" |
| #include "SVGStyledElement.h" |
| |
| using namespace std; |
| |
| namespace WebCore { |
| |
| SVGAnimateElement::SVGAnimateElement(const QualifiedName& tagName, Document* document) |
| : SVGAnimationElement(tagName, document) |
| , m_animatedAttributeType(AnimatedString) |
| , m_fromNumber(0) |
| , m_toNumber(0) |
| , m_animatedNumber(numeric_limits<double>::infinity()) |
| , m_animatedPathPointer(0) |
| { |
| } |
| |
| PassRefPtr<SVGAnimateElement> SVGAnimateElement::create(const QualifiedName& tagName, Document* document) |
| { |
| return adoptRef(new SVGAnimateElement(tagName, document)); |
| } |
| |
| SVGAnimateElement::~SVGAnimateElement() |
| { |
| } |
| |
| static bool parseNumberValueAndUnit(const String& in, double& value, String& unit) |
| { |
| // FIXME: These are from top of my head, figure out all property types that can be animated as numbers. |
| unsigned unitLength = 0; |
| String parse = in.stripWhiteSpace(); |
| if (parse.endsWith("%")) |
| unitLength = 1; |
| else if (parse.endsWith("px") || parse.endsWith("pt") || parse.endsWith("em")) |
| unitLength = 2; |
| else if (parse.endsWith("deg") || parse.endsWith("rad")) |
| unitLength = 3; |
| else if (parse.endsWith("grad")) |
| unitLength = 4; |
| String newUnit = parse.right(unitLength); |
| String number = parse.left(parse.length() - unitLength); |
| if ((!unit.isEmpty() && newUnit != unit) || number.isEmpty()) |
| return false; |
| UChar last = number[number.length() - 1]; |
| if (last < '0' || last > '9') |
| return false; |
| unit = newUnit; |
| bool ok; |
| value = number.toDouble(&ok); |
| return ok; |
| } |
| |
| static inline void adjustForCurrentColor(SVGElement* targetElement, Color& color) |
| { |
| ASSERT(targetElement); |
| |
| if (RenderObject* targetRenderer = targetElement->renderer()) |
| color = targetRenderer->style()->visitedDependentColor(CSSPropertyColor); |
| else |
| color = Color(); |
| } |
| |
| static inline void adjustForInheritance(SVGElement* targetElement, const QualifiedName& attributeName, String& value) |
| { |
| // FIXME: At the moment the computed style gets returned as a String and needs to get parsed again. |
| // In the future we might want to work with the value type directly to avoid the String parsing. |
| ASSERT(targetElement); |
| |
| Element* parent = targetElement->parentElement(); |
| if (!parent || !parent->isSVGElement()) |
| return; |
| |
| SVGElement* svgParent = static_cast<SVGElement*>(parent); |
| if (svgParent->isStyled()) |
| value = computedStyle(svgParent)->getPropertyValue(cssPropertyID(attributeName.localName())); |
| } |
| |
| bool SVGAnimateElement::hasValidAttributeType() const |
| { |
| SVGElement* targetElement = this->targetElement(); |
| if (!targetElement) |
| return false; |
| |
| return determineAnimatedAttributeType(targetElement) != AnimatedUnknown; |
| } |
| |
| AnimatedAttributeType SVGAnimateElement::determineAnimatedAttributeType(SVGElement* targetElement) const |
| { |
| ASSERT(targetElement); |
| |
| AnimatedAttributeType type = targetElement->animatedPropertyTypeForAttribute(attributeName()); |
| if (type == AnimatedUnknown || (hasTagName(SVGNames::animateColorTag) && type != AnimatedColor)) |
| return AnimatedUnknown; |
| |
| // FIXME: We need type specific animations in the future. Many animations marked as AnimatedString today will |
| // support continuous animations. |
| switch (type) { |
| case AnimatedBoolean: |
| case AnimatedEnumeration: |
| case AnimatedLengthList: |
| case AnimatedNumberList: |
| case AnimatedNumberOptionalNumber: |
| case AnimatedPreserveAspectRatio: |
| case AnimatedRect: |
| case AnimatedString: |
| return AnimatedString; |
| case AnimatedAngle: |
| case AnimatedInteger: |
| case AnimatedLength: |
| case AnimatedNumber: |
| return AnimatedNumber; |
| case AnimatedPath: |
| return AnimatedPath; |
| case AnimatedPoints: |
| return AnimatedPoints; |
| case AnimatedColor: |
| return AnimatedColor; |
| case AnimatedUnknown: |
| case AnimatedTransformList: |
| // Animations of transform lists are not allowed for <animate> or <set> |
| // http://www.w3.org/TR/SVG/animate.html#AnimationAttributesAndProperties |
| return AnimatedUnknown; |
| } |
| |
| ASSERT_NOT_REACHED(); |
| return AnimatedUnknown; |
| } |
| |
| void SVGAnimateElement::calculateAnimatedValue(float percentage, unsigned repeat, SVGSMILElement* resultElement) |
| { |
| ASSERT(percentage >= 0 && percentage <= 1); |
| ASSERT(resultElement); |
| bool isInFirstHalfOfAnimation = percentage < 0.5f; |
| AnimationMode animationMode = this->animationMode(); |
| SVGElement* targetElement = 0; |
| // Avoid targetElement() call if possible. It might slow down animations. |
| if (m_fromPropertyValueType == InheritValue || m_toPropertyValueType == InheritValue |
| || m_fromPropertyValueType == CurrentColorValue || m_toPropertyValueType == CurrentColorValue) { |
| targetElement = this->targetElement(); |
| if (!targetElement) |
| return; |
| } |
| |
| if (hasTagName(SVGNames::setTag)) |
| percentage = 1; |
| if (!resultElement->hasTagName(SVGNames::animateTag) && !resultElement->hasTagName(SVGNames::animateColorTag) |
| && !resultElement->hasTagName(SVGNames::setTag)) |
| return; |
| SVGAnimateElement* results = static_cast<SVGAnimateElement*>(resultElement); |
| // Can't accumulate over a string property. |
| if (results->m_animatedAttributeType == AnimatedString && m_animatedAttributeType != AnimatedString) |
| return; |
| if (m_animatedAttributeType == AnimatedNumber) { |
| // To animation uses contributions from the lower priority animations as the base value. |
| if (animationMode == ToAnimation) |
| m_fromNumber = results->m_animatedNumber; |
| |
| // Replace 'currentColor' / 'inherit' by their computed property values. |
| if (m_fromPropertyValueType == InheritValue) { |
| String fromNumberString; |
| adjustForInheritance(targetElement, attributeName(), fromNumberString); |
| if (!parseNumberValueAndUnit(fromNumberString, m_fromNumber, m_numberUnit)) |
| return; |
| } |
| if (m_toPropertyValueType == InheritValue) { |
| String toNumberString; |
| adjustForInheritance(targetElement, attributeName(), toNumberString); |
| if (!parseNumberValueAndUnit(toNumberString, m_toNumber, m_numberUnit)) |
| return; |
| } |
| |
| double number; |
| if (calcMode() == CalcModeDiscrete) |
| number = isInFirstHalfOfAnimation ? m_fromNumber : m_toNumber; |
| else |
| number = (m_toNumber - m_fromNumber) * percentage + m_fromNumber; |
| |
| // FIXME: This is not correct for values animation. |
| if (isAccumulated() && repeat) |
| number += m_toNumber * repeat; |
| if (isAdditive() && animationMode != ToAnimation) |
| results->m_animatedNumber += number; |
| else |
| results->m_animatedNumber = number; |
| return; |
| } |
| if (m_animatedAttributeType == AnimatedColor) { |
| if (animationMode == ToAnimation) |
| m_fromColor = results->m_animatedColor; |
| |
| // Replace 'currentColor' / 'inherit' by their computed property values. |
| if (m_fromPropertyValueType == CurrentColorValue) |
| adjustForCurrentColor(targetElement, m_fromColor); |
| else if (m_fromPropertyValueType == InheritValue) { |
| String fromColorString; |
| adjustForInheritance(targetElement, attributeName(), fromColorString); |
| m_fromColor = SVGColor::colorFromRGBColorString(fromColorString); |
| } |
| if (m_toPropertyValueType == CurrentColorValue) |
| adjustForCurrentColor(targetElement, m_toColor); |
| else if (m_toPropertyValueType == InheritValue) { |
| String toColorString; |
| adjustForInheritance(targetElement, attributeName(), toColorString); |
| m_toColor = SVGColor::colorFromRGBColorString(toColorString); |
| } |
| |
| Color color; |
| if (calcMode() == CalcModeDiscrete) |
| color = isInFirstHalfOfAnimation ? m_fromColor : m_toColor; |
| else |
| color = ColorDistance(m_fromColor, m_toColor).scaledDistance(percentage).addToColorAndClamp(m_fromColor); |
| |
| // FIXME: Accumulate colors. |
| if (isAdditive() && animationMode != ToAnimation) |
| results->m_animatedColor = ColorDistance::addColorsAndClamp(results->m_animatedColor, color); |
| else |
| results->m_animatedColor = color; |
| return; |
| } |
| if (m_animatedAttributeType == AnimatedPath) { |
| if (animationMode == ToAnimation) { |
| ASSERT(results->m_animatedPathPointer); |
| m_fromPath = results->m_animatedPathPointer->copy(); |
| } |
| if (!percentage) { |
| ASSERT(m_fromPath); |
| ASSERT(percentage >= 0); |
| results->m_animatedPathPointer = m_fromPath.get(); |
| } else if (percentage == 1) { |
| ASSERT(m_toPath); |
| results->m_animatedPathPointer = m_toPath.get(); |
| } else { |
| if (m_fromPath && m_toPath) { |
| SVGPathParserFactory* factory = SVGPathParserFactory::self(); |
| if (!factory->buildAnimatedSVGPathByteStream(m_fromPath.get(), m_toPath.get(), results->m_animatedPath, percentage)) { |
| results->m_animatedPath.clear(); |
| results->m_animatedPathPointer = 0; |
| } else |
| results->m_animatedPathPointer = results->m_animatedPath.get(); |
| } else |
| results->m_animatedPathPointer = 0; |
| // Fall back to discrete animation if the paths are not compatible |
| if (!results->m_animatedPathPointer) { |
| ASSERT(m_fromPath); |
| ASSERT(m_toPath); |
| ASSERT(!results->m_animatedPath); |
| results->m_animatedPathPointer = ((animationMode == FromToAnimation && percentage > 0.5f) || animationMode == ToAnimation || percentage == 1) |
| ? m_toPath.get() : m_fromPath.get(); |
| } |
| } |
| return; |
| } |
| if (m_animatedAttributeType == AnimatedPoints) { |
| if (!percentage) |
| results->m_animatedPoints = m_fromPoints; |
| else if (percentage == 1) |
| results->m_animatedPoints = m_toPoints; |
| else { |
| if (!m_fromPoints.isEmpty() && !m_toPoints.isEmpty()) |
| SVGPointList::createAnimated(m_fromPoints, m_toPoints, results->m_animatedPoints, percentage); |
| else |
| results->m_animatedPoints.clear(); |
| // Fall back to discrete animation if the points are not compatible |
| if (results->m_animatedPoints.isEmpty()) |
| results->m_animatedPoints = ((animationMode == FromToAnimation && percentage > 0.5f) || animationMode == ToAnimation || percentage == 1) |
| ? m_toPoints : m_fromPoints; |
| } |
| return; |
| } |
| ASSERT(animationMode == FromToAnimation || animationMode == ToAnimation || animationMode == ValuesAnimation); |
| // Replace 'currentColor' / 'inherit' by their computed property values. |
| if (m_fromPropertyValueType == InheritValue) |
| adjustForInheritance(targetElement, attributeName(), m_fromString); |
| if (m_toPropertyValueType == InheritValue) |
| adjustForInheritance(targetElement, attributeName(), m_toString); |
| |
| if ((animationMode == FromToAnimation && percentage > 0.5f) || animationMode == ToAnimation || percentage == 1) |
| results->m_animatedString = m_toString; |
| else |
| results->m_animatedString = m_fromString; |
| // Higher priority replace animation overrides any additive results so far. |
| results->m_animatedAttributeType = AnimatedString; |
| } |
| |
| static bool inheritsFromProperty(SVGElement* targetElement, const QualifiedName& attributeName, const String& value) |
| { |
| ASSERT(targetElement); |
| DEFINE_STATIC_LOCAL(const AtomicString, inherit, ("inherit")); |
| |
| if (value.isEmpty() || value != inherit || !targetElement->isStyled()) |
| return false; |
| return SVGStyledElement::isAnimatableCSSProperty(attributeName); |
| } |
| |
| static bool attributeValueIsCurrentColor(const String& value) |
| { |
| DEFINE_STATIC_LOCAL(const AtomicString, currentColor, ("currentColor")); |
| return value == currentColor; |
| } |
| |
| bool SVGAnimateElement::calculateFromAndToValues(const String& fromString, const String& toString) |
| { |
| SVGElement* targetElement = this->targetElement(); |
| if (!targetElement) |
| return false; |
| m_fromPropertyValueType = inheritsFromProperty(targetElement, attributeName(), fromString) ? InheritValue : RegularPropertyValue; |
| m_toPropertyValueType = inheritsFromProperty(targetElement, attributeName(), toString) ? InheritValue : RegularPropertyValue; |
| |
| // FIXME: Needs more solid way determine target attribute type. |
| m_animatedAttributeType = determineAnimatedAttributeType(targetElement); |
| if (m_animatedAttributeType == AnimatedColor) { |
| bool fromIsCurrentColor = attributeValueIsCurrentColor(fromString); |
| bool toIsCurrentColor = attributeValueIsCurrentColor(toString); |
| if (fromIsCurrentColor) |
| m_fromPropertyValueType = CurrentColorValue; |
| else |
| m_fromColor = SVGColor::colorFromRGBColorString(fromString); |
| if (toIsCurrentColor) |
| m_toPropertyValueType = CurrentColorValue; |
| else |
| m_toColor = SVGColor::colorFromRGBColorString(toString); |
| bool fromIsValid = m_fromColor.isValid() || fromIsCurrentColor || m_fromPropertyValueType == InheritValue; |
| bool toIsValid = m_toColor.isValid() || toIsCurrentColor || m_toPropertyValueType == InheritValue; |
| if ((fromIsValid && toIsValid) || (toIsValid && animationMode() == ToAnimation)) |
| return true; |
| } else if (m_animatedAttributeType == AnimatedNumber) { |
| m_numberUnit = String(); |
| if (parseNumberValueAndUnit(toString, m_toNumber, m_numberUnit)) { |
| // For to-animations the from number is calculated later |
| if (animationMode() == ToAnimation || parseNumberValueAndUnit(fromString, m_fromNumber, m_numberUnit)) |
| return true; |
| } |
| } else if (m_animatedAttributeType == AnimatedPath) { |
| SVGPathParserFactory* factory = SVGPathParserFactory::self(); |
| if (factory->buildSVGPathByteStreamFromString(toString, m_toPath, UnalteredParsing)) { |
| // For to-animations the from number is calculated later |
| if (animationMode() == ToAnimation || factory->buildSVGPathByteStreamFromString(fromString, m_fromPath, UnalteredParsing)) |
| return true; |
| } |
| m_fromPath.clear(); |
| m_toPath.clear(); |
| } else if (m_animatedAttributeType == AnimatedPoints) { |
| m_fromPoints.clear(); |
| if (pointsListFromSVGData(m_fromPoints, fromString)) { |
| m_toPoints.clear(); |
| if (pointsListFromSVGData(m_toPoints, toString)) |
| return true; |
| } |
| } |
| m_fromString = fromString; |
| m_toString = toString; |
| m_animatedAttributeType = AnimatedString; |
| return true; |
| } |
| |
| bool SVGAnimateElement::calculateFromAndByValues(const String& fromString, const String& byString) |
| { |
| SVGElement* targetElement = this->targetElement(); |
| if (!targetElement) |
| return false; |
| m_fromPropertyValueType = inheritsFromProperty(targetElement, attributeName(), fromString) ? InheritValue : RegularPropertyValue; |
| m_toPropertyValueType = inheritsFromProperty(targetElement, attributeName(), byString) ? InheritValue : RegularPropertyValue; |
| |
| ASSERT(!hasTagName(SVGNames::setTag)); |
| m_animatedAttributeType = determineAnimatedAttributeType(targetElement); |
| if (m_animatedAttributeType == AnimatedColor) { |
| bool fromIsCurrentColor = attributeValueIsCurrentColor(fromString); |
| bool byIsCurrentColor = attributeValueIsCurrentColor(byString); |
| if (fromIsCurrentColor) |
| m_fromPropertyValueType = CurrentColorValue; |
| else |
| m_fromColor = SVGColor::colorFromRGBColorString(fromString); |
| if (byIsCurrentColor) |
| m_toPropertyValueType = CurrentColorValue; |
| else |
| m_toColor = SVGColor::colorFromRGBColorString(byString); |
| |
| if ((!m_fromColor.isValid() && !fromIsCurrentColor) |
| || (!m_toColor.isValid() && !byIsCurrentColor)) |
| return false; |
| } else { |
| m_numberUnit = String(); |
| m_fromNumber = 0; |
| if (!fromString.isEmpty() && !parseNumberValueAndUnit(fromString, m_fromNumber, m_numberUnit)) |
| return false; |
| if (!parseNumberValueAndUnit(byString, m_toNumber, m_numberUnit)) |
| return false; |
| m_toNumber += m_fromNumber; |
| } |
| return true; |
| } |
| |
| void SVGAnimateElement::resetToBaseValue(const String& baseString) |
| { |
| SVGElement* targetElement = this->targetElement(); |
| ASSERT(targetElement); |
| m_animatedString = baseString; |
| AnimatedAttributeType lastType = m_animatedAttributeType; |
| m_animatedAttributeType = determineAnimatedAttributeType(targetElement); |
| if (m_animatedAttributeType == AnimatedColor) { |
| m_animatedColor = baseString.isEmpty() ? Color() : SVGColor::colorFromRGBColorString(baseString); |
| if (isContributing(elapsed())) { |
| m_animatedAttributeType = lastType; |
| return; |
| } |
| } else if (m_animatedAttributeType == AnimatedNumber) { |
| if (baseString.isEmpty()) { |
| m_animatedNumber = 0; |
| m_numberUnit = String(); |
| return; |
| } |
| if (parseNumberValueAndUnit(baseString, m_animatedNumber, m_numberUnit)) |
| return; |
| } else if (m_animatedAttributeType == AnimatedPath) { |
| m_animatedPath.clear(); |
| SVGPathParserFactory* factory = SVGPathParserFactory::self(); |
| factory->buildSVGPathByteStreamFromString(baseString, m_animatedPath, UnalteredParsing); |
| m_animatedPathPointer = m_animatedPath.get(); |
| return; |
| } else if (m_animatedAttributeType == AnimatedPoints) { |
| m_animatedPoints.clear(); |
| return; |
| } |
| m_animatedAttributeType = AnimatedString; |
| } |
| |
| void SVGAnimateElement::applyResultsToTarget() |
| { |
| String valueToApply; |
| if (m_animatedAttributeType == AnimatedColor) |
| valueToApply = m_animatedColor.serialized(); |
| else if (m_animatedAttributeType == AnimatedNumber) |
| valueToApply = String::number(m_animatedNumber) + m_numberUnit; |
| else if (m_animatedAttributeType == AnimatedPath) { |
| if (!m_animatedPathPointer || m_animatedPathPointer->isEmpty()) |
| valueToApply = m_animatedString; |
| else { |
| // We need to keep going to string and back because we are currently only able to paint |
| // "processed" paths where complex shapes are replaced with simpler ones. Path |
| // morphing needs to be done with unprocessed paths. |
| // FIXME: This could be optimized if paths were not processed at parse time. |
| SVGPathParserFactory* factory = SVGPathParserFactory::self(); |
| factory->buildStringFromByteStream(m_animatedPathPointer, valueToApply, UnalteredParsing); |
| } |
| } else if (m_animatedAttributeType == AnimatedPoints) |
| valueToApply = m_animatedPoints.isEmpty() ? m_animatedString : m_animatedPoints.valueAsString(); |
| else |
| valueToApply = m_animatedString; |
| |
| setTargetAttributeAnimatedValue(valueToApply); |
| } |
| |
| float SVGAnimateElement::calculateDistance(const String& fromString, const String& toString) |
| { |
| SVGElement* targetElement = this->targetElement(); |
| if (!targetElement) |
| return -1; |
| m_animatedAttributeType = determineAnimatedAttributeType(targetElement); |
| if (m_animatedAttributeType == AnimatedNumber) { |
| double from; |
| double to; |
| String unit; |
| if (!parseNumberValueAndUnit(fromString, from, unit)) |
| return -1; |
| if (!parseNumberValueAndUnit(toString, to, unit)) |
| return -1; |
| return narrowPrecisionToFloat(fabs(to - from)); |
| } |
| if (m_animatedAttributeType == AnimatedColor) { |
| Color from = SVGColor::colorFromRGBColorString(fromString); |
| if (!from.isValid()) |
| return -1; |
| Color to = SVGColor::colorFromRGBColorString(toString); |
| if (!to.isValid()) |
| return -1; |
| return ColorDistance(from, to).distance(); |
| } |
| return -1; |
| } |
| |
| } |
| #endif // ENABLE(SVG) |