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