Home | History | Annotate | Download | only in css
      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 "CSSGradientValue.h"
     28 
     29 #include "CSSValueKeywords.h"
     30 #include "CSSStyleSelector.h"
     31 #include "GeneratedImage.h"
     32 #include "Gradient.h"
     33 #include "Image.h"
     34 #include "IntSize.h"
     35 #include "IntSizeHash.h"
     36 #include "NodeRenderStyle.h"
     37 #include "PlatformString.h"
     38 #include "RenderObject.h"
     39 
     40 using namespace std;
     41 
     42 namespace WebCore {
     43 
     44 PassRefPtr<Image> CSSGradientValue::image(RenderObject* renderer, const IntSize& size)
     45 {
     46     if (size.isEmpty())
     47         return 0;
     48 
     49     bool cacheable = isCacheable();
     50     if (cacheable) {
     51         if (!m_clients.contains(renderer))
     52             return 0;
     53 
     54         // Need to look up our size.  Create a string of width*height to use as a hash key.
     55         Image* result = getImage(renderer, size);
     56         if (result)
     57             return result;
     58     }
     59 
     60     // We need to create an image.
     61     RefPtr<Image> newImage = GeneratedImage::create(createGradient(renderer, size), size);
     62     if (cacheable)
     63         putImage(size, newImage);
     64 
     65     return newImage.release();
     66 }
     67 
     68 // Should only ever be called for deprecated gradients.
     69 static inline bool compareStops(const CSSGradientColorStop& a, const CSSGradientColorStop& b)
     70 {
     71     double aVal = a.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER);
     72     double bVal = b.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER);
     73 
     74     return aVal < bVal;
     75 }
     76 
     77 void CSSGradientValue::sortStopsIfNeeded()
     78 {
     79     ASSERT(m_deprecatedType);
     80     if (!m_stopsSorted) {
     81         if (m_stops.size())
     82             std::stable_sort(m_stops.begin(), m_stops.end(), compareStops);
     83         m_stopsSorted = true;
     84     }
     85 }
     86 
     87 static inline int blend(int from, int to, float progress)
     88 {
     89     return int(from + (to - from) * progress);
     90 }
     91 
     92 static inline Color blend(const Color& from, const Color& to, float progress)
     93 {
     94     // FIXME: when we interpolate gradients using premultiplied colors, this should also do premultiplication.
     95     return Color(blend(from.red(), to.red(), progress),
     96         blend(from.green(), to.green(), progress),
     97         blend(from.blue(), to.blue(), progress),
     98         blend(from.alpha(), to.alpha(), progress));
     99 }
    100 
    101 struct GradientStop {
    102     Color color;
    103     float offset;
    104     bool specified;
    105 
    106     GradientStop()
    107         : offset(0)
    108         , specified(false)
    109     { }
    110 };
    111 
    112 void CSSGradientValue::addStops(Gradient* gradient, RenderObject* renderer, RenderStyle* rootStyle, float maxLengthForRepeat)
    113 {
    114     RenderStyle* style = renderer->style();
    115 
    116     if (m_deprecatedType) {
    117         sortStopsIfNeeded();
    118 
    119         // We have to resolve colors.
    120         for (unsigned i = 0; i < m_stops.size(); i++) {
    121             const CSSGradientColorStop& stop = m_stops[i];
    122             Color color = renderer->document()->styleSelector()->getColorFromPrimitiveValue(stop.m_color.get());
    123 
    124             float offset;
    125             if (stop.m_position->primitiveType() == CSSPrimitiveValue::CSS_PERCENTAGE)
    126                 offset = stop.m_position->getFloatValue(CSSPrimitiveValue::CSS_PERCENTAGE) / 100;
    127             else
    128                 offset = stop.m_position->getFloatValue(CSSPrimitiveValue::CSS_NUMBER);
    129 
    130             gradient->addColorStop(offset, color);
    131         }
    132 
    133         // The back end already sorted the stops.
    134         gradient->setStopsSorted(true);
    135         return;
    136     }
    137 
    138     size_t numStops = m_stops.size();
    139 
    140     Vector<GradientStop> stops(numStops);
    141 
    142     float gradientLength = 0;
    143     bool computedGradientLength = false;
    144 
    145     FloatPoint gradientStart = gradient->p0();
    146     FloatPoint gradientEnd;
    147     if (isLinearGradient())
    148         gradientEnd = gradient->p1();
    149     else if (isRadialGradient())
    150         gradientEnd = gradientStart + FloatSize(gradient->endRadius(), 0);
    151 
    152     for (size_t i = 0; i < numStops; ++i) {
    153         const CSSGradientColorStop& stop = m_stops[i];
    154 
    155         stops[i].color = renderer->document()->styleSelector()->getColorFromPrimitiveValue(stop.m_color.get());
    156 
    157         if (stop.m_position) {
    158             int type = stop.m_position->primitiveType();
    159             if (type == CSSPrimitiveValue::CSS_PERCENTAGE)
    160                 stops[i].offset = stop.m_position->getFloatValue(CSSPrimitiveValue::CSS_PERCENTAGE) / 100;
    161             else if (CSSPrimitiveValue::isUnitTypeLength(type)) {
    162                 float length = stop.m_position->computeLengthFloat(style, rootStyle, style->effectiveZoom());
    163                 if (!computedGradientLength) {
    164                     FloatSize gradientSize(gradientStart - gradientEnd);
    165                     gradientLength = gradientSize.diagonalLength();
    166                 }
    167                 stops[i].offset = (gradientLength > 0) ? length / gradientLength : 0;
    168             } else {
    169                 ASSERT_NOT_REACHED();
    170                 stops[i].offset = 0;
    171             }
    172             stops[i].specified = true;
    173         } else {
    174             // If the first color-stop does not have a position, its position defaults to 0%.
    175             // If the last color-stop does not have a position, its position defaults to 100%.
    176             if (!i) {
    177                 stops[i].offset = 0;
    178                 stops[i].specified = true;
    179             } else if (numStops > 1 && i == numStops - 1) {
    180                 stops[i].offset = 1;
    181                 stops[i].specified = true;
    182             }
    183         }
    184 
    185         // If a color-stop has a position that is less than the specified position of any
    186         // color-stop before it in the list, its position is changed to be equal to the
    187         // largest specified position of any color-stop before it.
    188         if (stops[i].specified && i > 0) {
    189             size_t prevSpecifiedIndex;
    190             for (prevSpecifiedIndex = i - 1; prevSpecifiedIndex; --prevSpecifiedIndex) {
    191                 if (stops[prevSpecifiedIndex].specified)
    192                     break;
    193             }
    194 
    195             if (stops[i].offset < stops[prevSpecifiedIndex].offset)
    196                 stops[i].offset = stops[prevSpecifiedIndex].offset;
    197         }
    198     }
    199 
    200     ASSERT(stops[0].specified && stops[numStops - 1].specified);
    201 
    202     // If any color-stop still does not have a position, then, for each run of adjacent
    203     // color-stops without positions, set their positions so that they are evenly spaced
    204     // between the preceding and following color-stops with positions.
    205     if (numStops > 2) {
    206         size_t unspecifiedRunStart = 0;
    207         bool inUnspecifiedRun = false;
    208 
    209         for (size_t i = 0; i < numStops; ++i) {
    210             if (!stops[i].specified && !inUnspecifiedRun) {
    211                 unspecifiedRunStart = i;
    212                 inUnspecifiedRun = true;
    213             } else if (stops[i].specified && inUnspecifiedRun) {
    214                 size_t unspecifiedRunEnd = i;
    215 
    216                 if (unspecifiedRunStart < unspecifiedRunEnd) {
    217                     float lastSpecifiedOffset = stops[unspecifiedRunStart - 1].offset;
    218                     float nextSpecifiedOffset = stops[unspecifiedRunEnd].offset;
    219                     float delta = (nextSpecifiedOffset - lastSpecifiedOffset) / (unspecifiedRunEnd - unspecifiedRunStart + 1);
    220 
    221                     for (size_t j = unspecifiedRunStart; j < unspecifiedRunEnd; ++j)
    222                         stops[j].offset = lastSpecifiedOffset + (j - unspecifiedRunStart + 1) * delta;
    223                 }
    224 
    225                 inUnspecifiedRun = false;
    226             }
    227         }
    228     }
    229 
    230     // If the gradient is repeating, repeat the color stops.
    231     // We can't just push this logic down into the platform-specific Gradient code,
    232     // because we have to know the extent of the gradient, and possible move the end points.
    233     if (m_repeating && numStops > 1) {
    234         // If the difference in the positions of the first and last color-stops is 0,
    235         // the gradient defines a solid-color image with the color of the last color-stop in the rule.
    236         float gradientRange = stops[numStops - 1].offset - stops[0].offset;
    237         if (!gradientRange) {
    238             stops.first().offset = 0;
    239             stops.first().color = stops.last().color;
    240             stops.shrink(1);
    241             numStops = 1;
    242         } else {
    243             float maxExtent = 1;
    244 
    245             // Radial gradients may need to extend further than the endpoints, because they have
    246             // to repeat out to the corners of the box.
    247             if (isRadialGradient()) {
    248                 if (!computedGradientLength) {
    249                     FloatSize gradientSize(gradientStart - gradientEnd);
    250                     gradientLength = gradientSize.diagonalLength();
    251                 }
    252 
    253                 if (maxLengthForRepeat > gradientLength)
    254                     maxExtent = maxLengthForRepeat / gradientLength;
    255             }
    256 
    257             size_t originalNumStops = numStops;
    258             size_t originalFirstStopIndex = 0;
    259 
    260             // Work backwards from the first, adding stops until we get one before 0.
    261             float firstOffset = stops[0].offset;
    262             if (firstOffset > 0) {
    263                 float currOffset = firstOffset;
    264                 size_t srcStopOrdinal = originalNumStops - 1;
    265 
    266                 while (true) {
    267                     GradientStop newStop = stops[originalFirstStopIndex + srcStopOrdinal];
    268                     newStop.offset = currOffset;
    269                     stops.prepend(newStop);
    270                     ++originalFirstStopIndex;
    271                     if (currOffset < 0)
    272                         break;
    273 
    274                     if (srcStopOrdinal)
    275                         currOffset -= stops[originalFirstStopIndex + srcStopOrdinal].offset - stops[originalFirstStopIndex + srcStopOrdinal - 1].offset;
    276                     srcStopOrdinal = (srcStopOrdinal + originalNumStops - 1) % originalNumStops;
    277                 }
    278             }
    279 
    280             // Work forwards from the end, adding stops until we get one after 1.
    281             float lastOffset = stops[stops.size() - 1].offset;
    282             if (lastOffset < maxExtent) {
    283                 float currOffset = lastOffset;
    284                 size_t srcStopOrdinal = originalFirstStopIndex;
    285 
    286                 while (true) {
    287                     GradientStop newStop = stops[srcStopOrdinal];
    288                     newStop.offset = currOffset;
    289                     stops.append(newStop);
    290                     if (currOffset > maxExtent)
    291                         break;
    292                     if (srcStopOrdinal < originalNumStops - 1)
    293                         currOffset += stops[srcStopOrdinal + 1].offset - stops[srcStopOrdinal].offset;
    294                     srcStopOrdinal = (srcStopOrdinal + 1) % originalNumStops;
    295                 }
    296             }
    297         }
    298     }
    299 
    300     numStops = stops.size();
    301 
    302     // If the gradient goes outside the 0-1 range, normalize it by moving the endpoints, and adjusting the stops.
    303     if (numStops > 1 && (stops[0].offset < 0 || stops[numStops - 1].offset > 1)) {
    304         if (isLinearGradient()) {
    305             float firstOffset = stops[0].offset;
    306             float lastOffset = stops[numStops - 1].offset;
    307             float scale = lastOffset - firstOffset;
    308 
    309             for (size_t i = 0; i < numStops; ++i)
    310                 stops[i].offset = (stops[i].offset - firstOffset) / scale;
    311 
    312             FloatPoint p0 = gradient->p0();
    313             FloatPoint p1 = gradient->p1();
    314             gradient->setP0(FloatPoint(p0.x() + firstOffset * (p1.x() - p0.x()), p0.y() + firstOffset * (p1.y() - p0.y())));
    315             gradient->setP1(FloatPoint(p1.x() + (lastOffset - 1) * (p1.x() - p0.x()), p1.y() + (lastOffset - 1) * (p1.y() - p0.y())));
    316         } else if (isRadialGradient()) {
    317             // Rather than scaling the points < 0, we truncate them, so only scale according to the largest point.
    318             float firstOffset = 0;
    319             float lastOffset = stops[numStops - 1].offset;
    320             float scale = lastOffset - firstOffset;
    321 
    322             // Reset points below 0 to the first visible color.
    323             size_t firstZeroOrGreaterIndex = numStops;
    324             for (size_t i = 0; i < numStops; ++i) {
    325                 if (stops[i].offset >= 0) {
    326                     firstZeroOrGreaterIndex = i;
    327                     break;
    328                 }
    329             }
    330 
    331             if (firstZeroOrGreaterIndex > 0) {
    332                 if (firstZeroOrGreaterIndex < numStops && stops[firstZeroOrGreaterIndex].offset > 0) {
    333                     float prevOffset = stops[firstZeroOrGreaterIndex - 1].offset;
    334                     float nextOffset = stops[firstZeroOrGreaterIndex].offset;
    335 
    336                     float interStopProportion = -prevOffset / (nextOffset - prevOffset);
    337                     Color blendedColor = blend(stops[firstZeroOrGreaterIndex - 1].color, stops[firstZeroOrGreaterIndex].color, interStopProportion);
    338 
    339                     // Clamp the positions to 0 and set the color.
    340                     for (size_t i = 0; i < firstZeroOrGreaterIndex; ++i) {
    341                         stops[i].offset = 0;
    342                         stops[i].color = blendedColor;
    343                     }
    344                 } else {
    345                     // All stops are below 0; just clamp them.
    346                     for (size_t i = 0; i < firstZeroOrGreaterIndex; ++i)
    347                         stops[i].offset = 0;
    348                 }
    349             }
    350 
    351             for (size_t i = 0; i < numStops; ++i)
    352                 stops[i].offset /= scale;
    353 
    354             gradient->setStartRadius(gradient->startRadius() * scale);
    355             gradient->setEndRadius(gradient->endRadius() * scale);
    356         }
    357     }
    358 
    359     for (unsigned i = 0; i < numStops; i++)
    360         gradient->addColorStop(stops[i].offset, stops[i].color);
    361 
    362     gradient->setStopsSorted(true);
    363 }
    364 
    365 static float positionFromValue(CSSPrimitiveValue* value, RenderStyle* style, RenderStyle* rootStyle, const IntSize& size, bool isHorizontal)
    366 {
    367     float zoomFactor = style->effectiveZoom();
    368 
    369     switch (value->primitiveType()) {
    370     case CSSPrimitiveValue::CSS_NUMBER:
    371         return value->getFloatValue() * zoomFactor;
    372 
    373     case CSSPrimitiveValue::CSS_PERCENTAGE:
    374         return value->getFloatValue() / 100.f * (isHorizontal ? size.width() : size.height());
    375 
    376     case CSSPrimitiveValue::CSS_IDENT:
    377         switch (value->getIdent()) {
    378             case CSSValueTop:
    379                 ASSERT(!isHorizontal);
    380                 return 0;
    381             case CSSValueLeft:
    382                 ASSERT(isHorizontal);
    383                 return 0;
    384             case CSSValueBottom:
    385                 ASSERT(!isHorizontal);
    386                 return size.height();
    387             case CSSValueRight:
    388                 ASSERT(isHorizontal);
    389                 return size.width();
    390         }
    391 
    392     default:
    393         return value->computeLengthFloat(style, rootStyle, zoomFactor);
    394     }
    395 }
    396 
    397 FloatPoint CSSGradientValue::computeEndPoint(CSSPrimitiveValue* first, CSSPrimitiveValue* second, RenderStyle* style, RenderStyle* rootStyle, const IntSize& size)
    398 {
    399     FloatPoint result;
    400 
    401     if (first)
    402         result.setX(positionFromValue(first, style, rootStyle, size, true));
    403 
    404     if (second)
    405         result.setY(positionFromValue(second, style, rootStyle, size, false));
    406 
    407     return result;
    408 }
    409 
    410 bool CSSGradientValue::isCacheable() const
    411 {
    412     for (size_t i = 0; i < m_stops.size(); ++i) {
    413         const CSSGradientColorStop& stop = m_stops[i];
    414         if (!stop.m_position)
    415             continue;
    416 
    417         unsigned short unitType = stop.m_position->primitiveType();
    418         if (unitType == CSSPrimitiveValue::CSS_EMS || unitType == CSSPrimitiveValue::CSS_EXS || unitType == CSSPrimitiveValue::CSS_REMS)
    419             return false;
    420     }
    421 
    422     return true;
    423 }
    424 
    425 String CSSLinearGradientValue::cssText() const
    426 {
    427     String result;
    428     if (m_deprecatedType) {
    429         result = "-webkit-gradient(linear, ";
    430         result += m_firstX->cssText() + " ";
    431         result += m_firstY->cssText() + ", ";
    432         result += m_secondX->cssText() + " ";
    433         result += m_secondY->cssText();
    434 
    435         for (unsigned i = 0; i < m_stops.size(); i++) {
    436             const CSSGradientColorStop& stop = m_stops[i];
    437             result += ", ";
    438             if (stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER) == 0)
    439                 result += "from(" + stop.m_color->cssText() + ")";
    440             else if (stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER) == 1)
    441                 result += "to(" + stop.m_color->cssText() + ")";
    442             else
    443                 result += "color-stop(" + String::number(stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER)) + ", " + stop.m_color->cssText() + ")";
    444         }
    445     } else {
    446         result = m_repeating ? "-webkit-repeating-linear-gradient(" : "-webkit-linear-gradient(";
    447         if (m_angle)
    448             result += m_angle->cssText();
    449         else {
    450             if (m_firstX && m_firstY)
    451                 result += m_firstX->cssText() + " " + m_firstY->cssText();
    452             else if (m_firstX || m_firstY) {
    453                 if (m_firstX)
    454                     result += m_firstX->cssText();
    455 
    456                 if (m_firstY)
    457                     result += m_firstY->cssText();
    458             }
    459         }
    460 
    461         for (unsigned i = 0; i < m_stops.size(); i++) {
    462             const CSSGradientColorStop& stop = m_stops[i];
    463             result += ", ";
    464             result += stop.m_color->cssText();
    465             if (stop.m_position)
    466                 result += " " + stop.m_position->cssText();
    467         }
    468     }
    469 
    470     result += ")";
    471     return result;
    472 }
    473 
    474 // Compute the endpoints so that a gradient of the given angle covers a box of the given size.
    475 static void endPointsFromAngle(float angleDeg, const IntSize& size, FloatPoint& firstPoint, FloatPoint& secondPoint)
    476 {
    477     angleDeg = fmodf(angleDeg, 360);
    478     if (angleDeg < 0)
    479         angleDeg += 360;
    480 
    481     if (!angleDeg) {
    482         firstPoint.set(0, 0);
    483         secondPoint.set(size.width(), 0);
    484         return;
    485     }
    486 
    487     if (angleDeg == 90) {
    488         firstPoint.set(0, size.height());
    489         secondPoint.set(0, 0);
    490         return;
    491     }
    492 
    493     if (angleDeg == 180) {
    494         firstPoint.set(size.width(), 0);
    495         secondPoint.set(0, 0);
    496         return;
    497     }
    498 
    499     float slope = tan(deg2rad(angleDeg));
    500 
    501     // We find the endpoint by computing the intersection of the line formed by the slope,
    502     // and a line perpendicular to it that intersects the corner.
    503     float perpendicularSlope = -1 / slope;
    504 
    505     // Compute start corner relative to center.
    506     float halfHeight = size.height() / 2;
    507     float halfWidth = size.width() / 2;
    508     FloatPoint endCorner;
    509     if (angleDeg < 90)
    510         endCorner.set(halfWidth, halfHeight);
    511     else if (angleDeg < 180)
    512         endCorner.set(-halfWidth, halfHeight);
    513     else if (angleDeg < 270)
    514         endCorner.set(-halfWidth, -halfHeight);
    515     else
    516         endCorner.set(halfWidth, -halfHeight);
    517 
    518     // Compute c (of y = mx + c) using the corner point.
    519     float c = endCorner.y() - perpendicularSlope * endCorner.x();
    520     float endX = c / (slope - perpendicularSlope);
    521     float endY = perpendicularSlope * endX + c;
    522 
    523     // We computed the end point, so set the second point, flipping the Y to account for angles going anticlockwise.
    524     secondPoint.set(halfWidth + endX, size.height() - (halfHeight + endY));
    525     // Reflect around the center for the start point.
    526     firstPoint.set(size.width() - secondPoint.x(), size.height() - secondPoint.y());
    527 }
    528 
    529 PassRefPtr<Gradient> CSSLinearGradientValue::createGradient(RenderObject* renderer, const IntSize& size)
    530 {
    531     ASSERT(!size.isEmpty());
    532 
    533     RenderStyle* rootStyle = renderer->document()->documentElement()->renderStyle();
    534 
    535     FloatPoint firstPoint;
    536     FloatPoint secondPoint;
    537     if (m_angle) {
    538         float angle = m_angle->getFloatValue(CSSPrimitiveValue::CSS_DEG);
    539         endPointsFromAngle(angle, size, firstPoint, secondPoint);
    540     } else {
    541         firstPoint = computeEndPoint(m_firstX.get(), m_firstY.get(), renderer->style(), rootStyle, size);
    542 
    543         if (m_secondX || m_secondY)
    544             secondPoint = computeEndPoint(m_secondX.get(), m_secondY.get(), renderer->style(), rootStyle, size);
    545         else {
    546             if (m_firstX)
    547                 secondPoint.setX(size.width() - firstPoint.x());
    548             if (m_firstY)
    549                 secondPoint.setY(size.height() - firstPoint.y());
    550         }
    551     }
    552 
    553     RefPtr<Gradient> gradient = Gradient::create(firstPoint, secondPoint);
    554 
    555     // Now add the stops.
    556     addStops(gradient.get(), renderer, rootStyle, 1);
    557 
    558     return gradient.release();
    559 }
    560 
    561 String CSSRadialGradientValue::cssText() const
    562 {
    563     String result;
    564 
    565     if (m_deprecatedType) {
    566         result = "-webkit-gradient(radial, ";
    567 
    568         result += m_firstX->cssText() + " ";
    569         result += m_firstY->cssText() + ", ";
    570         result += m_firstRadius->cssText() + ", ";
    571         result += m_secondX->cssText() + " ";
    572         result += m_secondY->cssText();
    573         result += ", ";
    574         result += m_secondRadius->cssText();
    575 
    576         // FIXME: share?
    577         for (unsigned i = 0; i < m_stops.size(); i++) {
    578             const CSSGradientColorStop& stop = m_stops[i];
    579             result += ", ";
    580             if (stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER) == 0)
    581                 result += "from(" + stop.m_color->cssText() + ")";
    582             else if (stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER) == 1)
    583                 result += "to(" + stop.m_color->cssText() + ")";
    584             else
    585                 result += "color-stop(" + String::number(stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER)) + ", " + stop.m_color->cssText() + ")";
    586         }
    587     } else {
    588 
    589         result = m_repeating ? "-webkit-repeating-radial-gradient(" : "-webkit-radial-gradient(";
    590         if (m_firstX && m_firstY) {
    591             result += m_firstX->cssText() + " " + m_firstY->cssText();
    592         } else if (m_firstX)
    593             result += m_firstX->cssText();
    594          else if (m_firstY)
    595             result += m_firstY->cssText();
    596         else
    597             result += "center";
    598 
    599 
    600         if (m_shape || m_sizingBehavior) {
    601             result += ", ";
    602             if (m_shape)
    603                 result += m_shape->cssText() + " ";
    604             else
    605                 result += "ellipse ";
    606 
    607             if (m_sizingBehavior)
    608                 result += m_sizingBehavior->cssText();
    609             else
    610                 result += "cover";
    611 
    612         } else if (m_endHorizontalSize && m_endVerticalSize) {
    613             result += ", ";
    614             result += m_endHorizontalSize->cssText() + " " + m_endVerticalSize->cssText();
    615         }
    616 
    617         for (unsigned i = 0; i < m_stops.size(); i++) {
    618             const CSSGradientColorStop& stop = m_stops[i];
    619             result += ", ";
    620             result += stop.m_color->cssText();
    621             if (stop.m_position)
    622                 result += " " + stop.m_position->cssText();
    623         }
    624     }
    625 
    626     result += ")";
    627     return result;
    628 }
    629 
    630 float CSSRadialGradientValue::resolveRadius(CSSPrimitiveValue* radius, RenderStyle* style, RenderStyle* rootStyle, float* widthOrHeight)
    631 {
    632     float zoomFactor = style->effectiveZoom();
    633 
    634     float result = 0;
    635     if (radius->primitiveType() == CSSPrimitiveValue::CSS_NUMBER)  // Can the radius be a percentage?
    636         result = radius->getFloatValue() * zoomFactor;
    637     else if (widthOrHeight && radius->primitiveType() == CSSPrimitiveValue::CSS_PERCENTAGE)
    638         result = *widthOrHeight * radius->getFloatValue() / 100;
    639     else
    640         result = radius->computeLengthFloat(style, rootStyle, zoomFactor);
    641 
    642     return result;
    643 }
    644 
    645 static float distanceToClosestCorner(const FloatPoint& p, const FloatSize& size, FloatPoint& corner)
    646 {
    647     FloatPoint topLeft;
    648     float topLeftDistance = FloatSize(p - topLeft).diagonalLength();
    649 
    650     FloatPoint topRight(size.width(), 0);
    651     float topRightDistance = FloatSize(p - topRight).diagonalLength();
    652 
    653     FloatPoint bottomLeft(0, size.height());
    654     float bottomLeftDistance = FloatSize(p - bottomLeft).diagonalLength();
    655 
    656     FloatPoint bottomRight(size.width(), size.height());
    657     float bottomRightDistance = FloatSize(p - bottomRight).diagonalLength();
    658 
    659     corner = topLeft;
    660     float minDistance = topLeftDistance;
    661     if (topRightDistance < minDistance) {
    662         minDistance = topRightDistance;
    663         corner = topRight;
    664     }
    665 
    666     if (bottomLeftDistance < minDistance) {
    667         minDistance = bottomLeftDistance;
    668         corner = bottomLeft;
    669     }
    670 
    671     if (bottomRightDistance < minDistance) {
    672         minDistance = bottomRightDistance;
    673         corner = bottomRight;
    674     }
    675     return minDistance;
    676 }
    677 
    678 static float distanceToFarthestCorner(const FloatPoint& p, const FloatSize& size, FloatPoint& corner)
    679 {
    680     FloatPoint topLeft;
    681     float topLeftDistance = FloatSize(p - topLeft).diagonalLength();
    682 
    683     FloatPoint topRight(size.width(), 0);
    684     float topRightDistance = FloatSize(p - topRight).diagonalLength();
    685 
    686     FloatPoint bottomLeft(0, size.height());
    687     float bottomLeftDistance = FloatSize(p - bottomLeft).diagonalLength();
    688 
    689     FloatPoint bottomRight(size.width(), size.height());
    690     float bottomRightDistance = FloatSize(p - bottomRight).diagonalLength();
    691 
    692     corner = topLeft;
    693     float maxDistance = topLeftDistance;
    694     if (topRightDistance > maxDistance) {
    695         maxDistance = topRightDistance;
    696         corner = topRight;
    697     }
    698 
    699     if (bottomLeftDistance > maxDistance) {
    700         maxDistance = bottomLeftDistance;
    701         corner = bottomLeft;
    702     }
    703 
    704     if (bottomRightDistance > maxDistance) {
    705         maxDistance = bottomRightDistance;
    706         corner = bottomRight;
    707     }
    708     return maxDistance;
    709 }
    710 
    711 // Compute horizontal radius of ellipse with center at 0,0 which passes through p, and has
    712 // width/height given by aspectRatio.
    713 static inline float horizontalEllipseRadius(const FloatSize& p, float aspectRatio)
    714 {
    715     // x^2/a^2 + y^2/b^2 = 1
    716     // a/b = aspectRatio, b = a/aspectRatio
    717     // a = sqrt(x^2 + y^2/(1/r^2))
    718     return sqrtf(p.width() * p.width() + (p.height() * p.height()) / (1 / (aspectRatio * aspectRatio)));
    719 }
    720 
    721 // FIXME: share code with the linear version
    722 PassRefPtr<Gradient> CSSRadialGradientValue::createGradient(RenderObject* renderer, const IntSize& size)
    723 {
    724     ASSERT(!size.isEmpty());
    725 
    726     RenderStyle* rootStyle = renderer->document()->documentElement()->renderStyle();
    727 
    728     FloatPoint firstPoint = computeEndPoint(m_firstX.get(), m_firstY.get(), renderer->style(), rootStyle, size);
    729     if (!m_firstX)
    730         firstPoint.setX(size.width() / 2);
    731     if (!m_firstY)
    732         firstPoint.setY(size.height() / 2);
    733 
    734     FloatPoint secondPoint = computeEndPoint(m_secondX.get(), m_secondY.get(), renderer->style(), rootStyle, size);
    735     if (!m_secondX)
    736         secondPoint.setX(size.width() / 2);
    737     if (!m_secondY)
    738         secondPoint.setY(size.height() / 2);
    739 
    740     float firstRadius = 0;
    741     if (m_firstRadius)
    742         firstRadius = resolveRadius(m_firstRadius.get(), renderer->style(), rootStyle);
    743 
    744     float secondRadius = 0;
    745     float aspectRatio = 1; // width / height.
    746     if (m_secondRadius)
    747         secondRadius = resolveRadius(m_secondRadius.get(), renderer->style(), rootStyle);
    748     else if (m_endHorizontalSize || m_endVerticalSize) {
    749         float width = size.width();
    750         float height = size.height();
    751         secondRadius = resolveRadius(m_endHorizontalSize.get(), renderer->style(), rootStyle, &width);
    752         aspectRatio = secondRadius / resolveRadius(m_endVerticalSize.get(), renderer->style(), rootStyle, &height);
    753     } else {
    754         enum GradientShape { Circle, Ellipse };
    755         GradientShape shape = Ellipse;
    756         if (m_shape && m_shape->primitiveType() == CSSPrimitiveValue::CSS_IDENT && m_shape->getIdent() == CSSValueCircle)
    757             shape = Circle;
    758 
    759         enum GradientFill { ClosestSide, ClosestCorner, FarthestSide, FarthestCorner };
    760         GradientFill fill = FarthestCorner;
    761 
    762         if (m_sizingBehavior && m_sizingBehavior->primitiveType() == CSSPrimitiveValue::CSS_IDENT) {
    763             switch (m_sizingBehavior->getIdent()) {
    764             case CSSValueContain:
    765             case CSSValueClosestSide:
    766                 fill = ClosestSide;
    767                 break;
    768             case CSSValueClosestCorner:
    769                 fill = ClosestCorner;
    770                 break;
    771             case CSSValueFarthestSide:
    772                 fill = FarthestSide;
    773                 break;
    774             case CSSValueCover:
    775             case CSSValueFarthestCorner:
    776                 fill = FarthestCorner;
    777                 break;
    778             }
    779         }
    780 
    781         // Now compute the end radii based on the second point, shape and fill.
    782 
    783         // Horizontal
    784         switch (fill) {
    785         case ClosestSide: {
    786             float xDist = min(secondPoint.x(), size.width() - secondPoint.x());
    787             float yDist = min(secondPoint.y(), size.height() - secondPoint.y());
    788             if (shape == Circle) {
    789                 float smaller = min(xDist, yDist);
    790                 xDist = smaller;
    791                 yDist = smaller;
    792             }
    793             secondRadius = xDist;
    794             aspectRatio = xDist / yDist;
    795             break;
    796         }
    797         case FarthestSide: {
    798             float xDist = max(secondPoint.x(), size.width() - secondPoint.x());
    799             float yDist = max(secondPoint.y(), size.height() - secondPoint.y());
    800             if (shape == Circle) {
    801                 float larger = max(xDist, yDist);
    802                 xDist = larger;
    803                 yDist = larger;
    804             }
    805             secondRadius = xDist;
    806             aspectRatio = xDist / yDist;
    807             break;
    808         }
    809         case ClosestCorner: {
    810             FloatPoint corner;
    811             float distance = distanceToClosestCorner(secondPoint, size, corner);
    812             if (shape == Circle)
    813                 secondRadius = distance;
    814             else {
    815                 // If <shape> is ellipse, the gradient-shape has the same ratio of width to height
    816                 // that it would if closest-side or farthest-side were specified, as appropriate.
    817                 float xDist = min(secondPoint.x(), size.width() - secondPoint.x());
    818                 float yDist = min(secondPoint.y(), size.height() - secondPoint.y());
    819 
    820                 secondRadius = horizontalEllipseRadius(corner - secondPoint, xDist / yDist);
    821                 aspectRatio = xDist / yDist;
    822             }
    823             break;
    824         }
    825 
    826         case FarthestCorner: {
    827             FloatPoint corner;
    828             float distance = distanceToFarthestCorner(secondPoint, size, corner);
    829             if (shape == Circle)
    830                 secondRadius = distance;
    831             else {
    832                 // If <shape> is ellipse, the gradient-shape has the same ratio of width to height
    833                 // that it would if closest-side or farthest-side were specified, as appropriate.
    834                 float xDist = max(secondPoint.x(), size.width() - secondPoint.x());
    835                 float yDist = max(secondPoint.y(), size.height() - secondPoint.y());
    836 
    837                 secondRadius = horizontalEllipseRadius(corner - secondPoint, xDist / yDist);
    838                 aspectRatio = xDist / yDist;
    839             }
    840             break;
    841         }
    842         }
    843     }
    844 
    845     RefPtr<Gradient> gradient = Gradient::create(firstPoint, firstRadius, secondPoint, secondRadius, aspectRatio);
    846 
    847     // addStops() only uses maxExtent for repeating gradients.
    848     float maxExtent = 0;
    849     if (m_repeating) {
    850         FloatPoint corner;
    851         maxExtent = distanceToFarthestCorner(secondPoint, size, corner);
    852     }
    853 
    854     // Now add the stops.
    855     addStops(gradient.get(), renderer, rootStyle, maxExtent);
    856 
    857     return gradient.release();
    858 }
    859 
    860 } // namespace WebCore
    861