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