1use alloc::string::{String, ToString};
4use core::{fmt, num::ParseFloatError};
5
6use crate::props::{basic::length::FloatValue, formatter::PrintAsCssValue};
7
8#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
10#[repr(C)]
11pub enum AngleMetric {
12 Degree,
13 Radians,
14 Grad,
15 Turn,
16 Percent,
17}
18
19impl Default for AngleMetric {
20 fn default() -> AngleMetric {
21 AngleMetric::Degree
22 }
23}
24
25impl fmt::Display for AngleMetric {
26 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
27 use self::AngleMetric::*;
28 match self {
29 Degree => write!(f, "deg"),
30 Radians => write!(f, "rad"),
31 Grad => write!(f, "grad"),
32 Turn => write!(f, "turn"),
33 Percent => write!(f, "%"),
34 }
35 }
36}
37
38#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
40#[repr(C)]
41pub struct AngleValue {
42 pub metric: AngleMetric,
43 pub number: FloatValue,
44}
45
46impl_option!(
47 AngleValue,
48 OptionAngleValue,
49 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
50);
51
52impl fmt::Debug for AngleValue {
53 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
54 write!(f, "{}", self)
55 }
56}
57
58impl fmt::Display for AngleValue {
59 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
60 write!(f, "{}{}", self.number, self.metric)
61 }
62}
63
64impl PrintAsCssValue for AngleValue {
65 fn print_as_css_value(&self) -> String {
66 format!("{}", self)
67 }
68}
69
70impl AngleValue {
71 #[inline]
72 pub const fn zero() -> Self {
73 const ZERO_DEG: AngleValue = AngleValue::const_deg(0);
74 ZERO_DEG
75 }
76
77 #[inline]
78 pub const fn const_deg(value: isize) -> Self {
79 Self::const_from_metric(AngleMetric::Degree, value)
80 }
81
82 #[inline]
83 pub const fn const_rad(value: isize) -> Self {
84 Self::const_from_metric(AngleMetric::Radians, value)
85 }
86
87 #[inline]
88 pub const fn const_grad(value: isize) -> Self {
89 Self::const_from_metric(AngleMetric::Grad, value)
90 }
91
92 #[inline]
93 pub const fn const_turn(value: isize) -> Self {
94 Self::const_from_metric(AngleMetric::Turn, value)
95 }
96
97 #[inline]
98 pub fn const_percent(value: isize) -> Self {
99 Self::const_from_metric(AngleMetric::Percent, value)
100 }
101
102 #[inline]
103 pub const fn const_from_metric(metric: AngleMetric, value: isize) -> Self {
104 Self {
105 metric,
106 number: FloatValue::const_new(value),
107 }
108 }
109
110 #[inline]
111 pub fn deg(value: f32) -> Self {
112 Self::from_metric(AngleMetric::Degree, value)
113 }
114
115 #[inline]
116 pub fn rad(value: f32) -> Self {
117 Self::from_metric(AngleMetric::Radians, value)
118 }
119
120 #[inline]
121 pub fn grad(value: f32) -> Self {
122 Self::from_metric(AngleMetric::Grad, value)
123 }
124
125 #[inline]
126 pub fn turn(value: f32) -> Self {
127 Self::from_metric(AngleMetric::Turn, value)
128 }
129
130 #[inline]
131 pub fn percent(value: f32) -> Self {
132 Self::from_metric(AngleMetric::Percent, value)
133 }
134
135 #[inline]
136 pub fn from_metric(metric: AngleMetric, value: f32) -> Self {
137 Self {
138 metric,
139 number: FloatValue::new(value),
140 }
141 }
142
143 #[inline]
147 pub fn to_degrees(&self) -> f32 {
148 let val = match self.metric {
149 AngleMetric::Degree => self.number.get(),
150 AngleMetric::Grad => self.number.get() / 400.0 * 360.0,
151 AngleMetric::Radians => self.number.get() * 180.0 / core::f32::consts::PI,
152 AngleMetric::Turn => self.number.get() * 360.0,
153 AngleMetric::Percent => self.number.get() / 100.0 * 360.0,
154 };
155
156 let mut val = val % 360.0;
157 if val < 0.0 {
158 val = 360.0 + val;
159 }
160 val
161 }
162
163 #[inline]
166 pub fn to_degrees_raw(&self) -> f32 {
167 match self.metric {
168 AngleMetric::Degree => self.number.get(),
169 AngleMetric::Grad => self.number.get() / 400.0 * 360.0,
170 AngleMetric::Radians => self.number.get() * 180.0 / core::f32::consts::PI,
171 AngleMetric::Turn => self.number.get() * 360.0,
172 AngleMetric::Percent => self.number.get() / 100.0 * 360.0,
173 }
174 }
175}
176
177#[derive(Clone, PartialEq)]
180pub enum CssAngleValueParseError<'a> {
181 EmptyString,
182 NoValueGiven(&'a str, AngleMetric),
183 ValueParseErr(ParseFloatError, &'a str),
184 InvalidAngle(&'a str),
185}
186
187impl_debug_as_display!(CssAngleValueParseError<'a>);
188impl_display! { CssAngleValueParseError<'a>, {
189 EmptyString => format!("Missing [rad / deg / turn / %] value"),
190 NoValueGiven(input, metric) => format!("Expected floating-point angle value, got: \"{}{}\"", input, metric),
191 ValueParseErr(err, number_str) => format!("Could not parse \"{}\" as floating-point value: \"{}\"", number_str, err),
192 InvalidAngle(s) => format!("Invalid angle value: \"{}\"", s),
193}}
194
195#[derive(Debug, Clone, PartialEq)]
196pub enum CssAngleValueParseErrorOwned {
197 EmptyString,
198 NoValueGiven(String, AngleMetric),
199 ValueParseErr(ParseFloatError, String),
200 InvalidAngle(String),
201}
202
203impl<'a> CssAngleValueParseError<'a> {
204 pub fn to_contained(&self) -> CssAngleValueParseErrorOwned {
205 match self {
206 CssAngleValueParseError::EmptyString => CssAngleValueParseErrorOwned::EmptyString,
207 CssAngleValueParseError::NoValueGiven(s, metric) => {
208 CssAngleValueParseErrorOwned::NoValueGiven(s.to_string(), *metric)
209 }
210 CssAngleValueParseError::ValueParseErr(err, s) => {
211 CssAngleValueParseErrorOwned::ValueParseErr(err.clone(), s.to_string())
212 }
213 CssAngleValueParseError::InvalidAngle(s) => {
214 CssAngleValueParseErrorOwned::InvalidAngle(s.to_string())
215 }
216 }
217 }
218}
219
220impl CssAngleValueParseErrorOwned {
221 pub fn to_shared<'a>(&'a self) -> CssAngleValueParseError<'a> {
222 match self {
223 CssAngleValueParseErrorOwned::EmptyString => CssAngleValueParseError::EmptyString,
224 CssAngleValueParseErrorOwned::NoValueGiven(s, metric) => {
225 CssAngleValueParseError::NoValueGiven(s.as_str(), *metric)
226 }
227 CssAngleValueParseErrorOwned::ValueParseErr(err, s) => {
228 CssAngleValueParseError::ValueParseErr(err.clone(), s.as_str())
229 }
230 CssAngleValueParseErrorOwned::InvalidAngle(s) => {
231 CssAngleValueParseError::InvalidAngle(s.as_str())
232 }
233 }
234 }
235}
236
237#[cfg(feature = "parser")]
238pub fn parse_angle_value<'a>(input: &'a str) -> Result<AngleValue, CssAngleValueParseError<'a>> {
239 let input = input.trim();
240
241 if input.is_empty() {
242 return Err(CssAngleValueParseError::EmptyString);
243 }
244
245 let match_values = &[
246 ("deg", AngleMetric::Degree),
247 ("turn", AngleMetric::Turn),
248 ("grad", AngleMetric::Grad),
249 ("rad", AngleMetric::Radians),
250 ("%", AngleMetric::Percent),
251 ];
252
253 for (match_val, metric) in match_values {
254 if input.ends_with(match_val) {
255 let value = &input[..input.len() - match_val.len()];
256 let value = value.trim();
257 if value.is_empty() {
258 return Err(CssAngleValueParseError::NoValueGiven(input, *metric));
259 }
260 match value.parse::<f32>() {
261 Ok(o) => return Ok(AngleValue::from_metric(*metric, o)),
262 Err(e) => return Err(CssAngleValueParseError::ValueParseErr(e, value)),
263 }
264 }
265 }
266
267 match input.parse::<f32>() {
268 Ok(o) => Ok(AngleValue::from_metric(AngleMetric::Degree, o)), Err(_) => Err(CssAngleValueParseError::InvalidAngle(input)),
270 }
271}
272
273#[cfg(all(test, feature = "parser"))]
274mod tests {
275 use super::*;
276
277 #[test]
278 fn test_parse_angle_value_deg() {
279 assert_eq!(parse_angle_value("90deg").unwrap(), AngleValue::deg(90.0));
280 assert_eq!(
281 parse_angle_value("-45.5deg").unwrap(),
282 AngleValue::deg(-45.5)
283 );
284 assert_eq!(parse_angle_value("180").unwrap(), AngleValue::deg(180.0));
286 }
287
288 #[test]
289 fn test_parse_angle_value_rad() {
290 assert_eq!(parse_angle_value("1.57rad").unwrap(), AngleValue::rad(1.57));
291 assert_eq!(
292 parse_angle_value(" -3.14rad ").unwrap(),
293 AngleValue::rad(-3.14)
294 );
295 }
296
297 #[test]
298 fn test_parse_angle_value_grad() {
299 assert_eq!(
300 parse_angle_value("100grad").unwrap(),
301 AngleValue::grad(100.0)
302 );
303 assert_eq!(
304 parse_angle_value("400grad").unwrap(),
305 AngleValue::grad(400.0)
306 );
307 }
308
309 #[test]
310 fn test_parse_angle_value_turn() {
311 assert_eq!(
312 parse_angle_value("0.25turn").unwrap(),
313 AngleValue::turn(0.25)
314 );
315 assert_eq!(parse_angle_value("1turn").unwrap(), AngleValue::turn(1.0));
316 }
317
318 #[test]
319 fn test_parse_angle_value_percent() {
320 assert_eq!(parse_angle_value("50%").unwrap(), AngleValue::percent(50.0));
321 }
322
323 #[test]
324 fn test_parse_angle_value_errors() {
325 assert!(parse_angle_value("").is_err());
326 assert!(parse_angle_value("deg").is_err());
327 assert!(parse_angle_value("90 degs").is_err());
328 assert!(parse_angle_value("ninety-deg").is_err());
329 assert!(parse_angle_value("1.57 rads").is_err());
330 }
331
332 #[test]
333 fn test_to_degrees_conversion() {
334 assert_eq!(AngleValue::deg(90.0).to_degrees(), 90.0);
335 assert!((AngleValue::rad(core::f32::consts::PI).to_degrees() - 180.0).abs() < 0.1);
337 assert_eq!(AngleValue::grad(100.0).to_degrees(), 90.0);
338 assert_eq!(AngleValue::turn(0.5).to_degrees(), 180.0);
339 assert_eq!(AngleValue::deg(-90.0).to_degrees(), 270.0);
340 assert_eq!(AngleValue::deg(450.0).to_degrees(), 90.0);
341 }
342}