//! CSS angle values. use super::calc::Calc; use super::length::serialize_dimension; use super::number::CSSNumber; use super::percentage::DimensionPercentage; use crate::error::{ParserError, PrinterError}; use crate::printer::Printer; use crate::traits::{ impl_op, private::{AddInternal, TryAdd}, Map, Op, Parse, Sign, ToCss, Zero, }; #[cfg(feature = "visitor")] use crate::visitor::Visit; use cssparser::*; use std::f32::consts::PI; /// A CSS [``](https://www.w3.org/TR/css-values-4/#angles) value. /// /// Angles may be explicit or computed by `calc()`, but are always stored and serialized /// as their computed value. #[derive(Debug, Clone)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr(feature = "visitor", visit(visit_angle, ANGLES))] #[cfg_attr( feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(tag = "type", content = "value", rename_all = "kebab-case") )] #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] pub enum Angle { /// An angle in degrees. There are 360 degrees in a full circle. Deg(CSSNumber), /// An angle in radians. There are 2π radians in a full circle. Rad(CSSNumber), /// An angle in gradians. There are 400 gradians in a full circle. Grad(CSSNumber), /// An angle in turns. There is 1 turn in a full circle. Turn(CSSNumber), } impl<'i> Parse<'i> for Angle { fn parse<'t>(input: &mut Parser<'i, 't>) -> Result>> { Self::parse_internal(input, false) } } impl Angle { /// Parses an angle, allowing unitless zero values. pub fn parse_with_unitless_zero<'i, 't>( input: &mut Parser<'i, 't>, ) -> Result>> { Self::parse_internal(input, true) } fn parse_internal<'i, 't>( input: &mut Parser<'i, 't>, allow_unitless_zero: bool, ) -> Result>> { match input.try_parse(Calc::parse) { Ok(Calc::Value(v)) => return Ok(*v), // Angles are always compatible, so they will always compute to a value. Ok(_) => return Err(input.new_custom_error(ParserError::InvalidValue)), _ => {} } let location = input.current_source_location(); let token = input.next()?; match *token { Token::Dimension { value, ref unit, .. } => { match_ignore_ascii_case! { unit, "deg" => Ok(Angle::Deg(value)), "grad" => Ok(Angle::Grad(value)), "turn" => Ok(Angle::Turn(value)), "rad" => Ok(Angle::Rad(value)), _ => return Err(location.new_unexpected_token_error(token.clone())), } } Token::Number { value, .. } if value == 0.0 && allow_unitless_zero => Ok(Angle::zero()), ref token => return Err(location.new_unexpected_token_error(token.clone())), } } } impl<'i> TryFrom<&Token<'i>> for Angle { type Error = (); fn try_from(token: &Token) -> Result { match token { Token::Dimension { value, ref unit, .. } => match_ignore_ascii_case! { unit, "deg" => Ok(Angle::Deg(*value)), "grad" => Ok(Angle::Grad(*value)), "turn" => Ok(Angle::Turn(*value)), "rad" => Ok(Angle::Rad(*value)), _ => Err(()), }, _ => Err(()), } } } impl ToCss for Angle { fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> where W: std::fmt::Write, { let (value, unit) = match self { Angle::Deg(val) => (*val, "deg"), Angle::Grad(val) => (*val, "grad"), Angle::Rad(val) => { let deg = self.to_degrees(); // We print 5 digits of precision by default. // Switch to degrees if there are an even number of them. if (deg * 100000.0).round().fract() == 0.0 { (deg, "deg") } else { (*val, "rad") } } Angle::Turn(val) => (*val, "turn"), }; serialize_dimension(value, unit, dest) } } impl Angle { /// Prints the angle, allowing unitless zero values. pub fn to_css_with_unitless_zero(&self, dest: &mut Printer) -> Result<(), PrinterError> where W: std::fmt::Write, { if self.is_zero() { (0.0).to_css(dest) } else { self.to_css(dest) } } } impl Angle { /// Returns the angle in radians. pub fn to_radians(&self) -> CSSNumber { const RAD_PER_DEG: f32 = PI / 180.0; match self { Angle::Deg(deg) => deg * RAD_PER_DEG, Angle::Rad(rad) => *rad, Angle::Grad(grad) => grad * 180.0 / 200.0 * RAD_PER_DEG, Angle::Turn(turn) => turn * 360.0 * RAD_PER_DEG, } } /// Returns the angle in degrees. pub fn to_degrees(&self) -> CSSNumber { const DEG_PER_RAD: f32 = 180.0 / PI; match self { Angle::Deg(deg) => *deg, Angle::Rad(rad) => rad * DEG_PER_RAD, Angle::Grad(grad) => grad * 180.0 / 200.0, Angle::Turn(turn) => turn * 360.0, } } } impl Zero for Angle { fn is_zero(&self) -> bool { use Angle::*; match self { Deg(v) | Rad(v) | Grad(v) | Turn(v) => *v == 0.0, } } fn zero() -> Self { Angle::Deg(0.0) } } impl Into> for Angle { fn into(self) -> Calc { Calc::Value(Box::new(self)) } } impl TryFrom> for Angle { type Error = (); fn try_from(calc: Calc) -> Result { match calc { Calc::Value(v) => Ok(*v), _ => Err(()), } } } impl std::ops::Mul for Angle { type Output = Self; fn mul(self, other: CSSNumber) -> Angle { match self { Angle::Deg(v) => Angle::Deg(v * other), Angle::Rad(v) => Angle::Rad(v * other), Angle::Grad(v) => Angle::Grad(v * other), Angle::Turn(v) => Angle::Turn(v * other), } } } impl AddInternal for Angle { fn add(self, other: Self) -> Self { self + other } } impl TryAdd for Angle { fn try_add(&self, other: &Angle) -> Option { Some(Angle::Deg(self.to_degrees() + other.to_degrees())) } } impl std::cmp::PartialEq for Angle { fn eq(&self, other: &Angle) -> bool { self.to_degrees() == other.to_degrees() } } impl std::cmp::PartialOrd for Angle { fn partial_cmp(&self, other: &Angle) -> Option { self.to_degrees().partial_cmp(&other.to_degrees()) } } impl Op for Angle { fn op f32>(&self, other: &Self, op: F) -> Self { match (self, other) { (Angle::Deg(a), Angle::Deg(b)) => Angle::Deg(op(*a, *b)), (Angle::Rad(a), Angle::Rad(b)) => Angle::Rad(op(*a, *b)), (Angle::Grad(a), Angle::Grad(b)) => Angle::Grad(op(*a, *b)), (Angle::Turn(a), Angle::Turn(b)) => Angle::Turn(op(*a, *b)), (a, b) => Angle::Deg(op(a.to_degrees(), b.to_degrees())), } } fn op_to T>(&self, other: &Self, op: F) -> T { match (self, other) { (Angle::Deg(a), Angle::Deg(b)) => op(*a, *b), (Angle::Rad(a), Angle::Rad(b)) => op(*a, *b), (Angle::Grad(a), Angle::Grad(b)) => op(*a, *b), (Angle::Turn(a), Angle::Turn(b)) => op(*a, *b), (a, b) => op(a.to_degrees(), b.to_degrees()), } } } impl Map for Angle { fn map f32>(&self, op: F) -> Self { match self { Angle::Deg(deg) => Angle::Deg(op(*deg)), Angle::Rad(rad) => Angle::Rad(op(*rad)), Angle::Grad(grad) => Angle::Grad(op(*grad)), Angle::Turn(turn) => Angle::Turn(op(*turn)), } } } impl Sign for Angle { fn sign(&self) -> f32 { match self { Angle::Deg(v) | Angle::Rad(v) | Angle::Grad(v) | Angle::Turn(v) => v.sign(), } } } impl_op!(Angle, std::ops::Rem, rem); impl_op!(Angle, std::ops::Add, add); /// A CSS [``](https://www.w3.org/TR/css-values-4/#typedef-angle-percentage) value. /// May be specified as either an angle or a percentage that resolves to an angle. pub type AnglePercentage = DimensionPercentage; macro_rules! impl_try_from_angle { ($t: ty) => { impl TryFrom for $t { type Error = (); fn try_from(_: crate::values::angle::Angle) -> Result { Err(()) } } impl TryInto for $t { type Error = (); fn try_into(self) -> Result { Err(()) } } }; } pub(crate) use impl_try_from_angle;