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
/* 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/. */

//! Computed angles.

use num_traits::Zero;
use std::{f32, f64};
use std::f64::consts::PI;
use std::ops::Add;
use values::CSSFloat;
use values::animated::{Animate, Procedure};
use values::distance::{ComputeSquaredDistance, SquaredDistance};

/// A computed angle.
#[animate(fallback = "Self::animate_fallback")]
#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
#[derive(Animate, Clone, Copy, Debug, MallocSizeOf, PartialEq, ToCss)]
#[derive(PartialOrd, ToAnimatedZero)]
pub enum Angle {
    /// An angle with degree unit.
    #[css(dimension)]
    Deg(CSSFloat),
    /// An angle with gradian unit.
    #[css(dimension)]
    Grad(CSSFloat),
    /// An angle with radian unit.
    #[css(dimension)]
    Rad(CSSFloat),
    /// An angle with turn unit.
    #[css(dimension)]
    Turn(CSSFloat),
}

impl Angle {
    /// Creates a computed `Angle` value from a radian amount.
    pub fn from_radians(radians: CSSFloat) -> Self {
        Angle::Rad(radians)
    }

    /// Returns the amount of radians this angle represents.
    #[inline]
    pub fn radians(&self) -> CSSFloat {
        self.radians64().min(f32::MAX as f64).max(f32::MIN as f64) as f32
    }

    /// Returns the amount of radians this angle represents as a `f64`.
    ///
    /// Gecko stores angles as singles, but does this computation using doubles.
    /// See nsCSSValue::GetAngleValueInRadians.
    /// This is significant enough to mess up rounding to the nearest
    /// quarter-turn for 225 degrees, for example.
    #[inline]
    pub fn radians64(&self) -> f64 {
        const RAD_PER_DEG: f64 = PI / 180.0;
        const RAD_PER_GRAD: f64 = PI / 200.0;
        const RAD_PER_TURN: f64 = PI * 2.0;

        let radians = match *self {
            Angle::Deg(val) => val as f64 * RAD_PER_DEG,
            Angle::Grad(val) => val as f64 * RAD_PER_GRAD,
            Angle::Turn(val) => val as f64 * RAD_PER_TURN,
            Angle::Rad(val) => val as f64,
        };
        radians.min(f64::MAX).max(f64::MIN)
    }

    /// <https://drafts.csswg.org/css-transitions/#animtype-number>
    #[inline]
    fn animate_fallback(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
        Ok(Angle::from_radians(self.radians().animate(&other.radians(), procedure)?))
    }
}

impl AsRef<Angle> for Angle {
    #[inline]
    fn as_ref(&self) -> &Self {
        self
    }
}

impl Add for Angle {
    type Output = Self;

    #[inline]
    fn add(self, rhs: Self) -> Self {
        match (self, rhs) {
            (Angle::Deg(x), Angle::Deg(y)) => Angle::Deg(x + y),
            (Angle::Grad(x), Angle::Grad(y)) => Angle::Grad(x + y),
            (Angle::Turn(x), Angle::Turn(y)) => Angle::Turn(x + y),
            (Angle::Rad(x), Angle::Rad(y)) => Angle::Rad(x + y),
            _ => Angle::from_radians(self.radians() + rhs.radians()),
        }
    }
}

impl Zero for Angle {
    #[inline]
    fn zero() -> Self {
        Angle::from_radians(0.0)
    }

    #[inline]
    fn is_zero(&self) -> bool {
        match *self {
            Angle::Deg(val) |
            Angle::Grad(val) |
            Angle::Turn(val) |
            Angle::Rad(val) => val == 0.
        }
    }
}

impl ComputeSquaredDistance for Angle {
    #[inline]
    fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
        // Use the formula for calculating the distance between angles defined in SVG:
        // https://www.w3.org/TR/SVG/animate.html#complexDistances
        self.radians64().compute_squared_distance(&other.radians64())
    }
}