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