summaryrefslogtreecommitdiff
path: root/chromium/third_party/blink/renderer/core/css/css_gradient_value.cc
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2018-05-15 10:20:33 +0200
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2018-05-15 10:28:57 +0000
commitd17ea114e5ef69ad5d5d7413280a13e6428098aa (patch)
tree2c01a75df69f30d27b1432467cfe7c1467a498da /chromium/third_party/blink/renderer/core/css/css_gradient_value.cc
parent8c5c43c7b138c9b4b0bf56d946e61d3bbc111bec (diff)
downloadqtwebengine-chromium-d17ea114e5ef69ad5d5d7413280a13e6428098aa.tar.gz
BASELINE: Update Chromium to 67.0.3396.47
Change-Id: Idcb1341782e417561a2473eeecc82642dafda5b7 Reviewed-by: Michal Klocek <michal.klocek@qt.io>
Diffstat (limited to 'chromium/third_party/blink/renderer/core/css/css_gradient_value.cc')
-rw-r--r--chromium/third_party/blink/renderer/core/css/css_gradient_value.cc1458
1 files changed, 1458 insertions, 0 deletions
diff --git a/chromium/third_party/blink/renderer/core/css/css_gradient_value.cc b/chromium/third_party/blink/renderer/core/css/css_gradient_value.cc
new file mode 100644
index 00000000000..17b6e903fa3
--- /dev/null
+++ b/chromium/third_party/blink/renderer/core/css/css_gradient_value.cc
@@ -0,0 +1,1458 @@
+/*
+ * 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 "third_party/blink/renderer/core/css/css_gradient_value.h"
+
+#include <algorithm>
+#include <tuple>
+#include <utility>
+#include "third_party/blink/renderer/core/css/css_calculation_value.h"
+#include "third_party/blink/renderer/core/css/css_identifier_value.h"
+#include "third_party/blink/renderer/core/css/css_to_length_conversion_data.h"
+#include "third_party/blink/renderer/core/css/css_value_pair.h"
+#include "third_party/blink/renderer/core/css_value_keywords.h"
+#include "third_party/blink/renderer/core/dom/node_computed_style.h"
+#include "third_party/blink/renderer/core/dom/text_link_colors.h"
+#include "third_party/blink/renderer/platform/geometry/int_size.h"
+#include "third_party/blink/renderer/platform/graphics/color_blend.h"
+#include "third_party/blink/renderer/platform/graphics/gradient.h"
+#include "third_party/blink/renderer/platform/graphics/gradient_generated_image.h"
+#include "third_party/blink/renderer/platform/graphics/image.h"
+#include "third_party/blink/renderer/platform/graphics/skia/skia_utils.h"
+#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
+#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
+
+namespace blink {
+namespace cssvalue {
+
+namespace {
+
+bool ColorIsDerivedFromElement(const CSSIdentifierValue& value) {
+ CSSValueID value_id = value.GetValueID();
+ switch (value_id) {
+ case CSSValueInternalQuirkInherit:
+ case CSSValueWebkitLink:
+ case CSSValueWebkitActivelink:
+ case CSSValueCurrentcolor:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool AppendPosition(StringBuilder& result,
+ const CSSValue* x,
+ const CSSValue* y,
+ bool wrote_something) {
+ if (!x && !y)
+ return false;
+
+ if (wrote_something)
+ result.Append(' ');
+ result.Append("at ");
+
+ if (x) {
+ result.Append(x->CssText());
+ if (y)
+ result.Append(' ');
+ }
+
+ if (y)
+ result.Append(y->CssText());
+
+ return true;
+}
+
+} // anonymous ns
+
+bool CSSGradientColorStop::IsCacheable() const {
+ if (!IsHint() && color_->IsIdentifierValue() &&
+ ColorIsDerivedFromElement(ToCSSIdentifierValue(*color_))) {
+ return false;
+ }
+
+ return !offset_ || !offset_->IsFontRelativeLength();
+}
+
+void CSSGradientColorStop::Trace(blink::Visitor* visitor) {
+ visitor->Trace(offset_);
+ visitor->Trace(color_);
+}
+
+scoped_refptr<Image> CSSGradientValue::GetImage(
+ const ImageResourceObserver& client,
+ const Document& document,
+ const ComputedStyle& style,
+ const FloatSize& size) const {
+ if (size.IsEmpty())
+ return nullptr;
+
+ if (is_cacheable_) {
+ if (!Clients().Contains(&client))
+ return nullptr;
+
+ if (Image* result = CSSImageGeneratorValue::GetImage(&client, size))
+ return result;
+ }
+
+ // We need to create an image.
+ const ComputedStyle* root_style =
+ document.documentElement()->GetComputedStyle();
+ CSSToLengthConversionData conversion_data(
+ &style, root_style, document.GetLayoutView(), style.EffectiveZoom());
+
+ scoped_refptr<Gradient> gradient;
+ switch (GetClassType()) {
+ case kLinearGradientClass:
+ gradient = ToCSSLinearGradientValue(this)->CreateGradient(
+ conversion_data, size, document, style);
+ break;
+ case kRadialGradientClass:
+ gradient = ToCSSRadialGradientValue(this)->CreateGradient(
+ conversion_data, size, document, style);
+ break;
+ case kConicGradientClass:
+ gradient = ToCSSConicGradientValue(this)->CreateGradient(
+ conversion_data, size, document, style);
+ break;
+ default:
+ NOTREACHED();
+ }
+
+ scoped_refptr<Image> new_image =
+ GradientGeneratedImage::Create(gradient, size);
+ if (is_cacheable_)
+ PutImage(size, new_image);
+
+ return new_image;
+}
+
+// Should only ever be called for deprecated gradients.
+static inline bool CompareStops(const CSSGradientColorStop& a,
+ const CSSGradientColorStop& b) {
+ double a_val = a.offset_->GetDoubleValue();
+ double b_val = b.offset_->GetDoubleValue();
+
+ return a_val < b_val;
+}
+
+struct GradientStop {
+ Color color;
+ float offset;
+ bool specified;
+
+ GradientStop() : offset(0), specified(false) {}
+};
+
+struct CSSGradientValue::GradientDesc {
+ STACK_ALLOCATED();
+
+ GradientDesc(const FloatPoint& p0,
+ const FloatPoint& p1,
+ GradientSpreadMethod spread_method)
+ : p0(p0), p1(p1), spread_method(spread_method) {}
+ GradientDesc(const FloatPoint& p0,
+ const FloatPoint& p1,
+ float r0,
+ float r1,
+ GradientSpreadMethod spread_method)
+ : p0(p0), p1(p1), r0(r0), r1(r1), spread_method(spread_method) {}
+
+ Vector<Gradient::ColorStop> stops;
+ FloatPoint p0, p1;
+ float r0 = 0, r1 = 0;
+ float start_angle = 0, end_angle = 360;
+ GradientSpreadMethod spread_method;
+};
+
+static void ReplaceColorHintsWithColorStops(
+ Vector<GradientStop>& stops,
+ const HeapVector<CSSGradientColorStop, 2>& css_gradient_stops) {
+ // 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 index_offset = 0;
+
+ // The first and the last color stops cannot be color hints.
+ for (size_t i = 1; i < css_gradient_stops.size() - 1; ++i) {
+ if (!css_gradient_stops[i].IsHint())
+ continue;
+
+ // The current index of the stops vector.
+ size_t x = i + index_offset;
+ DCHECK_GE(x, 1u);
+
+ // offsetLeft offset offsetRight
+ // |-------------------|---------------------------------|
+ // leftDist rightDist
+
+ float offset_left = stops[x - 1].offset;
+ float offset_right = stops[x + 1].offset;
+ float offset = stops[x].offset;
+ float left_dist = offset - offset_left;
+ float right_dist = offset_right - offset;
+ float total_dist = offset_right - offset_left;
+
+ Color left_color = stops[x - 1].color;
+ Color right_color = stops[x + 1].color;
+
+ DCHECK_LE(offset_left, offset);
+ DCHECK_LE(offset, offset_right);
+
+ if (WebCoreFloatNearlyEqual(left_dist, right_dist)) {
+ stops.EraseAt(x);
+ --index_offset;
+ continue;
+ }
+
+ if (WebCoreFloatNearlyEqual(left_dist, .0f)) {
+ stops[x].color = right_color;
+ continue;
+ }
+
+ if (WebCoreFloatNearlyEqual(right_dist, .0f)) {
+ stops[x].color = left_color;
+ continue;
+ }
+
+ GradientStop new_stops[9];
+ // Position the new color stops.
+ if (left_dist > right_dist) {
+ for (size_t y = 0; y < 7; ++y)
+ new_stops[y].offset = offset_left + left_dist * (7 + y) / 13;
+ new_stops[7].offset = offset + right_dist / 3;
+ new_stops[8].offset = offset + right_dist * 2 / 3;
+ } else {
+ new_stops[0].offset = offset_left + left_dist / 3;
+ new_stops[1].offset = offset_left + left_dist * 2 / 3;
+ for (size_t y = 0; y < 7; ++y)
+ new_stops[y + 2].offset = offset + right_dist * 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 hint_relative_offset = left_dist / total_dist;
+ for (size_t y = 0; y < 9; ++y) {
+ float point_relative_offset =
+ (new_stops[y].offset - offset_left) / total_dist;
+ float weighting =
+ powf(point_relative_offset, logf(.5f) / logf(hint_relative_offset));
+ new_stops[y].color = Blend(left_color, right_color, weighting);
+ }
+
+ // Replace the color hint with the new color stops.
+ stops.EraseAt(x);
+ stops.insert(x, new_stops, 9);
+ index_offset += 8;
+ }
+}
+
+static Color ResolveStopColor(const CSSValue& stop_color,
+ const Document& document,
+ const ComputedStyle& style) {
+ return document.GetTextLinkColors().ColorFromCSSValue(
+ stop_color, style.VisitedDependentColor(GetCSSPropertyColor()));
+}
+
+void CSSGradientValue::AddDeprecatedStops(GradientDesc& desc,
+ const Document& document,
+ const ComputedStyle& style) const {
+ DCHECK(gradient_type_ == kCSSDeprecatedLinearGradient ||
+ gradient_type_ == kCSSDeprecatedRadialGradient);
+
+ // Performance here is probably not important because this is for deprecated
+ // gradients.
+ auto stops_sorted = stops_;
+ std::stable_sort(stops_sorted.begin(), stops_sorted.end(), CompareStops);
+
+ for (const auto& stop : stops_sorted) {
+ float offset;
+ if (stop.offset_->IsPercentage())
+ offset = stop.offset_->GetFloatValue() / 100;
+ else
+ offset = stop.offset_->GetFloatValue();
+
+ const Color color = ResolveStopColor(*stop.color_, document, style);
+ desc.stops.emplace_back(offset, color);
+ }
+}
+
+namespace {
+
+bool RequiresStopsNormalization(const Vector<GradientStop>& stops,
+ CSSGradientValue::GradientDesc& desc) {
+ // 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 (desc.spread_method == kSpreadMethodRepeat)
+ 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.
+bool NormalizeAndAddStops(const Vector<GradientStop>& stops,
+ CSSGradientValue::GradientDesc& desc) {
+ DCHECK_GT(stops.size(), 1u);
+
+ const float first_offset = stops.front().offset;
+ const float last_offset = stops.back().offset;
+ const float span = last_offset - first_offset;
+
+ if (fabs(span) < std::numeric_limits<float>::epsilon()) {
+ // All stops are coincident -> use a single clamped offset value.
+ const float clamped_offset = std::min(std::max(first_offset, 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 (desc.spread_method != kSpreadMethodRepeat)
+ desc.stops.emplace_back(clamped_offset, stops.front().color);
+ desc.stops.emplace_back(clamped_offset, stops.back().color);
+
+ return false;
+ }
+
+ DCHECK_GT(span, 0);
+
+ for (size_t i = 0; i < stops.size(); ++i) {
+ const float normalized_offset = (stops[i].offset - first_offset) / span;
+
+ // stop offsets should be monotonically increasing in [0 , 1]
+ DCHECK_GE(normalized_offset, 0);
+ DCHECK_LE(normalized_offset, 1);
+ DCHECK(i == 0 ||
+ normalized_offset >= (stops[i - 1].offset - first_offset) / span);
+
+ desc.stops.emplace_back(normalized_offset, stops[i].color);
+ }
+
+ return true;
+}
+
+// Collapse all negative-offset stops to 0 and compute an interpolated color
+// value for that point.
+void ClampNegativeOffsets(Vector<GradientStop>& stops) {
+ float last_negative_offset = 0;
+
+ for (size_t i = 0; i < stops.size(); ++i) {
+ const float current_offset = stops[i].offset;
+ if (current_offset >= 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.
+ DCHECK_LT(last_negative_offset, 0);
+ float lerp_ratio =
+ -last_negative_offset / (current_offset - last_negative_offset);
+ stops[i - 1].color =
+ Blend(stops[i - 1].color, stops[i].color, lerp_ratio);
+ }
+
+ break;
+ }
+
+ // Clamp all negative stops to 0.
+ stops[i].offset = 0;
+ last_negative_offset = current_offset;
+ }
+}
+
+template <typename T>
+std::tuple<T, T> AdjustedGradientDomainForOffsetRange(const T& v0,
+ const T& v1,
+ float first_offset,
+ float last_offset) {
+ DCHECK_LE(first_offset, last_offset);
+
+ const auto d = v1 - v0;
+
+ // The offsets are relative to the [v0 , v1] segment.
+ return std::make_tuple(v0 + d * first_offset, v0 + d * last_offset);
+}
+
+// Update the radial gradient radii to align with the given offset range.
+void AdjustGradientRadiiForOffsetRange(CSSGradientValue::GradientDesc& desc,
+ float first_offset,
+ float last_offset) {
+ DCHECK_LE(first_offset, last_offset);
+
+ // Radial offsets are relative to the [0 , endRadius] segment.
+ float adjusted_r0 = desc.r1 * first_offset;
+ float adjusted_r1 = desc.r1 * last_offset;
+ DCHECK_LE(adjusted_r0, adjusted_r1);
+ // Unlike linear gradients (where we can adjust the points arbitrarily),
+ // we cannot let our radii turn negative here.
+ if (adjusted_r0 < 0) {
+ // For the non-repeat case, this can never happen: clampNegativeOffsets()
+ // ensures we don't have to deal with negative offsets at this point.
+
+ DCHECK_EQ(desc.spread_method, kSpreadMethodRepeat);
+
+ // 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 radius_span = adjusted_r1 - adjusted_r0;
+ const float shift_to_positive =
+ radius_span * ceilf(-adjusted_r0 / radius_span);
+ adjusted_r0 += shift_to_positive;
+ adjusted_r1 += shift_to_positive;
+ }
+ DCHECK_GE(adjusted_r0, 0);
+ DCHECK_GE(adjusted_r1, adjusted_r0);
+
+ desc.r0 = adjusted_r0;
+ desc.r1 = adjusted_r1;
+}
+
+} // anonymous ns
+
+void CSSGradientValue::AddStops(
+ CSSGradientValue::GradientDesc& desc,
+ const CSSToLengthConversionData& conversion_data,
+ const Document& document,
+ const ComputedStyle& style) const {
+ if (gradient_type_ == kCSSDeprecatedLinearGradient ||
+ gradient_type_ == kCSSDeprecatedRadialGradient) {
+ AddDeprecatedStops(desc, document, style);
+ return;
+ }
+
+ size_t num_stops = stops_.size();
+
+ Vector<GradientStop> stops(num_stops);
+
+ float gradient_length;
+ switch (GetClassType()) {
+ case kLinearGradientClass:
+ gradient_length = FloatSize(desc.p1 - desc.p0).DiagonalLength();
+ break;
+ case kRadialGradientClass:
+ gradient_length = desc.r1;
+ break;
+ case kConicGradientClass:
+ gradient_length = 1;
+ break;
+ default:
+ NOTREACHED();
+ gradient_length = 0;
+ }
+
+ bool has_hints = false;
+ for (size_t i = 0; i < num_stops; ++i) {
+ const CSSGradientColorStop& stop = stops_[i];
+
+ if (stop.IsHint())
+ has_hints = true;
+ else
+ stops[i].color = ResolveStopColor(*stop.color_, document, style);
+
+ if (stop.offset_) {
+ if (stop.offset_->IsPercentage()) {
+ stops[i].offset = stop.offset_->GetFloatValue() / 100;
+ } else if (stop.offset_->IsLength() ||
+ stop.offset_->IsCalculatedPercentageWithLength()) {
+ float length;
+ if (stop.offset_->IsLength())
+ length = stop.offset_->ComputeLength<float>(conversion_data);
+ else
+ length = stop.offset_->CssCalcValue()
+ ->ToCalcValue(conversion_data)
+ ->Evaluate(gradient_length);
+ stops[i].offset = (gradient_length > 0) ? length / gradient_length : 0;
+ } else if (stop.offset_->IsAngle()) {
+ stops[i].offset = stop.offset_->ComputeDegrees() / 360.0f;
+ } else {
+ NOTREACHED();
+ 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 (num_stops > 1 && i == num_stops - 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 prev_specified_index;
+ for (prev_specified_index = i - 1; prev_specified_index;
+ --prev_specified_index) {
+ if (stops[prev_specified_index].specified)
+ break;
+ }
+
+ if (stops[i].offset < stops[prev_specified_index].offset)
+ stops[i].offset = stops[prev_specified_index].offset;
+ }
+ }
+
+ DCHECK(stops.front().specified);
+ DCHECK(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 (num_stops > 2) {
+ size_t unspecified_run_start = 0;
+ bool in_unspecified_run = false;
+
+ for (size_t i = 0; i < num_stops; ++i) {
+ if (!stops[i].specified && !in_unspecified_run) {
+ unspecified_run_start = i;
+ in_unspecified_run = true;
+ } else if (stops[i].specified && in_unspecified_run) {
+ size_t unspecified_run_end = i;
+
+ if (unspecified_run_start < unspecified_run_end) {
+ float last_specified_offset = stops[unspecified_run_start - 1].offset;
+ float next_specified_offset = stops[unspecified_run_end].offset;
+ float delta = (next_specified_offset - last_specified_offset) /
+ (unspecified_run_end - unspecified_run_start + 1);
+
+ for (size_t j = unspecified_run_start; j < unspecified_run_end; ++j)
+ stops[j].offset =
+ last_specified_offset + (j - unspecified_run_start + 1) * delta;
+ }
+
+ in_unspecified_run = false;
+ }
+ }
+ }
+
+ DCHECK_EQ(stops.size(), stops_.size());
+ if (has_hints) {
+ ReplaceColorHintsWithColorStops(stops, 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, desc)) {
+ // No normalization required, just add the current stops.
+ for (const auto& stop : stops)
+ desc.stops.emplace_back(stop.offset, stop.color);
+ return;
+ }
+
+ switch (GetClassType()) {
+ case kLinearGradientClass:
+ if (NormalizeAndAddStops(stops, desc)) {
+ std::tie(desc.p0, desc.p1) = AdjustedGradientDomainForOffsetRange(
+ desc.p0, desc.p1, stops.front().offset, stops.back().offset);
+ }
+ break;
+ case kRadialGradientClass:
+ // 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 (!repeating_)
+ ClampNegativeOffsets(stops);
+
+ if (NormalizeAndAddStops(stops, desc)) {
+ AdjustGradientRadiiForOffsetRange(desc, stops.front().offset,
+ stops.back().offset);
+ }
+ break;
+ case kConicGradientClass:
+ if (NormalizeAndAddStops(stops, desc)) {
+ std::tie(desc.start_angle, desc.end_angle) =
+ AdjustedGradientDomainForOffsetRange(
+ desc.start_angle, desc.end_angle, stops.front().offset,
+ stops.back().offset);
+ }
+ break;
+ default:
+ NOTREACHED();
+ }
+}
+
+static float PositionFromValue(const CSSValue* value,
+ const CSSToLengthConversionData& conversion_data,
+ const FloatSize& size,
+ bool is_horizontal) {
+ float origin = 0;
+ int sign = 1;
+ float edge_distance = is_horizontal ? 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 origin_id = ToCSSIdentifierValue(pair.First()).GetValueID();
+ value = &pair.Second();
+
+ if (origin_id == CSSValueRight || origin_id == CSSValueBottom) {
+ // For right/bottom, the offset is relative to the far edge.
+ origin = edge_distance;
+ sign = -1;
+ }
+ }
+
+ if (value->IsIdentifierValue()) {
+ switch (ToCSSIdentifierValue(value)->GetValueID()) {
+ case CSSValueTop:
+ DCHECK(!is_horizontal);
+ return 0;
+ case CSSValueLeft:
+ DCHECK(is_horizontal);
+ return 0;
+ case CSSValueBottom:
+ DCHECK(!is_horizontal);
+ return size.Height();
+ case CSSValueRight:
+ DCHECK(is_horizontal);
+ return size.Width();
+ case CSSValueCenter:
+ return origin + sign * .5f * edge_distance;
+ default:
+ NOTREACHED();
+ break;
+ }
+ }
+
+ const CSSPrimitiveValue* primitive_value = ToCSSPrimitiveValue(value);
+
+ if (primitive_value->IsNumber())
+ return origin +
+ sign * primitive_value->GetFloatValue() * conversion_data.Zoom();
+
+ if (primitive_value->IsPercentage())
+ return origin +
+ sign * primitive_value->GetFloatValue() / 100.f * edge_distance;
+
+ if (primitive_value->IsCalculatedPercentageWithLength())
+ return origin + sign * primitive_value->CssCalcValue()
+ ->ToCalcValue(conversion_data)
+ ->Evaluate(edge_distance);
+
+ return origin + sign * primitive_value->ComputeLength<float>(conversion_data);
+}
+
+// Resolve points/radii to front end values.
+static FloatPoint ComputeEndPoint(
+ const CSSValue* horizontal,
+ const CSSValue* vertical,
+ const CSSToLengthConversionData& conversion_data,
+ const FloatSize& size) {
+ FloatPoint result;
+
+ if (horizontal)
+ result.SetX(PositionFromValue(horizontal, conversion_data, size, true));
+
+ if (vertical)
+ result.SetY(PositionFromValue(vertical, conversion_data, size, false));
+
+ return result;
+}
+
+bool CSSGradientValue::KnownToBeOpaque(const Document& document,
+ const ComputedStyle& style) const {
+ for (auto& stop : stops_) {
+ if (!stop.IsHint() &&
+ ResolveStopColor(*stop.color_, document, style).HasAlpha())
+ return false;
+ }
+ return true;
+}
+
+Vector<Color> CSSGradientValue::GetStopColors(
+ const Document& document,
+ const ComputedStyle& style) const {
+ Vector<Color> stop_colors;
+ for (const auto& stop : stops_) {
+ if (!stop.IsHint())
+ stop_colors.push_back(ResolveStopColor(*stop.color_, document, style));
+ }
+ return stop_colors;
+}
+
+void CSSGradientValue::TraceAfterDispatch(blink::Visitor* visitor) {
+ visitor->Trace(stops_);
+ CSSImageGeneratorValue::TraceAfterDispatch(visitor);
+}
+
+String CSSLinearGradientValue::CustomCSSText() const {
+ StringBuilder result;
+ if (gradient_type_ == kCSSDeprecatedLinearGradient) {
+ result.Append("-webkit-gradient(linear, ");
+ result.Append(first_x_->CssText());
+ result.Append(' ');
+ result.Append(first_y_->CssText());
+ result.Append(", ");
+ result.Append(second_x_->CssText());
+ result.Append(' ');
+ result.Append(second_y_->CssText());
+ AppendCSSTextForDeprecatedColorStops(result);
+ } else if (gradient_type_ == kCSSPrefixedLinearGradient) {
+ if (repeating_)
+ result.Append("-webkit-repeating-linear-gradient(");
+ else
+ result.Append("-webkit-linear-gradient(");
+
+ if (angle_)
+ result.Append(angle_->CssText());
+ else {
+ if (first_x_ && first_y_) {
+ result.Append(first_x_->CssText());
+ result.Append(' ');
+ result.Append(first_y_->CssText());
+ } else if (first_x_ || first_y_) {
+ if (first_x_)
+ result.Append(first_x_->CssText());
+
+ if (first_y_)
+ result.Append(first_y_->CssText());
+ }
+ }
+
+ constexpr bool kAppendSeparator = true;
+ AppendCSSTextForColorStops(result, kAppendSeparator);
+ } else {
+ if (repeating_)
+ result.Append("repeating-linear-gradient(");
+ else
+ result.Append("linear-gradient(");
+
+ bool wrote_something = false;
+
+ if (angle_ && angle_->ComputeDegrees() != 180) {
+ result.Append(angle_->CssText());
+ wrote_something = true;
+ } else if ((first_x_ || first_y_) &&
+ !(!first_x_ && first_y_ && first_y_->IsIdentifierValue() &&
+ ToCSSIdentifierValue(first_y_.Get())->GetValueID() ==
+ CSSValueBottom)) {
+ result.Append("to ");
+ if (first_x_ && first_y_) {
+ result.Append(first_x_->CssText());
+ result.Append(' ');
+ result.Append(first_y_->CssText());
+ } else if (first_x_)
+ result.Append(first_x_->CssText());
+ else
+ result.Append(first_y_->CssText());
+ wrote_something = true;
+ }
+
+ AppendCSSTextForColorStops(result, wrote_something);
+ }
+
+ 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 angle_deg,
+ const FloatSize& size,
+ FloatPoint& first_point,
+ FloatPoint& second_point,
+ CSSGradientType type) {
+ // Prefixed gradients use "polar coordinate" angles, rather than "bearing"
+ // angles.
+ if (type == kCSSPrefixedLinearGradient)
+ angle_deg = 90 - angle_deg;
+
+ angle_deg = fmodf(angle_deg, 360);
+ if (angle_deg < 0)
+ angle_deg += 360;
+
+ if (!angle_deg) {
+ first_point.Set(0, size.Height());
+ second_point.Set(0, 0);
+ return;
+ }
+
+ if (angle_deg == 90) {
+ first_point.Set(0, 0);
+ second_point.Set(size.Width(), 0);
+ return;
+ }
+
+ if (angle_deg == 180) {
+ first_point.Set(0, 0);
+ second_point.Set(0, size.Height());
+ return;
+ }
+
+ if (angle_deg == 270) {
+ first_point.Set(size.Width(), 0);
+ second_point.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 - angle_deg));
+
+ // 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 perpendicular_slope = -1 / slope;
+
+ // Compute start corner relative to center, in Cartesian space (+y = up).
+ float half_height = size.Height() / 2;
+ float half_width = size.Width() / 2;
+ FloatPoint end_corner;
+ if (angle_deg < 90)
+ end_corner.Set(half_width, half_height);
+ else if (angle_deg < 180)
+ end_corner.Set(half_width, -half_height);
+ else if (angle_deg < 270)
+ end_corner.Set(-half_width, -half_height);
+ else
+ end_corner.Set(-half_width, half_height);
+
+ // Compute c (of y = mx + c) using the corner point.
+ float c = end_corner.Y() - perpendicular_slope * end_corner.X();
+ float end_x = c / (slope - perpendicular_slope);
+ float end_y = perpendicular_slope * end_x + 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).
+ second_point.Set(half_width + end_x, half_height - end_y);
+ // Reflect around the center for the start point.
+ first_point.Set(half_width - end_x, half_height + end_y);
+}
+
+scoped_refptr<Gradient> CSSLinearGradientValue::CreateGradient(
+ const CSSToLengthConversionData& conversion_data,
+ const FloatSize& size,
+ const Document& document,
+ const ComputedStyle& style) const {
+ DCHECK(!size.IsEmpty());
+
+ FloatPoint first_point;
+ FloatPoint second_point;
+ if (angle_) {
+ float angle = angle_->ComputeDegrees();
+ EndPointsFromAngle(angle, size, first_point, second_point, gradient_type_);
+ } else {
+ switch (gradient_type_) {
+ case kCSSDeprecatedLinearGradient:
+ first_point = ComputeEndPoint(first_x_.Get(), first_y_.Get(),
+ conversion_data, size);
+ if (second_x_ || second_y_)
+ second_point = ComputeEndPoint(second_x_.Get(), second_y_.Get(),
+ conversion_data, size);
+ else {
+ if (first_x_)
+ second_point.SetX(size.Width() - first_point.X());
+ if (first_y_)
+ second_point.SetY(size.Height() - first_point.Y());
+ }
+ break;
+ case kCSSPrefixedLinearGradient:
+ first_point = ComputeEndPoint(first_x_.Get(), first_y_.Get(),
+ conversion_data, size);
+ if (first_x_)
+ second_point.SetX(size.Width() - first_point.X());
+ if (first_y_)
+ second_point.SetY(size.Height() - first_point.Y());
+ break;
+ case kCSSLinearGradient:
+ if (first_x_ && first_y_) {
+ // "Magic" corners, so the 50% line touches two corners.
+ float rise = size.Width();
+ float run = size.Height();
+ if (first_x_ && first_x_->IsIdentifierValue() &&
+ ToCSSIdentifierValue(first_x_.Get())->GetValueID() ==
+ CSSValueLeft)
+ run *= -1;
+ if (first_y_ && first_y_->IsIdentifierValue() &&
+ ToCSSIdentifierValue(first_y_.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, first_point, second_point,
+ gradient_type_);
+ } else if (first_x_ || first_y_) {
+ second_point = ComputeEndPoint(first_x_.Get(), first_y_.Get(),
+ conversion_data, size);
+ if (first_x_)
+ first_point.SetX(size.Width() - second_point.X());
+ if (first_y_)
+ first_point.SetY(size.Height() - second_point.Y());
+ } else
+ second_point.SetY(size.Height());
+ break;
+ default:
+ NOTREACHED();
+ }
+ }
+
+ GradientDesc desc(first_point, second_point,
+ repeating_ ? kSpreadMethodRepeat : kSpreadMethodPad);
+ AddStops(desc, conversion_data, document, style);
+
+ scoped_refptr<Gradient> gradient =
+ Gradient::CreateLinear(desc.p0, desc.p1, desc.spread_method,
+ Gradient::ColorInterpolation::kPremultiplied);
+
+ // Now add the stops.
+ gradient->AddColorStops(desc.stops);
+
+ return gradient;
+}
+
+bool CSSLinearGradientValue::Equals(const CSSLinearGradientValue& other) const {
+ if (gradient_type_ == kCSSDeprecatedLinearGradient)
+ return other.gradient_type_ == gradient_type_ &&
+ DataEquivalent(first_x_, other.first_x_) &&
+ DataEquivalent(first_y_, other.first_y_) &&
+ DataEquivalent(second_x_, other.second_x_) &&
+ DataEquivalent(second_y_, other.second_y_) && stops_ == other.stops_;
+
+ if (repeating_ != other.repeating_)
+ return false;
+
+ if (angle_)
+ return DataEquivalent(angle_, other.angle_) && stops_ == other.stops_;
+
+ if (other.angle_)
+ return false;
+
+ bool equal_xand_y = false;
+ if (first_x_ && first_y_) {
+ equal_xand_y = DataEquivalent(first_x_, other.first_x_) &&
+ DataEquivalent(first_y_, other.first_y_);
+ } else if (first_x_) {
+ equal_xand_y = DataEquivalent(first_x_, other.first_x_) && !other.first_y_;
+ } else if (first_y_) {
+ equal_xand_y = DataEquivalent(first_y_, other.first_y_) && !other.first_x_;
+ } else {
+ equal_xand_y = !other.first_x_ && !other.first_y_;
+ }
+
+ return equal_xand_y && stops_ == other.stops_;
+}
+
+void CSSLinearGradientValue::TraceAfterDispatch(blink::Visitor* visitor) {
+ visitor->Trace(first_x_);
+ visitor->Trace(first_y_);
+ visitor->Trace(second_x_);
+ visitor->Trace(second_y_);
+ visitor->Trace(angle_);
+ CSSGradientValue::TraceAfterDispatch(visitor);
+}
+
+void CSSGradientValue::AppendCSSTextForColorStops(
+ StringBuilder& result,
+ bool requires_separator) const {
+ const CSSValue* prev_color = nullptr;
+
+ for (const auto& stop : stops_) {
+ bool is_color_repeat = false;
+ if (RuntimeEnabledFeatures::MultipleColorStopPositionsEnabled()) {
+ is_color_repeat = stop.color_ && stop.offset_ &&
+ DataEquivalent(stop.color_.Get(), prev_color);
+ }
+
+ if (requires_separator) {
+ if (!is_color_repeat)
+ result.Append(", ");
+ } else {
+ requires_separator = true;
+ }
+
+ if (stop.color_ && !is_color_repeat)
+ result.Append(stop.color_->CssText());
+ if (stop.color_ && stop.offset_)
+ result.Append(' ');
+ if (stop.offset_)
+ result.Append(stop.offset_->CssText());
+
+ // Reset prevColor if we've emitted a color repeat.
+ prev_color = is_color_repeat ? nullptr : stop.color_.Get();
+ }
+}
+
+void CSSGradientValue::AppendCSSTextForDeprecatedColorStops(
+ StringBuilder& result) const {
+ for (unsigned i = 0; i < stops_.size(); i++) {
+ const CSSGradientColorStop& stop = stops_[i];
+ result.Append(", ");
+ if (stop.offset_->GetDoubleValue() == 0) {
+ result.Append("from(");
+ result.Append(stop.color_->CssText());
+ result.Append(')');
+ } else if (stop.offset_->GetDoubleValue() == 1) {
+ result.Append("to(");
+ result.Append(stop.color_->CssText());
+ result.Append(')');
+ } else {
+ result.Append("color-stop(");
+ result.AppendNumber(stop.offset_->GetDoubleValue());
+ result.Append(", ");
+ result.Append(stop.color_->CssText());
+ result.Append(')');
+ }
+ }
+}
+
+String CSSRadialGradientValue::CustomCSSText() const {
+ StringBuilder result;
+
+ if (gradient_type_ == kCSSDeprecatedRadialGradient) {
+ result.Append("-webkit-gradient(radial, ");
+ result.Append(first_x_->CssText());
+ result.Append(' ');
+ result.Append(first_y_->CssText());
+ result.Append(", ");
+ result.Append(first_radius_->CssText());
+ result.Append(", ");
+ result.Append(second_x_->CssText());
+ result.Append(' ');
+ result.Append(second_y_->CssText());
+ result.Append(", ");
+ result.Append(second_radius_->CssText());
+ AppendCSSTextForDeprecatedColorStops(result);
+ } else if (gradient_type_ == kCSSPrefixedRadialGradient) {
+ if (repeating_)
+ result.Append("-webkit-repeating-radial-gradient(");
+ else
+ result.Append("-webkit-radial-gradient(");
+
+ if (first_x_ && first_y_) {
+ result.Append(first_x_->CssText());
+ result.Append(' ');
+ result.Append(first_y_->CssText());
+ } else if (first_x_)
+ result.Append(first_x_->CssText());
+ else if (first_y_)
+ result.Append(first_y_->CssText());
+ else
+ result.Append("center");
+
+ if (shape_ || sizing_behavior_) {
+ result.Append(", ");
+ if (shape_) {
+ result.Append(shape_->CssText());
+ result.Append(' ');
+ } else {
+ result.Append("ellipse ");
+ }
+
+ if (sizing_behavior_)
+ result.Append(sizing_behavior_->CssText());
+ else
+ result.Append("cover");
+
+ } else if (end_horizontal_size_ && end_vertical_size_) {
+ result.Append(", ");
+ result.Append(end_horizontal_size_->CssText());
+ result.Append(' ');
+ result.Append(end_vertical_size_->CssText());
+ }
+
+ constexpr bool kAppendSeparator = true;
+ AppendCSSTextForColorStops(result, kAppendSeparator);
+ } else {
+ if (repeating_)
+ result.Append("repeating-radial-gradient(");
+ else
+ result.Append("radial-gradient(");
+
+ bool wrote_something = 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 (shape_ && shape_->GetValueID() != CSSValueEllipse &&
+ (sizing_behavior_ || (!sizing_behavior_ && !end_horizontal_size_))) {
+ result.Append("circle");
+ wrote_something = true;
+ }
+
+ if (sizing_behavior_ &&
+ sizing_behavior_->GetValueID() != CSSValueFarthestCorner) {
+ if (wrote_something)
+ result.Append(' ');
+ result.Append(sizing_behavior_->CssText());
+ wrote_something = true;
+ } else if (end_horizontal_size_) {
+ if (wrote_something)
+ result.Append(' ');
+ result.Append(end_horizontal_size_->CssText());
+ if (end_vertical_size_) {
+ result.Append(' ');
+ result.Append(end_vertical_size_->CssText());
+ }
+ wrote_something = true;
+ }
+
+ wrote_something |=
+ AppendPosition(result, first_x_, first_y_, wrote_something);
+
+ AppendCSSTextForColorStops(result, wrote_something);
+ }
+
+ result.Append(')');
+ return result.ToString();
+}
+
+namespace {
+
+// Resolve points/radii to front end values.
+float ResolveRadius(const CSSPrimitiveValue* radius,
+ const CSSToLengthConversionData& conversion_data,
+ float* width_or_height = nullptr) {
+ float result = 0;
+ if (radius->IsNumber())
+ result = radius->GetFloatValue() * conversion_data.Zoom();
+ else if (width_or_height && radius->IsPercentage())
+ result = *width_or_height * radius->GetFloatValue() / 100;
+ else
+ result = radius->ComputeLength<float>(conversion_data);
+
+ return clampTo<float>(std::max(result, 0.0f));
+}
+
+enum EndShapeType { kCircleEndShape, kEllipseEndShape };
+
+// 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 == kCircleEndShape)
+ return compare(dx, dy) ? FloatSize(dx, dx) : FloatSize(dy, dy);
+
+ DCHECK_EQ(shape, kEllipseEndShape);
+ 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 aspect_ratio) {
+ // If the aspectRatio is 0 or infinite, the ellipse is completely flat.
+ // TODO(sashab): Implement Degenerate Radial Gradients, see crbug.com/635727.
+ if (aspect_ratio == 0 || std::isinf(aspect_ratio))
+ 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() * aspect_ratio * aspect_ratio);
+ return FloatSize(clampTo<float>(a), clampTo<float>(a / aspect_ratio));
+}
+
+// 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 corner_index = 0;
+ float distance = (point - corners[corner_index]).DiagonalLength();
+ for (unsigned i = 1; i < WTF_ARRAY_LENGTH(corners); ++i) {
+ float new_distance = (point - corners[i]).DiagonalLength();
+ if (compare(new_distance, distance)) {
+ corner_index = i;
+ distance = new_distance;
+ }
+ }
+
+ if (shape == kCircleEndShape)
+ return FloatSize(distance, distance);
+
+ DCHECK_EQ(shape, kEllipseEndShape);
+ // 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 side_radius =
+ RadiusToSide(point, size, kEllipseEndShape, compare);
+
+ return EllipseRadius(FloatPoint(corners[corner_index] - point),
+ side_radius.AspectRatio());
+}
+
+} // anonymous namespace
+
+scoped_refptr<Gradient> CSSRadialGradientValue::CreateGradient(
+ const CSSToLengthConversionData& conversion_data,
+ const FloatSize& size,
+ const Document& document,
+ const ComputedStyle& style) const {
+ DCHECK(!size.IsEmpty());
+
+ FloatPoint first_point =
+ ComputeEndPoint(first_x_.Get(), first_y_.Get(), conversion_data, size);
+ if (!first_x_)
+ first_point.SetX(size.Width() / 2);
+ if (!first_y_)
+ first_point.SetY(size.Height() / 2);
+
+ FloatPoint second_point =
+ ComputeEndPoint(second_x_.Get(), second_y_.Get(), conversion_data, size);
+ if (!second_x_)
+ second_point.SetX(size.Width() / 2);
+ if (!second_y_)
+ second_point.SetY(size.Height() / 2);
+
+ float first_radius = 0;
+ if (first_radius_)
+ first_radius = ResolveRadius(first_radius_.Get(), conversion_data);
+
+ FloatSize second_radius(0, 0);
+ if (second_radius_) {
+ second_radius.SetWidth(
+ ResolveRadius(second_radius_.Get(), conversion_data));
+ second_radius.SetHeight(second_radius.Width());
+ } else if (end_horizontal_size_) {
+ float width = size.Width();
+ float height = size.Height();
+ second_radius.SetWidth(
+ ResolveRadius(end_horizontal_size_.Get(), conversion_data, &width));
+ second_radius.SetHeight(
+ end_vertical_size_
+ ? ResolveRadius(end_vertical_size_.Get(), conversion_data, &height)
+ : second_radius.Width());
+ } else {
+ EndShapeType shape = (shape_ && shape_->GetValueID() == CSSValueCircle) ||
+ (!shape_ && !sizing_behavior_ &&
+ end_horizontal_size_ && !end_vertical_size_)
+ ? kCircleEndShape
+ : kEllipseEndShape;
+
+ switch (sizing_behavior_ ? sizing_behavior_->GetValueID() : 0) {
+ case CSSValueContain:
+ case CSSValueClosestSide:
+ second_radius = RadiusToSide(second_point, size, shape,
+ [](float a, float b) { return a < b; });
+ break;
+ case CSSValueFarthestSide:
+ second_radius = RadiusToSide(second_point, size, shape,
+ [](float a, float b) { return a > b; });
+ break;
+ case CSSValueClosestCorner:
+ second_radius = RadiusToCorner(second_point, size, shape,
+ [](float a, float b) { return a < b; });
+ break;
+ default:
+ second_radius = RadiusToCorner(second_point, size, shape,
+ [](float a, float b) { return a > b; });
+ break;
+ }
+ }
+
+ DCHECK(std::isfinite(first_radius));
+ DCHECK(std::isfinite(second_radius.Width()));
+ DCHECK(std::isfinite(second_radius.Height()));
+
+ bool is_degenerate = !second_radius.Width() || !second_radius.Height();
+ GradientDesc desc(first_point, second_point, first_radius,
+ is_degenerate ? 0 : second_radius.Width(),
+ repeating_ ? kSpreadMethodRepeat : kSpreadMethodPad);
+ AddStops(desc, conversion_data, document, style);
+
+ scoped_refptr<Gradient> gradient = Gradient::CreateRadial(
+ desc.p0, desc.r0, desc.p1, desc.r1,
+ is_degenerate ? 1 : second_radius.AspectRatio(), desc.spread_method,
+ Gradient::ColorInterpolation::kPremultiplied);
+
+ // Now add the stops.
+ gradient->AddColorStops(desc.stops);
+
+ return gradient;
+}
+
+namespace {
+
+bool EqualIdentifiersWithDefault(const CSSIdentifierValue* id_a,
+ const CSSIdentifierValue* id_b,
+ CSSValueID default_id) {
+ CSSValueID value_a = id_a ? id_a->GetValueID() : default_id;
+ CSSValueID value_b = id_b ? id_b->GetValueID() : default_id;
+ return value_a == value_b;
+}
+
+} // namespace
+
+bool CSSRadialGradientValue::Equals(const CSSRadialGradientValue& other) const {
+ if (gradient_type_ == kCSSDeprecatedRadialGradient)
+ return other.gradient_type_ == gradient_type_ &&
+ DataEquivalent(first_x_, other.first_x_) &&
+ DataEquivalent(first_y_, other.first_y_) &&
+ DataEquivalent(second_x_, other.second_x_) &&
+ DataEquivalent(second_y_, other.second_y_) &&
+ DataEquivalent(first_radius_, other.first_radius_) &&
+ DataEquivalent(second_radius_, other.second_radius_) &&
+ stops_ == other.stops_;
+
+ if (repeating_ != other.repeating_)
+ return false;
+
+ if (!DataEquivalent(first_x_, other.first_x_) ||
+ !DataEquivalent(first_y_, other.first_y_))
+ return false;
+
+ // There's either a size keyword or an explicit size specification.
+ if (end_horizontal_size_) {
+ // Explicit size specification. One <length> or two <length-percentage>.
+ if (!DataEquivalent(end_horizontal_size_, other.end_horizontal_size_))
+ return false;
+ if (!DataEquivalent(end_vertical_size_, other.end_vertical_size_))
+ return false;
+ } else {
+ if (other.end_horizontal_size_)
+ return false;
+ // There's a size keyword.
+ if (!EqualIdentifiersWithDefault(sizing_behavior_, other.sizing_behavior_,
+ CSSValueFarthestCorner))
+ return false;
+ // Here the shape is 'ellipse' unless explicitly set to 'circle'.
+ if (!EqualIdentifiersWithDefault(shape_, other.shape_, CSSValueEllipse))
+ return false;
+ }
+ return stops_ == other.stops_;
+}
+
+void CSSRadialGradientValue::TraceAfterDispatch(blink::Visitor* visitor) {
+ visitor->Trace(first_x_);
+ visitor->Trace(first_y_);
+ visitor->Trace(second_x_);
+ visitor->Trace(second_y_);
+ visitor->Trace(first_radius_);
+ visitor->Trace(second_radius_);
+ visitor->Trace(shape_);
+ visitor->Trace(sizing_behavior_);
+ visitor->Trace(end_horizontal_size_);
+ visitor->Trace(end_vertical_size_);
+ CSSGradientValue::TraceAfterDispatch(visitor);
+}
+
+String CSSConicGradientValue::CustomCSSText() const {
+ StringBuilder result;
+
+ if (repeating_)
+ result.Append("repeating-");
+ result.Append("conic-gradient(");
+
+ bool wrote_something = false;
+
+ if (from_angle_) {
+ result.Append("from ");
+ result.Append(from_angle_->CssText());
+ wrote_something = true;
+ }
+
+ wrote_something |= AppendPosition(result, x_, y_, wrote_something);
+
+ AppendCSSTextForColorStops(result, wrote_something);
+
+ result.Append(')');
+ return result.ToString();
+}
+
+scoped_refptr<Gradient> CSSConicGradientValue::CreateGradient(
+ const CSSToLengthConversionData& conversion_data,
+ const FloatSize& size,
+ const Document& document,
+ const ComputedStyle& style) const {
+ DCHECK(!size.IsEmpty());
+
+ const float angle = from_angle_ ? from_angle_->ComputeDegrees() : 0;
+
+ const FloatPoint position(
+ x_ ? PositionFromValue(x_, conversion_data, size, true)
+ : size.Width() / 2,
+ y_ ? PositionFromValue(y_, conversion_data, size, false)
+ : size.Height() / 2);
+
+ GradientDesc desc(position, position,
+ repeating_ ? kSpreadMethodRepeat : kSpreadMethodPad);
+ AddStops(desc, conversion_data, document, style);
+
+ scoped_refptr<Gradient> gradient = Gradient::CreateConic(
+ position, angle, desc.start_angle, desc.end_angle, desc.spread_method,
+ Gradient::ColorInterpolation::kPremultiplied);
+ gradient->AddColorStops(desc.stops);
+
+ return gradient;
+}
+
+bool CSSConicGradientValue::Equals(const CSSConicGradientValue& other) const {
+ return repeating_ == other.repeating_ && DataEquivalent(x_, other.x_) &&
+ DataEquivalent(y_, other.y_) &&
+ DataEquivalent(from_angle_, other.from_angle_) &&
+ stops_ == other.stops_;
+}
+
+void CSSConicGradientValue::TraceAfterDispatch(blink::Visitor* visitor) {
+ visitor->Trace(x_);
+ visitor->Trace(y_);
+ visitor->Trace(from_angle_);
+ CSSGradientValue::TraceAfterDispatch(visitor);
+}
+
+} // namespace cssvalue
+} // namespace blink