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