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