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