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 "CSSCalculationValue.h" 30#include "CSSValueKeywords.h" 31#include "GeneratorGeneratedImage.h" 32#include "Gradient.h" 33#include "Image.h" 34#include "IntSize.h" 35#include "IntSizeHash.h" 36#include "NodeRenderStyle.h" 37#include "RenderObject.h" 38#include "StyleResolver.h" 39#include <wtf/text/StringBuilder.h> 40#include <wtf/text/WTFString.h> 41 42using namespace std; 43 44namespace WebCore { 45 46PassRefPtr<Image> CSSGradientValue::image(RenderObject* renderer, const IntSize& size) 47{ 48 if (size.isEmpty()) 49 return 0; 50 51 bool cacheable = isCacheable(); 52 if (cacheable) { 53 if (!clients().contains(renderer)) 54 return 0; 55 56 Image* result = cachedImageForSize(size); 57 if (result) 58 return result; 59 } 60 61 RefPtr<Gradient> gradient; 62 63 if (isLinearGradient()) 64 gradient = static_cast<CSSLinearGradientValue*>(this)->createGradient(renderer, size); 65 else { 66 ASSERT(isRadialGradient()); 67 gradient = static_cast<CSSRadialGradientValue*>(this)->createGradient(renderer, size); 68 } 69 70 RefPtr<GeneratorGeneratedImage> newImage = GeneratorGeneratedImage::create(gradient, size); 71 if (cacheable) 72 saveCachedImageForSize(size, newImage); 73 74 return newImage.release(); 75} 76 77// Should only ever be called for deprecated gradients. 78static inline bool compareStops(const CSSGradientColorStop& a, const CSSGradientColorStop& b) 79{ 80 double aVal = a.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER); 81 double bVal = b.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER); 82 83 return aVal < bVal; 84} 85 86void CSSGradientValue::sortStopsIfNeeded() 87{ 88 ASSERT(m_gradientType == CSSDeprecatedLinearGradient || m_gradientType == CSSDeprecatedRadialGradient); 89 if (!m_stopsSorted) { 90 if (m_stops.size()) 91 std::stable_sort(m_stops.begin(), m_stops.end(), compareStops); 92 m_stopsSorted = true; 93 } 94} 95 96struct GradientStop { 97 Color color; 98 float offset; 99 bool specified; 100 101 GradientStop() 102 : offset(0) 103 , specified(false) 104 { } 105}; 106 107PassRefPtr<CSSGradientValue> CSSGradientValue::gradientWithStylesResolved(StyleResolver* styleResolver) 108{ 109 bool derived = false; 110 for (unsigned i = 0; i < m_stops.size(); i++) 111 if (styleResolver->colorFromPrimitiveValueIsDerivedFromElement(m_stops[i].m_color.get())) { 112 m_stops[i].m_colorIsDerivedFromElement = true; 113 derived = true; 114 break; 115 } 116 117 RefPtr<CSSGradientValue> result; 118 if (!derived) 119 result = this; 120 else if (isLinearGradient()) 121 result = static_cast<CSSLinearGradientValue*>(this)->clone(); 122 else if (isRadialGradient()) 123 result = static_cast<CSSRadialGradientValue*>(this)->clone(); 124 else { 125 ASSERT_NOT_REACHED(); 126 return 0; 127 } 128 129 for (unsigned i = 0; i < result->m_stops.size(); i++) 130 result->m_stops[i].m_resolvedColor = styleResolver->colorFromPrimitiveValue(result->m_stops[i].m_color.get()); 131 132 return result.release(); 133} 134 135void CSSGradientValue::addStops(Gradient* gradient, RenderObject* renderer, RenderStyle* rootStyle, float maxLengthForRepeat) 136{ 137 RenderStyle* style = renderer->style(); 138 139 if (m_gradientType == CSSDeprecatedLinearGradient || m_gradientType == CSSDeprecatedRadialGradient) { 140 sortStopsIfNeeded(); 141 142 for (unsigned i = 0; i < m_stops.size(); i++) { 143 const CSSGradientColorStop& stop = m_stops[i]; 144 145 float offset; 146 if (stop.m_position->isPercentage()) 147 offset = stop.m_position->getFloatValue(CSSPrimitiveValue::CSS_PERCENTAGE) / 100; 148 else 149 offset = stop.m_position->getFloatValue(CSSPrimitiveValue::CSS_NUMBER); 150 151 gradient->addColorStop(offset, stop.m_resolvedColor); 152 } 153 154 // The back end already sorted the stops. 155 gradient->setStopsSorted(true); 156 return; 157 } 158 159 size_t numStops = m_stops.size(); 160 161 Vector<GradientStop> stops(numStops); 162 163 float gradientLength = 0; 164 bool computedGradientLength = false; 165 166 FloatPoint gradientStart = gradient->p0(); 167 FloatPoint gradientEnd; 168 if (isLinearGradient()) 169 gradientEnd = gradient->p1(); 170 else if (isRadialGradient()) 171 gradientEnd = gradientStart + FloatSize(gradient->endRadius(), 0); 172 173 for (size_t i = 0; i < numStops; ++i) { 174 const CSSGradientColorStop& stop = m_stops[i]; 175 176 stops[i].color = stop.m_resolvedColor; 177 178 if (stop.m_position) { 179 if (stop.m_position->isPercentage()) 180 stops[i].offset = stop.m_position->getFloatValue(CSSPrimitiveValue::CSS_PERCENTAGE) / 100; 181 else if (stop.m_position->isLength() || stop.m_position->isCalculatedPercentageWithLength()) { 182 if (!computedGradientLength) { 183 FloatSize gradientSize(gradientStart - gradientEnd); 184 gradientLength = gradientSize.diagonalLength(); 185 } 186 float length; 187 if (stop.m_position->isLength()) 188 length = stop.m_position->computeLength<float>(style, rootStyle, style->effectiveZoom()); 189 else 190 length = stop.m_position->cssCalcValue()->toCalcValue(style, rootStyle, style->effectiveZoom())->evaluate(gradientLength); 191 stops[i].offset = (gradientLength > 0) ? length / gradientLength : 0; 192 } else { 193 ASSERT_NOT_REACHED(); 194 stops[i].offset = 0; 195 } 196 stops[i].specified = true; 197 } else { 198 // If the first color-stop does not have a position, its position defaults to 0%. 199 // If the last color-stop does not have a position, its position defaults to 100%. 200 if (!i) { 201 stops[i].offset = 0; 202 stops[i].specified = true; 203 } else if (numStops > 1 && i == numStops - 1) { 204 stops[i].offset = 1; 205 stops[i].specified = true; 206 } 207 } 208 209 // If a color-stop has a position that is less than the specified position of any 210 // color-stop before it in the list, its position is changed to be equal to the 211 // largest specified position of any color-stop before it. 212 if (stops[i].specified && i > 0) { 213 size_t prevSpecifiedIndex; 214 for (prevSpecifiedIndex = i - 1; prevSpecifiedIndex; --prevSpecifiedIndex) { 215 if (stops[prevSpecifiedIndex].specified) 216 break; 217 } 218 219 if (stops[i].offset < stops[prevSpecifiedIndex].offset) 220 stops[i].offset = stops[prevSpecifiedIndex].offset; 221 } 222 } 223 224 ASSERT(stops[0].specified && stops[numStops - 1].specified); 225 226 // If any color-stop still does not have a position, then, for each run of adjacent 227 // color-stops without positions, set their positions so that they are evenly spaced 228 // between the preceding and following color-stops with positions. 229 if (numStops > 2) { 230 size_t unspecifiedRunStart = 0; 231 bool inUnspecifiedRun = false; 232 233 for (size_t i = 0; i < numStops; ++i) { 234 if (!stops[i].specified && !inUnspecifiedRun) { 235 unspecifiedRunStart = i; 236 inUnspecifiedRun = true; 237 } else if (stops[i].specified && inUnspecifiedRun) { 238 size_t unspecifiedRunEnd = i; 239 240 if (unspecifiedRunStart < unspecifiedRunEnd) { 241 float lastSpecifiedOffset = stops[unspecifiedRunStart - 1].offset; 242 float nextSpecifiedOffset = stops[unspecifiedRunEnd].offset; 243 float delta = (nextSpecifiedOffset - lastSpecifiedOffset) / (unspecifiedRunEnd - unspecifiedRunStart + 1); 244 245 for (size_t j = unspecifiedRunStart; j < unspecifiedRunEnd; ++j) 246 stops[j].offset = lastSpecifiedOffset + (j - unspecifiedRunStart + 1) * delta; 247 } 248 249 inUnspecifiedRun = false; 250 } 251 } 252 } 253 254 // If the gradient is repeating, repeat the color stops. 255 // We can't just push this logic down into the platform-specific Gradient code, 256 // because we have to know the extent of the gradient, and possible move the end points. 257 if (m_repeating && numStops > 1) { 258 // If the difference in the positions of the first and last color-stops is 0, 259 // the gradient defines a solid-color image with the color of the last color-stop in the rule. 260 float gradientRange = stops[numStops - 1].offset - stops[0].offset; 261 if (!gradientRange) { 262 stops.first().offset = 0; 263 stops.first().color = stops.last().color; 264 stops.shrink(1); 265 numStops = 1; 266 } else { 267 float maxExtent = 1; 268 269 // Radial gradients may need to extend further than the endpoints, because they have 270 // to repeat out to the corners of the box. 271 if (isRadialGradient()) { 272 if (!computedGradientLength) { 273 FloatSize gradientSize(gradientStart - gradientEnd); 274 gradientLength = gradientSize.diagonalLength(); 275 } 276 277 if (maxLengthForRepeat > gradientLength) 278 maxExtent = maxLengthForRepeat / gradientLength; 279 } 280 281 size_t originalNumStops = numStops; 282 size_t originalFirstStopIndex = 0; 283 284 // Work backwards from the first, adding stops until we get one before 0. 285 float firstOffset = stops[0].offset; 286 if (firstOffset > 0) { 287 float currOffset = firstOffset; 288 size_t srcStopOrdinal = originalNumStops - 1; 289 290 while (true) { 291 GradientStop newStop = stops[originalFirstStopIndex + srcStopOrdinal]; 292 newStop.offset = currOffset; 293 stops.insert(0, newStop); 294 ++originalFirstStopIndex; 295 if (currOffset < 0) 296 break; 297 298 if (srcStopOrdinal) 299 currOffset -= stops[originalFirstStopIndex + srcStopOrdinal].offset - stops[originalFirstStopIndex + srcStopOrdinal - 1].offset; 300 srcStopOrdinal = (srcStopOrdinal + originalNumStops - 1) % originalNumStops; 301 } 302 } 303 304 // Work forwards from the end, adding stops until we get one after 1. 305 float lastOffset = stops[stops.size() - 1].offset; 306 if (lastOffset < maxExtent) { 307 float currOffset = lastOffset; 308 size_t srcStopOrdinal = 0; 309 310 while (true) { 311 size_t srcStopIndex = originalFirstStopIndex + srcStopOrdinal; 312 GradientStop newStop = stops[srcStopIndex]; 313 newStop.offset = currOffset; 314 stops.append(newStop); 315 if (currOffset > maxExtent) 316 break; 317 if (srcStopOrdinal < originalNumStops - 1) 318 currOffset += stops[srcStopIndex + 1].offset - stops[srcStopIndex].offset; 319 srcStopOrdinal = (srcStopOrdinal + 1) % originalNumStops; 320 } 321 } 322 } 323 } 324 325 numStops = stops.size(); 326 327 // If the gradient goes outside the 0-1 range, normalize it by moving the endpoints, and adjusting the stops. 328 if (numStops > 1 && (stops[0].offset < 0 || stops[numStops - 1].offset > 1)) { 329 if (isLinearGradient()) { 330 float firstOffset = stops[0].offset; 331 float lastOffset = stops[numStops - 1].offset; 332 if (firstOffset != lastOffset) { 333 float scale = lastOffset - firstOffset; 334 335 for (size_t i = 0; i < numStops; ++i) 336 stops[i].offset = (stops[i].offset - firstOffset) / scale; 337 338 FloatPoint p0 = gradient->p0(); 339 FloatPoint p1 = gradient->p1(); 340 gradient->setP0(FloatPoint(p0.x() + firstOffset * (p1.x() - p0.x()), p0.y() + firstOffset * (p1.y() - p0.y()))); 341 gradient->setP1(FloatPoint(p1.x() + (lastOffset - 1) * (p1.x() - p0.x()), p1.y() + (lastOffset - 1) * (p1.y() - p0.y()))); 342 } else { 343 // There's a single position that is outside the scale, clamp the positions to 1. 344 for (size_t i = 0; i < numStops; ++i) 345 stops[i].offset = 1; 346 } 347 } else if (isRadialGradient()) { 348 // Rather than scaling the points < 0, we truncate them, so only scale according to the largest point. 349 float firstOffset = 0; 350 float lastOffset = stops[numStops - 1].offset; 351 float scale = lastOffset - firstOffset; 352 353 // Reset points below 0 to the first visible color. 354 size_t firstZeroOrGreaterIndex = numStops; 355 for (size_t i = 0; i < numStops; ++i) { 356 if (stops[i].offset >= 0) { 357 firstZeroOrGreaterIndex = i; 358 break; 359 } 360 } 361 362 if (firstZeroOrGreaterIndex > 0) { 363 if (firstZeroOrGreaterIndex < numStops && stops[firstZeroOrGreaterIndex].offset > 0) { 364 float prevOffset = stops[firstZeroOrGreaterIndex - 1].offset; 365 float nextOffset = stops[firstZeroOrGreaterIndex].offset; 366 367 float interStopProportion = -prevOffset / (nextOffset - prevOffset); 368 // FIXME: when we interpolate gradients using premultiplied colors, this should do premultiplication. 369 Color blendedColor = blend(stops[firstZeroOrGreaterIndex - 1].color, stops[firstZeroOrGreaterIndex].color, interStopProportion); 370 371 // Clamp the positions to 0 and set the color. 372 for (size_t i = 0; i < firstZeroOrGreaterIndex; ++i) { 373 stops[i].offset = 0; 374 stops[i].color = blendedColor; 375 } 376 } else { 377 // All stops are below 0; just clamp them. 378 for (size_t i = 0; i < firstZeroOrGreaterIndex; ++i) 379 stops[i].offset = 0; 380 } 381 } 382 383 for (size_t i = 0; i < numStops; ++i) 384 stops[i].offset /= scale; 385 386 gradient->setStartRadius(gradient->startRadius() * scale); 387 gradient->setEndRadius(gradient->endRadius() * scale); 388 } 389 } 390 391 for (unsigned i = 0; i < numStops; i++) 392 gradient->addColorStop(stops[i].offset, stops[i].color); 393 394 gradient->setStopsSorted(true); 395} 396 397static float positionFromValue(CSSPrimitiveValue* value, RenderStyle* style, RenderStyle* rootStyle, const IntSize& size, bool isHorizontal) 398{ 399 float zoomFactor = style->effectiveZoom(); 400 401 if (value->isNumber()) 402 return value->getFloatValue() * zoomFactor; 403 404 int edgeDistance = isHorizontal ? size.width() : size.height(); 405 if (value->isPercentage()) 406 return value->getFloatValue() / 100.f * edgeDistance; 407 408 if (value->isCalculatedPercentageWithLength()) 409 return value->cssCalcValue()->toCalcValue(style, rootStyle, style->effectiveZoom())->evaluate(edgeDistance); 410 411 switch (value->getIdent()) { 412 case CSSValueTop: 413 ASSERT(!isHorizontal); 414 return 0; 415 case CSSValueLeft: 416 ASSERT(isHorizontal); 417 return 0; 418 case CSSValueBottom: 419 ASSERT(!isHorizontal); 420 return size.height(); 421 case CSSValueRight: 422 ASSERT(isHorizontal); 423 return size.width(); 424 } 425 426 return value->computeLength<float>(style, rootStyle, zoomFactor); 427} 428 429FloatPoint CSSGradientValue::computeEndPoint(CSSPrimitiveValue* horizontal, CSSPrimitiveValue* vertical, RenderStyle* style, RenderStyle* rootStyle, const IntSize& size) 430{ 431 FloatPoint result; 432 433 if (horizontal) 434 result.setX(positionFromValue(horizontal, style, rootStyle, size, true)); 435 436 if (vertical) 437 result.setY(positionFromValue(vertical, style, rootStyle, size, false)); 438 439 return result; 440} 441 442bool CSSGradientValue::isCacheable() const 443{ 444 for (size_t i = 0; i < m_stops.size(); ++i) { 445 const CSSGradientColorStop& stop = m_stops[i]; 446 447 if (stop.m_colorIsDerivedFromElement) 448 return false; 449 450 if (!stop.m_position) 451 continue; 452 453 if (stop.m_position->isFontRelativeLength()) 454 return false; 455 } 456 457 return true; 458} 459 460bool CSSGradientValue::knownToBeOpaque(const RenderObject*) const 461{ 462 for (size_t i = 0; i < m_stops.size(); ++i) { 463 if (m_stops[i].m_resolvedColor.hasAlpha()) 464 return false; 465 } 466 return true; 467} 468 469String CSSLinearGradientValue::customCssText() const 470{ 471 StringBuilder result; 472 if (m_gradientType == CSSDeprecatedLinearGradient) { 473 result.appendLiteral("-webkit-gradient(linear, "); 474 result.append(m_firstX->cssText()); 475 result.append(' '); 476 result.append(m_firstY->cssText()); 477 result.appendLiteral(", "); 478 result.append(m_secondX->cssText()); 479 result.append(' '); 480 result.append(m_secondY->cssText()); 481 482 for (unsigned i = 0; i < m_stops.size(); i++) { 483 const CSSGradientColorStop& stop = m_stops[i]; 484 result.appendLiteral(", "); 485 if (stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER) == 0) { 486 result.appendLiteral("from("); 487 result.append(stop.m_color->cssText()); 488 result.append(')'); 489 } else if (stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER) == 1) { 490 result.appendLiteral("to("); 491 result.append(stop.m_color->cssText()); 492 result.append(')'); 493 } else { 494 result.appendLiteral("color-stop("); 495 result.append(String::number(stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER))); 496 result.appendLiteral(", "); 497 result.append(stop.m_color->cssText()); 498 result.append(')'); 499 } 500 } 501 } else if (m_gradientType == CSSPrefixedLinearGradient) { 502 if (m_repeating) 503 result.appendLiteral("-webkit-repeating-linear-gradient("); 504 else 505 result.appendLiteral("-webkit-linear-gradient("); 506 507 if (m_angle) 508 result.append(m_angle->cssText()); 509 else { 510 if (m_firstX && m_firstY) { 511 result.append(m_firstX->cssText()); 512 result.append(' '); 513 result.append(m_firstY->cssText()); 514 } else if (m_firstX || m_firstY) { 515 if (m_firstX) 516 result.append(m_firstX->cssText()); 517 518 if (m_firstY) 519 result.append(m_firstY->cssText()); 520 } 521 } 522 523 for (unsigned i = 0; i < m_stops.size(); i++) { 524 const CSSGradientColorStop& stop = m_stops[i]; 525 result.appendLiteral(", "); 526 result.append(stop.m_color->cssText()); 527 if (stop.m_position) { 528 result.append(' '); 529 result.append(stop.m_position->cssText()); 530 } 531 } 532 } else { 533 if (m_repeating) 534 result.appendLiteral("repeating-linear-gradient("); 535 else 536 result.appendLiteral("linear-gradient("); 537 538 bool wroteSomething = false; 539 540 if (m_angle && m_angle->computeDegrees() != 180) { 541 result.append(m_angle->cssText()); 542 wroteSomething = true; 543 } else if ((m_firstX || m_firstY) && !(!m_firstX && m_firstY && m_firstY->getIdent() == CSSValueBottom)) { 544 result.appendLiteral("to "); 545 if (m_firstX && m_firstY) { 546 result.append(m_firstX->cssText()); 547 result.append(' '); 548 result.append(m_firstY->cssText()); 549 } else if (m_firstX) 550 result.append(m_firstX->cssText()); 551 else 552 result.append(m_firstY->cssText()); 553 wroteSomething = true; 554 } 555 556 if (wroteSomething) 557 result.appendLiteral(", "); 558 559 for (unsigned i = 0; i < m_stops.size(); i++) { 560 const CSSGradientColorStop& stop = m_stops[i]; 561 if (i) 562 result.appendLiteral(", "); 563 result.append(stop.m_color->cssText()); 564 if (stop.m_position) { 565 result.append(' '); 566 result.append(stop.m_position->cssText()); 567 } 568 } 569 570 } 571 572 result.append(')'); 573 return result.toString(); 574} 575 576// Compute the endpoints so that a gradient of the given angle covers a box of the given size. 577static void endPointsFromAngle(float angleDeg, const IntSize& size, FloatPoint& firstPoint, FloatPoint& secondPoint, CSSGradientType type) 578{ 579 // Prefixed gradients use "polar coordinate" angles, rather than "bearing" angles. 580 if (type == CSSPrefixedLinearGradient) 581 angleDeg = 90 - angleDeg; 582 583 angleDeg = fmodf(angleDeg, 360); 584 if (angleDeg < 0) 585 angleDeg += 360; 586 587 if (!angleDeg) { 588 firstPoint.set(0, size.height()); 589 secondPoint.set(0, 0); 590 return; 591 } 592 593 if (angleDeg == 90) { 594 firstPoint.set(0, 0); 595 secondPoint.set(size.width(), 0); 596 return; 597 } 598 599 if (angleDeg == 180) { 600 firstPoint.set(0, 0); 601 secondPoint.set(0, size.height()); 602 return; 603 } 604 605 if (angleDeg == 270) { 606 firstPoint.set(size.width(), 0); 607 secondPoint.set(0, 0); 608 return; 609 } 610 611 // angleDeg is a "bearing angle" (0deg = N, 90deg = E), 612 // but tan expects 0deg = E, 90deg = N. 613 float slope = tan(deg2rad(90 - angleDeg)); 614 615 // We find the endpoint by computing the intersection of the line formed by the slope, 616 // and a line perpendicular to it that intersects the corner. 617 float perpendicularSlope = -1 / slope; 618 619 // Compute start corner relative to center, in Cartesian space (+y = up). 620 float halfHeight = size.height() / 2; 621 float halfWidth = size.width() / 2; 622 FloatPoint endCorner; 623 if (angleDeg < 90) 624 endCorner.set(halfWidth, halfHeight); 625 else if (angleDeg < 180) 626 endCorner.set(halfWidth, -halfHeight); 627 else if (angleDeg < 270) 628 endCorner.set(-halfWidth, -halfHeight); 629 else 630 endCorner.set(-halfWidth, halfHeight); 631 632 // Compute c (of y = mx + c) using the corner point. 633 float c = endCorner.y() - perpendicularSlope * endCorner.x(); 634 float endX = c / (slope - perpendicularSlope); 635 float endY = perpendicularSlope * endX + c; 636 637 // We computed the end point, so set the second point, 638 // taking into account the moved origin and the fact that we're in drawing space (+y = down). 639 secondPoint.set(halfWidth + endX, halfHeight - endY); 640 // Reflect around the center for the start point. 641 firstPoint.set(halfWidth - endX, halfHeight + endY); 642} 643 644PassRefPtr<Gradient> CSSLinearGradientValue::createGradient(RenderObject* renderer, const IntSize& size) 645{ 646 ASSERT(!size.isEmpty()); 647 648 RenderStyle* rootStyle = renderer->document()->documentElement()->renderStyle(); 649 650 FloatPoint firstPoint; 651 FloatPoint secondPoint; 652 if (m_angle) { 653 float angle = m_angle->getFloatValue(CSSPrimitiveValue::CSS_DEG); 654 endPointsFromAngle(angle, size, firstPoint, secondPoint, m_gradientType); 655 } else { 656 switch (m_gradientType) { 657 case CSSDeprecatedLinearGradient: 658 firstPoint = computeEndPoint(m_firstX.get(), m_firstY.get(), renderer->style(), rootStyle, size); 659 if (m_secondX || m_secondY) 660 secondPoint = computeEndPoint(m_secondX.get(), m_secondY.get(), renderer->style(), rootStyle, size); 661 else { 662 if (m_firstX) 663 secondPoint.setX(size.width() - firstPoint.x()); 664 if (m_firstY) 665 secondPoint.setY(size.height() - firstPoint.y()); 666 } 667 break; 668 case CSSPrefixedLinearGradient: 669 firstPoint = computeEndPoint(m_firstX.get(), m_firstY.get(), renderer->style(), rootStyle, size); 670 if (m_firstX) 671 secondPoint.setX(size.width() - firstPoint.x()); 672 if (m_firstY) 673 secondPoint.setY(size.height() - firstPoint.y()); 674 break; 675 case CSSLinearGradient: 676 if (m_firstX && m_firstY) { 677 // "Magic" corners, so the 50% line touches two corners. 678 float rise = size.width(); 679 float run = size.height(); 680 if (m_firstX && m_firstX->getIdent() == CSSValueLeft) 681 run *= -1; 682 if (m_firstY && m_firstY->getIdent() == CSSValueBottom) 683 rise *= -1; 684 // Compute angle, and flip it back to "bearing angle" degrees. 685 float angle = 90 - rad2deg(atan2(rise, run)); 686 endPointsFromAngle(angle, size, firstPoint, secondPoint, m_gradientType); 687 } else if (m_firstX || m_firstY) { 688 secondPoint = computeEndPoint(m_firstX.get(), m_firstY.get(), renderer->style(), rootStyle, size); 689 if (m_firstX) 690 firstPoint.setX(size.width() - secondPoint.x()); 691 if (m_firstY) 692 firstPoint.setY(size.height() - secondPoint.y()); 693 } else 694 secondPoint.setY(size.height()); 695 break; 696 default: 697 ASSERT_NOT_REACHED(); 698 } 699 700 } 701 702 RefPtr<Gradient> gradient = Gradient::create(firstPoint, secondPoint); 703 704 // Now add the stops. 705 addStops(gradient.get(), renderer, rootStyle, 1); 706 707 return gradient.release(); 708} 709 710bool CSSLinearGradientValue::equals(const CSSLinearGradientValue& other) const 711{ 712 if (m_gradientType == CSSDeprecatedLinearGradient) 713 return other.m_gradientType == m_gradientType 714 && compareCSSValuePtr(m_firstX, other.m_firstX) 715 && compareCSSValuePtr(m_firstY, other.m_firstY) 716 && compareCSSValuePtr(m_secondX, other.m_secondX) 717 && compareCSSValuePtr(m_secondY, other.m_secondY) 718 && m_stops == other.m_stops; 719 720 if (m_repeating != other.m_repeating) 721 return false; 722 723 if (m_angle) 724 return compareCSSValuePtr(m_angle, other.m_angle) && m_stops == other.m_stops; 725 726 if (other.m_angle) 727 return false; 728 729 bool equalXorY = false; 730 if (m_firstX && m_firstY) 731 equalXorY = compareCSSValuePtr(m_firstX, other.m_firstX) && compareCSSValuePtr(m_firstY, other.m_firstY); 732 else if (m_firstX) 733 equalXorY =compareCSSValuePtr(m_firstX, other.m_firstX) && !other.m_firstY; 734 else if (m_firstY) 735 equalXorY = compareCSSValuePtr(m_firstY, other.m_firstY) && !other.m_firstX; 736 else 737 equalXorY = !other.m_firstX || !other.m_firstY; 738 739 return equalXorY && m_stops == other.m_stops; 740} 741 742String CSSRadialGradientValue::customCssText() const 743{ 744 StringBuilder result; 745 746 if (m_gradientType == CSSDeprecatedRadialGradient) { 747 result.appendLiteral("-webkit-gradient(radial, "); 748 result.append(m_firstX->cssText()); 749 result.append(' '); 750 result.append(m_firstY->cssText()); 751 result.appendLiteral(", "); 752 result.append(m_firstRadius->cssText()); 753 result.appendLiteral(", "); 754 result.append(m_secondX->cssText()); 755 result.append(' '); 756 result.append(m_secondY->cssText()); 757 result.appendLiteral(", "); 758 result.append(m_secondRadius->cssText()); 759 760 // FIXME: share? 761 for (unsigned i = 0; i < m_stops.size(); i++) { 762 const CSSGradientColorStop& stop = m_stops[i]; 763 result.appendLiteral(", "); 764 if (stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER) == 0) { 765 result.appendLiteral("from("); 766 result.append(stop.m_color->cssText()); 767 result.append(')'); 768 } else if (stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER) == 1) { 769 result.appendLiteral("to("); 770 result.append(stop.m_color->cssText()); 771 result.append(')'); 772 } else { 773 result.appendLiteral("color-stop("); 774 result.append(String::number(stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER))); 775 result.appendLiteral(", "); 776 result.append(stop.m_color->cssText()); 777 result.append(')'); 778 } 779 } 780 } else if (m_gradientType == CSSPrefixedRadialGradient) { 781 if (m_repeating) 782 result.appendLiteral("-webkit-repeating-radial-gradient("); 783 else 784 result.appendLiteral("-webkit-radial-gradient("); 785 786 if (m_firstX && m_firstY) { 787 result.append(m_firstX->cssText()); 788 result.append(' '); 789 result.append(m_firstY->cssText()); 790 } else if (m_firstX) 791 result.append(m_firstX->cssText()); 792 else if (m_firstY) 793 result.append(m_firstY->cssText()); 794 else 795 result.appendLiteral("center"); 796 797 if (m_shape || m_sizingBehavior) { 798 result.appendLiteral(", "); 799 if (m_shape) { 800 result.append(m_shape->cssText()); 801 result.append(' '); 802 } else 803 result.appendLiteral("ellipse "); 804 805 if (m_sizingBehavior) 806 result.append(m_sizingBehavior->cssText()); 807 else 808 result.appendLiteral("cover"); 809 810 } else if (m_endHorizontalSize && m_endVerticalSize) { 811 result.appendLiteral(", "); 812 result.append(m_endHorizontalSize->cssText()); 813 result.append(' '); 814 result.append(m_endVerticalSize->cssText()); 815 } 816 817 for (unsigned i = 0; i < m_stops.size(); i++) { 818 const CSSGradientColorStop& stop = m_stops[i]; 819 result.appendLiteral(", "); 820 result.append(stop.m_color->cssText()); 821 if (stop.m_position) { 822 result.append(' '); 823 result.append(stop.m_position->cssText()); 824 } 825 } 826 } else { 827 if (m_repeating) 828 result.appendLiteral("repeating-radial-gradient("); 829 else 830 result.appendLiteral("radial-gradient("); 831 832 bool wroteSomething = false; 833 834 // The only ambiguous case that needs an explicit shape to be provided 835 // is when a sizing keyword is used (or all sizing is omitted). 836 if (m_shape && m_shape->getIdent() != CSSValueEllipse && (m_sizingBehavior || (!m_sizingBehavior && !m_endHorizontalSize))) { 837 result.appendLiteral("circle"); 838 wroteSomething = true; 839 } 840 841 if (m_sizingBehavior && m_sizingBehavior->getIdent() != CSSValueFarthestCorner) { 842 if (wroteSomething) 843 result.append(' '); 844 result.append(m_sizingBehavior->cssText()); 845 wroteSomething = true; 846 } else if (m_endHorizontalSize) { 847 if (wroteSomething) 848 result.append(' '); 849 result.append(m_endHorizontalSize->cssText()); 850 if (m_endVerticalSize) { 851 result.append(' '); 852 result.append(m_endVerticalSize->cssText()); 853 } 854 wroteSomething = true; 855 } 856 857 if (m_firstX || m_firstY) { 858 if (wroteSomething) 859 result.append(' '); 860 result.appendLiteral("at "); 861 if (m_firstX && m_firstY) { 862 result.append(m_firstX->cssText()); 863 result.append(' '); 864 result.append(m_firstY->cssText()); 865 } else if (m_firstX) 866 result.append(m_firstX->cssText()); 867 else 868 result.append(m_firstY->cssText()); 869 wroteSomething = true; 870 } 871 872 if (wroteSomething) 873 result.appendLiteral(", "); 874 875 for (unsigned i = 0; i < m_stops.size(); i++) { 876 const CSSGradientColorStop& stop = m_stops[i]; 877 if (i) 878 result.appendLiteral(", "); 879 result.append(stop.m_color->cssText()); 880 if (stop.m_position) { 881 result.append(' '); 882 result.append(stop.m_position->cssText()); 883 } 884 } 885 886 } 887 888 result.append(')'); 889 return result.toString(); 890} 891 892float CSSRadialGradientValue::resolveRadius(CSSPrimitiveValue* radius, RenderStyle* style, RenderStyle* rootStyle, float* widthOrHeight) 893{ 894 float zoomFactor = style->effectiveZoom(); 895 896 float result = 0; 897 if (radius->isNumber()) // Can the radius be a percentage? 898 result = radius->getFloatValue() * zoomFactor; 899 else if (widthOrHeight && radius->isPercentage()) 900 result = *widthOrHeight * radius->getFloatValue() / 100; 901 else 902 result = radius->computeLength<float>(style, rootStyle, zoomFactor); 903 904 return result; 905} 906 907static float distanceToClosestCorner(const FloatPoint& p, const FloatSize& size, FloatPoint& corner) 908{ 909 FloatPoint topLeft; 910 float topLeftDistance = FloatSize(p - topLeft).diagonalLength(); 911 912 FloatPoint topRight(size.width(), 0); 913 float topRightDistance = FloatSize(p - topRight).diagonalLength(); 914 915 FloatPoint bottomLeft(0, size.height()); 916 float bottomLeftDistance = FloatSize(p - bottomLeft).diagonalLength(); 917 918 FloatPoint bottomRight(size.width(), size.height()); 919 float bottomRightDistance = FloatSize(p - bottomRight).diagonalLength(); 920 921 corner = topLeft; 922 float minDistance = topLeftDistance; 923 if (topRightDistance < minDistance) { 924 minDistance = topRightDistance; 925 corner = topRight; 926 } 927 928 if (bottomLeftDistance < minDistance) { 929 minDistance = bottomLeftDistance; 930 corner = bottomLeft; 931 } 932 933 if (bottomRightDistance < minDistance) { 934 minDistance = bottomRightDistance; 935 corner = bottomRight; 936 } 937 return minDistance; 938} 939 940static float distanceToFarthestCorner(const FloatPoint& p, const FloatSize& size, FloatPoint& corner) 941{ 942 FloatPoint topLeft; 943 float topLeftDistance = FloatSize(p - topLeft).diagonalLength(); 944 945 FloatPoint topRight(size.width(), 0); 946 float topRightDistance = FloatSize(p - topRight).diagonalLength(); 947 948 FloatPoint bottomLeft(0, size.height()); 949 float bottomLeftDistance = FloatSize(p - bottomLeft).diagonalLength(); 950 951 FloatPoint bottomRight(size.width(), size.height()); 952 float bottomRightDistance = FloatSize(p - bottomRight).diagonalLength(); 953 954 corner = topLeft; 955 float maxDistance = topLeftDistance; 956 if (topRightDistance > maxDistance) { 957 maxDistance = topRightDistance; 958 corner = topRight; 959 } 960 961 if (bottomLeftDistance > maxDistance) { 962 maxDistance = bottomLeftDistance; 963 corner = bottomLeft; 964 } 965 966 if (bottomRightDistance > maxDistance) { 967 maxDistance = bottomRightDistance; 968 corner = bottomRight; 969 } 970 return maxDistance; 971} 972 973// Compute horizontal radius of ellipse with center at 0,0 which passes through p, and has 974// width/height given by aspectRatio. 975static inline float horizontalEllipseRadius(const FloatSize& p, float aspectRatio) 976{ 977 // x^2/a^2 + y^2/b^2 = 1 978 // a/b = aspectRatio, b = a/aspectRatio 979 // a = sqrt(x^2 + y^2/(1/r^2)) 980 return sqrtf(p.width() * p.width() + (p.height() * p.height()) / (1 / (aspectRatio * aspectRatio))); 981} 982 983// FIXME: share code with the linear version 984PassRefPtr<Gradient> CSSRadialGradientValue::createGradient(RenderObject* renderer, const IntSize& size) 985{ 986 ASSERT(!size.isEmpty()); 987 988 RenderStyle* rootStyle = renderer->document()->documentElement()->renderStyle(); 989 990 FloatPoint firstPoint = computeEndPoint(m_firstX.get(), m_firstY.get(), renderer->style(), rootStyle, size); 991 if (!m_firstX) 992 firstPoint.setX(size.width() / 2); 993 if (!m_firstY) 994 firstPoint.setY(size.height() / 2); 995 996 FloatPoint secondPoint = computeEndPoint(m_secondX.get(), m_secondY.get(), renderer->style(), rootStyle, size); 997 if (!m_secondX) 998 secondPoint.setX(size.width() / 2); 999 if (!m_secondY) 1000 secondPoint.setY(size.height() / 2); 1001 1002 float firstRadius = 0; 1003 if (m_firstRadius) 1004 firstRadius = resolveRadius(m_firstRadius.get(), renderer->style(), rootStyle); 1005 1006 float secondRadius = 0; 1007 float aspectRatio = 1; // width / height. 1008 if (m_secondRadius) 1009 secondRadius = resolveRadius(m_secondRadius.get(), renderer->style(), rootStyle); 1010 else if (m_endHorizontalSize) { 1011 float width = size.width(); 1012 float height = size.height(); 1013 secondRadius = resolveRadius(m_endHorizontalSize.get(), renderer->style(), rootStyle, &width); 1014 if (m_endVerticalSize) 1015 aspectRatio = secondRadius / resolveRadius(m_endVerticalSize.get(), renderer->style(), rootStyle, &height); 1016 else 1017 aspectRatio = 1; 1018 } else { 1019 enum GradientShape { Circle, Ellipse }; 1020 GradientShape shape = Ellipse; 1021 if ((m_shape && m_shape->getIdent() == CSSValueCircle) 1022 || (!m_shape && !m_sizingBehavior && m_endHorizontalSize && !m_endVerticalSize)) 1023 shape = Circle; 1024 1025 enum GradientFill { ClosestSide, ClosestCorner, FarthestSide, FarthestCorner }; 1026 GradientFill fill = FarthestCorner; 1027 1028 switch (m_sizingBehavior ? m_sizingBehavior->getIdent() : 0) { 1029 case CSSValueContain: 1030 case CSSValueClosestSide: 1031 fill = ClosestSide; 1032 break; 1033 case CSSValueClosestCorner: 1034 fill = ClosestCorner; 1035 break; 1036 case CSSValueFarthestSide: 1037 fill = FarthestSide; 1038 break; 1039 case CSSValueCover: 1040 case CSSValueFarthestCorner: 1041 fill = FarthestCorner; 1042 break; 1043 } 1044 1045 // Now compute the end radii based on the second point, shape and fill. 1046 1047 // Horizontal 1048 switch (fill) { 1049 case ClosestSide: { 1050 float xDist = min(secondPoint.x(), size.width() - secondPoint.x()); 1051 float yDist = min(secondPoint.y(), size.height() - secondPoint.y()); 1052 if (shape == Circle) { 1053 float smaller = min(xDist, yDist); 1054 xDist = smaller; 1055 yDist = smaller; 1056 } 1057 secondRadius = xDist; 1058 aspectRatio = xDist / yDist; 1059 break; 1060 } 1061 case FarthestSide: { 1062 float xDist = max(secondPoint.x(), size.width() - secondPoint.x()); 1063 float yDist = max(secondPoint.y(), size.height() - secondPoint.y()); 1064 if (shape == Circle) { 1065 float larger = max(xDist, yDist); 1066 xDist = larger; 1067 yDist = larger; 1068 } 1069 secondRadius = xDist; 1070 aspectRatio = xDist / yDist; 1071 break; 1072 } 1073 case ClosestCorner: { 1074 FloatPoint corner; 1075 float distance = distanceToClosestCorner(secondPoint, size, corner); 1076 if (shape == Circle) 1077 secondRadius = distance; 1078 else { 1079 // If <shape> is ellipse, the gradient-shape has the same ratio of width to height 1080 // that it would if closest-side or farthest-side were specified, as appropriate. 1081 float xDist = min(secondPoint.x(), size.width() - secondPoint.x()); 1082 float yDist = min(secondPoint.y(), size.height() - secondPoint.y()); 1083 1084 secondRadius = horizontalEllipseRadius(corner - secondPoint, xDist / yDist); 1085 aspectRatio = xDist / yDist; 1086 } 1087 break; 1088 } 1089 1090 case FarthestCorner: { 1091 FloatPoint corner; 1092 float distance = distanceToFarthestCorner(secondPoint, size, corner); 1093 if (shape == Circle) 1094 secondRadius = distance; 1095 else { 1096 // If <shape> is ellipse, the gradient-shape has the same ratio of width to height 1097 // that it would if closest-side or farthest-side were specified, as appropriate. 1098 float xDist = max(secondPoint.x(), size.width() - secondPoint.x()); 1099 float yDist = max(secondPoint.y(), size.height() - secondPoint.y()); 1100 1101 secondRadius = horizontalEllipseRadius(corner - secondPoint, xDist / yDist); 1102 aspectRatio = xDist / yDist; 1103 } 1104 break; 1105 } 1106 } 1107 } 1108 1109 RefPtr<Gradient> gradient = Gradient::create(firstPoint, firstRadius, secondPoint, secondRadius, aspectRatio); 1110 1111 // addStops() only uses maxExtent for repeating gradients. 1112 float maxExtent = 0; 1113 if (m_repeating) { 1114 FloatPoint corner; 1115 maxExtent = distanceToFarthestCorner(secondPoint, size, corner); 1116 } 1117 1118 // Now add the stops. 1119 addStops(gradient.get(), renderer, rootStyle, maxExtent); 1120 1121 return gradient.release(); 1122} 1123 1124bool CSSRadialGradientValue::equals(const CSSRadialGradientValue& other) const 1125{ 1126 if (m_gradientType == CSSDeprecatedRadialGradient) 1127 return other.m_gradientType == m_gradientType 1128 && compareCSSValuePtr(m_firstX, other.m_firstX) 1129 && compareCSSValuePtr(m_firstY, other.m_firstY) 1130 && compareCSSValuePtr(m_secondX, other.m_secondX) 1131 && compareCSSValuePtr(m_secondY, other.m_secondY) 1132 && compareCSSValuePtr(m_firstRadius, other.m_firstRadius) 1133 && compareCSSValuePtr(m_secondRadius, other.m_secondRadius) 1134 && m_stops == other.m_stops; 1135 1136 if (m_repeating != other.m_repeating) 1137 return false; 1138 1139 bool equalXorY = false; 1140 if (m_firstX && m_firstY) 1141 equalXorY = compareCSSValuePtr(m_firstX, other.m_firstX) && compareCSSValuePtr(m_firstY, other.m_firstY); 1142 else if (m_firstX) 1143 equalXorY = compareCSSValuePtr(m_firstX, other.m_firstX) && !other.m_firstY; 1144 else if (m_firstY) 1145 equalXorY = compareCSSValuePtr(m_firstY, other.m_firstY) && !other.m_firstX; 1146 else 1147 equalXorY == !other.m_firstX || !other.m_firstY; 1148 1149 if (!equalXorY) 1150 return false; 1151 1152 bool equalShape = true; 1153 bool equalSizingBehavior = true; 1154 bool equalHorizontalAndVerticalSize = true; 1155 1156 if (m_shape) 1157 equalShape = compareCSSValuePtr(m_shape, other.m_shape); 1158 else if (m_sizingBehavior) 1159 equalSizingBehavior = compareCSSValuePtr(m_sizingBehavior, other.m_sizingBehavior); 1160 else if (m_endHorizontalSize && m_endVerticalSize) 1161 equalHorizontalAndVerticalSize = compareCSSValuePtr(m_endHorizontalSize, other.m_endHorizontalSize) && compareCSSValuePtr(m_endVerticalSize, other.m_endVerticalSize); 1162 else { 1163 equalShape = !other.m_shape; 1164 equalSizingBehavior = !other.m_sizingBehavior; 1165 equalHorizontalAndVerticalSize = !other.m_endHorizontalSize && !other.m_endVerticalSize; 1166 } 1167 return equalShape && equalSizingBehavior && equalHorizontalAndVerticalSize && m_stops == other.m_stops; 1168} 1169 1170} // namespace WebCore 1171