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-2019 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/StringView.h>
35#include <wtf/text/TextStream.h>
36
37namespace WebCore {
38
39static Length parseLength(const UChar* data, unsigned length)
40{
41 if (length == 0)
42 return Length(1, Relative);
43
44 unsigned i = 0;
45 while (i < length && isSpaceOrNewline(data[i]))
46 ++i;
47 if (i < length && (data[i] == '+' || data[i] == '-'))
48 ++i;
49 while (i < length && isASCIIDigit(data[i]))
50 ++i;
51 unsigned intLength = i;
52 while (i < length && (isASCIIDigit(data[i]) || data[i] == '.'))
53 ++i;
54 unsigned doubleLength = i;
55
56 // IE quirk: Skip whitespace between the number and the % character (20 % => 20%).
57 while (i < length && isSpaceOrNewline(data[i]))
58 ++i;
59
60 bool ok;
61 UChar next = (i < length) ? data[i] : ' ';
62 if (next == '%') {
63 // IE quirk: accept decimal fractions for percentages.
64 double r = charactersToDouble(data, doubleLength, &ok);
65 if (ok)
66 return Length(r, Percent);
67 return Length(1, Relative);
68 }
69 int r = charactersToIntStrict(data, intLength, &ok);
70 if (next == '*') {
71 if (ok)
72 return Length(r, Relative);
73 return Length(1, Relative);
74 }
75 if (ok)
76 return Length(r, Fixed);
77 return Length(0, Relative);
78}
79
80static unsigned countCharacter(StringImpl& string, UChar character)
81{
82 unsigned count = 0;
83 unsigned length = string.length();
84 for (unsigned i = 0; i < length; ++i)
85 count += string[i] == character;
86 return count;
87}
88
89UniqueArray<Length> newCoordsArray(const String& string, int& len)
90{
91 unsigned length = string.length();
92 UChar* spacified;
93 auto str = StringImpl::createUninitialized(length, spacified);
94 for (unsigned i = 0; i < length; i++) {
95 UChar cc = string[i];
96 if (cc > '9' || (cc < '0' && cc != '-' && cc != '*' && cc != '.'))
97 spacified[i] = ' ';
98 else
99 spacified[i] = cc;
100 }
101
102 str = str->simplifyWhiteSpace();
103
104 len = countCharacter(str, ' ') + 1;
105 auto r = makeUniqueArray<Length>(len);
106
107 int i = 0;
108 unsigned pos = 0;
109 size_t pos2;
110
111 while ((pos2 = str->find(' ', pos)) != notFound) {
112 r[i++] = parseLength(str->characters16() + pos, pos2 - pos);
113 pos = pos2+1;
114 }
115 r[i] = parseLength(str->characters16() + pos, str->length() - pos);
116
117 ASSERT(i == len - 1);
118
119 return r;
120}
121
122UniqueArray<Length> newLengthArray(const String& string, int& len)
123{
124 RefPtr<StringImpl> str = string.impl()->simplifyWhiteSpace();
125 if (!str->length()) {
126 len = 1;
127 return nullptr;
128 }
129
130 len = countCharacter(*str, ',') + 1;
131 auto r = makeUniqueArray<Length>(len);
132
133 int i = 0;
134 unsigned pos = 0;
135 size_t pos2;
136
137 auto upconvertedCharacters = StringView(str.get()).upconvertedCharacters();
138 while ((pos2 = str->find(',', pos)) != notFound) {
139 r[i++] = parseLength(upconvertedCharacters + pos, pos2 - pos);
140 pos = pos2+1;
141 }
142
143 ASSERT(i == len - 1);
144
145 // IE Quirk: If the last comma is the last char skip it and reduce len by one.
146 if (str->length()-pos > 0)
147 r[i] = parseLength(upconvertedCharacters + pos, str->length() - pos);
148 else
149 len--;
150
151 return r;
152}
153
154class CalculationValueMap {
155public:
156 CalculationValueMap();
157
158 unsigned insert(Ref<CalculationValue>&&);
159 void ref(unsigned handle);
160 void deref(unsigned handle);
161
162 CalculationValue& get(unsigned handle) const;
163
164private:
165 struct Entry {
166 uint64_t referenceCountMinusOne;
167 CalculationValue* value;
168 Entry();
169 Entry(CalculationValue&);
170 };
171
172 unsigned m_nextAvailableHandle;
173 HashMap<unsigned, Entry> m_map;
174};
175
176inline CalculationValueMap::Entry::Entry()
177 : referenceCountMinusOne(0)
178 , value(nullptr)
179{
180}
181
182inline CalculationValueMap::Entry::Entry(CalculationValue& value)
183 : referenceCountMinusOne(0)
184 , value(&value)
185{
186}
187
188inline CalculationValueMap::CalculationValueMap()
189 : m_nextAvailableHandle(1)
190{
191}
192
193inline unsigned CalculationValueMap::insert(Ref<CalculationValue>&& value)
194{
195 ASSERT(m_nextAvailableHandle);
196
197 // The leakRef below is balanced by the adoptRef in the deref member function.
198 Entry leakedValue = value.leakRef();
199
200 // FIXME: This monotonically increasing handle generation scheme is potentially wasteful
201 // of the handle space. Consider reusing empty handles. https://bugs.webkit.org/show_bug.cgi?id=80489
202 while (!m_map.isValidKey(m_nextAvailableHandle) || !m_map.add(m_nextAvailableHandle, leakedValue).isNewEntry)
203 ++m_nextAvailableHandle;
204
205 return m_nextAvailableHandle++;
206}
207
208inline CalculationValue& CalculationValueMap::get(unsigned handle) const
209{
210 ASSERT(m_map.contains(handle));
211
212 return *m_map.find(handle)->value.value;
213}
214
215inline void CalculationValueMap::ref(unsigned handle)
216{
217 ASSERT(m_map.contains(handle));
218
219 ++m_map.find(handle)->value.referenceCountMinusOne;
220}
221
222inline void CalculationValueMap::deref(unsigned handle)
223{
224 ASSERT(m_map.contains(handle));
225
226 auto it = m_map.find(handle);
227 if (it->value.referenceCountMinusOne) {
228 --it->value.referenceCountMinusOne;
229 return;
230 }
231
232 // The adoptRef here is balanced by the leakRef in the insert member function.
233 Ref<CalculationValue> value { adoptRef(*it->value.value) };
234
235 m_map.remove(it);
236}
237
238static CalculationValueMap& calculationValues()
239{
240 static NeverDestroyed<CalculationValueMap> map;
241 return map;
242}
243
244Length::Length(Ref<CalculationValue>&& value)
245 : m_hasQuirk(false)
246 , m_type(Calculated)
247 , m_isFloat(false)
248{
249 m_calculationValueHandle = calculationValues().insert(WTFMove(value));
250}
251
252CalculationValue& Length::calculationValue() const
253{
254 ASSERT(isCalculated());
255 return calculationValues().get(m_calculationValueHandle);
256}
257
258void Length::ref() const
259{
260 ASSERT(isCalculated());
261 calculationValues().ref(m_calculationValueHandle);
262}
263
264void Length::deref() const
265{
266 ASSERT(isCalculated());
267 calculationValues().deref(m_calculationValueHandle);
268}
269
270float Length::nonNanCalculatedValue(int maxValue) const
271{
272 ASSERT(isCalculated());
273 float result = calculationValue().evaluate(maxValue);
274 if (std::isnan(result))
275 return 0;
276 return result;
277}
278
279bool Length::isCalculatedEqual(const Length& other) const
280{
281 return calculationValue() == other.calculationValue();
282}
283
284Length convertTo100PercentMinusLength(const Length& length)
285{
286 if (length.isPercent())
287 return Length(100 - length.value(), Percent);
288
289 // Turn this into a calc expression: calc(100% - length)
290 Vector<std::unique_ptr<CalcExpressionNode>> lengths;
291 lengths.reserveInitialCapacity(2);
292 lengths.uncheckedAppend(std::make_unique<CalcExpressionLength>(Length(100, Percent)));
293 lengths.uncheckedAppend(std::make_unique<CalcExpressionLength>(length));
294 auto op = std::make_unique<CalcExpressionOperation>(WTFMove(lengths), CalcOperator::Subtract);
295 return Length(CalculationValue::create(WTFMove(op), ValueRangeAll));
296}
297
298static Length blendMixedTypes(const Length& from, const Length& to, double progress)
299{
300 if (progress <= 0.0)
301 return from;
302
303 if (progress >= 1.0)
304 return to;
305
306 auto blend = std::make_unique<CalcExpressionBlendLength>(from, to, progress);
307 return Length(CalculationValue::create(WTFMove(blend), ValueRangeAll));
308}
309
310Length blend(const Length& from, const Length& to, double progress)
311{
312 if (from.isAuto() || to.isAuto())
313 return progress < 0.5 ? from : to;
314
315 if (from.isUndefined() || to.isUndefined())
316 return to;
317
318 if (from.type() == Calculated || to.type() == Calculated)
319 return blendMixedTypes(from, to, progress);
320
321 if (!from.isZero() && !to.isZero() && from.type() != to.type())
322 return blendMixedTypes(from, to, progress);
323
324 LengthType resultType = to.type();
325 if (to.isZero())
326 resultType = from.type();
327
328 if (resultType == Percent) {
329 float fromPercent = from.isZero() ? 0 : from.percent();
330 float toPercent = to.isZero() ? 0 : to.percent();
331 return Length(WebCore::blend(fromPercent, toPercent, progress), Percent);
332 }
333
334 float fromValue = from.isZero() ? 0 : from.value();
335 float toValue = to.isZero() ? 0 : to.value();
336 return Length(WebCore::blend(fromValue, toValue, progress), resultType);
337}
338
339struct SameSizeAsLength {
340 int32_t value;
341 int32_t metaData;
342};
343COMPILE_ASSERT(sizeof(Length) == sizeof(SameSizeAsLength), length_should_stay_small);
344
345static TextStream& operator<<(TextStream& ts, LengthType type)
346{
347 switch (type) {
348 case Auto: ts << "auto"; break;
349 case Relative: ts << "relative"; break;
350 case Percent: ts << "percent"; break;
351 case Fixed: ts << "fixed"; break;
352 case Intrinsic: ts << "intrinsic"; break;
353 case MinIntrinsic: ts << "min-intrinsic"; break;
354 case MinContent: ts << "min-content"; break;
355 case MaxContent: ts << "max-content"; break;
356 case FillAvailable: ts << "fill-available"; break;
357 case FitContent: ts << "fit-content"; break;
358 case Calculated: ts << "calc"; break;
359 case Undefined: ts << "undefined"; break;
360 }
361 return ts;
362}
363
364TextStream& operator<<(TextStream& ts, Length length)
365{
366 switch (length.type()) {
367 case Auto:
368 case Undefined:
369 ts << length.type();
370 break;
371 case Fixed:
372 ts << TextStream::FormatNumberRespectingIntegers(length.value()) << "px";
373 break;
374 case Relative:
375 case Intrinsic:
376 case MinIntrinsic:
377 case MinContent:
378 case MaxContent:
379 case FillAvailable:
380 case FitContent:
381 ts << length.type() << " " << TextStream::FormatNumberRespectingIntegers(length.value());
382 break;
383 case Percent:
384 ts << TextStream::FormatNumberRespectingIntegers(length.percent()) << "%";
385 break;
386 case Calculated:
387 ts << length.calculationValue();
388 break;
389 }
390
391 if (length.hasQuirk())
392 ts << " has-quirk";
393
394 return ts;
395}
396
397} // namespace WebCore
398