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