summaryrefslogtreecommitdiffstats
path: root/Source/WebCore/css/CSSGradientValue.cpp
diff options
context:
space:
mode:
authorSteve Block <steveblock@google.com>2011-05-06 11:45:16 +0100
committerSteve Block <steveblock@google.com>2011-05-12 13:44:10 +0100
commitcad810f21b803229eb11403f9209855525a25d57 (patch)
tree29a6fd0279be608e0fe9ffe9841f722f0f4e4269 /Source/WebCore/css/CSSGradientValue.cpp
parent121b0cf4517156d0ac5111caf9830c51b69bae8f (diff)
downloadexternal_webkit-cad810f21b803229eb11403f9209855525a25d57.zip
external_webkit-cad810f21b803229eb11403f9209855525a25d57.tar.gz
external_webkit-cad810f21b803229eb11403f9209855525a25d57.tar.bz2
Merge WebKit at r75315: Initial merge by git.
Change-Id: I570314b346ce101c935ed22a626b48c2af266b84
Diffstat (limited to 'Source/WebCore/css/CSSGradientValue.cpp')
-rw-r--r--Source/WebCore/css/CSSGradientValue.cpp833
1 files changed, 833 insertions, 0 deletions
diff --git a/Source/WebCore/css/CSSGradientValue.cpp b/Source/WebCore/css/CSSGradientValue.cpp
new file mode 100644
index 0000000..8040c6c
--- /dev/null
+++ b/Source/WebCore/css/CSSGradientValue.cpp
@@ -0,0 +1,833 @@
+/*
+ * Copyright (C) 2008 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 COMPUTER, 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 COMPUTER, 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 "CSSValueKeywords.h"
+#include "CSSStyleSelector.h"
+#include "GeneratedImage.h"
+#include "Gradient.h"
+#include "Image.h"
+#include "IntSize.h"
+#include "IntSizeHash.h"
+#include "NodeRenderStyle.h"
+#include "PlatformString.h"
+#include "RenderObject.h"
+
+using namespace std;
+
+namespace WebCore {
+
+Image* CSSGradientValue::image(RenderObject* renderer, const IntSize& size)
+{
+ ASSERT(m_clients.contains(renderer));
+
+ // Need to look up our size. Create a string of width*height to use as a hash key.
+ // FIXME: hashing based only on size is not sufficient. Color stops may use context-sensitive units (like em)
+ // that should force the color stop positions to be recomputed.
+ Image* result = getImage(renderer, size);
+ if (result)
+ return result;
+
+ if (size.isEmpty())
+ return 0;
+
+ // We need to create an image.
+ RefPtr<Image> newImage = GeneratedImage::create(createGradient(renderer, size), size);
+ result = newImage.get();
+ putImage(size, newImage.release());
+
+ return result;
+}
+
+// Should only ever be called for deprecated gradients.
+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_deprecatedType);
+ if (!m_stopsSorted) {
+ if (m_stops.size())
+ std::stable_sort(m_stops.begin(), m_stops.end(), compareStops);
+ m_stopsSorted = true;
+ }
+}
+
+static inline int blend(int from, int to, float progress)
+{
+ return int(from + (to - from) * progress);
+}
+
+static inline Color blend(const Color& from, const Color& to, float progress)
+{
+ // FIXME: when we interpolate gradients using premultiplied colors, this should also do premultiplication.
+ return Color(blend(from.red(), to.red(), progress),
+ blend(from.green(), to.green(), progress),
+ blend(from.blue(), to.blue(), progress),
+ blend(from.alpha(), to.alpha(), progress));
+}
+
+struct GradientStop {
+ Color color;
+ float offset;
+ bool specified;
+
+ GradientStop()
+ : offset(0)
+ , specified(false)
+ { }
+};
+
+void CSSGradientValue::addStops(Gradient* gradient, RenderObject* renderer, RenderStyle* rootStyle, float maxLengthForRepeat)
+{
+ RenderStyle* style = renderer->style();
+
+ if (m_deprecatedType) {
+ sortStopsIfNeeded();
+
+ // We have to resolve colors.
+ for (unsigned i = 0; i < m_stops.size(); i++) {
+ const CSSGradientColorStop& stop = m_stops[i];
+ Color color = renderer->document()->styleSelector()->getColorFromPrimitiveValue(stop.m_color.get());
+
+ float offset;
+ if (stop.m_position->primitiveType() == CSSPrimitiveValue::CSS_PERCENTAGE)
+ offset = stop.m_position->getFloatValue(CSSPrimitiveValue::CSS_PERCENTAGE) / 100;
+ else
+ offset = stop.m_position->getFloatValue(CSSPrimitiveValue::CSS_NUMBER);
+
+ gradient->addColorStop(offset, color);
+ }
+
+ // The back end already sorted the stops.
+ gradient->setStopsSorted(true);
+ return;
+ }
+
+ size_t numStops = m_stops.size();
+
+ Vector<GradientStop> stops(numStops);
+
+ float gradientLength = 0;
+ bool computedGradientLength = false;
+
+ FloatPoint gradientStart = gradient->p0();
+ FloatPoint gradientEnd;
+ if (isLinearGradient())
+ gradientEnd = gradient->p1();
+ else if (isRadialGradient())
+ gradientEnd = gradientStart + FloatSize(gradient->endRadius(), 0);
+
+ for (size_t i = 0; i < numStops; ++i) {
+ const CSSGradientColorStop& stop = m_stops[i];
+
+ stops[i].color = renderer->document()->styleSelector()->getColorFromPrimitiveValue(stop.m_color.get());
+
+ if (stop.m_position) {
+ int type = stop.m_position->primitiveType();
+ if (type == CSSPrimitiveValue::CSS_PERCENTAGE)
+ stops[i].offset = stop.m_position->getFloatValue(CSSPrimitiveValue::CSS_PERCENTAGE) / 100;
+ else if (CSSPrimitiveValue::isUnitTypeLength(type)) {
+ float length = stop.m_position->computeLengthFloat(style, rootStyle, style->effectiveZoom());
+ if (!computedGradientLength) {
+ FloatSize gradientSize(gradientStart - gradientEnd);
+ gradientLength = gradientSize.diagonalLength();
+ }
+ stops[i].offset = (gradientLength > 0) ? length / gradientLength : 0;
+ } else {
+ ASSERT_NOT_REACHED();
+ stops[i].offset = 0;
+ }
+ stops[i].specified = true;
+ } 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;
+ stops[i].specified = true;
+ } else if (numStops > 1 && i == numStops - 1) {
+ stops[i].offset = 1;
+ stops[i].specified = true;
+ }
+ }
+
+ // 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].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 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].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 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) {
+ float maxExtent = 1;
+
+ // Radial gradients may need to extend further than the endpoints, because they have
+ // to repeat out to the corners of the box.
+ if (isRadialGradient()) {
+ if (!computedGradientLength) {
+ FloatSize gradientSize(gradientStart - gradientEnd);
+ gradientLength = gradientSize.diagonalLength();
+ }
+
+ if (maxLengthForRepeat > gradientLength)
+ 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.prepend(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) {
+ GradientStop newStop = stops[srcStopOrdinal];
+ newStop.offset = currOffset;
+ stops.append(newStop);
+ if (currOffset > maxExtent)
+ break;
+ if (srcStopOrdinal < originalNumStops - 1)
+ currOffset += stops[originalFirstStopIndex + srcStopOrdinal + 1].offset - stops[originalFirstStopIndex + srcStopOrdinal].offset;
+ srcStopOrdinal = (srcStopOrdinal + 1) % originalNumStops;
+ }
+ }
+ }
+
+ numStops = stops.size();
+
+ // If the gradient goes outside the 0-1 range, normalize it by moving the endpoints, and adjusting the stops.
+ if (numStops > 1 && (stops[0].offset < 0 || stops[numStops - 1].offset > 1)) {
+ if (isLinearGradient()) {
+ 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 (isRadialGradient()) {
+ // Rather than scaling the points < 0, we truncate them, so only scale according to the largest point.
+ float firstOffset = 0;
+ float lastOffset = stops[numStops - 1].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);
+ 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 (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);
+
+ gradient->setStopsSorted(true);
+}
+
+static float positionFromValue(CSSPrimitiveValue* value, RenderStyle* style, RenderStyle* rootStyle, const IntSize& size, bool isHorizontal)
+{
+ float zoomFactor = style->effectiveZoom();
+
+ switch (value->primitiveType()) {
+ case CSSPrimitiveValue::CSS_NUMBER:
+ return value->getFloatValue() * zoomFactor;
+
+ case CSSPrimitiveValue::CSS_PERCENTAGE:
+ return value->getFloatValue() / 100.f * (isHorizontal ? size.width() : size.height());
+
+ case CSSPrimitiveValue::CSS_IDENT:
+ switch (value->getIdent()) {
+ 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:
+ return value->computeLengthFloat(style, rootStyle, zoomFactor);
+ }
+}
+
+FloatPoint CSSGradientValue::computeEndPoint(CSSPrimitiveValue* first, CSSPrimitiveValue* second, RenderStyle* style, RenderStyle* rootStyle, const IntSize& size)
+{
+ FloatPoint result;
+
+ if (first)
+ result.setX(positionFromValue(first, style, rootStyle, size, true));
+
+ if (second)
+ result.setY(positionFromValue(second, style, rootStyle, size, false));
+
+ return result;
+}
+
+String CSSLinearGradientValue::cssText() const
+{
+ String result;
+ if (m_deprecatedType) {
+ result = "-webkit-gradient(linear, ";
+ result += m_firstX->cssText() + " ";
+ result += m_firstY->cssText() + ", ";
+ result += m_secondX->cssText() + " ";
+ result += m_secondY->cssText();
+
+ for (unsigned i = 0; i < m_stops.size(); i++) {
+ const CSSGradientColorStop& stop = m_stops[i];
+ result += ", ";
+ if (stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER) == 0)
+ result += "from(" + stop.m_color->cssText() + ")";
+ else if (stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER) == 1)
+ result += "to(" + stop.m_color->cssText() + ")";
+ else
+ result += "color-stop(" + String::number(stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER)) + ", " + stop.m_color->cssText() + ")";
+ }
+ } else {
+ result = m_repeating ? "-webkit-repeating-linear-gradient(" : "-webkit-linear-gradient(";
+ if (m_angle)
+ result += m_angle->cssText();
+ else {
+ if (m_firstX && m_firstY)
+ result += m_firstX->cssText() + " " + m_firstY->cssText();
+ else if (m_firstX || m_firstY) {
+ if (m_firstX)
+ result += m_firstX->cssText();
+
+ if (m_firstY)
+ result += m_firstY->cssText();
+ }
+ }
+
+ for (unsigned i = 0; i < m_stops.size(); i++) {
+ const CSSGradientColorStop& stop = m_stops[i];
+ result += ", ";
+ result += stop.m_color->cssText();
+ if (stop.m_position)
+ result += " " + stop.m_position->cssText();
+ }
+ }
+
+ result += ")";
+ return result;
+}
+
+// Compute the endpoints so that a gradient of the given angle covers a box of the given size.
+static void endPointsFromAngle(float angleDeg, const IntSize& size, FloatPoint& firstPoint, FloatPoint& secondPoint)
+{
+ angleDeg = fmodf(angleDeg, 360);
+ if (angleDeg < 0)
+ angleDeg += 360;
+
+ if (!angleDeg) {
+ firstPoint.set(0, 0);
+ secondPoint.set(size.width(), 0);
+ return;
+ }
+
+ if (angleDeg == 90) {
+ firstPoint.set(0, size.height());
+ secondPoint.set(0, 0);
+ return;
+ }
+
+ if (angleDeg == 180) {
+ firstPoint.set(size.width(), 0);
+ secondPoint.set(0, 0);
+ return;
+ }
+
+ float slope = tan(deg2rad(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.
+ 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, flipping the Y to account for angles going anticlockwise.
+ secondPoint.set(halfWidth + endX, size.height() - (halfHeight + endY));
+ // Reflect around the center for the start point.
+ firstPoint.set(size.width() - secondPoint.x(), size.height() - secondPoint.y());
+}
+
+PassRefPtr<Gradient> CSSLinearGradientValue::createGradient(RenderObject* renderer, const IntSize& size)
+{
+ ASSERT(!size.isEmpty());
+
+ RenderStyle* rootStyle = renderer->document()->documentElement()->renderStyle();
+
+ FloatPoint firstPoint;
+ FloatPoint secondPoint;
+ if (m_angle) {
+ float angle = m_angle->getFloatValue(CSSPrimitiveValue::CSS_DEG);
+ endPointsFromAngle(angle, size, firstPoint, secondPoint);
+ } else {
+ firstPoint = computeEndPoint(m_firstX.get(), m_firstY.get(), renderer->style(), rootStyle, size);
+
+ if (m_secondX || m_secondY)
+ secondPoint = computeEndPoint(m_secondX.get(), m_secondY.get(), renderer->style(), rootStyle, size);
+ else {
+ if (m_firstX)
+ secondPoint.setX(size.width() - firstPoint.x());
+ if (m_firstY)
+ secondPoint.setY(size.height() - firstPoint.y());
+ }
+ }
+
+ RefPtr<Gradient> gradient = Gradient::create(firstPoint, secondPoint);
+
+ // Now add the stops.
+ addStops(gradient.get(), renderer, rootStyle, 1);
+
+ return gradient.release();
+}
+
+String CSSRadialGradientValue::cssText() const
+{
+ String result;
+
+ if (m_deprecatedType) {
+ result = "-webkit-gradient(radial, ";
+
+ result += m_firstX->cssText() + " ";
+ result += m_firstY->cssText() + ", ";
+ result += m_firstRadius->cssText() + ", ";
+ result += m_secondX->cssText() + " ";
+ result += m_secondY->cssText();
+ result += ", ";
+ result += m_secondRadius->cssText();
+
+ // FIXME: share?
+ for (unsigned i = 0; i < m_stops.size(); i++) {
+ const CSSGradientColorStop& stop = m_stops[i];
+ result += ", ";
+ if (stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER) == 0)
+ result += "from(" + stop.m_color->cssText() + ")";
+ else if (stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER) == 1)
+ result += "to(" + stop.m_color->cssText() + ")";
+ else
+ result += "color-stop(" + String::number(stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER)) + ", " + stop.m_color->cssText() + ")";
+ }
+ } else {
+
+ result = m_repeating ? "-webkit-repeating-radial-gradient(" : "-webkit-radial-gradient(";
+ if (m_firstX && m_firstY) {
+ result += m_firstX->cssText() + " " + m_firstY->cssText();
+ } else if (m_firstX)
+ result += m_firstX->cssText();
+ else if (m_firstY)
+ result += m_firstY->cssText();
+ else
+ result += "center";
+
+
+ if (m_shape || m_sizingBehavior) {
+ result += ", ";
+ if (m_shape)
+ result += m_shape->cssText() + " ";
+ else
+ result += "ellipse ";
+
+ if (m_sizingBehavior)
+ result += m_sizingBehavior->cssText();
+ else
+ result += "cover";
+
+ } else if (m_endHorizontalSize && m_endVerticalSize) {
+ result += ", ";
+ result += m_endHorizontalSize->cssText() + " " + m_endVerticalSize->cssText();
+ }
+
+ for (unsigned i = 0; i < m_stops.size(); i++) {
+ const CSSGradientColorStop& stop = m_stops[i];
+ result += ", ";
+ result += stop.m_color->cssText();
+ if (stop.m_position)
+ result += " " + stop.m_position->cssText();
+ }
+ }
+
+ result += ")";
+ return result;
+}
+
+float CSSRadialGradientValue::resolveRadius(CSSPrimitiveValue* radius, RenderStyle* style, RenderStyle* rootStyle, float* widthOrHeight)
+{
+ float zoomFactor = style->effectiveZoom();
+
+ float result = 0;
+ if (radius->primitiveType() == CSSPrimitiveValue::CSS_NUMBER) // Can the radius be a percentage?
+ result = radius->getFloatValue() * zoomFactor;
+ else if (widthOrHeight && radius->primitiveType() == CSSPrimitiveValue::CSS_PERCENTAGE)
+ result = *widthOrHeight * radius->getFloatValue() / 100;
+ else
+ result = radius->computeLengthFloat(style, rootStyle, zoomFactor);
+
+ 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 sqrtf(p.width() * p.width() + (p.height() * p.height()) / (1 / (aspectRatio * aspectRatio)));
+}
+
+// FIXME: share code with the linear version
+PassRefPtr<Gradient> CSSRadialGradientValue::createGradient(RenderObject* renderer, const IntSize& size)
+{
+ ASSERT(!size.isEmpty());
+
+ RenderStyle* rootStyle = renderer->document()->documentElement()->renderStyle();
+
+ FloatPoint firstPoint = computeEndPoint(m_firstX.get(), m_firstY.get(), renderer->style(), rootStyle, 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(), renderer->style(), rootStyle, 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(), renderer->style(), rootStyle);
+
+ float secondRadius = 0;
+ float aspectRatio = 1; // width / height.
+ if (m_secondRadius)
+ secondRadius = resolveRadius(m_secondRadius.get(), renderer->style(), rootStyle);
+ else if (m_endHorizontalSize || m_endVerticalSize) {
+ float width = size.width();
+ float height = size.height();
+ secondRadius = resolveRadius(m_endHorizontalSize.get(), renderer->style(), rootStyle, &width);
+ aspectRatio = secondRadius / resolveRadius(m_endVerticalSize.get(), renderer->style(), rootStyle, &height);
+ } else {
+ enum GradientShape { Circle, Ellipse };
+ GradientShape shape = Ellipse;
+ if (m_shape && m_shape->primitiveType() == CSSPrimitiveValue::CSS_IDENT && m_shape->getIdent() == CSSValueCircle)
+ shape = Circle;
+
+ enum GradientFill { ClosestSide, ClosestCorner, FarthestSide, FarthestCorner };
+ GradientFill fill = FarthestCorner;
+
+ if (m_sizingBehavior && m_sizingBehavior->primitiveType() == CSSPrimitiveValue::CSS_IDENT) {
+ switch (m_sizingBehavior->getIdent()) {
+ case CSSValueContain:
+ case CSSValueClosestSide:
+ fill = ClosestSide;
+ break;
+ case CSSValueClosestCorner:
+ fill = ClosestCorner;
+ break;
+ case CSSValueFarthestSide:
+ fill = FarthestSide;
+ break;
+ case CSSValueCover:
+ case CSSValueFarthestCorner:
+ fill = FarthestCorner;
+ break;
+ }
+ }
+
+ // Now compute the end radii based on the second point, shape and fill.
+
+ // Horizontal
+ 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 {
+ // 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 = 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 {
+ // 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 = 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);
+
+ // addStops() only uses maxExtent for repeating gradients.
+ float maxExtent = 0;
+ if (m_repeating) {
+ FloatPoint corner;
+ maxExtent = distanceToFarthestCorner(secondPoint, size, corner);
+ }
+
+ // Now add the stops.
+ addStops(gradient.get(), renderer, rootStyle, maxExtent);
+
+ return gradient.release();
+}
+
+} // namespace WebCore