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