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