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