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/StringConcatenateNumbers.h>
34#include <wtf/text/TextStream.h>
35
36namespace WebCore {
37
38TextStream& operator<<(TextStream& ts, const TimingFunction& timingFunction)
39{
40 switch (timingFunction.type()) {
41 case TimingFunction::LinearFunction:
42 ts << "linear";
43 break;
44 case TimingFunction::CubicBezierFunction: {
45 auto& function = downcast<CubicBezierTimingFunction>(timingFunction);
46 ts << "cubic-bezier(" << function.x1() << ", " << function.y1() << ", " << function.x2() << ", " << function.y2() << ")";
47 break;
48 }
49 case TimingFunction::StepsFunction: {
50 auto& function = downcast<StepsTimingFunction>(timingFunction);
51 ts << "steps(" << function.numberOfSteps() << ", " << (function.stepAtStart() ? "start" : "end") << ")";
52 break;
53 }
54 case TimingFunction::SpringFunction: {
55 auto& function = downcast<SpringTimingFunction>(timingFunction);
56 ts << "spring(" << function.mass() << " " << function.stiffness() << " " << function.damping() << " " << function.initialVelocity() << ")";
57 break;
58 }
59 }
60 return ts;
61}
62
63double TimingFunction::transformTime(double inputTime, double duration, bool before) const
64{
65 switch (m_type) {
66 case TimingFunction::CubicBezierFunction: {
67 auto& function = downcast<CubicBezierTimingFunction>(*this);
68 if (function.isLinear())
69 return inputTime;
70 // The epsilon value we pass to UnitBezier::solve given that the animation is going to run over |dur| seconds. The longer the
71 // animation, the more precision we need in the timing function result to avoid ugly discontinuities.
72 auto epsilon = 1.0 / (1000.0 * duration);
73 return UnitBezier(function.x1(), function.y1(), function.x2(), function.y2()).solve(inputTime, epsilon);
74 }
75 case TimingFunction::StepsFunction: {
76 // https://drafts.csswg.org/css-timing/#step-timing-functions
77 auto& function = downcast<StepsTimingFunction>(*this);
78 auto steps = function.numberOfSteps();
79 // 1. Calculate the current step as floor(input progress value × steps).
80 auto currentStep = std::floor(inputTime * steps);
81 // 2. If the step position property is start, increment current step by one.
82 if (function.stepAtStart())
83 currentStep++;
84 // 3. If both of the following conditions are true:
85 // - the before flag is set, and
86 // - input progress value × steps mod 1 equals zero (that is, if input progress value × steps is integral), then
87 // decrement current step by one.
88 if (before && !fmod(inputTime * steps, 1))
89 currentStep--;
90 // 4. If input progress value ≥ 0 and current step < 0, let current step be zero.
91 if (inputTime >= 0 && currentStep < 0)
92 currentStep = 0;
93 // 5. If input progress value ≤ 1 and current step > steps, let current step be steps.
94 if (inputTime <= 1 && currentStep > steps)
95 currentStep = steps;
96 // 6. The output progress value is current step / steps.
97 return currentStep / steps;
98 }
99 case TimingFunction::SpringFunction: {
100 auto& function = downcast<SpringTimingFunction>(*this);
101 return SpringSolver(function.mass(), function.stiffness(), function.damping(), function.initialVelocity()).solve(inputTime * duration);
102 }
103 case TimingFunction::LinearFunction:
104 return inputTime;
105 }
106
107 ASSERT_NOT_REACHED();
108 return 0;
109}
110
111ExceptionOr<RefPtr<TimingFunction>> TimingFunction::createFromCSSText(const String& cssText)
112{
113 StringBuilder cssString;
114 cssString.append(getPropertyNameString(CSSPropertyAnimationTimingFunction));
115 cssString.appendLiteral(": ");
116 cssString.append(cssText);
117 auto styleProperties = MutableStyleProperties::create();
118 styleProperties->parseDeclaration(cssString.toString(), CSSParserContext(HTMLStandardMode));
119
120 if (auto cssValue = styleProperties->getPropertyCSSValue(CSSPropertyAnimationTimingFunction)) {
121 if (auto timingFunction = createFromCSSValue(*cssValue.get()))
122 return timingFunction;
123 }
124
125 return Exception { TypeError };
126}
127
128RefPtr<TimingFunction> TimingFunction::createFromCSSValue(const CSSValue& value)
129{
130 if (is<CSSPrimitiveValue>(value)) {
131 switch (downcast<CSSPrimitiveValue>(value).valueID()) {
132 case CSSValueLinear:
133 return LinearTimingFunction::create();
134 case CSSValueEase:
135 return CubicBezierTimingFunction::create();
136 case CSSValueEaseIn:
137 return CubicBezierTimingFunction::create(CubicBezierTimingFunction::EaseIn);
138 case CSSValueEaseOut:
139 return CubicBezierTimingFunction::create(CubicBezierTimingFunction::EaseOut);
140 case CSSValueEaseInOut:
141 return CubicBezierTimingFunction::create(CubicBezierTimingFunction::EaseInOut);
142 case CSSValueStepStart:
143 return StepsTimingFunction::create(1, true);
144 case CSSValueStepEnd:
145 return StepsTimingFunction::create(1, false);
146 default:
147 return nullptr;
148 }
149 }
150
151 if (is<CSSCubicBezierTimingFunctionValue>(value)) {
152 auto& cubicTimingFunction = downcast<CSSCubicBezierTimingFunctionValue>(value);
153 return CubicBezierTimingFunction::create(cubicTimingFunction.x1(), cubicTimingFunction.y1(), cubicTimingFunction.x2(), cubicTimingFunction.y2());
154 }
155 if (is<CSSStepsTimingFunctionValue>(value)) {
156 auto& stepsTimingFunction = downcast<CSSStepsTimingFunctionValue>(value);
157 return StepsTimingFunction::create(stepsTimingFunction.numberOfSteps(), stepsTimingFunction.stepAtStart());
158 }
159 if (is<CSSSpringTimingFunctionValue>(value)) {
160 auto& springTimingFunction = downcast<CSSSpringTimingFunctionValue>(value);
161 return SpringTimingFunction::create(springTimingFunction.mass(), springTimingFunction.stiffness(), springTimingFunction.damping(), springTimingFunction.initialVelocity());
162 }
163
164 return nullptr;
165}
166
167String TimingFunction::cssText() const
168{
169 if (m_type == TimingFunction::CubicBezierFunction) {
170 auto& function = downcast<CubicBezierTimingFunction>(*this);
171 if (function.x1() == 0.25 && function.y1() == 0.1 && function.x2() == 0.25 && function.y2() == 1.0)
172 return "ease";
173 if (function.x1() == 0.42 && !function.y1() && function.x2() == 1.0 && function.y2() == 1.0)
174 return "ease-in";
175 if (!function.x1() && !function.y1() && function.x2() == 0.58 && function.y2() == 1.0)
176 return "ease-out";
177 if (function.x1() == 0.42 && !function.y1() && function.x2() == 0.58 && function.y2() == 1.0)
178 return "ease-in-out";
179 return makeString("cubic-bezier(", function.x1(), ", ", function.y1(), ", ", function.x2(), ", ", function.y2(), ')');
180 }
181
182 if (m_type == TimingFunction::StepsFunction) {
183 auto& function = downcast<StepsTimingFunction>(*this);
184 if (!function.stepAtStart())
185 return makeString("steps(", function.numberOfSteps(), ')');
186 }
187
188 TextStream stream;
189 stream << *this;
190 return stream.release();
191}
192
193} // namespace WebCore
194