summaryrefslogtreecommitdiff
path: root/Source/WebCore/css/CSSGradientValue.cpp
diff options
context:
space:
mode:
authorLorry Tar Creator <lorry-tar-importer@lorry>2015-10-15 09:45:50 +0000
committerLorry Tar Creator <lorry-tar-importer@lorry>2015-10-15 09:45:50 +0000
commite15dd966d523731101f70ccf768bba12435a0208 (patch)
treeae9cb828a24ded2585a41af3f21411523b47897d /Source/WebCore/css/CSSGradientValue.cpp
downloadWebKitGtk-tarball-e15dd966d523731101f70ccf768bba12435a0208.tar.gz
webkitgtk-2.10.2webkitgtk-2.10.2
Diffstat (limited to 'Source/WebCore/css/CSSGradientValue.cpp')
-rw-r--r--Source/WebCore/css/CSSGradientValue.cpp1276
1 files changed, 1276 insertions, 0 deletions
diff --git a/Source/WebCore/css/CSSGradientValue.cpp b/Source/WebCore/css/CSSGradientValue.cpp
new file mode 100644
index 000000000..568857e1b
--- /dev/null
+++ b/Source/WebCore/css/CSSGradientValue.cpp
@@ -0,0 +1,1276 @@
+/*
+ * 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 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 "CSSCalculationValue.h"
+#include "CSSToLengthConversionData.h"
+#include "CSSValueKeywords.h"
+#include "FloatSize.h"
+#include "FloatSizeHash.h"
+#include "Gradient.h"
+#include "GradientImage.h"
+#include "Image.h"
+#include "NodeRenderStyle.h"
+#include "RenderElement.h"
+#include "RenderView.h"
+#include "StyleResolver.h"
+#include <wtf/text/StringBuilder.h>
+#include <wtf/text/WTFString.h>
+
+namespace WebCore {
+
+RefPtr<Image> CSSGradientValue::image(RenderElement* renderer, const FloatSize& size)
+{
+ if (size.isEmpty())
+ return nullptr;
+
+ bool cacheable = isCacheable();
+ if (cacheable) {
+ if (!clients().contains(renderer))
+ return nullptr;
+
+ Image* result = cachedImageForSize(size);
+ if (result)
+ return result;
+ }
+
+ RefPtr<Gradient> gradient;
+
+ if (is<CSSLinearGradientValue>(*this))
+ gradient = downcast<CSSLinearGradientValue>(*this).createGradient(*renderer, size);
+ else
+ gradient = downcast<CSSRadialGradientValue>(*this).createGradient(*renderer, size);
+
+ RefPtr<GradientImage> newImage = GradientImage::create(gradient, size);
+ if (cacheable)
+ saveCachedImageForSize(size, newImage);
+
+ return newImage;
+}
+
+// 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_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;
+ bool isMidpoint;
+
+ GradientStop()
+ : offset(0)
+ , specified(false)
+ , isMidpoint(false)
+ { }
+};
+
+RefPtr<CSSGradientValue> CSSGradientValue::gradientWithStylesResolved(StyleResolver* styleResolver)
+{
+ bool derived = false;
+ for (auto& stop : m_stops) {
+ if (!stop.isMidpoint && styleResolver->colorFromPrimitiveValueIsDerivedFromElement(*stop.m_color)) {
+ stop.m_colorIsDerivedFromElement = true;
+ derived = true;
+ break;
+ }
+ }
+
+ RefPtr<CSSGradientValue> result;
+ if (!derived)
+ result = this;
+ else if (is<CSSLinearGradientValue>(*this))
+ result = downcast<CSSLinearGradientValue>(*this).clone();
+ else if (is<CSSRadialGradientValue>(*this))
+ result = downcast<CSSRadialGradientValue>(*this).clone();
+ else {
+ ASSERT_NOT_REACHED();
+ return nullptr;
+ }
+
+ for (auto& stop : result->m_stops) {
+ if (!stop.isMidpoint)
+ stop.m_resolvedColor = styleResolver->colorFromPrimitiveValue(*stop.m_color);
+ }
+
+ return result;
+}
+
+static inline int interpolate(int min, int max, float position)
+{
+ return min + static_cast<int>(position * (max - min));
+}
+
+static inline Color interpolate(Color color1, Color color2, float position)
+{
+ int red = interpolate(color1.red(), color2.red(), position);
+ int green = interpolate(color1.green(), color2.green(), position);
+ int blue = interpolate(color1.blue(), color2.blue(), position);
+ int alpha = interpolate(color1.alpha(), color2.alpha(), position);
+
+ return Color(red, green, blue, alpha);
+}
+
+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);
+ }
+
+ // 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 (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].isMidpoint = stop.isMidpoint;
+ stops[i].color = stop.m_resolvedColor;
+
+ if (stop.m_position) {
+ const CSSPrimitiveValue& positionValue = *stop.m_position;
+ if (positionValue.isPercentage())
+ stops[i].offset = positionValue.getFloatValue(CSSPrimitiveValue::CSS_PERCENTAGE) / 100;
+ else if (positionValue.isLength() || positionValue.isViewportPercentageLength() || positionValue.isCalculatedPercentageWithLength()) {
+ if (!computedGradientLength) {
+ FloatSize gradientSize(gradientStart - gradientEnd);
+ gradientLength = gradientSize.diagonalLength();
+ }
+ 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 {
+ 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;
+ }
+ }
+ }
+
+ // 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;
+ stops[x].isMidpoint = false;
+ 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;
+ stops[x].isMidpoint = false;
+ 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 = powf(relativeOffset, logf(.5f) / logf(midpoint));
+ newStops[y].color = interpolate(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[numStops - 1].offset - stops[0].offset;
+ if (!gradientRange) {
+ stops.first().offset = 0;
+ stops.first().color = stops.last().color;
+ stops.shrink(1);
+ numStops = 1;
+ } else {
+ 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 (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;
+
+ // 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;
+ }
+ }
+ }
+ }
+
+ 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 (isLinearGradientValue()) {
+ float firstOffset = stops[0].offset;
+ float lastOffset = stops[numStops - 1].offset;
+ if (firstOffset != lastOffset) {
+ 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 {
+ // There's a single position that is outside the scale, clamp the positions to 1.
+ for (size_t i = 0; i < numStops; ++i)
+ stops[i].offset = 1;
+ }
+ } else if (isRadialGradientValue()) {
+ // 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);
+ // 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 (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, const CSSToLengthConversionData& conversionData, const FloatSize& 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()) {
+ Ref<CalculationValue> calculationValue { value.cssCalcValue()->createCalculationValue(conversionData) };
+ return calculationValue->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 FloatSize& 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 RenderElement*) const
+{
+ for (size_t i = 0; i < m_stops.size(); ++i) {
+ if (m_stops[i].m_resolvedColor.hasAlpha())
+ return false;
+ }
+ return true;
+}
+
+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.appendNumber(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(", ");
+ if (!stop.isMidpoint)
+ result.append(stop.m_color->cssText());
+ if (stop.m_position) {
+ if (!stop.isMidpoint)
+ result.append(' ');
+ result.append(stop.m_position->cssText());
+ }
+ }
+
+ }
+
+ 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());
+
+ CSSToLengthConversionData conversionData(&renderer.style(), renderer.document().documentElement()->renderStyle(), &renderer.view());
+
+ 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) {
+ // "Magic" corners, so the 50% line touches two corners.
+ 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;
+ // Compute angle, and flip it back to "bearing angle" degrees.
+ 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();
+ }
+
+ }
+
+ Ref<Gradient> gradient = Gradient::create(firstPoint, secondPoint);
+
+ // Now add the stops.
+ addStops(gradient, conversionData, 1);
+
+ return gradient;
+}
+
+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;
+}
+
+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());
+
+ // FIXME: share?
+ 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.appendNumber(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;
+
+ // 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->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(", ");
+ if (!stop.isMidpoint)
+ result.append(stop.m_color->cssText());
+ if (stop.m_position) {
+ if (!stop.isMidpoint)
+ 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()) // Can the radius be a percentage?
+ 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;
+}
+
+// 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
+Ref<Gradient> CSSRadialGradientValue::createGradient(RenderElement& renderer, const FloatSize& size)
+{
+ ASSERT(!size.isEmpty());
+
+ CSSToLengthConversionData conversionData(&renderer.style(), renderer.document().documentElement()->renderStyle(), &renderer.view());
+
+ 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, 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->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;
+ }
+
+ // 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;
+ }
+ }
+ }
+
+ Ref<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, conversionData, maxExtent);
+
+ return gradient;
+}
+
+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;
+}
+
+} // namespace WebCore