Home | History | Annotate | Download | only in css
      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 
     38 using namespace WTF;
     39 
     40 namespace blink {
     41 
     42 DEFINE_EMPTY_DESTRUCTOR_WILL_BE_REMOVED(CSSBasicShape)
     43 
     44 static 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 
     70 static 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 
     78 static 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 
    112 String 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 
    127 bool 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 
    139 void 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 
    147 static 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 
    182 String 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 
    208 bool 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 
    221 void 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 
    230 static 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 
    275 String 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 
    286 bool 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 
    299 void CSSBasicShapePolygon::trace(Visitor* visitor)
    300 {
    301     visitor->trace(m_values);
    302     CSSBasicShape::trace(visitor);
    303 }
    304 
    305 static 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 
    322 static 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 
    381 static 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 
    392 String 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 
    422 bool 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 
    438 void 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