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