1 /* 2 * Copyright (C) 2008 Apple Inc. 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 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR 17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26 #include "config.h" 27 #include "CSSGradientValue.h" 28 29 #include "CSSValueKeywords.h" 30 #include "CSSStyleSelector.h" 31 #include "GeneratedImage.h" 32 #include "Gradient.h" 33 #include "Image.h" 34 #include "IntSize.h" 35 #include "IntSizeHash.h" 36 #include "NodeRenderStyle.h" 37 #include "PlatformString.h" 38 #include "RenderObject.h" 39 40 using namespace std; 41 42 namespace WebCore { 43 44 PassRefPtr<Image> CSSGradientValue::image(RenderObject* renderer, const IntSize& size) 45 { 46 if (size.isEmpty()) 47 return 0; 48 49 bool cacheable = isCacheable(); 50 if (cacheable) { 51 if (!m_clients.contains(renderer)) 52 return 0; 53 54 // Need to look up our size. Create a string of width*height to use as a hash key. 55 Image* result = getImage(renderer, size); 56 if (result) 57 return result; 58 } 59 60 // We need to create an image. 61 RefPtr<Image> newImage = GeneratedImage::create(createGradient(renderer, size), size); 62 if (cacheable) 63 putImage(size, newImage); 64 65 return newImage.release(); 66 } 67 68 // Should only ever be called for deprecated gradients. 69 static inline bool compareStops(const CSSGradientColorStop& a, const CSSGradientColorStop& b) 70 { 71 double aVal = a.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER); 72 double bVal = b.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER); 73 74 return aVal < bVal; 75 } 76 77 void CSSGradientValue::sortStopsIfNeeded() 78 { 79 ASSERT(m_deprecatedType); 80 if (!m_stopsSorted) { 81 if (m_stops.size()) 82 std::stable_sort(m_stops.begin(), m_stops.end(), compareStops); 83 m_stopsSorted = true; 84 } 85 } 86 87 static inline int blend(int from, int to, float progress) 88 { 89 return int(from + (to - from) * progress); 90 } 91 92 static inline Color blend(const Color& from, const Color& to, float progress) 93 { 94 // FIXME: when we interpolate gradients using premultiplied colors, this should also do premultiplication. 95 return Color(blend(from.red(), to.red(), progress), 96 blend(from.green(), to.green(), progress), 97 blend(from.blue(), to.blue(), progress), 98 blend(from.alpha(), to.alpha(), progress)); 99 } 100 101 struct GradientStop { 102 Color color; 103 float offset; 104 bool specified; 105 106 GradientStop() 107 : offset(0) 108 , specified(false) 109 { } 110 }; 111 112 void CSSGradientValue::addStops(Gradient* gradient, RenderObject* renderer, RenderStyle* rootStyle, float maxLengthForRepeat) 113 { 114 RenderStyle* style = renderer->style(); 115 116 if (m_deprecatedType) { 117 sortStopsIfNeeded(); 118 119 // We have to resolve colors. 120 for (unsigned i = 0; i < m_stops.size(); i++) { 121 const CSSGradientColorStop& stop = m_stops[i]; 122 Color color = renderer->document()->styleSelector()->getColorFromPrimitiveValue(stop.m_color.get()); 123 124 float offset; 125 if (stop.m_position->primitiveType() == CSSPrimitiveValue::CSS_PERCENTAGE) 126 offset = stop.m_position->getFloatValue(CSSPrimitiveValue::CSS_PERCENTAGE) / 100; 127 else 128 offset = stop.m_position->getFloatValue(CSSPrimitiveValue::CSS_NUMBER); 129 130 gradient->addColorStop(offset, color); 131 } 132 133 // The back end already sorted the stops. 134 gradient->setStopsSorted(true); 135 return; 136 } 137 138 size_t numStops = m_stops.size(); 139 140 Vector<GradientStop> stops(numStops); 141 142 float gradientLength = 0; 143 bool computedGradientLength = false; 144 145 FloatPoint gradientStart = gradient->p0(); 146 FloatPoint gradientEnd; 147 if (isLinearGradient()) 148 gradientEnd = gradient->p1(); 149 else if (isRadialGradient()) 150 gradientEnd = gradientStart + FloatSize(gradient->endRadius(), 0); 151 152 for (size_t i = 0; i < numStops; ++i) { 153 const CSSGradientColorStop& stop = m_stops[i]; 154 155 stops[i].color = renderer->document()->styleSelector()->getColorFromPrimitiveValue(stop.m_color.get()); 156 157 if (stop.m_position) { 158 int type = stop.m_position->primitiveType(); 159 if (type == CSSPrimitiveValue::CSS_PERCENTAGE) 160 stops[i].offset = stop.m_position->getFloatValue(CSSPrimitiveValue::CSS_PERCENTAGE) / 100; 161 else if (CSSPrimitiveValue::isUnitTypeLength(type)) { 162 float length = stop.m_position->computeLengthFloat(style, rootStyle, style->effectiveZoom()); 163 if (!computedGradientLength) { 164 FloatSize gradientSize(gradientStart - gradientEnd); 165 gradientLength = gradientSize.diagonalLength(); 166 } 167 stops[i].offset = (gradientLength > 0) ? length / gradientLength : 0; 168 } else { 169 ASSERT_NOT_REACHED(); 170 stops[i].offset = 0; 171 } 172 stops[i].specified = true; 173 } else { 174 // If the first color-stop does not have a position, its position defaults to 0%. 175 // If the last color-stop does not have a position, its position defaults to 100%. 176 if (!i) { 177 stops[i].offset = 0; 178 stops[i].specified = true; 179 } else if (numStops > 1 && i == numStops - 1) { 180 stops[i].offset = 1; 181 stops[i].specified = true; 182 } 183 } 184 185 // If a color-stop has a position that is less than the specified position of any 186 // color-stop before it in the list, its position is changed to be equal to the 187 // largest specified position of any color-stop before it. 188 if (stops[i].specified && i > 0) { 189 size_t prevSpecifiedIndex; 190 for (prevSpecifiedIndex = i - 1; prevSpecifiedIndex; --prevSpecifiedIndex) { 191 if (stops[prevSpecifiedIndex].specified) 192 break; 193 } 194 195 if (stops[i].offset < stops[prevSpecifiedIndex].offset) 196 stops[i].offset = stops[prevSpecifiedIndex].offset; 197 } 198 } 199 200 ASSERT(stops[0].specified && stops[numStops - 1].specified); 201 202 // If any color-stop still does not have a position, then, for each run of adjacent 203 // color-stops without positions, set their positions so that they are evenly spaced 204 // between the preceding and following color-stops with positions. 205 if (numStops > 2) { 206 size_t unspecifiedRunStart = 0; 207 bool inUnspecifiedRun = false; 208 209 for (size_t i = 0; i < numStops; ++i) { 210 if (!stops[i].specified && !inUnspecifiedRun) { 211 unspecifiedRunStart = i; 212 inUnspecifiedRun = true; 213 } else if (stops[i].specified && inUnspecifiedRun) { 214 size_t unspecifiedRunEnd = i; 215 216 if (unspecifiedRunStart < unspecifiedRunEnd) { 217 float lastSpecifiedOffset = stops[unspecifiedRunStart - 1].offset; 218 float nextSpecifiedOffset = stops[unspecifiedRunEnd].offset; 219 float delta = (nextSpecifiedOffset - lastSpecifiedOffset) / (unspecifiedRunEnd - unspecifiedRunStart + 1); 220 221 for (size_t j = unspecifiedRunStart; j < unspecifiedRunEnd; ++j) 222 stops[j].offset = lastSpecifiedOffset + (j - unspecifiedRunStart + 1) * delta; 223 } 224 225 inUnspecifiedRun = false; 226 } 227 } 228 } 229 230 // If the gradient is repeating, repeat the color stops. 231 // We can't just push this logic down into the platform-specific Gradient code, 232 // because we have to know the extent of the gradient, and possible move the end points. 233 if (m_repeating && numStops > 1) { 234 // If the difference in the positions of the first and last color-stops is 0, 235 // the gradient defines a solid-color image with the color of the last color-stop in the rule. 236 float gradientRange = stops[numStops - 1].offset - stops[0].offset; 237 if (!gradientRange) { 238 stops.first().offset = 0; 239 stops.first().color = stops.last().color; 240 stops.shrink(1); 241 numStops = 1; 242 } else { 243 float maxExtent = 1; 244 245 // Radial gradients may need to extend further than the endpoints, because they have 246 // to repeat out to the corners of the box. 247 if (isRadialGradient()) { 248 if (!computedGradientLength) { 249 FloatSize gradientSize(gradientStart - gradientEnd); 250 gradientLength = gradientSize.diagonalLength(); 251 } 252 253 if (maxLengthForRepeat > gradientLength) 254 maxExtent = maxLengthForRepeat / gradientLength; 255 } 256 257 size_t originalNumStops = numStops; 258 size_t originalFirstStopIndex = 0; 259 260 // Work backwards from the first, adding stops until we get one before 0. 261 float firstOffset = stops[0].offset; 262 if (firstOffset > 0) { 263 float currOffset = firstOffset; 264 size_t srcStopOrdinal = originalNumStops - 1; 265 266 while (true) { 267 GradientStop newStop = stops[originalFirstStopIndex + srcStopOrdinal]; 268 newStop.offset = currOffset; 269 stops.prepend(newStop); 270 ++originalFirstStopIndex; 271 if (currOffset < 0) 272 break; 273 274 if (srcStopOrdinal) 275 currOffset -= stops[originalFirstStopIndex + srcStopOrdinal].offset - stops[originalFirstStopIndex + srcStopOrdinal - 1].offset; 276 srcStopOrdinal = (srcStopOrdinal + originalNumStops - 1) % originalNumStops; 277 } 278 } 279 280 // Work forwards from the end, adding stops until we get one after 1. 281 float lastOffset = stops[stops.size() - 1].offset; 282 if (lastOffset < maxExtent) { 283 float currOffset = lastOffset; 284 size_t srcStopOrdinal = originalFirstStopIndex; 285 286 while (true) { 287 GradientStop newStop = stops[srcStopOrdinal]; 288 newStop.offset = currOffset; 289 stops.append(newStop); 290 if (currOffset > maxExtent) 291 break; 292 if (srcStopOrdinal < originalNumStops - 1) 293 currOffset += stops[srcStopOrdinal + 1].offset - stops[srcStopOrdinal].offset; 294 srcStopOrdinal = (srcStopOrdinal + 1) % originalNumStops; 295 } 296 } 297 } 298 } 299 300 numStops = stops.size(); 301 302 // If the gradient goes outside the 0-1 range, normalize it by moving the endpoints, and adjusting the stops. 303 if (numStops > 1 && (stops[0].offset < 0 || stops[numStops - 1].offset > 1)) { 304 if (isLinearGradient()) { 305 float firstOffset = stops[0].offset; 306 float lastOffset = stops[numStops - 1].offset; 307 float scale = lastOffset - firstOffset; 308 309 for (size_t i = 0; i < numStops; ++i) 310 stops[i].offset = (stops[i].offset - firstOffset) / scale; 311 312 FloatPoint p0 = gradient->p0(); 313 FloatPoint p1 = gradient->p1(); 314 gradient->setP0(FloatPoint(p0.x() + firstOffset * (p1.x() - p0.x()), p0.y() + firstOffset * (p1.y() - p0.y()))); 315 gradient->setP1(FloatPoint(p1.x() + (lastOffset - 1) * (p1.x() - p0.x()), p1.y() + (lastOffset - 1) * (p1.y() - p0.y()))); 316 } else if (isRadialGradient()) { 317 // Rather than scaling the points < 0, we truncate them, so only scale according to the largest point. 318 float firstOffset = 0; 319 float lastOffset = stops[numStops - 1].offset; 320 float scale = lastOffset - firstOffset; 321 322 // Reset points below 0 to the first visible color. 323 size_t firstZeroOrGreaterIndex = numStops; 324 for (size_t i = 0; i < numStops; ++i) { 325 if (stops[i].offset >= 0) { 326 firstZeroOrGreaterIndex = i; 327 break; 328 } 329 } 330 331 if (firstZeroOrGreaterIndex > 0) { 332 if (firstZeroOrGreaterIndex < numStops && stops[firstZeroOrGreaterIndex].offset > 0) { 333 float prevOffset = stops[firstZeroOrGreaterIndex - 1].offset; 334 float nextOffset = stops[firstZeroOrGreaterIndex].offset; 335 336 float interStopProportion = -prevOffset / (nextOffset - prevOffset); 337 Color blendedColor = blend(stops[firstZeroOrGreaterIndex - 1].color, stops[firstZeroOrGreaterIndex].color, interStopProportion); 338 339 // Clamp the positions to 0 and set the color. 340 for (size_t i = 0; i < firstZeroOrGreaterIndex; ++i) { 341 stops[i].offset = 0; 342 stops[i].color = blendedColor; 343 } 344 } else { 345 // All stops are below 0; just clamp them. 346 for (size_t i = 0; i < firstZeroOrGreaterIndex; ++i) 347 stops[i].offset = 0; 348 } 349 } 350 351 for (size_t i = 0; i < numStops; ++i) 352 stops[i].offset /= scale; 353 354 gradient->setStartRadius(gradient->startRadius() * scale); 355 gradient->setEndRadius(gradient->endRadius() * scale); 356 } 357 } 358 359 for (unsigned i = 0; i < numStops; i++) 360 gradient->addColorStop(stops[i].offset, stops[i].color); 361 362 gradient->setStopsSorted(true); 363 } 364 365 static float positionFromValue(CSSPrimitiveValue* value, RenderStyle* style, RenderStyle* rootStyle, const IntSize& size, bool isHorizontal) 366 { 367 float zoomFactor = style->effectiveZoom(); 368 369 switch (value->primitiveType()) { 370 case CSSPrimitiveValue::CSS_NUMBER: 371 return value->getFloatValue() * zoomFactor; 372 373 case CSSPrimitiveValue::CSS_PERCENTAGE: 374 return value->getFloatValue() / 100.f * (isHorizontal ? size.width() : size.height()); 375 376 case CSSPrimitiveValue::CSS_IDENT: 377 switch (value->getIdent()) { 378 case CSSValueTop: 379 ASSERT(!isHorizontal); 380 return 0; 381 case CSSValueLeft: 382 ASSERT(isHorizontal); 383 return 0; 384 case CSSValueBottom: 385 ASSERT(!isHorizontal); 386 return size.height(); 387 case CSSValueRight: 388 ASSERT(isHorizontal); 389 return size.width(); 390 } 391 392 default: 393 return value->computeLengthFloat(style, rootStyle, zoomFactor); 394 } 395 } 396 397 FloatPoint CSSGradientValue::computeEndPoint(CSSPrimitiveValue* first, CSSPrimitiveValue* second, RenderStyle* style, RenderStyle* rootStyle, const IntSize& size) 398 { 399 FloatPoint result; 400 401 if (first) 402 result.setX(positionFromValue(first, style, rootStyle, size, true)); 403 404 if (second) 405 result.setY(positionFromValue(second, style, rootStyle, size, false)); 406 407 return result; 408 } 409 410 bool CSSGradientValue::isCacheable() const 411 { 412 for (size_t i = 0; i < m_stops.size(); ++i) { 413 const CSSGradientColorStop& stop = m_stops[i]; 414 if (!stop.m_position) 415 continue; 416 417 unsigned short unitType = stop.m_position->primitiveType(); 418 if (unitType == CSSPrimitiveValue::CSS_EMS || unitType == CSSPrimitiveValue::CSS_EXS || unitType == CSSPrimitiveValue::CSS_REMS) 419 return false; 420 } 421 422 return true; 423 } 424 425 String CSSLinearGradientValue::cssText() const 426 { 427 String result; 428 if (m_deprecatedType) { 429 result = "-webkit-gradient(linear, "; 430 result += m_firstX->cssText() + " "; 431 result += m_firstY->cssText() + ", "; 432 result += m_secondX->cssText() + " "; 433 result += m_secondY->cssText(); 434 435 for (unsigned i = 0; i < m_stops.size(); i++) { 436 const CSSGradientColorStop& stop = m_stops[i]; 437 result += ", "; 438 if (stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER) == 0) 439 result += "from(" + stop.m_color->cssText() + ")"; 440 else if (stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER) == 1) 441 result += "to(" + stop.m_color->cssText() + ")"; 442 else 443 result += "color-stop(" + String::number(stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER)) + ", " + stop.m_color->cssText() + ")"; 444 } 445 } else { 446 result = m_repeating ? "-webkit-repeating-linear-gradient(" : "-webkit-linear-gradient("; 447 if (m_angle) 448 result += m_angle->cssText(); 449 else { 450 if (m_firstX && m_firstY) 451 result += m_firstX->cssText() + " " + m_firstY->cssText(); 452 else if (m_firstX || m_firstY) { 453 if (m_firstX) 454 result += m_firstX->cssText(); 455 456 if (m_firstY) 457 result += m_firstY->cssText(); 458 } 459 } 460 461 for (unsigned i = 0; i < m_stops.size(); i++) { 462 const CSSGradientColorStop& stop = m_stops[i]; 463 result += ", "; 464 result += stop.m_color->cssText(); 465 if (stop.m_position) 466 result += " " + stop.m_position->cssText(); 467 } 468 } 469 470 result += ")"; 471 return result; 472 } 473 474 // Compute the endpoints so that a gradient of the given angle covers a box of the given size. 475 static void endPointsFromAngle(float angleDeg, const IntSize& size, FloatPoint& firstPoint, FloatPoint& secondPoint) 476 { 477 angleDeg = fmodf(angleDeg, 360); 478 if (angleDeg < 0) 479 angleDeg += 360; 480 481 if (!angleDeg) { 482 firstPoint.set(0, 0); 483 secondPoint.set(size.width(), 0); 484 return; 485 } 486 487 if (angleDeg == 90) { 488 firstPoint.set(0, size.height()); 489 secondPoint.set(0, 0); 490 return; 491 } 492 493 if (angleDeg == 180) { 494 firstPoint.set(size.width(), 0); 495 secondPoint.set(0, 0); 496 return; 497 } 498 499 float slope = tan(deg2rad(angleDeg)); 500 501 // We find the endpoint by computing the intersection of the line formed by the slope, 502 // and a line perpendicular to it that intersects the corner. 503 float perpendicularSlope = -1 / slope; 504 505 // Compute start corner relative to center. 506 float halfHeight = size.height() / 2; 507 float halfWidth = size.width() / 2; 508 FloatPoint endCorner; 509 if (angleDeg < 90) 510 endCorner.set(halfWidth, halfHeight); 511 else if (angleDeg < 180) 512 endCorner.set(-halfWidth, halfHeight); 513 else if (angleDeg < 270) 514 endCorner.set(-halfWidth, -halfHeight); 515 else 516 endCorner.set(halfWidth, -halfHeight); 517 518 // Compute c (of y = mx + c) using the corner point. 519 float c = endCorner.y() - perpendicularSlope * endCorner.x(); 520 float endX = c / (slope - perpendicularSlope); 521 float endY = perpendicularSlope * endX + c; 522 523 // We computed the end point, so set the second point, flipping the Y to account for angles going anticlockwise. 524 secondPoint.set(halfWidth + endX, size.height() - (halfHeight + endY)); 525 // Reflect around the center for the start point. 526 firstPoint.set(size.width() - secondPoint.x(), size.height() - secondPoint.y()); 527 } 528 529 PassRefPtr<Gradient> CSSLinearGradientValue::createGradient(RenderObject* renderer, const IntSize& size) 530 { 531 ASSERT(!size.isEmpty()); 532 533 RenderStyle* rootStyle = renderer->document()->documentElement()->renderStyle(); 534 535 FloatPoint firstPoint; 536 FloatPoint secondPoint; 537 if (m_angle) { 538 float angle = m_angle->getFloatValue(CSSPrimitiveValue::CSS_DEG); 539 endPointsFromAngle(angle, size, firstPoint, secondPoint); 540 } else { 541 firstPoint = computeEndPoint(m_firstX.get(), m_firstY.get(), renderer->style(), rootStyle, size); 542 543 if (m_secondX || m_secondY) 544 secondPoint = computeEndPoint(m_secondX.get(), m_secondY.get(), renderer->style(), rootStyle, size); 545 else { 546 if (m_firstX) 547 secondPoint.setX(size.width() - firstPoint.x()); 548 if (m_firstY) 549 secondPoint.setY(size.height() - firstPoint.y()); 550 } 551 } 552 553 RefPtr<Gradient> gradient = Gradient::create(firstPoint, secondPoint); 554 555 // Now add the stops. 556 addStops(gradient.get(), renderer, rootStyle, 1); 557 558 return gradient.release(); 559 } 560 561 String CSSRadialGradientValue::cssText() const 562 { 563 String result; 564 565 if (m_deprecatedType) { 566 result = "-webkit-gradient(radial, "; 567 568 result += m_firstX->cssText() + " "; 569 result += m_firstY->cssText() + ", "; 570 result += m_firstRadius->cssText() + ", "; 571 result += m_secondX->cssText() + " "; 572 result += m_secondY->cssText(); 573 result += ", "; 574 result += m_secondRadius->cssText(); 575 576 // FIXME: share? 577 for (unsigned i = 0; i < m_stops.size(); i++) { 578 const CSSGradientColorStop& stop = m_stops[i]; 579 result += ", "; 580 if (stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER) == 0) 581 result += "from(" + stop.m_color->cssText() + ")"; 582 else if (stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER) == 1) 583 result += "to(" + stop.m_color->cssText() + ")"; 584 else 585 result += "color-stop(" + String::number(stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER)) + ", " + stop.m_color->cssText() + ")"; 586 } 587 } else { 588 589 result = m_repeating ? "-webkit-repeating-radial-gradient(" : "-webkit-radial-gradient("; 590 if (m_firstX && m_firstY) { 591 result += m_firstX->cssText() + " " + m_firstY->cssText(); 592 } else if (m_firstX) 593 result += m_firstX->cssText(); 594 else if (m_firstY) 595 result += m_firstY->cssText(); 596 else 597 result += "center"; 598 599 600 if (m_shape || m_sizingBehavior) { 601 result += ", "; 602 if (m_shape) 603 result += m_shape->cssText() + " "; 604 else 605 result += "ellipse "; 606 607 if (m_sizingBehavior) 608 result += m_sizingBehavior->cssText(); 609 else 610 result += "cover"; 611 612 } else if (m_endHorizontalSize && m_endVerticalSize) { 613 result += ", "; 614 result += m_endHorizontalSize->cssText() + " " + m_endVerticalSize->cssText(); 615 } 616 617 for (unsigned i = 0; i < m_stops.size(); i++) { 618 const CSSGradientColorStop& stop = m_stops[i]; 619 result += ", "; 620 result += stop.m_color->cssText(); 621 if (stop.m_position) 622 result += " " + stop.m_position->cssText(); 623 } 624 } 625 626 result += ")"; 627 return result; 628 } 629 630 float CSSRadialGradientValue::resolveRadius(CSSPrimitiveValue* radius, RenderStyle* style, RenderStyle* rootStyle, float* widthOrHeight) 631 { 632 float zoomFactor = style->effectiveZoom(); 633 634 float result = 0; 635 if (radius->primitiveType() == CSSPrimitiveValue::CSS_NUMBER) // Can the radius be a percentage? 636 result = radius->getFloatValue() * zoomFactor; 637 else if (widthOrHeight && radius->primitiveType() == CSSPrimitiveValue::CSS_PERCENTAGE) 638 result = *widthOrHeight * radius->getFloatValue() / 100; 639 else 640 result = radius->computeLengthFloat(style, rootStyle, zoomFactor); 641 642 return result; 643 } 644 645 static float distanceToClosestCorner(const FloatPoint& p, const FloatSize& size, FloatPoint& corner) 646 { 647 FloatPoint topLeft; 648 float topLeftDistance = FloatSize(p - topLeft).diagonalLength(); 649 650 FloatPoint topRight(size.width(), 0); 651 float topRightDistance = FloatSize(p - topRight).diagonalLength(); 652 653 FloatPoint bottomLeft(0, size.height()); 654 float bottomLeftDistance = FloatSize(p - bottomLeft).diagonalLength(); 655 656 FloatPoint bottomRight(size.width(), size.height()); 657 float bottomRightDistance = FloatSize(p - bottomRight).diagonalLength(); 658 659 corner = topLeft; 660 float minDistance = topLeftDistance; 661 if (topRightDistance < minDistance) { 662 minDistance = topRightDistance; 663 corner = topRight; 664 } 665 666 if (bottomLeftDistance < minDistance) { 667 minDistance = bottomLeftDistance; 668 corner = bottomLeft; 669 } 670 671 if (bottomRightDistance < minDistance) { 672 minDistance = bottomRightDistance; 673 corner = bottomRight; 674 } 675 return minDistance; 676 } 677 678 static float distanceToFarthestCorner(const FloatPoint& p, const FloatSize& size, FloatPoint& corner) 679 { 680 FloatPoint topLeft; 681 float topLeftDistance = FloatSize(p - topLeft).diagonalLength(); 682 683 FloatPoint topRight(size.width(), 0); 684 float topRightDistance = FloatSize(p - topRight).diagonalLength(); 685 686 FloatPoint bottomLeft(0, size.height()); 687 float bottomLeftDistance = FloatSize(p - bottomLeft).diagonalLength(); 688 689 FloatPoint bottomRight(size.width(), size.height()); 690 float bottomRightDistance = FloatSize(p - bottomRight).diagonalLength(); 691 692 corner = topLeft; 693 float maxDistance = topLeftDistance; 694 if (topRightDistance > maxDistance) { 695 maxDistance = topRightDistance; 696 corner = topRight; 697 } 698 699 if (bottomLeftDistance > maxDistance) { 700 maxDistance = bottomLeftDistance; 701 corner = bottomLeft; 702 } 703 704 if (bottomRightDistance > maxDistance) { 705 maxDistance = bottomRightDistance; 706 corner = bottomRight; 707 } 708 return maxDistance; 709 } 710 711 // Compute horizontal radius of ellipse with center at 0,0 which passes through p, and has 712 // width/height given by aspectRatio. 713 static inline float horizontalEllipseRadius(const FloatSize& p, float aspectRatio) 714 { 715 // x^2/a^2 + y^2/b^2 = 1 716 // a/b = aspectRatio, b = a/aspectRatio 717 // a = sqrt(x^2 + y^2/(1/r^2)) 718 return sqrtf(p.width() * p.width() + (p.height() * p.height()) / (1 / (aspectRatio * aspectRatio))); 719 } 720 721 // FIXME: share code with the linear version 722 PassRefPtr<Gradient> CSSRadialGradientValue::createGradient(RenderObject* renderer, const IntSize& size) 723 { 724 ASSERT(!size.isEmpty()); 725 726 RenderStyle* rootStyle = renderer->document()->documentElement()->renderStyle(); 727 728 FloatPoint firstPoint = computeEndPoint(m_firstX.get(), m_firstY.get(), renderer->style(), rootStyle, size); 729 if (!m_firstX) 730 firstPoint.setX(size.width() / 2); 731 if (!m_firstY) 732 firstPoint.setY(size.height() / 2); 733 734 FloatPoint secondPoint = computeEndPoint(m_secondX.get(), m_secondY.get(), renderer->style(), rootStyle, size); 735 if (!m_secondX) 736 secondPoint.setX(size.width() / 2); 737 if (!m_secondY) 738 secondPoint.setY(size.height() / 2); 739 740 float firstRadius = 0; 741 if (m_firstRadius) 742 firstRadius = resolveRadius(m_firstRadius.get(), renderer->style(), rootStyle); 743 744 float secondRadius = 0; 745 float aspectRatio = 1; // width / height. 746 if (m_secondRadius) 747 secondRadius = resolveRadius(m_secondRadius.get(), renderer->style(), rootStyle); 748 else if (m_endHorizontalSize || m_endVerticalSize) { 749 float width = size.width(); 750 float height = size.height(); 751 secondRadius = resolveRadius(m_endHorizontalSize.get(), renderer->style(), rootStyle, &width); 752 aspectRatio = secondRadius / resolveRadius(m_endVerticalSize.get(), renderer->style(), rootStyle, &height); 753 } else { 754 enum GradientShape { Circle, Ellipse }; 755 GradientShape shape = Ellipse; 756 if (m_shape && m_shape->primitiveType() == CSSPrimitiveValue::CSS_IDENT && m_shape->getIdent() == CSSValueCircle) 757 shape = Circle; 758 759 enum GradientFill { ClosestSide, ClosestCorner, FarthestSide, FarthestCorner }; 760 GradientFill fill = FarthestCorner; 761 762 if (m_sizingBehavior && m_sizingBehavior->primitiveType() == CSSPrimitiveValue::CSS_IDENT) { 763 switch (m_sizingBehavior->getIdent()) { 764 case CSSValueContain: 765 case CSSValueClosestSide: 766 fill = ClosestSide; 767 break; 768 case CSSValueClosestCorner: 769 fill = ClosestCorner; 770 break; 771 case CSSValueFarthestSide: 772 fill = FarthestSide; 773 break; 774 case CSSValueCover: 775 case CSSValueFarthestCorner: 776 fill = FarthestCorner; 777 break; 778 } 779 } 780 781 // Now compute the end radii based on the second point, shape and fill. 782 783 // Horizontal 784 switch (fill) { 785 case ClosestSide: { 786 float xDist = min(secondPoint.x(), size.width() - secondPoint.x()); 787 float yDist = min(secondPoint.y(), size.height() - secondPoint.y()); 788 if (shape == Circle) { 789 float smaller = min(xDist, yDist); 790 xDist = smaller; 791 yDist = smaller; 792 } 793 secondRadius = xDist; 794 aspectRatio = xDist / yDist; 795 break; 796 } 797 case FarthestSide: { 798 float xDist = max(secondPoint.x(), size.width() - secondPoint.x()); 799 float yDist = max(secondPoint.y(), size.height() - secondPoint.y()); 800 if (shape == Circle) { 801 float larger = max(xDist, yDist); 802 xDist = larger; 803 yDist = larger; 804 } 805 secondRadius = xDist; 806 aspectRatio = xDist / yDist; 807 break; 808 } 809 case ClosestCorner: { 810 FloatPoint corner; 811 float distance = distanceToClosestCorner(secondPoint, size, corner); 812 if (shape == Circle) 813 secondRadius = distance; 814 else { 815 // If <shape> is ellipse, the gradient-shape has the same ratio of width to height 816 // that it would if closest-side or farthest-side were specified, as appropriate. 817 float xDist = min(secondPoint.x(), size.width() - secondPoint.x()); 818 float yDist = min(secondPoint.y(), size.height() - secondPoint.y()); 819 820 secondRadius = horizontalEllipseRadius(corner - secondPoint, xDist / yDist); 821 aspectRatio = xDist / yDist; 822 } 823 break; 824 } 825 826 case FarthestCorner: { 827 FloatPoint corner; 828 float distance = distanceToFarthestCorner(secondPoint, size, corner); 829 if (shape == Circle) 830 secondRadius = distance; 831 else { 832 // If <shape> is ellipse, the gradient-shape has the same ratio of width to height 833 // that it would if closest-side or farthest-side were specified, as appropriate. 834 float xDist = max(secondPoint.x(), size.width() - secondPoint.x()); 835 float yDist = max(secondPoint.y(), size.height() - secondPoint.y()); 836 837 secondRadius = horizontalEllipseRadius(corner - secondPoint, xDist / yDist); 838 aspectRatio = xDist / yDist; 839 } 840 break; 841 } 842 } 843 } 844 845 RefPtr<Gradient> gradient = Gradient::create(firstPoint, firstRadius, secondPoint, secondRadius, aspectRatio); 846 847 // addStops() only uses maxExtent for repeating gradients. 848 float maxExtent = 0; 849 if (m_repeating) { 850 FloatPoint corner; 851 maxExtent = distanceToFarthestCorner(secondPoint, size, corner); 852 } 853 854 // Now add the stops. 855 addStops(gradient.get(), renderer, rootStyle, maxExtent); 856 857 return gradient.release(); 858 } 859 860 } // namespace WebCore 861