1/*
2 * Copyright (C) 2004, 2005, 2007, 2008 Nikolas Zimmermann <zimmermann@kde.org>
3 * Copyright (C) 2004, 2005, 2006 Rob Buis <buis@kde.org>
4 * Copyright (C) Research In Motion Limited 2010. 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#include "core/svg/SVGAngle.h"
24
25#include "bindings/core/v8/ExceptionState.h"
26#include "bindings/core/v8/ExceptionStatePlaceholder.h"
27#include "core/dom/ExceptionCode.h"
28#include "core/svg/SVGAnimationElement.h"
29#include "core/svg/SVGParserUtilities.h"
30#include "wtf/MathExtras.h"
31#include "wtf/text/WTFString.h"
32
33namespace blink {
34
35template<> const SVGEnumerationStringEntries& getStaticStringEntries<SVGMarkerOrientType>()
36{
37    DEFINE_STATIC_LOCAL(SVGEnumerationStringEntries, entries, ());
38    if (entries.isEmpty()) {
39        entries.append(std::make_pair(SVGMarkerOrientAuto, "auto"));
40        entries.append(std::make_pair(SVGMarkerOrientAngle, "angle"));
41        entries.append(std::make_pair(SVGMarkerOrientAutoStartReverse, "auto-start-reverse"));
42    }
43    return entries;
44}
45
46template<> unsigned short getMaxExposedEnumValue<SVGMarkerOrientType>()
47{
48    return SVGMarkerOrientAngle;
49}
50
51SVGMarkerOrientEnumeration::SVGMarkerOrientEnumeration(SVGAngle* angle)
52    : SVGEnumeration<SVGMarkerOrientType>(SVGMarkerOrientAngle)
53    , m_angle(angle)
54{
55}
56
57SVGMarkerOrientEnumeration::~SVGMarkerOrientEnumeration()
58{
59}
60
61void SVGMarkerOrientEnumeration::notifyChange()
62{
63    ASSERT(m_angle);
64    m_angle->orientTypeChanged();
65}
66
67void SVGMarkerOrientEnumeration::add(PassRefPtrWillBeRawPtr<SVGPropertyBase>, SVGElement*)
68{
69    // SVGMarkerOrientEnumeration is only animated via SVGAngle
70    ASSERT_NOT_REACHED();
71}
72
73void SVGMarkerOrientEnumeration::calculateAnimatedValue(SVGAnimationElement*, float percentage, unsigned repeatCount, PassRefPtr<SVGPropertyBase> from, PassRefPtr<SVGPropertyBase> to, PassRefPtr<SVGPropertyBase> toAtEndOfDurationValue, SVGElement* contextElement)
74{
75    // SVGMarkerOrientEnumeration is only animated via SVGAngle
76    ASSERT_NOT_REACHED();
77}
78
79float SVGMarkerOrientEnumeration::calculateDistance(PassRefPtr<SVGPropertyBase> to, SVGElement* contextElement)
80{
81    // SVGMarkerOrientEnumeration is only animated via SVGAngle
82    ASSERT_NOT_REACHED();
83    return -1.0;
84}
85
86SVGAngle::SVGAngle()
87    : m_unitType(SVG_ANGLETYPE_UNSPECIFIED)
88    , m_valueInSpecifiedUnits(0)
89    , m_orientType(SVGMarkerOrientEnumeration::create(this))
90{
91}
92
93SVGAngle::SVGAngle(SVGAngleType unitType, float valueInSpecifiedUnits, SVGMarkerOrientType orientType)
94    : m_unitType(unitType)
95    , m_valueInSpecifiedUnits(valueInSpecifiedUnits)
96    , m_orientType(SVGMarkerOrientEnumeration::create(this))
97{
98    m_orientType->setEnumValue(orientType);
99}
100
101SVGAngle::~SVGAngle()
102{
103}
104
105PassRefPtr<SVGAngle> SVGAngle::clone() const
106{
107    return adoptRef(new SVGAngle(m_unitType, m_valueInSpecifiedUnits, m_orientType->enumValue()));
108}
109
110float SVGAngle::value() const
111{
112    switch (m_unitType) {
113    case SVG_ANGLETYPE_GRAD:
114        return grad2deg(m_valueInSpecifiedUnits);
115    case SVG_ANGLETYPE_RAD:
116        return rad2deg(m_valueInSpecifiedUnits);
117    case SVG_ANGLETYPE_TURN:
118        return turn2deg(m_valueInSpecifiedUnits);
119    case SVG_ANGLETYPE_UNSPECIFIED:
120    case SVG_ANGLETYPE_UNKNOWN:
121    case SVG_ANGLETYPE_DEG:
122        return m_valueInSpecifiedUnits;
123    }
124
125    ASSERT_NOT_REACHED();
126    return 0;
127}
128
129void SVGAngle::setValue(float value)
130{
131    switch (m_unitType) {
132    case SVG_ANGLETYPE_GRAD:
133        m_valueInSpecifiedUnits = deg2grad(value);
134        break;
135    case SVG_ANGLETYPE_RAD:
136        m_valueInSpecifiedUnits = deg2rad(value);
137        break;
138    case SVG_ANGLETYPE_TURN:
139        m_valueInSpecifiedUnits = deg2turn(value);
140        break;
141    case SVG_ANGLETYPE_UNSPECIFIED:
142    case SVG_ANGLETYPE_UNKNOWN:
143    case SVG_ANGLETYPE_DEG:
144        m_valueInSpecifiedUnits = value;
145        break;
146    }
147    m_orientType->setEnumValue(SVGMarkerOrientAngle);
148}
149
150template<typename CharType>
151static SVGAngle::SVGAngleType stringToAngleType(const CharType*& ptr, const CharType* end)
152{
153    // If there's no unit given, the angle type is unspecified.
154    if (ptr == end)
155        return SVGAngle::SVG_ANGLETYPE_UNSPECIFIED;
156
157    SVGAngle::SVGAngleType type = SVGAngle::SVG_ANGLETYPE_UNKNOWN;
158    const CharType firstChar = *ptr++;
159
160    if (isHTMLSpace<CharType>(firstChar)) {
161        type = SVGAngle::SVG_ANGLETYPE_UNSPECIFIED;
162    } else if (end - ptr >= 2) {
163        const CharType secondChar = *ptr++;
164        const CharType thirdChar = *ptr++;
165        if (firstChar == 'd' && secondChar == 'e' && thirdChar == 'g') {
166            type = SVGAngle::SVG_ANGLETYPE_DEG;
167        } else if (firstChar == 'r' && secondChar == 'a' && thirdChar == 'd') {
168            type = SVGAngle::SVG_ANGLETYPE_RAD;
169        } else if (ptr != end) {
170            const CharType fourthChar = *ptr++;
171            if (firstChar == 'g' && secondChar == 'r' && thirdChar == 'a' && fourthChar == 'd')
172                type = SVGAngle::SVG_ANGLETYPE_GRAD;
173            else if (firstChar == 't' && secondChar == 'u' && thirdChar == 'r' && fourthChar == 'n')
174                type = SVGAngle::SVG_ANGLETYPE_TURN;
175        }
176    }
177
178    if (!skipOptionalSVGSpaces(ptr, end))
179        return type;
180
181    return SVGAngle::SVG_ANGLETYPE_UNKNOWN;
182}
183
184String SVGAngle::valueAsString() const
185{
186    switch (m_unitType) {
187    case SVG_ANGLETYPE_DEG: {
188        DEFINE_STATIC_LOCAL(String, degString, ("deg"));
189        return String::number(m_valueInSpecifiedUnits) + degString;
190    }
191    case SVG_ANGLETYPE_RAD: {
192        DEFINE_STATIC_LOCAL(String, radString, ("rad"));
193        return String::number(m_valueInSpecifiedUnits) + radString;
194    }
195    case SVG_ANGLETYPE_GRAD: {
196        DEFINE_STATIC_LOCAL(String, gradString, ("grad"));
197        return String::number(m_valueInSpecifiedUnits) + gradString;
198    }
199    case SVG_ANGLETYPE_TURN: {
200        DEFINE_STATIC_LOCAL(String, turnString, ("turn"));
201        return String::number(m_valueInSpecifiedUnits) + turnString;
202    }
203    case SVG_ANGLETYPE_UNSPECIFIED:
204    case SVG_ANGLETYPE_UNKNOWN:
205        return String::number(m_valueInSpecifiedUnits);
206    }
207
208    ASSERT_NOT_REACHED();
209    return String();
210}
211
212template<typename CharType>
213static bool parseValue(const String& value, float& valueInSpecifiedUnits, SVGAngle::SVGAngleType& unitType)
214{
215    const CharType* ptr = value.getCharacters<CharType>();
216    const CharType* end = ptr + value.length();
217
218    if (!parseNumber(ptr, end, valueInSpecifiedUnits, AllowLeadingWhitespace))
219        return false;
220
221    unitType = stringToAngleType(ptr, end);
222    if (unitType == SVGAngle::SVG_ANGLETYPE_UNKNOWN)
223        return false;
224
225    return true;
226}
227
228void SVGAngle::setValueAsString(const String& value, ExceptionState& exceptionState)
229{
230    if (value.isEmpty()) {
231        newValueSpecifiedUnits(SVG_ANGLETYPE_UNSPECIFIED, 0);
232        return;
233    }
234
235    if (value == "auto") {
236        newValueSpecifiedUnits(SVG_ANGLETYPE_UNSPECIFIED, 0);
237        m_orientType->setEnumValue(SVGMarkerOrientAuto);
238        return;
239    }
240    if (value == "auto-start-reverse") {
241        newValueSpecifiedUnits(SVG_ANGLETYPE_UNSPECIFIED, 0);
242        m_orientType->setEnumValue(SVGMarkerOrientAutoStartReverse);
243        return;
244    }
245
246    float valueInSpecifiedUnits = 0;
247    SVGAngleType unitType = SVG_ANGLETYPE_UNKNOWN;
248
249    bool success = value.is8Bit() ? parseValue<LChar>(value, valueInSpecifiedUnits, unitType)
250                                  : parseValue<UChar>(value, valueInSpecifiedUnits, unitType);
251    if (!success) {
252        exceptionState.throwDOMException(SyntaxError, "The value provided ('" + value + "') is invalid.");
253        return;
254    }
255
256    m_orientType->setEnumValue(SVGMarkerOrientAngle);
257    m_unitType = unitType;
258    m_valueInSpecifiedUnits = valueInSpecifiedUnits;
259}
260
261void SVGAngle::newValueSpecifiedUnits(SVGAngleType unitType, float valueInSpecifiedUnits)
262{
263    m_orientType->setEnumValue(SVGMarkerOrientAngle);
264    m_unitType = unitType;
265    m_valueInSpecifiedUnits = valueInSpecifiedUnits;
266}
267
268void SVGAngle::convertToSpecifiedUnits(SVGAngleType unitType, ExceptionState& exceptionState)
269{
270    if (m_unitType == SVG_ANGLETYPE_UNKNOWN) {
271        exceptionState.throwDOMException(NotSupportedError, "Cannot convert from unknown or invalid units.");
272        return;
273    }
274
275    if (unitType == m_unitType)
276        return;
277
278    switch (m_unitType) {
279    case SVG_ANGLETYPE_TURN:
280        switch (unitType) {
281        case SVG_ANGLETYPE_GRAD:
282            m_valueInSpecifiedUnits = turn2grad(m_valueInSpecifiedUnits);
283            break;
284        case SVG_ANGLETYPE_UNSPECIFIED:
285        case SVG_ANGLETYPE_DEG:
286            m_valueInSpecifiedUnits = turn2deg(m_valueInSpecifiedUnits);
287            break;
288        case SVG_ANGLETYPE_RAD:
289            m_valueInSpecifiedUnits = deg2rad(turn2deg(m_valueInSpecifiedUnits));
290            break;
291        case SVG_ANGLETYPE_TURN:
292        case SVG_ANGLETYPE_UNKNOWN:
293            ASSERT_NOT_REACHED();
294            break;
295        }
296        break;
297    case SVG_ANGLETYPE_RAD:
298        switch (unitType) {
299        case SVG_ANGLETYPE_GRAD:
300            m_valueInSpecifiedUnits = rad2grad(m_valueInSpecifiedUnits);
301            break;
302        case SVG_ANGLETYPE_UNSPECIFIED:
303        case SVG_ANGLETYPE_DEG:
304            m_valueInSpecifiedUnits = rad2deg(m_valueInSpecifiedUnits);
305            break;
306        case SVG_ANGLETYPE_TURN:
307            m_valueInSpecifiedUnits = deg2turn(rad2deg(m_valueInSpecifiedUnits));
308            break;
309        case SVG_ANGLETYPE_RAD:
310        case SVG_ANGLETYPE_UNKNOWN:
311            ASSERT_NOT_REACHED();
312            break;
313        }
314        break;
315    case SVG_ANGLETYPE_GRAD:
316        switch (unitType) {
317        case SVG_ANGLETYPE_RAD:
318            m_valueInSpecifiedUnits = grad2rad(m_valueInSpecifiedUnits);
319            break;
320        case SVG_ANGLETYPE_UNSPECIFIED:
321        case SVG_ANGLETYPE_DEG:
322            m_valueInSpecifiedUnits = grad2deg(m_valueInSpecifiedUnits);
323            break;
324        case SVG_ANGLETYPE_TURN:
325            m_valueInSpecifiedUnits = grad2turn(m_valueInSpecifiedUnits);
326            break;
327        case SVG_ANGLETYPE_GRAD:
328        case SVG_ANGLETYPE_UNKNOWN:
329            ASSERT_NOT_REACHED();
330            break;
331        }
332        break;
333    case SVG_ANGLETYPE_UNSPECIFIED:
334        // Spec: For angles, a unitless value is treated the same as if degrees were specified.
335    case SVG_ANGLETYPE_DEG:
336        switch (unitType) {
337        case SVG_ANGLETYPE_RAD:
338            m_valueInSpecifiedUnits = deg2rad(m_valueInSpecifiedUnits);
339            break;
340        case SVG_ANGLETYPE_GRAD:
341            m_valueInSpecifiedUnits = deg2grad(m_valueInSpecifiedUnits);
342            break;
343        case SVG_ANGLETYPE_TURN:
344            m_valueInSpecifiedUnits = deg2turn(m_valueInSpecifiedUnits);
345            break;
346        case SVG_ANGLETYPE_UNSPECIFIED:
347        case SVG_ANGLETYPE_DEG:
348            break;
349        case SVG_ANGLETYPE_UNKNOWN:
350            ASSERT_NOT_REACHED();
351            break;
352        }
353        break;
354    case SVG_ANGLETYPE_UNKNOWN:
355        ASSERT_NOT_REACHED();
356        break;
357    }
358
359    m_unitType = unitType;
360    m_orientType->setEnumValue(SVGMarkerOrientAngle);
361}
362
363void SVGAngle::add(PassRefPtrWillBeRawPtr<SVGPropertyBase> other, SVGElement*)
364{
365    RefPtr<SVGAngle> otherAngle = toSVGAngle(other);
366
367    // Only respect by animations, if from and by are both specified in angles (and not eg. 'auto').
368    if (orientType()->enumValue() != SVGMarkerOrientAngle || otherAngle->orientType()->enumValue() != SVGMarkerOrientAngle)
369        return;
370
371    setValue(value() + otherAngle->value());
372}
373
374void SVGAngle::calculateAnimatedValue(SVGAnimationElement* animationElement, float percentage, unsigned repeatCount, PassRefPtr<SVGPropertyBase> from, PassRefPtr<SVGPropertyBase> to, PassRefPtr<SVGPropertyBase> toAtEndOfDuration, SVGElement*)
375{
376    ASSERT(animationElement);
377    bool isToAnimation = animationElement->animationMode() == ToAnimation;
378
379    RefPtr<SVGAngle> fromAngle = isToAnimation ? this : toSVGAngle(from);
380    RefPtr<SVGAngle> toAngle = toSVGAngle(to);
381    RefPtr<SVGAngle> toAtEndOfDurationAngle = toSVGAngle(toAtEndOfDuration);
382
383    SVGMarkerOrientType fromOrientType = fromAngle->orientType()->enumValue();
384    SVGMarkerOrientType toOrientType = toAngle->orientType()->enumValue();
385
386    if (fromOrientType != toOrientType) {
387        // Animating from eg. 90deg to auto.
388        if (fromOrientType == SVGMarkerOrientAngle) {
389            // Animating from an angle value to eg. 'auto' - this disabled additive as 'auto' is a keyword..
390            if (toOrientType == SVGMarkerOrientAuto || toOrientType == SVGMarkerOrientAutoStartReverse) {
391                if (percentage < 0.5f) {
392                    newValueSpecifiedUnits(fromAngle->unitType(), fromAngle->valueInSpecifiedUnits());
393                    return;
394                }
395                orientType()->setEnumValue(toOrientType);
396                return;
397            }
398            m_valueInSpecifiedUnits = 0;
399            orientType()->setEnumValue(SVGMarkerOrientUnknown);
400            return;
401        } else if (toOrientType == SVGMarkerOrientAuto || toOrientType == SVGMarkerOrientAutoStartReverse) {
402            // Animating from e.g 'auto' to 'auto-start-reverse'
403            if (percentage >= 0.5f) {
404                m_valueInSpecifiedUnits = 0;
405                orientType()->setEnumValue(toOrientType);
406                return;
407            }
408        }
409    }
410
411    // From 'auto' to 'auto', or 'auto-start-reverse' to 'auto-start-reverse'
412    if (fromOrientType == SVGMarkerOrientAuto || fromOrientType == SVGMarkerOrientAutoStartReverse) {
413        m_valueInSpecifiedUnits = 0;
414        orientType()->setEnumValue(fromOrientType);
415        return;
416    }
417
418    // If the enumeration value is not angle or auto, its unknown.
419    if (fromOrientType != SVGMarkerOrientAngle) {
420        m_valueInSpecifiedUnits = 0;
421        orientType()->setEnumValue(SVGMarkerOrientUnknown);
422        return;
423    }
424
425    // Regular from angle to angle animation, with all features like additive etc.
426    float animatedValue = value();
427    animationElement->animateAdditiveNumber(percentage, repeatCount, fromAngle->value(), toAngle->value(), toAtEndOfDurationAngle->value(), animatedValue);
428    orientType()->setEnumValue(SVGMarkerOrientAngle);
429    setValue(animatedValue);
430}
431
432float SVGAngle::calculateDistance(PassRefPtr<SVGPropertyBase> other, SVGElement*)
433{
434    return fabsf(value() - toSVGAngle(other)->value());
435}
436
437void SVGAngle::orientTypeChanged()
438{
439    if (orientType()->enumValue() == SVGMarkerOrientAuto || orientType()->enumValue() == SVGMarkerOrientAutoStartReverse) {
440        m_unitType = SVG_ANGLETYPE_UNSPECIFIED;
441        m_valueInSpecifiedUnits = 0;
442    }
443}
444
445}
446