source: webkit/trunk/Source/WebCore/css/CSSGradientValue.cpp

Last change on this file was 295675, checked in by Antti Koivisto, 3 years ago

[CSS Container Queries] Container units don't work in gradients
https://bugs.webkit.org/show_bug.cgi?id=241780

Reviewed by Tim Nguyen.

  • LayoutTests/TestExpectations:
  • Source/WebCore/css/CSSGradientValue.cpp:

(WebCore::CSSLinearGradientValue::createGradient):
(WebCore::CSSRadialGradientValue::createGradient):
(WebCore::CSSConicGradientValue::createGradient):

Provide the element to CSSToLengthConversionData so a container can be selected.

  • Source/WebCore/css/CSSPrimitiveValue.cpp:

(WebCore::CSSPrimitiveValue::computeNonCalcLengthDouble):

  • Source/WebCore/css/CSSToLengthConversionData.cpp:

(WebCore::CSSToLengthConversionData::CSSToLengthConversionData):

  • Source/WebCore/css/CSSToLengthConversionData.h:

(WebCore::CSSToLengthConversionData::elementForContainerUnitResolution const):
(WebCore::CSSToLengthConversionData::element const): Deleted.

Rename for clarity.

Canonical link: https://commits.webkit.org/251680@main

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