1 /*
2 * Copyright (C) 2008 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26 #include "config.h"
27 #include "CSSGradientValue.h"
28
29 #include "CSSValueKeywords.h"
30 #include "CSSStyleSelector.h"
31 #include "GeneratedImage.h"
32 #include "Gradient.h"
33 #include "Image.h"
34 #include "IntSize.h"
35 #include "IntSizeHash.h"
36 #include "NodeRenderStyle.h"
37 #include "PlatformString.h"
38 #include "RenderObject.h"
39
40 using namespace std;
41
42 namespace WebCore {
43
image(RenderObject * renderer,const IntSize & size)44 PassRefPtr<Image> CSSGradientValue::image(RenderObject* renderer, const IntSize& size)
45 {
46 if (size.isEmpty())
47 return 0;
48
49 bool cacheable = isCacheable();
50 if (cacheable) {
51 if (!m_clients.contains(renderer))
52 return 0;
53
54 // Need to look up our size. Create a string of width*height to use as a hash key.
55 Image* result = getImage(renderer, size);
56 if (result)
57 return result;
58 }
59
60 // We need to create an image.
61 RefPtr<Image> newImage = GeneratedImage::create(createGradient(renderer, size), size);
62 if (cacheable)
63 putImage(size, newImage);
64
65 return newImage.release();
66 }
67
68 // Should only ever be called for deprecated gradients.
compareStops(const CSSGradientColorStop & a,const CSSGradientColorStop & b)69 static inline bool compareStops(const CSSGradientColorStop& a, const CSSGradientColorStop& b)
70 {
71 double aVal = a.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER);
72 double bVal = b.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER);
73
74 return aVal < bVal;
75 }
76
sortStopsIfNeeded()77 void CSSGradientValue::sortStopsIfNeeded()
78 {
79 ASSERT(m_deprecatedType);
80 if (!m_stopsSorted) {
81 if (m_stops.size())
82 std::stable_sort(m_stops.begin(), m_stops.end(), compareStops);
83 m_stopsSorted = true;
84 }
85 }
86
blend(int from,int to,float progress)87 static inline int blend(int from, int to, float progress)
88 {
89 return int(from + (to - from) * progress);
90 }
91
blend(const Color & from,const Color & to,float progress)92 static inline Color blend(const Color& from, const Color& to, float progress)
93 {
94 // FIXME: when we interpolate gradients using premultiplied colors, this should also do premultiplication.
95 return Color(blend(from.red(), to.red(), progress),
96 blend(from.green(), to.green(), progress),
97 blend(from.blue(), to.blue(), progress),
98 blend(from.alpha(), to.alpha(), progress));
99 }
100
101 struct GradientStop {
102 Color color;
103 float offset;
104 bool specified;
105
GradientStopWebCore::GradientStop106 GradientStop()
107 : offset(0)
108 , specified(false)
109 { }
110 };
111
addStops(Gradient * gradient,RenderObject * renderer,RenderStyle * rootStyle,float maxLengthForRepeat)112 void CSSGradientValue::addStops(Gradient* gradient, RenderObject* renderer, RenderStyle* rootStyle, float maxLengthForRepeat)
113 {
114 RenderStyle* style = renderer->style();
115
116 if (m_deprecatedType) {
117 sortStopsIfNeeded();
118
119 // We have to resolve colors.
120 for (unsigned i = 0; i < m_stops.size(); i++) {
121 const CSSGradientColorStop& stop = m_stops[i];
122 Color color = renderer->document()->styleSelector()->getColorFromPrimitiveValue(stop.m_color.get());
123
124 float offset;
125 if (stop.m_position->primitiveType() == CSSPrimitiveValue::CSS_PERCENTAGE)
126 offset = stop.m_position->getFloatValue(CSSPrimitiveValue::CSS_PERCENTAGE) / 100;
127 else
128 offset = stop.m_position->getFloatValue(CSSPrimitiveValue::CSS_NUMBER);
129
130 gradient->addColorStop(offset, color);
131 }
132
133 // The back end already sorted the stops.
134 gradient->setStopsSorted(true);
135 return;
136 }
137
138 size_t numStops = m_stops.size();
139
140 Vector<GradientStop> stops(numStops);
141
142 float gradientLength = 0;
143 bool computedGradientLength = false;
144
145 FloatPoint gradientStart = gradient->p0();
146 FloatPoint gradientEnd;
147 if (isLinearGradient())
148 gradientEnd = gradient->p1();
149 else if (isRadialGradient())
150 gradientEnd = gradientStart + FloatSize(gradient->endRadius(), 0);
151
152 for (size_t i = 0; i < numStops; ++i) {
153 const CSSGradientColorStop& stop = m_stops[i];
154
155 stops[i].color = renderer->document()->styleSelector()->getColorFromPrimitiveValue(stop.m_color.get());
156
157 if (stop.m_position) {
158 int type = stop.m_position->primitiveType();
159 if (type == CSSPrimitiveValue::CSS_PERCENTAGE)
160 stops[i].offset = stop.m_position->getFloatValue(CSSPrimitiveValue::CSS_PERCENTAGE) / 100;
161 else if (CSSPrimitiveValue::isUnitTypeLength(type)) {
162 float length = stop.m_position->computeLengthFloat(style, rootStyle, style->effectiveZoom());
163 if (!computedGradientLength) {
164 FloatSize gradientSize(gradientStart - gradientEnd);
165 gradientLength = gradientSize.diagonalLength();
166 }
167 stops[i].offset = (gradientLength > 0) ? length / gradientLength : 0;
168 } else {
169 ASSERT_NOT_REACHED();
170 stops[i].offset = 0;
171 }
172 stops[i].specified = true;
173 } else {
174 // If the first color-stop does not have a position, its position defaults to 0%.
175 // If the last color-stop does not have a position, its position defaults to 100%.
176 if (!i) {
177 stops[i].offset = 0;
178 stops[i].specified = true;
179 } else if (numStops > 1 && i == numStops - 1) {
180 stops[i].offset = 1;
181 stops[i].specified = true;
182 }
183 }
184
185 // If a color-stop has a position that is less than the specified position of any
186 // color-stop before it in the list, its position is changed to be equal to the
187 // largest specified position of any color-stop before it.
188 if (stops[i].specified && i > 0) {
189 size_t prevSpecifiedIndex;
190 for (prevSpecifiedIndex = i - 1; prevSpecifiedIndex; --prevSpecifiedIndex) {
191 if (stops[prevSpecifiedIndex].specified)
192 break;
193 }
194
195 if (stops[i].offset < stops[prevSpecifiedIndex].offset)
196 stops[i].offset = stops[prevSpecifiedIndex].offset;
197 }
198 }
199
200 ASSERT(stops[0].specified && stops[numStops - 1].specified);
201
202 // If any color-stop still does not have a position, then, for each run of adjacent
203 // color-stops without positions, set their positions so that they are evenly spaced
204 // between the preceding and following color-stops with positions.
205 if (numStops > 2) {
206 size_t unspecifiedRunStart = 0;
207 bool inUnspecifiedRun = false;
208
209 for (size_t i = 0; i < numStops; ++i) {
210 if (!stops[i].specified && !inUnspecifiedRun) {
211 unspecifiedRunStart = i;
212 inUnspecifiedRun = true;
213 } else if (stops[i].specified && inUnspecifiedRun) {
214 size_t unspecifiedRunEnd = i;
215
216 if (unspecifiedRunStart < unspecifiedRunEnd) {
217 float lastSpecifiedOffset = stops[unspecifiedRunStart - 1].offset;
218 float nextSpecifiedOffset = stops[unspecifiedRunEnd].offset;
219 float delta = (nextSpecifiedOffset - lastSpecifiedOffset) / (unspecifiedRunEnd - unspecifiedRunStart + 1);
220
221 for (size_t j = unspecifiedRunStart; j < unspecifiedRunEnd; ++j)
222 stops[j].offset = lastSpecifiedOffset + (j - unspecifiedRunStart + 1) * delta;
223 }
224
225 inUnspecifiedRun = false;
226 }
227 }
228 }
229
230 // If the gradient is repeating, repeat the color stops.
231 // We can't just push this logic down into the platform-specific Gradient code,
232 // because we have to know the extent of the gradient, and possible move the end points.
233 if (m_repeating && numStops > 1) {
234 // If the difference in the positions of the first and last color-stops is 0,
235 // the gradient defines a solid-color image with the color of the last color-stop in the rule.
236 float gradientRange = stops[numStops - 1].offset - stops[0].offset;
237 if (!gradientRange) {
238 stops.first().offset = 0;
239 stops.first().color = stops.last().color;
240 stops.shrink(1);
241 numStops = 1;
242 } else {
243 float maxExtent = 1;
244
245 // Radial gradients may need to extend further than the endpoints, because they have
246 // to repeat out to the corners of the box.
247 if (isRadialGradient()) {
248 if (!computedGradientLength) {
249 FloatSize gradientSize(gradientStart - gradientEnd);
250 gradientLength = gradientSize.diagonalLength();
251 }
252
253 if (maxLengthForRepeat > gradientLength)
254 maxExtent = maxLengthForRepeat / gradientLength;
255 }
256
257 size_t originalNumStops = numStops;
258 size_t originalFirstStopIndex = 0;
259
260 // Work backwards from the first, adding stops until we get one before 0.
261 float firstOffset = stops[0].offset;
262 if (firstOffset > 0) {
263 float currOffset = firstOffset;
264 size_t srcStopOrdinal = originalNumStops - 1;
265
266 while (true) {
267 GradientStop newStop = stops[originalFirstStopIndex + srcStopOrdinal];
268 newStop.offset = currOffset;
269 stops.prepend(newStop);
270 ++originalFirstStopIndex;
271 if (currOffset < 0)
272 break;
273
274 if (srcStopOrdinal)
275 currOffset -= stops[originalFirstStopIndex + srcStopOrdinal].offset - stops[originalFirstStopIndex + srcStopOrdinal - 1].offset;
276 srcStopOrdinal = (srcStopOrdinal + originalNumStops - 1) % originalNumStops;
277 }
278 }
279
280 // Work forwards from the end, adding stops until we get one after 1.
281 float lastOffset = stops[stops.size() - 1].offset;
282 if (lastOffset < maxExtent) {
283 float currOffset = lastOffset;
284 size_t srcStopOrdinal = originalFirstStopIndex;
285
286 while (true) {
287 GradientStop newStop = stops[srcStopOrdinal];
288 newStop.offset = currOffset;
289 stops.append(newStop);
290 if (currOffset > maxExtent)
291 break;
292 if (srcStopOrdinal < originalNumStops - 1)
293 currOffset += stops[srcStopOrdinal + 1].offset - stops[srcStopOrdinal].offset;
294 srcStopOrdinal = (srcStopOrdinal + 1) % originalNumStops;
295 }
296 }
297 }
298 }
299
300 numStops = stops.size();
301
302 // If the gradient goes outside the 0-1 range, normalize it by moving the endpoints, and adjusting the stops.
303 if (numStops > 1 && (stops[0].offset < 0 || stops[numStops - 1].offset > 1)) {
304 if (isLinearGradient()) {
305 float firstOffset = stops[0].offset;
306 float lastOffset = stops[numStops - 1].offset;
307 float scale = lastOffset - firstOffset;
308
309 for (size_t i = 0; i < numStops; ++i)
310 stops[i].offset = (stops[i].offset - firstOffset) / scale;
311
312 FloatPoint p0 = gradient->p0();
313 FloatPoint p1 = gradient->p1();
314 gradient->setP0(FloatPoint(p0.x() + firstOffset * (p1.x() - p0.x()), p0.y() + firstOffset * (p1.y() - p0.y())));
315 gradient->setP1(FloatPoint(p1.x() + (lastOffset - 1) * (p1.x() - p0.x()), p1.y() + (lastOffset - 1) * (p1.y() - p0.y())));
316 } else if (isRadialGradient()) {
317 // Rather than scaling the points < 0, we truncate them, so only scale according to the largest point.
318 float firstOffset = 0;
319 float lastOffset = stops[numStops - 1].offset;
320 float scale = lastOffset - firstOffset;
321
322 // Reset points below 0 to the first visible color.
323 size_t firstZeroOrGreaterIndex = numStops;
324 for (size_t i = 0; i < numStops; ++i) {
325 if (stops[i].offset >= 0) {
326 firstZeroOrGreaterIndex = i;
327 break;
328 }
329 }
330
331 if (firstZeroOrGreaterIndex > 0) {
332 if (firstZeroOrGreaterIndex < numStops && stops[firstZeroOrGreaterIndex].offset > 0) {
333 float prevOffset = stops[firstZeroOrGreaterIndex - 1].offset;
334 float nextOffset = stops[firstZeroOrGreaterIndex].offset;
335
336 float interStopProportion = -prevOffset / (nextOffset - prevOffset);
337 Color blendedColor = blend(stops[firstZeroOrGreaterIndex - 1].color, stops[firstZeroOrGreaterIndex].color, interStopProportion);
338
339 // Clamp the positions to 0 and set the color.
340 for (size_t i = 0; i < firstZeroOrGreaterIndex; ++i) {
341 stops[i].offset = 0;
342 stops[i].color = blendedColor;
343 }
344 } else {
345 // All stops are below 0; just clamp them.
346 for (size_t i = 0; i < firstZeroOrGreaterIndex; ++i)
347 stops[i].offset = 0;
348 }
349 }
350
351 for (size_t i = 0; i < numStops; ++i)
352 stops[i].offset /= scale;
353
354 gradient->setStartRadius(gradient->startRadius() * scale);
355 gradient->setEndRadius(gradient->endRadius() * scale);
356 }
357 }
358
359 for (unsigned i = 0; i < numStops; i++)
360 gradient->addColorStop(stops[i].offset, stops[i].color);
361
362 gradient->setStopsSorted(true);
363 }
364
positionFromValue(CSSPrimitiveValue * value,RenderStyle * style,RenderStyle * rootStyle,const IntSize & size,bool isHorizontal)365 static float positionFromValue(CSSPrimitiveValue* value, RenderStyle* style, RenderStyle* rootStyle, const IntSize& size, bool isHorizontal)
366 {
367 float zoomFactor = style->effectiveZoom();
368
369 switch (value->primitiveType()) {
370 case CSSPrimitiveValue::CSS_NUMBER:
371 return value->getFloatValue() * zoomFactor;
372
373 case CSSPrimitiveValue::CSS_PERCENTAGE:
374 return value->getFloatValue() / 100.f * (isHorizontal ? size.width() : size.height());
375
376 case CSSPrimitiveValue::CSS_IDENT:
377 switch (value->getIdent()) {
378 case CSSValueTop:
379 ASSERT(!isHorizontal);
380 return 0;
381 case CSSValueLeft:
382 ASSERT(isHorizontal);
383 return 0;
384 case CSSValueBottom:
385 ASSERT(!isHorizontal);
386 return size.height();
387 case CSSValueRight:
388 ASSERT(isHorizontal);
389 return size.width();
390 }
391
392 default:
393 return value->computeLengthFloat(style, rootStyle, zoomFactor);
394 }
395 }
396
computeEndPoint(CSSPrimitiveValue * first,CSSPrimitiveValue * second,RenderStyle * style,RenderStyle * rootStyle,const IntSize & size)397 FloatPoint CSSGradientValue::computeEndPoint(CSSPrimitiveValue* first, CSSPrimitiveValue* second, RenderStyle* style, RenderStyle* rootStyle, const IntSize& size)
398 {
399 FloatPoint result;
400
401 if (first)
402 result.setX(positionFromValue(first, style, rootStyle, size, true));
403
404 if (second)
405 result.setY(positionFromValue(second, style, rootStyle, size, false));
406
407 return result;
408 }
409
isCacheable() const410 bool CSSGradientValue::isCacheable() const
411 {
412 for (size_t i = 0; i < m_stops.size(); ++i) {
413 const CSSGradientColorStop& stop = m_stops[i];
414 if (!stop.m_position)
415 continue;
416
417 unsigned short unitType = stop.m_position->primitiveType();
418 if (unitType == CSSPrimitiveValue::CSS_EMS || unitType == CSSPrimitiveValue::CSS_EXS || unitType == CSSPrimitiveValue::CSS_REMS)
419 return false;
420 }
421
422 return true;
423 }
424
cssText() const425 String CSSLinearGradientValue::cssText() const
426 {
427 String result;
428 if (m_deprecatedType) {
429 result = "-webkit-gradient(linear, ";
430 result += m_firstX->cssText() + " ";
431 result += m_firstY->cssText() + ", ";
432 result += m_secondX->cssText() + " ";
433 result += m_secondY->cssText();
434
435 for (unsigned i = 0; i < m_stops.size(); i++) {
436 const CSSGradientColorStop& stop = m_stops[i];
437 result += ", ";
438 if (stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER) == 0)
439 result += "from(" + stop.m_color->cssText() + ")";
440 else if (stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER) == 1)
441 result += "to(" + stop.m_color->cssText() + ")";
442 else
443 result += "color-stop(" + String::number(stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER)) + ", " + stop.m_color->cssText() + ")";
444 }
445 } else {
446 result = m_repeating ? "-webkit-repeating-linear-gradient(" : "-webkit-linear-gradient(";
447 if (m_angle)
448 result += m_angle->cssText();
449 else {
450 if (m_firstX && m_firstY)
451 result += m_firstX->cssText() + " " + m_firstY->cssText();
452 else if (m_firstX || m_firstY) {
453 if (m_firstX)
454 result += m_firstX->cssText();
455
456 if (m_firstY)
457 result += m_firstY->cssText();
458 }
459 }
460
461 for (unsigned i = 0; i < m_stops.size(); i++) {
462 const CSSGradientColorStop& stop = m_stops[i];
463 result += ", ";
464 result += stop.m_color->cssText();
465 if (stop.m_position)
466 result += " " + stop.m_position->cssText();
467 }
468 }
469
470 result += ")";
471 return result;
472 }
473
474 // 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)475 static void endPointsFromAngle(float angleDeg, const IntSize& size, FloatPoint& firstPoint, FloatPoint& secondPoint)
476 {
477 angleDeg = fmodf(angleDeg, 360);
478 if (angleDeg < 0)
479 angleDeg += 360;
480
481 if (!angleDeg) {
482 firstPoint.set(0, 0);
483 secondPoint.set(size.width(), 0);
484 return;
485 }
486
487 if (angleDeg == 90) {
488 firstPoint.set(0, size.height());
489 secondPoint.set(0, 0);
490 return;
491 }
492
493 if (angleDeg == 180) {
494 firstPoint.set(size.width(), 0);
495 secondPoint.set(0, 0);
496 return;
497 }
498
499 float slope = tan(deg2rad(angleDeg));
500
501 // We find the endpoint by computing the intersection of the line formed by the slope,
502 // and a line perpendicular to it that intersects the corner.
503 float perpendicularSlope = -1 / slope;
504
505 // Compute start corner relative to center.
506 float halfHeight = size.height() / 2;
507 float halfWidth = size.width() / 2;
508 FloatPoint endCorner;
509 if (angleDeg < 90)
510 endCorner.set(halfWidth, halfHeight);
511 else if (angleDeg < 180)
512 endCorner.set(-halfWidth, halfHeight);
513 else if (angleDeg < 270)
514 endCorner.set(-halfWidth, -halfHeight);
515 else
516 endCorner.set(halfWidth, -halfHeight);
517
518 // Compute c (of y = mx + c) using the corner point.
519 float c = endCorner.y() - perpendicularSlope * endCorner.x();
520 float endX = c / (slope - perpendicularSlope);
521 float endY = perpendicularSlope * endX + c;
522
523 // We computed the end point, so set the second point, flipping the Y to account for angles going anticlockwise.
524 secondPoint.set(halfWidth + endX, size.height() - (halfHeight + endY));
525 // Reflect around the center for the start point.
526 firstPoint.set(size.width() - secondPoint.x(), size.height() - secondPoint.y());
527 }
528
createGradient(RenderObject * renderer,const IntSize & size)529 PassRefPtr<Gradient> CSSLinearGradientValue::createGradient(RenderObject* renderer, const IntSize& size)
530 {
531 ASSERT(!size.isEmpty());
532
533 RenderStyle* rootStyle = renderer->document()->documentElement()->renderStyle();
534
535 FloatPoint firstPoint;
536 FloatPoint secondPoint;
537 if (m_angle) {
538 float angle = m_angle->getFloatValue(CSSPrimitiveValue::CSS_DEG);
539 endPointsFromAngle(angle, size, firstPoint, secondPoint);
540 } else {
541 firstPoint = computeEndPoint(m_firstX.get(), m_firstY.get(), renderer->style(), rootStyle, size);
542
543 if (m_secondX || m_secondY)
544 secondPoint = computeEndPoint(m_secondX.get(), m_secondY.get(), renderer->style(), rootStyle, size);
545 else {
546 if (m_firstX)
547 secondPoint.setX(size.width() - firstPoint.x());
548 if (m_firstY)
549 secondPoint.setY(size.height() - firstPoint.y());
550 }
551 }
552
553 RefPtr<Gradient> gradient = Gradient::create(firstPoint, secondPoint);
554
555 // Now add the stops.
556 addStops(gradient.get(), renderer, rootStyle, 1);
557
558 return gradient.release();
559 }
560
cssText() const561 String CSSRadialGradientValue::cssText() const
562 {
563 String result;
564
565 if (m_deprecatedType) {
566 result = "-webkit-gradient(radial, ";
567
568 result += m_firstX->cssText() + " ";
569 result += m_firstY->cssText() + ", ";
570 result += m_firstRadius->cssText() + ", ";
571 result += m_secondX->cssText() + " ";
572 result += m_secondY->cssText();
573 result += ", ";
574 result += m_secondRadius->cssText();
575
576 // FIXME: share?
577 for (unsigned i = 0; i < m_stops.size(); i++) {
578 const CSSGradientColorStop& stop = m_stops[i];
579 result += ", ";
580 if (stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER) == 0)
581 result += "from(" + stop.m_color->cssText() + ")";
582 else if (stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER) == 1)
583 result += "to(" + stop.m_color->cssText() + ")";
584 else
585 result += "color-stop(" + String::number(stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER)) + ", " + stop.m_color->cssText() + ")";
586 }
587 } else {
588
589 result = m_repeating ? "-webkit-repeating-radial-gradient(" : "-webkit-radial-gradient(";
590 if (m_firstX && m_firstY) {
591 result += m_firstX->cssText() + " " + m_firstY->cssText();
592 } else if (m_firstX)
593 result += m_firstX->cssText();
594 else if (m_firstY)
595 result += m_firstY->cssText();
596 else
597 result += "center";
598
599
600 if (m_shape || m_sizingBehavior) {
601 result += ", ";
602 if (m_shape)
603 result += m_shape->cssText() + " ";
604 else
605 result += "ellipse ";
606
607 if (m_sizingBehavior)
608 result += m_sizingBehavior->cssText();
609 else
610 result += "cover";
611
612 } else if (m_endHorizontalSize && m_endVerticalSize) {
613 result += ", ";
614 result += m_endHorizontalSize->cssText() + " " + m_endVerticalSize->cssText();
615 }
616
617 for (unsigned i = 0; i < m_stops.size(); i++) {
618 const CSSGradientColorStop& stop = m_stops[i];
619 result += ", ";
620 result += stop.m_color->cssText();
621 if (stop.m_position)
622 result += " " + stop.m_position->cssText();
623 }
624 }
625
626 result += ")";
627 return result;
628 }
629
resolveRadius(CSSPrimitiveValue * radius,RenderStyle * style,RenderStyle * rootStyle,float * widthOrHeight)630 float CSSRadialGradientValue::resolveRadius(CSSPrimitiveValue* radius, RenderStyle* style, RenderStyle* rootStyle, float* widthOrHeight)
631 {
632 float zoomFactor = style->effectiveZoom();
633
634 float result = 0;
635 if (radius->primitiveType() == CSSPrimitiveValue::CSS_NUMBER) // Can the radius be a percentage?
636 result = radius->getFloatValue() * zoomFactor;
637 else if (widthOrHeight && radius->primitiveType() == CSSPrimitiveValue::CSS_PERCENTAGE)
638 result = *widthOrHeight * radius->getFloatValue() / 100;
639 else
640 result = radius->computeLengthFloat(style, rootStyle, zoomFactor);
641
642 return result;
643 }
644
distanceToClosestCorner(const FloatPoint & p,const FloatSize & size,FloatPoint & corner)645 static float distanceToClosestCorner(const FloatPoint& p, const FloatSize& size, FloatPoint& corner)
646 {
647 FloatPoint topLeft;
648 float topLeftDistance = FloatSize(p - topLeft).diagonalLength();
649
650 FloatPoint topRight(size.width(), 0);
651 float topRightDistance = FloatSize(p - topRight).diagonalLength();
652
653 FloatPoint bottomLeft(0, size.height());
654 float bottomLeftDistance = FloatSize(p - bottomLeft).diagonalLength();
655
656 FloatPoint bottomRight(size.width(), size.height());
657 float bottomRightDistance = FloatSize(p - bottomRight).diagonalLength();
658
659 corner = topLeft;
660 float minDistance = topLeftDistance;
661 if (topRightDistance < minDistance) {
662 minDistance = topRightDistance;
663 corner = topRight;
664 }
665
666 if (bottomLeftDistance < minDistance) {
667 minDistance = bottomLeftDistance;
668 corner = bottomLeft;
669 }
670
671 if (bottomRightDistance < minDistance) {
672 minDistance = bottomRightDistance;
673 corner = bottomRight;
674 }
675 return minDistance;
676 }
677
distanceToFarthestCorner(const FloatPoint & p,const FloatSize & size,FloatPoint & corner)678 static float distanceToFarthestCorner(const FloatPoint& p, const FloatSize& size, FloatPoint& corner)
679 {
680 FloatPoint topLeft;
681 float topLeftDistance = FloatSize(p - topLeft).diagonalLength();
682
683 FloatPoint topRight(size.width(), 0);
684 float topRightDistance = FloatSize(p - topRight).diagonalLength();
685
686 FloatPoint bottomLeft(0, size.height());
687 float bottomLeftDistance = FloatSize(p - bottomLeft).diagonalLength();
688
689 FloatPoint bottomRight(size.width(), size.height());
690 float bottomRightDistance = FloatSize(p - bottomRight).diagonalLength();
691
692 corner = topLeft;
693 float maxDistance = topLeftDistance;
694 if (topRightDistance > maxDistance) {
695 maxDistance = topRightDistance;
696 corner = topRight;
697 }
698
699 if (bottomLeftDistance > maxDistance) {
700 maxDistance = bottomLeftDistance;
701 corner = bottomLeft;
702 }
703
704 if (bottomRightDistance > maxDistance) {
705 maxDistance = bottomRightDistance;
706 corner = bottomRight;
707 }
708 return maxDistance;
709 }
710
711 // Compute horizontal radius of ellipse with center at 0,0 which passes through p, and has
712 // width/height given by aspectRatio.
horizontalEllipseRadius(const FloatSize & p,float aspectRatio)713 static inline float horizontalEllipseRadius(const FloatSize& p, float aspectRatio)
714 {
715 // x^2/a^2 + y^2/b^2 = 1
716 // a/b = aspectRatio, b = a/aspectRatio
717 // a = sqrt(x^2 + y^2/(1/r^2))
718 return sqrtf(p.width() * p.width() + (p.height() * p.height()) / (1 / (aspectRatio * aspectRatio)));
719 }
720
721 // FIXME: share code with the linear version
createGradient(RenderObject * renderer,const IntSize & size)722 PassRefPtr<Gradient> CSSRadialGradientValue::createGradient(RenderObject* renderer, const IntSize& size)
723 {
724 ASSERT(!size.isEmpty());
725
726 RenderStyle* rootStyle = renderer->document()->documentElement()->renderStyle();
727
728 FloatPoint firstPoint = computeEndPoint(m_firstX.get(), m_firstY.get(), renderer->style(), rootStyle, size);
729 if (!m_firstX)
730 firstPoint.setX(size.width() / 2);
731 if (!m_firstY)
732 firstPoint.setY(size.height() / 2);
733
734 FloatPoint secondPoint = computeEndPoint(m_secondX.get(), m_secondY.get(), renderer->style(), rootStyle, size);
735 if (!m_secondX)
736 secondPoint.setX(size.width() / 2);
737 if (!m_secondY)
738 secondPoint.setY(size.height() / 2);
739
740 float firstRadius = 0;
741 if (m_firstRadius)
742 firstRadius = resolveRadius(m_firstRadius.get(), renderer->style(), rootStyle);
743
744 float secondRadius = 0;
745 float aspectRatio = 1; // width / height.
746 if (m_secondRadius)
747 secondRadius = resolveRadius(m_secondRadius.get(), renderer->style(), rootStyle);
748 else if (m_endHorizontalSize || m_endVerticalSize) {
749 float width = size.width();
750 float height = size.height();
751 secondRadius = resolveRadius(m_endHorizontalSize.get(), renderer->style(), rootStyle, &width);
752 aspectRatio = secondRadius / resolveRadius(m_endVerticalSize.get(), renderer->style(), rootStyle, &height);
753 } else {
754 enum GradientShape { Circle, Ellipse };
755 GradientShape shape = Ellipse;
756 if (m_shape && m_shape->primitiveType() == CSSPrimitiveValue::CSS_IDENT && m_shape->getIdent() == CSSValueCircle)
757 shape = Circle;
758
759 enum GradientFill { ClosestSide, ClosestCorner, FarthestSide, FarthestCorner };
760 GradientFill fill = FarthestCorner;
761
762 if (m_sizingBehavior && m_sizingBehavior->primitiveType() == CSSPrimitiveValue::CSS_IDENT) {
763 switch (m_sizingBehavior->getIdent()) {
764 case CSSValueContain:
765 case CSSValueClosestSide:
766 fill = ClosestSide;
767 break;
768 case CSSValueClosestCorner:
769 fill = ClosestCorner;
770 break;
771 case CSSValueFarthestSide:
772 fill = FarthestSide;
773 break;
774 case CSSValueCover:
775 case CSSValueFarthestCorner:
776 fill = FarthestCorner;
777 break;
778 }
779 }
780
781 // Now compute the end radii based on the second point, shape and fill.
782
783 // Horizontal
784 switch (fill) {
785 case ClosestSide: {
786 float xDist = min(secondPoint.x(), size.width() - secondPoint.x());
787 float yDist = min(secondPoint.y(), size.height() - secondPoint.y());
788 if (shape == Circle) {
789 float smaller = min(xDist, yDist);
790 xDist = smaller;
791 yDist = smaller;
792 }
793 secondRadius = xDist;
794 aspectRatio = xDist / yDist;
795 break;
796 }
797 case FarthestSide: {
798 float xDist = max(secondPoint.x(), size.width() - secondPoint.x());
799 float yDist = max(secondPoint.y(), size.height() - secondPoint.y());
800 if (shape == Circle) {
801 float larger = max(xDist, yDist);
802 xDist = larger;
803 yDist = larger;
804 }
805 secondRadius = xDist;
806 aspectRatio = xDist / yDist;
807 break;
808 }
809 case ClosestCorner: {
810 FloatPoint corner;
811 float distance = distanceToClosestCorner(secondPoint, size, corner);
812 if (shape == Circle)
813 secondRadius = distance;
814 else {
815 // If <shape> is ellipse, the gradient-shape has the same ratio of width to height
816 // that it would if closest-side or farthest-side were specified, as appropriate.
817 float xDist = min(secondPoint.x(), size.width() - secondPoint.x());
818 float yDist = min(secondPoint.y(), size.height() - secondPoint.y());
819
820 secondRadius = horizontalEllipseRadius(corner - secondPoint, xDist / yDist);
821 aspectRatio = xDist / yDist;
822 }
823 break;
824 }
825
826 case FarthestCorner: {
827 FloatPoint corner;
828 float distance = distanceToFarthestCorner(secondPoint, size, corner);
829 if (shape == Circle)
830 secondRadius = distance;
831 else {
832 // If <shape> is ellipse, the gradient-shape has the same ratio of width to height
833 // that it would if closest-side or farthest-side were specified, as appropriate.
834 float xDist = max(secondPoint.x(), size.width() - secondPoint.x());
835 float yDist = max(secondPoint.y(), size.height() - secondPoint.y());
836
837 secondRadius = horizontalEllipseRadius(corner - secondPoint, xDist / yDist);
838 aspectRatio = xDist / yDist;
839 }
840 break;
841 }
842 }
843 }
844
845 RefPtr<Gradient> gradient = Gradient::create(firstPoint, firstRadius, secondPoint, secondRadius, aspectRatio);
846
847 // addStops() only uses maxExtent for repeating gradients.
848 float maxExtent = 0;
849 if (m_repeating) {
850 FloatPoint corner;
851 maxExtent = distanceToFarthestCorner(secondPoint, size, corner);
852 }
853
854 // Now add the stops.
855 addStops(gradient.get(), renderer, rootStyle, maxExtent);
856
857 return gradient.release();
858 }
859
860 } // namespace WebCore
861