1/*
2 * Copyright (C) Research In Motion Limited 2010, 2011. All rights reserved.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public License
15 * along with this library; see the file COPYING.LIB.  If not, write to
16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 */
19
20#include "config.h"
21
22#if ENABLE(SVG)
23#include "SVGPathBlender.h"
24
25#include "AnimationUtilities.h"
26#include "SVGPathSeg.h"
27#include <wtf/TemporaryChange.h>
28
29namespace WebCore {
30
31SVGPathBlender::SVGPathBlender()
32    : m_fromSource(0)
33    , m_toSource(0)
34    , m_consumer(0)
35    , m_progress(0)
36    , m_addTypesCount(0)
37    , m_isInFirstHalfOfAnimation(false)
38{
39}
40
41// Helper functions
42static inline FloatPoint blendFloatPoint(const FloatPoint& a, const FloatPoint& b, float progress)
43{
44    return FloatPoint(blend(a.x(), b.x(), progress), blend(a.y(), b.y(), progress));
45}
46
47float SVGPathBlender::blendAnimatedDimensonalFloat(float from, float to, FloatBlendMode blendMode)
48{
49    if (m_addTypesCount) {
50        ASSERT(m_fromMode == m_toMode);
51        return from + to * m_addTypesCount;
52    }
53
54    if (m_fromMode == m_toMode)
55        return blend(from, to, m_progress);
56
57    float fromValue = blendMode == BlendHorizontal ? m_fromCurrentPoint.x() : m_fromCurrentPoint.y();
58    float toValue = blendMode == BlendHorizontal ? m_toCurrentPoint.x() : m_toCurrentPoint.y();
59
60    // Transform toY to the coordinate mode of fromY
61    float animValue = blend(from, m_fromMode == AbsoluteCoordinates ? to + toValue : to - toValue, m_progress);
62
63    if (m_isInFirstHalfOfAnimation)
64        return animValue;
65
66    // Transform the animated point to the coordinate mode, needed for the current progress.
67    float currentValue = blend(fromValue, toValue, m_progress);
68    return m_toMode == AbsoluteCoordinates ? animValue + currentValue : animValue - currentValue;
69}
70
71FloatPoint SVGPathBlender::blendAnimatedFloatPoint(const FloatPoint& fromPoint, const FloatPoint& toPoint)
72{
73    if (m_addTypesCount) {
74        ASSERT(m_fromMode == m_toMode);
75        FloatPoint repeatedToPoint = toPoint;
76        repeatedToPoint.scale(m_addTypesCount, m_addTypesCount);
77        return fromPoint + repeatedToPoint;
78    }
79
80    if (m_fromMode == m_toMode)
81        return blendFloatPoint(fromPoint, toPoint, m_progress);
82
83    // Transform toPoint to the coordinate mode of fromPoint
84    FloatPoint animatedPoint = toPoint;
85    if (m_fromMode == AbsoluteCoordinates)
86        animatedPoint += m_toCurrentPoint;
87    else
88        animatedPoint.move(-m_toCurrentPoint.x(), -m_toCurrentPoint.y());
89
90    animatedPoint = blendFloatPoint(fromPoint, animatedPoint, m_progress);
91
92    if (m_isInFirstHalfOfAnimation)
93        return animatedPoint;
94
95    // Transform the animated point to the coordinate mode, needed for the current progress.
96    FloatPoint currentPoint = blendFloatPoint(m_fromCurrentPoint, m_toCurrentPoint, m_progress);
97    if (m_toMode == AbsoluteCoordinates)
98        return animatedPoint + currentPoint;
99
100    animatedPoint.move(-currentPoint.x(), -currentPoint.y());
101    return animatedPoint;
102}
103
104bool SVGPathBlender::blendMoveToSegment()
105{
106    FloatPoint fromTargetPoint;
107    FloatPoint toTargetPoint;
108    if ((m_fromSource->hasMoreData() && !m_fromSource->parseMoveToSegment(fromTargetPoint))
109        || !m_toSource->parseMoveToSegment(toTargetPoint))
110        return false;
111
112    m_consumer->moveTo(blendAnimatedFloatPoint(fromTargetPoint, toTargetPoint), false, m_isInFirstHalfOfAnimation ? m_fromMode : m_toMode);
113    m_fromCurrentPoint = m_fromMode == AbsoluteCoordinates ? fromTargetPoint : m_fromCurrentPoint + fromTargetPoint;
114    m_toCurrentPoint = m_toMode == AbsoluteCoordinates ? toTargetPoint : m_toCurrentPoint + toTargetPoint;
115    return true;
116}
117
118bool SVGPathBlender::blendLineToSegment()
119{
120    FloatPoint fromTargetPoint;
121    FloatPoint toTargetPoint;
122    if ((m_fromSource->hasMoreData() && !m_fromSource->parseLineToSegment(fromTargetPoint))
123        || !m_toSource->parseLineToSegment(toTargetPoint))
124        return false;
125
126    m_consumer->lineTo(blendAnimatedFloatPoint(fromTargetPoint, toTargetPoint), m_isInFirstHalfOfAnimation ? m_fromMode : m_toMode);
127    m_fromCurrentPoint = m_fromMode == AbsoluteCoordinates ? fromTargetPoint : m_fromCurrentPoint + fromTargetPoint;
128    m_toCurrentPoint = m_toMode == AbsoluteCoordinates ? toTargetPoint : m_toCurrentPoint + toTargetPoint;
129    return true;
130}
131
132bool SVGPathBlender::blendLineToHorizontalSegment()
133{
134    float fromX = 0;
135    float toX = 0;
136    if ((m_fromSource->hasMoreData() && !m_fromSource->parseLineToHorizontalSegment(fromX))
137        || !m_toSource->parseLineToHorizontalSegment(toX))
138        return false;
139
140    m_consumer->lineToHorizontal(blendAnimatedDimensonalFloat(fromX, toX, BlendHorizontal), m_isInFirstHalfOfAnimation ? m_fromMode : m_toMode);
141    m_fromCurrentPoint.setX(m_fromMode == AbsoluteCoordinates ? fromX : m_fromCurrentPoint.x() + fromX);
142    m_toCurrentPoint.setX(m_toMode == AbsoluteCoordinates ? toX : m_toCurrentPoint.x() + toX);
143    return true;
144}
145
146bool SVGPathBlender::blendLineToVerticalSegment()
147{
148    float fromY = 0;
149    float toY = 0;
150    if ((m_fromSource->hasMoreData() && !m_fromSource->parseLineToVerticalSegment(fromY))
151        || !m_toSource->parseLineToVerticalSegment(toY))
152        return false;
153
154    m_consumer->lineToVertical(blendAnimatedDimensonalFloat(fromY, toY, BlendVertical), m_isInFirstHalfOfAnimation ? m_fromMode : m_toMode);
155    m_fromCurrentPoint.setY(m_fromMode == AbsoluteCoordinates ? fromY : m_fromCurrentPoint.y() + fromY);
156    m_toCurrentPoint.setY(m_toMode == AbsoluteCoordinates ? toY : m_toCurrentPoint.y() + toY);
157    return true;
158}
159
160bool SVGPathBlender::blendCurveToCubicSegment()
161{
162    FloatPoint fromTargetPoint;
163    FloatPoint fromPoint1;
164    FloatPoint fromPoint2;
165    FloatPoint toTargetPoint;
166    FloatPoint toPoint1;
167    FloatPoint toPoint2;
168    if ((m_fromSource->hasMoreData() && !m_fromSource->parseCurveToCubicSegment(fromPoint1, fromPoint2, fromTargetPoint))
169        || !m_toSource->parseCurveToCubicSegment(toPoint1, toPoint2, toTargetPoint))
170        return false;
171
172    m_consumer->curveToCubic(blendAnimatedFloatPoint(fromPoint1, toPoint1),
173                             blendAnimatedFloatPoint(fromPoint2, toPoint2),
174                             blendAnimatedFloatPoint(fromTargetPoint, toTargetPoint),
175                             m_isInFirstHalfOfAnimation ? m_fromMode : m_toMode);
176    m_fromCurrentPoint = m_fromMode == AbsoluteCoordinates ? fromTargetPoint : m_fromCurrentPoint + fromTargetPoint;
177    m_toCurrentPoint = m_toMode == AbsoluteCoordinates ? toTargetPoint : m_toCurrentPoint + toTargetPoint;
178    return true;
179}
180
181bool SVGPathBlender::blendCurveToCubicSmoothSegment()
182{
183    FloatPoint fromTargetPoint;
184    FloatPoint fromPoint2;
185    FloatPoint toTargetPoint;
186    FloatPoint toPoint2;
187    if ((m_fromSource->hasMoreData() && !m_fromSource->parseCurveToCubicSmoothSegment(fromPoint2, fromTargetPoint))
188        || !m_toSource->parseCurveToCubicSmoothSegment(toPoint2, toTargetPoint))
189        return false;
190
191    m_consumer->curveToCubicSmooth(blendAnimatedFloatPoint(fromPoint2, toPoint2),
192                                   blendAnimatedFloatPoint(fromTargetPoint, toTargetPoint),
193                                   m_isInFirstHalfOfAnimation ? m_fromMode : m_toMode);
194    m_fromCurrentPoint = m_fromMode == AbsoluteCoordinates ? fromTargetPoint : m_fromCurrentPoint + fromTargetPoint;
195    m_toCurrentPoint = m_toMode == AbsoluteCoordinates ? toTargetPoint : m_toCurrentPoint + toTargetPoint;
196    return true;
197}
198
199bool SVGPathBlender::blendCurveToQuadraticSegment()
200{
201    FloatPoint fromTargetPoint;
202    FloatPoint fromPoint1;
203    FloatPoint toTargetPoint;
204    FloatPoint toPoint1;
205    if ((m_fromSource->hasMoreData() && !m_fromSource->parseCurveToQuadraticSegment(fromPoint1, fromTargetPoint))
206        || !m_toSource->parseCurveToQuadraticSegment(toPoint1, toTargetPoint))
207        return false;
208
209    m_consumer->curveToQuadratic(blendAnimatedFloatPoint(fromPoint1, toPoint1),
210                                 blendAnimatedFloatPoint(fromTargetPoint, toTargetPoint),
211                                 m_isInFirstHalfOfAnimation ? m_fromMode : m_toMode);
212    m_fromCurrentPoint = m_fromMode == AbsoluteCoordinates ? fromTargetPoint : m_fromCurrentPoint + fromTargetPoint;
213    m_toCurrentPoint = m_toMode == AbsoluteCoordinates ? toTargetPoint : m_toCurrentPoint + toTargetPoint;
214    return true;
215}
216
217bool SVGPathBlender::blendCurveToQuadraticSmoothSegment()
218{
219    FloatPoint fromTargetPoint;
220    FloatPoint toTargetPoint;
221    if ((m_fromSource->hasMoreData() && !m_fromSource->parseCurveToQuadraticSmoothSegment(fromTargetPoint))
222        || !m_toSource->parseCurveToQuadraticSmoothSegment(toTargetPoint))
223        return false;
224
225    m_consumer->curveToQuadraticSmooth(blendAnimatedFloatPoint(fromTargetPoint, toTargetPoint), m_isInFirstHalfOfAnimation ? m_fromMode : m_toMode);
226    m_fromCurrentPoint = m_fromMode == AbsoluteCoordinates ? fromTargetPoint : m_fromCurrentPoint + fromTargetPoint;
227    m_toCurrentPoint = m_toMode == AbsoluteCoordinates ? toTargetPoint : m_toCurrentPoint + toTargetPoint;
228    return true;
229}
230
231bool SVGPathBlender::blendArcToSegment()
232{
233    float fromRx = 0;
234    float fromRy = 0;
235    float fromAngle = 0;
236    bool fromLargeArc = false;
237    bool fromSweep = false;
238    FloatPoint fromTargetPoint;
239    float toRx = 0;
240    float toRy = 0;
241    float toAngle = 0;
242    bool toLargeArc = false;
243    bool toSweep = false;
244    FloatPoint toTargetPoint;
245    if ((m_fromSource->hasMoreData() && !m_fromSource->parseArcToSegment(fromRx, fromRy, fromAngle, fromLargeArc, fromSweep, fromTargetPoint))
246        || !m_toSource->parseArcToSegment(toRx, toRy, toAngle, toLargeArc, toSweep, toTargetPoint))
247        return false;
248
249    if (m_addTypesCount) {
250        ASSERT(m_fromMode == m_toMode);
251        FloatPoint scaledToTargetPoint = toTargetPoint;
252        scaledToTargetPoint.scale(m_addTypesCount, m_addTypesCount);
253        m_consumer->arcTo(fromRx + toRx * m_addTypesCount,
254                          fromRy + toRy * m_addTypesCount,
255                          fromAngle + toAngle * m_addTypesCount,
256                          fromLargeArc || toLargeArc,
257                          fromSweep || toSweep,
258                          fromTargetPoint + scaledToTargetPoint,
259                          m_fromMode);
260    } else {
261        m_consumer->arcTo(blend(fromRx, toRx, m_progress),
262                          blend(fromRy, toRy, m_progress),
263                          blend(fromAngle, toAngle, m_progress),
264                          m_isInFirstHalfOfAnimation ? fromLargeArc : toLargeArc,
265                          m_isInFirstHalfOfAnimation ? fromSweep : toSweep,
266                          blendAnimatedFloatPoint(fromTargetPoint, toTargetPoint),
267                          m_isInFirstHalfOfAnimation ? m_fromMode : m_toMode);
268    }
269    m_fromCurrentPoint = m_fromMode == AbsoluteCoordinates ? fromTargetPoint : m_fromCurrentPoint + fromTargetPoint;
270    m_toCurrentPoint = m_toMode == AbsoluteCoordinates ? toTargetPoint : m_toCurrentPoint + toTargetPoint;
271    return true;
272}
273
274static inline PathCoordinateMode coordinateModeOfCommand(const SVGPathSegType& type)
275{
276    if (type < PathSegMoveToAbs)
277        return AbsoluteCoordinates;
278
279    // Odd number = relative command
280    if (type % 2)
281        return RelativeCoordinates;
282
283    return AbsoluteCoordinates;
284}
285
286static inline bool isSegmentEqual(const SVGPathSegType& fromType, const SVGPathSegType& toType, const PathCoordinateMode& fromMode, const PathCoordinateMode& toMode)
287{
288    if (fromType == toType && (fromType == PathSegUnknown || fromType == PathSegClosePath))
289        return true;
290
291    unsigned short from = fromType;
292    unsigned short to = toType;
293    if (fromMode == toMode)
294        return from == to;
295    if (fromMode == AbsoluteCoordinates)
296        return from == to - 1;
297    return to == from - 1;
298}
299
300bool SVGPathBlender::addAnimatedPath(SVGPathSource* fromSource, SVGPathSource* toSource, SVGPathConsumer* consumer, unsigned repeatCount)
301{
302    TemporaryChange<unsigned> change(m_addTypesCount, repeatCount);
303    return blendAnimatedPath(0, fromSource, toSource, consumer);
304}
305
306bool SVGPathBlender::blendAnimatedPath(float progress, SVGPathSource* fromSource, SVGPathSource* toSource, SVGPathConsumer* consumer)
307{
308    ASSERT(fromSource);
309    ASSERT(toSource);
310    ASSERT(consumer);
311    m_fromSource = fromSource;
312    m_toSource = toSource;
313    m_consumer = consumer;
314    m_isInFirstHalfOfAnimation = progress < 0.5f;
315    m_progress = progress;
316
317    bool fromSourceHadData = m_fromSource->hasMoreData();
318    while (m_toSource->hasMoreData()) {
319        SVGPathSegType fromCommand;
320        SVGPathSegType toCommand;
321        if ((fromSourceHadData && !m_fromSource->parseSVGSegmentType(fromCommand)) || !m_toSource->parseSVGSegmentType(toCommand))
322            return false;
323
324        m_toMode = coordinateModeOfCommand(toCommand);
325        m_fromMode = fromSourceHadData ? coordinateModeOfCommand(fromCommand) : m_toMode;
326        if (m_fromMode != m_toMode && m_addTypesCount)
327            return false;
328
329        if (fromSourceHadData && !isSegmentEqual(fromCommand, toCommand, m_fromMode, m_toMode))
330            return false;
331
332        switch (toCommand) {
333        case PathSegMoveToRel:
334        case PathSegMoveToAbs:
335            if (!blendMoveToSegment())
336                return false;
337            break;
338        case PathSegLineToRel:
339        case PathSegLineToAbs:
340            if (!blendLineToSegment())
341                return false;
342            break;
343        case PathSegLineToHorizontalRel:
344        case PathSegLineToHorizontalAbs:
345            if (!blendLineToHorizontalSegment())
346                return false;
347            break;
348        case PathSegLineToVerticalRel:
349        case PathSegLineToVerticalAbs:
350            if (!blendLineToVerticalSegment())
351                return false;
352            break;
353        case PathSegClosePath:
354            m_consumer->closePath();
355            break;
356        case PathSegCurveToCubicRel:
357        case PathSegCurveToCubicAbs:
358            if (!blendCurveToCubicSegment())
359                return false;
360            break;
361        case PathSegCurveToCubicSmoothRel:
362        case PathSegCurveToCubicSmoothAbs:
363            if (!blendCurveToCubicSmoothSegment())
364                return false;
365            break;
366        case PathSegCurveToQuadraticRel:
367        case PathSegCurveToQuadraticAbs:
368            if (!blendCurveToQuadraticSegment())
369                return false;
370            break;
371        case PathSegCurveToQuadraticSmoothRel:
372        case PathSegCurveToQuadraticSmoothAbs:
373            if (!blendCurveToQuadraticSmoothSegment())
374                return false;
375            break;
376        case PathSegArcRel:
377        case PathSegArcAbs:
378            if (!blendArcToSegment())
379                return false;
380            break;
381        case PathSegUnknown:
382            return false;
383        }
384
385        if (!fromSourceHadData)
386            continue;
387        if (m_fromSource->hasMoreData() != m_toSource->hasMoreData())
388            return false;
389        if (!m_fromSource->hasMoreData() || !m_toSource->hasMoreData())
390            return true;
391    }
392
393    return true;
394}
395
396void SVGPathBlender::cleanup()
397{
398    ASSERT(m_toSource);
399    ASSERT(m_fromSource);
400    ASSERT(m_consumer);
401
402    m_consumer->cleanup();
403    m_toSource = 0;
404    m_fromSource = 0;
405    m_consumer = 0;
406    m_fromCurrentPoint = FloatPoint();
407    m_toCurrentPoint = FloatPoint();
408}
409
410}
411
412#endif // ENABLE(SVG)
413