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