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