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