1/*
2 * Copyright (C) 2011 Adobe Systems Incorporated. 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 *
8 * 1. Redistributions of source code must retain the above
9 *    copyright notice, this list of conditions and the following
10 *    disclaimer.
11 * 2. Redistributions in binary form must reproduce the above
12 *    copyright notice, this list of conditions and the following
13 *    disclaimer in the documentation and/or other materials
14 *    provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
21 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
22 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
23 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
25 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
26 * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 * SUCH DAMAGE.
28 */
29
30#include "config.h"
31
32#include "CSSBasicShapes.h"
33
34#include "CSSPrimitiveValueMappings.h"
35#include "CSSValuePool.h"
36#include "Pair.h"
37#include <wtf/text/StringBuilder.h>
38
39using namespace WTF;
40
41namespace WebCore {
42
43static String serializePositionOffset(const Pair& offset, const Pair& other)
44{
45    if ((offset.first()->getValueID() == CSSValueLeft && other.first()->getValueID() == CSSValueTop)
46        || (offset.first()->getValueID() == CSSValueTop && other.first()->getValueID() == CSSValueLeft))
47        return offset.second()->cssText();
48    return offset.cssText();
49}
50
51static PassRefPtr<CSSPrimitiveValue> buildSerializablePositionOffset(PassRefPtr<CSSPrimitiveValue> offset, CSSValueID defaultSide)
52{
53    CSSValueID side = defaultSide;
54    RefPtr<CSSPrimitiveValue> amount;
55
56    if (!offset)
57        side = CSSValueCenter;
58    else if (offset->isValueID())
59        side = offset->getValueID();
60    else if (Pair* pair = offset->getPairValue()) {
61        side = pair->first()->getValueID();
62        amount = pair->second();
63    } else
64        amount = offset;
65
66    if (side == CSSValueCenter) {
67        side = defaultSide;
68        amount = cssValuePool().createValue(Length(50, Percent));
69    } else if ((side == CSSValueRight || side == CSSValueBottom)
70        && amount->isPercentage()) {
71        side = defaultSide;
72        amount = cssValuePool().createValue(Length(100 - amount->getFloatValue(), Percent));
73    } else if (amount->isLength() && !amount->getFloatValue()) {
74        if (side == CSSValueRight || side == CSSValueBottom)
75            amount = cssValuePool().createValue(Length(100, Percent));
76        else
77            amount = cssValuePool().createValue(Length(0, Percent));
78        side = defaultSide;
79    }
80
81    return cssValuePool().createValue(Pair::create(cssValuePool().createValue(side), amount.release()));
82}
83
84static String buildCircleString(const String& radius, const String& centerX, const String& centerY, const String& box)
85{
86    char opening[] = "circle(";
87    char at[] = "at";
88    char separator[] = " ";
89    StringBuilder result;
90    result.appendLiteral(opening);
91    if (!radius.isNull())
92        result.append(radius);
93
94    if (!centerX.isNull() || !centerY.isNull()) {
95        if (!radius.isNull())
96            result.appendLiteral(separator);
97        result.appendLiteral(at);
98        result.appendLiteral(separator);
99        result.append(centerX);
100        result.appendLiteral(separator);
101        result.append(centerY);
102    }
103    result.appendLiteral(")");
104    if (box.length()) {
105        result.appendLiteral(separator);
106        result.append(box);
107    }
108    return result.toString();
109}
110
111String CSSBasicShapeCircle::cssText() const
112{
113    RefPtr<CSSPrimitiveValue> normalizedCX = buildSerializablePositionOffset(m_centerX, CSSValueLeft);
114    RefPtr<CSSPrimitiveValue> normalizedCY = buildSerializablePositionOffset(m_centerY, CSSValueTop);
115
116    String radius;
117    if (m_radius && m_radius->getValueID() != CSSValueClosestSide)
118        radius = m_radius->cssText();
119
120    return buildCircleString(radius,
121        serializePositionOffset(*normalizedCX->getPairValue(), *normalizedCY->getPairValue()),
122        serializePositionOffset(*normalizedCY->getPairValue(), *normalizedCX->getPairValue()),
123        m_referenceBox ? m_referenceBox->cssText() : String());
124}
125
126bool CSSBasicShapeCircle::equals(const CSSBasicShape& shape) const
127{
128    if (shape.type() != CSSBasicShapeCircleType)
129        return false;
130
131    const CSSBasicShapeCircle& other = static_cast<const CSSBasicShapeCircle&>(shape);
132    return compareCSSValuePtr(m_centerX, other.m_centerX)
133        && compareCSSValuePtr(m_centerY, other.m_centerY)
134        && compareCSSValuePtr(m_radius, other.m_radius)
135        && compareCSSValuePtr(m_referenceBox, other.m_referenceBox);
136}
137
138static String buildEllipseString(const String& radiusX, const String& radiusY, const String& centerX, const String& centerY, const String& box)
139{
140    char opening[] = "ellipse(";
141    char at[] = "at";
142    char separator[] = " ";
143    StringBuilder result;
144    result.appendLiteral(opening);
145    bool needsSeparator = false;
146    if (!radiusX.isNull()) {
147        result.append(radiusX);
148        needsSeparator = true;
149    }
150    if (!radiusY.isNull()) {
151        if (needsSeparator)
152            result.appendLiteral(separator);
153        result.append(radiusY);
154        needsSeparator = true;
155    }
156
157    if (!centerX.isNull() || !centerY.isNull()) {
158        if (needsSeparator)
159            result.appendLiteral(separator);
160        result.appendLiteral(at);
161        result.appendLiteral(separator);
162        result.append(centerX);
163        result.appendLiteral(separator);
164        result.append(centerY);
165    }
166    result.appendLiteral(")");
167    if (box.length()) {
168        result.appendLiteral(separator);
169        result.append(box);
170    }
171    return result.toString();
172}
173
174String CSSBasicShapeEllipse::cssText() const
175{
176    RefPtr<CSSPrimitiveValue> normalizedCX = buildSerializablePositionOffset(m_centerX, CSSValueLeft);
177    RefPtr<CSSPrimitiveValue> normalizedCY = buildSerializablePositionOffset(m_centerY, CSSValueTop);
178
179    String radiusX;
180    String radiusY;
181    if (m_radiusX) {
182        bool shouldSerializeRadiusXValue = m_radiusX->getValueID() != CSSValueClosestSide;
183        bool shouldSerializeRadiusYValue = false;
184
185        if (m_radiusY) {
186            shouldSerializeRadiusYValue = m_radiusY->getValueID() != CSSValueClosestSide;
187            if (shouldSerializeRadiusYValue)
188                radiusY = m_radiusY->cssText();
189        }
190        if (shouldSerializeRadiusXValue || (!shouldSerializeRadiusXValue && shouldSerializeRadiusYValue))
191            radiusX = m_radiusX->cssText();
192    }
193    return buildEllipseString(radiusX, radiusY,
194        serializePositionOffset(*normalizedCX->getPairValue(), *normalizedCY->getPairValue()),
195        serializePositionOffset(*normalizedCY->getPairValue(), *normalizedCX->getPairValue()),
196        m_referenceBox ? m_referenceBox->cssText() : String());
197}
198
199bool CSSBasicShapeEllipse::equals(const CSSBasicShape& shape) const
200{
201    if (shape.type() != CSSBasicShapeEllipseType)
202        return false;
203
204    const CSSBasicShapeEllipse& other = static_cast<const CSSBasicShapeEllipse&>(shape);
205    return compareCSSValuePtr(m_centerX, other.m_centerX)
206        && compareCSSValuePtr(m_centerY, other.m_centerY)
207        && compareCSSValuePtr(m_radiusX, other.m_radiusX)
208        && compareCSSValuePtr(m_radiusY, other.m_radiusY)
209        && compareCSSValuePtr(m_referenceBox, other.m_referenceBox);
210}
211
212static String buildPolygonString(const WindRule& windRule, const Vector<String>& points, const String& box)
213{
214    ASSERT(!(points.size() % 2));
215
216    StringBuilder result;
217    char evenOddOpening[] = "polygon(evenodd, ";
218    char nonZeroOpening[] = "polygon(";
219    char commaSeparator[] = ", ";
220    COMPILE_ASSERT(sizeof(evenOddOpening) >= sizeof(nonZeroOpening), polygon_evenodd_is_longest_string_opening);
221
222    // Compute the required capacity in advance to reduce allocations.
223    size_t length = sizeof(evenOddOpening) - 1;
224    for (size_t i = 0; i < points.size(); i += 2) {
225        if (i)
226            length += (sizeof(commaSeparator) - 1);
227        // add length of two strings, plus one for the space separator.
228        length += points[i].length() + 1 + points[i + 1].length();
229    }
230
231    if (box.length())
232        length += box.length() + 1;
233
234    result.reserveCapacity(length);
235
236    if (windRule == RULE_EVENODD)
237        result.appendLiteral(evenOddOpening);
238    else
239        result.appendLiteral(nonZeroOpening);
240
241    for (size_t i = 0; i < points.size(); i += 2) {
242        if (i)
243            result.appendLiteral(commaSeparator);
244        result.append(points[i]);
245        result.append(' ');
246        result.append(points[i + 1]);
247    }
248
249    result.append(')');
250
251    if (box.length()) {
252        result.append(' ');
253        result.append(box);
254    }
255
256    return result.toString();
257}
258
259String CSSBasicShapePolygon::cssText() const
260{
261    Vector<String> points;
262    points.reserveInitialCapacity(m_values.size());
263
264    for (size_t i = 0; i < m_values.size(); ++i)
265        points.append(m_values.at(i)->cssText());
266
267    return buildPolygonString(m_windRule, points, m_referenceBox ? m_referenceBox->cssText() : String());
268}
269
270bool CSSBasicShapePolygon::equals(const CSSBasicShape& shape) const
271{
272    if (shape.type() != CSSBasicShapePolygonType)
273        return false;
274
275    const CSSBasicShapePolygon& rhs = static_cast<const CSSBasicShapePolygon&>(shape);
276    return compareCSSValuePtr(m_referenceBox, rhs.m_referenceBox)
277        && compareCSSValueVector<CSSPrimitiveValue>(m_values, rhs.m_values);
278}
279
280static bool buildInsetRadii(Vector<String>& radii, const String& topLeftRadius, const String& topRightRadius, const String& bottomRightRadius, const String& bottomLeftRadius)
281{
282    bool showBottomLeft = topRightRadius != bottomLeftRadius;
283    bool showBottomRight = showBottomLeft || (bottomRightRadius != topLeftRadius);
284    bool showTopRight = showBottomRight || (topRightRadius != topLeftRadius);
285
286    radii.append(topLeftRadius);
287    if (showTopRight)
288        radii.append(topRightRadius);
289    if (showBottomRight)
290        radii.append(bottomRightRadius);
291    if (showBottomLeft)
292        radii.append(bottomLeftRadius);
293
294    return radii.size() == 1 && radii[0] == "0px";
295}
296
297static String buildInsetString(const String& top, const String& right, const String& bottom, const String& left,
298    const String& topLeftRadiusWidth, const String& topLeftRadiusHeight,
299    const String& topRightRadiusWidth, const String& topRightRadiusHeight,
300    const String& bottomRightRadiusWidth, const String& bottomRightRadiusHeight,
301    const String& bottomLeftRadiusWidth, const String& bottomLeftRadiusHeight,
302    const String& box)
303{
304    char opening[] = "inset(";
305    char separator[] = " ";
306    char cornersSeparator[] = "round";
307    StringBuilder result;
308    result.appendLiteral(opening);
309    result.append(top);
310
311    bool showLeftArg = !left.isNull() && left != right;
312    bool showBottomArg = !bottom.isNull() && (bottom != top || showLeftArg);
313    bool showRightArg = !right.isNull() && (right != top || showBottomArg);
314    if (showRightArg) {
315        result.appendLiteral(separator);
316        result.append(right);
317    }
318    if (showBottomArg) {
319        result.appendLiteral(separator);
320        result.append(bottom);
321    }
322    if (showLeftArg) {
323        result.appendLiteral(separator);
324        result.append(left);
325    }
326
327    if (!topLeftRadiusWidth.isNull() && !topLeftRadiusHeight.isNull()) {
328        Vector<String> horizontalRadii;
329        bool areDefaultCornerRadii = buildInsetRadii(horizontalRadii, topLeftRadiusWidth, topRightRadiusWidth, bottomRightRadiusWidth, bottomLeftRadiusWidth);
330
331        Vector<String> verticalRadii;
332        areDefaultCornerRadii &= buildInsetRadii(verticalRadii, topLeftRadiusHeight, topRightRadiusHeight, bottomRightRadiusHeight, bottomLeftRadiusHeight);
333
334        if (!areDefaultCornerRadii) {
335            result.appendLiteral(separator);
336            result.appendLiteral(cornersSeparator);
337
338            for (size_t i = 0; i < horizontalRadii.size(); ++i) {
339                result.appendLiteral(separator);
340                result.append(horizontalRadii[i]);
341            }
342
343            if (verticalRadii.size() != horizontalRadii.size()
344                || !VectorComparer<false, String>::compare(verticalRadii.data(), horizontalRadii.data(), verticalRadii.size())) {
345                result.appendLiteral(separator);
346                result.appendLiteral("/");
347
348                for (size_t i = 0; i < verticalRadii.size(); ++i) {
349                    result.appendLiteral(separator);
350                    result.append(verticalRadii[i]);
351                }
352            }
353        }
354    }
355    result.append(')');
356    if (box.length()) {
357        result.append(' ');
358        result.append(box);
359    }
360    return result.toString();
361}
362
363static inline void updateCornerRadiusWidthAndHeight(CSSPrimitiveValue* corner, String& width, String& height)
364{
365    if (!corner)
366        return;
367
368    Pair* radius = corner->getPairValue();
369    width = radius->first() ? radius->first()->cssText() : String("0");
370    if (radius->second())
371        height = radius->second()->cssText();
372}
373
374String CSSBasicShapeInset::cssText() const
375{
376    String topLeftRadiusWidth;
377    String topLeftRadiusHeight;
378    String topRightRadiusWidth;
379    String topRightRadiusHeight;
380    String bottomRightRadiusWidth;
381    String bottomRightRadiusHeight;
382    String bottomLeftRadiusWidth;
383    String bottomLeftRadiusHeight;
384
385    updateCornerRadiusWidthAndHeight(topLeftRadius(), topLeftRadiusWidth, topLeftRadiusHeight);
386    updateCornerRadiusWidthAndHeight(topRightRadius(), topRightRadiusWidth, topRightRadiusHeight);
387    updateCornerRadiusWidthAndHeight(bottomRightRadius(), bottomRightRadiusWidth, bottomRightRadiusHeight);
388    updateCornerRadiusWidthAndHeight(bottomLeftRadius(), bottomLeftRadiusWidth, bottomLeftRadiusHeight);
389
390    return buildInsetString(m_top ? m_top->cssText() : String(),
391        m_right ? m_right->cssText() : String(),
392        m_bottom ? m_bottom->cssText() : String(),
393        m_left ? m_left->cssText() : String(),
394        topLeftRadiusWidth,
395        topLeftRadiusHeight,
396        topRightRadiusWidth,
397        topRightRadiusHeight,
398        bottomRightRadiusWidth,
399        bottomRightRadiusHeight,
400        bottomLeftRadiusWidth,
401        bottomLeftRadiusHeight,
402        m_referenceBox ? m_referenceBox->cssText() : String());
403}
404
405bool CSSBasicShapeInset::equals(const CSSBasicShape& shape) const
406{
407    if (shape.type() != CSSBasicShapeInsetType)
408        return false;
409
410    const CSSBasicShapeInset& other = static_cast<const CSSBasicShapeInset&>(shape);
411    return compareCSSValuePtr(m_top, other.m_top)
412        && compareCSSValuePtr(m_right, other.m_right)
413        && compareCSSValuePtr(m_bottom, other.m_bottom)
414        && compareCSSValuePtr(m_left, other.m_left)
415        && compareCSSValuePtr(m_topLeftRadius, other.m_topLeftRadius)
416        && compareCSSValuePtr(m_topRightRadius, other.m_topRightRadius)
417        && compareCSSValuePtr(m_bottomRightRadius, other.m_bottomRightRadius)
418        && compareCSSValuePtr(m_bottomLeftRadius, other.m_bottomLeftRadius);
419}
420
421} // namespace WebCore
422
423