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