1use 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#[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 Deg(CSSNumber),
36 Rad(CSSNumber),
38 Grad(CSSNumber),
40 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 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 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 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 let value = if value == 0.0 { 0.0 } else { value };
126
127 serialize_dimension(value, unit, dest)
128 }
129}
130
131impl Angle {
132 pub fn to_css_with_unitless_zero<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
134 where
135 W: std::fmt::Write,
136 {
137 if self.is_zero() {
138 (0.0).to_css(dest)
139 } else {
140 self.to_css(dest)
141 }
142 }
143}
144
145impl Angle {
146 pub fn to_radians(&self) -> CSSNumber {
148 const RAD_PER_DEG: f32 = PI / 180.0;
149 match self {
150 Angle::Deg(deg) => deg * RAD_PER_DEG,
151 Angle::Rad(rad) => *rad,
152 Angle::Grad(grad) => grad * 180.0 / 200.0 * RAD_PER_DEG,
153 Angle::Turn(turn) => turn * 360.0 * RAD_PER_DEG,
154 }
155 }
156
157 pub fn to_degrees(&self) -> CSSNumber {
159 const DEG_PER_RAD: f32 = 180.0 / PI;
160 match self {
161 Angle::Deg(deg) => *deg,
162 Angle::Rad(rad) => rad * DEG_PER_RAD,
163 Angle::Grad(grad) => grad * 180.0 / 200.0,
164 Angle::Turn(turn) => turn * 360.0,
165 }
166 }
167}
168
169impl Zero for Angle {
170 fn is_zero(&self) -> bool {
171 use Angle::*;
172 match self {
173 Deg(v) | Rad(v) | Grad(v) | Turn(v) => *v == 0.0,
174 }
175 }
176
177 fn zero() -> Self {
178 Angle::Deg(0.0)
179 }
180}
181
182impl Into<Calc<Angle>> for Angle {
183 fn into(self) -> Calc<Angle> {
184 Calc::Value(Box::new(self))
185 }
186}
187
188impl TryFrom<Calc<Angle>> for Angle {
189 type Error = ();
190
191 fn try_from(calc: Calc<Angle>) -> Result<Angle, ()> {
192 match calc {
193 Calc::Value(v) => Ok(*v),
194 _ => Err(()),
195 }
196 }
197}
198
199impl std::ops::Mul<CSSNumber> for Angle {
200 type Output = Self;
201
202 fn mul(self, other: CSSNumber) -> Angle {
203 match self {
204 Angle::Deg(v) => Angle::Deg(v * other),
205 Angle::Rad(v) => Angle::Rad(v * other),
206 Angle::Grad(v) => Angle::Grad(v * other),
207 Angle::Turn(v) => Angle::Turn(v * other),
208 }
209 }
210}
211
212impl AddInternal for Angle {
213 fn add(self, other: Self) -> Self {
214 self + other
215 }
216}
217
218impl TryAdd<Angle> for Angle {
219 fn try_add(&self, other: &Angle) -> Option<Angle> {
220 Some(Angle::Deg(self.to_degrees() + other.to_degrees()))
221 }
222}
223
224impl std::cmp::PartialEq<Angle> for Angle {
225 fn eq(&self, other: &Angle) -> bool {
226 self.to_degrees() == other.to_degrees()
227 }
228}
229
230impl std::cmp::PartialOrd<Angle> for Angle {
231 fn partial_cmp(&self, other: &Angle) -> Option<std::cmp::Ordering> {
232 self.to_degrees().partial_cmp(&other.to_degrees())
233 }
234}
235
236impl Op for Angle {
237 fn op<F: FnOnce(f32, f32) -> f32>(&self, other: &Self, op: F) -> Self {
238 match (self, other) {
239 (Angle::Deg(a), Angle::Deg(b)) => Angle::Deg(op(*a, *b)),
240 (Angle::Rad(a), Angle::Rad(b)) => Angle::Rad(op(*a, *b)),
241 (Angle::Grad(a), Angle::Grad(b)) => Angle::Grad(op(*a, *b)),
242 (Angle::Turn(a), Angle::Turn(b)) => Angle::Turn(op(*a, *b)),
243 (a, b) => Angle::Deg(op(a.to_degrees(), b.to_degrees())),
244 }
245 }
246
247 fn op_to<T, F: FnOnce(f32, f32) -> T>(&self, other: &Self, op: F) -> T {
248 match (self, other) {
249 (Angle::Deg(a), Angle::Deg(b)) => op(*a, *b),
250 (Angle::Rad(a), Angle::Rad(b)) => op(*a, *b),
251 (Angle::Grad(a), Angle::Grad(b)) => op(*a, *b),
252 (Angle::Turn(a), Angle::Turn(b)) => op(*a, *b),
253 (a, b) => op(a.to_degrees(), b.to_degrees()),
254 }
255 }
256}
257
258impl Map for Angle {
259 fn map<F: FnOnce(f32) -> f32>(&self, op: F) -> Self {
260 match self {
261 Angle::Deg(deg) => Angle::Deg(op(*deg)),
262 Angle::Rad(rad) => Angle::Rad(op(*rad)),
263 Angle::Grad(grad) => Angle::Grad(op(*grad)),
264 Angle::Turn(turn) => Angle::Turn(op(*turn)),
265 }
266 }
267}
268
269impl Sign for Angle {
270 fn sign(&self) -> f32 {
271 match self {
272 Angle::Deg(v) | Angle::Rad(v) | Angle::Grad(v) | Angle::Turn(v) => v.sign(),
273 }
274 }
275}
276
277impl_op!(Angle, std::ops::Rem, rem);
278impl_op!(Angle, std::ops::Add, add);
279
280pub type AnglePercentage = DimensionPercentage<Angle>;
283
284macro_rules! impl_try_from_angle {
285 ($t: ty) => {
286 impl TryFrom<crate::values::angle::Angle> for $t {
287 type Error = ();
288 fn try_from(_: crate::values::angle::Angle) -> Result<Self, Self::Error> {
289 Err(())
290 }
291 }
292
293 impl TryInto<crate::values::angle::Angle> for $t {
294 type Error = ();
295 fn try_into(self) -> Result<crate::values::angle::Angle, Self::Error> {
296 Err(())
297 }
298 }
299 };
300}
301
302pub(crate) use impl_try_from_angle;