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