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