[Conic Gradients] Add support for parsing conic gradients
[WebKit-https.git] / Source / WebCore / css / CSSGradientValue.cpp
1 /*
2  * Copyright (C) 2008 Apple Inc.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "CSSGradientValue.h"
28
29 #include "CSSCalculationValue.h"
30 #include "CSSToLengthConversionData.h"
31 #include "CSSValueKeywords.h"
32 #include "FloatSize.h"
33 #include "Gradient.h"
34 #include "GradientImage.h"
35 #include "NodeRenderStyle.h"
36 #include "Pair.h"
37 #include "RenderElement.h"
38 #include "RenderView.h"
39 #include "StyleResolver.h"
40 #include <wtf/text/StringBuilder.h>
41
42 namespace WebCore {
43
44 static inline Ref<Gradient> createGradient(CSSGradientValue& value, RenderElement& renderer, FloatSize size)
45 {
46     if (is<CSSLinearGradientValue>(value))
47         return downcast<CSSLinearGradientValue>(value).createGradient(renderer, size);
48     return downcast<CSSRadialGradientValue>(value).createGradient(renderer, size);
49 }
50
51 RefPtr<Image> CSSGradientValue::image(RenderElement& renderer, const FloatSize& size)
52 {
53     if (size.isEmpty())
54         return nullptr;
55     bool cacheable = isCacheable();
56     if (cacheable) {
57         if (!clients().contains(&renderer))
58             return nullptr;
59         if (auto* result = cachedImageForSize(size))
60             return result;
61     }
62     auto newImage = GradientImage::create(createGradient(*this, renderer, size), size);
63     if (cacheable)
64         saveCachedImageForSize(size, newImage.get());
65     return WTFMove(newImage);
66 }
67
68 // Should only ever be called for deprecated gradients.
69 static inline bool compareStops(const CSSGradientColorStop& a, const CSSGradientColorStop& b)
70 {
71     double aVal = a.m_position->doubleValue(CSSPrimitiveValue::CSS_NUMBER);
72     double bVal = b.m_position->doubleValue(CSSPrimitiveValue::CSS_NUMBER);
73
74     return aVal < bVal;
75 }
76
77 void CSSGradientValue::sortStopsIfNeeded()
78 {
79     ASSERT(m_gradientType == CSSDeprecatedLinearGradient || m_gradientType == CSSDeprecatedRadialGradient);
80     if (!m_stopsSorted) {
81         if (m_stops.size())
82             std::stable_sort(m_stops.begin(), m_stops.end(), compareStops);
83         m_stopsSorted = true;
84     }
85 }
86
87 struct GradientStop {
88     Color color;
89     float offset { 0 };
90     bool specified { false };
91     bool isMidpoint { false };
92 };
93
94 static inline Ref<CSSGradientValue> clone(CSSGradientValue& value)
95 {
96     if (is<CSSLinearGradientValue>(value))
97         return downcast<CSSLinearGradientValue>(value).clone();
98     if (is<CSSRadialGradientValue>(value))
99         return downcast<CSSRadialGradientValue>(value).clone();
100     ASSERT(is<CSSConicGradientValue>(value));
101     return downcast<CSSConicGradientValue>(value).clone();
102 }
103
104 Ref<CSSGradientValue> CSSGradientValue::gradientWithStylesResolved(const StyleResolver& styleResolver)
105 {
106     bool colorIsDerivedFromElement = false;
107     for (auto& stop : m_stops) {
108         if (!stop.isMidpoint && styleResolver.colorFromPrimitiveValueIsDerivedFromElement(*stop.m_color)) {
109             stop.m_colorIsDerivedFromElement = true;
110             colorIsDerivedFromElement = true;
111             break;
112         }
113     }
114     auto result = colorIsDerivedFromElement ? clone(*this) : makeRef(*this);
115     for (auto& stop : result->m_stops) {
116         if (!stop.isMidpoint)
117             stop.m_resolvedColor = styleResolver.colorFromPrimitiveValue(*stop.m_color);
118     }
119     return result;
120 }
121
122 static inline int interpolate(int min, int max, float position)
123 {
124     return min + static_cast<int>(position * (max - min));
125 }
126
127 static inline Color interpolate(const Color& color1, const Color& color2, float position)
128 {
129     // FIXME: ExtendedColor - Doesn't work with extended colors, and really should be a helper in Color.h, not here.
130     int red = interpolate(color1.red(), color2.red(), position);
131     int green = interpolate(color1.green(), color2.green(), position);
132     int blue = interpolate(color1.blue(), color2.blue(), position);
133     int alpha = interpolate(color1.alpha(), color2.alpha(), position);
134
135     return Color(red, green, blue, alpha);
136 }
137
138 void CSSGradientValue::addStops(Gradient& gradient, const CSSToLengthConversionData& conversionData, float maxLengthForRepeat)
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->floatValue(CSSPrimitiveValue::CSS_PERCENTAGE) / 100;
149             else
150                 offset = stop.m_position->floatValue(CSSPrimitiveValue::CSS_NUMBER);
151
152             gradient.addColorStop(offset, 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 (isLinearGradientValue())
170         gradientEnd = gradient.p1();
171     else if (isRadialGradientValue())
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].isMidpoint = stop.isMidpoint;
178         stops[i].color = stop.m_resolvedColor;
179
180         if (stop.m_position) {
181             const CSSPrimitiveValue& positionValue = *stop.m_position;
182             if (positionValue.isPercentage())
183                 stops[i].offset = positionValue.floatValue(CSSPrimitiveValue::CSS_PERCENTAGE) / 100;
184             else if (positionValue.isLength() || positionValue.isViewportPercentageLength() || positionValue.isCalculatedPercentageWithLength()) {
185                 if (!computedGradientLength) {
186                     FloatSize gradientSize(gradientStart - gradientEnd);
187                     gradientLength = gradientSize.diagonalLength();
188                 }
189                 float length;
190                 if (positionValue.isLength())
191                     length = positionValue.computeLength<float>(conversionData);
192                 else {
193                     Ref<CalculationValue> calculationValue { positionValue.cssCalcValue()->createCalculationValue(conversionData) };
194                     length = calculationValue->evaluate(gradientLength);
195                 }
196                 stops[i].offset = (gradientLength > 0) ? length / gradientLength : 0;
197             } else {
198                 ASSERT_NOT_REACHED();
199                 stops[i].offset = 0;
200             }
201             stops[i].specified = true;
202         } else {
203             // If the first color-stop does not have a position, its position defaults to 0%.
204             // If the last color-stop does not have a position, its position defaults to 100%.
205             if (!i) {
206                 stops[i].offset = 0;
207                 stops[i].specified = true;
208             } else if (numStops > 1 && i == numStops - 1) {
209                 stops[i].offset = 1;
210                 stops[i].specified = true;
211             }
212         }
213
214         // If a color-stop has a position that is less than the specified position of any
215         // color-stop before it in the list, its position is changed to be equal to the
216         // largest specified position of any color-stop before it.
217         if (stops[i].specified && i > 0) {
218             size_t prevSpecifiedIndex;
219             for (prevSpecifiedIndex = i - 1; prevSpecifiedIndex; --prevSpecifiedIndex) {
220                 if (stops[prevSpecifiedIndex].specified)
221                     break;
222             }
223
224             if (stops[i].offset < stops[prevSpecifiedIndex].offset)
225                 stops[i].offset = stops[prevSpecifiedIndex].offset;
226         }
227     }
228
229     ASSERT(stops[0].specified && stops[numStops - 1].specified);
230
231     // If any color-stop still does not have a position, then, for each run of adjacent
232     // color-stops without positions, set their positions so that they are evenly spaced
233     // between the preceding and following color-stops with positions.
234     if (numStops > 2) {
235         size_t unspecifiedRunStart = 0;
236         bool inUnspecifiedRun = false;
237
238         for (size_t i = 0; i < numStops; ++i) {
239             if (!stops[i].specified && !inUnspecifiedRun) {
240                 unspecifiedRunStart = i;
241                 inUnspecifiedRun = true;
242             } else if (stops[i].specified && inUnspecifiedRun) {
243                 size_t unspecifiedRunEnd = i;
244
245                 if (unspecifiedRunStart < unspecifiedRunEnd) {
246                     float lastSpecifiedOffset = stops[unspecifiedRunStart - 1].offset;
247                     float nextSpecifiedOffset = stops[unspecifiedRunEnd].offset;
248                     float delta = (nextSpecifiedOffset - lastSpecifiedOffset) / (unspecifiedRunEnd - unspecifiedRunStart + 1);
249
250                     for (size_t j = unspecifiedRunStart; j < unspecifiedRunEnd; ++j)
251                         stops[j].offset = lastSpecifiedOffset + (j - unspecifiedRunStart + 1) * delta;
252                 }
253
254                 inUnspecifiedRun = false;
255             }
256         }
257     }
258
259     // Walk over the color stops, look for midpoints and add stops as needed.
260     // If mid < 50%, add 2 stops to the left and 6 to the right
261     // else add 6 stops to the left and 2 to the right.
262     // Stops on the side with the most stops start midway because the curve approximates
263     // a line in that region. We then add 5 more color stops on that side to minimize the change
264     // how the luminance changes at each of the color stops. We don't have to add as many on the other side
265     // since it becomes small which increases the differentation of luminance which hides the color stops.
266     // Even with 4 extra color stops, it *is* possible to discern the steps when the gradient is large and has
267     // large luminance differences between midpoint and color stop. If this becomes an issue, we can consider
268     // making this algorithm a bit smarter.
269
270     // Midpoints that coincide with color stops are treated specially since they don't require
271     // extra stops and generate hard lines.
272     for (size_t x = 1; x < stops.size() - 1;) {
273         if (!stops[x].isMidpoint) {
274             ++x;
275             continue;
276         }
277
278         // Find previous and next color so we know what to interpolate between.
279         // We already know they have a color since we checked for that earlier.
280         Color color1 = stops[x - 1].color;
281         Color color2 = stops[x + 1].color;
282         // Likewise find the position of previous and next color stop.
283         float offset1 = stops[x - 1].offset;
284         float offset2 = stops[x + 1].offset;
285         float offset = stops[x].offset;
286
287         // Check if everything coincides or the midpoint is exactly in the middle.
288         // If so, ignore the midpoint.
289         if (offset - offset1 == offset2 - offset) {
290             stops.remove(x);
291             continue;
292         }
293
294         // Check if we coincide with the left color stop.
295         if (offset1 == offset) {
296             // Morph the midpoint to a regular stop with the color of the next color stop.
297             stops[x].color = color2;
298             stops[x].isMidpoint = false;
299             continue;
300         }
301
302         // Check if we coincide with the right color stop.
303         if (offset2 == offset) {
304             // Morph the midpoint to a regular stop with the color of the previous color stop.
305             stops[x].color = color1;
306             stops[x].isMidpoint = false;
307             continue;
308         }
309
310         float midpoint = (offset - offset1) / (offset2 - offset1);
311         GradientStop newStops[9];
312         if (midpoint > .5f) {
313             for (size_t y = 0; y < 7; ++y)
314                 newStops[y].offset = offset1 + (offset - offset1) * (7 + y) / 13;
315
316             newStops[7].offset = offset + (offset2 - offset) / 3;
317             newStops[8].offset = offset + (offset2 - offset) * 2 / 3;
318         } else {
319             newStops[0].offset = offset1 + (offset - offset1) / 3;
320             newStops[1].offset = offset1 + (offset - offset1) * 2 / 3;
321
322             for (size_t y = 0; y < 7; ++y)
323                 newStops[y + 2].offset = offset + (offset2 - offset) * y / 13;
324         }
325         // calculate colors
326         for (size_t y = 0; y < 9; ++y) {
327             float relativeOffset = (newStops[y].offset - offset1) / (offset2 - offset1);
328             float multiplier = powf(relativeOffset, logf(.5f) / logf(midpoint));
329             newStops[y].color = interpolate(color1, color2, multiplier);
330         }
331
332         stops.remove(x);
333         stops.insert(x, newStops, 9);
334         x += 9;
335     }
336
337     numStops = stops.size();
338
339     // If the gradient is repeating, repeat the color stops.
340     // We can't just push this logic down into the platform-specific Gradient code,
341     // because we have to know the extent of the gradient, and possible move the end points.
342     if (m_repeating && numStops > 1) {
343         // If the difference in the positions of the first and last color-stops is 0,
344         // the gradient defines a solid-color image with the color of the last color-stop in the rule.
345         float gradientRange = stops[numStops - 1].offset - stops[0].offset;
346         if (!gradientRange) {
347             stops.first().offset = 0;
348             stops.first().color = stops.last().color;
349             stops.shrink(1);
350             numStops = 1;
351         } else {
352             float maxExtent = 1;
353
354             // Radial gradients may need to extend further than the endpoints, because they have
355             // to repeat out to the corners of the box.
356             if (isRadialGradientValue()) {
357                 if (!computedGradientLength) {
358                     FloatSize gradientSize(gradientStart - gradientEnd);
359                     gradientLength = gradientSize.diagonalLength();
360                 }
361
362                 if (maxLengthForRepeat > gradientLength)
363                     maxExtent = gradientLength > 0 ? maxLengthForRepeat / gradientLength : 0;
364             }
365
366             size_t originalNumStops = numStops;
367             size_t originalFirstStopIndex = 0;
368
369             // Work backwards from the first, adding stops until we get one before 0.
370             float firstOffset = stops[0].offset;
371             if (firstOffset > 0) {
372                 float currOffset = firstOffset;
373                 size_t srcStopOrdinal = originalNumStops - 1;
374
375                 while (true) {
376                     GradientStop newStop = stops[originalFirstStopIndex + srcStopOrdinal];
377                     newStop.offset = currOffset;
378                     stops.insert(0, newStop);
379                     ++originalFirstStopIndex;
380                     if (currOffset < 0)
381                         break;
382
383                     if (srcStopOrdinal)
384                         currOffset -= stops[originalFirstStopIndex + srcStopOrdinal].offset - stops[originalFirstStopIndex + srcStopOrdinal - 1].offset;
385                     srcStopOrdinal = (srcStopOrdinal + originalNumStops - 1) % originalNumStops;
386                 }
387             }
388
389             // Work forwards from the end, adding stops until we get one after 1.
390             float lastOffset = stops[stops.size() - 1].offset;
391             if (lastOffset < maxExtent) {
392                 float currOffset = lastOffset;
393                 size_t srcStopOrdinal = 0;
394
395                 while (true) {
396                     size_t srcStopIndex = originalFirstStopIndex + srcStopOrdinal;
397                     GradientStop newStop = stops[srcStopIndex];
398                     newStop.offset = currOffset;
399                     stops.append(newStop);
400                     if (currOffset > maxExtent)
401                         break;
402                     if (srcStopOrdinal < originalNumStops - 1)
403                         currOffset += stops[srcStopIndex + 1].offset - stops[srcStopIndex].offset;
404                     srcStopOrdinal = (srcStopOrdinal + 1) % originalNumStops;
405                 }
406             }
407         }
408     }
409
410     numStops = stops.size();
411
412     // If the gradient goes outside the 0-1 range, normalize it by moving the endpoints, and adjusting the stops.
413     if (numStops > 1 && (stops[0].offset < 0 || stops[numStops - 1].offset > 1)) {
414         if (isLinearGradientValue()) {
415             float firstOffset = stops[0].offset;
416             float lastOffset = stops[numStops - 1].offset;
417             if (firstOffset != lastOffset) {
418                 float scale = lastOffset - firstOffset;
419
420                 for (size_t i = 0; i < numStops; ++i)
421                     stops[i].offset = (stops[i].offset - firstOffset) / scale;
422
423                 FloatPoint p0 = gradient.p0();
424                 FloatPoint p1 = gradient.p1();
425                 gradient.setP0(FloatPoint(p0.x() + firstOffset * (p1.x() - p0.x()), p0.y() + firstOffset * (p1.y() - p0.y())));
426                 gradient.setP1(FloatPoint(p1.x() + (lastOffset - 1) * (p1.x() - p0.x()), p1.y() + (lastOffset - 1) * (p1.y() - p0.y())));
427             } else {
428                 // There's a single position that is outside the scale, clamp the positions to 1.
429                 for (size_t i = 0; i < numStops; ++i)
430                     stops[i].offset = 1;
431             }
432         } else if (isRadialGradientValue()) {
433             // Rather than scaling the points < 0, we truncate them, so only scale according to the largest point.
434             float firstOffset = 0;
435             float lastOffset = stops[numStops - 1].offset;
436             float scale = lastOffset - firstOffset;
437
438             // Reset points below 0 to the first visible color.
439             size_t firstZeroOrGreaterIndex = numStops;
440             for (size_t i = 0; i < numStops; ++i) {
441                 if (stops[i].offset >= 0) {
442                     firstZeroOrGreaterIndex = i;
443                     break;
444                 }
445             }
446
447             if (firstZeroOrGreaterIndex > 0) {
448                 if (firstZeroOrGreaterIndex < numStops && stops[firstZeroOrGreaterIndex].offset > 0) {
449                     float prevOffset = stops[firstZeroOrGreaterIndex - 1].offset;
450                     float nextOffset = stops[firstZeroOrGreaterIndex].offset;
451
452                     float interStopProportion = -prevOffset / (nextOffset - prevOffset);
453                     // FIXME: when we interpolate gradients using premultiplied colors, this should do premultiplication.
454                     Color blendedColor = blend(stops[firstZeroOrGreaterIndex - 1].color, stops[firstZeroOrGreaterIndex].color, interStopProportion);
455
456                     // Clamp the positions to 0 and set the color.
457                     for (size_t i = 0; i < firstZeroOrGreaterIndex; ++i) {
458                         stops[i].offset = 0;
459                         stops[i].color = blendedColor;
460                     }
461                 } else {
462                     // All stops are below 0; just clamp them.
463                     for (size_t i = 0; i < firstZeroOrGreaterIndex; ++i)
464                         stops[i].offset = 0;
465                 }
466             }
467
468             for (size_t i = 0; i < numStops; ++i)
469                 stops[i].offset /= scale;
470
471             gradient.setStartRadius(gradient.startRadius() * scale);
472             gradient.setEndRadius(gradient.endRadius() * scale);
473         }
474     }
475
476     for (unsigned i = 0; i < numStops; i++)
477         gradient.addColorStop(stops[i].offset, stops[i].color);
478
479     gradient.setStopsSorted(true);
480 }
481
482 static float positionFromValue(const CSSPrimitiveValue* value, const CSSToLengthConversionData& conversionData, const FloatSize& size, bool isHorizontal)
483 {
484     int origin = 0;
485     int sign = 1;
486     int edgeDistance = isHorizontal ? size.width() : size.height();
487     
488     // In this case the center of the gradient is given relative to an edge in the
489     // form of: [ top | bottom | right | left ] [ <percentage> | <length> ].
490     if (value->isPair()) {
491         CSSValueID originID = value->pairValue()->first()->valueID();
492         value = value->pairValue()->second();
493         
494         if (originID == CSSValueRight || originID == CSSValueBottom) {
495             // For right/bottom, the offset is relative to the far edge.
496             origin = edgeDistance;
497             sign = -1;
498         }
499     }
500     
501     if (value->isNumber())
502         return origin + sign * value->floatValue() * conversionData.zoom();
503     
504     if (value->isPercentage())
505         return origin + sign * value->floatValue() / 100.f * edgeDistance;
506
507     if (value->isCalculatedPercentageWithLength()) {
508         Ref<CalculationValue> calculationValue { value->cssCalcValue()->createCalculationValue(conversionData) };
509         return origin + sign * calculationValue->evaluate(edgeDistance);
510     }
511     
512     switch (value->valueID()) {
513     case CSSValueTop:
514         ASSERT(!isHorizontal);
515         return 0;
516     case CSSValueLeft:
517         ASSERT(isHorizontal);
518         return 0;
519     case CSSValueBottom:
520         ASSERT(!isHorizontal);
521         return size.height();
522     case CSSValueRight:
523         ASSERT(isHorizontal);
524         return size.width();
525     case CSSValueCenter:
526         return origin + sign * .5f * edgeDistance;
527     default:
528         break;
529     }
530
531     return origin + sign * value->computeLength<float>(conversionData);
532 }
533
534 FloatPoint CSSGradientValue::computeEndPoint(CSSPrimitiveValue* horizontal, CSSPrimitiveValue* vertical, const CSSToLengthConversionData& conversionData, const FloatSize& size)
535 {
536     FloatPoint result;
537
538     if (horizontal)
539         result.setX(positionFromValue(horizontal, conversionData, size, true));
540
541     if (vertical)
542         result.setY(positionFromValue(vertical, conversionData, size, false));
543
544     return result;
545 }
546
547 bool CSSGradientValue::isCacheable() const
548 {
549     for (auto& stop : m_stops) {
550         if (stop.m_colorIsDerivedFromElement)
551             return false;
552
553         if (!stop.m_position)
554             continue;
555
556         if (stop.m_position->isFontRelativeLength())
557             return false;
558     }
559
560     return true;
561 }
562
563 bool CSSGradientValue::knownToBeOpaque() const
564 {
565     for (auto& stop : m_stops) {
566         if (!stop.m_resolvedColor.isOpaque())
567             return false;
568     }
569     return true;
570 }
571
572 String CSSLinearGradientValue::customCSSText() const
573 {
574     StringBuilder result;
575     if (m_gradientType == CSSDeprecatedLinearGradient) {
576         result.appendLiteral("-webkit-gradient(linear, ");
577         result.append(m_firstX->cssText());
578         result.append(' ');
579         result.append(m_firstY->cssText());
580         result.appendLiteral(", ");
581         result.append(m_secondX->cssText());
582         result.append(' ');
583         result.append(m_secondY->cssText());
584
585         for (auto& stop : m_stops) {
586             result.appendLiteral(", ");
587             auto position = stop.m_position->doubleValue(CSSPrimitiveValue::CSS_NUMBER);
588             if (!position) {
589                 result.appendLiteral("from(");
590                 result.append(stop.m_color->cssText());
591                 result.append(')');
592             } else if (position == 1) {
593                 result.appendLiteral("to(");
594                 result.append(stop.m_color->cssText());
595                 result.append(')');
596             } else {
597                 result.appendLiteral("color-stop(");
598                 result.appendNumber(position);
599                 result.appendLiteral(", ");
600                 result.append(stop.m_color->cssText());
601                 result.append(')');
602             }
603         }
604     } else if (m_gradientType == CSSPrefixedLinearGradient) {
605         if (m_repeating)
606             result.appendLiteral("-webkit-repeating-linear-gradient(");
607         else
608             result.appendLiteral("-webkit-linear-gradient(");
609
610         if (m_angle)
611             result.append(m_angle->cssText());
612         else {
613             if (m_firstX && m_firstY) {
614                 result.append(m_firstX->cssText());
615                 result.append(' ');
616                 result.append(m_firstY->cssText());
617             } else if (m_firstX || m_firstY) {
618                 if (m_firstX)
619                     result.append(m_firstX->cssText());
620
621                 if (m_firstY)
622                     result.append(m_firstY->cssText());
623             }
624         }
625
626         for (unsigned i = 0; i < m_stops.size(); i++) {
627             const CSSGradientColorStop& stop = m_stops[i];
628             result.appendLiteral(", ");
629             result.append(stop.m_color->cssText());
630             if (stop.m_position) {
631                 result.append(' ');
632                 result.append(stop.m_position->cssText());
633             }
634         }
635     } else {
636         if (m_repeating)
637             result.appendLiteral("repeating-linear-gradient(");
638         else
639             result.appendLiteral("linear-gradient(");
640
641         bool wroteSomething = false;
642
643         if (m_angle && m_angle->computeDegrees() != 180) {
644             result.append(m_angle->cssText());
645             wroteSomething = true;
646         } else if ((m_firstX || m_firstY) && !(!m_firstX && m_firstY && m_firstY->valueID() == CSSValueBottom)) {
647             result.appendLiteral("to ");
648             if (m_firstX && m_firstY) {
649                 result.append(m_firstX->cssText());
650                 result.append(' ');
651                 result.append(m_firstY->cssText());
652             } else if (m_firstX)
653                 result.append(m_firstX->cssText());
654             else
655                 result.append(m_firstY->cssText());
656             wroteSomething = true;
657         }
658
659         if (wroteSomething)
660             result.appendLiteral(", ");
661
662         for (unsigned i = 0; i < m_stops.size(); i++) {
663             const CSSGradientColorStop& stop = m_stops[i];
664             if (i)
665                 result.appendLiteral(", ");
666             if (!stop.isMidpoint)
667                 result.append(stop.m_color->cssText());
668             if (stop.m_position) {
669                 if (!stop.isMidpoint)
670                     result.append(' ');
671                 result.append(stop.m_position->cssText());
672             }
673         }
674         
675     }
676
677     result.append(')');
678     return result.toString();
679 }
680
681 // Compute the endpoints so that a gradient of the given angle covers a box of the given size.
682 static void endPointsFromAngle(float angleDeg, const FloatSize& size, FloatPoint& firstPoint, FloatPoint& secondPoint, CSSGradientType type)
683 {
684     // Prefixed gradients use "polar coordinate" angles, rather than "bearing" angles.
685     if (type == CSSPrefixedLinearGradient)
686         angleDeg = 90 - angleDeg;
687
688     angleDeg = fmodf(angleDeg, 360);
689     if (angleDeg < 0)
690         angleDeg += 360;
691
692     if (!angleDeg) {
693         firstPoint.set(0, size.height());
694         secondPoint.set(0, 0);
695         return;
696     }
697
698     if (angleDeg == 90) {
699         firstPoint.set(0, 0);
700         secondPoint.set(size.width(), 0);
701         return;
702     }
703
704     if (angleDeg == 180) {
705         firstPoint.set(0, 0);
706         secondPoint.set(0, size.height());
707         return;
708     }
709
710     if (angleDeg == 270) {
711         firstPoint.set(size.width(), 0);
712         secondPoint.set(0, 0);
713         return;
714     }
715
716     // angleDeg is a "bearing angle" (0deg = N, 90deg = E),
717     // but tan expects 0deg = E, 90deg = N.
718     float slope = tan(deg2rad(90 - angleDeg));
719
720     // We find the endpoint by computing the intersection of the line formed by the slope,
721     // and a line perpendicular to it that intersects the corner.
722     float perpendicularSlope = -1 / slope;
723
724     // Compute start corner relative to center, in Cartesian space (+y = up).
725     float halfHeight = size.height() / 2;
726     float halfWidth = size.width() / 2;
727     FloatPoint endCorner;
728     if (angleDeg < 90)
729         endCorner.set(halfWidth, halfHeight);
730     else if (angleDeg < 180)
731         endCorner.set(halfWidth, -halfHeight);
732     else if (angleDeg < 270)
733         endCorner.set(-halfWidth, -halfHeight);
734     else
735         endCorner.set(-halfWidth, halfHeight);
736
737     // Compute c (of y = mx + c) using the corner point.
738     float c = endCorner.y() - perpendicularSlope * endCorner.x();
739     float endX = c / (slope - perpendicularSlope);
740     float endY = perpendicularSlope * endX + c;
741
742     // We computed the end point, so set the second point, 
743     // taking into account the moved origin and the fact that we're in drawing space (+y = down).
744     secondPoint.set(halfWidth + endX, halfHeight - endY);
745     // Reflect around the center for the start point.
746     firstPoint.set(halfWidth - endX, halfHeight + endY);
747 }
748
749 Ref<Gradient> CSSLinearGradientValue::createGradient(RenderElement& renderer, const FloatSize& size)
750 {
751     ASSERT(!size.isEmpty());
752
753     CSSToLengthConversionData conversionData(&renderer.style(), renderer.document().documentElement()->renderStyle(), &renderer.view());
754
755     FloatPoint firstPoint;
756     FloatPoint secondPoint;
757     if (m_angle) {
758         float angle = m_angle->floatValue(CSSPrimitiveValue::CSS_DEG);
759         endPointsFromAngle(angle, size, firstPoint, secondPoint, m_gradientType);
760     } else {
761         switch (m_gradientType) {
762         case CSSDeprecatedLinearGradient:
763             firstPoint = computeEndPoint(m_firstX.get(), m_firstY.get(), conversionData, size);
764             if (m_secondX || m_secondY)
765                 secondPoint = computeEndPoint(m_secondX.get(), m_secondY.get(), conversionData, size);
766             else {
767                 if (m_firstX)
768                     secondPoint.setX(size.width() - firstPoint.x());
769                 if (m_firstY)
770                     secondPoint.setY(size.height() - firstPoint.y());
771             }
772             break;
773         case CSSPrefixedLinearGradient:
774             firstPoint = computeEndPoint(m_firstX.get(), m_firstY.get(), conversionData, size);
775             if (m_firstX)
776                 secondPoint.setX(size.width() - firstPoint.x());
777             if (m_firstY)
778                 secondPoint.setY(size.height() - firstPoint.y());
779             break;
780         case CSSLinearGradient:
781             if (m_firstX && m_firstY) {
782                 // "Magic" corners, so the 50% line touches two corners.
783                 float rise = size.width();
784                 float run = size.height();
785                 if (m_firstX && m_firstX->valueID() == CSSValueLeft)
786                     run *= -1;
787                 if (m_firstY && m_firstY->valueID() == CSSValueBottom)
788                     rise *= -1;
789                 // Compute angle, and flip it back to "bearing angle" degrees.
790                 float angle = 90 - rad2deg(atan2(rise, run));
791                 endPointsFromAngle(angle, size, firstPoint, secondPoint, m_gradientType);
792             } else if (m_firstX || m_firstY) { 
793                 secondPoint = computeEndPoint(m_firstX.get(), m_firstY.get(), conversionData, size);
794                 if (m_firstX)
795                     firstPoint.setX(size.width() - secondPoint.x());
796                 if (m_firstY)
797                     firstPoint.setY(size.height() - secondPoint.y());
798             } else
799                 secondPoint.setY(size.height());
800             break;
801         default:
802             ASSERT_NOT_REACHED();
803         }
804
805     }
806
807     Ref<Gradient> gradient = Gradient::create(firstPoint, secondPoint);
808
809     // Now add the stops.
810     addStops(gradient, conversionData, 1);
811
812     return gradient;
813 }
814
815 bool CSSLinearGradientValue::equals(const CSSLinearGradientValue& other) const
816 {
817     if (m_gradientType == CSSDeprecatedLinearGradient)
818         return other.m_gradientType == m_gradientType
819             && compareCSSValuePtr(m_firstX, other.m_firstX)
820             && compareCSSValuePtr(m_firstY, other.m_firstY)
821             && compareCSSValuePtr(m_secondX, other.m_secondX)
822             && compareCSSValuePtr(m_secondY, other.m_secondY)
823             && m_stops == other.m_stops;
824
825     if (m_repeating != other.m_repeating)
826         return false;
827
828     if (m_angle)
829         return compareCSSValuePtr(m_angle, other.m_angle) && m_stops == other.m_stops;
830
831     if (other.m_angle)
832         return false;
833
834     bool equalXandY = false;
835     if (m_firstX && m_firstY)
836         equalXandY = compareCSSValuePtr(m_firstX, other.m_firstX) && compareCSSValuePtr(m_firstY, other.m_firstY);
837     else if (m_firstX)
838         equalXandY = compareCSSValuePtr(m_firstX, other.m_firstX) && !other.m_firstY;
839     else if (m_firstY)
840         equalXandY = compareCSSValuePtr(m_firstY, other.m_firstY) && !other.m_firstX;
841     else
842         equalXandY = !other.m_firstX && !other.m_firstY;
843
844     return equalXandY && m_stops == other.m_stops;
845 }
846
847 String CSSRadialGradientValue::customCSSText() const
848 {
849     StringBuilder result;
850
851     if (m_gradientType == CSSDeprecatedRadialGradient) {
852         result.appendLiteral("-webkit-gradient(radial, ");
853         result.append(m_firstX->cssText());
854         result.append(' ');
855         result.append(m_firstY->cssText());
856         result.appendLiteral(", ");
857         result.append(m_firstRadius->cssText());
858         result.appendLiteral(", ");
859         result.append(m_secondX->cssText());
860         result.append(' ');
861         result.append(m_secondY->cssText());
862         result.appendLiteral(", ");
863         result.append(m_secondRadius->cssText());
864
865         // FIXME: share?
866         for (auto& stop : m_stops) {
867             result.appendLiteral(", ");
868             auto position = stop.m_position->doubleValue(CSSPrimitiveValue::CSS_NUMBER);
869             if (!position) {
870                 result.appendLiteral("from(");
871                 result.append(stop.m_color->cssText());
872                 result.append(')');
873             } else if (position == 1) {
874                 result.appendLiteral("to(");
875                 result.append(stop.m_color->cssText());
876                 result.append(')');
877             } else {
878                 result.appendLiteral("color-stop(");
879                 result.appendNumber(position);
880                 result.appendLiteral(", ");
881                 result.append(stop.m_color->cssText());
882                 result.append(')');
883             }
884         }
885     } else if (m_gradientType == CSSPrefixedRadialGradient) {
886         if (m_repeating)
887             result.appendLiteral("-webkit-repeating-radial-gradient(");
888         else
889             result.appendLiteral("-webkit-radial-gradient(");
890
891         if (m_firstX && m_firstY) {
892             result.append(m_firstX->cssText());
893             result.append(' ');
894             result.append(m_firstY->cssText());
895         } else if (m_firstX)
896             result.append(m_firstX->cssText());
897          else if (m_firstY)
898             result.append(m_firstY->cssText());
899         else
900             result.appendLiteral("center");
901
902         if (m_shape || m_sizingBehavior) {
903             result.appendLiteral(", ");
904             if (m_shape) {
905                 result.append(m_shape->cssText());
906                 result.append(' ');
907             } else
908                 result.appendLiteral("ellipse ");
909
910             if (m_sizingBehavior)
911                 result.append(m_sizingBehavior->cssText());
912             else
913                 result.appendLiteral("cover");
914
915         } else if (m_endHorizontalSize && m_endVerticalSize) {
916             result.appendLiteral(", ");
917             result.append(m_endHorizontalSize->cssText());
918             result.append(' ');
919             result.append(m_endVerticalSize->cssText());
920         }
921
922         for (unsigned i = 0; i < m_stops.size(); i++) {
923             const CSSGradientColorStop& stop = m_stops[i];
924             result.appendLiteral(", ");
925             result.append(stop.m_color->cssText());
926             if (stop.m_position) {
927                 result.append(' ');
928                 result.append(stop.m_position->cssText());
929             }
930         }
931     } else {
932         if (m_repeating)
933             result.appendLiteral("repeating-radial-gradient(");
934         else
935             result.appendLiteral("radial-gradient(");
936
937         bool wroteSomething = false;
938
939         // The only ambiguous case that needs an explicit shape to be provided
940         // is when a sizing keyword is used (or all sizing is omitted).
941         if (m_shape && m_shape->valueID() != CSSValueEllipse && (m_sizingBehavior || (!m_sizingBehavior && !m_endHorizontalSize))) {
942             result.appendLiteral("circle");
943             wroteSomething = true;
944         }
945
946         if (m_sizingBehavior && m_sizingBehavior->valueID() != CSSValueFarthestCorner) {
947             if (wroteSomething)
948                 result.append(' ');
949             result.append(m_sizingBehavior->cssText());
950             wroteSomething = true;
951         } else if (m_endHorizontalSize) {
952             if (wroteSomething)
953                 result.append(' ');
954             result.append(m_endHorizontalSize->cssText());
955             if (m_endVerticalSize) {
956                 result.append(' ');
957                 result.append(m_endVerticalSize->cssText());
958             }
959             wroteSomething = true;
960         }
961
962         if (m_firstX || m_firstY) {
963             if (wroteSomething)
964                 result.append(' ');
965             result.appendLiteral("at ");
966             if (m_firstX && m_firstY) {
967                 result.append(m_firstX->cssText());
968                 result.append(' ');
969                 result.append(m_firstY->cssText());
970             } else if (m_firstX)
971                 result.append(m_firstX->cssText());
972             else
973                 result.append(m_firstY->cssText());
974             wroteSomething = true;
975         }
976
977         if (wroteSomething)
978             result.appendLiteral(", ");
979
980         for (unsigned i = 0; i < m_stops.size(); i++) {
981             const CSSGradientColorStop& stop = m_stops[i];
982             if (i)
983                 result.appendLiteral(", ");
984             if (!stop.isMidpoint)
985                 result.append(stop.m_color->cssText());
986             if (stop.m_position) {
987                 if (!stop.isMidpoint)
988                     result.append(' ');
989                 result.append(stop.m_position->cssText());
990             }
991         }
992
993     }
994
995     result.append(')');
996     return result.toString();
997 }
998
999 float CSSRadialGradientValue::resolveRadius(CSSPrimitiveValue& radius, const CSSToLengthConversionData& conversionData, float* widthOrHeight)
1000 {
1001     float result = 0;
1002     if (radius.isNumber()) // Can the radius be a percentage?
1003         result = radius.floatValue() * conversionData.zoom();
1004     else if (widthOrHeight && radius.isPercentage())
1005         result = *widthOrHeight * radius.floatValue() / 100;
1006     else
1007         result = radius.computeLength<float>(conversionData);
1008
1009     return result;
1010 }
1011
1012 static float distanceToClosestCorner(const FloatPoint& p, const FloatSize& size, FloatPoint& corner)
1013 {
1014     FloatPoint topLeft;
1015     float topLeftDistance = FloatSize(p - topLeft).diagonalLength();
1016
1017     FloatPoint topRight(size.width(), 0);
1018     float topRightDistance = FloatSize(p - topRight).diagonalLength();
1019
1020     FloatPoint bottomLeft(0, size.height());
1021     float bottomLeftDistance = FloatSize(p - bottomLeft).diagonalLength();
1022
1023     FloatPoint bottomRight(size.width(), size.height());
1024     float bottomRightDistance = FloatSize(p - bottomRight).diagonalLength();
1025
1026     corner = topLeft;
1027     float minDistance = topLeftDistance;
1028     if (topRightDistance < minDistance) {
1029         minDistance = topRightDistance;
1030         corner = topRight;
1031     }
1032
1033     if (bottomLeftDistance < minDistance) {
1034         minDistance = bottomLeftDistance;
1035         corner = bottomLeft;
1036     }
1037
1038     if (bottomRightDistance < minDistance) {
1039         minDistance = bottomRightDistance;
1040         corner = bottomRight;
1041     }
1042     return minDistance;
1043 }
1044
1045 static float distanceToFarthestCorner(const FloatPoint& p, const FloatSize& size, FloatPoint& corner)
1046 {
1047     FloatPoint topLeft;
1048     float topLeftDistance = FloatSize(p - topLeft).diagonalLength();
1049
1050     FloatPoint topRight(size.width(), 0);
1051     float topRightDistance = FloatSize(p - topRight).diagonalLength();
1052
1053     FloatPoint bottomLeft(0, size.height());
1054     float bottomLeftDistance = FloatSize(p - bottomLeft).diagonalLength();
1055
1056     FloatPoint bottomRight(size.width(), size.height());
1057     float bottomRightDistance = FloatSize(p - bottomRight).diagonalLength();
1058
1059     corner = topLeft;
1060     float maxDistance = topLeftDistance;
1061     if (topRightDistance > maxDistance) {
1062         maxDistance = topRightDistance;
1063         corner = topRight;
1064     }
1065
1066     if (bottomLeftDistance > maxDistance) {
1067         maxDistance = bottomLeftDistance;
1068         corner = bottomLeft;
1069     }
1070
1071     if (bottomRightDistance > maxDistance) {
1072         maxDistance = bottomRightDistance;
1073         corner = bottomRight;
1074     }
1075     return maxDistance;
1076 }
1077
1078 // Compute horizontal radius of ellipse with center at 0,0 which passes through p, and has
1079 // width/height given by aspectRatio.
1080 static inline float horizontalEllipseRadius(const FloatSize& p, float aspectRatio)
1081 {
1082     // x^2/a^2 + y^2/b^2 = 1
1083     // a/b = aspectRatio, b = a/aspectRatio
1084     // a = sqrt(x^2 + y^2/(1/r^2))
1085     return sqrtf(p.width() * p.width() + (p.height() * p.height()) / (1 / (aspectRatio * aspectRatio)));
1086 }
1087
1088 // FIXME: share code with the linear version
1089 Ref<Gradient> CSSRadialGradientValue::createGradient(RenderElement& renderer, const FloatSize& size)
1090 {
1091     ASSERT(!size.isEmpty());
1092
1093     CSSToLengthConversionData conversionData(&renderer.style(), renderer.document().documentElement()->renderStyle(), &renderer.view());
1094
1095     FloatPoint firstPoint = computeEndPoint(m_firstX.get(), m_firstY.get(), conversionData, size);
1096     if (!m_firstX)
1097         firstPoint.setX(size.width() / 2);
1098     if (!m_firstY)
1099         firstPoint.setY(size.height() / 2);
1100
1101     FloatPoint secondPoint = computeEndPoint(m_secondX.get(), m_secondY.get(), conversionData, size);
1102     if (!m_secondX)
1103         secondPoint.setX(size.width() / 2);
1104     if (!m_secondY)
1105         secondPoint.setY(size.height() / 2);
1106
1107     float firstRadius = 0;
1108     if (m_firstRadius)
1109         firstRadius = resolveRadius(*m_firstRadius, conversionData);
1110
1111     float secondRadius = 0;
1112     float aspectRatio = 1; // width / height.
1113     if (m_secondRadius)
1114         secondRadius = resolveRadius(*m_secondRadius, conversionData);
1115     else if (m_endHorizontalSize) {
1116         float width = size.width();
1117         float height = size.height();
1118         secondRadius = resolveRadius(*m_endHorizontalSize, conversionData, &width);
1119         if (m_endVerticalSize)
1120             aspectRatio = secondRadius / resolveRadius(*m_endVerticalSize, conversionData, &height);
1121         else
1122             aspectRatio = 1;
1123     } else {
1124         enum GradientShape { Circle, Ellipse };
1125         GradientShape shape = Ellipse;
1126         if ((m_shape && m_shape->valueID() == CSSValueCircle)
1127             || (!m_shape && !m_sizingBehavior && m_endHorizontalSize && !m_endVerticalSize))
1128             shape = Circle;
1129
1130         enum GradientFill { ClosestSide, ClosestCorner, FarthestSide, FarthestCorner };
1131         GradientFill fill = FarthestCorner;
1132
1133         switch (m_sizingBehavior ? m_sizingBehavior->valueID() : 0) {
1134         case CSSValueContain:
1135         case CSSValueClosestSide:
1136             fill = ClosestSide;
1137             break;
1138         case CSSValueClosestCorner:
1139             fill = ClosestCorner;
1140             break;
1141         case CSSValueFarthestSide:
1142             fill = FarthestSide;
1143             break;
1144         case CSSValueCover:
1145         case CSSValueFarthestCorner:
1146             fill = FarthestCorner;
1147             break;
1148         default:
1149             break;
1150         }
1151
1152         // Now compute the end radii based on the second point, shape and fill.
1153
1154         // Horizontal
1155         switch (fill) {
1156         case ClosestSide: {
1157             float xDist = std::min(secondPoint.x(), size.width() - secondPoint.x());
1158             float yDist = std::min(secondPoint.y(), size.height() - secondPoint.y());
1159             if (shape == Circle) {
1160                 float smaller = std::min(xDist, yDist);
1161                 xDist = smaller;
1162                 yDist = smaller;
1163             }
1164             secondRadius = xDist;
1165             aspectRatio = xDist / yDist;
1166             break;
1167         }
1168         case FarthestSide: {
1169             float xDist = std::max(secondPoint.x(), size.width() - secondPoint.x());
1170             float yDist = std::max(secondPoint.y(), size.height() - secondPoint.y());
1171             if (shape == Circle) {
1172                 float larger = std::max(xDist, yDist);
1173                 xDist = larger;
1174                 yDist = larger;
1175             }
1176             secondRadius = xDist;
1177             aspectRatio = xDist / yDist;
1178             break;
1179         }
1180         case ClosestCorner: {
1181             FloatPoint corner;
1182             float distance = distanceToClosestCorner(secondPoint, size, corner);
1183             if (shape == Circle)
1184                 secondRadius = distance;
1185             else {
1186                 // If <shape> is ellipse, the gradient-shape has the same ratio of width to height
1187                 // that it would if closest-side or farthest-side were specified, as appropriate.
1188                 float xDist = std::min(secondPoint.x(), size.width() - secondPoint.x());
1189                 float yDist = std::min(secondPoint.y(), size.height() - secondPoint.y());
1190
1191                 secondRadius = horizontalEllipseRadius(corner - secondPoint, xDist / yDist);
1192                 aspectRatio = xDist / yDist;
1193             }
1194             break;
1195         }
1196
1197         case FarthestCorner: {
1198             FloatPoint corner;
1199             float distance = distanceToFarthestCorner(secondPoint, size, corner);
1200             if (shape == Circle)
1201                 secondRadius = distance;
1202             else {
1203                 // If <shape> is ellipse, the gradient-shape has the same ratio of width to height
1204                 // that it would if closest-side or farthest-side were specified, as appropriate.
1205                 float xDist = std::max(secondPoint.x(), size.width() - secondPoint.x());
1206                 float yDist = std::max(secondPoint.y(), size.height() - secondPoint.y());
1207
1208                 secondRadius = horizontalEllipseRadius(corner - secondPoint, xDist / yDist);
1209                 aspectRatio = xDist / yDist;
1210             }
1211             break;
1212         }
1213         }
1214     }
1215
1216     Ref<Gradient> gradient = Gradient::create(firstPoint, firstRadius, secondPoint, secondRadius, aspectRatio);
1217
1218     // addStops() only uses maxExtent for repeating gradients.
1219     float maxExtent = 0;
1220     if (m_repeating) {
1221         FloatPoint corner;
1222         maxExtent = distanceToFarthestCorner(secondPoint, size, corner);
1223     }
1224
1225     // Now add the stops.
1226     addStops(gradient, conversionData, maxExtent);
1227
1228     return gradient;
1229 }
1230
1231 bool CSSRadialGradientValue::equals(const CSSRadialGradientValue& other) const
1232 {
1233     if (m_gradientType == CSSDeprecatedRadialGradient)
1234         return other.m_gradientType == m_gradientType
1235             && compareCSSValuePtr(m_firstX, other.m_firstX)
1236             && compareCSSValuePtr(m_firstY, other.m_firstY)
1237             && compareCSSValuePtr(m_secondX, other.m_secondX)
1238             && compareCSSValuePtr(m_secondY, other.m_secondY)
1239             && compareCSSValuePtr(m_firstRadius, other.m_firstRadius)
1240             && compareCSSValuePtr(m_secondRadius, other.m_secondRadius)
1241             && m_stops == other.m_stops;
1242
1243     if (m_repeating != other.m_repeating)
1244         return false;
1245
1246     bool equalXandY = false;
1247     if (m_firstX && m_firstY)
1248         equalXandY = compareCSSValuePtr(m_firstX, other.m_firstX) && compareCSSValuePtr(m_firstY, other.m_firstY);
1249     else if (m_firstX)
1250         equalXandY = compareCSSValuePtr(m_firstX, other.m_firstX) && !other.m_firstY;
1251     else if (m_firstY)
1252         equalXandY = compareCSSValuePtr(m_firstY, other.m_firstY) && !other.m_firstX;
1253     else
1254         equalXandY = !other.m_firstX && !other.m_firstY;
1255
1256     if (!equalXandY)
1257         return false;
1258
1259     bool equalShape = true;
1260     bool equalSizingBehavior = true;
1261     bool equalHorizontalAndVerticalSize = true;
1262
1263     if (m_shape)
1264         equalShape = compareCSSValuePtr(m_shape, other.m_shape);
1265     else if (m_sizingBehavior)
1266         equalSizingBehavior = compareCSSValuePtr(m_sizingBehavior, other.m_sizingBehavior);
1267     else if (m_endHorizontalSize && m_endVerticalSize)
1268         equalHorizontalAndVerticalSize = compareCSSValuePtr(m_endHorizontalSize, other.m_endHorizontalSize) && compareCSSValuePtr(m_endVerticalSize, other.m_endVerticalSize);
1269     else {
1270         equalShape = !other.m_shape;
1271         equalSizingBehavior = !other.m_sizingBehavior;
1272         equalHorizontalAndVerticalSize = !other.m_endHorizontalSize && !other.m_endVerticalSize;
1273     }
1274     return equalShape && equalSizingBehavior && equalHorizontalAndVerticalSize && m_stops == other.m_stops;
1275 }
1276
1277
1278 String CSSConicGradientValue::customCSSText() const
1279 {
1280     StringBuilder result;
1281
1282     if (m_repeating)
1283         result.appendLiteral("repeating-conic-gradient(");
1284     else
1285         result.appendLiteral("conic-gradient(");
1286
1287     bool wroteSomething = false;
1288
1289     if (m_angle) {
1290         result.appendLiteral("from ");
1291         result.append(m_angle->cssText());
1292         wroteSomething = true;
1293     }
1294
1295     if (m_firstX && m_firstY) {
1296         if (wroteSomething)
1297             result.appendLiteral(" ");
1298         result.appendLiteral("at ");
1299         result.append(m_firstX->cssText());
1300         result.append(' ');
1301         result.append(m_firstY->cssText());
1302         wroteSomething = true;
1303     }
1304
1305     if (wroteSomething)
1306         result.appendLiteral(", ");
1307
1308     bool wroteFirstStop = false;
1309     for (auto& stop : m_stops) {
1310         if (wroteFirstStop)
1311             result.appendLiteral(", ");
1312         wroteFirstStop = true;
1313         if (!stop.isMidpoint)
1314             result.append(stop.m_color->cssText());
1315         if (stop.m_position) {
1316             if (!stop.isMidpoint)
1317                 result.append(' ');
1318             result.append(stop.m_position->cssText());
1319         }
1320     }
1321     
1322     result.append(')');
1323     return result.toString();
1324 }
1325
1326 Ref<Gradient> CSSConicGradientValue::createGradient(RenderElement&, const FloatSize&)
1327 {
1328     // FIXME: Implement.
1329     return Gradient::create(FloatPoint { }, FloatPoint { });
1330 }
1331
1332 bool CSSConicGradientValue::equals(const CSSConicGradientValue& other) const
1333 {
1334     if (m_repeating != other.m_repeating)
1335         return false;
1336
1337     if (!compareCSSValuePtr(m_angle, other.m_angle))
1338         return false;
1339
1340     bool equalXandY = false;
1341     if (m_firstX && m_firstY)
1342         equalXandY = compareCSSValuePtr(m_firstX, other.m_firstX) && compareCSSValuePtr(m_firstY, other.m_firstY);
1343     else if (m_firstX)
1344         equalXandY = compareCSSValuePtr(m_firstX, other.m_firstX) && !other.m_firstY;
1345     else if (m_firstY)
1346         equalXandY = compareCSSValuePtr(m_firstY, other.m_firstY) && !other.m_firstX;
1347     else
1348         equalXandY = !other.m_firstX && !other.m_firstY;
1349
1350     return equalXandY && m_stops == other.m_stops;
1351 }
1352
1353 } // namespace WebCore