tor-browser

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

angle.rs (8632B)


      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 https://mozilla.org/MPL/2.0/. */
      4 
      5 //! Specified angles.
      6 
      7 use crate::derives::*;
      8 use crate::parser::{Parse, ParserContext};
      9 use crate::values::computed::angle::Angle as ComputedAngle;
     10 use crate::values::computed::{Context, ToComputedValue};
     11 use crate::values::specified::calc::CalcNode;
     12 use crate::values::CSSFloat;
     13 use crate::Zero;
     14 use cssparser::{match_ignore_ascii_case, Parser, Token};
     15 use std::f32::consts::PI;
     16 use std::fmt::{self, Write};
     17 use std::ops::Neg;
     18 use style_traits::{CssWriter, ParseError, SpecifiedValueInfo, ToCss};
     19 
     20 /// A specified angle dimension.
     21 #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
     22 #[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, PartialOrd, ToCss, ToShmem)]
     23 pub enum AngleDimension {
     24    /// An angle with degree unit.
     25    #[css(dimension)]
     26    Deg(CSSFloat),
     27    /// An angle with gradian unit.
     28    #[css(dimension)]
     29    Grad(CSSFloat),
     30    /// An angle with radian unit.
     31    #[css(dimension)]
     32    Rad(CSSFloat),
     33    /// An angle with turn unit.
     34    #[css(dimension)]
     35    Turn(CSSFloat),
     36 }
     37 
     38 impl Zero for AngleDimension {
     39    fn zero() -> Self {
     40        AngleDimension::Deg(0.)
     41    }
     42 
     43    fn is_zero(&self) -> bool {
     44        self.unitless_value() == 0.0
     45    }
     46 }
     47 
     48 impl AngleDimension {
     49    /// Returns the amount of degrees this angle represents.
     50    #[inline]
     51    fn degrees(&self) -> CSSFloat {
     52        const DEG_PER_RAD: f32 = 180.0 / PI;
     53        const DEG_PER_TURN: f32 = 360.0;
     54        const DEG_PER_GRAD: f32 = 180.0 / 200.0;
     55 
     56        match *self {
     57            AngleDimension::Deg(d) => d,
     58            AngleDimension::Rad(rad) => rad * DEG_PER_RAD,
     59            AngleDimension::Turn(turns) => turns * DEG_PER_TURN,
     60            AngleDimension::Grad(gradians) => gradians * DEG_PER_GRAD,
     61        }
     62    }
     63 
     64    fn unitless_value(&self) -> CSSFloat {
     65        match *self {
     66            AngleDimension::Deg(v)
     67            | AngleDimension::Rad(v)
     68            | AngleDimension::Turn(v)
     69            | AngleDimension::Grad(v) => v,
     70        }
     71    }
     72 
     73    fn unit(&self) -> &'static str {
     74        match *self {
     75            AngleDimension::Deg(_) => "deg",
     76            AngleDimension::Rad(_) => "rad",
     77            AngleDimension::Turn(_) => "turn",
     78            AngleDimension::Grad(_) => "grad",
     79        }
     80    }
     81 }
     82 
     83 /// A specified Angle value, which is just the angle dimension, plus whether it
     84 /// was specified as `calc()` or not.
     85 #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
     86 #[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToShmem)]
     87 pub struct Angle {
     88    value: AngleDimension,
     89    was_calc: bool,
     90 }
     91 
     92 impl Zero for Angle {
     93    fn zero() -> Self {
     94        Self {
     95            value: Zero::zero(),
     96            was_calc: false,
     97        }
     98    }
     99 
    100    fn is_zero(&self) -> bool {
    101        self.value.is_zero()
    102    }
    103 }
    104 
    105 impl ToCss for Angle {
    106    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
    107    where
    108        W: Write,
    109    {
    110        crate::values::serialize_specified_dimension(
    111            self.value.unitless_value(),
    112            self.value.unit(),
    113            self.was_calc,
    114            dest,
    115        )
    116    }
    117 }
    118 
    119 impl ToComputedValue for Angle {
    120    type ComputedValue = ComputedAngle;
    121 
    122    #[inline]
    123    fn to_computed_value(&self, _context: &Context) -> Self::ComputedValue {
    124        let degrees = self.degrees();
    125 
    126        // NaN and +-infinity should degenerate to 0: https://github.com/w3c/csswg-drafts/issues/6105
    127        ComputedAngle::from_degrees(if degrees.is_finite() { degrees } else { 0.0 })
    128    }
    129 
    130    #[inline]
    131    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
    132        Angle {
    133            value: AngleDimension::Deg(computed.degrees()),
    134            was_calc: false,
    135        }
    136    }
    137 }
    138 
    139 impl Angle {
    140    /// Creates an angle with the given value in degrees.
    141    #[inline]
    142    pub fn from_degrees(value: CSSFloat, was_calc: bool) -> Self {
    143        Angle {
    144            value: AngleDimension::Deg(value),
    145            was_calc,
    146        }
    147    }
    148 
    149    /// Creates an angle with the given value in radians.
    150    #[inline]
    151    pub fn from_radians(value: CSSFloat) -> Self {
    152        Angle {
    153            value: AngleDimension::Rad(value),
    154            was_calc: false,
    155        }
    156    }
    157 
    158    /// Return `0deg`.
    159    pub fn zero() -> Self {
    160        Self::from_degrees(0.0, false)
    161    }
    162 
    163    /// Returns the value of the angle in degrees, mostly for `calc()`.
    164    #[inline]
    165    pub fn degrees(&self) -> CSSFloat {
    166        self.value.degrees()
    167    }
    168 
    169    /// Returns the value of the angle in radians.
    170    #[inline]
    171    pub fn radians(&self) -> CSSFloat {
    172        const RAD_PER_DEG: f32 = PI / 180.0;
    173        self.value.degrees() * RAD_PER_DEG
    174    }
    175 
    176    /// Whether this specified angle came from a `calc()` expression.
    177    #[inline]
    178    pub fn was_calc(&self) -> bool {
    179        self.was_calc
    180    }
    181 
    182    /// Returns an `Angle` parsed from a `calc()` expression.
    183    pub fn from_calc(degrees: CSSFloat) -> Self {
    184        Angle {
    185            value: AngleDimension::Deg(degrees),
    186            was_calc: true,
    187        }
    188    }
    189 
    190    /// Returns the unit of the angle.
    191    #[inline]
    192    pub fn unit(&self) -> &'static str {
    193        self.value.unit()
    194    }
    195 }
    196 
    197 /// Whether to allow parsing an unitless zero as a valid angle.
    198 ///
    199 /// This should always be `No`, except for exceptions like:
    200 ///
    201 ///   https://github.com/w3c/fxtf-drafts/issues/228
    202 ///
    203 /// See also: https://github.com/w3c/csswg-drafts/issues/1162.
    204 #[allow(missing_docs)]
    205 pub enum AllowUnitlessZeroAngle {
    206    Yes,
    207    No,
    208 }
    209 
    210 impl Parse for Angle {
    211    /// Parses an angle according to CSS-VALUES ยง 6.1.
    212    fn parse<'i, 't>(
    213        context: &ParserContext,
    214        input: &mut Parser<'i, 't>,
    215    ) -> Result<Self, ParseError<'i>> {
    216        Self::parse_internal(context, input, AllowUnitlessZeroAngle::No)
    217    }
    218 }
    219 
    220 impl Angle {
    221    /// Parse an `<angle>` value given a value and an unit.
    222    pub fn parse_dimension(value: CSSFloat, unit: &str, was_calc: bool) -> Result<Angle, ()> {
    223        let value = match_ignore_ascii_case! { unit,
    224            "deg" => AngleDimension::Deg(value),
    225            "grad" => AngleDimension::Grad(value),
    226            "turn" => AngleDimension::Turn(value),
    227            "rad" => AngleDimension::Rad(value),
    228             _ => return Err(())
    229        };
    230 
    231        Ok(Self { value, was_calc })
    232    }
    233 
    234    /// Parse an `<angle>` allowing unitless zero to represent a zero angle.
    235    ///
    236    /// See the comment in `AllowUnitlessZeroAngle` for why.
    237    #[inline]
    238    pub fn parse_with_unitless<'i, 't>(
    239        context: &ParserContext,
    240        input: &mut Parser<'i, 't>,
    241    ) -> Result<Self, ParseError<'i>> {
    242        Self::parse_internal(context, input, AllowUnitlessZeroAngle::Yes)
    243    }
    244 
    245    pub(super) fn parse_internal<'i, 't>(
    246        context: &ParserContext,
    247        input: &mut Parser<'i, 't>,
    248        allow_unitless_zero: AllowUnitlessZeroAngle,
    249    ) -> Result<Self, ParseError<'i>> {
    250        let location = input.current_source_location();
    251        let t = input.next()?;
    252        let allow_unitless_zero = matches!(allow_unitless_zero, AllowUnitlessZeroAngle::Yes);
    253        match *t {
    254            Token::Dimension {
    255                value, ref unit, ..
    256            } => {
    257                match Angle::parse_dimension(value, unit, /* from_calc = */ false) {
    258                    Ok(angle) => Ok(angle),
    259                    Err(()) => {
    260                        let t = t.clone();
    261                        Err(input.new_unexpected_token_error(t))
    262                    },
    263                }
    264            },
    265            Token::Function(ref name) => {
    266                let function = CalcNode::math_function(context, name, location)?;
    267                CalcNode::parse_angle(context, input, function)
    268            },
    269            Token::Number { value, .. } if value == 0. && allow_unitless_zero => Ok(Angle::zero()),
    270            ref t => {
    271                let t = t.clone();
    272                Err(input.new_unexpected_token_error(t))
    273            },
    274        }
    275    }
    276 }
    277 
    278 impl SpecifiedValueInfo for Angle {}
    279 
    280 impl Neg for Angle {
    281    type Output = Angle;
    282 
    283    #[inline]
    284    fn neg(self) -> Angle {
    285        let value = match self.value {
    286            AngleDimension::Deg(v) => AngleDimension::Deg(-v),
    287            AngleDimension::Rad(v) => AngleDimension::Rad(-v),
    288            AngleDimension::Turn(v) => AngleDimension::Turn(-v),
    289            AngleDimension::Grad(v) => AngleDimension::Grad(-v),
    290        };
    291        Angle {
    292            value,
    293            was_calc: self.was_calc,
    294        }
    295    }
    296 }