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 WebCore { 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 WebCore 452 453