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