Simplify and streamline some Color-related code to prepare for some Color/ExtendedCol...
[WebKit-https.git] / Source / WebCore / css / CSSGradientValue.cpp
index bd382ca..eef295b 100644 (file)
  *    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
+ * 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 COMPUTER, INC. OR
+ * 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
 #include "CSSGradientValue.h"
 
 #include "CSSCalculationValue.h"
+#include "CSSToLengthConversionData.h"
 #include "CSSValueKeywords.h"
+#include "FloatSize.h"
 #include "Gradient.h"
 #include "GradientImage.h"
-#include "Image.h"
-#include "IntSize.h"
-#include "IntSizeHash.h"
 #include "NodeRenderStyle.h"
+#include "Pair.h"
 #include "RenderElement.h"
+#include "RenderView.h"
 #include "StyleResolver.h"
 #include <wtf/text/StringBuilder.h>
-#include <wtf/text/WTFString.h>
-
-using namespace std;
 
 namespace WebCore {
 
-PassRefPtr<Image> CSSGradientValue::image(RenderElement* renderer, const IntSize& size)
+static inline Ref<Gradient> createGradient(CSSGradientValue& value, RenderElement& renderer, FloatSize size)
 {
-    if (size.isEmpty())
-        return 0;
+    if (is<CSSLinearGradientValue>(value))
+        return downcast<CSSLinearGradientValue>(value).createGradient(renderer, size);
+    return downcast<CSSRadialGradientValue>(value).createGradient(renderer, size);
+}
 
+RefPtr<Image> CSSGradientValue::image(RenderElement& renderer, const FloatSize& size)
+{
+    if (size.isEmpty())
+        return nullptr;
     bool cacheable = isCacheable();
     if (cacheable) {
-        if (!clients().contains(renderer))
-            return 0;
-
-        Image* result = cachedImageForSize(size);
-        if (result)
+        if (!clients().contains(&renderer))
+            return nullptr;
+        if (auto* result = cachedImageForSize(size))
             return result;
     }
-
-    RefPtr<Gradient> gradient;
-
-    if (isLinearGradientValue())
-        gradient = toCSSLinearGradientValue(this)->createGradient(renderer, size);
-    else
-        gradient = toCSSRadialGradientValue(this)->createGradient(renderer, size);
-
-    RefPtr<GradientImage> newImage = GradientImage::create(gradient, size);
+    auto newImage = GradientImage::create(createGradient(*this, renderer, size), size);
     if (cacheable)
-        saveCachedImageForSize(size, newImage);
-
-    return newImage.release();
+        saveCachedImageForSize(size, newImage.get());
+    return WTFMove(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);
+    double aVal = a.m_position->doubleValue(CSSPrimitiveValue::CSS_NUMBER);
+    double bVal = b.m_position->doubleValue(CSSPrimitiveValue::CSS_NUMBER);
 
     return aVal < bVal;
 }
@@ -93,99 +86,193 @@ void CSSGradientValue::sortStopsIfNeeded()
 
 struct GradientStop {
     Color color;
-    float offset;
-    bool specified;
-
-    GradientStop()
-        : offset(0)
-        , specified(false)
-    { }
+    float offset { 0 };
+    bool specified { false };
+    bool isMidpoint { false };
 };
 
-PassRefPtr<CSSGradientValue> CSSGradientValue::gradientWithStylesResolved(StyleResolver* styleResolver)
+static inline Ref<CSSGradientValue> clone(CSSGradientValue& value)
+{
+    if (is<CSSLinearGradientValue>(value))
+        return downcast<CSSLinearGradientValue>(value).clone();
+    if (is<CSSRadialGradientValue>(value))
+        return downcast<CSSRadialGradientValue>(value).clone();
+    ASSERT(is<CSSConicGradientValue>(value));
+    return downcast<CSSConicGradientValue>(value).clone();
+}
+
+Ref<CSSGradientValue> CSSGradientValue::gradientWithStylesResolved(const StyleResolver& styleResolver)
 {
-    bool derived = false;
-    for (unsigned i = 0; i < m_stops.size(); i++)
-        if (styleResolver->colorFromPrimitiveValueIsDerivedFromElement(m_stops[i].m_color.get())) {
-            m_stops[i].m_colorIsDerivedFromElement = true;
-            derived = true;
+    bool colorIsDerivedFromElement = false;
+    for (auto& stop : m_stops) {
+        if (!stop.isMidpoint && styleResolver.colorFromPrimitiveValueIsDerivedFromElement(*stop.m_color)) {
+            stop.m_colorIsDerivedFromElement = true;
+            colorIsDerivedFromElement = true;
             break;
         }
+    }
+    auto result = colorIsDerivedFromElement ? clone(*this) : makeRef(*this);
+    for (auto& stop : result->m_stops) {
+        if (!stop.isMidpoint)
+            stop.m_resolvedColor = styleResolver.colorFromPrimitiveValue(*stop.m_color);
+    }
+    return result;
+}
 
-    RefPtr<CSSGradientValue> result;
-    if (!derived)
-        result = this;
-    else if (isLinearGradientValue())
-        result = toCSSLinearGradientValue(this)->clone();
-    else if (isRadialGradientValue())
-        result = toCSSRadialGradientValue(this)->clone();
-    else {
-        ASSERT_NOT_REACHED();
-        return 0;
+class LinearGradientAdapter {
+public:
+    explicit LinearGradientAdapter(Gradient::LinearData& data)
+        : m_data(data)
+    {
     }
 
-    for (unsigned i = 0; i < result->m_stops.size(); i++)
-        result->m_stops[i].m_resolvedColor = styleResolver->colorFromPrimitiveValue(result->m_stops[i].m_color.get());
+    FloatPoint startPoint() const { return m_data.point0; }
+    FloatPoint endPoint() const { return m_data.point1; }
+    float maxExtent(float, float) const { return 1; }
 
-    return result.release();
-}
+    void normalizeStopsAndEndpointsOutsideRange(Vector<GradientStop>& stops)
+    {
+        float firstOffset = stops.first().offset;
+        float lastOffset = stops.last().offset;
+        if (firstOffset != lastOffset) {
+            float scale = lastOffset - firstOffset;
 
-void CSSGradientValue::addStops(Gradient* gradient, RenderElement* renderer, RenderStyle* rootStyle, float maxLengthForRepeat)
-{
-    RenderStyle* style = renderer->style();
+            for (auto& stop : stops)
+                stop.offset = (stop.offset - firstOffset) / scale;
+
+            auto p0 = m_data.point0;
+            auto p1 = m_data.point1;
+            m_data.point0 = { p0.x() + firstOffset * (p1.x() - p0.x()), p0.y() + firstOffset * (p1.y() - p0.y()) };
+            m_data.point1 = { p1.x() + (lastOffset - 1) * (p1.x() - p0.x()), p1.y() + (lastOffset - 1) * (p1.y() - p0.y()) };
+        } else {
+            // There's a single position that is outside the scale, clamp the positions to 1.
+            for (auto& stop : stops)
+                stop.offset = 1;
+        }
+    }
+
+private:
+    Gradient::LinearData& m_data;
+};
+
+class RadialGradientAdapter {
+public:
+    explicit RadialGradientAdapter(Gradient::RadialData& data)
+        : m_data(data)
+    {
+    }
+
+    FloatPoint startPoint() const { return m_data.point0; }
+    FloatPoint endPoint() const { return m_data.point0 + FloatSize { m_data.endRadius, 0 }; }
+
+    // Radial gradients may need to extend further than the endpoints, because they have
+    // to repeat out to the corners of the box.
+    float maxExtent(float maxLengthForRepeat, float gradientLength) const
+    {
+        if (maxLengthForRepeat > gradientLength)
+            return gradientLength > 0 ? maxLengthForRepeat / gradientLength : 0;
+        return 1;
+    }
+
+    void normalizeStopsAndEndpointsOutsideRange(Vector<GradientStop>& stops)
+    {
+        auto numStops = stops.size();
+
+        // Rather than scaling the points < 0, we truncate them, so only scale according to the largest point.
+        float firstOffset = 0;
+        float lastOffset = stops.last().offset;
+        float scale = lastOffset - firstOffset;
+
+        // Reset points below 0 to the first visible color.
+        size_t firstZeroOrGreaterIndex = numStops;
+        for (size_t i = 0; i < numStops; ++i) {
+            if (stops[i].offset >= 0) {
+                firstZeroOrGreaterIndex = i;
+                break;
+            }
+        }
+
+        if (firstZeroOrGreaterIndex > 0) {
+            if (firstZeroOrGreaterIndex < numStops && stops[firstZeroOrGreaterIndex].offset > 0) {
+                float prevOffset = stops[firstZeroOrGreaterIndex - 1].offset;
+                float nextOffset = stops[firstZeroOrGreaterIndex].offset;
+
+                float interStopProportion = -prevOffset / (nextOffset - prevOffset);
+                // FIXME: when we interpolate gradients using premultiplied colors, this should do premultiplication.
+                Color blendedColor = blend(stops[firstZeroOrGreaterIndex - 1].color, stops[firstZeroOrGreaterIndex].color, interStopProportion);
+
+                // Clamp the positions to 0 and set the color.
+                for (size_t i = 0; i < firstZeroOrGreaterIndex; ++i) {
+                    stops[i].offset = 0;
+                    stops[i].color = blendedColor;
+                }
+            } else {
+                // All stops are below 0; just clamp them.
+                for (size_t i = 0; i < firstZeroOrGreaterIndex; ++i)
+                    stops[i].offset = 0;
+            }
+        }
+
+        for (auto& stop : stops)
+            stop.offset /= scale;
+
+        m_data.startRadius *= scale;
+        m_data.endRadius *= scale;
+    }
 
+private:
+    Gradient::RadialData& m_data;
+};
+
+template<typename GradientAdapter>
+Gradient::ColorStopVector CSSGradientValue::computeStops(GradientAdapter& 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];
+        Gradient::ColorStopVector result;
+        result.reserveInitialCapacity(m_stops.size());
 
+        for (auto& stop : m_stops) {
             float offset;
             if (stop.m_position->isPercentage())
-                offset = stop.m_position->getFloatValue(CSSPrimitiveValue::CSS_PERCENTAGE) / 100;
+                offset = stop.m_position->floatValue(CSSPrimitiveValue::CSS_PERCENTAGE) / 100;
             else
-                offset = stop.m_position->getFloatValue(CSSPrimitiveValue::CSS_NUMBER);
+                offset = stop.m_position->floatValue(CSSPrimitiveValue::CSS_NUMBER);
 
-            gradient->addColorStop(offset, stop.m_resolvedColor);
+            result.uncheckedAppend({ offset, stop.m_resolvedColor });
         }
 
-        // The back end already sorted the stops.
-        gradient->setStopsSorted(true);
-        return;
+        return result;
     }
 
     size_t numStops = m_stops.size();
-
     Vector<GradientStop> stops(numStops);
 
-    float gradientLength = 0;
-    bool computedGradientLength = false;
+    auto gradientStart = gradient.startPoint();
+    auto gradientEnd = gradient.endPoint();
 
-    FloatPoint gradientStart = gradient->p0();
-    FloatPoint gradientEnd;
-    if (isLinearGradientValue())
-        gradientEnd = gradient->p1();
-    else if (isRadialGradientValue())
-        gradientEnd = gradientStart + FloatSize(gradient->endRadius(), 0);
+    auto gradientSize = gradientStart - gradientEnd;
+    float gradientLength = gradientSize.diagonalLength();
 
     for (size_t i = 0; i < numStops; ++i) {
-        const CSSGradientColorStop& stop = m_stops[i];
+        auto& stop = m_stops[i];
 
+        stops[i].isMidpoint = stop.isMidpoint;
         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();
-                }
+            auto& positionValue = *stop.m_position;
+            if (positionValue.isPercentage())
+                stops[i].offset = positionValue.floatValue(CSSPrimitiveValue::CSS_PERCENTAGE) / 100;
+            else if (positionValue.isLength() || positionValue.isViewportPercentageLength() || positionValue.isCalculatedPercentageWithLength()) {
                 float length;
-                if (stop.m_position->isLength())
-                    length = stop.m_position->computeLength<float>(style, rootStyle, style->effectiveZoom());
-                else 
-                    length = stop.m_position->cssCalcValue()->toCalcValue(style, rootStyle, style->effectiveZoom())->evaluate(gradientLength);
+                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();
@@ -249,32 +336,101 @@ void CSSGradientValue::addStops(Gradient* gradient, RenderElement* renderer, Ren
         }
     }
 
+    // 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 = std::pow(relativeOffset, std::log(.5f) / std::log(midpoint));
+            // FIXME: Why not premultiply here?
+            newStops[y].color = blend(color1, color2, multiplier, false /* do not premultiply */);
+        }
+
+        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;
+        float gradientRange = stops.last().offset - stops.first().offset;
         if (!gradientRange) {
             stops.first().offset = 0;
             stops.first().color = stops.last().color;
             stops.shrink(1);
             numStops = 1;
         } else {
-            float maxExtent = 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 = maxLengthForRepeat / gradientLength;
-            }
+            float maxExtent = gradient.maxExtent(maxLengthForRepeat, gradientLength);
 
             size_t originalNumStops = numStops;
             size_t originalFirstStopIndex = 0;
@@ -320,93 +476,49 @@ void CSSGradientValue::addStops(Gradient* gradient, RenderElement* renderer, Ren
         }
     }
 
-    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;
+    if (stops.size() > 1 && (stops.first().offset < 0 || stops.last().offset > 1))
+        gradient.normalizeStopsAndEndpointsOutsideRange(stops);
+    
+    Gradient::ColorStopVector result;
+    result.reserveInitialCapacity(stops.size());
+    for (auto& stop : stops)
+        result.uncheckedAppend({ stop.offset, stop.color });
 
-                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);
+    return result;
 }
 
-static float positionFromValue(CSSPrimitiveValue* value, RenderStyle* style, RenderStyle* rootStyle, const IntSize& size, bool isHorizontal)
+static float positionFromValue(const CSSPrimitiveValue* value, const CSSToLengthConversionData& conversionData, const FloatSize& size, bool isHorizontal)
 {
-    float zoomFactor = style->effectiveZoom();
-
-    if (value->isNumber())
-        return value->getFloatValue() * zoomFactor;
-
+    int origin = 0;
+    int sign = 1;
     int edgeDistance = isHorizontal ? size.width() : size.height();
+    
+    // In this case the center of the gradient is given relative to an edge in the
+    // form of: [ top | bottom | right | left ] [ <percentage> | <length> ].
+    if (value->isPair()) {
+        CSSValueID originID = value->pairValue()->first()->valueID();
+        value = value->pairValue()->second();
+        
+        if (originID == CSSValueRight || originID == CSSValueBottom) {
+            // For right/bottom, the offset is relative to the far edge.
+            origin = edgeDistance;
+            sign = -1;
+        }
+    }
+    
+    if (value->isNumber())
+        return origin + sign * value->floatValue() * conversionData.zoom();
+    
     if (value->isPercentage())
-        return value->getFloatValue() / 100.f * edgeDistance;
+        return origin + sign * value->floatValue() / 100.f * edgeDistance;
 
-    if (value->isCalculatedPercentageWithLength())
-        return value->cssCalcValue()->toCalcValue(style, rootStyle, style->effectiveZoom())->evaluate(edgeDistance);
-
-    switch (value->getValueID()) {
+    if (value->isCalculatedPercentageWithLength()) {
+        Ref<CalculationValue> calculationValue { value->cssCalcValue()->createCalculationValue(conversionData) };
+        return origin + sign * calculationValue->evaluate(edgeDistance);
+    }
+    
+    switch (value->valueID()) {
     case CSSValueTop:
         ASSERT(!isHorizontal);
         return 0;
@@ -419,31 +531,31 @@ static float positionFromValue(CSSPrimitiveValue* value, RenderStyle* style, Ren
     case CSSValueRight:
         ASSERT(isHorizontal);
         return size.width();
+    case CSSValueCenter:
+        return origin + sign * .5f * edgeDistance;
     default:
         break;
     }
 
-    return value->computeLength<float>(style, rootStyle, zoomFactor);
+    return origin + sign * value->computeLength<float>(conversionData);
 }
 
-FloatPoint CSSGradientValue::computeEndPoint(CSSPrimitiveValue* horizontal, CSSPrimitiveValue* vertical, RenderStyle* style, RenderStyle* rootStyle, const IntSize& size)
+FloatPoint CSSGradientValue::computeEndPoint(CSSPrimitiveValue* horizontal, CSSPrimitiveValue* vertical, const CSSToLengthConversionData& conversionData, const FloatSize& size)
 {
     FloatPoint result;
 
     if (horizontal)
-        result.setX(positionFromValue(horizontal, style, rootStyle, size, true));
+        result.setX(positionFromValue(horizontal, conversionData, size, true));
 
     if (vertical)
-        result.setY(positionFromValue(vertical, style, rootStyle, size, false));
+        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];
-
+    for (auto& stop : m_stops) {
         if (stop.m_colorIsDerivedFromElement)
             return false;
 
@@ -457,10 +569,10 @@ bool CSSGradientValue::isCacheable() const
     return true;
 }
 
-bool CSSGradientValue::knownToBeOpaque(const RenderElement*) const
+bool CSSGradientValue::knownToBeOpaque() const
 {
-    for (size_t i = 0; i < m_stops.size(); ++i) {
-        if (m_stops[i].m_resolvedColor.hasAlpha())
+    for (auto& stop : m_stops) {
+        if (!stop.m_resolvedColor.isOpaque())
             return false;
     }
     return true;
@@ -479,20 +591,20 @@ String CSSLinearGradientValue::customCSSText() const
         result.append(' ');
         result.append(m_secondY->cssText());
 
-        for (unsigned i = 0; i < m_stops.size(); i++) {
-            const CSSGradientColorStop& stop = m_stops[i];
+        for (auto& stop : m_stops) {
             result.appendLiteral(", ");
-            if (stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER) == 0) {
+            auto position = stop.m_position->doubleValue(CSSPrimitiveValue::CSS_NUMBER);
+            if (!position) {
                 result.appendLiteral("from(");
                 result.append(stop.m_color->cssText());
                 result.append(')');
-            } else if (stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER) == 1) {
+            } else if (position == 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.appendNumber(position);
                 result.appendLiteral(", ");
                 result.append(stop.m_color->cssText());
                 result.append(')');
@@ -521,7 +633,7 @@ String CSSLinearGradientValue::customCSSText() const
         }
 
         for (unsigned i = 0; i < m_stops.size(); i++) {
-            const CSSGradientColorStop& stop = m_stops[i];
+            auto& stop = m_stops[i];
             result.appendLiteral(", ");
             result.append(stop.m_color->cssText());
             if (stop.m_position) {
@@ -540,7 +652,7 @@ String CSSLinearGradientValue::customCSSText() const
         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)) {
+        } else if ((m_firstX || m_firstY) && !(!m_firstX && m_firstY && m_firstY->valueID() == CSSValueBottom)) {
             result.appendLiteral("to ");
             if (m_firstX && m_firstY) {
                 result.append(m_firstX->cssText());
@@ -560,9 +672,11 @@ String CSSLinearGradientValue::customCSSText() const
             const CSSGradientColorStop& stop = m_stops[i];
             if (i)
                 result.appendLiteral(", ");
-            result.append(stop.m_color->cssText());
+            if (!stop.isMidpoint)
+                result.append(stop.m_color->cssText());
             if (stop.m_position) {
-                result.append(' ');
+                if (!stop.isMidpoint)
+                    result.append(' ');
                 result.append(stop.m_position->cssText());
             }
         }
@@ -574,7 +688,7 @@ String CSSLinearGradientValue::customCSSText() const
 }
 
 // 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, CSSGradientType type)
+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)
@@ -641,23 +755,23 @@ static void endPointsFromAngle(float angleDeg, const IntSize& size, FloatPoint&
     firstPoint.set(halfWidth - endX, halfHeight + endY);
 }
 
-PassRefPtr<Gradient> CSSLinearGradientValue::createGradient(RenderElement* renderer, const IntSize& size)
+Ref<Gradient> CSSLinearGradientValue::createGradient(RenderElement& renderer, const FloatSize& size)
 {
     ASSERT(!size.isEmpty());
 
-    RenderStyle* rootStyle = renderer->document().documentElement()->renderStyle();
+    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);
+        float angle = m_angle->floatValue(CSSPrimitiveValue::CSS_DEG);
         endPointsFromAngle(angle, size, firstPoint, secondPoint, m_gradientType);
     } else {
         switch (m_gradientType) {
         case CSSDeprecatedLinearGradient:
-            firstPoint = computeEndPoint(m_firstX.get(), m_firstY.get(), renderer->style(), rootStyle, size);
+            firstPoint = computeEndPoint(m_firstX.get(), m_firstY.get(), conversionData, size);
             if (m_secondX || m_secondY)
-                secondPoint = computeEndPoint(m_secondX.get(), m_secondY.get(), renderer->style(), rootStyle, size);
+                secondPoint = computeEndPoint(m_secondX.get(), m_secondY.get(), conversionData, size);
             else {
                 if (m_firstX)
                     secondPoint.setX(size.width() - firstPoint.x());
@@ -666,7 +780,7 @@ PassRefPtr<Gradient> CSSLinearGradientValue::createGradient(RenderElement* rende
             }
             break;
         case CSSPrefixedLinearGradient:
-            firstPoint = computeEndPoint(m_firstX.get(), m_firstY.get(), renderer->style(), rootStyle, size);
+            firstPoint = computeEndPoint(m_firstX.get(), m_firstY.get(), conversionData, size);
             if (m_firstX)
                 secondPoint.setX(size.width() - firstPoint.x());
             if (m_firstY)
@@ -677,15 +791,15 @@ PassRefPtr<Gradient> CSSLinearGradientValue::createGradient(RenderElement* rende
                 // "Magic" corners, so the 50% line touches two corners.
                 float rise = size.width();
                 float run = size.height();
-                if (m_firstX && m_firstX->getValueID() == CSSValueLeft)
+                if (m_firstX && m_firstX->valueID() == CSSValueLeft)
                     run *= -1;
-                if (m_firstY && m_firstY->getValueID() == CSSValueBottom)
+                if (m_firstY && m_firstY->valueID() == CSSValueBottom)
                     rise *= -1;
                 // Compute angle, and flip it back to "bearing angle" degrees.
                 float angle = 90 - rad2deg(atan2(rise, run));
                 endPointsFromAngle(angle, size, firstPoint, secondPoint, m_gradientType);
             } else if (m_firstX || m_firstY) { 
-                secondPoint = computeEndPoint(m_firstX.get(), m_firstY.get(), renderer->style(), rootStyle, size);
+                secondPoint = computeEndPoint(m_firstX.get(), m_firstY.get(), conversionData, size);
                 if (m_firstX)
                     firstPoint.setX(size.width() - secondPoint.x());
                 if (m_firstY)
@@ -696,15 +810,15 @@ PassRefPtr<Gradient> CSSLinearGradientValue::createGradient(RenderElement* rende
         default:
             ASSERT_NOT_REACHED();
         }
-
     }
 
-    RefPtr<Gradient> gradient = Gradient::create(firstPoint, secondPoint);
-
-    // Now add the stops.
-    addStops(gradient.get(), renderer, rootStyle, 1);
+    Gradient::LinearData data { firstPoint, secondPoint };
+    LinearGradientAdapter adapter { data };
+    auto stops = computeStops(adapter, conversionData, 1);
 
-    return gradient.release();
+    auto gradient = Gradient::create(WTFMove(data));
+    gradient->setSortedColorStops(WTFMove(stops));
+    return gradient;
 }
 
 bool CSSLinearGradientValue::equals(const CSSLinearGradientValue& other) const
@@ -758,20 +872,20 @@ String CSSRadialGradientValue::customCSSText() const
         result.append(m_secondRadius->cssText());
 
         // FIXME: share?
-        for (unsigned i = 0; i < m_stops.size(); i++) {
-            const CSSGradientColorStop& stop = m_stops[i];
+        for (auto& stop : m_stops) {
             result.appendLiteral(", ");
-            if (stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER) == 0) {
+            auto position = stop.m_position->doubleValue(CSSPrimitiveValue::CSS_NUMBER);
+            if (!position) {
                 result.appendLiteral("from(");
                 result.append(stop.m_color->cssText());
                 result.append(')');
-            } else if (stop.m_position->getDoubleValue(CSSPrimitiveValue::CSS_NUMBER) == 1) {
+            } else if (position == 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.appendNumber(position);
                 result.appendLiteral(", ");
                 result.append(stop.m_color->cssText());
                 result.append(')');
@@ -833,12 +947,12 @@ String CSSRadialGradientValue::customCSSText() const
 
         // 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))) {
+        if (m_shape && m_shape->valueID() != CSSValueEllipse && (m_sizingBehavior || (!m_sizingBehavior && !m_endHorizontalSize))) {
             result.appendLiteral("circle");
             wroteSomething = true;
         }
 
-        if (m_sizingBehavior && m_sizingBehavior->getValueID() != CSSValueFarthestCorner) {
+        if (m_sizingBehavior && m_sizingBehavior->valueID() != CSSValueFarthestCorner) {
             if (wroteSomething)
                 result.append(' ');
             result.append(m_sizingBehavior->cssText());
@@ -876,9 +990,11 @@ String CSSRadialGradientValue::customCSSText() const
             const CSSGradientColorStop& stop = m_stops[i];
             if (i)
                 result.appendLiteral(", ");
-            result.append(stop.m_color->cssText());
+            if (!stop.isMidpoint)
+                result.append(stop.m_color->cssText());
             if (stop.m_position) {
-                result.append(' ');
+                if (!stop.isMidpoint)
+                    result.append(' ');
                 result.append(stop.m_position->cssText());
             }
         }
@@ -889,17 +1005,15 @@ String CSSRadialGradientValue::customCSSText() const
     return result.toString();
 }
 
-float CSSRadialGradientValue::resolveRadius(CSSPrimitiveValue* radius, RenderStyle* style, RenderStyle* rootStyle, float* widthOrHeight)
+float CSSRadialGradientValue::resolveRadius(CSSPrimitiveValue& radius, const CSSToLengthConversionData& conversionData, float* widthOrHeight)
 {
-    float zoomFactor = style->effectiveZoom();
-
     float result = 0;
-    if (radius->isNumber()) // Can the radius be a percentage?
-        result = radius->getFloatValue() * zoomFactor;
-    else if (widthOrHeight && radius->isPercentage())
-        result = *widthOrHeight * radius->getFloatValue() / 100;
+    if (radius.isNumber()) // Can the radius be a percentage?
+        result = radius.floatValue() * conversionData.zoom();
+    else if (widthOrHeight && radius.isPercentage())
+        result = *widthOrHeight * radius.floatValue() / 100;
     else
-        result = radius->computeLength<float>(style, rootStyle, zoomFactor);
+        result = radius.computeLength<float>(conversionData);
 
     return result;
 }
@@ -981,19 +1095,19 @@ static inline float horizontalEllipseRadius(const FloatSize& p, float aspectRati
 }
 
 // FIXME: share code with the linear version
-PassRefPtr<Gradient> CSSRadialGradientValue::createGradient(RenderElement* renderer, const IntSize& size)
+Ref<Gradient> CSSRadialGradientValue::createGradient(RenderElement& renderer, const FloatSize& size)
 {
     ASSERT(!size.isEmpty());
 
-    RenderStyle* rootStyle = renderer->document().documentElement()->renderStyle();
+    CSSToLengthConversionData conversionData(&renderer.style(), renderer.document().documentElement()->renderStyle(), &renderer.view());
 
-    FloatPoint firstPoint = computeEndPoint(m_firstX.get(), m_firstY.get(), renderer->style(), rootStyle, size);
+    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(), renderer->style(), rootStyle, size);
+    FloatPoint secondPoint = computeEndPoint(m_secondX.get(), m_secondY.get(), conversionData, size);
     if (!m_secondX)
         secondPoint.setX(size.width() / 2);
     if (!m_secondY)
@@ -1001,31 +1115,31 @@ PassRefPtr<Gradient> CSSRadialGradientValue::createGradient(RenderElement* rende
 
     float firstRadius = 0;
     if (m_firstRadius)
-        firstRadius = resolveRadius(m_firstRadius.get(), renderer->style(), rootStyle);
+        firstRadius = resolveRadius(*m_firstRadius, conversionData);
 
     float secondRadius = 0;
     float aspectRatio = 1; // width / height.
     if (m_secondRadius)
-        secondRadius = resolveRadius(m_secondRadius.get(), renderer->style(), rootStyle);
+        secondRadius = resolveRadius(*m_secondRadius, conversionData);
     else if (m_endHorizontalSize) {
         float width = size.width();
         float height = size.height();
-        secondRadius = resolveRadius(m_endHorizontalSize.get(), renderer->style(), rootStyle, &width);
+        secondRadius = resolveRadius(*m_endHorizontalSize, conversionData, &width);
         if (m_endVerticalSize)
-            aspectRatio = secondRadius / resolveRadius(m_endVerticalSize.get(), renderer->style(), rootStyle, &height);
+            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)
+        if ((m_shape && m_shape->valueID() == CSSValueCircle)
             || (!m_shape && !m_sizingBehavior && m_endHorizontalSize && !m_endVerticalSize))
             shape = Circle;
 
         enum GradientFill { ClosestSide, ClosestCorner, FarthestSide, FarthestCorner };
         GradientFill fill = FarthestCorner;
 
-        switch (m_sizingBehavior ? m_sizingBehavior->getValueID() : 0) {
+        switch (m_sizingBehavior ? m_sizingBehavior->valueID() : 0) {
         case CSSValueContain:
         case CSSValueClosestSide:
             fill = ClosestSide;
@@ -1049,10 +1163,10 @@ PassRefPtr<Gradient> CSSRadialGradientValue::createGradient(RenderElement* rende
         // Horizontal
         switch (fill) {
         case ClosestSide: {
-            float xDist = min(secondPoint.x(), size.width() - secondPoint.x());
-            float yDist = min(secondPoint.y(), size.height() - secondPoint.y());
+            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 = min(xDist, yDist);
+                float smaller = std::min(xDist, yDist);
                 xDist = smaller;
                 yDist = smaller;
             }
@@ -1061,10 +1175,10 @@ PassRefPtr<Gradient> CSSRadialGradientValue::createGradient(RenderElement* rende
             break;
         }
         case FarthestSide: {
-            float xDist = max(secondPoint.x(), size.width() - secondPoint.x());
-            float yDist = max(secondPoint.y(), size.height() - secondPoint.y());
+            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 = max(xDist, yDist);
+                float larger = std::max(xDist, yDist);
                 xDist = larger;
                 yDist = larger;
             }
@@ -1080,8 +1194,8 @@ PassRefPtr<Gradient> CSSRadialGradientValue::createGradient(RenderElement* rende
             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());
+                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;
@@ -1097,8 +1211,8 @@ PassRefPtr<Gradient> CSSRadialGradientValue::createGradient(RenderElement* rende
             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());
+                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;
@@ -1108,19 +1222,20 @@ PassRefPtr<Gradient> CSSRadialGradientValue::createGradient(RenderElement* rende
         }
     }
 
-    RefPtr<Gradient> gradient = Gradient::create(firstPoint, firstRadius, secondPoint, secondRadius, aspectRatio);
-
-    // addStops() only uses maxExtent for repeating gradients.
+    // computeStops() 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);
+    Gradient::RadialData data { firstPoint, secondPoint, firstRadius, secondRadius, aspectRatio };
+    RadialGradientAdapter adapter { data };
+    auto stops = computeStops(adapter, conversionData, maxExtent);
 
-    return gradient.release();
+    auto gradient = Gradient::create(WTFMove(data));
+    gradient->setSortedColorStops(WTFMove(stops));
+    return gradient;
 }
 
 bool CSSRadialGradientValue::equals(const CSSRadialGradientValue& other) const
@@ -1169,4 +1284,80 @@ bool CSSRadialGradientValue::equals(const CSSRadialGradientValue& other) const
     return equalShape && equalSizingBehavior && equalHorizontalAndVerticalSize && m_stops == other.m_stops;
 }
 
+
+String CSSConicGradientValue::customCSSText() const
+{
+    StringBuilder result;
+
+    if (m_repeating)
+        result.appendLiteral("repeating-conic-gradient(");
+    else
+        result.appendLiteral("conic-gradient(");
+
+    bool wroteSomething = false;
+
+    if (m_angle) {
+        result.appendLiteral("from ");
+        result.append(m_angle->cssText());
+        wroteSomething = true;
+    }
+
+    if (m_firstX && m_firstY) {
+        if (wroteSomething)
+            result.appendLiteral(" ");
+        result.appendLiteral("at ");
+        result.append(m_firstX->cssText());
+        result.append(' ');
+        result.append(m_firstY->cssText());
+        wroteSomething = true;
+    }
+
+    if (wroteSomething)
+        result.appendLiteral(", ");
+
+    bool wroteFirstStop = false;
+    for (auto& stop : m_stops) {
+        if (wroteFirstStop)
+            result.appendLiteral(", ");
+        wroteFirstStop = true;
+        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();
+}
+
+Ref<Gradient> CSSConicGradientValue::createGradient(RenderElement&, const FloatSize&)
+{
+    // FIXME: Implement.
+    return Gradient::create(Gradient::LinearData { });
+}
+
+bool CSSConicGradientValue::equals(const CSSConicGradientValue& other) const
+{
+    if (m_repeating != other.m_repeating)
+        return false;
+
+    if (!compareCSSValuePtr(m_angle, 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;
+}
+
 } // namespace WebCore