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 |
|
---|
42 | namespace WebCore {
|
---|
43 |
|
---|
44 | static 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 |
|
---|
53 | RefPtr<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 |
|
---|
70 | struct 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 |
|
---|
78 | static 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 |
|
---|
88 | template<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 |
|
---|
96 | bool 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 |
|
---|
110 | Ref<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 |
|
---|
119 | void CSSGradientValue::resolveRGBColors()
|
---|
120 | {
|
---|
121 | resolveStopColors(m_stops, [&](const CSSPrimitiveValue& colorValue) {
|
---|
122 | ASSERT(colorValue.isRGBColor());
|
---|
123 | return colorValue.color();
|
---|
124 | });
|
---|
125 | }
|
---|
126 |
|
---|
127 | class LinearGradientAdapter {
|
---|
128 | public:
|
---|
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 |
|
---|
162 | private:
|
---|
163 | Gradient::LinearData& m_data;
|
---|
164 | };
|
---|
165 |
|
---|
166 | class RadialGradientAdapter {
|
---|
167 | public:
|
---|
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 |
|
---|
229 | private:
|
---|
230 | Gradient::RadialData& m_data;
|
---|
231 | };
|
---|
232 |
|
---|
233 | class ConicGradientAdapter {
|
---|
234 | public:
|
---|
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 |
|
---|
303 | template<typename GradientAdapter>
|
---|
304 | GradientColorStops 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 |
|
---|
561 | static 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.
|
---|
614 | static 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 |
|
---|
619 | bool 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 |
|
---|
630 | bool 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 |
|
---|
643 | bool 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 |
|
---|
655 | static 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 |
|
---|
675 | static 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 |
|
---|
731 | static 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 |
|
---|
744 | template<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 |
|
---|
754 | static void writeColorStop(StringBuilder& builder, const CSSGradientColorStop& stop)
|
---|
755 | {
|
---|
756 | appendSpaceSeparatedOptionalCSSPtrText(builder, stop.color, stop.position);
|
---|
757 | }
|
---|
758 |
|
---|
759 | String 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.
|
---|
813 | static 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 |
|
---|
878 | Ref<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 |
|
---|
946 | bool CSSLinearGradientValue::equals(const CSSLinearGradientValue& other) const
|
---|
947 | {
|
---|
948 | return CSSGradientValue::equals(other) && compareCSSValuePtr(m_angle, other.m_angle);
|
---|
949 | }
|
---|
950 |
|
---|
951 | String 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 |
|
---|
1043 | float 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 |
|
---|
1059 | static 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 |
|
---|
1092 | static 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.
|
---|
1127 | static 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
|
---|
1136 | Ref<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 |
|
---|
1281 | bool 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 |
|
---|
1290 | String 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 |
|
---|
1329 | Ref<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 |
|
---|
1356 | bool CSSConicGradientValue::equals(const CSSConicGradientValue& other) const
|
---|
1357 | {
|
---|
1358 | return CSSGradientValue::equals(other) && compareCSSValuePtr(m_angle, other.m_angle);
|
---|
1359 | }
|
---|
1360 |
|
---|
1361 | } // namespace WebCore
|
---|