| /* |
| * Copyright (C) 2008 Apple Inc. All rights reserved. |
| * Copyright (C) 2015 Google 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 "core/css/CSSGradientValue.h" |
| |
| #include "core/CSSValueKeywords.h" |
| #include "core/css/CSSCalculationValue.h" |
| #include "core/css/CSSIdentifierValue.h" |
| #include "core/css/CSSToLengthConversionData.h" |
| #include "core/css/CSSValuePair.h" |
| #include "core/dom/NodeComputedStyle.h" |
| #include "core/dom/TextLinkColors.h" |
| #include "core/layout/LayoutObject.h" |
| #include "core/layout/api/LayoutViewItem.h" |
| #include "platform/geometry/IntSize.h" |
| #include "platform/graphics/Gradient.h" |
| #include "platform/graphics/GradientGeneratedImage.h" |
| #include "platform/graphics/Image.h" |
| #include "platform/graphics/skia/SkiaUtils.h" |
| #include "wtf/text/StringBuilder.h" |
| #include "wtf/text/WTFString.h" |
| #include <algorithm> |
| #include <utility> |
| |
| namespace blink { |
| |
| namespace { |
| |
| static bool colorIsDerivedFromElement(const CSSIdentifierValue& value) { |
| CSSValueID valueID = value.getValueID(); |
| switch (valueID) { |
| case CSSValueInternalQuirkInherit: |
| case CSSValueWebkitLink: |
| case CSSValueWebkitActivelink: |
| case CSSValueCurrentcolor: |
| return true; |
| default: |
| return false; |
| } |
| } |
| } |
| |
| DEFINE_TRACE(CSSGradientColorStop) { |
| visitor->trace(m_position); |
| visitor->trace(m_color); |
| } |
| |
| PassRefPtr<Image> CSSGradientValue::image(const LayoutObject& layoutObject, |
| const IntSize& size) { |
| if (size.isEmpty()) |
| return nullptr; |
| |
| bool cacheable = isCacheable(); |
| if (cacheable) { |
| if (!clients().contains(&layoutObject)) |
| return nullptr; |
| |
| // Need to look up our size. Create a string of width*height to use as a |
| // hash key. |
| Image* result = getImage(&layoutObject, size); |
| if (result) |
| return result; |
| } |
| |
| // We need to create an image. |
| RefPtr<Gradient> gradient; |
| |
| const ComputedStyle* rootStyle = |
| layoutObject.document().documentElement()->computedStyle(); |
| CSSToLengthConversionData conversionData( |
| layoutObject.style(), rootStyle, LayoutViewItem(layoutObject.view()), |
| layoutObject.style()->effectiveZoom()); |
| if (isLinearGradientValue()) |
| gradient = toCSSLinearGradientValue(this)->createGradient( |
| conversionData, size, layoutObject); |
| else |
| gradient = toCSSRadialGradientValue(this)->createGradient( |
| conversionData, size, layoutObject); |
| |
| RefPtr<Image> newImage = GradientGeneratedImage::create(gradient, size); |
| if (cacheable) |
| putImage(size, newImage); |
| |
| return newImage.release(); |
| } |
| |
| // Should only ever be called for deprecated gradients. |
| static inline bool compareStops(const CSSGradientColorStop& a, |
| const CSSGradientColorStop& b) { |
| double aVal = a.m_position->getDoubleValue(); |
| double bVal = b.m_position->getDoubleValue(); |
| |
| return aVal < bVal; |
| } |
| |
| struct GradientStop { |
| Color color; |
| float offset; |
| bool specified; |
| |
| GradientStop() : offset(0), specified(false) {} |
| }; |
| |
| static void replaceColorHintsWithColorStops( |
| Vector<GradientStop>& stops, |
| const HeapVector<CSSGradientColorStop, 2>& cssGradientStops) { |
| // This algorithm will replace each color interpolation hint with 9 regular |
| // color stops. The color values for the new color stops will be calculated |
| // using the color weighting formula defined in the spec. The new color |
| // stops will be positioned in such a way that all the pixels between the two |
| // user defined color stops have color values close to the interpolation |
| // curve. |
| // If the hint is closer to the left color stop, add 2 stops to the left and |
| // 6 to the right, else add 6 stops to the left and 2 to the right. |
| // The color stops on the side with more space start midway because |
| // the curve approximates a line in that region. |
| // Using this aproximation, it is possible to discern the color steps when |
| // the gradient is large. If this becomes an issue, we can consider improving |
| // the algorithm, or adding support for color interpolation hints to skia |
| // shaders. |
| |
| int indexOffset = 0; |
| |
| // The first and the last color stops cannot be color hints. |
| for (size_t i = 1; i < cssGradientStops.size() - 1; ++i) { |
| if (!cssGradientStops[i].isHint()) |
| continue; |
| |
| // The current index of the stops vector. |
| size_t x = i + indexOffset; |
| ASSERT(x >= 1); |
| |
| // offsetLeft offset offsetRight |
| // |-------------------|---------------------------------| |
| // leftDist rightDist |
| |
| float offsetLeft = stops[x - 1].offset; |
| float offsetRight = stops[x + 1].offset; |
| float offset = stops[x].offset; |
| float leftDist = offset - offsetLeft; |
| float rightDist = offsetRight - offset; |
| float totalDist = offsetRight - offsetLeft; |
| |
| Color leftColor = stops[x - 1].color; |
| Color rightColor = stops[x + 1].color; |
| |
| ASSERT(offsetLeft <= offset && offset <= offsetRight); |
| |
| if (WebCoreFloatNearlyEqual(leftDist, rightDist)) { |
| stops.remove(x); |
| --indexOffset; |
| continue; |
| } |
| |
| if (WebCoreFloatNearlyEqual(leftDist, .0f)) { |
| stops[x].color = rightColor; |
| continue; |
| } |
| |
| if (WebCoreFloatNearlyEqual(rightDist, .0f)) { |
| stops[x].color = leftColor; |
| continue; |
| } |
| |
| GradientStop newStops[9]; |
| // Position the new color stops. |
| if (leftDist > rightDist) { |
| for (size_t y = 0; y < 7; ++y) |
| newStops[y].offset = offsetLeft + leftDist * (7 + y) / 13; |
| newStops[7].offset = offset + rightDist / 3; |
| newStops[8].offset = offset + rightDist * 2 / 3; |
| } else { |
| newStops[0].offset = offsetLeft + leftDist / 3; |
| newStops[1].offset = offsetLeft + leftDist * 2 / 3; |
| for (size_t y = 0; y < 7; ++y) |
| newStops[y + 2].offset = offset + rightDist * y / 13; |
| } |
| |
| // calculate colors for the new color hints. |
| // The color weighting for the new color stops will be |
| // pointRelativeOffset^(ln(0.5)/ln(hintRelativeOffset)). |
| float hintRelativeOffset = leftDist / totalDist; |
| for (size_t y = 0; y < 9; ++y) { |
| float pointRelativeOffset = (newStops[y].offset - offsetLeft) / totalDist; |
| float weighting = |
| powf(pointRelativeOffset, logf(.5f) / logf(hintRelativeOffset)); |
| newStops[y].color = blend(leftColor, rightColor, weighting); |
| } |
| |
| // Replace the color hint with the new color stops. |
| stops.remove(x); |
| stops.insert(x, newStops, 9); |
| indexOffset += 8; |
| } |
| } |
| |
| static Color resolveStopColor(const CSSValue& stopColor, |
| const LayoutObject& object) { |
| return object.document().textLinkColors().colorFromCSSValue( |
| stopColor, object.resolveColor(CSSPropertyColor)); |
| } |
| |
| void CSSGradientValue::addDeprecatedStops(Gradient* gradient, |
| const LayoutObject& object) { |
| 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; |
| } |
| |
| for (const auto& stop : m_stops) { |
| float offset; |
| if (stop.m_position->isPercentage()) |
| offset = stop.m_position->getFloatValue() / 100; |
| else |
| offset = stop.m_position->getFloatValue(); |
| |
| gradient->addColorStop(offset, resolveStopColor(*stop.m_color, object)); |
| } |
| } |
| |
| static bool requiresStopsNormalization(const Vector<GradientStop>& stops, |
| const Gradient* gradient) { |
| // We need at least two stops to normalize |
| if (stops.size() < 2) |
| return false; |
| |
| // Repeating gradients are implemented using a normalized stop offset range |
| // with the point/radius pairs aligned on the interval endpoints. |
| if (gradient->spreadMethod() == SpreadMethodRepeat) |
| return true; |
| |
| // Degenerate stops |
| if (stops.front().offset < 0 || stops.back().offset > 1) |
| return true; |
| |
| return false; |
| } |
| |
| // Redistribute the stops such that they fully cover [0 , 1] and add them to the |
| // gradient. |
| static bool normalizeAndAddStops(const Vector<GradientStop>& stops, |
| Gradient* gradient) { |
| ASSERT(stops.size() > 1); |
| |
| const float firstOffset = stops.front().offset; |
| const float lastOffset = stops.back().offset; |
| const float span = lastOffset - firstOffset; |
| |
| if (fabs(span) < std::numeric_limits<float>::epsilon()) { |
| // All stops are coincident -> use a single clamped offset value. |
| const float clampedOffset = std::min(std::max(firstOffset, 0.f), 1.f); |
| |
| // For repeating gradients, a coincident stop set defines a solid-color |
| // image with the color of the last color-stop in the rule. |
| // For non-repeating gradients, both the first color and the last color can |
| // be significant (padding on both sides of the offset). |
| if (gradient->spreadMethod() != SpreadMethodRepeat) |
| gradient->addColorStop(clampedOffset, stops.front().color); |
| gradient->addColorStop(clampedOffset, stops.back().color); |
| |
| return false; |
| } |
| |
| ASSERT(span > 0); |
| |
| for (size_t i = 0; i < stops.size(); ++i) { |
| const float normalizedOffset = (stops[i].offset - firstOffset) / span; |
| |
| // stop offsets should be monotonically increasing in [0 , 1] |
| ASSERT(normalizedOffset >= 0 && normalizedOffset <= 1); |
| ASSERT(i == 0 || |
| normalizedOffset >= (stops[i - 1].offset - firstOffset) / span); |
| |
| gradient->addColorStop(normalizedOffset, stops[i].color); |
| } |
| |
| return true; |
| } |
| |
| // Collapse all negative-offset stops to 0 and compute an interpolated color |
| // value for that point. |
| static void clampNegativeOffsets(Vector<GradientStop>& stops) { |
| float lastNegativeOffset = 0; |
| |
| for (size_t i = 0; i < stops.size(); ++i) { |
| const float currentOffset = stops[i].offset; |
| if (currentOffset >= 0) { |
| if (i > 0) { |
| // We found the negative -> positive offset transition: compute an |
| // interpolated color value for 0 and use it with the last clamped stop. |
| ASSERT(lastNegativeOffset < 0); |
| float lerpRatio = |
| -lastNegativeOffset / (currentOffset - lastNegativeOffset); |
| stops[i - 1].color = |
| blend(stops[i - 1].color, stops[i].color, lerpRatio); |
| } |
| |
| break; |
| } |
| |
| // Clamp all negative stops to 0. |
| stops[i].offset = 0; |
| lastNegativeOffset = currentOffset; |
| } |
| } |
| |
| // Update the linear gradient points to align with the given offset range. |
| static void adjustGradientPointsForOffsetRange(Gradient* gradient, |
| float firstOffset, |
| float lastOffset) { |
| ASSERT(!gradient->isRadial()); |
| ASSERT(firstOffset <= lastOffset); |
| |
| const FloatPoint p0 = gradient->p0(); |
| const FloatPoint p1 = gradient->p1(); |
| const FloatSize d(p1 - p0); |
| |
| // Linear offsets are relative to the [p0 , p1] segment. |
| gradient->setP0(p0 + d * firstOffset); |
| gradient->setP1(p0 + d * lastOffset); |
| } |
| |
| // Update the radial gradient radii to align with the given offset range. |
| static void adjustGradientRadiiForOffsetRange(Gradient* gradient, |
| float firstOffset, |
| float lastOffset) { |
| ASSERT(gradient->isRadial()); |
| ASSERT(firstOffset <= lastOffset); |
| |
| // Radial offsets are relative to the [0 , endRadius] segment. |
| float adjustedR0 = gradient->endRadius() * firstOffset; |
| float adjustedR1 = gradient->endRadius() * lastOffset; |
| ASSERT(adjustedR0 <= adjustedR1); |
| |
| // Unlike linear gradients (where we can adjust the points arbitrarily), |
| // we cannot let our radii turn negative here. |
| if (adjustedR0 < 0) { |
| // For the non-repeat case, this can never happen: clampNegativeOffsets() |
| // ensures we don't have to deal with negative offsets at this point. |
| ASSERT(gradient->spreadMethod() == SpreadMethodRepeat); |
| |
| // When in repeat mode, we deal with it by repositioning both radii in the |
| // positive domain - shifting them by a multiple of the radius span (which |
| // is the period of our repeating gradient -> hence no visible side |
| // effects). |
| const float radiusSpan = adjustedR1 - adjustedR0; |
| const float shiftToPositive = radiusSpan * ceilf(-adjustedR0 / radiusSpan); |
| adjustedR0 += shiftToPositive; |
| adjustedR1 += shiftToPositive; |
| } |
| ASSERT(adjustedR0 >= 0); |
| ASSERT(adjustedR1 >= adjustedR0); |
| |
| gradient->setStartRadius(adjustedR0); |
| gradient->setEndRadius(adjustedR1); |
| } |
| |
| void CSSGradientValue::addStops(Gradient* gradient, |
| const CSSToLengthConversionData& conversionData, |
| const LayoutObject& object) { |
| if (m_gradientType == CSSDeprecatedLinearGradient || |
| m_gradientType == CSSDeprecatedRadialGradient) { |
| addDeprecatedStops(gradient, object); |
| return; |
| } |
| |
| size_t numStops = m_stops.size(); |
| |
| Vector<GradientStop> stops(numStops); |
| |
| bool hasHints = false; |
| |
| FloatPoint gradientStart = gradient->p0(); |
| FloatPoint gradientEnd; |
| if (isLinearGradientValue()) |
| gradientEnd = gradient->p1(); |
| else if (isRadialGradientValue()) |
| gradientEnd = gradientStart + FloatSize(gradient->endRadius(), 0); |
| float gradientLength = |
| FloatSize(gradientStart - gradientEnd).diagonalLength(); |
| |
| for (size_t i = 0; i < numStops; ++i) { |
| const CSSGradientColorStop& stop = m_stops[i]; |
| |
| if (stop.isHint()) |
| hasHints = true; |
| else |
| stops[i].color = resolveStopColor(*stop.m_color, object); |
| |
| if (stop.m_position) { |
| if (stop.m_position->isPercentage()) |
| stops[i].offset = stop.m_position->getFloatValue() / 100; |
| else if (stop.m_position->isLength() || |
| stop.m_position->isCalculatedPercentageWithLength()) { |
| float length; |
| if (stop.m_position->isLength()) |
| length = stop.m_position->computeLength<float>(conversionData); |
| else |
| length = stop.m_position->cssCalcValue() |
| ->toCalcValue(conversionData) |
| ->evaluate(gradientLength); |
| stops[i].offset = (gradientLength > 0) ? length / gradientLength : 0; |
| } else { |
| ASSERT_NOT_REACHED(); |
| stops[i].offset = 0; |
| } |
| stops[i].specified = true; |
| } else { |
| // If 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.front().specified && stops.back().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; |
| } |
| } |
| } |
| |
| ASSERT(stops.size() == m_stops.size()); |
| if (hasHints) { |
| replaceColorHintsWithColorStops(stops, m_stops); |
| } |
| |
| // At this point we have a fully resolved set of stops. Time to perform |
| // adjustments for repeat gradients and degenerate values if needed. |
| if (requiresStopsNormalization(stops, gradient)) { |
| // Negative offsets are only an issue for non-repeating radial gradients: |
| // linear gradient points can be repositioned arbitrarily, and for repeating |
| // radial gradients we shift the radii into equivalent positive values. |
| if (isRadialGradientValue() && !m_repeating) |
| clampNegativeOffsets(stops); |
| |
| if (normalizeAndAddStops(stops, gradient)) { |
| if (isLinearGradientValue()) { |
| adjustGradientPointsForOffsetRange(gradient, stops.front().offset, |
| stops.back().offset); |
| } else { |
| adjustGradientRadiiForOffsetRange(gradient, stops.front().offset, |
| stops.back().offset); |
| } |
| } else { |
| // Normalization failed because the stop set is coincident. |
| } |
| } else { |
| // No normalization required, just add the current stops. |
| for (const auto& stop : stops) |
| gradient->addColorStop(stop.offset, stop.color); |
| } |
| } |
| |
| static float positionFromValue(const CSSValue* value, |
| const CSSToLengthConversionData& conversionData, |
| const IntSize& size, |
| bool isHorizontal) { |
| 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->isValuePair()) { |
| const CSSValuePair& pair = toCSSValuePair(*value); |
| CSSValueID originID = toCSSIdentifierValue(pair.first()).getValueID(); |
| value = &pair.second(); |
| |
| if (originID == CSSValueRight || originID == CSSValueBottom) { |
| // For right/bottom, the offset is relative to the far edge. |
| origin = edgeDistance; |
| sign = -1; |
| } |
| } |
| |
| if (value->isIdentifierValue()) { |
| switch (toCSSIdentifierValue(value)->getValueID()) { |
| case CSSValueTop: |
| DCHECK(!isHorizontal); |
| return 0; |
| case CSSValueLeft: |
| DCHECK(isHorizontal); |
| return 0; |
| case CSSValueBottom: |
| DCHECK(!isHorizontal); |
| return size.height(); |
| case CSSValueRight: |
| DCHECK(isHorizontal); |
| return size.width(); |
| case CSSValueCenter: |
| return origin + sign * .5f * edgeDistance; |
| default: |
| NOTREACHED(); |
| break; |
| } |
| } |
| |
| const CSSPrimitiveValue* primitiveValue = toCSSPrimitiveValue(value); |
| |
| if (primitiveValue->isNumber()) |
| return origin + |
| sign * primitiveValue->getFloatValue() * conversionData.zoom(); |
| |
| if (primitiveValue->isPercentage()) |
| return origin + |
| sign * primitiveValue->getFloatValue() / 100.f * edgeDistance; |
| |
| if (primitiveValue->isCalculatedPercentageWithLength()) |
| return origin + |
| sign * |
| primitiveValue->cssCalcValue() |
| ->toCalcValue(conversionData) |
| ->evaluate(edgeDistance); |
| |
| return origin + sign * primitiveValue->computeLength<float>(conversionData); |
| } |
| |
| FloatPoint CSSGradientValue::computeEndPoint( |
| CSSValue* horizontal, |
| CSSValue* vertical, |
| const CSSToLengthConversionData& conversionData, |
| const IntSize& size) { |
| FloatPoint result; |
| |
| if (horizontal) |
| result.setX(positionFromValue(horizontal, conversionData, size, true)); |
| |
| if (vertical) |
| result.setY(positionFromValue(vertical, conversionData, size, false)); |
| |
| return result; |
| } |
| |
| bool CSSGradientValue::isCacheable() const { |
| for (size_t i = 0; i < m_stops.size(); ++i) { |
| const CSSGradientColorStop& stop = m_stops[i]; |
| |
| if (!stop.isHint() && stop.m_color->isIdentifierValue() && |
| colorIsDerivedFromElement(toCSSIdentifierValue(*stop.m_color))) |
| return false; |
| |
| if (!stop.m_position) |
| continue; |
| |
| if (stop.m_position->isFontRelativeLength()) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool CSSGradientValue::knownToBeOpaque(const LayoutObject& object) const { |
| for (auto& stop : m_stops) { |
| if (!stop.isHint() && resolveStopColor(*stop.m_color, object).hasAlpha()) |
| return false; |
| } |
| return true; |
| } |
| |
| void CSSGradientValue::getStopColors(Vector<Color>& stopColors, |
| const LayoutObject& object) const { |
| for (auto& stop : m_stops) { |
| if (!stop.isHint()) |
| stopColors.push_back(resolveStopColor(*stop.m_color, object)); |
| } |
| } |
| |
| DEFINE_TRACE_AFTER_DISPATCH(CSSGradientValue) { |
| visitor->trace(m_firstX); |
| visitor->trace(m_firstY); |
| visitor->trace(m_secondX); |
| visitor->trace(m_secondY); |
| visitor->trace(m_stops); |
| CSSImageGeneratorValue::traceAfterDispatch(visitor); |
| } |
| |
| String CSSLinearGradientValue::customCSSText() const { |
| StringBuilder result; |
| if (m_gradientType == CSSDeprecatedLinearGradient) { |
| result.append("-webkit-gradient(linear, "); |
| result.append(m_firstX->cssText()); |
| result.append(' '); |
| result.append(m_firstY->cssText()); |
| result.append(", "); |
| result.append(m_secondX->cssText()); |
| result.append(' '); |
| result.append(m_secondY->cssText()); |
| appendCSSTextForDeprecatedColorStops(result); |
| } else if (m_gradientType == CSSPrefixedLinearGradient) { |
| if (m_repeating) |
| result.append("-webkit-repeating-linear-gradient("); |
| else |
| result.append("-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.append(", "); |
| result.append(stop.m_color->cssText()); |
| if (stop.m_position) { |
| result.append(' '); |
| result.append(stop.m_position->cssText()); |
| } |
| } |
| } else { |
| if (m_repeating) |
| result.append("repeating-linear-gradient("); |
| else |
| result.append("linear-gradient("); |
| |
| bool wroteSomething = false; |
| |
| if (m_angle && m_angle->computeDegrees() != 180) { |
| result.append(m_angle->cssText()); |
| wroteSomething = true; |
| } else if ((m_firstX || m_firstY) && |
| !(!m_firstX && m_firstY && m_firstY->isIdentifierValue() && |
| toCSSIdentifierValue(m_firstY.get())->getValueID() == |
| CSSValueBottom)) { |
| result.append("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.append(", "); |
| |
| for (unsigned i = 0; i < m_stops.size(); i++) { |
| const CSSGradientColorStop& stop = m_stops[i]; |
| if (i) |
| result.append(", "); |
| if (stop.m_color) |
| result.append(stop.m_color->cssText()); |
| if (stop.m_color && stop.m_position) |
| result.append(' '); |
| if (stop.m_position) |
| 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 IntSize& 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); |
| } |
| |
| PassRefPtr<Gradient> CSSLinearGradientValue::createGradient( |
| const CSSToLengthConversionData& conversionData, |
| const IntSize& size, |
| const LayoutObject& object) { |
| ASSERT(!size.isEmpty()); |
| |
| FloatPoint firstPoint; |
| FloatPoint secondPoint; |
| if (m_angle) { |
| float angle = m_angle->computeDegrees(); |
| 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->isIdentifierValue() && |
| toCSSIdentifierValue(m_firstX.get())->getValueID() == |
| CSSValueLeft) |
| run *= -1; |
| if (m_firstY && m_firstY->isIdentifierValue() && |
| toCSSIdentifierValue(m_firstY.get())->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(); |
| } |
| } |
| |
| RefPtr<Gradient> gradient = Gradient::create(firstPoint, secondPoint); |
| |
| gradient->setSpreadMethod(m_repeating ? SpreadMethodRepeat : SpreadMethodPad); |
| gradient->setDrawsInPMColorSpace(true); |
| |
| // Now add the stops. |
| addStops(gradient.get(), conversionData, object); |
| |
| return gradient.release(); |
| } |
| |
| bool CSSLinearGradientValue::equals(const CSSLinearGradientValue& other) const { |
| if (m_gradientType == CSSDeprecatedLinearGradient) |
| return other.m_gradientType == m_gradientType && |
| dataEquivalent(m_firstX, other.m_firstX) && |
| dataEquivalent(m_firstY, other.m_firstY) && |
| dataEquivalent(m_secondX, other.m_secondX) && |
| dataEquivalent(m_secondY, other.m_secondY) && |
| m_stops == other.m_stops; |
| |
| if (m_repeating != other.m_repeating) |
| return false; |
| |
| if (m_angle) |
| return dataEquivalent(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 = dataEquivalent(m_firstX, other.m_firstX) && |
| dataEquivalent(m_firstY, other.m_firstY); |
| } else if (m_firstX) { |
| equalXandY = dataEquivalent(m_firstX, other.m_firstX) && !other.m_firstY; |
| } else if (m_firstY) { |
| equalXandY = dataEquivalent(m_firstY, other.m_firstY) && !other.m_firstX; |
| } else { |
| equalXandY = !other.m_firstX && !other.m_firstY; |
| } |
| |
| return equalXandY && m_stops == other.m_stops; |
| } |
| |
| DEFINE_TRACE_AFTER_DISPATCH(CSSLinearGradientValue) { |
| visitor->trace(m_angle); |
| CSSGradientValue::traceAfterDispatch(visitor); |
| } |
| |
| inline void CSSGradientValue::appendCSSTextForDeprecatedColorStops( |
| StringBuilder& result) const { |
| for (unsigned i = 0; i < m_stops.size(); i++) { |
| const CSSGradientColorStop& stop = m_stops[i]; |
| result.append(", "); |
| if (stop.m_position->getDoubleValue() == 0) { |
| result.append("from("); |
| result.append(stop.m_color->cssText()); |
| result.append(')'); |
| } else if (stop.m_position->getDoubleValue() == 1) { |
| result.append("to("); |
| result.append(stop.m_color->cssText()); |
| result.append(')'); |
| } else { |
| result.append("color-stop("); |
| result.appendNumber(stop.m_position->getDoubleValue()); |
| result.append(", "); |
| result.append(stop.m_color->cssText()); |
| result.append(')'); |
| } |
| } |
| } |
| |
| String CSSRadialGradientValue::customCSSText() const { |
| StringBuilder result; |
| |
| if (m_gradientType == CSSDeprecatedRadialGradient) { |
| result.append("-webkit-gradient(radial, "); |
| result.append(m_firstX->cssText()); |
| result.append(' '); |
| result.append(m_firstY->cssText()); |
| result.append(", "); |
| result.append(m_firstRadius->cssText()); |
| result.append(", "); |
| result.append(m_secondX->cssText()); |
| result.append(' '); |
| result.append(m_secondY->cssText()); |
| result.append(", "); |
| result.append(m_secondRadius->cssText()); |
| appendCSSTextForDeprecatedColorStops(result); |
| } else if (m_gradientType == CSSPrefixedRadialGradient) { |
| if (m_repeating) |
| result.append("-webkit-repeating-radial-gradient("); |
| else |
| result.append("-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.append("center"); |
| |
| if (m_shape || m_sizingBehavior) { |
| result.append(", "); |
| if (m_shape) { |
| result.append(m_shape->cssText()); |
| result.append(' '); |
| } else { |
| result.append("ellipse "); |
| } |
| |
| if (m_sizingBehavior) |
| result.append(m_sizingBehavior->cssText()); |
| else |
| result.append("cover"); |
| |
| } else if (m_endHorizontalSize && m_endVerticalSize) { |
| result.append(", "); |
| 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.append(", "); |
| result.append(stop.m_color->cssText()); |
| if (stop.m_position) { |
| result.append(' '); |
| result.append(stop.m_position->cssText()); |
| } |
| } |
| } else { |
| if (m_repeating) |
| result.append("repeating-radial-gradient("); |
| else |
| result.append("radial-gradient("); |
| |
| bool wroteSomething = false; |
| |
| // The only ambiguous case that needs an explicit shape to be provided |
| // is when a sizing keyword is used (or all sizing is omitted). |
| if (m_shape && m_shape->getValueID() != CSSValueEllipse && |
| (m_sizingBehavior || (!m_sizingBehavior && !m_endHorizontalSize))) { |
| result.append("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.append("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.append(", "); |
| |
| for (unsigned i = 0; i < m_stops.size(); i++) { |
| const CSSGradientColorStop& stop = m_stops[i]; |
| if (i) |
| result.append(", "); |
| if (stop.m_color) |
| result.append(stop.m_color->cssText()); |
| if (stop.m_color && stop.m_position) |
| result.append(' '); |
| if (stop.m_position) |
| result.append(stop.m_position->cssText()); |
| } |
| } |
| |
| result.append(')'); |
| return result.toString(); |
| } |
| |
| float CSSRadialGradientValue::resolveRadius( |
| CSSPrimitiveValue* radius, |
| const CSSToLengthConversionData& conversionData, |
| float* widthOrHeight) { |
| float result = 0; |
| if (radius->isNumber()) |
| result = radius->getFloatValue() * conversionData.zoom(); |
| else if (widthOrHeight && radius->isPercentage()) |
| result = *widthOrHeight * radius->getFloatValue() / 100; |
| else |
| result = radius->computeLength<float>(conversionData); |
| |
| return clampTo<float>(std::max(result, 0.0f)); |
| } |
| |
| namespace { |
| |
| enum EndShapeType { CircleEndShape, EllipseEndShape }; |
| |
| // Compute the radius to the closest/farthest side (depending on the compare |
| // functor). |
| FloatSize radiusToSide(const FloatPoint& point, |
| const FloatSize& size, |
| EndShapeType shape, |
| bool (*compare)(float, float)) { |
| float dx1 = clampTo<float>(fabs(point.x())); |
| float dy1 = clampTo<float>(fabs(point.y())); |
| float dx2 = clampTo<float>(fabs(point.x() - size.width())); |
| float dy2 = clampTo<float>(fabs(point.y() - size.height())); |
| |
| float dx = compare(dx1, dx2) ? dx1 : dx2; |
| float dy = compare(dy1, dy2) ? dy1 : dy2; |
| |
| if (shape == CircleEndShape) |
| return compare(dx, dy) ? FloatSize(dx, dx) : FloatSize(dy, dy); |
| |
| ASSERT(shape == EllipseEndShape); |
| return FloatSize(dx, dy); |
| } |
| |
| // Compute the radius of an ellipse with center at 0,0 which passes through p, |
| // and has width/height given by aspectRatio. |
| inline FloatSize ellipseRadius(const FloatPoint& p, float aspectRatio) { |
| // If the aspectRatio is 0 or infinite, the ellipse is completely flat. |
| // TODO(sashab): Implement Degenerate Radial Gradients, see crbug.com/635727. |
| if (aspectRatio == 0 || std::isinf(aspectRatio)) |
| return FloatSize(0, 0); |
| |
| // x^2/a^2 + y^2/b^2 = 1 |
| // a/b = aspectRatio, b = a/aspectRatio |
| // a = sqrt(x^2 + y^2/(1/r^2)) |
| float a = sqrtf(p.x() * p.x() + p.y() * p.y() * aspectRatio * aspectRatio); |
| return FloatSize(clampTo<float>(a), clampTo<float>(a / aspectRatio)); |
| } |
| |
| // Compute the radius to the closest/farthest corner (depending on the compare |
| // functor). |
| FloatSize radiusToCorner(const FloatPoint& point, |
| const FloatSize& size, |
| EndShapeType shape, |
| bool (*compare)(float, float)) { |
| const FloatRect rect(FloatPoint(), size); |
| const FloatPoint corners[] = {rect.minXMinYCorner(), rect.maxXMinYCorner(), |
| rect.maxXMaxYCorner(), rect.minXMaxYCorner()}; |
| |
| unsigned cornerIndex = 0; |
| float distance = (point - corners[cornerIndex]).diagonalLength(); |
| for (unsigned i = 1; i < WTF_ARRAY_LENGTH(corners); ++i) { |
| float newDistance = (point - corners[i]).diagonalLength(); |
| if (compare(newDistance, distance)) { |
| cornerIndex = i; |
| distance = newDistance; |
| } |
| } |
| |
| if (shape == CircleEndShape) |
| return FloatSize(distance, distance); |
| |
| ASSERT(shape == EllipseEndShape); |
| // If the end shape is an 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. |
| const FloatSize sideRadius = |
| radiusToSide(point, size, EllipseEndShape, compare); |
| |
| return ellipseRadius(FloatPoint(corners[cornerIndex] - point), |
| sideRadius.aspectRatio()); |
| } |
| |
| } // anonymous namespace |
| |
| PassRefPtr<Gradient> CSSRadialGradientValue::createGradient( |
| const CSSToLengthConversionData& conversionData, |
| const IntSize& size, |
| const LayoutObject& object) { |
| ASSERT(!size.isEmpty()); |
| |
| FloatPoint firstPoint = |
| computeEndPoint(m_firstX.get(), m_firstY.get(), conversionData, size); |
| if (!m_firstX) |
| firstPoint.setX(size.width() / 2); |
| if (!m_firstY) |
| firstPoint.setY(size.height() / 2); |
| |
| FloatPoint secondPoint = |
| computeEndPoint(m_secondX.get(), m_secondY.get(), conversionData, size); |
| if (!m_secondX) |
| secondPoint.setX(size.width() / 2); |
| if (!m_secondY) |
| secondPoint.setY(size.height() / 2); |
| |
| float firstRadius = 0; |
| if (m_firstRadius) |
| firstRadius = resolveRadius(m_firstRadius.get(), conversionData); |
| |
| FloatSize secondRadius(0, 0); |
| if (m_secondRadius) { |
| secondRadius.setWidth(resolveRadius(m_secondRadius.get(), conversionData)); |
| secondRadius.setHeight(secondRadius.width()); |
| } else if (m_endHorizontalSize) { |
| float width = size.width(); |
| float height = size.height(); |
| secondRadius.setWidth( |
| resolveRadius(m_endHorizontalSize.get(), conversionData, &width)); |
| secondRadius.setHeight( |
| m_endVerticalSize |
| ? resolveRadius(m_endVerticalSize.get(), conversionData, &height) |
| : secondRadius.width()); |
| } else { |
| EndShapeType shape = (m_shape && m_shape->getValueID() == CSSValueCircle) || |
| (!m_shape && !m_sizingBehavior && |
| m_endHorizontalSize && !m_endVerticalSize) |
| ? CircleEndShape |
| : EllipseEndShape; |
| |
| FloatSize floatSize(size); |
| switch (m_sizingBehavior ? m_sizingBehavior->getValueID() : 0) { |
| case CSSValueContain: |
| case CSSValueClosestSide: |
| secondRadius = radiusToSide(secondPoint, floatSize, shape, |
| [](float a, float b) { return a < b; }); |
| break; |
| case CSSValueFarthestSide: |
| secondRadius = radiusToSide(secondPoint, floatSize, shape, |
| [](float a, float b) { return a > b; }); |
| break; |
| case CSSValueClosestCorner: |
| secondRadius = radiusToCorner(secondPoint, floatSize, shape, |
| [](float a, float b) { return a < b; }); |
| break; |
| default: |
| secondRadius = radiusToCorner(secondPoint, floatSize, shape, |
| [](float a, float b) { return a > b; }); |
| break; |
| } |
| } |
| |
| DCHECK(std::isfinite(firstRadius)); |
| DCHECK(std::isfinite(secondRadius.width())); |
| DCHECK(std::isfinite(secondRadius.height())); |
| |
| bool isDegenerate = !secondRadius.width() || !secondRadius.height(); |
| RefPtr<Gradient> gradient = |
| Gradient::create(firstPoint, firstRadius, secondPoint, |
| isDegenerate ? 0 : secondRadius.width(), |
| isDegenerate ? 1 : secondRadius.aspectRatio()); |
| |
| gradient->setSpreadMethod(m_repeating ? SpreadMethodRepeat : SpreadMethodPad); |
| gradient->setDrawsInPMColorSpace(true); |
| |
| // Now add the stops. |
| addStops(gradient.get(), conversionData, object); |
| |
| return gradient.release(); |
| } |
| |
| bool CSSRadialGradientValue::equals(const CSSRadialGradientValue& other) const { |
| if (m_gradientType == CSSDeprecatedRadialGradient) |
| return other.m_gradientType == m_gradientType && |
| dataEquivalent(m_firstX, other.m_firstX) && |
| dataEquivalent(m_firstY, other.m_firstY) && |
| dataEquivalent(m_secondX, other.m_secondX) && |
| dataEquivalent(m_secondY, other.m_secondY) && |
| dataEquivalent(m_firstRadius, other.m_firstRadius) && |
| dataEquivalent(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 = dataEquivalent(m_firstX, other.m_firstX) && |
| dataEquivalent(m_firstY, other.m_firstY); |
| } else if (m_firstX) { |
| equalXandY = dataEquivalent(m_firstX, other.m_firstX) && !other.m_firstY; |
| } else if (m_firstY) { |
| equalXandY = dataEquivalent(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 = dataEquivalent(m_shape, other.m_shape); |
| } else if (m_sizingBehavior) { |
| equalSizingBehavior = |
| dataEquivalent(m_sizingBehavior, other.m_sizingBehavior); |
| } else if (m_endHorizontalSize && m_endVerticalSize) { |
| equalHorizontalAndVerticalSize = |
| dataEquivalent(m_endHorizontalSize, other.m_endHorizontalSize) && |
| dataEquivalent(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; |
| } |
| |
| DEFINE_TRACE_AFTER_DISPATCH(CSSRadialGradientValue) { |
| visitor->trace(m_firstRadius); |
| visitor->trace(m_secondRadius); |
| visitor->trace(m_shape); |
| visitor->trace(m_sizingBehavior); |
| visitor->trace(m_endHorizontalSize); |
| visitor->trace(m_endVerticalSize); |
| CSSGradientValue::traceAfterDispatch(visitor); |
| } |
| |
| } // namespace blink |