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