1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use crate::display_item as di;
use crate::units::*;
/// Construct a gradient to be used in display lists.
///
/// Each gradient needs at least two stops.
pub struct GradientBuilder {
stops: Vec<di::GradientStop>,
}
impl GradientBuilder {
/// Create a new gradient builder.
pub fn new() -> Self {
GradientBuilder {
stops: Vec::new(),
}
}
/// Create a gradient builder with a list of stops.
pub fn with_stops(stops: Vec<di::GradientStop>) -> GradientBuilder {
GradientBuilder { stops }
}
/// Push an additional stop for the gradient.
pub fn push(&mut self, stop: di::GradientStop) {
self.stops.push(stop);
}
/// Get a reference to the list of stops.
pub fn stops(&self) -> &[di::GradientStop] {
self.stops.as_ref()
}
/// Return the gradient stops vector.
pub fn into_stops(self) -> Vec<di::GradientStop> {
self.stops
}
/// Produce a linear gradient, normalize the stops.
pub fn gradient(
&mut self,
start_point: LayoutPoint,
end_point: LayoutPoint,
extend_mode: di::ExtendMode,
) -> di::Gradient {
let (start_offset, end_offset) = self.normalize(extend_mode);
let start_to_end = end_point - start_point;
di::Gradient {
start_point: start_point + start_to_end * start_offset,
end_point: start_point + start_to_end * end_offset,
extend_mode,
}
}
/// Produce a radial gradient, normalize the stops.
///
/// Will replace the gradient with a single color
/// if the radius negative.
pub fn radial_gradient(
&mut self,
center: LayoutPoint,
radius: LayoutSize,
extend_mode: di::ExtendMode,
) -> di::RadialGradient {
if radius.width <= 0.0 || radius.height <= 0.0 {
// The shader cannot handle a non positive radius. So
// reuse the stops vector and construct an equivalent
// gradient.
let last_color = self.stops.last().unwrap().color;
self.stops.clear();
self.stops.push(di::GradientStop { offset: 0.0, color: last_color, });
self.stops.push(di::GradientStop { offset: 1.0, color: last_color, });
return di::RadialGradient {
center,
radius: LayoutSize::new(1.0, 1.0),
start_offset: 0.0,
end_offset: 1.0,
extend_mode,
};
}
let (start_offset, end_offset) =
self.normalize(extend_mode);
di::RadialGradient {
center,
radius,
start_offset,
end_offset,
extend_mode,
}
}
/// Produce a conic gradient, normalize the stops.
pub fn conic_gradient(
&mut self,
center: LayoutPoint,
angle: f32,
extend_mode: di::ExtendMode,
) -> di::ConicGradient {
let (start_offset, end_offset) =
self.normalize(extend_mode);
di::ConicGradient {
center,
angle,
start_offset,
end_offset,
extend_mode,
}
}
/// Gradients can be defined with stops outside the range of [0, 1]
/// when this happens the gradient needs to be normalized by adjusting
/// the gradient stops and gradient line into an equivalent gradient
/// with stops in the range [0, 1]. this is done by moving the beginning
/// of the gradient line to where stop[0] and the end of the gradient line
/// to stop[n-1]. this function adjusts the stops in place, and returns
/// the amount to adjust the gradient line start and stop.
fn normalize(&mut self, extend_mode: di::ExtendMode) -> (f32, f32) {
let stops = &mut self.stops;
assert!(stops.len() >= 2);
let first = *stops.first().unwrap();
let last = *stops.last().unwrap();
let stops_delta = last.offset - first.offset;
if stops_delta > 0.000001 {
for stop in stops {
stop.offset = (stop.offset - first.offset) / stops_delta;
}
(first.offset, last.offset)
} else if stops_delta.is_nan() {
// We have no good way to render a NaN offset, but make something
// that is at least renderable.
stops.clear();
stops.push(di::GradientStop { color: last.color, offset: 0.0, });
stops.push(di::GradientStop { color: last.color, offset: 1.0, });
(0.0, 1.0)
} else {
// We have a degenerate gradient and can't accurately transform the stops
// what happens here depends on the repeat behavior, but in any case
// we reconstruct the gradient stops to something simpler and equivalent
stops.clear();
match extend_mode {
di::ExtendMode::Clamp => {
// This gradient is two colors split at the offset of the stops,
// so create a gradient with two colors split at 0.5 and adjust
// the gradient line so 0.5 is at the offset of the stops
stops.push(di::GradientStop { color: first.color, offset: 0.0, });
stops.push(di::GradientStop { color: first.color, offset: 0.5, });
stops.push(di::GradientStop { color: last.color, offset: 0.5, });
stops.push(di::GradientStop { color: last.color, offset: 1.0, });
let offset = last.offset;
(offset - 0.5, offset + 0.5)
}
di::ExtendMode::Repeat => {
// A repeating gradient with stops that are all in the same
// position should just display the last color. I believe the
// spec says that it should be the average color of the gradient,
// but this matches what Gecko and Blink does
stops.push(di::GradientStop { color: last.color, offset: 0.0, });
stops.push(di::GradientStop { color: last.color, offset: 1.0, });
(0.0, 1.0)
}
}
}
}
}