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