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 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 "platform/Length.h"
27
28#include "platform/CalculationValue.h"
29#include "platform/animation/AnimationUtilities.h"
30#include "wtf/ASCIICType.h"
31#include "wtf/text/StringBuffer.h"
32#include "wtf/text/WTFString.h"
33
34using namespace WTF;
35
36namespace blink {
37
38template<typename CharType>
39static unsigned splitLength(const CharType* data, unsigned length, unsigned& intLength, unsigned& doubleLength)
40{
41    ASSERT(length);
42
43    unsigned i = 0;
44    while (i < length && isSpaceOrNewline(data[i]))
45        ++i;
46    if (i < length && (data[i] == '+' || data[i] == '-'))
47        ++i;
48    while (i < length && isASCIIDigit(data[i]))
49        ++i;
50    intLength = i;
51    while (i < length && (isASCIIDigit(data[i]) || data[i] == '.'))
52        ++i;
53    doubleLength = i;
54
55    // IE quirk: Skip whitespace between the number and the % character (20 % => 20%).
56    while (i < length && isSpaceOrNewline(data[i]))
57        ++i;
58
59    return i;
60}
61
62template<typename CharType>
63static Length parseHTMLAreaCoordinate(const CharType* data, unsigned length)
64{
65    unsigned intLength;
66    unsigned doubleLength;
67    splitLength(data, length, intLength, doubleLength);
68
69    bool ok;
70    int r = charactersToIntStrict(data, intLength, &ok);
71    if (ok)
72        return Length(r, Fixed);
73    return Length(0, Fixed);
74}
75
76// FIXME: Per HTML5, this should follow the "rules for parsing a list of integers".
77Vector<Length> parseHTMLAreaElementCoords(const String& string)
78{
79    unsigned length = string.length();
80    StringBuffer<LChar> spacified(length);
81    for (unsigned i = 0; i < length; i++) {
82        UChar cc = string[i];
83        if (cc > '9' || (cc < '0' && cc != '-' && cc != '.'))
84            spacified[i] = ' ';
85        else
86            spacified[i] = cc;
87    }
88    RefPtr<StringImpl> str = spacified.release();
89    str = str->simplifyWhiteSpace();
90    ASSERT(str->is8Bit());
91
92    if (!str->length())
93        return Vector<Length>();
94
95    unsigned len = str->count(' ') + 1;
96    Vector<Length> r(len);
97
98    unsigned i = 0;
99    unsigned pos = 0;
100    size_t pos2;
101
102    while ((pos2 = str->find(' ', pos)) != kNotFound) {
103        r[i++] = parseHTMLAreaCoordinate(str->characters8() + pos, pos2 - pos);
104        pos = pos2 + 1;
105    }
106    r[i] = parseHTMLAreaCoordinate(str->characters8() + pos, str->length() - pos);
107
108    ASSERT(i == len - 1);
109
110    return r;
111}
112
113class CalculationValueHandleMap {
114    WTF_MAKE_FAST_ALLOCATED;
115public:
116    CalculationValueHandleMap()
117        : m_index(1)
118    {
119    }
120
121    int insert(PassRefPtr<CalculationValue> calcValue)
122    {
123        ASSERT(m_index);
124        // FIXME calc(): https://bugs.webkit.org/show_bug.cgi?id=80489
125        // This monotonically increasing handle generation scheme is potentially wasteful
126        // of the handle space. Consider reusing empty handles.
127        while (m_map.contains(m_index))
128            m_index++;
129
130        m_map.set(m_index, calcValue);
131
132        return m_index;
133    }
134
135    void remove(int index)
136    {
137        ASSERT(m_map.contains(index));
138        m_map.remove(index);
139    }
140
141    CalculationValue& get(int index)
142    {
143        ASSERT(m_map.contains(index));
144        return *m_map.get(index);
145    }
146
147    void decrementRef(int index)
148    {
149        ASSERT(m_map.contains(index));
150        CalculationValue* value = m_map.get(index);
151        if (value->hasOneRef()) {
152            // Force the CalculationValue destructor early to avoid a potential recursive call inside HashMap remove().
153            m_map.set(index, nullptr);
154            m_map.remove(index);
155        } else {
156            value->deref();
157        }
158    }
159
160private:
161    int m_index;
162    HashMap<int, RefPtr<CalculationValue> > m_map;
163};
164
165static CalculationValueHandleMap& calcHandles()
166{
167    DEFINE_STATIC_LOCAL(CalculationValueHandleMap, handleMap, ());
168    return handleMap;
169}
170
171Length::Length(PassRefPtr<CalculationValue> calc)
172    : m_quirk(false)
173    , m_type(Calculated)
174    , m_isFloat(false)
175{
176    m_intValue = calcHandles().insert(calc);
177}
178
179Length Length::blendMixedTypes(const Length& from, double progress, ValueRange range) const
180{
181    ASSERT(from.isSpecified());
182    ASSERT(isSpecified());
183    PixelsAndPercent fromPixelsAndPercent = from.pixelsAndPercent();
184    PixelsAndPercent toPixelsAndPercent = pixelsAndPercent();
185    const float pixels = blink::blend(fromPixelsAndPercent.pixels, toPixelsAndPercent.pixels, progress);
186    const float percent = blink::blend(fromPixelsAndPercent.percent, toPixelsAndPercent.percent, progress);
187    return Length(CalculationValue::create(PixelsAndPercent(pixels, percent), range));
188}
189
190PixelsAndPercent Length::pixelsAndPercent() const
191{
192    switch (type()) {
193    case Fixed:
194        return PixelsAndPercent(value(), 0);
195    case Percent:
196        return PixelsAndPercent(0, value());
197    case Calculated:
198        return calculationValue().pixelsAndPercent();
199    default:
200        ASSERT_NOT_REACHED();
201        return PixelsAndPercent(0, 0);
202    }
203}
204
205Length Length::subtractFromOneHundredPercent() const
206{
207    PixelsAndPercent result = pixelsAndPercent();
208    result.pixels = -result.pixels;
209    result.percent = 100 - result.percent;
210    if (result.pixels && result.percent)
211        return Length(CalculationValue::create(result, ValueRangeAll));
212    if (result.percent)
213        return Length(result.percent, Percent);
214    return Length(result.pixels, Fixed);
215}
216
217CalculationValue& Length::calculationValue() const
218{
219    ASSERT(isCalculated());
220    return calcHandles().get(calculationHandle());
221}
222
223void Length::incrementCalculatedRef() const
224{
225    ASSERT(isCalculated());
226    calculationValue().ref();
227}
228
229void Length::decrementCalculatedRef() const
230{
231    ASSERT(isCalculated());
232    calcHandles().decrementRef(calculationHandle());
233}
234
235float Length::nonNanCalculatedValue(int maxValue) const
236{
237    ASSERT(isCalculated());
238    float result = calculationValue().evaluate(maxValue);
239    if (std::isnan(result))
240        return 0;
241    return result;
242}
243
244bool Length::isCalculatedEqual(const Length& o) const
245{
246    return isCalculated() && (&calculationValue() == &o.calculationValue() || calculationValue() == o.calculationValue());
247}
248
249struct SameSizeAsLength {
250    int32_t value;
251    int32_t metaData;
252};
253COMPILE_ASSERT(sizeof(Length) == sizeof(SameSizeAsLength), length_should_stay_small);
254
255} // namespace blink
256