1/*
2 * Copyright (C) 2008 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "CSSGradientValue.h"
28
29#include "CSSCalculationValue.h"
30#include "CSSToLengthConversionData.h"
31#include "CSSValueKeywords.h"
32#include "FloatSize.h"
33#include "Gradient.h"
34#include "GradientImage.h"
35#include "NodeRenderStyle.h"
36#include "Pair.h"
37#include "RenderElement.h"
38#include "RenderView.h"
39#include "StyleResolver.h"
40#include <wtf/text/StringBuilder.h>
41
42namespace WebCore {
43
44static inline Ref<Gradient> createGradient(CSSGradientValue& value, RenderElement& renderer, FloatSize size)
45{
46 if (is<CSSLinearGradientValue>(value))
47 return downcast<CSSLinearGradientValue>(value).createGradient(renderer, size);
48 if (is<CSSRadialGradientValue>(value))
49 return downcast<CSSRadialGradientValue>(value).createGradient(renderer, size);
50 return downcast<CSSConicGradientValue>(value).createGradient(renderer, size);
51}
52
53RefPtr<Image> CSSGradientValue::image(RenderElement& renderer, const FloatSize& size)
54{
55 if (size.isEmpty())
56 return nullptr;
57 bool cacheable = isCacheable() && !renderer.style().hasAppleColorFilter();
58 if (cacheable) {
59 if (!clients().contains(&renderer))
60 return nullptr;
61 if (auto* result = cachedImageForSize(size))
62 return result;
63 }
64 auto newImage = GradientImage::create(createGradient(*this, renderer, size), size);
65 if (cacheable)
66 saveCachedImageForSize(size, newImage.get());
67 return newImage;
68}
69
70// Should only ever be called for deprecated gradients.
71static inline bool compareStops(const CSSGradientColorStop& a, const CSSGradientColorStop& b)
72{
73 double aVal = a.m_position->doubleValue(CSSPrimitiveValue::CSS_NUMBER);
74 double bVal = b.m_position->doubleValue(CSSPrimitiveValue::CSS_NUMBER);
75
76 return aVal < bVal;
77}
78
79void CSSGradientValue::sortStopsIfNeeded()
80{
81 ASSERT(m_gradientType == CSSDeprecatedLinearGradient || m_gradientType == CSSDeprecatedRadialGradient);
82 if (!m_stopsSorted) {
83 if (m_stops.size())
84 std::stable_sort(m_stops.begin(), m_stops.end(), compareStops);
85 m_stopsSorted = true;
86 }
87}
88
89struct GradientStop {
90 Color color;
91 float offset { 0 };
92 bool specified { false };
93 bool isMidpoint { false };
94};
95
96static inline Ref<CSSGradientValue> clone(CSSGradientValue& value)
97{
98 if (is<CSSLinearGradientValue>(value))
99 return downcast<CSSLinearGradientValue>(value).clone();
100 if (is<CSSRadialGradientValue>(value))
101 return downcast<CSSRadialGradientValue>(value).clone();
102 ASSERT(is<CSSConicGradientValue>(value));
103 return downcast<CSSConicGradientValue>(value).clone();
104}
105
106Ref<CSSGradientValue> CSSGradientValue::gradientWithStylesResolved(const StyleResolver& styleResolver)
107{
108 bool colorIsDerivedFromElement = false;
109 for (auto& stop : m_stops) {
110 if (!stop.isMidpoint && styleResolver.colorFromPrimitiveValueIsDerivedFromElement(*stop.m_color)) {
111 stop.m_colorIsDerivedFromElement = true;
112 colorIsDerivedFromElement = true;
113 break;
114 }
115 }
116 auto result = colorIsDerivedFromElement ? clone(*this) : makeRef(*this);
117 for (auto& stop : result->m_stops) {
118 if (!stop.isMidpoint)
119 stop.m_resolvedColor = styleResolver.colorFromPrimitiveValue(*stop.m_color);
120 }
121 return result;
122}
123
124class LinearGradientAdapter {
125public:
126 explicit LinearGradientAdapter(Gradient::LinearData& data)
127 : m_data(data)
128 {
129 }
130
131 float gradientLength() const
132 {
133 auto gradientSize = m_data.point0 - m_data.point1;
134 return gradientSize.diagonalLength();
135 }
136 float maxExtent(float, float) const { return 1; }
137
138 void normalizeStopsAndEndpointsOutsideRange(Vector<GradientStop>& stops)
139 {
140 float firstOffset = stops.first().offset;
141 float lastOffset = stops.last().offset;
142 if (firstOffset != lastOffset) {
143 float scale = lastOffset - firstOffset;
144
145 for (auto& stop : stops)
146 stop.offset = (stop.offset - firstOffset) / scale;
147
148 auto p0 = m_data.point0;
149 auto p1 = m_data.point1;
150 m_data.point0 = { p0.x() + firstOffset * (p1.x() - p0.x()), p0.y() + firstOffset * (p1.y() - p0.y()) };
151 m_data.point1 = { p1.x() + (lastOffset - 1) * (p1.x() - p0.x()), p1.y() + (lastOffset - 1) * (p1.y() - p0.y()) };
152 } else {
153 // There's a single position that is outside the scale, clamp the positions to 1.
154 for (auto& stop : stops)
155 stop.offset = 1;
156 }
157 }
158
159private:
160 Gradient::LinearData& m_data;
161};
162
163class RadialGradientAdapter {
164public:
165 explicit RadialGradientAdapter(Gradient::RadialData& data)
166 : m_data(data)
167 {
168 }
169
170 float gradientLength() const { return m_data.endRadius; }
171
172 // Radial gradients may need to extend further than the endpoints, because they have
173 // to repeat out to the corners of the box.
174 float maxExtent(float maxLengthForRepeat, float gradientLength) const
175 {
176 if (maxLengthForRepeat > gradientLength)
177 return gradientLength > 0 ? maxLengthForRepeat / gradientLength : 0;
178 return 1;
179 }
180
181 void normalizeStopsAndEndpointsOutsideRange(Vector<GradientStop>& stops)
182 {
183 auto numStops = stops.size();
184
185 // Rather than scaling the points < 0, we truncate them, so only scale according to the largest point.
186 float firstOffset = 0;
187 float lastOffset = stops.last().offset;
188 float scale = lastOffset - firstOffset;
189
190 // Reset points below 0 to the first visible color.
191 size_t firstZeroOrGreaterIndex = numStops;
192 for (size_t i = 0; i < numStops; ++i) {
193 if (stops[i].offset >= 0) {
194 firstZeroOrGreaterIndex = i;
195 break;
196 }
197 }
198
199 if (firstZeroOrGreaterIndex > 0) {
200 if (firstZeroOrGreaterIndex < numStops && stops[firstZeroOrGreaterIndex].offset > 0) {
201 float prevOffset = stops[firstZeroOrGreaterIndex - 1].offset;
202 float nextOffset = stops[firstZeroOrGreaterIndex].offset;
203
204 float interStopProportion = -prevOffset / (nextOffset - prevOffset);
205 // FIXME: when we interpolate gradients using premultiplied colors, this should do premultiplication.
206 Color blendedColor = blend(stops[firstZeroOrGreaterIndex - 1].color, stops[firstZeroOrGreaterIndex].color, interStopProportion);
207
208 // Clamp the positions to 0 and set the color.
209 for (size_t i = 0; i < firstZeroOrGreaterIndex; ++i) {
210 stops[i].offset = 0;
211 stops[i].color = blendedColor;
212 }
213 } else {
214 // All stops are below 0; just clamp them.
215 for (size_t i = 0; i < firstZeroOrGreaterIndex; ++i)
216 stops[i].offset = 0;
217 }
218 }
219
220 for (auto& stop : stops)
221 stop.offset /= scale;
222
223 m_data.startRadius *= scale;
224 m_data.endRadius *= scale;
225 }
226
227private:
228 Gradient::RadialData& m_data;
229};
230
231class ConicGradientAdapter {
232public:
233 explicit ConicGradientAdapter() { }
234 float gradientLength() const { return 1; }
235 float maxExtent(float, float) const { return 1; }
236
237 void normalizeStopsAndEndpointsOutsideRange(Vector<GradientStop>& stops)
238 {
239 auto numStops = stops.size();
240
241 size_t firstZeroOrGreaterIndex = numStops;
242 for (size_t i = 0; i < numStops; ++i) {
243 if (stops[i].offset >= 0) {
244 firstZeroOrGreaterIndex = i;
245 break;
246 }
247 }
248
249 if (firstZeroOrGreaterIndex > 0) {
250 if (firstZeroOrGreaterIndex < numStops && stops[firstZeroOrGreaterIndex].offset > 0) {
251 float prevOffset = stops[firstZeroOrGreaterIndex - 1].offset;
252 float nextOffset = stops[firstZeroOrGreaterIndex].offset;
253
254 float interStopProportion = -prevOffset / (nextOffset - prevOffset);
255 // FIXME: when we interpolate gradients using premultiplied colors, this should do premultiplication.
256 Color blendedColor = blend(stops[firstZeroOrGreaterIndex - 1].color, stops[firstZeroOrGreaterIndex].color, interStopProportion);
257
258 // Clamp the positions to 0 and set the color.
259 for (size_t i = 0; i < firstZeroOrGreaterIndex; ++i) {
260 stops[i].offset = 0;
261 stops[i].color = blendedColor;
262 }
263 } else {
264 // All stops are below 0; just clamp them.
265 for (size_t i = 0; i < firstZeroOrGreaterIndex; ++i)
266 stops[i].offset = 0;
267 }
268 }
269
270 size_t lastOneOrLessIndex = numStops;
271 for (int i = numStops - 1; i >= 0; --i) {
272 if (stops[i].offset <= 1) {
273 lastOneOrLessIndex = i;
274 break;
275 }
276 }
277
278 if (lastOneOrLessIndex < numStops - 1) {
279 if (lastOneOrLessIndex < numStops && stops[lastOneOrLessIndex].offset < 1) {
280 float prevOffset = stops[lastOneOrLessIndex].offset;
281 float nextOffset = stops[lastOneOrLessIndex + 1].offset;
282
283 float interStopProportion = (1 - prevOffset) / (nextOffset - prevOffset);
284 // FIXME: when we interpolate gradients using premultiplied colors, this should do premultiplication.
285 Color blendedColor = blend(stops[lastOneOrLessIndex].color, stops[lastOneOrLessIndex + 1].color, interStopProportion);
286
287 // Clamp the positions to 1 and set the color.
288 for (size_t i = lastOneOrLessIndex + 1; i < numStops; ++i) {
289 stops[i].offset = 1;
290 stops[i].color = blendedColor;
291 }
292 } else {
293 // All stops are above 1; just clamp them.
294 for (size_t i = lastOneOrLessIndex; i < numStops; ++i)
295 stops[i].offset = 1;
296 }
297 }
298 }
299};
300
301template<typename GradientAdapter>
302Gradient::ColorStopVector CSSGradientValue::computeStops(GradientAdapter& gradientAdapter, const CSSToLengthConversionData& conversionData, const RenderStyle& style, float maxLengthForRepeat)
303{
304 if (m_gradientType == CSSDeprecatedLinearGradient || m_gradientType == CSSDeprecatedRadialGradient) {
305 sortStopsIfNeeded();
306
307 Gradient::ColorStopVector result;
308 result.reserveInitialCapacity(m_stops.size());
309
310 for (auto& stop : m_stops) {
311 float offset;
312 if (stop.m_position->isPercentage())
313 offset = stop.m_position->floatValue(CSSPrimitiveValue::CSS_PERCENTAGE) / 100;
314 else
315 offset = stop.m_position->floatValue(CSSPrimitiveValue::CSS_NUMBER);
316
317 Color color = stop.m_resolvedColor;
318 if (style.hasAppleColorFilter())
319 style.appleColorFilter().transformColor(color);
320 result.uncheckedAppend({ offset, color });
321 }
322
323 return result;
324 }
325
326 size_t numStops = m_stops.size();
327 Vector<GradientStop> stops(numStops);
328
329 float gradientLength = gradientAdapter.gradientLength();
330
331 for (size_t i = 0; i < numStops; ++i) {
332 auto& stop = m_stops[i];
333
334 stops[i].isMidpoint = stop.isMidpoint;
335
336 Color color = stop.m_resolvedColor;
337 if (style.hasAppleColorFilter())
338 style.appleColorFilter().transformColor(color);
339
340 stops[i].color = color;
341
342 if (stop.m_position) {
343 auto& positionValue = *stop.m_position;
344 if (positionValue.isPercentage())
345 stops[i].offset = positionValue.floatValue(CSSPrimitiveValue::CSS_PERCENTAGE) / 100;
346 else if (positionValue.isLength() || positionValue.isViewportPercentageLength() || positionValue.isCalculatedPercentageWithLength()) {
347 float length;
348 if (positionValue.isLength())
349 length = positionValue.computeLength<float>(conversionData);
350 else {
351 Ref<CalculationValue> calculationValue { positionValue.cssCalcValue()->createCalculationValue(conversionData) };
352 length = calculationValue->evaluate(gradientLength);
353 }
354 stops[i].offset = (gradientLength > 0) ? length / gradientLength : 0;
355 } else if (positionValue.isAngle())
356 stops[i].offset = positionValue.floatValue(CSSPrimitiveValue::CSS_DEG) / 360;
357 else {
358 ASSERT_NOT_REACHED();
359 stops[i].offset = 0;
360 }
361 stops[i].specified = true;
362 } else {
363 // If the first color-stop does not have a position, its position defaults to 0%.
364 // If the last color-stop does not have a position, its position defaults to 100%.
365 if (!i) {
366 stops[i].offset = 0;
367 stops[i].specified = true;
368 } else if (numStops > 1 && i == numStops - 1) {
369 stops[i].offset = 1;
370 stops[i].specified = true;
371 }
372 }
373
374 // If a color-stop has a position that is less than the specified position of any
375 // color-stop before it in the list, its position is changed to be equal to the
376 // largest specified position of any color-stop before it.
377 if (stops[i].specified && i > 0) {
378 size_t prevSpecifiedIndex;
379 for (prevSpecifiedIndex = i - 1; prevSpecifiedIndex; --prevSpecifiedIndex) {
380 if (stops[prevSpecifiedIndex].specified)
381 break;
382 }
383
384 if (stops[i].offset < stops[prevSpecifiedIndex].offset)
385 stops[i].offset = stops[prevSpecifiedIndex].offset;
386 }
387 }
388
389 ASSERT(stops[0].specified && stops[numStops - 1].specified);
390
391 // If any color-stop still does not have a position, then, for each run of adjacent
392 // color-stops without positions, set their positions so that they are evenly spaced
393 // between the preceding and following color-stops with positions.
394 if (numStops > 2) {
395 size_t unspecifiedRunStart = 0;
396 bool inUnspecifiedRun = false;
397
398 for (size_t i = 0; i < numStops; ++i) {
399 if (!stops[i].specified && !inUnspecifiedRun) {
400 unspecifiedRunStart = i;
401 inUnspecifiedRun = true;
402 } else if (stops[i].specified && inUnspecifiedRun) {
403 size_t unspecifiedRunEnd = i;
404
405 if (unspecifiedRunStart < unspecifiedRunEnd) {
406 float lastSpecifiedOffset = stops[unspecifiedRunStart - 1].offset;
407 float nextSpecifiedOffset = stops[unspecifiedRunEnd].offset;
408 float delta = (nextSpecifiedOffset - lastSpecifiedOffset) / (unspecifiedRunEnd - unspecifiedRunStart + 1);
409
410 for (size_t j = unspecifiedRunStart; j < unspecifiedRunEnd; ++j)
411 stops[j].offset = lastSpecifiedOffset + (j - unspecifiedRunStart + 1) * delta;
412 }
413
414 inUnspecifiedRun = false;
415 }
416 }
417 }
418
419 // Walk over the color stops, look for midpoints and add stops as needed.
420 // If mid < 50%, add 2 stops to the left and 6 to the right
421 // else add 6 stops to the left and 2 to the right.
422 // Stops on the side with the most stops start midway because the curve approximates
423 // a line in that region. We then add 5 more color stops on that side to minimize the change
424 // how the luminance changes at each of the color stops. We don't have to add as many on the other side
425 // since it becomes small which increases the differentation of luminance which hides the color stops.
426 // Even with 4 extra color stops, it *is* possible to discern the steps when the gradient is large and has
427 // large luminance differences between midpoint and color stop. If this becomes an issue, we can consider
428 // making this algorithm a bit smarter.
429
430 // Midpoints that coincide with color stops are treated specially since they don't require
431 // extra stops and generate hard lines.
432 for (size_t x = 1; x < stops.size() - 1;) {
433 if (!stops[x].isMidpoint) {
434 ++x;
435 continue;
436 }
437
438 // Find previous and next color so we know what to interpolate between.
439 // We already know they have a color since we checked for that earlier.
440 Color color1 = stops[x - 1].color;
441 Color color2 = stops[x + 1].color;
442 // Likewise find the position of previous and next color stop.
443 float offset1 = stops[x - 1].offset;
444 float offset2 = stops[x + 1].offset;
445 float offset = stops[x].offset;
446
447 // Check if everything coincides or the midpoint is exactly in the middle.
448 // If so, ignore the midpoint.
449 if (offset - offset1 == offset2 - offset) {
450 stops.remove(x);
451 continue;
452 }
453
454 // Check if we coincide with the left color stop.
455 if (offset1 == offset) {
456 // Morph the midpoint to a regular stop with the color of the next color stop.
457 stops[x].color = color2;
458 stops[x].isMidpoint = false;
459 continue;
460 }
461
462 // Check if we coincide with the right color stop.
463 if (offset2 == offset) {
464 // Morph the midpoint to a regular stop with the color of the previous color stop.
465 stops[x].color = color1;
466 stops[x].isMidpoint = false;
467 continue;
468 }
469
470 float midpoint = (offset - offset1) / (offset2 - offset1);
471 GradientStop newStops[9];
472 if (midpoint > .5f) {
473 for (size_t y = 0; y < 7; ++y)
474 newStops[y].offset = offset1 + (offset - offset1) * (7 + y) / 13;
475
476 newStops[7].offset = offset + (offset2 - offset) / 3;
477 newStops[8].offset = offset + (offset2 - offset) * 2 / 3;
478 } else {
479 newStops[0].offset = offset1 + (offset - offset1) / 3;
480 newStops[1].offset = offset1 + (offset - offset1) * 2 / 3;
481
482 for (size_t y = 0; y < 7; ++y)
483 newStops[y + 2].offset = offset + (offset2 - offset) * y / 13;
484 }
485 // calculate colors
486 for (size_t y = 0; y < 9; ++y) {
487 float relativeOffset = (newStops[y].offset - offset1) / (offset2 - offset1);
488 float multiplier = std::pow(relativeOffset, std::log(.5f) / std::log(midpoint));
489 // FIXME: Why not premultiply here?
490 newStops[y].color = blend(color1, color2, multiplier, false /* do not premultiply */);
491 }
492
493 stops.remove(x);
494 stops.insert(x, newStops, 9);
495 x += 9;
496 }
497
498 numStops = stops.size();
499
500 // If the gradient is repeating, repeat the color stops.
501 // We can't just push this logic down into the platform-specific Gradient code,
502 // because we have to know the extent of the gradient, and possible move the end points.
503 if (m_repeating && numStops > 1) {
504 // If the difference in the positions of the first and last color-stops is 0,
505 // the gradient defines a solid-color image with the color of the last color-stop in the rule.
506 float gradientRange = stops.last().offset - stops.first().offset;
507 if (!gradientRange) {
508 stops.first().offset = 0;
509 stops.first().color = stops.last().color;
510 stops.shrink(1);
511 numStops = 1;
512 } else {
513 float maxExtent = gradientAdapter.maxExtent(maxLengthForRepeat, gradientLength);
514
515 size_t originalNumStops = numStops;
516 size_t originalFirstStopIndex = 0;
517
518 // Work backwards from the first, adding stops until we get one before 0.
519 float firstOffset = stops[0].offset;
520 if (firstOffset > 0) {
521 float currOffset = firstOffset;
522 size_t srcStopOrdinal = originalNumStops - 1;
523
524 while (true) {
525 GradientStop newStop = stops[originalFirstStopIndex + srcStopOrdinal];
526 newStop.offset = currOffset;
527 stops.insert(0, newStop);
528 ++originalFirstStopIndex;
529 if (currOffset < 0)
530 break;
531
532 if (srcStopOrdinal)
533 currOffset -= stops[originalFirstStopIndex + srcStopOrdinal].offset - stops[originalFirstStopIndex + srcStopOrdinal - 1].offset;
534 srcStopOrdinal = (srcStopOrdinal + originalNumStops - 1) % originalNumStops;
535 }
536 }
537
538 // Work forwards from the end, adding stops until we get one after 1.
539 float lastOffset = stops[stops.size() - 1].offset;
540 if (lastOffset < maxExtent) {
541 float currOffset = lastOffset;
542 size_t srcStopOrdinal = 0;
543
544 while (true) {
545 size_t srcStopIndex = originalFirstStopIndex + srcStopOrdinal;
546 GradientStop newStop = stops[srcStopIndex];
547 newStop.offset = currOffset;
548 stops.append(newStop);
549 if (currOffset > maxExtent)
550 break;
551 if (srcStopOrdinal < originalNumStops - 1)
552 currOffset += stops[srcStopIndex + 1].offset - stops[srcStopIndex].offset;
553 srcStopOrdinal = (srcStopOrdinal + 1) % originalNumStops;
554 }
555 }
556 }
557 }
558
559 // If the gradient goes outside the 0-1 range, normalize it by moving the endpoints, and adjusting the stops.
560 if (stops.size() > 1 && (stops.first().offset < 0 || stops.last().offset > 1))
561 gradientAdapter.normalizeStopsAndEndpointsOutsideRange(stops);
562
563 Gradient::ColorStopVector result;
564 result.reserveInitialCapacity(stops.size());
565 for (auto& stop : stops)
566 result.uncheckedAppend({ stop.offset, stop.color });
567
568 return result;
569}
570
571static float positionFromValue(const CSSPrimitiveValue* value, const CSSToLengthConversionData& conversionData, const FloatSize& size, bool isHorizontal)
572{
573 int origin = 0;
574 int sign = 1;
575 int edgeDistance = isHorizontal ? size.width() : size.height();
576
577 // In this case the center of the gradient is given relative to an edge in the
578 // form of: [ top | bottom | right | left ] [ <percentage> | <length> ].
579 if (value->isPair()) {
580 CSSValueID originID = value->pairValue()->first()->valueID();
581 value = value->pairValue()->second();
582
583 if (originID == CSSValueRight || originID == CSSValueBottom) {
584 // For right/bottom, the offset is relative to the far edge.
585 origin = edgeDistance;
586 sign = -1;
587 }
588 }
589
590 if (value->isNumber())
591 return origin + sign * value->floatValue() * conversionData.zoom();
592
593 if (value->isPercentage())
594 return origin + sign * value->floatValue() / 100.f * edgeDistance;
595
596 if (value->isCalculatedPercentageWithLength()) {
597 Ref<CalculationValue> calculationValue { value->cssCalcValue()->createCalculationValue(conversionData) };
598 return origin + sign * calculationValue->evaluate(edgeDistance);
599 }
600
601 switch (value->valueID()) {
602 case CSSValueTop:
603 ASSERT(!isHorizontal);
604 return 0;
605 case CSSValueLeft:
606 ASSERT(isHorizontal);
607 return 0;
608 case CSSValueBottom:
609 ASSERT(!isHorizontal);
610 return size.height();
611 case CSSValueRight:
612 ASSERT(isHorizontal);
613 return size.width();
614 case CSSValueCenter:
615 return origin + sign * .5f * edgeDistance;
616 default:
617 break;
618 }
619
620 return origin + sign * value->computeLength<float>(conversionData);
621}
622
623FloatPoint CSSGradientValue::computeEndPoint(CSSPrimitiveValue* horizontal, CSSPrimitiveValue* vertical, const CSSToLengthConversionData& conversionData, const FloatSize& size)
624{
625 FloatPoint result;
626
627 if (horizontal)
628 result.setX(positionFromValue(horizontal, conversionData, size, true));
629
630 if (vertical)
631 result.setY(positionFromValue(vertical, conversionData, size, false));
632
633 return result;
634}
635
636bool CSSGradientValue::isCacheable() const
637{
638 for (auto& stop : m_stops) {
639 if (stop.m_colorIsDerivedFromElement)
640 return false;
641
642 if (!stop.m_position)
643 continue;
644
645 if (stop.m_position->isFontRelativeLength())
646 return false;
647 }
648
649 return true;
650}
651
652bool CSSGradientValue::knownToBeOpaque(const RenderElement& renderer) const
653{
654 bool hasColorFilter = renderer.style().hasAppleColorFilter();
655
656 for (auto& stop : m_stops) {
657 if (hasColorFilter) {
658 Color stopColor = stop.m_resolvedColor;
659 renderer.style().appleColorFilter().transformColor(stopColor);
660 if (!stopColor.isOpaque())
661 return false;
662 }
663
664 if (!stop.m_resolvedColor.isOpaque())
665 return false;
666 }
667 return true;
668}
669
670String CSSLinearGradientValue::customCSSText() const
671{
672 StringBuilder result;
673 if (m_gradientType == CSSDeprecatedLinearGradient) {
674 result.appendLiteral("-webkit-gradient(linear, ");
675 result.append(m_firstX->cssText());
676 result.append(' ');
677 result.append(m_firstY->cssText());
678 result.appendLiteral(", ");
679 result.append(m_secondX->cssText());
680 result.append(' ');
681 result.append(m_secondY->cssText());
682
683 for (auto& stop : m_stops) {
684 result.appendLiteral(", ");
685 auto position = stop.m_position->doubleValue(CSSPrimitiveValue::CSS_NUMBER);
686 if (!position) {
687 result.appendLiteral("from(");
688 result.append(stop.m_color->cssText());
689 result.append(')');
690 } else if (position == 1) {
691 result.appendLiteral("to(");
692 result.append(stop.m_color->cssText());
693 result.append(')');
694 } else {
695 result.appendLiteral("color-stop(");
696 result.appendFixedPrecisionNumber(position);
697 result.appendLiteral(", ");
698 result.append(stop.m_color->cssText());
699 result.append(')');
700 }
701 }
702 } else if (m_gradientType == CSSPrefixedLinearGradient) {
703 if (m_repeating)
704 result.appendLiteral("-webkit-repeating-linear-gradient(");
705 else
706 result.appendLiteral("-webkit-linear-gradient(");
707
708 if (m_angle)
709 result.append(m_angle->cssText());
710 else {
711 if (m_firstX && m_firstY) {
712 result.append(m_firstX->cssText());
713 result.append(' ');
714 result.append(m_firstY->cssText());
715 } else if (m_firstX || m_firstY) {
716 if (m_firstX)
717 result.append(m_firstX->cssText());
718
719 if (m_firstY)
720 result.append(m_firstY->cssText());
721 }
722 }
723
724 for (unsigned i = 0; i < m_stops.size(); i++) {
725 auto& stop = m_stops[i];
726 result.appendLiteral(", ");
727 result.append(stop.m_color->cssText());
728 if (stop.m_position) {
729 result.append(' ');
730 result.append(stop.m_position->cssText());
731 }
732 }
733 } else {
734 if (m_repeating)
735 result.appendLiteral("repeating-linear-gradient(");
736 else
737 result.appendLiteral("linear-gradient(");
738
739 bool wroteSomething = false;
740
741 if (m_angle && m_angle->computeDegrees() != 180) {
742 result.append(m_angle->cssText());
743 wroteSomething = true;
744 } else if ((m_firstX || m_firstY) && !(!m_firstX && m_firstY && m_firstY->valueID() == CSSValueBottom)) {
745 result.appendLiteral("to ");
746 if (m_firstX && m_firstY) {
747 result.append(m_firstX->cssText());
748 result.append(' ');
749 result.append(m_firstY->cssText());
750 } else if (m_firstX)
751 result.append(m_firstX->cssText());
752 else
753 result.append(m_firstY->cssText());
754 wroteSomething = true;
755 }
756
757 if (wroteSomething)
758 result.appendLiteral(", ");
759
760 for (unsigned i = 0; i < m_stops.size(); i++) {
761 const CSSGradientColorStop& stop = m_stops[i];
762 if (i)
763 result.appendLiteral(", ");
764 if (!stop.isMidpoint)
765 result.append(stop.m_color->cssText());
766 if (stop.m_position) {
767 if (!stop.isMidpoint)
768 result.append(' ');
769 result.append(stop.m_position->cssText());
770 }
771 }
772
773 }
774
775 result.append(')');
776 return result.toString();
777}
778
779// Compute the endpoints so that a gradient of the given angle covers a box of the given size.
780static void endPointsFromAngle(float angleDeg, const FloatSize& size, FloatPoint& firstPoint, FloatPoint& secondPoint, CSSGradientType type)
781{
782 // Prefixed gradients use "polar coordinate" angles, rather than "bearing" angles.
783 if (type == CSSPrefixedLinearGradient)
784 angleDeg = 90 - angleDeg;
785
786 angleDeg = fmodf(angleDeg, 360);
787 if (angleDeg < 0)
788 angleDeg += 360;
789
790 if (!angleDeg) {
791 firstPoint.set(0, size.height());
792 secondPoint.set(0, 0);
793 return;
794 }
795
796 if (angleDeg == 90) {
797 firstPoint.set(0, 0);
798 secondPoint.set(size.width(), 0);
799 return;
800 }
801
802 if (angleDeg == 180) {
803 firstPoint.set(0, 0);
804 secondPoint.set(0, size.height());
805 return;
806 }
807
808 if (angleDeg == 270) {
809 firstPoint.set(size.width(), 0);
810 secondPoint.set(0, 0);
811 return;
812 }
813
814 // angleDeg is a "bearing angle" (0deg = N, 90deg = E),
815 // but tan expects 0deg = E, 90deg = N.
816 float slope = tan(deg2rad(90 - angleDeg));
817
818 // We find the endpoint by computing the intersection of the line formed by the slope,
819 // and a line perpendicular to it that intersects the corner.
820 float perpendicularSlope = -1 / slope;
821
822 // Compute start corner relative to center, in Cartesian space (+y = up).
823 float halfHeight = size.height() / 2;
824 float halfWidth = size.width() / 2;
825 FloatPoint endCorner;
826 if (angleDeg < 90)
827 endCorner.set(halfWidth, halfHeight);
828 else if (angleDeg < 180)
829 endCorner.set(halfWidth, -halfHeight);
830 else if (angleDeg < 270)
831 endCorner.set(-halfWidth, -halfHeight);
832 else
833 endCorner.set(-halfWidth, halfHeight);
834
835 // Compute c (of y = mx + c) using the corner point.
836 float c = endCorner.y() - perpendicularSlope * endCorner.x();
837 float endX = c / (slope - perpendicularSlope);
838 float endY = perpendicularSlope * endX + c;
839
840 // We computed the end point, so set the second point,
841 // taking into account the moved origin and the fact that we're in drawing space (+y = down).
842 secondPoint.set(halfWidth + endX, halfHeight - endY);
843 // Reflect around the center for the start point.
844 firstPoint.set(halfWidth - endX, halfHeight + endY);
845}
846
847Ref<Gradient> CSSLinearGradientValue::createGradient(RenderElement& renderer, const FloatSize& size)
848{
849 ASSERT(!size.isEmpty());
850
851 CSSToLengthConversionData conversionData(&renderer.style(), renderer.document().documentElement()->renderStyle(), &renderer.view());
852
853 FloatPoint firstPoint;
854 FloatPoint secondPoint;
855 if (m_angle) {
856 float angle = m_angle->floatValue(CSSPrimitiveValue::CSS_DEG);
857 endPointsFromAngle(angle, size, firstPoint, secondPoint, m_gradientType);
858 } else {
859 switch (m_gradientType) {
860 case CSSDeprecatedLinearGradient:
861 firstPoint = computeEndPoint(m_firstX.get(), m_firstY.get(), conversionData, size);
862 if (m_secondX || m_secondY)
863 secondPoint = computeEndPoint(m_secondX.get(), m_secondY.get(), conversionData, size);
864 else {
865 if (m_firstX)
866 secondPoint.setX(size.width() - firstPoint.x());
867 if (m_firstY)
868 secondPoint.setY(size.height() - firstPoint.y());
869 }
870 break;
871 case CSSPrefixedLinearGradient:
872 firstPoint = computeEndPoint(m_firstX.get(), m_firstY.get(), conversionData, size);
873 if (m_firstX)
874 secondPoint.setX(size.width() - firstPoint.x());
875 if (m_firstY)
876 secondPoint.setY(size.height() - firstPoint.y());
877 break;
878 case CSSLinearGradient:
879 if (m_firstX && m_firstY) {
880 // "Magic" corners, so the 50% line touches two corners.
881 float rise = size.width();
882 float run = size.height();
883 if (m_firstX && m_firstX->valueID() == CSSValueLeft)
884 run *= -1;
885 if (m_firstY && m_firstY->valueID() == CSSValueBottom)
886 rise *= -1;
887 // Compute angle, and flip it back to "bearing angle" degrees.
888 float angle = 90 - rad2deg(atan2(rise, run));
889 endPointsFromAngle(angle, size, firstPoint, secondPoint, m_gradientType);
890 } else if (m_firstX || m_firstY) {
891 secondPoint = computeEndPoint(m_firstX.get(), m_firstY.get(), conversionData, size);
892 if (m_firstX)
893 firstPoint.setX(size.width() - secondPoint.x());
894 if (m_firstY)
895 firstPoint.setY(size.height() - secondPoint.y());
896 } else
897 secondPoint.setY(size.height());
898 break;
899 default:
900 ASSERT_NOT_REACHED();
901 }
902 }
903
904 Gradient::LinearData data { firstPoint, secondPoint };
905 LinearGradientAdapter adapter { data };
906 auto stops = computeStops(adapter, conversionData, renderer.style(), 1);
907
908 auto gradient = Gradient::create(WTFMove(data));
909 gradient->setSortedColorStops(WTFMove(stops));
910 return gradient;
911}
912
913bool CSSLinearGradientValue::equals(const CSSLinearGradientValue& other) const
914{
915 if (m_gradientType == CSSDeprecatedLinearGradient)
916 return other.m_gradientType == m_gradientType
917 && compareCSSValuePtr(m_firstX, other.m_firstX)
918 && compareCSSValuePtr(m_firstY, other.m_firstY)
919 && compareCSSValuePtr(m_secondX, other.m_secondX)
920 && compareCSSValuePtr(m_secondY, other.m_secondY)
921 && m_stops == other.m_stops;
922
923 if (m_repeating != other.m_repeating)
924 return false;
925
926 if (m_angle)
927 return compareCSSValuePtr(m_angle, other.m_angle) && m_stops == other.m_stops;
928
929 if (other.m_angle)
930 return false;
931
932 bool equalXandY = false;
933 if (m_firstX && m_firstY)
934 equalXandY = compareCSSValuePtr(m_firstX, other.m_firstX) && compareCSSValuePtr(m_firstY, other.m_firstY);
935 else if (m_firstX)
936 equalXandY = compareCSSValuePtr(m_firstX, other.m_firstX) && !other.m_firstY;
937 else if (m_firstY)
938 equalXandY = compareCSSValuePtr(m_firstY, other.m_firstY) && !other.m_firstX;
939 else
940 equalXandY = !other.m_firstX && !other.m_firstY;
941
942 return equalXandY && m_stops == other.m_stops;
943}
944
945String CSSRadialGradientValue::customCSSText() const
946{
947 StringBuilder result;
948
949 if (m_gradientType == CSSDeprecatedRadialGradient) {
950 result.appendLiteral("-webkit-gradient(radial, ");
951 result.append(m_firstX->cssText());
952 result.append(' ');
953 result.append(m_firstY->cssText());
954 result.appendLiteral(", ");
955 result.append(m_firstRadius->cssText());
956 result.appendLiteral(", ");
957 result.append(m_secondX->cssText());
958 result.append(' ');
959 result.append(m_secondY->cssText());
960 result.appendLiteral(", ");
961 result.append(m_secondRadius->cssText());
962
963 // FIXME: share?
964 for (auto& stop : m_stops) {
965 result.appendLiteral(", ");
966 auto position = stop.m_position->doubleValue(CSSPrimitiveValue::CSS_NUMBER);
967 if (!position) {
968 result.appendLiteral("from(");
969 result.append(stop.m_color->cssText());
970 result.append(')');
971 } else if (position == 1) {
972 result.appendLiteral("to(");
973 result.append(stop.m_color->cssText());
974 result.append(')');
975 } else {
976 result.appendLiteral("color-stop(");
977 result.appendFixedPrecisionNumber(position);
978 result.appendLiteral(", ");
979 result.append(stop.m_color->cssText());
980 result.append(')');
981 }
982 }
983 } else if (m_gradientType == CSSPrefixedRadialGradient) {
984 if (m_repeating)
985 result.appendLiteral("-webkit-repeating-radial-gradient(");
986 else
987 result.appendLiteral("-webkit-radial-gradient(");
988
989 if (m_firstX && m_firstY) {
990 result.append(m_firstX->cssText());
991 result.append(' ');
992 result.append(m_firstY->cssText());
993 } else if (m_firstX)
994 result.append(m_firstX->cssText());
995 else if (m_firstY)
996 result.append(m_firstY->cssText());
997 else
998 result.appendLiteral("center");
999
1000 if (m_shape || m_sizingBehavior) {
1001 result.appendLiteral(", ");
1002 if (m_shape) {
1003 result.append(m_shape->cssText());
1004 result.append(' ');
1005 } else
1006 result.appendLiteral("ellipse ");
1007
1008 if (m_sizingBehavior)
1009 result.append(m_sizingBehavior->cssText());
1010 else
1011 result.appendLiteral("cover");
1012
1013 } else if (m_endHorizontalSize && m_endVerticalSize) {
1014 result.appendLiteral(", ");
1015 result.append(m_endHorizontalSize->cssText());
1016 result.append(' ');
1017 result.append(m_endVerticalSize->cssText());
1018 }
1019
1020 for (unsigned i = 0; i < m_stops.size(); i++) {
1021 const CSSGradientColorStop& stop = m_stops[i];
1022 result.appendLiteral(", ");
1023 result.append(stop.m_color->cssText());
1024 if (stop.m_position) {
1025 result.append(' ');
1026 result.append(stop.m_position->cssText());
1027 }
1028 }
1029 } else {
1030 if (m_repeating)
1031 result.appendLiteral("repeating-radial-gradient(");
1032 else
1033 result.appendLiteral("radial-gradient(");
1034
1035 bool wroteSomething = false;
1036
1037 // The only ambiguous case that needs an explicit shape to be provided
1038 // is when a sizing keyword is used (or all sizing is omitted).
1039 if (m_shape && m_shape->valueID() != CSSValueEllipse && (m_sizingBehavior || (!m_sizingBehavior && !m_endHorizontalSize))) {
1040 result.appendLiteral("circle");
1041 wroteSomething = true;
1042 }
1043
1044 if (m_sizingBehavior && m_sizingBehavior->valueID() != CSSValueFarthestCorner) {
1045 if (wroteSomething)
1046 result.append(' ');
1047 result.append(m_sizingBehavior->cssText());
1048 wroteSomething = true;
1049 } else if (m_endHorizontalSize) {
1050 if (wroteSomething)
1051 result.append(' ');
1052 result.append(m_endHorizontalSize->cssText());
1053 if (m_endVerticalSize) {
1054 result.append(' ');
1055 result.append(m_endVerticalSize->cssText());
1056 }
1057 wroteSomething = true;
1058 }
1059
1060 if (m_firstX || m_firstY) {
1061 if (wroteSomething)
1062 result.append(' ');
1063 result.appendLiteral("at ");
1064 if (m_firstX && m_firstY) {
1065 result.append(m_firstX->cssText());
1066 result.append(' ');
1067 result.append(m_firstY->cssText());
1068 } else if (m_firstX)
1069 result.append(m_firstX->cssText());
1070 else
1071 result.append(m_firstY->cssText());
1072 wroteSomething = true;
1073 }
1074
1075 if (wroteSomething)
1076 result.appendLiteral(", ");
1077
1078 for (unsigned i = 0; i < m_stops.size(); i++) {
1079 const CSSGradientColorStop& stop = m_stops[i];
1080 if (i)
1081 result.appendLiteral(", ");
1082 if (!stop.isMidpoint)
1083 result.append(stop.m_color->cssText());
1084 if (stop.m_position) {
1085 if (!stop.isMidpoint)
1086 result.append(' ');
1087 result.append(stop.m_position->cssText());
1088 }
1089 }
1090
1091 }
1092
1093 result.append(')');
1094 return result.toString();
1095}
1096
1097float CSSRadialGradientValue::resolveRadius(CSSPrimitiveValue& radius, const CSSToLengthConversionData& conversionData, float* widthOrHeight)
1098{
1099 float result = 0;
1100 if (radius.isNumber()) // Can the radius be a percentage?
1101 result = radius.floatValue() * conversionData.zoom();
1102 else if (widthOrHeight && radius.isPercentage())
1103 result = *widthOrHeight * radius.floatValue() / 100;
1104 else
1105 result = radius.computeLength<float>(conversionData);
1106
1107 return result;
1108}
1109
1110static float distanceToClosestCorner(const FloatPoint& p, const FloatSize& size, FloatPoint& corner)
1111{
1112 FloatPoint topLeft;
1113 float topLeftDistance = FloatSize(p - topLeft).diagonalLength();
1114
1115 FloatPoint topRight(size.width(), 0);
1116 float topRightDistance = FloatSize(p - topRight).diagonalLength();
1117
1118 FloatPoint bottomLeft(0, size.height());
1119 float bottomLeftDistance = FloatSize(p - bottomLeft).diagonalLength();
1120
1121 FloatPoint bottomRight(size.width(), size.height());
1122 float bottomRightDistance = FloatSize(p - bottomRight).diagonalLength();
1123
1124 corner = topLeft;
1125 float minDistance = topLeftDistance;
1126 if (topRightDistance < minDistance) {
1127 minDistance = topRightDistance;
1128 corner = topRight;
1129 }
1130
1131 if (bottomLeftDistance < minDistance) {
1132 minDistance = bottomLeftDistance;
1133 corner = bottomLeft;
1134 }
1135
1136 if (bottomRightDistance < minDistance) {
1137 minDistance = bottomRightDistance;
1138 corner = bottomRight;
1139 }
1140 return minDistance;
1141}
1142
1143static float distanceToFarthestCorner(const FloatPoint& p, const FloatSize& size, FloatPoint& corner)
1144{
1145 FloatPoint topLeft;
1146 float topLeftDistance = FloatSize(p - topLeft).diagonalLength();
1147
1148 FloatPoint topRight(size.width(), 0);
1149 float topRightDistance = FloatSize(p - topRight).diagonalLength();
1150
1151 FloatPoint bottomLeft(0, size.height());
1152 float bottomLeftDistance = FloatSize(p - bottomLeft).diagonalLength();
1153
1154 FloatPoint bottomRight(size.width(), size.height());
1155 float bottomRightDistance = FloatSize(p - bottomRight).diagonalLength();
1156
1157 corner = topLeft;
1158 float maxDistance = topLeftDistance;
1159 if (topRightDistance > maxDistance) {
1160 maxDistance = topRightDistance;
1161 corner = topRight;
1162 }
1163
1164 if (bottomLeftDistance > maxDistance) {
1165 maxDistance = bottomLeftDistance;
1166 corner = bottomLeft;
1167 }
1168
1169 if (bottomRightDistance > maxDistance) {
1170 maxDistance = bottomRightDistance;
1171 corner = bottomRight;
1172 }
1173 return maxDistance;
1174}
1175
1176// Compute horizontal radius of ellipse with center at 0,0 which passes through p, and has
1177// width/height given by aspectRatio.
1178static inline float horizontalEllipseRadius(const FloatSize& p, float aspectRatio)
1179{
1180 // x^2/a^2 + y^2/b^2 = 1
1181 // a/b = aspectRatio, b = a/aspectRatio
1182 // a = sqrt(x^2 + y^2/(1/r^2))
1183 return sqrtf(p.width() * p.width() + (p.height() * p.height()) / (1 / (aspectRatio * aspectRatio)));
1184}
1185
1186// FIXME: share code with the linear version
1187Ref<Gradient> CSSRadialGradientValue::createGradient(RenderElement& renderer, const FloatSize& size)
1188{
1189 ASSERT(!size.isEmpty());
1190
1191 CSSToLengthConversionData conversionData(&renderer.style(), renderer.document().documentElement()->renderStyle(), &renderer.view());
1192
1193 FloatPoint firstPoint = computeEndPoint(m_firstX.get(), m_firstY.get(), conversionData, size);
1194 if (!m_firstX)
1195 firstPoint.setX(size.width() / 2);
1196 if (!m_firstY)
1197 firstPoint.setY(size.height() / 2);
1198
1199 FloatPoint secondPoint = computeEndPoint(m_secondX.get(), m_secondY.get(), conversionData, size);
1200 if (!m_secondX)
1201 secondPoint.setX(size.width() / 2);
1202 if (!m_secondY)
1203 secondPoint.setY(size.height() / 2);
1204
1205 float firstRadius = 0;
1206 if (m_firstRadius)
1207 firstRadius = resolveRadius(*m_firstRadius, conversionData);
1208
1209 float secondRadius = 0;
1210 float aspectRatio = 1; // width / height.
1211 if (m_secondRadius)
1212 secondRadius = resolveRadius(*m_secondRadius, conversionData);
1213 else if (m_endHorizontalSize) {
1214 float width = size.width();
1215 float height = size.height();
1216 secondRadius = resolveRadius(*m_endHorizontalSize, conversionData, &width);
1217 if (m_endVerticalSize)
1218 aspectRatio = secondRadius / resolveRadius(*m_endVerticalSize, conversionData, &height);
1219 else
1220 aspectRatio = 1;
1221 } else {
1222 enum GradientShape { Circle, Ellipse };
1223 GradientShape shape = Ellipse;
1224 if ((m_shape && m_shape->valueID() == CSSValueCircle)
1225 || (!m_shape && !m_sizingBehavior && m_endHorizontalSize && !m_endVerticalSize))
1226 shape = Circle;
1227
1228 enum GradientFill { ClosestSide, ClosestCorner, FarthestSide, FarthestCorner };
1229 GradientFill fill = FarthestCorner;
1230
1231 switch (m_sizingBehavior ? m_sizingBehavior->valueID() : 0) {
1232 case CSSValueContain:
1233 case CSSValueClosestSide:
1234 fill = ClosestSide;
1235 break;
1236 case CSSValueClosestCorner:
1237 fill = ClosestCorner;
1238 break;
1239 case CSSValueFarthestSide:
1240 fill = FarthestSide;
1241 break;
1242 case CSSValueCover:
1243 case CSSValueFarthestCorner:
1244 fill = FarthestCorner;
1245 break;
1246 default:
1247 break;
1248 }
1249
1250 // Now compute the end radii based on the second point, shape and fill.
1251
1252 // Horizontal
1253 switch (fill) {
1254 case ClosestSide: {
1255 float xDist = std::min(secondPoint.x(), size.width() - secondPoint.x());
1256 float yDist = std::min(secondPoint.y(), size.height() - secondPoint.y());
1257 if (shape == Circle) {
1258 float smaller = std::min(xDist, yDist);
1259 xDist = smaller;
1260 yDist = smaller;
1261 }
1262 secondRadius = xDist;
1263 aspectRatio = xDist / yDist;
1264 break;
1265 }
1266 case FarthestSide: {
1267 float xDist = std::max(secondPoint.x(), size.width() - secondPoint.x());
1268 float yDist = std::max(secondPoint.y(), size.height() - secondPoint.y());
1269 if (shape == Circle) {
1270 float larger = std::max(xDist, yDist);
1271 xDist = larger;
1272 yDist = larger;
1273 }
1274 secondRadius = xDist;
1275 aspectRatio = xDist / yDist;
1276 break;
1277 }
1278 case ClosestCorner: {
1279 FloatPoint corner;
1280 float distance = distanceToClosestCorner(secondPoint, size, corner);
1281 if (shape == Circle)
1282 secondRadius = distance;
1283 else {
1284 // If <shape> is ellipse, the gradient-shape has the same ratio of width to height
1285 // that it would if closest-side or farthest-side were specified, as appropriate.
1286 float xDist = std::min(secondPoint.x(), size.width() - secondPoint.x());
1287 float yDist = std::min(secondPoint.y(), size.height() - secondPoint.y());
1288
1289 secondRadius = horizontalEllipseRadius(corner - secondPoint, xDist / yDist);
1290 aspectRatio = xDist / yDist;
1291 }
1292 break;
1293 }
1294
1295 case FarthestCorner: {
1296 FloatPoint corner;
1297 float distance = distanceToFarthestCorner(secondPoint, size, corner);
1298 if (shape == Circle)
1299 secondRadius = distance;
1300 else {
1301 // If <shape> is ellipse, the gradient-shape has the same ratio of width to height
1302 // that it would if closest-side or farthest-side were specified, as appropriate.
1303 float xDist = std::max(secondPoint.x(), size.width() - secondPoint.x());
1304 float yDist = std::max(secondPoint.y(), size.height() - secondPoint.y());
1305
1306 secondRadius = horizontalEllipseRadius(corner - secondPoint, xDist / yDist);
1307 aspectRatio = xDist / yDist;
1308 }
1309 break;
1310 }
1311 }
1312 }
1313
1314 // computeStops() only uses maxExtent for repeating gradients.
1315 float maxExtent = 0;
1316 if (m_repeating) {
1317 FloatPoint corner;
1318 maxExtent = distanceToFarthestCorner(secondPoint, size, corner);
1319 }
1320
1321 Gradient::RadialData data { firstPoint, secondPoint, firstRadius, secondRadius, aspectRatio };
1322 RadialGradientAdapter adapter { data };
1323 auto stops = computeStops(adapter, conversionData, renderer.style(), maxExtent);
1324
1325 auto gradient = Gradient::create(WTFMove(data));
1326 gradient->setSortedColorStops(WTFMove(stops));
1327 return gradient;
1328}
1329
1330bool CSSRadialGradientValue::equals(const CSSRadialGradientValue& other) const
1331{
1332 if (m_gradientType == CSSDeprecatedRadialGradient)
1333 return other.m_gradientType == m_gradientType
1334 && compareCSSValuePtr(m_firstX, other.m_firstX)
1335 && compareCSSValuePtr(m_firstY, other.m_firstY)
1336 && compareCSSValuePtr(m_secondX, other.m_secondX)
1337 && compareCSSValuePtr(m_secondY, other.m_secondY)
1338 && compareCSSValuePtr(m_firstRadius, other.m_firstRadius)
1339 && compareCSSValuePtr(m_secondRadius, other.m_secondRadius)
1340 && m_stops == other.m_stops;
1341
1342 if (m_repeating != other.m_repeating)
1343 return false;
1344
1345 bool equalXandY = false;
1346 if (m_firstX && m_firstY)
1347 equalXandY = compareCSSValuePtr(m_firstX, other.m_firstX) && compareCSSValuePtr(m_firstY, other.m_firstY);
1348 else if (m_firstX)
1349 equalXandY = compareCSSValuePtr(m_firstX, other.m_firstX) && !other.m_firstY;
1350 else if (m_firstY)
1351 equalXandY = compareCSSValuePtr(m_firstY, other.m_firstY) && !other.m_firstX;
1352 else
1353 equalXandY = !other.m_firstX && !other.m_firstY;
1354
1355 if (!equalXandY)
1356 return false;
1357
1358 bool equalShape = true;
1359 bool equalSizingBehavior = true;
1360 bool equalHorizontalAndVerticalSize = true;
1361
1362 if (m_shape)
1363 equalShape = compareCSSValuePtr(m_shape, other.m_shape);
1364 else if (m_sizingBehavior)
1365 equalSizingBehavior = compareCSSValuePtr(m_sizingBehavior, other.m_sizingBehavior);
1366 else if (m_endHorizontalSize && m_endVerticalSize)
1367 equalHorizontalAndVerticalSize = compareCSSValuePtr(m_endHorizontalSize, other.m_endHorizontalSize) && compareCSSValuePtr(m_endVerticalSize, other.m_endVerticalSize);
1368 else {
1369 equalShape = !other.m_shape;
1370 equalSizingBehavior = !other.m_sizingBehavior;
1371 equalHorizontalAndVerticalSize = !other.m_endHorizontalSize && !other.m_endVerticalSize;
1372 }
1373 return equalShape && equalSizingBehavior && equalHorizontalAndVerticalSize && m_stops == other.m_stops;
1374}
1375
1376
1377String CSSConicGradientValue::customCSSText() const
1378{
1379 StringBuilder result;
1380
1381 if (m_repeating)
1382 result.appendLiteral("repeating-conic-gradient(");
1383 else
1384 result.appendLiteral("conic-gradient(");
1385
1386 bool wroteSomething = false;
1387
1388 if (m_angle) {
1389 result.appendLiteral("from ");
1390 result.append(m_angle->cssText());
1391 wroteSomething = true;
1392 }
1393
1394 if (m_firstX && m_firstY) {
1395 if (wroteSomething)
1396 result.appendLiteral(" ");
1397 result.appendLiteral("at ");
1398 result.append(m_firstX->cssText());
1399 result.append(' ');
1400 result.append(m_firstY->cssText());
1401 wroteSomething = true;
1402 }
1403
1404 if (wroteSomething)
1405 result.appendLiteral(", ");
1406
1407 bool wroteFirstStop = false;
1408 for (auto& stop : m_stops) {
1409 if (wroteFirstStop)
1410 result.appendLiteral(", ");
1411 wroteFirstStop = true;
1412 if (!stop.isMidpoint)
1413 result.append(stop.m_color->cssText());
1414 if (stop.m_position) {
1415 if (!stop.isMidpoint)
1416 result.append(' ');
1417 result.append(stop.m_position->cssText());
1418 }
1419 }
1420
1421 result.append(')');
1422 return result.toString();
1423}
1424
1425Ref<Gradient> CSSConicGradientValue::createGradient(RenderElement& renderer, const FloatSize& size)
1426{
1427 ASSERT(!size.isEmpty());
1428
1429 CSSToLengthConversionData conversionData(&renderer.style(), renderer.document().documentElement()->renderStyle(), &renderer.view());
1430
1431 FloatPoint centerPoint = computeEndPoint(m_firstX.get(), m_firstY.get(), conversionData, size);
1432 if (!m_firstX)
1433 centerPoint.setX(size.width() / 2);
1434 if (!m_firstY)
1435 centerPoint.setY(size.height() / 2);
1436
1437 float angleRadians = 0;
1438 if (m_angle)
1439 angleRadians = m_angle->floatValue(CSSPrimitiveValue::CSS_RAD);
1440
1441 Gradient::ConicData data { centerPoint, angleRadians };
1442 ConicGradientAdapter adapter;
1443 auto stops = computeStops(adapter, conversionData, renderer.style(), 1);
1444
1445 auto gradient = Gradient::create(WTFMove(data));
1446 gradient->setSortedColorStops(WTFMove(stops));
1447 return gradient;
1448}
1449
1450bool CSSConicGradientValue::equals(const CSSConicGradientValue& other) const
1451{
1452 if (m_repeating != other.m_repeating)
1453 return false;
1454
1455 if (!compareCSSValuePtr(m_angle, other.m_angle))
1456 return false;
1457
1458 bool equalXandY = false;
1459 if (m_firstX && m_firstY)
1460 equalXandY = compareCSSValuePtr(m_firstX, other.m_firstX) && compareCSSValuePtr(m_firstY, other.m_firstY);
1461 else if (m_firstX)
1462 equalXandY = compareCSSValuePtr(m_firstX, other.m_firstX) && !other.m_firstY;
1463 else if (m_firstY)
1464 equalXandY = compareCSSValuePtr(m_firstY, other.m_firstY) && !other.m_firstX;
1465 else
1466 equalXandY = !other.m_firstX && !other.m_firstY;
1467
1468 return equalXandY && m_stops == other.m_stops;
1469}
1470
1471} // namespace WebCore
1472