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