zng_webrender_api/
gradient_builder.rs

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