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