Adding occlusion detection to reduce overdraw in RenderBox background rendering
[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 bool CSSGradientValue::hasAlpha(const RenderObject*) const
466 {
467     for (size_t i = 0; i < m_stops.size(); ++i) {
468         if (m_stops[i].m_resolvedColor.hasAlpha())
469             return true;
470     }
471     return false;
472 }
473
474 void CSSGradientValue::reportBaseClassMemoryUsage(MemoryObjectInfo* memoryObjectInfo) const
475 {
476     MemoryClassInfo info(memoryObjectInfo, this, WebCoreMemoryTypes::CSS);
477     CSSImageGeneratorValue::reportBaseClassMemoryUsage(memoryObjectInfo);
478     info.addMember(m_firstX);
479     info.addMember(m_firstY);
480     info.addMember(m_secondX);
481     info.addMember(m_secondY);
482     info.addMember(m_stops);
483 }
484
485 String CSSLinearGradientValue::customCssText() const
486 {
487     StringBuilder result;
488     if (m_deprecatedType) {
489         result.appendLiteral("-webkit-gradient(linear, ");
490         result.append(m_firstX->cssText());
491         result.append(' ');
492         result.append(m_firstY->cssText());
493         result.appendLiteral(", ");
494         result.append(m_secondX->cssText());
495         result.append(' ');
496         result.append(m_secondY->cssText());
497
498         for (unsigned i = 0; i < m_stops.size(); i++) {
499             const CSSGradientColorStop& stop = m_stops[i];
500             result.appendLiteral(", ");
501             if (stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER) == 0) {
502                 result.appendLiteral("from(");
503                 result.append(stop.m_color->cssText());
504                 result.append(')');
505             } else if (stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER) == 1) {
506                 result.appendLiteral("to(");
507                 result.append(stop.m_color->cssText());
508                 result.append(')');
509             } else {
510                 result.appendLiteral("color-stop(");
511                 result.append(String::number(stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER)));
512                 result.appendLiteral(", ");
513                 result.append(stop.m_color->cssText());
514                 result.append(')');
515             }
516         }
517     } else {
518         if (m_repeating)
519             result.appendLiteral("-webkit-repeating-linear-gradient(");
520         else
521             result.appendLiteral("-webkit-linear-gradient(");
522
523         if (m_angle)
524             result.append(m_angle->cssText());
525         else {
526             if (m_firstX && m_firstY) {
527                 result.append(m_firstX->cssText());
528                 result.append(' ');
529                 result.append(m_firstY->cssText());
530             } else if (m_firstX || m_firstY) {
531                 if (m_firstX)
532                     result.append(m_firstX->cssText());
533
534                 if (m_firstY)
535                     result.append(m_firstY->cssText());
536             }
537         }
538
539         for (unsigned i = 0; i < m_stops.size(); i++) {
540             const CSSGradientColorStop& stop = m_stops[i];
541             result.appendLiteral(", ");
542             result.append(stop.m_color->cssText());
543             if (stop.m_position) {
544                 result.append(' ');
545                 result.append(stop.m_position->cssText());
546             }
547         }
548     }
549
550     result.append(')');
551     return result.toString();
552 }
553
554 // Compute the endpoints so that a gradient of the given angle covers a box of the given size.
555 static void endPointsFromAngle(float angleDeg, const IntSize& size, FloatPoint& firstPoint, FloatPoint& secondPoint)
556 {
557     angleDeg = fmodf(angleDeg, 360);
558     if (angleDeg < 0)
559         angleDeg += 360;
560
561     if (!angleDeg) {
562         firstPoint.set(0, 0);
563         secondPoint.set(size.width(), 0);
564         return;
565     }
566
567     if (angleDeg == 90) {
568         firstPoint.set(0, size.height());
569         secondPoint.set(0, 0);
570         return;
571     }
572
573     if (angleDeg == 180) {
574         firstPoint.set(size.width(), 0);
575         secondPoint.set(0, 0);
576         return;
577     }
578
579     if (angleDeg == 270) {
580         firstPoint.set(0, 0);
581         secondPoint.set(0, size.height());
582         return;
583     }
584
585     float slope = tan(deg2rad(angleDeg));
586
587     // We find the endpoint by computing the intersection of the line formed by the slope,
588     // and a line perpendicular to it that intersects the corner.
589     float perpendicularSlope = -1 / slope;
590
591     // Compute start corner relative to center.
592     float halfHeight = size.height() / 2;
593     float halfWidth = size.width() / 2;
594     FloatPoint endCorner;
595     if (angleDeg < 90)
596         endCorner.set(halfWidth, halfHeight);
597     else if (angleDeg < 180)
598         endCorner.set(-halfWidth, halfHeight);
599     else if (angleDeg < 270)
600         endCorner.set(-halfWidth, -halfHeight);
601     else
602         endCorner.set(halfWidth, -halfHeight);
603
604     // Compute c (of y = mx + c) using the corner point.
605     float c = endCorner.y() - perpendicularSlope * endCorner.x();
606     float endX = c / (slope - perpendicularSlope);
607     float endY = perpendicularSlope * endX + c;
608
609     // We computed the end point, so set the second point, flipping the Y to account for angles going anticlockwise.
610     secondPoint.set(halfWidth + endX, size.height() - (halfHeight + endY));
611     // Reflect around the center for the start point.
612     firstPoint.set(size.width() - secondPoint.x(), size.height() - secondPoint.y());
613 }
614
615 PassRefPtr<Gradient> CSSLinearGradientValue::createGradient(RenderObject* renderer, const IntSize& size)
616 {
617     ASSERT(!size.isEmpty());
618
619     RenderStyle* rootStyle = renderer->document()->documentElement()->renderStyle();
620
621     FloatPoint firstPoint;
622     FloatPoint secondPoint;
623     if (m_angle) {
624         float angle = m_angle->getFloatValue(CSSPrimitiveValue::CSS_DEG);
625         endPointsFromAngle(angle, size, firstPoint, secondPoint);
626     } else {
627         firstPoint = computeEndPoint(m_firstX.get(), m_firstY.get(), renderer->style(), rootStyle, size);
628
629         if (m_secondX || m_secondY)
630             secondPoint = computeEndPoint(m_secondX.get(), m_secondY.get(), renderer->style(), rootStyle, size);
631         else {
632             if (m_firstX)
633                 secondPoint.setX(size.width() - firstPoint.x());
634             if (m_firstY)
635                 secondPoint.setY(size.height() - firstPoint.y());
636         }
637     }
638
639     RefPtr<Gradient> gradient = Gradient::create(firstPoint, secondPoint);
640
641     // Now add the stops.
642     addStops(gradient.get(), renderer, rootStyle, 1);
643
644     return gradient.release();
645 }
646
647 void CSSLinearGradientValue::reportDescendantMemoryUsage(MemoryObjectInfo* memoryObjectInfo) const
648 {
649     MemoryClassInfo info(memoryObjectInfo, this, WebCoreMemoryTypes::CSS);
650     CSSGradientValue::reportBaseClassMemoryUsage(memoryObjectInfo);
651     info.addMember(m_angle);
652 }
653
654 String CSSRadialGradientValue::customCssText() const
655 {
656     StringBuilder result;
657
658     if (m_deprecatedType) {
659         result.appendLiteral("-webkit-gradient(radial, ");
660         result.append(m_firstX->cssText());
661         result.append(' ');
662         result.append(m_firstY->cssText());
663         result.appendLiteral(", ");
664         result.append(m_firstRadius->cssText());
665         result.appendLiteral(", ");
666         result.append(m_secondX->cssText());
667         result.append(' ');
668         result.append(m_secondY->cssText());
669         result.appendLiteral(", ");
670         result.append(m_secondRadius->cssText());
671
672         // FIXME: share?
673         for (unsigned i = 0; i < m_stops.size(); i++) {
674             const CSSGradientColorStop& stop = m_stops[i];
675             result.appendLiteral(", ");
676             if (stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER) == 0) {
677                 result.appendLiteral("from(");
678                 result.append(stop.m_color->cssText());
679                 result.append(')');
680             } else if (stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER) == 1) {
681                 result.appendLiteral("to(");
682                 result.append(stop.m_color->cssText());
683                 result.append(')');
684             } else {
685                 result.appendLiteral("color-stop(");
686                 result.append(String::number(stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER)));
687                 result.appendLiteral(", ");
688                 result.append(stop.m_color->cssText());
689                 result.append(')');
690             }
691         }
692     } else {
693         if (m_repeating)
694             result.appendLiteral("-webkit-repeating-radial-gradient(");
695         else
696             result.appendLiteral("-webkit-radial-gradient(");
697
698         if (m_firstX && m_firstY) {
699             result.append(m_firstX->cssText());
700             result.append(' ');
701             result.append(m_firstY->cssText());
702         } else if (m_firstX)
703             result.append(m_firstX->cssText());
704          else if (m_firstY)
705             result.append(m_firstY->cssText());
706         else
707             result.appendLiteral("center");
708
709         if (m_shape || m_sizingBehavior) {
710             result.appendLiteral(", ");
711             if (m_shape) {
712                 result.append(m_shape->cssText());
713                 result.append(' ');
714             } else
715                 result.appendLiteral("ellipse ");
716
717             if (m_sizingBehavior)
718                 result.append(m_sizingBehavior->cssText());
719             else
720                 result.appendLiteral("cover");
721
722         } else if (m_endHorizontalSize && m_endVerticalSize) {
723             result.appendLiteral(", ");
724             result.append(m_endHorizontalSize->cssText());
725             result.append(' ');
726             result.append(m_endVerticalSize->cssText());
727         }
728
729         for (unsigned i = 0; i < m_stops.size(); i++) {
730             const CSSGradientColorStop& stop = m_stops[i];
731             result.appendLiteral(", ");
732             result.append(stop.m_color->cssText());
733             if (stop.m_position) {
734                 result.append(' ');
735                 result.append(stop.m_position->cssText());
736             }
737         }
738     }
739
740     result.append(')');
741     return result.toString();
742 }
743
744 float CSSRadialGradientValue::resolveRadius(CSSPrimitiveValue* radius, RenderStyle* style, RenderStyle* rootStyle, float* widthOrHeight)
745 {
746     float zoomFactor = style->effectiveZoom();
747
748     float result = 0;
749     if (radius->isNumber()) // Can the radius be a percentage?
750         result = radius->getFloatValue() * zoomFactor;
751     else if (widthOrHeight && radius->isPercentage())
752         result = *widthOrHeight * radius->getFloatValue() / 100;
753     else
754         result = radius->computeLength<float>(style, rootStyle, zoomFactor);
755
756     return result;
757 }
758
759 static float distanceToClosestCorner(const FloatPoint& p, const FloatSize& size, FloatPoint& corner)
760 {
761     FloatPoint topLeft;
762     float topLeftDistance = FloatSize(p - topLeft).diagonalLength();
763
764     FloatPoint topRight(size.width(), 0);
765     float topRightDistance = FloatSize(p - topRight).diagonalLength();
766
767     FloatPoint bottomLeft(0, size.height());
768     float bottomLeftDistance = FloatSize(p - bottomLeft).diagonalLength();
769
770     FloatPoint bottomRight(size.width(), size.height());
771     float bottomRightDistance = FloatSize(p - bottomRight).diagonalLength();
772
773     corner = topLeft;
774     float minDistance = topLeftDistance;
775     if (topRightDistance < minDistance) {
776         minDistance = topRightDistance;
777         corner = topRight;
778     }
779
780     if (bottomLeftDistance < minDistance) {
781         minDistance = bottomLeftDistance;
782         corner = bottomLeft;
783     }
784
785     if (bottomRightDistance < minDistance) {
786         minDistance = bottomRightDistance;
787         corner = bottomRight;
788     }
789     return minDistance;
790 }
791
792 static float distanceToFarthestCorner(const FloatPoint& p, const FloatSize& size, FloatPoint& corner)
793 {
794     FloatPoint topLeft;
795     float topLeftDistance = FloatSize(p - topLeft).diagonalLength();
796
797     FloatPoint topRight(size.width(), 0);
798     float topRightDistance = FloatSize(p - topRight).diagonalLength();
799
800     FloatPoint bottomLeft(0, size.height());
801     float bottomLeftDistance = FloatSize(p - bottomLeft).diagonalLength();
802
803     FloatPoint bottomRight(size.width(), size.height());
804     float bottomRightDistance = FloatSize(p - bottomRight).diagonalLength();
805
806     corner = topLeft;
807     float maxDistance = topLeftDistance;
808     if (topRightDistance > maxDistance) {
809         maxDistance = topRightDistance;
810         corner = topRight;
811     }
812
813     if (bottomLeftDistance > maxDistance) {
814         maxDistance = bottomLeftDistance;
815         corner = bottomLeft;
816     }
817
818     if (bottomRightDistance > maxDistance) {
819         maxDistance = bottomRightDistance;
820         corner = bottomRight;
821     }
822     return maxDistance;
823 }
824
825 // Compute horizontal radius of ellipse with center at 0,0 which passes through p, and has
826 // width/height given by aspectRatio.
827 static inline float horizontalEllipseRadius(const FloatSize& p, float aspectRatio)
828 {
829     // x^2/a^2 + y^2/b^2 = 1
830     // a/b = aspectRatio, b = a/aspectRatio
831     // a = sqrt(x^2 + y^2/(1/r^2))
832     return sqrtf(p.width() * p.width() + (p.height() * p.height()) / (1 / (aspectRatio * aspectRatio)));
833 }
834
835 // FIXME: share code with the linear version
836 PassRefPtr<Gradient> CSSRadialGradientValue::createGradient(RenderObject* renderer, const IntSize& size)
837 {
838     ASSERT(!size.isEmpty());
839
840     RenderStyle* rootStyle = renderer->document()->documentElement()->renderStyle();
841
842     FloatPoint firstPoint = computeEndPoint(m_firstX.get(), m_firstY.get(), renderer->style(), rootStyle, size);
843     if (!m_firstX)
844         firstPoint.setX(size.width() / 2);
845     if (!m_firstY)
846         firstPoint.setY(size.height() / 2);
847
848     FloatPoint secondPoint = computeEndPoint(m_secondX.get(), m_secondY.get(), renderer->style(), rootStyle, size);
849     if (!m_secondX)
850         secondPoint.setX(size.width() / 2);
851     if (!m_secondY)
852         secondPoint.setY(size.height() / 2);
853
854     float firstRadius = 0;
855     if (m_firstRadius)
856         firstRadius = resolveRadius(m_firstRadius.get(), renderer->style(), rootStyle);
857
858     float secondRadius = 0;
859     float aspectRatio = 1; // width / height.
860     if (m_secondRadius)
861         secondRadius = resolveRadius(m_secondRadius.get(), renderer->style(), rootStyle);
862     else if (m_endHorizontalSize || m_endVerticalSize) {
863         float width = size.width();
864         float height = size.height();
865         secondRadius = resolveRadius(m_endHorizontalSize.get(), renderer->style(), rootStyle, &width);
866         aspectRatio = secondRadius / resolveRadius(m_endVerticalSize.get(), renderer->style(), rootStyle, &height);
867     } else {
868         enum GradientShape { Circle, Ellipse };
869         GradientShape shape = Ellipse;
870         if (m_shape && m_shape->getIdent() == CSSValueCircle)
871             shape = Circle;
872
873         enum GradientFill { ClosestSide, ClosestCorner, FarthestSide, FarthestCorner };
874         GradientFill fill = FarthestCorner;
875
876         switch (m_sizingBehavior ? m_sizingBehavior->getIdent() : 0) {
877         case CSSValueContain:
878         case CSSValueClosestSide:
879             fill = ClosestSide;
880             break;
881         case CSSValueClosestCorner:
882             fill = ClosestCorner;
883             break;
884         case CSSValueFarthestSide:
885             fill = FarthestSide;
886             break;
887         case CSSValueCover:
888         case CSSValueFarthestCorner:
889             fill = FarthestCorner;
890             break;
891         }
892
893         // Now compute the end radii based on the second point, shape and fill.
894
895         // Horizontal
896         switch (fill) {
897         case ClosestSide: {
898             float xDist = min(secondPoint.x(), size.width() - secondPoint.x());
899             float yDist = min(secondPoint.y(), size.height() - secondPoint.y());
900             if (shape == Circle) {
901                 float smaller = min(xDist, yDist);
902                 xDist = smaller;
903                 yDist = smaller;
904             }
905             secondRadius = xDist;
906             aspectRatio = xDist / yDist;
907             break;
908         }
909         case FarthestSide: {
910             float xDist = max(secondPoint.x(), size.width() - secondPoint.x());
911             float yDist = max(secondPoint.y(), size.height() - secondPoint.y());
912             if (shape == Circle) {
913                 float larger = max(xDist, yDist);
914                 xDist = larger;
915                 yDist = larger;
916             }
917             secondRadius = xDist;
918             aspectRatio = xDist / yDist;
919             break;
920         }
921         case ClosestCorner: {
922             FloatPoint corner;
923             float distance = distanceToClosestCorner(secondPoint, size, corner);
924             if (shape == Circle)
925                 secondRadius = distance;
926             else {
927                 // If <shape> is ellipse, the gradient-shape has the same ratio of width to height
928                 // that it would if closest-side or farthest-side were specified, as appropriate.
929                 float xDist = min(secondPoint.x(), size.width() - secondPoint.x());
930                 float yDist = min(secondPoint.y(), size.height() - secondPoint.y());
931
932                 secondRadius = horizontalEllipseRadius(corner - secondPoint, xDist / yDist);
933                 aspectRatio = xDist / yDist;
934             }
935             break;
936         }
937
938         case FarthestCorner: {
939             FloatPoint corner;
940             float distance = distanceToFarthestCorner(secondPoint, size, corner);
941             if (shape == Circle)
942                 secondRadius = distance;
943             else {
944                 // If <shape> is ellipse, the gradient-shape has the same ratio of width to height
945                 // that it would if closest-side or farthest-side were specified, as appropriate.
946                 float xDist = max(secondPoint.x(), size.width() - secondPoint.x());
947                 float yDist = max(secondPoint.y(), size.height() - secondPoint.y());
948
949                 secondRadius = horizontalEllipseRadius(corner - secondPoint, xDist / yDist);
950                 aspectRatio = xDist / yDist;
951             }
952             break;
953         }
954         }
955     }
956
957     RefPtr<Gradient> gradient = Gradient::create(firstPoint, firstRadius, secondPoint, secondRadius, aspectRatio);
958
959     // addStops() only uses maxExtent for repeating gradients.
960     float maxExtent = 0;
961     if (m_repeating) {
962         FloatPoint corner;
963         maxExtent = distanceToFarthestCorner(secondPoint, size, corner);
964     }
965
966     // Now add the stops.
967     addStops(gradient.get(), renderer, rootStyle, maxExtent);
968
969     return gradient.release();
970 }
971
972 void CSSRadialGradientValue::reportDescendantMemoryUsage(MemoryObjectInfo* memoryObjectInfo) const
973 {
974     MemoryClassInfo info(memoryObjectInfo, this, WebCoreMemoryTypes::CSS);
975     CSSGradientValue::reportBaseClassMemoryUsage(memoryObjectInfo);
976     info.addMember(m_firstRadius);
977     info.addMember(m_secondRadius);
978     info.addMember(m_shape);
979     info.addMember(m_sizingBehavior);
980     info.addMember(m_endHorizontalSize);
981     info.addMember(m_endVerticalSize);
982 }
983
984 } // namespace WebCore