This source file includes following definitions.
- trace
- image
- compareStops
- sortStopsIfNeeded
- gradientWithStylesResolved
- addStops
- positionFromValue
- computeEndPoint
- isCacheable
- knownToBeOpaque
- traceAfterDispatch
- customCSSText
- endPointsFromAngle
- createGradient
- equals
- traceAfterDispatch
- customCSSText
- resolveRadius
- distanceToClosestCorner
- distanceToFarthestCorner
- horizontalEllipseRadius
- createGradient
- equals
- traceAfterDispatch
#include "config.h"
#include "core/css/CSSGradientValue.h"
#include "CSSValueKeywords.h"
#include "core/css/CSSCalculationValue.h"
#include "core/css/CSSToLengthConversionData.h"
#include "core/dom/NodeRenderStyle.h"
#include "core/dom/TextLinkColors.h"
#include "core/rendering/RenderObject.h"
#include "platform/geometry/IntSize.h"
#include "platform/graphics/Gradient.h"
#include "platform/graphics/GradientGeneratedImage.h"
#include "platform/graphics/Image.h"
#include "wtf/text/StringBuilder.h"
#include "wtf/text/WTFString.h"
using namespace std;
namespace WebCore {
void CSSGradientColorStop::trace(Visitor* visitor)
{
visitor->trace(m_position);
visitor->trace(m_color);
}
PassRefPtr<Image> CSSGradientValue::image(RenderObject* renderer, const IntSize& size)
{
if (size.isEmpty())
return nullptr;
bool cacheable = isCacheable();
if (cacheable) {
if (!clients().contains(renderer))
return nullptr;
Image* result = getImage(renderer, size);
if (result)
return result;
}
RefPtr<Gradient> gradient;
RenderStyle* rootStyle = renderer->document().documentElement()->renderStyle();
CSSToLengthConversionData conversionData(renderer->style(), rootStyle, renderer->view());
if (isLinearGradientValue())
gradient = toCSSLinearGradientValue(this)->createGradient(conversionData, size);
else
gradient = toCSSRadialGradientValue(this)->createGradient(conversionData, size);
RefPtr<Image> newImage = GradientGeneratedImage::create(gradient, size);
if (cacheable)
putImage(size, newImage);
return newImage.release();
}
static inline bool compareStops(const CSSGradientColorStop& a, const CSSGradientColorStop& b)
{
double aVal = a.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER);
double bVal = b.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER);
return aVal < bVal;
}
void CSSGradientValue::sortStopsIfNeeded()
{
ASSERT(m_gradientType == CSSDeprecatedLinearGradient || m_gradientType == CSSDeprecatedRadialGradient);
if (!m_stopsSorted) {
if (m_stops.size())
std::stable_sort(m_stops.begin(), m_stops.end(), compareStops);
m_stopsSorted = true;
}
}
struct GradientStop {
Color color;
float offset;
bool specified;
GradientStop()
: offset(0)
, specified(false)
{ }
};
PassRefPtrWillBeRawPtr<CSSGradientValue> CSSGradientValue::gradientWithStylesResolved(const TextLinkColors& textLinkColors, Color currentColor)
{
bool derived = false;
for (unsigned i = 0; i < m_stops.size(); i++)
if (m_stops[i].m_color->colorIsDerivedFromElement()) {
m_stops[i].m_colorIsDerivedFromElement = true;
derived = true;
break;
}
RefPtrWillBeRawPtr<CSSGradientValue> result = nullptr;
if (!derived)
result = this;
else if (isLinearGradientValue())
result = toCSSLinearGradientValue(this)->clone();
else if (isRadialGradientValue())
result = toCSSRadialGradientValue(this)->clone();
else {
ASSERT_NOT_REACHED();
return nullptr;
}
for (unsigned i = 0; i < result->m_stops.size(); i++)
result->m_stops[i].m_resolvedColor = textLinkColors.colorFromPrimitiveValue(result->m_stops[i].m_color.get(), currentColor);
return result.release();
}
void CSSGradientValue::addStops(Gradient* gradient, const CSSToLengthConversionData& conversionData, float maxLengthForRepeat)
{
if (m_gradientType == CSSDeprecatedLinearGradient || m_gradientType == CSSDeprecatedRadialGradient) {
sortStopsIfNeeded();
for (unsigned i = 0; i < m_stops.size(); i++) {
const CSSGradientColorStop& stop = m_stops[i];
float offset;
if (stop.m_position->isPercentage())
offset = stop.m_position->getFloatValue(CSSPrimitiveValue::CSS_PERCENTAGE) / 100;
else
offset = stop.m_position->getFloatValue(CSSPrimitiveValue::CSS_NUMBER);
gradient->addColorStop(offset, stop.m_resolvedColor);
}
return;
}
size_t numStops = m_stops.size();
Vector<GradientStop> stops(numStops);
float gradientLength = 0;
bool computedGradientLength = false;
FloatPoint gradientStart = gradient->p0();
FloatPoint gradientEnd;
if (isLinearGradientValue())
gradientEnd = gradient->p1();
else if (isRadialGradientValue())
gradientEnd = gradientStart + FloatSize(gradient->endRadius(), 0);
for (size_t i = 0; i < numStops; ++i) {
const CSSGradientColorStop& stop = m_stops[i];
stops[i].color = stop.m_resolvedColor;
if (stop.m_position) {
if (stop.m_position->isPercentage())
stops[i].offset = stop.m_position->getFloatValue(CSSPrimitiveValue::CSS_PERCENTAGE) / 100;
else if (stop.m_position->isLength() || stop.m_position->isCalculatedPercentageWithLength()) {
if (!computedGradientLength) {
FloatSize gradientSize(gradientStart - gradientEnd);
gradientLength = gradientSize.diagonalLength();
}
float length;
if (stop.m_position->isLength())
length = stop.m_position->computeLength<float>(conversionData);
else
length = stop.m_position->cssCalcValue()->toCalcValue(conversionData)->evaluate(gradientLength);
stops[i].offset = (gradientLength > 0) ? length / gradientLength : 0;
} else {
ASSERT_NOT_REACHED();
stops[i].offset = 0;
}
stops[i].specified = true;
} else {
if (!i) {
stops[i].offset = 0;
stops[i].specified = true;
} else if (numStops > 1 && i == numStops - 1) {
stops[i].offset = 1;
stops[i].specified = true;
}
}
if (stops[i].specified && i > 0) {
size_t prevSpecifiedIndex;
for (prevSpecifiedIndex = i - 1; prevSpecifiedIndex; --prevSpecifiedIndex) {
if (stops[prevSpecifiedIndex].specified)
break;
}
if (stops[i].offset < stops[prevSpecifiedIndex].offset)
stops[i].offset = stops[prevSpecifiedIndex].offset;
}
}
ASSERT(stops[0].specified && stops[numStops - 1].specified);
if (numStops > 2) {
size_t unspecifiedRunStart = 0;
bool inUnspecifiedRun = false;
for (size_t i = 0; i < numStops; ++i) {
if (!stops[i].specified && !inUnspecifiedRun) {
unspecifiedRunStart = i;
inUnspecifiedRun = true;
} else if (stops[i].specified && 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;
}
}
}
if (m_repeating && numStops > 1) {
float gradientRange = stops[numStops - 1].offset - stops[0].offset;
if (!gradientRange) {
stops.first().offset = 0;
stops.first().color = stops.last().color;
stops.shrink(1);
} else {
float maxExtent = 1;
if (isRadialGradientValue()) {
if (!computedGradientLength) {
FloatSize gradientSize(gradientStart - gradientEnd);
gradientLength = gradientSize.diagonalLength();
}
if (maxLengthForRepeat > gradientLength)
maxExtent = gradientLength > 0 ? maxLengthForRepeat / gradientLength : 0;
}
size_t originalNumStops = numStops;
size_t originalFirstStopIndex = 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.prepend(newStop);
++originalFirstStopIndex;
if (currOffset < 0)
break;
if (srcStopOrdinal)
currOffset -= stops[originalFirstStopIndex + srcStopOrdinal].offset - stops[originalFirstStopIndex + srcStopOrdinal - 1].offset;
srcStopOrdinal = (srcStopOrdinal + originalNumStops - 1) % originalNumStops;
}
}
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;
}
}
}
}
numStops = stops.size();
if (numStops > 1 && (stops[0].offset < 0 || stops[numStops - 1].offset > 1)) {
if (isLinearGradientValue()) {
float firstOffset = stops[0].offset;
float lastOffset = stops[numStops - 1].offset;
float scale = lastOffset - firstOffset;
for (size_t i = 0; i < numStops; ++i)
stops[i].offset = (stops[i].offset - firstOffset) / scale;
FloatPoint p0 = gradient->p0();
FloatPoint p1 = gradient->p1();
gradient->setP0(FloatPoint(p0.x() + firstOffset * (p1.x() - p0.x()), p0.y() + firstOffset * (p1.y() - p0.y())));
gradient->setP1(FloatPoint(p1.x() + (lastOffset - 1) * (p1.x() - p0.x()), p1.y() + (lastOffset - 1) * (p1.y() - p0.y())));
} else if (isRadialGradientValue()) {
float firstOffset = 0;
float lastOffset = stops[numStops - 1].offset;
float scale = lastOffset - firstOffset;
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);
Color blendedColor = blend(stops[firstZeroOrGreaterIndex - 1].color, stops[firstZeroOrGreaterIndex].color, interStopProportion);
for (size_t i = 0; i < firstZeroOrGreaterIndex; ++i) {
stops[i].offset = 0;
stops[i].color = blendedColor;
}
} else {
for (size_t i = 0; i < firstZeroOrGreaterIndex; ++i)
stops[i].offset = 0;
}
}
for (size_t i = 0; i < numStops; ++i)
stops[i].offset /= scale;
gradient->setStartRadius(gradient->startRadius() * scale);
gradient->setEndRadius(gradient->endRadius() * scale);
}
}
for (unsigned i = 0; i < numStops; i++)
gradient->addColorStop(stops[i].offset, stops[i].color);
}
static float positionFromValue(CSSPrimitiveValue* value, const CSSToLengthConversionData& conversionData, const IntSize& size, bool isHorizontal)
{
if (value->isNumber())
return value->getFloatValue() * conversionData.zoom();
int edgeDistance = isHorizontal ? size.width() : size.height();
if (value->isPercentage())
return value->getFloatValue() / 100.f * edgeDistance;
if (value->isCalculatedPercentageWithLength())
return value->cssCalcValue()->toCalcValue(conversionData)->evaluate(edgeDistance);
switch (value->getValueID()) {
case CSSValueTop:
ASSERT(!isHorizontal);
return 0;
case CSSValueLeft:
ASSERT(isHorizontal);
return 0;
case CSSValueBottom:
ASSERT(!isHorizontal);
return size.height();
case CSSValueRight:
ASSERT(isHorizontal);
return size.width();
default:
break;
}
return value->computeLength<float>(conversionData);
}
FloatPoint CSSGradientValue::computeEndPoint(CSSPrimitiveValue* horizontal, CSSPrimitiveValue* vertical, const CSSToLengthConversionData& conversionData, const IntSize& size)
{
FloatPoint result;
if (horizontal)
result.setX(positionFromValue(horizontal, conversionData, size, true));
if (vertical)
result.setY(positionFromValue(vertical, conversionData, size, false));
return result;
}
bool CSSGradientValue::isCacheable() const
{
for (size_t i = 0; i < m_stops.size(); ++i) {
const CSSGradientColorStop& stop = m_stops[i];
if (stop.m_colorIsDerivedFromElement)
return false;
if (!stop.m_position)
continue;
if (stop.m_position->isFontRelativeLength())
return false;
}
return true;
}
bool CSSGradientValue::knownToBeOpaque(const RenderObject*) const
{
for (size_t i = 0; i < m_stops.size(); ++i) {
if (m_stops[i].m_resolvedColor.hasAlpha())
return false;
}
return true;
}
void CSSGradientValue::traceAfterDispatch(Visitor* visitor)
{
visitor->trace(m_firstX);
visitor->trace(m_firstY);
visitor->trace(m_secondX);
visitor->trace(m_secondY);
visitor->trace(m_stops);
CSSImageGeneratorValue::traceAfterDispatch(visitor);
}
String CSSLinearGradientValue::customCSSText() const
{
StringBuilder result;
if (m_gradientType == CSSDeprecatedLinearGradient) {
result.appendLiteral("-webkit-gradient(linear, ");
result.append(m_firstX->cssText());
result.append(' ');
result.append(m_firstY->cssText());
result.appendLiteral(", ");
result.append(m_secondX->cssText());
result.append(' ');
result.append(m_secondY->cssText());
for (unsigned i = 0; i < m_stops.size(); i++) {
const CSSGradientColorStop& stop = m_stops[i];
result.appendLiteral(", ");
if (stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER) == 0) {
result.appendLiteral("from(");
result.append(stop.m_color->cssText());
result.append(')');
} else if (stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER) == 1) {
result.appendLiteral("to(");
result.append(stop.m_color->cssText());
result.append(')');
} else {
result.appendLiteral("color-stop(");
result.append(String::number(stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER)));
result.appendLiteral(", ");
result.append(stop.m_color->cssText());
result.append(')');
}
}
} else if (m_gradientType == CSSPrefixedLinearGradient) {
if (m_repeating)
result.appendLiteral("-webkit-repeating-linear-gradient(");
else
result.appendLiteral("-webkit-linear-gradient(");
if (m_angle)
result.append(m_angle->cssText());
else {
if (m_firstX && m_firstY) {
result.append(m_firstX->cssText());
result.append(' ');
result.append(m_firstY->cssText());
} else if (m_firstX || m_firstY) {
if (m_firstX)
result.append(m_firstX->cssText());
if (m_firstY)
result.append(m_firstY->cssText());
}
}
for (unsigned i = 0; i < m_stops.size(); i++) {
const CSSGradientColorStop& stop = m_stops[i];
result.appendLiteral(", ");
result.append(stop.m_color->cssText());
if (stop.m_position) {
result.append(' ');
result.append(stop.m_position->cssText());
}
}
} else {
if (m_repeating)
result.appendLiteral("repeating-linear-gradient(");
else
result.appendLiteral("linear-gradient(");
bool wroteSomething = false;
if (m_angle && m_angle->computeDegrees() != 180) {
result.append(m_angle->cssText());
wroteSomething = true;
} else if ((m_firstX || m_firstY) && !(!m_firstX && m_firstY && m_firstY->getValueID() == CSSValueBottom)) {
result.appendLiteral("to ");
if (m_firstX && m_firstY) {
result.append(m_firstX->cssText());
result.append(' ');
result.append(m_firstY->cssText());
} else if (m_firstX)
result.append(m_firstX->cssText());
else
result.append(m_firstY->cssText());
wroteSomething = true;
}
if (wroteSomething)
result.appendLiteral(", ");
for (unsigned i = 0; i < m_stops.size(); i++) {
const CSSGradientColorStop& stop = m_stops[i];
if (i)
result.appendLiteral(", ");
result.append(stop.m_color->cssText());
if (stop.m_position) {
result.append(' ');
result.append(stop.m_position->cssText());
}
}
}
result.append(')');
return result.toString();
}
static void endPointsFromAngle(float angleDeg, const IntSize& size, FloatPoint& firstPoint, FloatPoint& secondPoint, CSSGradientType type)
{
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;
}
float slope = tan(deg2rad(90 - angleDeg));
float perpendicularSlope = -1 / slope;
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);
float c = endCorner.y() - perpendicularSlope * endCorner.x();
float endX = c / (slope - perpendicularSlope);
float endY = perpendicularSlope * endX + c;
secondPoint.set(halfWidth + endX, halfHeight - endY);
firstPoint.set(halfWidth - endX, halfHeight + endY);
}
PassRefPtr<Gradient> CSSLinearGradientValue::createGradient(const CSSToLengthConversionData& conversionData, const IntSize& size)
{
ASSERT(!size.isEmpty());
FloatPoint firstPoint;
FloatPoint secondPoint;
if (m_angle) {
float angle = m_angle->getFloatValue(CSSPrimitiveValue::CSS_DEG);
endPointsFromAngle(angle, size, firstPoint, secondPoint, m_gradientType);
} else {
switch (m_gradientType) {
case CSSDeprecatedLinearGradient:
firstPoint = computeEndPoint(m_firstX.get(), m_firstY.get(), conversionData, size);
if (m_secondX || m_secondY)
secondPoint = computeEndPoint(m_secondX.get(), m_secondY.get(), conversionData, size);
else {
if (m_firstX)
secondPoint.setX(size.width() - firstPoint.x());
if (m_firstY)
secondPoint.setY(size.height() - firstPoint.y());
}
break;
case CSSPrefixedLinearGradient:
firstPoint = computeEndPoint(m_firstX.get(), m_firstY.get(), conversionData, size);
if (m_firstX)
secondPoint.setX(size.width() - firstPoint.x());
if (m_firstY)
secondPoint.setY(size.height() - firstPoint.y());
break;
case CSSLinearGradient:
if (m_firstX && m_firstY) {
float rise = size.width();
float run = size.height();
if (m_firstX && m_firstX->getValueID() == CSSValueLeft)
run *= -1;
if (m_firstY && m_firstY->getValueID() == CSSValueBottom)
rise *= -1;
float angle = 90 - rad2deg(atan2(rise, run));
endPointsFromAngle(angle, size, firstPoint, secondPoint, m_gradientType);
} else if (m_firstX || m_firstY) {
secondPoint = computeEndPoint(m_firstX.get(), m_firstY.get(), conversionData, size);
if (m_firstX)
firstPoint.setX(size.width() - secondPoint.x());
if (m_firstY)
firstPoint.setY(size.height() - secondPoint.y());
} else
secondPoint.setY(size.height());
break;
default:
ASSERT_NOT_REACHED();
}
}
RefPtr<Gradient> gradient = Gradient::create(firstPoint, secondPoint);
gradient->setDrawsInPMColorSpace(true);
addStops(gradient.get(), conversionData, 1);
return gradient.release();
}
bool CSSLinearGradientValue::equals(const CSSLinearGradientValue& other) const
{
if (m_gradientType == CSSDeprecatedLinearGradient)
return other.m_gradientType == m_gradientType
&& 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;
if (m_repeating != other.m_repeating)
return false;
if (m_angle)
return compareCSSValuePtr(m_angle, other.m_angle) && m_stops == other.m_stops;
if (other.m_angle)
return false;
bool equalXandY = false;
if (m_firstX && m_firstY)
equalXandY = compareCSSValuePtr(m_firstX, other.m_firstX) && compareCSSValuePtr(m_firstY, other.m_firstY);
else if (m_firstX)
equalXandY = compareCSSValuePtr(m_firstX, other.m_firstX) && !other.m_firstY;
else if (m_firstY)
equalXandY = compareCSSValuePtr(m_firstY, other.m_firstY) && !other.m_firstX;
else
equalXandY = !other.m_firstX && !other.m_firstY;
return equalXandY && m_stops == other.m_stops;
}
void CSSLinearGradientValue::traceAfterDispatch(Visitor* visitor)
{
visitor->trace(m_angle);
CSSGradientValue::traceAfterDispatch(visitor);
}
String CSSRadialGradientValue::customCSSText() const
{
StringBuilder result;
if (m_gradientType == CSSDeprecatedRadialGradient) {
result.appendLiteral("-webkit-gradient(radial, ");
result.append(m_firstX->cssText());
result.append(' ');
result.append(m_firstY->cssText());
result.appendLiteral(", ");
result.append(m_firstRadius->cssText());
result.appendLiteral(", ");
result.append(m_secondX->cssText());
result.append(' ');
result.append(m_secondY->cssText());
result.appendLiteral(", ");
result.append(m_secondRadius->cssText());
for (unsigned i = 0; i < m_stops.size(); i++) {
const CSSGradientColorStop& stop = m_stops[i];
result.appendLiteral(", ");
if (stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER) == 0) {
result.appendLiteral("from(");
result.append(stop.m_color->cssText());
result.append(')');
} else if (stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER) == 1) {
result.appendLiteral("to(");
result.append(stop.m_color->cssText());
result.append(')');
} else {
result.appendLiteral("color-stop(");
result.append(String::number(stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER)));
result.appendLiteral(", ");
result.append(stop.m_color->cssText());
result.append(')');
}
}
} else if (m_gradientType == CSSPrefixedRadialGradient) {
if (m_repeating)
result.appendLiteral("-webkit-repeating-radial-gradient(");
else
result.appendLiteral("-webkit-radial-gradient(");
if (m_firstX && m_firstY) {
result.append(m_firstX->cssText());
result.append(' ');
result.append(m_firstY->cssText());
} else if (m_firstX)
result.append(m_firstX->cssText());
else if (m_firstY)
result.append(m_firstY->cssText());
else
result.appendLiteral("center");
if (m_shape || m_sizingBehavior) {
result.appendLiteral(", ");
if (m_shape) {
result.append(m_shape->cssText());
result.append(' ');
} else
result.appendLiteral("ellipse ");
if (m_sizingBehavior)
result.append(m_sizingBehavior->cssText());
else
result.appendLiteral("cover");
} else if (m_endHorizontalSize && m_endVerticalSize) {
result.appendLiteral(", ");
result.append(m_endHorizontalSize->cssText());
result.append(' ');
result.append(m_endVerticalSize->cssText());
}
for (unsigned i = 0; i < m_stops.size(); i++) {
const CSSGradientColorStop& stop = m_stops[i];
result.appendLiteral(", ");
result.append(stop.m_color->cssText());
if (stop.m_position) {
result.append(' ');
result.append(stop.m_position->cssText());
}
}
} else {
if (m_repeating)
result.appendLiteral("repeating-radial-gradient(");
else
result.appendLiteral("radial-gradient(");
bool wroteSomething = false;
if (m_shape && m_shape->getValueID() != CSSValueEllipse && (m_sizingBehavior || (!m_sizingBehavior && !m_endHorizontalSize))) {
result.appendLiteral("circle");
wroteSomething = true;
}
if (m_sizingBehavior && m_sizingBehavior->getValueID() != 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(' ');
result.append(m_endVerticalSize->cssText());
}
wroteSomething = true;
}
if (m_firstX || m_firstY) {
if (wroteSomething)
result.append(' ');
result.appendLiteral("at ");
if (m_firstX && m_firstY) {
result.append(m_firstX->cssText());
result.append(' ');
result.append(m_firstY->cssText());
} else if (m_firstX)
result.append(m_firstX->cssText());
else
result.append(m_firstY->cssText());
wroteSomething = true;
}
if (wroteSomething)
result.appendLiteral(", ");
for (unsigned i = 0; i < m_stops.size(); i++) {
const CSSGradientColorStop& stop = m_stops[i];
if (i)
result.appendLiteral(", ");
result.append(stop.m_color->cssText());
if (stop.m_position) {
result.append(' ');
result.append(stop.m_position->cssText());
}
}
}
result.append(')');
return result.toString();
}
float CSSRadialGradientValue::resolveRadius(CSSPrimitiveValue* radius, const CSSToLengthConversionData& conversionData, float* widthOrHeight)
{
float result = 0;
if (radius->isNumber())
result = radius->getFloatValue() * conversionData.zoom();
else if (widthOrHeight && radius->isPercentage())
result = *widthOrHeight * radius->getFloatValue() / 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;
}
static inline float horizontalEllipseRadius(const FloatSize& p, float aspectRatio)
{
return sqrtf(p.width() * p.width() + (p.height() * p.height()) / (1 / (aspectRatio * aspectRatio)));
}
PassRefPtr<Gradient> CSSRadialGradientValue::createGradient(const CSSToLengthConversionData& conversionData, const IntSize& size)
{
ASSERT(!size.isEmpty());
FloatPoint firstPoint = computeEndPoint(m_firstX.get(), m_firstY.get(), conversionData, size);
if (!m_firstX)
firstPoint.setX(size.width() / 2);
if (!m_firstY)
firstPoint.setY(size.height() / 2);
FloatPoint secondPoint = computeEndPoint(m_secondX.get(), m_secondY.get(), conversionData, size);
if (!m_secondX)
secondPoint.setX(size.width() / 2);
if (!m_secondY)
secondPoint.setY(size.height() / 2);
float firstRadius = 0;
if (m_firstRadius)
firstRadius = resolveRadius(m_firstRadius.get(), conversionData);
float secondRadius = 0;
float aspectRatio = 1;
if (m_secondRadius)
secondRadius = resolveRadius(m_secondRadius.get(), conversionData);
else if (m_endHorizontalSize) {
float width = size.width();
float height = size.height();
secondRadius = resolveRadius(m_endHorizontalSize.get(), conversionData, &width);
if (m_endVerticalSize)
aspectRatio = secondRadius / resolveRadius(m_endVerticalSize.get(), conversionData, &height);
else
aspectRatio = 1;
} else {
enum GradientShape { Circle, Ellipse };
GradientShape shape = Ellipse;
if ((m_shape && m_shape->getValueID() == 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->getValueID() : 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;
}
switch (fill) {
case ClosestSide: {
float xDist = min(secondPoint.x(), size.width() - secondPoint.x());
float yDist = min(secondPoint.y(), size.height() - secondPoint.y());
if (shape == Circle) {
float smaller = min(xDist, yDist);
xDist = smaller;
yDist = smaller;
}
secondRadius = xDist;
aspectRatio = xDist / yDist;
break;
}
case FarthestSide: {
float xDist = max(secondPoint.x(), size.width() - secondPoint.x());
float yDist = max(secondPoint.y(), size.height() - secondPoint.y());
if (shape == Circle) {
float larger = 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 {
float xDist = min(secondPoint.x(), size.width() - secondPoint.x());
float yDist = 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 {
float xDist = max(secondPoint.x(), size.width() - secondPoint.x());
float yDist = max(secondPoint.y(), size.height() - secondPoint.y());
secondRadius = horizontalEllipseRadius(corner - secondPoint, xDist / yDist);
aspectRatio = xDist / yDist;
}
break;
}
}
}
RefPtr<Gradient> gradient = Gradient::create(firstPoint, firstRadius, secondPoint, secondRadius, aspectRatio);
gradient->setDrawsInPMColorSpace(true);
float maxExtent = 0;
if (m_repeating) {
FloatPoint corner;
maxExtent = distanceToFarthestCorner(secondPoint, size, corner);
}
addStops(gradient.get(), conversionData, maxExtent);
return gradient.release();
}
bool CSSRadialGradientValue::equals(const CSSRadialGradientValue& other) const
{
if (m_gradientType == CSSDeprecatedRadialGradient)
return other.m_gradientType == m_gradientType
&& compareCSSValuePtr(m_firstX, other.m_firstX)
&& compareCSSValuePtr(m_firstY, other.m_firstY)
&& compareCSSValuePtr(m_secondX, other.m_secondX)
&& compareCSSValuePtr(m_secondY, other.m_secondY)
&& compareCSSValuePtr(m_firstRadius, other.m_firstRadius)
&& compareCSSValuePtr(m_secondRadius, other.m_secondRadius)
&& m_stops == other.m_stops;
if (m_repeating != other.m_repeating)
return false;
bool equalXandY = false;
if (m_firstX && m_firstY)
equalXandY = compareCSSValuePtr(m_firstX, other.m_firstX) && compareCSSValuePtr(m_firstY, other.m_firstY);
else if (m_firstX)
equalXandY = compareCSSValuePtr(m_firstX, other.m_firstX) && !other.m_firstY;
else if (m_firstY)
equalXandY = compareCSSValuePtr(m_firstY, other.m_firstY) && !other.m_firstX;
else
equalXandY = !other.m_firstX && !other.m_firstY;
if (!equalXandY)
return false;
bool equalShape = true;
bool equalSizingBehavior = true;
bool equalHorizontalAndVerticalSize = true;
if (m_shape)
equalShape = compareCSSValuePtr(m_shape, other.m_shape);
else if (m_sizingBehavior)
equalSizingBehavior = compareCSSValuePtr(m_sizingBehavior, other.m_sizingBehavior);
else if (m_endHorizontalSize && m_endVerticalSize)
equalHorizontalAndVerticalSize = compareCSSValuePtr(m_endHorizontalSize, other.m_endHorizontalSize) && compareCSSValuePtr(m_endVerticalSize, other.m_endVerticalSize);
else {
equalShape = !other.m_shape;
equalSizingBehavior = !other.m_sizingBehavior;
equalHorizontalAndVerticalSize = !other.m_endHorizontalSize && !other.m_endVerticalSize;
}
return equalShape && equalSizingBehavior && equalHorizontalAndVerticalSize && m_stops == other.m_stops;
}
void CSSRadialGradientValue::traceAfterDispatch(Visitor* visitor)
{
visitor->trace(m_firstRadius);
visitor->trace(m_secondRadius);
visitor->trace(m_shape);
visitor->trace(m_sizingBehavior);
visitor->trace(m_endHorizontalSize);
visitor->trace(m_endVerticalSize);
CSSGradientValue::traceAfterDispatch(visitor);
}
}