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