1/*
2    Copyright (C) 2004, 2005 Nikolas Zimmermann <wildfox@kde.org>
3                  2004, 2005, 2006 Rob Buis <buis@kde.org>
4    Copyright (C) 2008 Apple Inc. All rights reserved.
5
6    This library is free software; you can redistribute it and/or
7    modify it under the terms of the GNU Library General Public
8    License as published by the Free Software Foundation; either
9    version 2 of the License, or (at your option) any later version.
10
11    This library is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14    Library General Public License for more details.
15
16    You should have received a copy of the GNU Library General Public License
17    along with this library; see the file COPYING.LIB.  If not, write to
18    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19    Boston, MA 02110-1301, USA.
20*/
21
22#include "config.h"
23#if ENABLE(SVG) && ENABLE(SVG_ANIMATION)
24#include "SVGAnimateElement.h"
25
26#include "ColorDistance.h"
27#include "FloatConversion.h"
28#include "SVGColor.h"
29#include "SVGParserUtilities.h"
30#include "SVGPathSegList.h"
31#include <math.h>
32
33using namespace std;
34
35namespace WebCore {
36
37SVGAnimateElement::SVGAnimateElement(const QualifiedName& tagName, Document* doc)
38    : SVGAnimationElement(tagName, doc)
39    , m_propertyType(StringProperty)
40    , m_fromNumber(0)
41    , m_toNumber(0)
42    , m_animatedNumber(numeric_limits<double>::infinity())
43{
44}
45
46SVGAnimateElement::~SVGAnimateElement()
47{
48}
49
50static bool parseNumberValueAndUnit(const String& in, double& value, String& unit)
51{
52    // FIXME: These are from top of my head, figure out all property types that can be animated as numbers.
53    unsigned unitLength = 0;
54    String parse = in.stripWhiteSpace();
55    if (parse.endsWith("%"))
56        unitLength = 1;
57    else if (parse.endsWith("px") || parse.endsWith("pt") || parse.endsWith("em"))
58        unitLength = 2;
59    else if (parse.endsWith("deg") || parse.endsWith("rad"))
60        unitLength = 3;
61    else if (parse.endsWith("grad"))
62        unitLength = 4;
63    String newUnit = parse.right(unitLength);
64    String number = parse.left(parse.length() - unitLength);
65    if ((!unit.isEmpty() && newUnit != unit) || number.isEmpty())
66        return false;
67    UChar last = number[number.length() - 1];
68    if (last < '0' || last > '9')
69        return false;
70    unit = newUnit;
71    bool ok;
72    value = number.toDouble(&ok);
73    return ok;
74}
75
76SVGAnimateElement::PropertyType SVGAnimateElement::determinePropertyType(const String& attribute) const
77{
78    // FIXME: We need a full property table for figuring this out reliably.
79    if (hasTagName(SVGNames::animateColorTag))
80        return ColorProperty;
81    if (attribute == "d")
82        return PathProperty;
83    if (attribute == "color" || attribute == "fill" || attribute == "stroke")
84        return ColorProperty;
85    return NumberProperty;
86}
87
88void SVGAnimateElement::calculateAnimatedValue(float percentage, unsigned repeat, SVGSMILElement* resultElement)
89{
90    ASSERT(percentage >= 0.f && percentage <= 1.f);
91    ASSERT(resultElement);
92    if (hasTagName(SVGNames::setTag))
93        percentage = 1.f;
94    if (!resultElement->hasTagName(SVGNames::animateTag) && !resultElement->hasTagName(SVGNames::animateColorTag)
95        && !resultElement->hasTagName(SVGNames::setTag))
96        return;
97    SVGAnimateElement* results = static_cast<SVGAnimateElement*>(resultElement);
98    // Can't accumulate over a string property.
99    if (results->m_propertyType == StringProperty && m_propertyType != StringProperty)
100        return;
101    if (m_propertyType == NumberProperty) {
102        // To animation uses contributions from the lower priority animations as the base value.
103        if (animationMode() == ToAnimation)
104            m_fromNumber = results->m_animatedNumber;
105
106        double number = (m_toNumber - m_fromNumber) * percentage + m_fromNumber;
107
108        // FIXME: This is not correct for values animation.
109        if (isAccumulated() && repeat)
110            number += m_toNumber * repeat;
111        if (isAdditive() && animationMode() != ToAnimation)
112            results->m_animatedNumber += number;
113        else
114            results->m_animatedNumber = number;
115        return;
116    }
117    if (m_propertyType == ColorProperty) {
118        if (animationMode() == ToAnimation)
119            m_fromColor = results->m_animatedColor;
120        Color color = ColorDistance(m_fromColor, m_toColor).scaledDistance(percentage).addToColorAndClamp(m_fromColor);
121        // FIXME: Accumulate colors.
122        if (isAdditive() && animationMode() != ToAnimation)
123            results->m_animatedColor = ColorDistance::addColorsAndClamp(results->m_animatedColor, color);
124        else
125            results->m_animatedColor = color;
126        return;
127    }
128    AnimationMode animationMode = this->animationMode();
129    if (m_propertyType == PathProperty) {
130        if (percentage == 0)
131            results->m_animatedPath = m_fromPath;
132        else if (percentage == 1.f)
133            results->m_animatedPath = m_toPath;
134        else {
135            if (m_fromPath && m_toPath)
136                results->m_animatedPath = SVGPathSegList::createAnimated(m_fromPath.get(), m_toPath.get(), percentage);
137            else
138                results->m_animatedPath.clear();
139            // Fall back to discrete animation if the paths are not compatible
140            if (!results->m_animatedPath)
141                results->m_animatedPath = ((animationMode == FromToAnimation && percentage > 0.5f) || animationMode == ToAnimation || percentage == 1.0f)
142                    ? m_toPath : m_fromPath;
143        }
144        return;
145    }
146    ASSERT(animationMode == FromToAnimation || animationMode == ToAnimation || animationMode == ValuesAnimation);
147    if ((animationMode == FromToAnimation && percentage > 0.5f) || animationMode == ToAnimation || percentage == 1.0f)
148        results->m_animatedString = m_toString;
149    else
150        results->m_animatedString = m_fromString;
151    // Higher priority replace animation overrides any additive results so far.
152    results->m_propertyType = StringProperty;
153}
154
155bool SVGAnimateElement::calculateFromAndToValues(const String& fromString, const String& toString)
156{
157    // FIXME: Needs more solid way determine target attribute type.
158    m_propertyType = determinePropertyType(attributeName());
159    if (m_propertyType == ColorProperty) {
160        m_fromColor = SVGColor::colorFromRGBColorString(fromString);
161        m_toColor = SVGColor::colorFromRGBColorString(toString);
162        if (m_fromColor.isValid() && m_toColor.isValid())
163            return true;
164    } else if (m_propertyType == NumberProperty) {
165        m_numberUnit = String();
166        if (parseNumberValueAndUnit(toString, m_toNumber, m_numberUnit)) {
167            // For to-animations the from number is calculated later
168            if (animationMode() == ToAnimation || parseNumberValueAndUnit(fromString, m_fromNumber, m_numberUnit))
169                return true;
170        }
171    } else if (m_propertyType == PathProperty) {
172        m_fromPath = SVGPathSegList::create(SVGNames::dAttr);
173        if (pathSegListFromSVGData(m_fromPath.get(), fromString)) {
174            m_toPath = SVGPathSegList::create(SVGNames::dAttr);
175            if (pathSegListFromSVGData(m_toPath.get(), toString))
176                return true;
177        }
178        m_fromPath.clear();
179        m_toPath.clear();
180    }
181    m_fromString = fromString;
182    m_toString = toString;
183    m_propertyType = StringProperty;
184    return true;
185}
186
187bool SVGAnimateElement::calculateFromAndByValues(const String& fromString, const String& byString)
188{
189    ASSERT(!hasTagName(SVGNames::setTag));
190    m_propertyType = determinePropertyType(attributeName());
191    if (m_propertyType == ColorProperty) {
192        m_fromColor = fromString.isEmpty() ? Color() : SVGColor::colorFromRGBColorString(fromString);
193        m_toColor = ColorDistance::addColorsAndClamp(m_fromColor, SVGColor::colorFromRGBColorString(byString));
194        if (!m_fromColor.isValid() || !m_toColor.isValid())
195            return false;
196    } else {
197        m_numberUnit = String();
198        m_fromNumber = 0;
199        if (!fromString.isEmpty() && !parseNumberValueAndUnit(fromString, m_fromNumber, m_numberUnit))
200            return false;
201        if (!parseNumberValueAndUnit(byString, m_toNumber, m_numberUnit))
202            return false;
203        m_toNumber += m_fromNumber;
204    }
205    return true;
206}
207
208void SVGAnimateElement::resetToBaseValue(const String& baseString)
209{
210    m_animatedString = baseString;
211    m_propertyType = determinePropertyType(attributeName());
212    if (m_propertyType == ColorProperty) {
213        m_animatedColor = baseString.isEmpty() ? Color() : SVGColor::colorFromRGBColorString(baseString);
214        if (m_animatedColor.isValid())
215            return;
216    } else if (m_propertyType == NumberProperty) {
217        if (baseString.isEmpty()) {
218            m_animatedNumber = 0;
219            m_numberUnit = String();
220            return;
221        }
222        if (parseNumberValueAndUnit(baseString, m_animatedNumber, m_numberUnit))
223            return;
224    } else if (m_propertyType == PathProperty) {
225        m_animatedPath.clear();
226        return;
227    }
228    m_propertyType = StringProperty;
229}
230
231void SVGAnimateElement::applyResultsToTarget()
232{
233    String valueToApply;
234    if (m_propertyType == ColorProperty)
235        valueToApply = m_animatedColor.name();
236    else if (m_propertyType == NumberProperty)
237        valueToApply = String::number(m_animatedNumber) + m_numberUnit;
238    else if (m_propertyType == PathProperty) {
239        if (!m_animatedPath || !m_animatedPath->numberOfItems())
240            valueToApply = m_animatedString;
241        else {
242            // We need to keep going to string and back because we are currently only able to paint
243            // "processed" paths where complex shapes are replaced with simpler ones. Path
244            // morphing needs to be done with unprocessed paths.
245            // FIXME: This could be optimized if paths were not processed at parse time.
246            unsigned itemCount = m_animatedPath->numberOfItems();
247            ExceptionCode ec;
248            for (unsigned n = 0; n < itemCount; ++n) {
249                RefPtr<SVGPathSeg> segment = m_animatedPath->getItem(n, ec);
250                valueToApply.append(segment->toString() + " ");
251            }
252        }
253    } else
254        valueToApply = m_animatedString;
255
256    setTargetAttributeAnimatedValue(valueToApply);
257}
258
259float SVGAnimateElement::calculateDistance(const String& fromString, const String& toString)
260{
261    m_propertyType = determinePropertyType(attributeName());
262    if (m_propertyType == NumberProperty) {
263        double from;
264        double to;
265        String unit;
266        if (!parseNumberValueAndUnit(fromString, from, unit))
267            return -1.f;
268        if (!parseNumberValueAndUnit(toString, to, unit))
269            return -1.f;
270        return narrowPrecisionToFloat(fabs(to - from));
271    } else if (m_propertyType == ColorProperty) {
272        Color from = SVGColor::colorFromRGBColorString(fromString);
273        if (!from.isValid())
274            return -1.f;
275        Color to = SVGColor::colorFromRGBColorString(toString);
276        if (!to.isValid())
277            return -1.f;
278        return ColorDistance(from, to).distance();
279    }
280    return -1.f;
281}
282
283}
284
285// vim:ts=4:noet
286#endif // ENABLE(SVG)
287
288