Unreviewed, rolling out r234489.
[WebKit-https.git] / Source / WebCore / platform / animation / TimingFunction.cpp
1 /*
2  * Copyright (C) 2015 Apple Inc.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
24  */
25
26 #include "config.h"
27 #include "TimingFunction.h"
28
29 #include "CSSTimingFunctionValue.h"
30 #include "SpringSolver.h"
31 #include "StyleProperties.h"
32 #include "UnitBezier.h"
33 #include <wtf/text/TextStream.h>
34
35 namespace WebCore {
36
37 TextStream& operator<<(TextStream& ts, const TimingFunction& timingFunction)
38 {
39     switch (timingFunction.type()) {
40     case TimingFunction::LinearFunction:
41         ts << "linear";
42         break;
43     case TimingFunction::CubicBezierFunction: {
44         auto& function = downcast<CubicBezierTimingFunction>(timingFunction);
45         ts << "cubic-bezier(" << function.x1() << ", " << function.y1() << ", " <<  function.x2() << ", " << function.y2() << ")";
46         break;
47     }
48     case TimingFunction::StepsFunction: {
49         auto& function = downcast<StepsTimingFunction>(timingFunction);
50         ts << "steps(" << function.numberOfSteps() << ", " << (function.stepAtStart() ? "start" : "end") << ")";
51         break;
52     }
53     case TimingFunction::FramesFunction: {
54         auto& function = downcast<FramesTimingFunction>(timingFunction);
55         ts << "frames(" << function.numberOfFrames() << ")";
56         break;
57     }
58     case TimingFunction::SpringFunction: {
59         auto& function = downcast<SpringTimingFunction>(timingFunction);
60         ts << "spring(" << function.mass() << " " << function.stiffness() << " " <<  function.damping() << " " << function.initialVelocity() << ")";
61         break;
62     }
63     }
64     return ts;
65 }
66
67 double TimingFunction::transformTime(double inputTime, double duration, bool before) const
68 {
69     switch (m_type) {
70     case TimingFunction::CubicBezierFunction: {
71         auto& function = downcast<CubicBezierTimingFunction>(*this);
72         if (function.isLinear())
73             return inputTime;
74         // The epsilon value we pass to UnitBezier::solve given that the animation is going to run over |dur| seconds. The longer the
75         // animation, the more precision we need in the timing function result to avoid ugly discontinuities.
76         auto epsilon = 1.0 / (1000.0 * duration);
77         return UnitBezier(function.x1(), function.y1(), function.x2(), function.y2()).solve(inputTime, epsilon);
78     }
79     case TimingFunction::StepsFunction: {
80         // https://drafts.csswg.org/css-timing/#step-timing-functions
81         auto& function = downcast<StepsTimingFunction>(*this);
82         auto steps = function.numberOfSteps();
83         // 1. Calculate the current step as floor(input progress value × steps).
84         auto currentStep = std::floor(inputTime * steps);
85         // 2. If the step position property is start, increment current step by one.
86         if (function.stepAtStart())
87             currentStep++;
88         // 3. If both of the following conditions are true:
89         //    - the before flag is set, and
90         //    - input progress value × steps mod 1 equals zero (that is, if input progress value × steps is integral), then
91         //    decrement current step by one.
92         if (before && !fmod(inputTime * steps, 1))
93             currentStep--;
94         // 4. If input progress value ≥ 0 and current step < 0, let current step be zero.
95         if (inputTime >= 0 && currentStep < 0)
96             currentStep = 0;
97         // 5. If input progress value ≤ 1 and current step > steps, let current step be steps.
98         if (inputTime <= 1 && currentStep > steps)
99             currentStep = steps;
100         // 6. The output progress value is current step / steps.
101         return currentStep / steps;
102     }
103     case TimingFunction::FramesFunction: {
104         // https://drafts.csswg.org/css-timing/#frames-timing-functions
105         auto& function = downcast<FramesTimingFunction>(*this);
106         auto numberOfFrames = function.numberOfFrames();
107         ASSERT(numberOfFrames > 1);
108         auto outputTime = std::floor(inputTime * numberOfFrames) / (numberOfFrames - 1);
109         if (inputTime <= 1 && outputTime > 1)
110             return 1;
111         return outputTime;
112     }
113     case TimingFunction::SpringFunction: {
114         auto& function = downcast<SpringTimingFunction>(*this);
115         return SpringSolver(function.mass(), function.stiffness(), function.damping(), function.initialVelocity()).solve(inputTime * duration);
116     }
117     case TimingFunction::LinearFunction:
118         return inputTime;
119     }
120
121     ASSERT_NOT_REACHED();
122     return 0;
123 }
124
125 ExceptionOr<RefPtr<TimingFunction>> TimingFunction::createFromCSSText(const String& cssText)
126 {
127     StringBuilder cssString;
128     cssString.append(getPropertyNameString(CSSPropertyAnimationTimingFunction));
129     cssString.appendLiteral(": ");
130     cssString.append(cssText);
131     auto styleProperties = MutableStyleProperties::create();
132     styleProperties->parseDeclaration(cssString.toString(), CSSParserContext(HTMLStandardMode));
133
134     if (auto cssValue = styleProperties->getPropertyCSSValue(CSSPropertyAnimationTimingFunction)) {
135         if (auto timingFunction = createFromCSSValue(*cssValue.get()))
136             return WTFMove(timingFunction);
137     }
138     
139     return Exception { TypeError };
140 }
141
142 RefPtr<TimingFunction> TimingFunction::createFromCSSValue(const CSSValue& value)
143 {
144     if (is<CSSPrimitiveValue>(value)) {
145         switch (downcast<CSSPrimitiveValue>(value).valueID()) {
146         case CSSValueLinear:
147             return LinearTimingFunction::create();
148         case CSSValueEase:
149             return CubicBezierTimingFunction::create();
150         case CSSValueEaseIn:
151             return CubicBezierTimingFunction::create(CubicBezierTimingFunction::EaseIn);
152         case CSSValueEaseOut:
153             return CubicBezierTimingFunction::create(CubicBezierTimingFunction::EaseOut);
154         case CSSValueEaseInOut:
155             return CubicBezierTimingFunction::create(CubicBezierTimingFunction::EaseInOut);
156         case CSSValueStepStart:
157             return StepsTimingFunction::create(1, true);
158         case CSSValueStepEnd:
159             return StepsTimingFunction::create(1, false);
160         default:
161             return nullptr;
162         }
163     }
164
165     if (is<CSSCubicBezierTimingFunctionValue>(value)) {
166         auto& cubicTimingFunction = downcast<CSSCubicBezierTimingFunctionValue>(value);
167         return CubicBezierTimingFunction::create(cubicTimingFunction.x1(), cubicTimingFunction.y1(), cubicTimingFunction.x2(), cubicTimingFunction.y2());
168     }
169     if (is<CSSStepsTimingFunctionValue>(value)) {
170         auto& stepsTimingFunction = downcast<CSSStepsTimingFunctionValue>(value);
171         return StepsTimingFunction::create(stepsTimingFunction.numberOfSteps(), stepsTimingFunction.stepAtStart());
172     }
173     if (is<CSSFramesTimingFunctionValue>(value)) {
174         auto& framesTimingFunction = downcast<CSSFramesTimingFunctionValue>(value);
175         return FramesTimingFunction::create(framesTimingFunction.numberOfFrames());
176     }
177     if (is<CSSSpringTimingFunctionValue>(value)) {
178         auto& springTimingFunction = downcast<CSSSpringTimingFunctionValue>(value);
179         return SpringTimingFunction::create(springTimingFunction.mass(), springTimingFunction.stiffness(), springTimingFunction.damping(), springTimingFunction.initialVelocity());
180     }
181
182     return nullptr;
183 }
184
185 String TimingFunction::cssText() const
186 {
187     if (m_type == TimingFunction::CubicBezierFunction) {
188         auto& function = downcast<CubicBezierTimingFunction>(*this);
189         if (function.x1() == 0.25 && function.y1() == 0.1 && function.x2() == 0.25 && function.y2() == 1.0)
190             return "ease";
191         if (function.x1() == 0.42 && !function.y1() && function.x2() == 1.0 && function.y2() == 1.0)
192             return "ease-in";
193         if (!function.x1() && !function.y1() && function.x2() == 0.58 && function.y2() == 1.0)
194             return "ease-out";
195         if (function.x1() == 0.42 && !function.y1() && function.x2() == 0.58 && function.y2() == 1.0)
196             return "ease-in-out";
197         return String::format("cubic-bezier(%g, %g, %g, %g)", function.x1(), function.y1(), function.x2(), function.y2());
198     }
199
200     if (m_type == TimingFunction::StepsFunction) {
201         auto& function = downcast<StepsTimingFunction>(*this);
202         if (!function.stepAtStart())
203             return String::format("steps(%d)", function.numberOfSteps());
204     }
205
206     TextStream stream;
207     stream << *this;
208     return stream.release();
209 }
210
211 } // namespace WebCore