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