[Web Animations] Interpolation between lengths with an "auto" value should be discrete
[WebKit-https.git] / Source / WebCore / platform / Length.cpp
1 /*
2  * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3  *           (C) 1999 Antti Koivisto (koivisto@kde.org)
4  *           (C) 2001 Dirk Mueller ( mueller@kde.org )
5  * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2014 Apple Inc. All rights reserved.
6  * Copyright (C) 2006 Andrew Wellington (proton@wiretapped.net)
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Library General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Library General Public License for more details.
17  *
18  * You should have received a copy of the GNU Library General Public License
19  * along with this library; see the file COPYING.LIB.  If not, write to
20  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21  * Boston, MA 02110-1301, USA.
22  *
23  */
24
25 #include "config.h"
26 #include "Length.h"
27
28 #include "CalculationValue.h"
29 #include <wtf/ASCIICType.h>
30 #include <wtf/HashMap.h>
31 #include <wtf/MallocPtr.h>
32 #include <wtf/NeverDestroyed.h>
33 #include <wtf/StdLibExtras.h>
34 #include <wtf/text/StringBuffer.h>
35 #include <wtf/text/StringView.h>
36 #include <wtf/text/TextStream.h>
37
38
39 namespace WebCore {
40 using namespace WTF;
41
42 static Length parseLength(const UChar* data, unsigned length)
43 {
44     if (length == 0)
45         return Length(1, Relative);
46
47     unsigned i = 0;
48     while (i < length && isSpaceOrNewline(data[i]))
49         ++i;
50     if (i < length && (data[i] == '+' || data[i] == '-'))
51         ++i;
52     while (i < length && isASCIIDigit(data[i]))
53         ++i;
54     unsigned intLength = i;
55     while (i < length && (isASCIIDigit(data[i]) || data[i] == '.'))
56         ++i;
57     unsigned doubleLength = i;
58
59     // IE quirk: Skip whitespace between the number and the % character (20 % => 20%).
60     while (i < length && isSpaceOrNewline(data[i]))
61         ++i;
62
63     bool ok;
64     UChar next = (i < length) ? data[i] : ' ';
65     if (next == '%') {
66         // IE quirk: accept decimal fractions for percentages.
67         double r = charactersToDouble(data, doubleLength, &ok);
68         if (ok)
69             return Length(r, Percent);
70         return Length(1, Relative);
71     }
72     int r = charactersToIntStrict(data, intLength, &ok);
73     if (next == '*') {
74         if (ok)
75             return Length(r, Relative);
76         return Length(1, Relative);
77     }
78     if (ok)
79         return Length(r, Fixed);
80     return Length(0, Relative);
81 }
82
83 static unsigned countCharacter(StringImpl& string, UChar character)
84 {
85     unsigned count = 0;
86     unsigned length = string.length();
87     for (unsigned i = 0; i < length; ++i)
88         count += string[i] == character;
89     return count;
90 }
91
92 UniqueArray<Length> newCoordsArray(const String& string, int& len)
93 {
94     unsigned length = string.length();
95     StringBuffer<UChar> spacified(length);
96     for (unsigned i = 0; i < length; i++) {
97         UChar cc = string[i];
98         if (cc > '9' || (cc < '0' && cc != '-' && cc != '*' && cc != '.'))
99             spacified[i] = ' ';
100         else
101             spacified[i] = cc;
102     }
103     RefPtr<StringImpl> str = StringImpl::adopt(WTFMove(spacified));
104
105     str = str->simplifyWhiteSpace();
106
107     len = countCharacter(*str, ' ') + 1;
108     auto r = makeUniqueArray<Length>(len);
109
110     int i = 0;
111     unsigned pos = 0;
112     size_t pos2;
113
114     auto upconvertedCharacters = StringView(str.get()).upconvertedCharacters();
115     while ((pos2 = str->find(' ', pos)) != notFound) {
116         r[i++] = parseLength(upconvertedCharacters + pos, pos2 - pos);
117         pos = pos2+1;
118     }
119     r[i] = parseLength(upconvertedCharacters + pos, str->length() - pos);
120
121     ASSERT(i == len - 1);
122
123     return r;
124 }
125
126 UniqueArray<Length> newLengthArray(const String& string, int& len)
127 {
128     RefPtr<StringImpl> str = string.impl()->simplifyWhiteSpace();
129     if (!str->length()) {
130         len = 1;
131         return nullptr;
132     }
133
134     len = countCharacter(*str, ',') + 1;
135     auto r = makeUniqueArray<Length>(len);
136
137     int i = 0;
138     unsigned pos = 0;
139     size_t pos2;
140
141     auto upconvertedCharacters = StringView(str.get()).upconvertedCharacters();
142     while ((pos2 = str->find(',', pos)) != notFound) {
143         r[i++] = parseLength(upconvertedCharacters + pos, pos2 - pos);
144         pos = pos2+1;
145     }
146
147     ASSERT(i == len - 1);
148
149     // IE Quirk: If the last comma is the last char skip it and reduce len by one.
150     if (str->length()-pos > 0)
151         r[i] = parseLength(upconvertedCharacters + pos, str->length() - pos);
152     else
153         len--;
154
155     return r;
156 }
157
158 class CalculationValueMap {
159 public:
160     CalculationValueMap();
161
162     unsigned insert(Ref<CalculationValue>&&);
163     void ref(unsigned handle);
164     void deref(unsigned handle);
165
166     CalculationValue& get(unsigned handle) const;
167
168 private:
169     struct Entry {
170         uint64_t referenceCountMinusOne;
171         CalculationValue* value;
172         Entry();
173         Entry(CalculationValue&);
174     };
175
176     unsigned m_nextAvailableHandle;
177     HashMap<unsigned, Entry> m_map;
178 };
179
180 inline CalculationValueMap::Entry::Entry()
181     : referenceCountMinusOne(0)
182     , value(nullptr)
183 {
184 }
185
186 inline CalculationValueMap::Entry::Entry(CalculationValue& value)
187     : referenceCountMinusOne(0)
188     , value(&value)
189 {
190 }
191
192 inline CalculationValueMap::CalculationValueMap()
193     : m_nextAvailableHandle(1)
194 {
195 }
196     
197 inline unsigned CalculationValueMap::insert(Ref<CalculationValue>&& value)
198 {
199     ASSERT(m_nextAvailableHandle);
200
201     // The leakRef below is balanced by the adoptRef in the deref member function.
202     Entry leakedValue = value.leakRef();
203
204     // FIXME: This monotonically increasing handle generation scheme is potentially wasteful
205     // of the handle space. Consider reusing empty handles. https://bugs.webkit.org/show_bug.cgi?id=80489
206     while (!m_map.isValidKey(m_nextAvailableHandle) || !m_map.add(m_nextAvailableHandle, leakedValue).isNewEntry)
207         ++m_nextAvailableHandle;
208
209     return m_nextAvailableHandle++;
210 }
211
212 inline CalculationValue& CalculationValueMap::get(unsigned handle) const
213 {
214     ASSERT(m_map.contains(handle));
215
216     return *m_map.find(handle)->value.value;
217 }
218
219 inline void CalculationValueMap::ref(unsigned handle)
220 {
221     ASSERT(m_map.contains(handle));
222
223     ++m_map.find(handle)->value.referenceCountMinusOne;
224 }
225
226 inline void CalculationValueMap::deref(unsigned handle)
227 {
228     ASSERT(m_map.contains(handle));
229
230     auto it = m_map.find(handle);
231     if (it->value.referenceCountMinusOne) {
232         --it->value.referenceCountMinusOne;
233         return;
234     }
235
236     // The adoptRef here is balanced by the leakRef in the insert member function.
237     Ref<CalculationValue> value { adoptRef(*it->value.value) };
238
239     m_map.remove(it);
240 }
241
242 static CalculationValueMap& calculationValues()
243 {
244     static NeverDestroyed<CalculationValueMap> map;
245     return map;
246 }
247
248 Length::Length(Ref<CalculationValue>&& value)
249     : m_hasQuirk(false)
250     , m_type(Calculated)
251     , m_isFloat(false)
252 {
253     m_calculationValueHandle = calculationValues().insert(WTFMove(value));
254 }
255
256 CalculationValue& Length::calculationValue() const
257 {
258     ASSERT(isCalculated());
259     return calculationValues().get(m_calculationValueHandle);
260 }
261     
262 void Length::ref() const
263 {
264     ASSERT(isCalculated());
265     calculationValues().ref(m_calculationValueHandle);
266 }
267
268 void Length::deref() const
269 {
270     ASSERT(isCalculated());
271     calculationValues().deref(m_calculationValueHandle);
272 }
273
274 float Length::nonNanCalculatedValue(int maxValue) const
275 {
276     ASSERT(isCalculated());
277     float result = calculationValue().evaluate(maxValue);
278     if (std::isnan(result))
279         return 0;
280     return result;
281 }
282
283 bool Length::isCalculatedEqual(const Length& other) const
284 {
285     return calculationValue() == other.calculationValue();
286 }
287
288 Length convertTo100PercentMinusLength(const Length& length)
289 {
290     if (length.isPercent())
291         return Length(100 - length.value(), Percent);
292     
293     // Turn this into a calc expression: calc(100% - length)
294     Vector<std::unique_ptr<CalcExpressionNode>> lengths;
295     lengths.reserveInitialCapacity(2);
296     lengths.uncheckedAppend(std::make_unique<CalcExpressionLength>(Length(100, Percent)));
297     lengths.uncheckedAppend(std::make_unique<CalcExpressionLength>(length));
298     auto op = std::make_unique<CalcExpressionOperation>(WTFMove(lengths), CalcSubtract);
299     return Length(CalculationValue::create(WTFMove(op), ValueRangeAll));
300 }
301
302 static Length blendMixedTypes(const Length& from, const Length& to, double progress)
303 {
304     if (progress <= 0.0)
305         return from;
306         
307     if (progress >= 1.0)
308         return to;
309         
310     auto blend = std::make_unique<CalcExpressionBlendLength>(from, to, progress);
311     return Length(CalculationValue::create(WTFMove(blend), ValueRangeAll));
312 }
313
314 Length blend(const Length& from, const Length& to, double progress)
315 {
316     if (from.isAuto() || to.isAuto())
317         return progress < 0.5 ? from : to;
318
319     if (from.isUndefined() || to.isUndefined())
320         return to;
321
322     if (from.type() == Calculated || to.type() == Calculated)
323         return blendMixedTypes(from, to, progress);
324
325     if (!from.isZero() && !to.isZero() && from.type() != to.type())
326         return blendMixedTypes(from, to, progress);
327
328     LengthType resultType = to.type();
329     if (to.isZero())
330         resultType = from.type();
331
332     if (resultType == Percent) {
333         float fromPercent = from.isZero() ? 0 : from.percent();
334         float toPercent = to.isZero() ? 0 : to.percent();
335         return Length(WebCore::blend(fromPercent, toPercent, progress), Percent);
336     }
337
338     float fromValue = from.isZero() ? 0 : from.value();
339     float toValue = to.isZero() ? 0 : to.value();
340     return Length(WebCore::blend(fromValue, toValue, progress), resultType);
341 }
342
343 struct SameSizeAsLength {
344     int32_t value;
345     int32_t metaData;
346 };
347 COMPILE_ASSERT(sizeof(Length) == sizeof(SameSizeAsLength), length_should_stay_small);
348
349 static TextStream& operator<<(TextStream& ts, LengthType type)
350 {
351     switch (type) {
352     case Auto: ts << "auto"; break;
353     case Relative: ts << "relative"; break;
354     case Percent: ts << "percent"; break;
355     case Fixed: ts << "fixed"; break;
356     case Intrinsic: ts << "intrinsic"; break;
357     case MinIntrinsic: ts << "min-intrinsic"; break;
358     case MinContent: ts << "min-content"; break;
359     case MaxContent: ts << "max-content"; break;
360     case FillAvailable: ts << "fill-available"; break;
361     case FitContent: ts << "fit-content"; break;
362     case Calculated: ts << "calc"; break;
363     case Undefined: ts << "undefined"; break;
364     }
365     return ts;
366 }
367
368 TextStream& operator<<(TextStream& ts, Length length)
369 {
370     switch (length.type()) {
371     case Auto:
372     case Undefined:
373         ts << length.type();
374         break;
375     case Fixed:
376         ts << TextStream::FormatNumberRespectingIntegers(length.value()) << "px";
377         break;
378     case Relative:
379     case Intrinsic:
380     case MinIntrinsic:
381     case MinContent:
382     case MaxContent:
383     case FillAvailable:
384     case FitContent:
385         ts << length.type() << " " << TextStream::FormatNumberRespectingIntegers(length.value());
386         break;
387     case Percent:
388         ts << TextStream::FormatNumberRespectingIntegers(length.percent()) << "%";
389         break;
390     case Calculated:
391         ts << length.calculationValue();
392         break;
393     }
394     
395     if (length.hasQuirk())
396         ts << " has-quirk";
397
398     return ts;
399 }
400
401 } // namespace WebCore