SVGAnimateElement.cpp 11.2 KB
Newer Older
eseidel's avatar
eseidel committed
1
/*
eseidel's avatar
eseidel committed
2
    Copyright (C) 2004, 2005 Nikolas Zimmermann <wildfox@kde.org>
rwlbuis's avatar
rwlbuis committed
3
                  2004, 2005, 2006 Rob Buis <buis@kde.org>
4
    Copyright (C) 2008 Apple Inc. All rights reserved.
eseidel's avatar
eseidel committed
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

    This file is part of the KDE project

    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
ddkilzer's avatar
ddkilzer committed
20
21
    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
    Boston, MA 02110-1301, USA.
eseidel's avatar
eseidel committed
22
23
*/

eseidel's avatar
eseidel committed
24
#include "config.h"
25
#if ENABLE(SVG) && ENABLE(SVG_ANIMATION)
darin's avatar
darin committed
26
#include "SVGAnimateElement.h"
27

28
#include "ColorDistance.h"
29
#include "FloatConversion.h"
30
31
#include "SVGColor.h"
#include "SVGParserUtilities.h"
antti@apple.com's avatar
antti@apple.com committed
32
#include "SVGPathSegList.h"
33
#include <math.h>
34
35

using namespace std;
eseidel's avatar
eseidel committed
36

rwlbuis's avatar
rwlbuis committed
37
namespace WebCore {
eseidel's avatar
eseidel committed
38

rwlbuis's avatar
rwlbuis committed
39
SVGAnimateElement::SVGAnimateElement(const QualifiedName& tagName, Document* doc)
40
    : SVGAnimationElement(tagName, doc)
41
42
43
44
    , m_propertyType(StringProperty)
    , m_fromNumber(0)
    , m_toNumber(0)
    , m_animatedNumber(numeric_limits<double>::infinity())
eseidel's avatar
eseidel committed
45
46
47
{
}

darin's avatar
darin committed
48
SVGAnimateElement::~SVGAnimateElement()
eseidel's avatar
eseidel committed
49
50
51
{
}

52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
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;
}

78
SVGAnimateElement::PropertyType SVGAnimateElement::determinePropertyType(const String& attribute) const
79
{
80
81
82
    // FIXME: We need a full property table for figuring this out reliably.
    if (hasTagName(SVGNames::animateColorTag))
        return ColorProperty;
antti@apple.com's avatar
antti@apple.com committed
83
84
    if (attribute == "d")
        return PathProperty;
85
86
87
    if (attribute == "color" || attribute == "fill" || attribute == "stroke")
        return ColorProperty;
    return NumberProperty;
88
89
}

90
void SVGAnimateElement::calculateAnimatedValue(float percentage, unsigned repeat, SVGSMILElement* resultElement)
91
{
92
93
94
95
96
97
98
99
100
101
102
    ASSERT(percentage >= 0.f && percentage <= 1.f);
    ASSERT(resultElement);
    if (hasTagName(SVGNames::setTag))
        percentage = 1.f;
    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_propertyType == StringProperty && m_propertyType != StringProperty)
        return;
103
    if (m_propertyType == NumberProperty) {
104
105
106
107
108
109
110
111
112
113
114
115
116
117
        // To animation uses contributions from the lower priority animations as the base value.
        if (animationMode() == ToAnimation)
            m_fromNumber = results->m_animatedNumber;
    
        double 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;
118
119
    } 
    if (m_propertyType == ColorProperty) {
120
121
122
123
124
125
126
127
128
        if (animationMode() == ToAnimation)
            m_fromColor = results->m_animatedColor;
        Color 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;
129
    }
antti@apple.com's avatar
antti@apple.com committed
130
    AnimationMode animationMode = this->animationMode();
antti@apple.com's avatar
antti@apple.com committed
131
    if (m_propertyType == PathProperty) {
antti@apple.com's avatar
antti@apple.com committed
132
133
134
135
136
137
138
139
140
141
142
143
144
145
        if (percentage == 0)
            results->m_animatedPath = m_fromPath;
        else if (percentage == 1.f)
            results->m_animatedPath = m_toPath;
        else {
            if (m_fromPath && m_toPath)
                results->m_animatedPath = SVGPathSegList::createAnimated(m_fromPath.get(), m_toPath.get(), percentage);
            else
                results->m_animatedPath.clear();
            // Fall back to discrete animation if the paths are not compatible
            if (!results->m_animatedPath)
                results->m_animatedPath = ((animationMode == FromToAnimation && percentage > 0.5f) || animationMode == ToAnimation || percentage == 1.0f) 
                    ? m_toPath : m_fromPath;
        }
antti@apple.com's avatar
antti@apple.com committed
146
147
        return;
    }
148
149
    ASSERT(animationMode == FromToAnimation || animationMode == ToAnimation || animationMode == ValuesAnimation);
    if ((animationMode == FromToAnimation && percentage > 0.5f) || animationMode == ToAnimation || percentage == 1.0f)
150
        results->m_animatedString = m_toString;
151
    else
152
153
154
        results->m_animatedString = m_fromString;
    // Higher priority replace animation overrides any additive results so far.
    results->m_propertyType = StringProperty;
155
156
157
158
159
}

bool SVGAnimateElement::calculateFromAndToValues(const String& fromString, const String& toString)
{
    // FIXME: Needs more solid way determine target attribute type.
160
    m_propertyType = determinePropertyType(attributeName());
161
162
163
164
165
    if (m_propertyType == ColorProperty) {
        m_fromColor = SVGColor::colorFromRGBColorString(fromString);
        m_toColor = SVGColor::colorFromRGBColorString(toString);
        if (m_fromColor.isValid() && m_toColor.isValid())
            return true;
166
    } else if (m_propertyType == NumberProperty) {
167
        m_numberUnit = String();
168
169
170
        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))
171
172
                return true;
        }
antti@apple.com's avatar
antti@apple.com committed
173
174
175
176
177
178
179
180
181
    } else if (m_propertyType == PathProperty) {
        m_fromPath = SVGPathSegList::create(SVGNames::dAttr);
        if (pathSegListFromSVGData(m_fromPath.get(), fromString)) {
            m_toPath = SVGPathSegList::create(SVGNames::dAttr);
            if (pathSegListFromSVGData(m_toPath.get(), toString))
                return true;
        }
        m_fromPath.clear();
        m_toPath.clear();
182
183
184
185
186
187
188
189
190
    }
    m_fromString = fromString;
    m_toString = toString;
    m_propertyType = StringProperty;
    return true;
}

bool SVGAnimateElement::calculateFromAndByValues(const String& fromString, const String& byString)
{
191
192
    ASSERT(!hasTagName(SVGNames::setTag));
    m_propertyType = determinePropertyType(attributeName());
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
    if (m_propertyType == ColorProperty) {
        m_fromColor = fromString.isEmpty() ? Color() : SVGColor::colorFromRGBColorString(fromString);
        m_toColor = ColorDistance::addColorsAndClamp(m_fromColor, SVGColor::colorFromRGBColorString(byString));
        if (!m_fromColor.isValid() || !m_toColor.isValid())
            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;
}

210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
void SVGAnimateElement::resetToBaseValue(const String& baseString)
{
    m_animatedString = baseString;
    m_propertyType = determinePropertyType(attributeName());
    if (m_propertyType == ColorProperty) {
        m_animatedColor = baseString.isEmpty() ? Color() : SVGColor::colorFromRGBColorString(baseString);
        if (m_animatedColor.isValid())
            return;
    } else if (m_propertyType == NumberProperty) {
        if (baseString.isEmpty()) {
            m_animatedNumber = 0;
            m_numberUnit = String();
            return;
        }
        if (parseNumberValueAndUnit(baseString, m_animatedNumber, m_numberUnit))
            return;
antti@apple.com's avatar
antti@apple.com committed
226
227
228
    } else if (m_propertyType == PathProperty) {
        m_animatedPath.clear();
        return;
229
230
231
232
233
234
235
236
237
238
239
    }
    m_propertyType = StringProperty;
}
    
void SVGAnimateElement::applyResultsToTarget()
{
    String valueToApply;
    if (m_propertyType == ColorProperty)
        valueToApply = m_animatedColor.name();
    else if (m_propertyType == NumberProperty)
        valueToApply = String::number(m_animatedNumber) + m_numberUnit;
antti@apple.com's avatar
antti@apple.com committed
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
    else if (m_propertyType == PathProperty) {
        if (!m_animatedPath || !m_animatedPath->numberOfItems())
            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.
            unsigned itemCount = m_animatedPath->numberOfItems();
            ExceptionCode ec;
            for (unsigned n = 0; n < itemCount; ++n) {
                RefPtr<SVGPathSeg> segment = m_animatedPath->getItem(n, ec);
                valueToApply.append(segment->toString() + " ");
            }
        }
    } else
256
257
258
259
260
        valueToApply = m_animatedString;
    
    setTargetAttributeAnimatedValue(valueToApply);
}
    
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
float SVGAnimateElement::calculateDistance(const String& fromString, const String& toString)
{
    m_propertyType = determinePropertyType(attributeName());
    if (m_propertyType == NumberProperty) {
        double from;
        double to;
        String unit;
        if (!parseNumberValueAndUnit(fromString, from, unit))
            return -1.f;
        if (!parseNumberValueAndUnit(toString, to, unit))
            return -1.f;
        return narrowPrecisionToFloat(fabs(to - from));
    } else if (m_propertyType == ColorProperty) {
        Color from = SVGColor::colorFromRGBColorString(fromString);
        if (!from.isValid())
            return -1.f;
        Color to = SVGColor::colorFromRGBColorString(toString);
        if (!to.isValid())
            return -1.f;
        return ColorDistance(from, to).distance();
    }
    return -1.f;
}
   
rwlbuis's avatar
rwlbuis committed
285
286
}

eseidel's avatar
eseidel committed
287
// vim:ts=4:noet
mjs's avatar
mjs committed
288
#endif // ENABLE(SVG)
289