1use alloc::string::{String, ToString};
4use core::{fmt, num::ParseFloatError};
5use crate::corety::AzString;
6
7use crate::props::basic::error::ParseFloatErrorWithInput;
8
9use crate::props::{basic::length::FloatValue, formatter::PrintAsCssValue};
10
11#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
13#[repr(C)]
14#[derive(Default)]
15pub enum AngleMetric {
16 #[default]
17 Degree,
18 Radians,
19 Grad,
20 Turn,
21 Percent,
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]
73 pub const fn zero() -> Self {
74 const ZERO_DEG: AngleValue = AngleValue::const_deg(0);
75 ZERO_DEG
76 }
77
78 #[inline]
80 pub const fn const_deg(value: isize) -> Self {
81 Self::const_from_metric(AngleMetric::Degree, value)
82 }
83
84 #[inline]
86 pub const fn const_rad(value: isize) -> Self {
87 Self::const_from_metric(AngleMetric::Radians, value)
88 }
89
90 #[inline]
92 pub const fn const_grad(value: isize) -> Self {
93 Self::const_from_metric(AngleMetric::Grad, value)
94 }
95
96 #[inline]
98 pub const fn const_turn(value: isize) -> Self {
99 Self::const_from_metric(AngleMetric::Turn, value)
100 }
101
102 #[inline]
104 pub const fn const_percent(value: isize) -> Self {
105 Self::const_from_metric(AngleMetric::Percent, value)
106 }
107
108 #[inline]
110 pub const fn const_from_metric(metric: AngleMetric, value: isize) -> Self {
111 Self {
112 metric,
113 number: FloatValue::const_new(value),
114 }
115 }
116
117 #[inline]
124 pub const fn const_from_metric_fractional(metric: AngleMetric, pre_comma: isize, post_comma: isize) -> Self {
125 Self {
126 metric,
127 number: FloatValue::const_new_fractional(pre_comma, post_comma),
128 }
129 }
130
131 #[inline]
133 pub fn deg(value: f32) -> Self {
134 Self::from_metric(AngleMetric::Degree, value)
135 }
136
137 #[inline]
139 pub fn rad(value: f32) -> Self {
140 Self::from_metric(AngleMetric::Radians, value)
141 }
142
143 #[inline]
145 pub fn grad(value: f32) -> Self {
146 Self::from_metric(AngleMetric::Grad, value)
147 }
148
149 #[inline]
151 pub fn turn(value: f32) -> Self {
152 Self::from_metric(AngleMetric::Turn, value)
153 }
154
155 #[inline]
157 pub fn percent(value: f32) -> Self {
158 Self::from_metric(AngleMetric::Percent, value)
159 }
160
161 #[inline]
163 pub fn from_metric(metric: AngleMetric, value: f32) -> Self {
164 Self {
165 metric,
166 number: FloatValue::new(value),
167 }
168 }
169
170 #[inline]
174 pub fn to_degrees(&self) -> f32 {
175 let mut val = self.to_degrees_raw() % 360.0;
176 if val < 0.0 {
177 val += 360.0;
178 }
179 val
180 }
181
182 #[inline]
185 pub fn to_degrees_raw(&self) -> f32 {
186 match self.metric {
187 AngleMetric::Degree => self.number.get(),
188 AngleMetric::Grad => self.number.get() / 400.0 * 360.0,
189 AngleMetric::Radians => self.number.get() * 180.0 / core::f32::consts::PI,
190 AngleMetric::Turn => self.number.get() * 360.0,
191 AngleMetric::Percent => self.number.get() / 100.0 * 360.0,
192 }
193 }
194}
195
196#[derive(Clone, PartialEq)]
200pub enum CssAngleValueParseError<'a> {
201 EmptyString,
202 NoValueGiven(&'a str, AngleMetric),
203 ValueParseErr(ParseFloatError, &'a str),
204 InvalidAngle(&'a str),
205}
206
207impl_debug_as_display!(CssAngleValueParseError<'a>);
208impl_display! { CssAngleValueParseError<'a>, {
209 EmptyString => format!("Missing [rad / deg / turn / %] value"),
210 NoValueGiven(input, metric) => format!("Expected floating-point angle value, got: \"{}{}\"", input, metric),
211 ValueParseErr(err, number_str) => format!("Could not parse \"{}\" as floating-point value: \"{}\"", number_str, err),
212 InvalidAngle(s) => format!("Invalid angle value: \"{}\"", s),
213}}
214
215#[derive(Debug, Clone, PartialEq)]
217#[repr(C)]
218pub struct AngleNoValueGivenError {
219 pub value: AzString,
220 pub metric: AngleMetric,
221}
222
223#[derive(Debug, Clone, PartialEq)]
225#[repr(C, u8)]
226pub enum CssAngleValueParseErrorOwned {
227 EmptyString,
228 NoValueGiven(AngleNoValueGivenError),
229 ValueParseErr(ParseFloatErrorWithInput),
230 InvalidAngle(AzString),
231}
232
233impl<'a> CssAngleValueParseError<'a> {
234 pub fn to_contained(&self) -> CssAngleValueParseErrorOwned {
235 match self {
236 CssAngleValueParseError::EmptyString => CssAngleValueParseErrorOwned::EmptyString,
237 CssAngleValueParseError::NoValueGiven(s, metric) => {
238 CssAngleValueParseErrorOwned::NoValueGiven(AngleNoValueGivenError { value: s.to_string().into(), metric: *metric })
239 }
240 CssAngleValueParseError::ValueParseErr(err, s) => {
241 CssAngleValueParseErrorOwned::ValueParseErr(ParseFloatErrorWithInput { error: err.clone().into(), input: s.to_string().into() })
242 }
243 CssAngleValueParseError::InvalidAngle(s) => {
244 CssAngleValueParseErrorOwned::InvalidAngle(s.to_string().into())
245 }
246 }
247 }
248}
249
250impl CssAngleValueParseErrorOwned {
251 pub fn to_shared<'a>(&'a self) -> CssAngleValueParseError<'a> {
252 match self {
253 CssAngleValueParseErrorOwned::EmptyString => CssAngleValueParseError::EmptyString,
254 CssAngleValueParseErrorOwned::NoValueGiven(e) => {
255 CssAngleValueParseError::NoValueGiven(e.value.as_str(), e.metric)
256 }
257 CssAngleValueParseErrorOwned::ValueParseErr(e) => {
258 CssAngleValueParseError::ValueParseErr(e.error.to_std(), e.input.as_str())
259 }
260 CssAngleValueParseErrorOwned::InvalidAngle(s) => {
261 CssAngleValueParseError::InvalidAngle(s.as_str())
262 }
263 }
264 }
265}
266
267#[cfg(feature = "parser")]
270pub fn parse_angle_value<'a>(input: &'a str) -> Result<AngleValue, CssAngleValueParseError<'a>> {
271 let input = input.trim();
272
273 if input.is_empty() {
274 return Err(CssAngleValueParseError::EmptyString);
275 }
276
277 let match_values = &[
278 ("deg", AngleMetric::Degree),
279 ("turn", AngleMetric::Turn),
280 ("grad", AngleMetric::Grad),
281 ("rad", AngleMetric::Radians),
282 ("%", AngleMetric::Percent),
283 ];
284
285 for (match_val, metric) in match_values {
286 if input.ends_with(match_val) {
287 let value = &input[..input.len() - match_val.len()];
288 let value = value.trim();
289 if value.is_empty() {
290 return Err(CssAngleValueParseError::NoValueGiven(input, *metric));
291 }
292 match value.parse::<f32>() {
293 Ok(o) => return Ok(AngleValue::from_metric(*metric, o)),
294 Err(e) => return Err(CssAngleValueParseError::ValueParseErr(e, value)),
295 }
296 }
297 }
298
299 match input.parse::<f32>() {
300 Ok(o) => Ok(AngleValue::from_metric(AngleMetric::Degree, o)), Err(_) => Err(CssAngleValueParseError::InvalidAngle(input)),
302 }
303}
304
305#[cfg(all(test, feature = "parser"))]
306mod tests {
307 use super::*;
308
309 #[test]
310 fn test_parse_angle_value_deg() {
311 assert_eq!(parse_angle_value("90deg").unwrap(), AngleValue::deg(90.0));
312 assert_eq!(
313 parse_angle_value("-45.5deg").unwrap(),
314 AngleValue::deg(-45.5)
315 );
316 assert_eq!(parse_angle_value("180").unwrap(), AngleValue::deg(180.0));
318 }
319
320 #[test]
321 fn test_parse_angle_value_rad() {
322 assert_eq!(parse_angle_value("1.57rad").unwrap(), AngleValue::rad(1.57));
323 assert_eq!(
324 parse_angle_value(" -3.14rad ").unwrap(),
325 AngleValue::rad(-3.14)
326 );
327 }
328
329 #[test]
330 fn test_parse_angle_value_grad() {
331 assert_eq!(
332 parse_angle_value("100grad").unwrap(),
333 AngleValue::grad(100.0)
334 );
335 assert_eq!(
336 parse_angle_value("400grad").unwrap(),
337 AngleValue::grad(400.0)
338 );
339 }
340
341 #[test]
342 fn test_parse_angle_value_turn() {
343 assert_eq!(
344 parse_angle_value("0.25turn").unwrap(),
345 AngleValue::turn(0.25)
346 );
347 assert_eq!(parse_angle_value("1turn").unwrap(), AngleValue::turn(1.0));
348 }
349
350 #[test]
351 fn test_parse_angle_value_percent() {
352 assert_eq!(parse_angle_value("50%").unwrap(), AngleValue::percent(50.0));
353 }
354
355 #[test]
356 fn test_parse_angle_value_errors() {
357 assert!(parse_angle_value("").is_err());
358 assert!(parse_angle_value("deg").is_err());
359 assert!(parse_angle_value("90 degs").is_err());
360 assert!(parse_angle_value("ninety-deg").is_err());
361 assert!(parse_angle_value("1.57 rads").is_err());
362 }
363
364 #[test]
365 fn test_to_degrees_conversion() {
366 assert_eq!(AngleValue::deg(90.0).to_degrees(), 90.0);
367 assert!((AngleValue::rad(core::f32::consts::PI).to_degrees() - 180.0).abs() < 0.1);
369 assert_eq!(AngleValue::grad(100.0).to_degrees(), 90.0);
370 assert_eq!(AngleValue::turn(0.5).to_degrees(), 180.0);
371 assert_eq!(AngleValue::deg(-90.0).to_degrees(), 270.0);
372 assert_eq!(AngleValue::deg(450.0).to_degrees(), 90.0);
373 }
374}