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