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