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