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