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