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