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#include "core/css/CSSBasicShapes.h"
32
33#include "core/css/CSSValuePool.h"
34#include "core/css/Pair.h"
35#include "platform/Length.h"
36#include "wtf/text/StringBuilder.h"
37
38using namespace WTF;
39
40namespace blink {
41
42DEFINE_EMPTY_DESTRUCTOR_WILL_BE_REMOVED(CSSBasicShape)
43
44static String buildCircleString(const String& radius, const String& centerX, const String& centerY, const String& box)
45{
46    char at[] = "at";
47    char separator[] = " ";
48    StringBuilder result;
49    result.appendLiteral("circle(");
50    if (!radius.isNull())
51        result.append(radius);
52
53    if (!centerX.isNull() || !centerY.isNull()) {
54        if (!radius.isNull())
55            result.appendLiteral(separator);
56        result.append(at);
57        result.appendLiteral(separator);
58        result.append(centerX);
59        result.appendLiteral(separator);
60        result.append(centerY);
61    }
62    result.append(')');
63    if (box.length()) {
64        result.appendLiteral(separator);
65        result.append(box);
66    }
67    return result.toString();
68}
69
70static String serializePositionOffset(const Pair& offset, const Pair& other)
71{
72    if ((offset.first()->getValueID() == CSSValueLeft && other.first()->getValueID() == CSSValueTop)
73        || (offset.first()->getValueID() == CSSValueTop && other.first()->getValueID() == CSSValueLeft))
74        return offset.second()->cssText();
75    return offset.cssText();
76}
77
78static PassRefPtrWillBeRawPtr<CSSPrimitiveValue> buildSerializablePositionOffset(PassRefPtrWillBeRawPtr<CSSPrimitiveValue> offset, CSSValueID defaultSide)
79{
80    CSSValueID side = defaultSide;
81    RefPtrWillBeRawPtr<CSSPrimitiveValue> amount = nullptr;
82
83    if (!offset) {
84        side = CSSValueCenter;
85    } else if (offset->isValueID()) {
86        side = offset->getValueID();
87    } else if (Pair* pair = offset->getPairValue()) {
88        side = pair->first()->getValueID();
89        amount = pair->second();
90    } else {
91        amount = offset;
92    }
93
94    if (side == CSSValueCenter) {
95        side = defaultSide;
96        amount = cssValuePool().createValue(50, CSSPrimitiveValue::CSS_PERCENTAGE);
97    } else if ((side == CSSValueRight || side == CSSValueBottom)
98        && amount->isPercentage()) {
99        side = defaultSide;
100        amount = cssValuePool().createValue(100 - amount->getFloatValue(), CSSPrimitiveValue::CSS_PERCENTAGE);
101    } else if (amount->isLength() && !amount->getFloatValue()) {
102        if (side == CSSValueRight || side == CSSValueBottom)
103            amount = cssValuePool().createValue(100, CSSPrimitiveValue::CSS_PERCENTAGE);
104        else
105            amount = cssValuePool().createValue(0, CSSPrimitiveValue::CSS_PERCENTAGE);
106        side = defaultSide;
107    }
108
109    return cssValuePool().createValue(Pair::create(cssValuePool().createValue(side), amount.release(), Pair::KeepIdenticalValues));
110}
111
112String CSSBasicShapeCircle::cssText() const
113{
114    RefPtrWillBeRawPtr<CSSPrimitiveValue> normalizedCX = buildSerializablePositionOffset(m_centerX, CSSValueLeft);
115    RefPtrWillBeRawPtr<CSSPrimitiveValue> normalizedCY = buildSerializablePositionOffset(m_centerY, CSSValueTop);
116
117    String radius;
118    if (m_radius && m_radius->getValueID() != CSSValueClosestSide)
119        radius = m_radius->cssText();
120
121    return buildCircleString(radius,
122        serializePositionOffset(*normalizedCX->getPairValue(), *normalizedCY->getPairValue()),
123        serializePositionOffset(*normalizedCY->getPairValue(), *normalizedCX->getPairValue()),
124        m_referenceBox ? m_referenceBox->cssText() : String());
125}
126
127bool CSSBasicShapeCircle::equals(const CSSBasicShape& shape) const
128{
129    if (shape.type() != CSSBasicShapeCircleType)
130        return false;
131
132    const CSSBasicShapeCircle& other = static_cast<const CSSBasicShapeCircle&>(shape);
133    return compareCSSValuePtr(m_centerX, other.m_centerX)
134        && compareCSSValuePtr(m_centerY, other.m_centerY)
135        && compareCSSValuePtr(m_radius, other.m_radius)
136        && compareCSSValuePtr(m_referenceBox, other.m_referenceBox);
137}
138
139void CSSBasicShapeCircle::trace(Visitor* visitor)
140{
141    visitor->trace(m_centerX);
142    visitor->trace(m_centerY);
143    visitor->trace(m_radius);
144    CSSBasicShape::trace(visitor);
145}
146
147static String buildEllipseString(const String& radiusX, const String& radiusY, const String& centerX, const String& centerY, const String& box)
148{
149    char at[] = "at";
150    char separator[] = " ";
151    StringBuilder result;
152    result.appendLiteral("ellipse(");
153    bool needsSeparator = false;
154    if (!radiusX.isNull()) {
155        result.append(radiusX);
156        needsSeparator = true;
157    }
158    if (!radiusY.isNull()) {
159        if (needsSeparator)
160            result.appendLiteral(separator);
161        result.append(radiusY);
162        needsSeparator = true;
163    }
164
165    if (!centerX.isNull() || !centerY.isNull()) {
166        if (needsSeparator)
167            result.appendLiteral(separator);
168        result.appendLiteral(at);
169        result.appendLiteral(separator);
170        result.append(centerX);
171        result.appendLiteral(separator);
172        result.append(centerY);
173    }
174    result.append(')');
175    if (box.length()) {
176        result.appendLiteral(separator);
177        result.append(box);
178    }
179    return result.toString();
180}
181
182String CSSBasicShapeEllipse::cssText() const
183{
184    RefPtrWillBeRawPtr<CSSPrimitiveValue> normalizedCX = buildSerializablePositionOffset(m_centerX, CSSValueLeft);
185    RefPtrWillBeRawPtr<CSSPrimitiveValue> normalizedCY = buildSerializablePositionOffset(m_centerY, CSSValueTop);
186
187    String radiusX;
188    String radiusY;
189    if (m_radiusX) {
190        bool shouldSerializeRadiusXValue = m_radiusX->getValueID() != CSSValueClosestSide;
191        bool shouldSerializeRadiusYValue = false;
192
193        if (m_radiusY) {
194            shouldSerializeRadiusYValue = m_radiusY->getValueID() != CSSValueClosestSide;
195            if (shouldSerializeRadiusYValue)
196                radiusY = m_radiusY->cssText();
197        }
198        if (shouldSerializeRadiusXValue || (!shouldSerializeRadiusXValue && shouldSerializeRadiusYValue))
199            radiusX = m_radiusX->cssText();
200    }
201
202    return buildEllipseString(radiusX, radiusY,
203        serializePositionOffset(*normalizedCX->getPairValue(), *normalizedCY->getPairValue()),
204        serializePositionOffset(*normalizedCY->getPairValue(), *normalizedCX->getPairValue()),
205        m_referenceBox ? m_referenceBox->cssText() : String());
206}
207
208bool CSSBasicShapeEllipse::equals(const CSSBasicShape& shape) const
209{
210    if (shape.type() != CSSBasicShapeEllipseType)
211        return false;
212
213    const CSSBasicShapeEllipse& other = static_cast<const CSSBasicShapeEllipse&>(shape);
214    return compareCSSValuePtr(m_centerX, other.m_centerX)
215        && compareCSSValuePtr(m_centerY, other.m_centerY)
216        && compareCSSValuePtr(m_radiusX, other.m_radiusX)
217        && compareCSSValuePtr(m_radiusY, other.m_radiusY)
218        && compareCSSValuePtr(m_referenceBox, other.m_referenceBox);
219}
220
221void CSSBasicShapeEllipse::trace(Visitor* visitor)
222{
223    visitor->trace(m_centerX);
224    visitor->trace(m_centerY);
225    visitor->trace(m_radiusX);
226    visitor->trace(m_radiusY);
227    CSSBasicShape::trace(visitor);
228}
229
230static String buildPolygonString(const WindRule& windRule, const Vector<String>& points, const String& box)
231{
232    ASSERT(!(points.size() % 2));
233
234    StringBuilder result;
235    const char evenOddOpening[] = "polygon(evenodd, ";
236    const char nonZeroOpening[] = "polygon(";
237    const char commaSeparator[] = ", ";
238    COMPILE_ASSERT(sizeof(evenOddOpening) > sizeof(nonZeroOpening), polygon_string_openings_have_same_length);
239
240    // Compute the required capacity in advance to reduce allocations.
241    size_t length = sizeof(evenOddOpening) - 1;
242    for (size_t i = 0; i < points.size(); i += 2) {
243        if (i)
244            length += (sizeof(commaSeparator) - 1);
245        // add length of two strings, plus one for the space separator.
246        length += points[i].length() + 1 + points[i + 1].length();
247    }
248    if (!box.isEmpty())
249        length += box.length() + 1;
250    result.reserveCapacity(length);
251
252    if (windRule == RULE_EVENODD)
253        result.appendLiteral(evenOddOpening);
254    else
255        result.appendLiteral(nonZeroOpening);
256
257    for (size_t i = 0; i < points.size(); i += 2) {
258        if (i)
259            result.appendLiteral(commaSeparator);
260        result.append(points[i]);
261        result.append(' ');
262        result.append(points[i + 1]);
263    }
264
265    result.append(')');
266
267    if (!box.isEmpty()) {
268        result.append(' ');
269        result.append(box);
270    }
271
272    return result.toString();
273}
274
275String CSSBasicShapePolygon::cssText() const
276{
277    Vector<String> points;
278    points.reserveInitialCapacity(m_values.size());
279
280    for (size_t i = 0; i < m_values.size(); ++i)
281        points.append(m_values.at(i)->cssText());
282
283    return buildPolygonString(m_windRule, points, m_referenceBox ? m_referenceBox->cssText() : String());
284}
285
286bool CSSBasicShapePolygon::equals(const CSSBasicShape& shape) const
287{
288    if (shape.type() != CSSBasicShapePolygonType)
289        return false;
290
291    const CSSBasicShapePolygon& rhs = static_cast<const CSSBasicShapePolygon&>(shape);
292
293    if (!compareCSSValuePtr(m_referenceBox, rhs.m_referenceBox))
294        return false;
295
296    return compareCSSValueVector(m_values, rhs.m_values);
297}
298
299void CSSBasicShapePolygon::trace(Visitor* visitor)
300{
301    visitor->trace(m_values);
302    CSSBasicShape::trace(visitor);
303}
304
305static bool buildInsetRadii(Vector<String> &radii, const String& topLeftRadius, const String& topRightRadius, const String& bottomRightRadius, const String& bottomLeftRadius)
306{
307    bool showBottomLeft = topRightRadius != bottomLeftRadius;
308    bool showBottomRight = showBottomLeft || (bottomRightRadius != topLeftRadius);
309    bool showTopRight = showBottomRight || (topRightRadius != topLeftRadius);
310
311    radii.append(topLeftRadius);
312    if (showTopRight)
313        radii.append(topRightRadius);
314    if (showBottomRight)
315        radii.append(bottomRightRadius);
316    if (showBottomLeft)
317        radii.append(bottomLeftRadius);
318
319    return radii.size() == 1 && radii[0] == "0px";
320}
321
322static String buildInsetString(const String& top, const String& right, const String& bottom, const String& left,
323    const String& topLeftRadiusWidth, const String& topLeftRadiusHeight,
324    const String& topRightRadiusWidth, const String& topRightRadiusHeight,
325    const String& bottomRightRadiusWidth, const String& bottomRightRadiusHeight,
326    const String& bottomLeftRadiusWidth, const String& bottomLeftRadiusHeight)
327{
328    char opening[] = "inset(";
329    char separator[] = " ";
330    char cornersSeparator[] = "round";
331    StringBuilder result;
332    result.appendLiteral(opening);
333    result.append(top);
334    bool showLeftArg = !left.isNull() && left != right;
335    bool showBottomArg = !bottom.isNull() && (bottom != top || showLeftArg);
336    bool showRightArg = !right.isNull() && (right != top || showBottomArg);
337    if (showRightArg) {
338        result.appendLiteral(separator);
339        result.append(right);
340    }
341    if (showBottomArg) {
342        result.appendLiteral(separator);
343        result.append(bottom);
344    }
345    if (showLeftArg) {
346        result.appendLiteral(separator);
347        result.append(left);
348    }
349
350    if (!topLeftRadiusWidth.isNull() && !topLeftRadiusHeight.isNull()) {
351        Vector<String> horizontalRadii;
352        bool areDefaultCornerRadii = buildInsetRadii(horizontalRadii, topLeftRadiusWidth, topRightRadiusWidth, bottomRightRadiusWidth, bottomLeftRadiusWidth);
353
354        Vector<String> verticalRadii;
355        areDefaultCornerRadii &= buildInsetRadii(verticalRadii, topLeftRadiusHeight, topRightRadiusHeight, bottomRightRadiusHeight, bottomLeftRadiusHeight);
356
357        if (!areDefaultCornerRadii) {
358            result.appendLiteral(separator);
359            result.appendLiteral(cornersSeparator);
360
361            for (size_t i = 0; i < horizontalRadii.size(); ++i) {
362                result.appendLiteral(separator);
363                result.append(horizontalRadii[i]);
364            }
365            if (horizontalRadii != verticalRadii) {
366                result.appendLiteral(separator);
367                result.appendLiteral("/");
368
369                for (size_t i = 0; i < verticalRadii.size(); ++i) {
370                    result.appendLiteral(separator);
371                    result.append(verticalRadii[i]);
372                }
373            }
374        }
375    }
376    result.append(')');
377
378    return result.toString();
379}
380
381static inline void updateCornerRadiusWidthAndHeight(CSSPrimitiveValue* corner, String& width, String& height)
382{
383    if (!corner)
384        return;
385
386    Pair* radius = corner->getPairValue();
387    width = radius->first() ? radius->first()->cssText() : String("0");
388    if (radius->second())
389        height = radius->second()->cssText();
390}
391
392String CSSBasicShapeInset::cssText() const
393{
394    String topLeftRadiusWidth;
395    String topLeftRadiusHeight;
396    String topRightRadiusWidth;
397    String topRightRadiusHeight;
398    String bottomRightRadiusWidth;
399    String bottomRightRadiusHeight;
400    String bottomLeftRadiusWidth;
401    String bottomLeftRadiusHeight;
402
403    updateCornerRadiusWidthAndHeight(topLeftRadius(), topLeftRadiusWidth, topLeftRadiusHeight);
404    updateCornerRadiusWidthAndHeight(topRightRadius(), topRightRadiusWidth, topRightRadiusHeight);
405    updateCornerRadiusWidthAndHeight(bottomRightRadius(), bottomRightRadiusWidth, bottomRightRadiusHeight);
406    updateCornerRadiusWidthAndHeight(bottomLeftRadius(), bottomLeftRadiusWidth, bottomLeftRadiusHeight);
407
408    return buildInsetString(m_top ? m_top->cssText() : String(),
409        m_right ? m_right->cssText() : String(),
410        m_bottom ? m_bottom->cssText() : String(),
411        m_left ? m_left->cssText() : String(),
412        topLeftRadiusWidth,
413        topLeftRadiusHeight,
414        topRightRadiusWidth,
415        topRightRadiusHeight,
416        bottomRightRadiusWidth,
417        bottomRightRadiusHeight,
418        bottomLeftRadiusWidth,
419        bottomLeftRadiusHeight);
420}
421
422bool CSSBasicShapeInset::equals(const CSSBasicShape& shape) const
423{
424    if (shape.type() != CSSBasicShapeInsetType)
425        return false;
426
427    const CSSBasicShapeInset& other = static_cast<const CSSBasicShapeInset&>(shape);
428    return compareCSSValuePtr(m_top, other.m_top)
429        && compareCSSValuePtr(m_right, other.m_right)
430        && compareCSSValuePtr(m_bottom, other.m_bottom)
431        && compareCSSValuePtr(m_left, other.m_left)
432        && compareCSSValuePtr(m_topLeftRadius, other.m_topLeftRadius)
433        && compareCSSValuePtr(m_topRightRadius, other.m_topRightRadius)
434        && compareCSSValuePtr(m_bottomRightRadius, other.m_bottomRightRadius)
435        && compareCSSValuePtr(m_bottomLeftRadius, other.m_bottomLeftRadius);
436}
437
438void CSSBasicShapeInset::trace(Visitor* visitor)
439{
440    visitor->trace(m_top);
441    visitor->trace(m_right);
442    visitor->trace(m_bottom);
443    visitor->trace(m_left);
444    visitor->trace(m_topLeftRadius);
445    visitor->trace(m_topRightRadius);
446    visitor->trace(m_bottomRightRadius);
447    visitor->trace(m_bottomLeftRadius);
448    CSSBasicShape::trace(visitor);
449}
450
451} // namespace blink
452
453