lightningcss/values/
angle.rs

1//! CSS angle values.
2
3use super::calc::Calc;
4use super::length::serialize_dimension;
5use super::number::CSSNumber;
6use super::percentage::DimensionPercentage;
7use crate::error::{ParserError, PrinterError};
8use crate::printer::Printer;
9use crate::traits::{
10  impl_op,
11  private::{AddInternal, TryAdd},
12  Map, Op, Parse, Sign, ToCss, Zero,
13};
14#[cfg(feature = "visitor")]
15use crate::visitor::Visit;
16use cssparser::*;
17use std::f32::consts::PI;
18
19/// A CSS [`<angle>`](https://www.w3.org/TR/css-values-4/#angles) value.
20///
21/// Angles may be explicit or computed by `calc()`, but are always stored and serialized
22/// as their computed value.
23#[derive(Debug, Clone)]
24#[cfg_attr(feature = "visitor", derive(Visit))]
25#[cfg_attr(feature = "visitor", visit(visit_angle, ANGLES))]
26#[cfg_attr(
27  feature = "serde",
28  derive(serde::Serialize, serde::Deserialize),
29  serde(tag = "type", content = "value", rename_all = "kebab-case")
30)]
31#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
32#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
33pub enum Angle {
34  /// An angle in degrees. There are 360 degrees in a full circle.
35  Deg(CSSNumber),
36  /// An angle in radians. There are 2π radians in a full circle.
37  Rad(CSSNumber),
38  /// An angle in gradians. There are 400 gradians in a full circle.
39  Grad(CSSNumber),
40  /// An angle in turns. There is 1 turn in a full circle.
41  Turn(CSSNumber),
42}
43
44impl<'i> Parse<'i> for Angle {
45  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
46    Self::parse_internal(input, false)
47  }
48}
49
50impl Angle {
51  /// Parses an angle, allowing unitless zero values.
52  pub fn parse_with_unitless_zero<'i, 't>(
53    input: &mut Parser<'i, 't>,
54  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
55    Self::parse_internal(input, true)
56  }
57
58  fn parse_internal<'i, 't>(
59    input: &mut Parser<'i, 't>,
60    allow_unitless_zero: bool,
61  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
62    match input.try_parse(Calc::parse) {
63      Ok(Calc::Value(v)) => return Ok(*v),
64      // Angles are always compatible, so they will always compute to a value.
65      Ok(_) => return Err(input.new_custom_error(ParserError::InvalidValue)),
66      _ => {}
67    }
68
69    let location = input.current_source_location();
70    let token = input.next()?;
71    match *token {
72      Token::Dimension { value, ref unit, .. } => {
73        match_ignore_ascii_case! { unit,
74          "deg" => Ok(Angle::Deg(value)),
75          "grad" => Ok(Angle::Grad(value)),
76          "turn" => Ok(Angle::Turn(value)),
77          "rad" => Ok(Angle::Rad(value)),
78          _ => return Err(location.new_unexpected_token_error(token.clone())),
79        }
80      }
81      Token::Number { value, .. } if value == 0.0 && allow_unitless_zero => Ok(Angle::zero()),
82      ref token => return Err(location.new_unexpected_token_error(token.clone())),
83    }
84  }
85}
86
87impl<'i> TryFrom<&Token<'i>> for Angle {
88  type Error = ();
89
90  fn try_from(token: &Token) -> Result<Self, Self::Error> {
91    match token {
92      Token::Dimension { value, ref unit, .. } => match_ignore_ascii_case! { unit,
93        "deg" => Ok(Angle::Deg(*value)),
94        "grad" => Ok(Angle::Grad(*value)),
95        "turn" => Ok(Angle::Turn(*value)),
96        "rad" => Ok(Angle::Rad(*value)),
97        _ => Err(()),
98      },
99      _ => Err(()),
100    }
101  }
102}
103
104impl ToCss for Angle {
105  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
106  where
107    W: std::fmt::Write,
108  {
109    let (value, unit) = match self {
110      Angle::Deg(val) => (*val, "deg"),
111      Angle::Grad(val) => (*val, "grad"),
112      Angle::Rad(val) => {
113        let deg = self.to_degrees();
114        // We print 5 digits of precision by default.
115        // Switch to degrees if there are an even number of them.
116        if (deg * 100000.0).round().fract() == 0.0 {
117          (deg, "deg")
118        } else {
119          (*val, "rad")
120        }
121      }
122      Angle::Turn(val) => (*val, "turn"),
123    };
124
125    serialize_dimension(value, unit, dest)
126  }
127}
128
129impl Angle {
130  /// Prints the angle, allowing unitless zero values.
131  pub fn to_css_with_unitless_zero<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
132  where
133    W: std::fmt::Write,
134  {
135    if self.is_zero() {
136      (0.0).to_css(dest)
137    } else {
138      self.to_css(dest)
139    }
140  }
141}
142
143impl Angle {
144  /// Returns the angle in radians.
145  pub fn to_radians(&self) -> CSSNumber {
146    const RAD_PER_DEG: f32 = PI / 180.0;
147    match self {
148      Angle::Deg(deg) => deg * RAD_PER_DEG,
149      Angle::Rad(rad) => *rad,
150      Angle::Grad(grad) => grad * 180.0 / 200.0 * RAD_PER_DEG,
151      Angle::Turn(turn) => turn * 360.0 * RAD_PER_DEG,
152    }
153  }
154
155  /// Returns the angle in degrees.
156  pub fn to_degrees(&self) -> CSSNumber {
157    const DEG_PER_RAD: f32 = 180.0 / PI;
158    match self {
159      Angle::Deg(deg) => *deg,
160      Angle::Rad(rad) => rad * DEG_PER_RAD,
161      Angle::Grad(grad) => grad * 180.0 / 200.0,
162      Angle::Turn(turn) => turn * 360.0,
163    }
164  }
165}
166
167impl Zero for Angle {
168  fn is_zero(&self) -> bool {
169    use Angle::*;
170    match self {
171      Deg(v) | Rad(v) | Grad(v) | Turn(v) => *v == 0.0,
172    }
173  }
174
175  fn zero() -> Self {
176    Angle::Deg(0.0)
177  }
178}
179
180impl Into<Calc<Angle>> for Angle {
181  fn into(self) -> Calc<Angle> {
182    Calc::Value(Box::new(self))
183  }
184}
185
186impl From<Calc<Angle>> for Angle {
187  fn from(calc: Calc<Angle>) -> Angle {
188    match calc {
189      Calc::Value(v) => *v,
190      _ => unreachable!(),
191    }
192  }
193}
194
195impl std::ops::Mul<CSSNumber> for Angle {
196  type Output = Self;
197
198  fn mul(self, other: CSSNumber) -> Angle {
199    match self {
200      Angle::Deg(v) => Angle::Deg(v * other),
201      Angle::Rad(v) => Angle::Rad(v * other),
202      Angle::Grad(v) => Angle::Grad(v * other),
203      Angle::Turn(v) => Angle::Turn(v * other),
204    }
205  }
206}
207
208impl AddInternal for Angle {
209  fn add(self, other: Self) -> Self {
210    self + other
211  }
212}
213
214impl TryAdd<Angle> for Angle {
215  fn try_add(&self, other: &Angle) -> Option<Angle> {
216    Some(Angle::Deg(self.to_degrees() + other.to_degrees()))
217  }
218}
219
220impl std::cmp::PartialEq<Angle> for Angle {
221  fn eq(&self, other: &Angle) -> bool {
222    self.to_degrees() == other.to_degrees()
223  }
224}
225
226impl std::cmp::PartialOrd<Angle> for Angle {
227  fn partial_cmp(&self, other: &Angle) -> Option<std::cmp::Ordering> {
228    self.to_degrees().partial_cmp(&other.to_degrees())
229  }
230}
231
232impl Op for Angle {
233  fn op<F: FnOnce(f32, f32) -> f32>(&self, other: &Self, op: F) -> Self {
234    match (self, other) {
235      (Angle::Deg(a), Angle::Deg(b)) => Angle::Deg(op(*a, *b)),
236      (Angle::Rad(a), Angle::Rad(b)) => Angle::Rad(op(*a, *b)),
237      (Angle::Grad(a), Angle::Grad(b)) => Angle::Grad(op(*a, *b)),
238      (Angle::Turn(a), Angle::Turn(b)) => Angle::Turn(op(*a, *b)),
239      (a, b) => Angle::Deg(op(a.to_degrees(), b.to_degrees())),
240    }
241  }
242
243  fn op_to<T, F: FnOnce(f32, f32) -> T>(&self, other: &Self, op: F) -> T {
244    match (self, other) {
245      (Angle::Deg(a), Angle::Deg(b)) => op(*a, *b),
246      (Angle::Rad(a), Angle::Rad(b)) => op(*a, *b),
247      (Angle::Grad(a), Angle::Grad(b)) => op(*a, *b),
248      (Angle::Turn(a), Angle::Turn(b)) => op(*a, *b),
249      (a, b) => op(a.to_degrees(), b.to_degrees()),
250    }
251  }
252}
253
254impl Map for Angle {
255  fn map<F: FnOnce(f32) -> f32>(&self, op: F) -> Self {
256    match self {
257      Angle::Deg(deg) => Angle::Deg(op(*deg)),
258      Angle::Rad(rad) => Angle::Rad(op(*rad)),
259      Angle::Grad(grad) => Angle::Grad(op(*grad)),
260      Angle::Turn(turn) => Angle::Turn(op(*turn)),
261    }
262  }
263}
264
265impl Sign for Angle {
266  fn sign(&self) -> f32 {
267    match self {
268      Angle::Deg(v) | Angle::Rad(v) | Angle::Grad(v) | Angle::Turn(v) => v.sign(),
269    }
270  }
271}
272
273impl_op!(Angle, std::ops::Rem, rem);
274impl_op!(Angle, std::ops::Add, add);
275
276/// A CSS [`<angle-percentage>`](https://www.w3.org/TR/css-values-4/#typedef-angle-percentage) value.
277/// May be specified as either an angle or a percentage that resolves to an angle.
278pub type AnglePercentage = DimensionPercentage<Angle>;
279
280macro_rules! impl_try_from_angle {
281  ($t: ty) => {
282    impl TryFrom<crate::values::angle::Angle> for $t {
283      type Error = ();
284      fn try_from(_: crate::values::angle::Angle) -> Result<Self, Self::Error> {
285        Err(())
286      }
287    }
288  };
289}
290
291pub(crate) use impl_try_from_angle;