tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

gradient_builder.rs (6500B)


      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 
      5 use crate::display_item as di;
      6 use crate::units::*;
      7 
      8 
      9 /// Construct a gradient to be used in display lists.
     10 ///
     11 /// Each gradient needs at least two stops.
     12 pub struct GradientBuilder {
     13    stops: Vec<di::GradientStop>,
     14 }
     15 
     16 impl 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 }