1289 lines
48 KiB
C++
1289 lines
48 KiB
C++
/*
|
|
* Copyright (C) 2008-2021 Apple Inc. All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
|
|
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
|
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
|
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include "config.h"
|
|
#include "CSSGradientValue.h"
|
|
|
|
#include "AnimationUtilities.h"
|
|
#include "CSSCalcValue.h"
|
|
#include "CSSToLengthConversionData.h"
|
|
#include "CSSValueKeywords.h"
|
|
#include "ColorBlending.h"
|
|
#include "GradientImage.h"
|
|
#include "NodeRenderStyle.h"
|
|
#include "Pair.h"
|
|
#include "RenderElement.h"
|
|
#include "RenderView.h"
|
|
#include "StyleBuilderState.h"
|
|
#include <wtf/text/StringBuilder.h>
|
|
|
|
namespace WebCore {
|
|
|
|
static inline Ref<Gradient> createGradient(CSSGradientValue& value, RenderElement& renderer, FloatSize size)
|
|
{
|
|
if (is<CSSLinearGradientValue>(value))
|
|
return downcast<CSSLinearGradientValue>(value).createGradient(renderer, size);
|
|
if (is<CSSRadialGradientValue>(value))
|
|
return downcast<CSSRadialGradientValue>(value).createGradient(renderer, size);
|
|
return downcast<CSSConicGradientValue>(value).createGradient(renderer, size);
|
|
}
|
|
|
|
RefPtr<Image> CSSGradientValue::image(RenderElement& renderer, const FloatSize& size)
|
|
{
|
|
if (size.isEmpty())
|
|
return nullptr;
|
|
bool cacheable = isCacheable() && !renderer.style().hasAppleColorFilter();
|
|
if (cacheable) {
|
|
if (!clients().contains(&renderer))
|
|
return nullptr;
|
|
if (auto* result = cachedImageForSize(size))
|
|
return result;
|
|
}
|
|
auto newImage = GradientImage::create(createGradient(*this, renderer, size), size);
|
|
if (cacheable)
|
|
saveCachedImageForSize(size, newImage);
|
|
return newImage;
|
|
}
|
|
|
|
struct GradientStop {
|
|
Color color;
|
|
std::optional<float> offset;
|
|
|
|
bool isSpecified() const { return offset.has_value(); }
|
|
bool isMidpoint() const { return !color.isValid(); }
|
|
};
|
|
|
|
static inline Ref<CSSGradientValue> clone(CSSGradientValue& value)
|
|
{
|
|
if (is<CSSLinearGradientValue>(value))
|
|
return downcast<CSSLinearGradientValue>(value).clone();
|
|
if (is<CSSRadialGradientValue>(value))
|
|
return downcast<CSSRadialGradientValue>(value).clone();
|
|
ASSERT(is<CSSConicGradientValue>(value));
|
|
return downcast<CSSConicGradientValue>(value).clone();
|
|
}
|
|
|
|
template<typename Function> void resolveStopColors(Vector<CSSGradientColorStop, 2>& stops, Function&& colorResolveFunction)
|
|
{
|
|
for (auto& stop : stops) {
|
|
if (stop.color)
|
|
stop.resolvedColor = colorResolveFunction(*stop.color);
|
|
}
|
|
}
|
|
|
|
bool CSSGradientValue::hasColorDerivedFromElement() const
|
|
{
|
|
if (!m_hasColorDerivedFromElement) {
|
|
m_hasColorDerivedFromElement = false;
|
|
for (auto& stop : m_stops) {
|
|
if (stop.color && Style::BuilderState::isColorFromPrimitiveValueDerivedFromElement(*stop.color)) {
|
|
m_hasColorDerivedFromElement = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return *m_hasColorDerivedFromElement;
|
|
}
|
|
|
|
Ref<CSSGradientValue> CSSGradientValue::valueWithStylesResolved(Style::BuilderState& builderState)
|
|
{
|
|
auto result = hasColorDerivedFromElement() ? clone(*this) : makeRef(*this);
|
|
resolveStopColors(result->m_stops, [&](const CSSPrimitiveValue& colorValue) {
|
|
return builderState.colorFromPrimitiveValueWithResolvedCurrentColor(colorValue);
|
|
});
|
|
return result;
|
|
}
|
|
|
|
void CSSGradientValue::resolveRGBColors()
|
|
{
|
|
resolveStopColors(m_stops, [&](const CSSPrimitiveValue& colorValue) {
|
|
ASSERT(colorValue.isRGBColor());
|
|
return colorValue.color();
|
|
});
|
|
}
|
|
|
|
class LinearGradientAdapter {
|
|
public:
|
|
explicit LinearGradientAdapter(Gradient::LinearData& data)
|
|
: m_data(data)
|
|
{
|
|
}
|
|
|
|
float gradientLength() const
|
|
{
|
|
auto gradientSize = m_data.point0 - m_data.point1;
|
|
return gradientSize.diagonalLength();
|
|
}
|
|
float maxExtent(float, float) const { return 1; }
|
|
|
|
void normalizeStopsAndEndpointsOutsideRange(Vector<GradientStop>& stops)
|
|
{
|
|
float firstOffset = *stops.first().offset;
|
|
float lastOffset = *stops.last().offset;
|
|
if (firstOffset != lastOffset) {
|
|
float scale = lastOffset - firstOffset;
|
|
|
|
for (auto& stop : stops)
|
|
stop.offset = (*stop.offset - firstOffset) / scale;
|
|
|
|
auto p0 = m_data.point0;
|
|
auto p1 = m_data.point1;
|
|
m_data.point0 = { p0.x() + firstOffset * (p1.x() - p0.x()), p0.y() + firstOffset * (p1.y() - p0.y()) };
|
|
m_data.point1 = { p1.x() + (lastOffset - 1) * (p1.x() - p0.x()), p1.y() + (lastOffset - 1) * (p1.y() - p0.y()) };
|
|
} else {
|
|
// There's a single position that is outside the scale, clamp the positions to 1.
|
|
for (auto& stop : stops)
|
|
stop.offset = 1;
|
|
}
|
|
}
|
|
|
|
private:
|
|
Gradient::LinearData& m_data;
|
|
};
|
|
|
|
class RadialGradientAdapter {
|
|
public:
|
|
explicit RadialGradientAdapter(Gradient::RadialData& data)
|
|
: m_data(data)
|
|
{
|
|
}
|
|
|
|
float gradientLength() const { return m_data.endRadius; }
|
|
|
|
// Radial gradients may need to extend further than the endpoints, because they have
|
|
// to repeat out to the corners of the box.
|
|
float maxExtent(float maxLengthForRepeat, float gradientLength) const
|
|
{
|
|
if (maxLengthForRepeat > gradientLength)
|
|
return gradientLength > 0 ? maxLengthForRepeat / gradientLength : 0;
|
|
return 1;
|
|
}
|
|
|
|
void normalizeStopsAndEndpointsOutsideRange(Vector<GradientStop>& stops)
|
|
{
|
|
auto numStops = stops.size();
|
|
|
|
// Rather than scaling the points < 0, we truncate them, so only scale according to the largest point.
|
|
float firstOffset = 0;
|
|
float lastOffset = *stops.last().offset;
|
|
float scale = lastOffset - firstOffset;
|
|
|
|
// Reset points below 0 to the first visible color.
|
|
size_t firstZeroOrGreaterIndex = numStops;
|
|
for (size_t i = 0; i < numStops; ++i) {
|
|
if (*stops[i].offset >= 0) {
|
|
firstZeroOrGreaterIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (firstZeroOrGreaterIndex > 0) {
|
|
if (firstZeroOrGreaterIndex < numStops && *stops[firstZeroOrGreaterIndex].offset > 0) {
|
|
float prevOffset = *stops[firstZeroOrGreaterIndex - 1].offset;
|
|
float nextOffset = *stops[firstZeroOrGreaterIndex].offset;
|
|
|
|
float interStopProportion = -prevOffset / (nextOffset - prevOffset);
|
|
// FIXME: when we interpolate gradients using premultiplied colors, this should do premultiplication.
|
|
Color blendedColor = blend(stops[firstZeroOrGreaterIndex - 1].color, stops[firstZeroOrGreaterIndex].color, { interStopProportion });
|
|
|
|
// Clamp the positions to 0 and set the color.
|
|
for (size_t i = 0; i < firstZeroOrGreaterIndex; ++i) {
|
|
stops[i].offset = 0;
|
|
stops[i].color = blendedColor;
|
|
}
|
|
} else {
|
|
// All stops are below 0; just clamp them.
|
|
for (size_t i = 0; i < firstZeroOrGreaterIndex; ++i)
|
|
stops[i].offset = 0;
|
|
}
|
|
}
|
|
|
|
for (auto& stop : stops)
|
|
*stop.offset /= scale;
|
|
|
|
m_data.startRadius *= scale;
|
|
m_data.endRadius *= scale;
|
|
}
|
|
|
|
private:
|
|
Gradient::RadialData& m_data;
|
|
};
|
|
|
|
class ConicGradientAdapter {
|
|
public:
|
|
float gradientLength() const { return 1; }
|
|
float maxExtent(float, float) const { return 1; }
|
|
|
|
void normalizeStopsAndEndpointsOutsideRange(Vector<GradientStop>& stops)
|
|
{
|
|
size_t numStops = stops.size();
|
|
size_t lastStopIndex = numStops - 1;
|
|
|
|
std::optional<size_t> firstZeroOrGreaterIndex;
|
|
for (size_t i = 0; i < numStops; ++i) {
|
|
if (*stops[i].offset >= 0) {
|
|
firstZeroOrGreaterIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (firstZeroOrGreaterIndex) {
|
|
size_t index = *firstZeroOrGreaterIndex;
|
|
if (index > 0) {
|
|
float previousOffset = *stops[index - 1].offset;
|
|
float nextOffset = *stops[index].offset;
|
|
|
|
float interStopProportion = -previousOffset / (nextOffset - previousOffset);
|
|
// FIXME: when we interpolate gradients using premultiplied colors, this should do premultiplication.
|
|
Color blendedColor = blend(stops[index - 1].color, stops[index].color, { interStopProportion });
|
|
|
|
// Clamp the positions to 0 and set the color.
|
|
for (size_t i = 0; i < index; ++i) {
|
|
stops[i].offset = 0;
|
|
stops[i].color = blendedColor;
|
|
}
|
|
}
|
|
} else {
|
|
// All stop offsets below 0, clamp them.
|
|
for (auto& stop : stops)
|
|
stop.offset = 0;
|
|
}
|
|
|
|
std::optional<size_t> lastOneOrLessIndex;
|
|
for (int i = lastStopIndex; i >= 0; --i) {
|
|
if (*stops[i].offset <= 1) {
|
|
lastOneOrLessIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (lastOneOrLessIndex) {
|
|
size_t index = *lastOneOrLessIndex;
|
|
if (index < lastStopIndex) {
|
|
float previousOffset = *stops[index].offset;
|
|
float nextOffset = *stops[index + 1].offset;
|
|
|
|
float interStopProportion = (1 - previousOffset) / (nextOffset - previousOffset);
|
|
// FIXME: when we interpolate gradients using premultiplied colors, this should do premultiplication.
|
|
Color blendedColor = blend(stops[index].color, stops[index + 1].color, { interStopProportion });
|
|
|
|
// Clamp the positions to 1 and set the color.
|
|
for (size_t i = index + 1; i < numStops; ++i) {
|
|
stops[i].offset = 1;
|
|
stops[i].color = blendedColor;
|
|
}
|
|
}
|
|
} else {
|
|
// All stop offsets above 1, clamp them.
|
|
for (auto& stop : stops)
|
|
stop.offset = 1;
|
|
}
|
|
}
|
|
};
|
|
|
|
template<typename GradientAdapter>
|
|
Gradient::ColorStopVector CSSGradientValue::computeStops(GradientAdapter& gradientAdapter, const CSSToLengthConversionData& conversionData, const RenderStyle& style, float maxLengthForRepeat)
|
|
{
|
|
if (m_gradientType == CSSDeprecatedLinearGradient || m_gradientType == CSSDeprecatedRadialGradient) {
|
|
Gradient::ColorStopVector result;
|
|
result.reserveInitialCapacity(m_stops.size());
|
|
|
|
for (auto& stop : m_stops) {
|
|
float offset;
|
|
if (stop.position->isPercentage())
|
|
offset = stop.position->floatValue(CSSUnitType::CSS_PERCENTAGE) / 100;
|
|
else
|
|
offset = stop.position->floatValue(CSSUnitType::CSS_NUMBER);
|
|
|
|
Color color = stop.resolvedColor;
|
|
if (style.hasAppleColorFilter())
|
|
style.appleColorFilter().transformColor(color);
|
|
result.uncheckedAppend({ offset, color });
|
|
}
|
|
|
|
std::stable_sort(result.begin(), result.end(), [] (const Gradient::ColorStop& a, const Gradient::ColorStop& b) {
|
|
return a.offset < b.offset;
|
|
});
|
|
|
|
return result;
|
|
}
|
|
|
|
size_t numStops = m_stops.size();
|
|
Vector<GradientStop> stops(numStops);
|
|
|
|
float gradientLength = gradientAdapter.gradientLength();
|
|
|
|
for (size_t i = 0; i < numStops; ++i) {
|
|
auto& stop = m_stops[i];
|
|
|
|
Color color = stop.resolvedColor;
|
|
if (style.hasAppleColorFilter())
|
|
style.appleColorFilter().transformColor(color);
|
|
|
|
stops[i].color = color;
|
|
|
|
if (stop.position) {
|
|
auto& positionValue = *stop.position;
|
|
if (positionValue.isPercentage())
|
|
stops[i].offset = positionValue.floatValue(CSSUnitType::CSS_PERCENTAGE) / 100;
|
|
else if (positionValue.isLength() || positionValue.isViewportPercentageLength() || positionValue.isCalculatedPercentageWithLength()) {
|
|
float length;
|
|
if (positionValue.isLength())
|
|
length = positionValue.computeLength<float>(conversionData);
|
|
else {
|
|
Ref<CalculationValue> calculationValue { positionValue.cssCalcValue()->createCalculationValue(conversionData) };
|
|
length = calculationValue->evaluate(gradientLength);
|
|
}
|
|
stops[i].offset = (gradientLength > 0) ? length / gradientLength : 0;
|
|
} else if (positionValue.isAngle())
|
|
stops[i].offset = positionValue.floatValue(CSSUnitType::CSS_DEG) / 360;
|
|
else {
|
|
ASSERT_NOT_REACHED();
|
|
stops[i].offset = 0;
|
|
}
|
|
} else {
|
|
// If the first color-stop does not have a position, its position defaults to 0%.
|
|
// If the last color-stop does not have a position, its position defaults to 100%.
|
|
if (!i)
|
|
stops[i].offset = 0;
|
|
else if (numStops > 1 && i == numStops - 1)
|
|
stops[i].offset = 1;
|
|
}
|
|
|
|
// If a color-stop has a position that is less than the specified position of any
|
|
// color-stop before it in the list, its position is changed to be equal to the
|
|
// largest specified position of any color-stop before it.
|
|
if (stops[i].isSpecified() && i > 0) {
|
|
size_t prevSpecifiedIndex;
|
|
for (prevSpecifiedIndex = i - 1; prevSpecifiedIndex; --prevSpecifiedIndex) {
|
|
if (stops[prevSpecifiedIndex].isSpecified())
|
|
break;
|
|
}
|
|
|
|
if (*stops[i].offset < *stops[prevSpecifiedIndex].offset)
|
|
stops[i].offset = stops[prevSpecifiedIndex].offset;
|
|
}
|
|
}
|
|
|
|
ASSERT(stops[0].isSpecified() && stops[numStops - 1].isSpecified());
|
|
|
|
// If any color-stop still does not have a position, then, for each run of adjacent
|
|
// color-stops without positions, set their positions so that they are evenly spaced
|
|
// between the preceding and following color-stops with positions.
|
|
if (numStops > 2) {
|
|
size_t unspecifiedRunStart = 0;
|
|
bool inUnspecifiedRun = false;
|
|
|
|
for (size_t i = 0; i < numStops; ++i) {
|
|
if (!stops[i].isSpecified() && !inUnspecifiedRun) {
|
|
unspecifiedRunStart = i;
|
|
inUnspecifiedRun = true;
|
|
} else if (stops[i].isSpecified() && inUnspecifiedRun) {
|
|
size_t unspecifiedRunEnd = i;
|
|
|
|
if (unspecifiedRunStart < unspecifiedRunEnd) {
|
|
float lastSpecifiedOffset = *stops[unspecifiedRunStart - 1].offset;
|
|
float nextSpecifiedOffset = *stops[unspecifiedRunEnd].offset;
|
|
float delta = (nextSpecifiedOffset - lastSpecifiedOffset) / (unspecifiedRunEnd - unspecifiedRunStart + 1);
|
|
|
|
for (size_t j = unspecifiedRunStart; j < unspecifiedRunEnd; ++j)
|
|
stops[j].offset = lastSpecifiedOffset + (j - unspecifiedRunStart + 1) * delta;
|
|
}
|
|
|
|
inUnspecifiedRun = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Walk over the color stops, look for midpoints and add stops as needed.
|
|
// If mid < 50%, add 2 stops to the left and 6 to the right
|
|
// else add 6 stops to the left and 2 to the right.
|
|
// Stops on the side with the most stops start midway because the curve approximates
|
|
// a line in that region. We then add 5 more color stops on that side to minimize the change
|
|
// how the luminance changes at each of the color stops. We don't have to add as many on the other side
|
|
// since it becomes small which increases the differentation of luminance which hides the color stops.
|
|
// Even with 4 extra color stops, it *is* possible to discern the steps when the gradient is large and has
|
|
// large luminance differences between midpoint and color stop. If this becomes an issue, we can consider
|
|
// making this algorithm a bit smarter.
|
|
|
|
// Midpoints that coincide with color stops are treated specially since they don't require
|
|
// extra stops and generate hard lines.
|
|
for (size_t x = 1; x < stops.size() - 1;) {
|
|
if (!stops[x].isMidpoint()) {
|
|
++x;
|
|
continue;
|
|
}
|
|
|
|
// Find previous and next color so we know what to interpolate between.
|
|
// We already know they have a color since we checked for that earlier.
|
|
Color color1 = stops[x - 1].color;
|
|
Color color2 = stops[x + 1].color;
|
|
// Likewise find the position of previous and next color stop.
|
|
float offset1 = *stops[x - 1].offset;
|
|
float offset2 = *stops[x + 1].offset;
|
|
float offset = *stops[x].offset;
|
|
|
|
// Check if everything coincides or the midpoint is exactly in the middle.
|
|
// If so, ignore the midpoint.
|
|
if (offset - offset1 == offset2 - offset) {
|
|
stops.remove(x);
|
|
continue;
|
|
}
|
|
|
|
// Check if we coincide with the left color stop.
|
|
if (offset1 == offset) {
|
|
// Morph the midpoint to a regular stop with the color of the next color stop.
|
|
stops[x].color = color2;
|
|
continue;
|
|
}
|
|
|
|
// Check if we coincide with the right color stop.
|
|
if (offset2 == offset) {
|
|
// Morph the midpoint to a regular stop with the color of the previous color stop.
|
|
stops[x].color = color1;
|
|
continue;
|
|
}
|
|
|
|
float midpoint = (offset - offset1) / (offset2 - offset1);
|
|
GradientStop newStops[9];
|
|
if (midpoint > .5f) {
|
|
for (size_t y = 0; y < 7; ++y)
|
|
newStops[y].offset = offset1 + (offset - offset1) * (7 + y) / 13;
|
|
|
|
newStops[7].offset = offset + (offset2 - offset) / 3;
|
|
newStops[8].offset = offset + (offset2 - offset) * 2 / 3;
|
|
} else {
|
|
newStops[0].offset = offset1 + (offset - offset1) / 3;
|
|
newStops[1].offset = offset1 + (offset - offset1) * 2 / 3;
|
|
|
|
for (size_t y = 0; y < 7; ++y)
|
|
newStops[y + 2].offset = offset + (offset2 - offset) * y / 13;
|
|
}
|
|
// calculate colors
|
|
for (size_t y = 0; y < 9; ++y) {
|
|
float relativeOffset = (*newStops[y].offset - offset1) / (offset2 - offset1);
|
|
float multiplier = std::pow(relativeOffset, std::log(.5f) / std::log(midpoint));
|
|
// FIXME: Why not premultiply here?
|
|
newStops[y].color = blendWithoutPremultiply(color1, color2, { multiplier });
|
|
}
|
|
|
|
stops.remove(x);
|
|
stops.insert(x, newStops, 9);
|
|
x += 9;
|
|
}
|
|
|
|
numStops = stops.size();
|
|
|
|
// If the gradient is repeating, repeat the color stops.
|
|
// We can't just push this logic down into the platform-specific Gradient code,
|
|
// because we have to know the extent of the gradient, and possible move the end points.
|
|
if (m_repeating && numStops > 1) {
|
|
// If the difference in the positions of the first and last color-stops is 0,
|
|
// the gradient defines a solid-color image with the color of the last color-stop in the rule.
|
|
float gradientRange = *stops.last().offset - *stops.first().offset;
|
|
if (!gradientRange) {
|
|
stops.first().offset = 0;
|
|
stops.first().color = stops.last().color;
|
|
stops.shrink(1);
|
|
numStops = 1;
|
|
} else {
|
|
float maxExtent = gradientAdapter.maxExtent(maxLengthForRepeat, gradientLength);
|
|
|
|
size_t originalNumStops = numStops;
|
|
size_t originalFirstStopIndex = 0;
|
|
|
|
// Work backwards from the first, adding stops until we get one before 0.
|
|
float firstOffset = *stops[0].offset;
|
|
if (firstOffset > 0) {
|
|
float currOffset = firstOffset;
|
|
size_t srcStopOrdinal = originalNumStops - 1;
|
|
|
|
while (true) {
|
|
GradientStop newStop = stops[originalFirstStopIndex + srcStopOrdinal];
|
|
newStop.offset = currOffset;
|
|
stops.insert(0, newStop);
|
|
++originalFirstStopIndex;
|
|
if (currOffset < 0)
|
|
break;
|
|
|
|
if (srcStopOrdinal)
|
|
currOffset -= *stops[originalFirstStopIndex + srcStopOrdinal].offset - *stops[originalFirstStopIndex + srcStopOrdinal - 1].offset;
|
|
srcStopOrdinal = (srcStopOrdinal + originalNumStops - 1) % originalNumStops;
|
|
}
|
|
}
|
|
|
|
// Work forwards from the end, adding stops until we get one after 1.
|
|
float lastOffset = *stops[stops.size() - 1].offset;
|
|
if (lastOffset < maxExtent) {
|
|
float currOffset = lastOffset;
|
|
size_t srcStopOrdinal = 0;
|
|
|
|
while (true) {
|
|
size_t srcStopIndex = originalFirstStopIndex + srcStopOrdinal;
|
|
GradientStop newStop = stops[srcStopIndex];
|
|
newStop.offset = currOffset;
|
|
stops.append(newStop);
|
|
if (currOffset > maxExtent)
|
|
break;
|
|
if (srcStopOrdinal < originalNumStops - 1)
|
|
currOffset += *stops[srcStopIndex + 1].offset - *stops[srcStopIndex].offset;
|
|
srcStopOrdinal = (srcStopOrdinal + 1) % originalNumStops;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If the gradient goes outside the 0-1 range, normalize it by moving the endpoints, and adjusting the stops.
|
|
if (stops.size() > 1 && (*stops.first().offset < 0 || *stops.last().offset > 1))
|
|
gradientAdapter.normalizeStopsAndEndpointsOutsideRange(stops);
|
|
|
|
Gradient::ColorStopVector result;
|
|
result.reserveInitialCapacity(stops.size());
|
|
for (auto& stop : stops)
|
|
result.uncheckedAppend({ *stop.offset, stop.color });
|
|
|
|
return result;
|
|
}
|
|
|
|
static float positionFromValue(const CSSPrimitiveValue* value, const CSSToLengthConversionData& conversionData, const FloatSize& size, bool isHorizontal)
|
|
{
|
|
if (!value)
|
|
return 0;
|
|
|
|
float origin = 0;
|
|
float sign = 1;
|
|
float edgeDistance = isHorizontal ? size.width() : size.height();
|
|
|
|
// In this case the center of the gradient is given relative to an edge in the
|
|
// form of: [ top | bottom | right | left ] [ <percentage> | <length> ].
|
|
if (value->isPair()) {
|
|
CSSValueID originID = value->pairValue()->first()->valueID();
|
|
if (originID == CSSValueRight || originID == CSSValueBottom) {
|
|
// For right/bottom, the offset is relative to the far edge.
|
|
origin = edgeDistance;
|
|
sign = -1;
|
|
}
|
|
value = value->pairValue()->second();
|
|
}
|
|
|
|
if (value->isNumber())
|
|
return origin + sign * value->floatValue() * conversionData.zoom();
|
|
|
|
if (value->isPercentage())
|
|
return origin + sign * value->floatValue() / 100 * edgeDistance;
|
|
|
|
if (value->isCalculatedPercentageWithLength())
|
|
return origin + sign * value->cssCalcValue()->createCalculationValue(conversionData)->evaluate(edgeDistance);
|
|
|
|
switch (value->valueID()) {
|
|
case CSSValueTop:
|
|
ASSERT(!isHorizontal);
|
|
return 0;
|
|
case CSSValueLeft:
|
|
ASSERT(isHorizontal);
|
|
return 0;
|
|
case CSSValueBottom:
|
|
ASSERT(!isHorizontal);
|
|
return edgeDistance;
|
|
case CSSValueRight:
|
|
ASSERT(isHorizontal);
|
|
return edgeDistance;
|
|
case CSSValueCenter:
|
|
return origin + sign * .5f * edgeDistance;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return origin + sign * value->computeLength<float>(conversionData);
|
|
}
|
|
|
|
// Resolve points/radii to front end values.
|
|
static FloatPoint computeEndPoint(const CSSPrimitiveValue* horizontal, const CSSPrimitiveValue* vertical, const CSSToLengthConversionData& conversionData, const FloatSize& size)
|
|
{
|
|
return { positionFromValue(horizontal, conversionData, size, true), positionFromValue(vertical, conversionData, size, false) };
|
|
}
|
|
|
|
bool CSSGradientValue::isCacheable() const
|
|
{
|
|
if (hasColorDerivedFromElement())
|
|
return false;
|
|
for (auto& stop : m_stops) {
|
|
if (stop.position && stop.position->isFontRelativeLength())
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool CSSGradientValue::knownToBeOpaque(const RenderElement& renderer) const
|
|
{
|
|
bool hasColorFilter = renderer.style().hasAppleColorFilter();
|
|
for (auto& stop : m_stops) {
|
|
Color color = stop.resolvedColor;
|
|
if (hasColorFilter)
|
|
renderer.style().appleColorFilter().transformColor(color);
|
|
if (!color.isOpaque())
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool CSSGradientValue::equals(const CSSGradientValue& other) const
|
|
{
|
|
return compareCSSValuePtr(m_firstX, other.m_firstX)
|
|
&& compareCSSValuePtr(m_firstY, other.m_firstY)
|
|
&& compareCSSValuePtr(m_secondX, other.m_secondX)
|
|
&& compareCSSValuePtr(m_secondY, other.m_secondY)
|
|
&& m_stops == other.m_stops
|
|
&& m_gradientType == other.m_gradientType
|
|
&& m_repeating == other.m_repeating;
|
|
}
|
|
|
|
static void appendGradientStops(StringBuilder& builder, const Vector<CSSGradientColorStop, 2>& stops)
|
|
{
|
|
for (auto& stop : stops) {
|
|
double position = stop.position->doubleValue(CSSUnitType::CSS_NUMBER);
|
|
if (!position)
|
|
builder.append(", from(", stop.color->cssText(), ')');
|
|
else if (position == 1)
|
|
builder.append(", to(", stop.color->cssText(), ')');
|
|
else
|
|
builder.append(", color-stop(", position, ", ", stop.color->cssText(), ')');
|
|
}
|
|
}
|
|
|
|
template<typename T, typename U> static void appendSpaceSeparatedOptionalCSSPtrText(StringBuilder& builder, const T& a, const U& b)
|
|
{
|
|
if (a && b)
|
|
builder.append(a->cssText(), ' ', b->cssText());
|
|
else if (a)
|
|
builder.append(a->cssText());
|
|
else if (b)
|
|
builder.append(b->cssText());
|
|
}
|
|
|
|
static void writeColorStop(StringBuilder& builder, const CSSGradientColorStop& stop)
|
|
{
|
|
appendSpaceSeparatedOptionalCSSPtrText(builder, stop.color, stop.position);
|
|
}
|
|
|
|
String CSSLinearGradientValue::customCSSText() const
|
|
{
|
|
StringBuilder result;
|
|
if (gradientType() == CSSDeprecatedLinearGradient) {
|
|
result.append("-webkit-gradient(linear, ", firstX()->cssText(), ' ', firstY()->cssText(), ", ", secondX()->cssText(), ' ', secondY()->cssText());
|
|
appendGradientStops(result, stops());
|
|
} else if (gradientType() == CSSPrefixedLinearGradient) {
|
|
if (isRepeating())
|
|
result.append("-webkit-repeating-linear-gradient(");
|
|
else
|
|
result.append("-webkit-linear-gradient(");
|
|
|
|
if (m_angle)
|
|
result.append(m_angle->cssText());
|
|
else
|
|
appendSpaceSeparatedOptionalCSSPtrText(result, firstX(), firstY());
|
|
|
|
for (auto& stop : stops()) {
|
|
result.append(", ");
|
|
writeColorStop(result, stop);
|
|
}
|
|
} else {
|
|
if (isRepeating())
|
|
result.append("repeating-linear-gradient(");
|
|
else
|
|
result.append("linear-gradient(");
|
|
|
|
bool wroteSomething = false;
|
|
|
|
if (m_angle && m_angle->computeDegrees() != 180) {
|
|
result.append(m_angle->cssText());
|
|
wroteSomething = true;
|
|
} else if (firstX() || (firstY() && firstY()->valueID() != CSSValueBottom)) {
|
|
result.append("to ");
|
|
appendSpaceSeparatedOptionalCSSPtrText(result, firstX(), firstY());
|
|
wroteSomething = true;
|
|
}
|
|
|
|
for (auto& stop : stops()) {
|
|
if (wroteSomething)
|
|
result.append(", ");
|
|
wroteSomething = true;
|
|
writeColorStop(result, stop);
|
|
}
|
|
}
|
|
|
|
result.append(')');
|
|
return result.toString();
|
|
}
|
|
|
|
// Compute the endpoints so that a gradient of the given angle covers a box of the given size.
|
|
static void endPointsFromAngle(float angleDeg, const FloatSize& size, FloatPoint& firstPoint, FloatPoint& secondPoint, CSSGradientType type)
|
|
{
|
|
// Prefixed gradients use "polar coordinate" angles, rather than "bearing" angles.
|
|
if (type == CSSPrefixedLinearGradient)
|
|
angleDeg = 90 - angleDeg;
|
|
|
|
angleDeg = fmodf(angleDeg, 360);
|
|
if (angleDeg < 0)
|
|
angleDeg += 360;
|
|
|
|
if (!angleDeg) {
|
|
firstPoint.set(0, size.height());
|
|
secondPoint.set(0, 0);
|
|
return;
|
|
}
|
|
|
|
if (angleDeg == 90) {
|
|
firstPoint.set(0, 0);
|
|
secondPoint.set(size.width(), 0);
|
|
return;
|
|
}
|
|
|
|
if (angleDeg == 180) {
|
|
firstPoint.set(0, 0);
|
|
secondPoint.set(0, size.height());
|
|
return;
|
|
}
|
|
|
|
if (angleDeg == 270) {
|
|
firstPoint.set(size.width(), 0);
|
|
secondPoint.set(0, 0);
|
|
return;
|
|
}
|
|
|
|
// angleDeg is a "bearing angle" (0deg = N, 90deg = E),
|
|
// but tan expects 0deg = E, 90deg = N.
|
|
float slope = tan(deg2rad(90 - angleDeg));
|
|
|
|
// We find the endpoint by computing the intersection of the line formed by the slope,
|
|
// and a line perpendicular to it that intersects the corner.
|
|
float perpendicularSlope = -1 / slope;
|
|
|
|
// Compute start corner relative to center, in Cartesian space (+y = up).
|
|
float halfHeight = size.height() / 2;
|
|
float halfWidth = size.width() / 2;
|
|
FloatPoint endCorner;
|
|
if (angleDeg < 90)
|
|
endCorner.set(halfWidth, halfHeight);
|
|
else if (angleDeg < 180)
|
|
endCorner.set(halfWidth, -halfHeight);
|
|
else if (angleDeg < 270)
|
|
endCorner.set(-halfWidth, -halfHeight);
|
|
else
|
|
endCorner.set(-halfWidth, halfHeight);
|
|
|
|
// Compute c (of y = mx + c) using the corner point.
|
|
float c = endCorner.y() - perpendicularSlope * endCorner.x();
|
|
float endX = c / (slope - perpendicularSlope);
|
|
float endY = perpendicularSlope * endX + c;
|
|
|
|
// We computed the end point, so set the second point,
|
|
// taking into account the moved origin and the fact that we're in drawing space (+y = down).
|
|
secondPoint.set(halfWidth + endX, halfHeight - endY);
|
|
// Reflect around the center for the start point.
|
|
firstPoint.set(halfWidth - endX, halfHeight + endY);
|
|
}
|
|
|
|
Ref<Gradient> CSSLinearGradientValue::createGradient(RenderElement& renderer, const FloatSize& size)
|
|
{
|
|
ASSERT(!size.isEmpty());
|
|
|
|
const RenderStyle* rootStyle = nullptr;
|
|
if (auto* documentElement = renderer.document().documentElement())
|
|
rootStyle = documentElement->renderStyle();
|
|
|
|
CSSToLengthConversionData conversionData(&renderer.style(), rootStyle, renderer.parentStyle(), &renderer.view());
|
|
|
|
FloatPoint firstPoint;
|
|
FloatPoint secondPoint;
|
|
if (m_angle) {
|
|
float angle = m_angle->floatValue(CSSUnitType::CSS_DEG);
|
|
endPointsFromAngle(angle, size, firstPoint, secondPoint, gradientType());
|
|
} else {
|
|
switch (gradientType()) {
|
|
case CSSDeprecatedLinearGradient:
|
|
firstPoint = computeEndPoint(firstX(), firstY(), conversionData, size);
|
|
if (secondX() || secondY())
|
|
secondPoint = computeEndPoint(secondX(), secondY(), conversionData, size);
|
|
else {
|
|
if (firstX())
|
|
secondPoint.setX(size.width() - firstPoint.x());
|
|
if (firstY())
|
|
secondPoint.setY(size.height() - firstPoint.y());
|
|
}
|
|
break;
|
|
case CSSPrefixedLinearGradient:
|
|
firstPoint = computeEndPoint(firstX(), firstY(), conversionData, size);
|
|
if (firstX())
|
|
secondPoint.setX(size.width() - firstPoint.x());
|
|
if (firstY())
|
|
secondPoint.setY(size.height() - firstPoint.y());
|
|
break;
|
|
case CSSLinearGradient:
|
|
if (firstX() && firstY()) {
|
|
// "Magic" corners, so the 50% line touches two corners.
|
|
float rise = size.width();
|
|
float run = size.height();
|
|
if (firstX() && firstX()->valueID() == CSSValueLeft)
|
|
run *= -1;
|
|
if (firstY() && firstY()->valueID() == CSSValueBottom)
|
|
rise *= -1;
|
|
// Compute angle, and flip it back to "bearing angle" degrees.
|
|
float angle = 90 - rad2deg(atan2(rise, run));
|
|
endPointsFromAngle(angle, size, firstPoint, secondPoint, gradientType());
|
|
} else if (firstX() || firstY()) {
|
|
secondPoint = computeEndPoint(firstX(), firstY(), conversionData, size);
|
|
if (firstX())
|
|
firstPoint.setX(size.width() - secondPoint.x());
|
|
if (firstY())
|
|
firstPoint.setY(size.height() - secondPoint.y());
|
|
} else
|
|
secondPoint.setY(size.height());
|
|
break;
|
|
default:
|
|
ASSERT_NOT_REACHED();
|
|
}
|
|
}
|
|
|
|
Gradient::LinearData data { firstPoint, secondPoint };
|
|
LinearGradientAdapter adapter { data };
|
|
auto stops = computeStops(adapter, conversionData, renderer.style(), 1);
|
|
|
|
auto gradient = Gradient::create(WTFMove(data));
|
|
gradient->setSortedColorStops(WTFMove(stops));
|
|
return gradient;
|
|
}
|
|
|
|
bool CSSLinearGradientValue::equals(const CSSLinearGradientValue& other) const
|
|
{
|
|
return CSSGradientValue::equals(other) && compareCSSValuePtr(m_angle, other.m_angle);
|
|
}
|
|
|
|
String CSSRadialGradientValue::customCSSText() const
|
|
{
|
|
StringBuilder result;
|
|
|
|
if (gradientType() == CSSDeprecatedRadialGradient) {
|
|
result.append("-webkit-gradient(radial, ", firstX()->cssText(), ' ', firstY()->cssText(), ", ", m_firstRadius->cssText(),
|
|
", ", secondX()->cssText(), ' ', secondY()->cssText(), ", ", m_secondRadius->cssText());
|
|
appendGradientStops(result, stops());
|
|
} else if (gradientType() == CSSPrefixedRadialGradient) {
|
|
if (isRepeating())
|
|
result.append("-webkit-repeating-radial-gradient(");
|
|
else
|
|
result.append("-webkit-radial-gradient(");
|
|
|
|
if (firstX() || firstY())
|
|
appendSpaceSeparatedOptionalCSSPtrText(result, firstX(), firstY());
|
|
else
|
|
result.append("center");
|
|
|
|
if (m_shape || m_sizingBehavior) {
|
|
result.append(", ");
|
|
if (m_shape)
|
|
result.append(m_shape->cssText(), ' ');
|
|
else
|
|
result.append("ellipse ");
|
|
if (m_sizingBehavior)
|
|
result.append(m_sizingBehavior->cssText());
|
|
else
|
|
result.append("cover");
|
|
} else if (m_endHorizontalSize && m_endVerticalSize)
|
|
result.append(", ", m_endHorizontalSize->cssText(), ' ', m_endVerticalSize->cssText());
|
|
|
|
for (auto& stop : stops()) {
|
|
result.append(", ");
|
|
writeColorStop(result, stop);
|
|
}
|
|
} else {
|
|
if (isRepeating())
|
|
result.append("repeating-radial-gradient(");
|
|
else
|
|
result.append("radial-gradient(");
|
|
|
|
bool wroteSomething = false;
|
|
|
|
// The only ambiguous case that needs an explicit shape to be provided
|
|
// is when a sizing keyword is used (or all sizing is omitted).
|
|
if (m_shape && m_shape->valueID() != CSSValueEllipse && (m_sizingBehavior || (!m_sizingBehavior && !m_endHorizontalSize))) {
|
|
result.append("circle");
|
|
wroteSomething = true;
|
|
}
|
|
|
|
if (m_sizingBehavior && m_sizingBehavior->valueID() != CSSValueFarthestCorner) {
|
|
if (wroteSomething)
|
|
result.append(' ');
|
|
result.append(m_sizingBehavior->cssText());
|
|
wroteSomething = true;
|
|
} else if (m_endHorizontalSize) {
|
|
if (wroteSomething)
|
|
result.append(' ');
|
|
result.append(m_endHorizontalSize->cssText());
|
|
if (m_endVerticalSize)
|
|
result.append(' ', m_endVerticalSize->cssText());
|
|
wroteSomething = true;
|
|
}
|
|
|
|
if ((firstX() && !firstX()->isCenterPosition()) || (firstY() && !firstY()->isCenterPosition())) {
|
|
if (wroteSomething)
|
|
result.append(' ');
|
|
result.append("at ");
|
|
appendSpaceSeparatedOptionalCSSPtrText(result, firstX(), firstY());
|
|
wroteSomething = true;
|
|
}
|
|
|
|
if (wroteSomething)
|
|
result.append(", ");
|
|
|
|
bool wroteFirstStop = false;
|
|
for (auto& stop : stops()) {
|
|
if (wroteFirstStop)
|
|
result.append(", ");
|
|
wroteFirstStop = true;
|
|
writeColorStop(result, stop);
|
|
}
|
|
}
|
|
|
|
result.append(')');
|
|
return result.toString();
|
|
}
|
|
|
|
float CSSRadialGradientValue::resolveRadius(CSSPrimitiveValue& radius, const CSSToLengthConversionData& conversionData, float* widthOrHeight)
|
|
{
|
|
float result = 0;
|
|
if (radius.isNumber())
|
|
result = radius.floatValue() * conversionData.zoom();
|
|
else if (widthOrHeight && radius.isPercentage())
|
|
result = *widthOrHeight * radius.floatValue() / 100;
|
|
else
|
|
result = radius.computeLength<float>(conversionData);
|
|
return result;
|
|
}
|
|
|
|
static float distanceToClosestCorner(const FloatPoint& p, const FloatSize& size, FloatPoint& corner)
|
|
{
|
|
FloatPoint topLeft;
|
|
float topLeftDistance = FloatSize(p - topLeft).diagonalLength();
|
|
|
|
FloatPoint topRight(size.width(), 0);
|
|
float topRightDistance = FloatSize(p - topRight).diagonalLength();
|
|
|
|
FloatPoint bottomLeft(0, size.height());
|
|
float bottomLeftDistance = FloatSize(p - bottomLeft).diagonalLength();
|
|
|
|
FloatPoint bottomRight(size.width(), size.height());
|
|
float bottomRightDistance = FloatSize(p - bottomRight).diagonalLength();
|
|
|
|
corner = topLeft;
|
|
float minDistance = topLeftDistance;
|
|
if (topRightDistance < minDistance) {
|
|
minDistance = topRightDistance;
|
|
corner = topRight;
|
|
}
|
|
|
|
if (bottomLeftDistance < minDistance) {
|
|
minDistance = bottomLeftDistance;
|
|
corner = bottomLeft;
|
|
}
|
|
|
|
if (bottomRightDistance < minDistance) {
|
|
minDistance = bottomRightDistance;
|
|
corner = bottomRight;
|
|
}
|
|
return minDistance;
|
|
}
|
|
|
|
static float distanceToFarthestCorner(const FloatPoint& p, const FloatSize& size, FloatPoint& corner)
|
|
{
|
|
FloatPoint topLeft;
|
|
float topLeftDistance = FloatSize(p - topLeft).diagonalLength();
|
|
|
|
FloatPoint topRight(size.width(), 0);
|
|
float topRightDistance = FloatSize(p - topRight).diagonalLength();
|
|
|
|
FloatPoint bottomLeft(0, size.height());
|
|
float bottomLeftDistance = FloatSize(p - bottomLeft).diagonalLength();
|
|
|
|
FloatPoint bottomRight(size.width(), size.height());
|
|
float bottomRightDistance = FloatSize(p - bottomRight).diagonalLength();
|
|
|
|
corner = topLeft;
|
|
float maxDistance = topLeftDistance;
|
|
if (topRightDistance > maxDistance) {
|
|
maxDistance = topRightDistance;
|
|
corner = topRight;
|
|
}
|
|
|
|
if (bottomLeftDistance > maxDistance) {
|
|
maxDistance = bottomLeftDistance;
|
|
corner = bottomLeft;
|
|
}
|
|
|
|
if (bottomRightDistance > maxDistance) {
|
|
maxDistance = bottomRightDistance;
|
|
corner = bottomRight;
|
|
}
|
|
return maxDistance;
|
|
}
|
|
|
|
// Compute horizontal radius of ellipse with center at 0,0 which passes through p, and has
|
|
// width/height given by aspectRatio.
|
|
static inline float horizontalEllipseRadius(const FloatSize& p, float aspectRatio)
|
|
{
|
|
// x^2/a^2 + y^2/b^2 = 1
|
|
// a/b = aspectRatio, b = a/aspectRatio
|
|
// a = sqrt(x^2 + y^2/(1/r^2))
|
|
return std::hypot(p.width(), p.height() * aspectRatio);
|
|
}
|
|
|
|
// FIXME: share code with the linear version
|
|
Ref<Gradient> CSSRadialGradientValue::createGradient(RenderElement& renderer, const FloatSize& size)
|
|
{
|
|
ASSERT(!size.isEmpty());
|
|
|
|
const RenderStyle* rootStyle = nullptr;
|
|
if (auto* documentElement = renderer.document().documentElement())
|
|
rootStyle = documentElement->renderStyle();
|
|
|
|
CSSToLengthConversionData conversionData(&renderer.style(), rootStyle, renderer.parentStyle(), &renderer.view());
|
|
|
|
FloatPoint firstPoint = computeEndPoint(firstX(), firstY(), conversionData, size);
|
|
if (!firstX())
|
|
firstPoint.setX(size.width() / 2);
|
|
if (!firstY())
|
|
firstPoint.setY(size.height() / 2);
|
|
|
|
FloatPoint secondPoint = computeEndPoint(secondX(), secondY(), conversionData, size);
|
|
if (!secondX())
|
|
secondPoint.setX(size.width() / 2);
|
|
if (!secondY())
|
|
secondPoint.setY(size.height() / 2);
|
|
|
|
float firstRadius = 0;
|
|
if (m_firstRadius)
|
|
firstRadius = resolveRadius(*m_firstRadius, conversionData);
|
|
|
|
float secondRadius = 0;
|
|
float aspectRatio = 1; // width / height.
|
|
if (m_secondRadius)
|
|
secondRadius = resolveRadius(*m_secondRadius, conversionData);
|
|
else if (m_endHorizontalSize) {
|
|
float width = size.width();
|
|
float height = size.height();
|
|
secondRadius = resolveRadius(*m_endHorizontalSize, conversionData, &width);
|
|
if (m_endVerticalSize)
|
|
aspectRatio = secondRadius / resolveRadius(*m_endVerticalSize, conversionData, &height);
|
|
else
|
|
aspectRatio = 1;
|
|
} else {
|
|
enum GradientShape { Circle, Ellipse };
|
|
GradientShape shape = Ellipse;
|
|
if ((m_shape && m_shape->valueID() == CSSValueCircle)
|
|
|| (!m_shape && !m_sizingBehavior && m_endHorizontalSize && !m_endVerticalSize))
|
|
shape = Circle;
|
|
|
|
enum GradientFill { ClosestSide, ClosestCorner, FarthestSide, FarthestCorner };
|
|
GradientFill fill = FarthestCorner;
|
|
|
|
switch (m_sizingBehavior ? m_sizingBehavior->valueID() : 0) {
|
|
case CSSValueContain:
|
|
case CSSValueClosestSide:
|
|
fill = ClosestSide;
|
|
break;
|
|
case CSSValueClosestCorner:
|
|
fill = ClosestCorner;
|
|
break;
|
|
case CSSValueFarthestSide:
|
|
fill = FarthestSide;
|
|
break;
|
|
case CSSValueCover:
|
|
case CSSValueFarthestCorner:
|
|
fill = FarthestCorner;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// Now compute the end radii based on the second point, shape and fill.
|
|
|
|
// Horizontal
|
|
switch (fill) {
|
|
case ClosestSide: {
|
|
float xDist = std::min(secondPoint.x(), size.width() - secondPoint.x());
|
|
float yDist = std::min(secondPoint.y(), size.height() - secondPoint.y());
|
|
if (shape == Circle) {
|
|
float smaller = std::min(xDist, yDist);
|
|
xDist = smaller;
|
|
yDist = smaller;
|
|
}
|
|
secondRadius = xDist;
|
|
aspectRatio = xDist / yDist;
|
|
break;
|
|
}
|
|
case FarthestSide: {
|
|
float xDist = std::max(secondPoint.x(), size.width() - secondPoint.x());
|
|
float yDist = std::max(secondPoint.y(), size.height() - secondPoint.y());
|
|
if (shape == Circle) {
|
|
float larger = std::max(xDist, yDist);
|
|
xDist = larger;
|
|
yDist = larger;
|
|
}
|
|
secondRadius = xDist;
|
|
aspectRatio = xDist / yDist;
|
|
break;
|
|
}
|
|
case ClosestCorner: {
|
|
FloatPoint corner;
|
|
float distance = distanceToClosestCorner(secondPoint, size, corner);
|
|
if (shape == Circle)
|
|
secondRadius = distance;
|
|
else {
|
|
// If <shape> is ellipse, the gradient-shape has the same ratio of width to height
|
|
// that it would if closest-side or farthest-side were specified, as appropriate.
|
|
float xDist = std::min(secondPoint.x(), size.width() - secondPoint.x());
|
|
float yDist = std::min(secondPoint.y(), size.height() - secondPoint.y());
|
|
|
|
secondRadius = horizontalEllipseRadius(corner - secondPoint, xDist / yDist);
|
|
aspectRatio = xDist / yDist;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case FarthestCorner: {
|
|
FloatPoint corner;
|
|
float distance = distanceToFarthestCorner(secondPoint, size, corner);
|
|
if (shape == Circle)
|
|
secondRadius = distance;
|
|
else {
|
|
// If <shape> is ellipse, the gradient-shape has the same ratio of width to height
|
|
// that it would if closest-side or farthest-side were specified, as appropriate.
|
|
float xDist = std::max(secondPoint.x(), size.width() - secondPoint.x());
|
|
float yDist = std::max(secondPoint.y(), size.height() - secondPoint.y());
|
|
|
|
secondRadius = horizontalEllipseRadius(corner - secondPoint, xDist / yDist);
|
|
aspectRatio = xDist / yDist;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// computeStops() only uses maxExtent for repeating gradients.
|
|
float maxExtent = 0;
|
|
if (isRepeating()) {
|
|
FloatPoint corner;
|
|
maxExtent = distanceToFarthestCorner(secondPoint, size, corner);
|
|
}
|
|
|
|
Gradient::RadialData data { firstPoint, secondPoint, firstRadius, secondRadius, aspectRatio };
|
|
RadialGradientAdapter adapter { data };
|
|
auto stops = computeStops(adapter, conversionData, renderer.style(), maxExtent);
|
|
|
|
auto gradient = Gradient::create(WTFMove(data));
|
|
gradient->setSortedColorStops(WTFMove(stops));
|
|
return gradient;
|
|
}
|
|
|
|
bool CSSRadialGradientValue::equals(const CSSRadialGradientValue& other) const
|
|
{
|
|
return CSSGradientValue::equals(other)
|
|
&& compareCSSValuePtr(m_shape, other.m_shape)
|
|
&& compareCSSValuePtr(m_sizingBehavior, other.m_sizingBehavior)
|
|
&& compareCSSValuePtr(m_endHorizontalSize, other.m_endHorizontalSize)
|
|
&& compareCSSValuePtr(m_endVerticalSize, other.m_endVerticalSize);
|
|
}
|
|
|
|
String CSSConicGradientValue::customCSSText() const
|
|
{
|
|
StringBuilder result;
|
|
|
|
result.append(isRepeating() ? "repeating-conic-gradient(" : "conic-gradient(");
|
|
|
|
bool wroteSomething = false;
|
|
|
|
if (m_angle && m_angle->computeDegrees()) {
|
|
result.append("from ", m_angle->cssText());
|
|
wroteSomething = true;
|
|
}
|
|
|
|
if ((firstX() && !firstX()->isCenterPosition()) || (firstY() && !firstY()->isCenterPosition())) {
|
|
if (wroteSomething)
|
|
result.append(' ');
|
|
result.append("at ");
|
|
appendSpaceSeparatedOptionalCSSPtrText(result, firstX(), firstY());
|
|
wroteSomething = true;
|
|
}
|
|
|
|
if (wroteSomething)
|
|
result.append(", ");
|
|
|
|
bool wroteFirstStop = false;
|
|
for (auto& stop : stops()) {
|
|
if (wroteFirstStop)
|
|
result.append(", ");
|
|
wroteFirstStop = true;
|
|
writeColorStop(result, stop);
|
|
}
|
|
|
|
result.append(')');
|
|
return result.toString();
|
|
}
|
|
|
|
Ref<Gradient> CSSConicGradientValue::createGradient(RenderElement& renderer, const FloatSize& size)
|
|
{
|
|
ASSERT(!size.isEmpty());
|
|
|
|
const RenderStyle* rootStyle = nullptr;
|
|
if (auto* documentElement = renderer.document().documentElement())
|
|
rootStyle = documentElement->renderStyle();
|
|
|
|
CSSToLengthConversionData conversionData(&renderer.style(), rootStyle, renderer.parentStyle(), &renderer.view());
|
|
|
|
FloatPoint centerPoint = computeEndPoint(firstX(), firstY(), conversionData, size);
|
|
if (!firstX())
|
|
centerPoint.setX(size.width() / 2);
|
|
if (!firstY())
|
|
centerPoint.setY(size.height() / 2);
|
|
|
|
float angleRadians = 0;
|
|
if (m_angle)
|
|
angleRadians = m_angle->floatValue(CSSUnitType::CSS_RAD);
|
|
|
|
Gradient::ConicData data { centerPoint, angleRadians };
|
|
ConicGradientAdapter adapter;
|
|
auto stops = computeStops(adapter, conversionData, renderer.style(), 1);
|
|
|
|
auto gradient = Gradient::create(WTFMove(data));
|
|
gradient->setSortedColorStops(WTFMove(stops));
|
|
return gradient;
|
|
}
|
|
|
|
bool CSSConicGradientValue::equals(const CSSConicGradientValue& other) const
|
|
{
|
|
return CSSGradientValue::equals(other) && compareCSSValuePtr(m_angle, other.m_angle);
|
|
}
|
|
|
|
} // namespace WebCore
|