1use core::fmt;
2use std::num::ParseFloatError;
3
4use crate::corety::AzString;
5
6pub const FP_PRECISION_MULTIPLIER: f32 = 1000.0;
12pub const FP_PRECISION_MULTIPLIER_CONST: isize = FP_PRECISION_MULTIPLIER as isize;
13
14#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
17#[repr(C)]
18pub struct PercentageValue {
19 number: FloatValue,
20}
21
22impl_option!(
23 PercentageValue,
24 OptionPercentageValue,
25 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
26);
27
28impl fmt::Display for PercentageValue {
29 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
30 write!(f, "{}%", self.normalized() * 100.0)
31 }
32}
33
34impl PercentageValue {
35 #[inline]
38 pub const fn const_new(value: isize) -> Self {
39 Self {
40 number: FloatValue::const_new(value),
41 }
42 }
43
44 #[inline]
56 pub const fn const_new_fractional(pre_comma: isize, post_comma: isize) -> Self {
57 Self {
58 number: FloatValue::const_new_fractional(pre_comma, post_comma),
59 }
60 }
61
62 #[inline]
63 pub fn new(value: f32) -> Self {
64 Self {
65 number: value.into(),
66 }
67 }
68
69 #[inline]
72 pub fn normalized(&self) -> f32 {
73 self.number.get() / 100.0
74 }
75
76 #[inline]
77 pub fn interpolate(&self, other: &Self, t: f32) -> Self {
78 Self {
79 number: self.number.interpolate(&other.number, t),
80 }
81 }
82}
83
84#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
87#[repr(C)]
88pub struct FloatValue {
89 pub number: isize,
90}
91
92impl fmt::Display for FloatValue {
93 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
94 write!(f, "{}", self.get())
95 }
96}
97
98impl ::core::fmt::Debug for FloatValue {
99 fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
100 write!(f, "{}", self)
101 }
102}
103
104impl Default for FloatValue {
105 fn default() -> Self {
106 const DEFAULT_FLV: FloatValue = FloatValue::const_new(0);
107 DEFAULT_FLV
108 }
109}
110
111impl FloatValue {
112 #[inline]
115 pub const fn const_new(value: isize) -> Self {
116 Self {
117 number: value * FP_PRECISION_MULTIPLIER_CONST,
118 }
119 }
120
121 #[inline]
144 pub const fn const_new_fractional(pre_comma: isize, post_comma: isize) -> Self {
145 let abs_post = if post_comma < 0 {
147 -post_comma
148 } else {
149 post_comma
150 };
151
152 let (normalized_post, divisor) = if abs_post < 10 {
155 (abs_post, 10)
157 } else if abs_post < 100 {
158 (abs_post, 100)
160 } else if abs_post < 1000 {
161 (abs_post, 1000)
163 } else if abs_post < 10000 {
164 (abs_post / 10, 1000)
166 } else if abs_post < 100000 {
167 (abs_post / 100, 1000)
168 } else if abs_post < 1000000 {
169 (abs_post / 1000, 1000)
170 } else if abs_post < 10000000 {
171 (abs_post / 10000, 1000)
172 } else if abs_post < 100000000 {
173 (abs_post / 100000, 1000)
174 } else if abs_post < 1000000000 {
175 (abs_post / 1000000, 1000)
176 } else {
177 (abs_post / 10000000, 1000)
180 };
181
182 let fractional_part = normalized_post * (FP_PRECISION_MULTIPLIER_CONST / divisor);
184
185 let signed_fractional = if post_comma < 0 {
187 -fractional_part
188 } else {
189 fractional_part
190 };
191
192 let final_fractional = if pre_comma < 0 && post_comma >= 0 {
195 -signed_fractional
196 } else {
197 signed_fractional
198 };
199
200 Self {
201 number: pre_comma * FP_PRECISION_MULTIPLIER_CONST + final_fractional,
202 }
203 }
204
205 #[inline]
206 pub fn new(value: f32) -> Self {
207 Self {
208 number: (value * FP_PRECISION_MULTIPLIER) as isize,
209 }
210 }
211
212 #[inline]
213 pub fn get(&self) -> f32 {
214 self.number as f32 / FP_PRECISION_MULTIPLIER
215 }
216
217 #[inline]
218 pub fn interpolate(&self, other: &Self, t: f32) -> Self {
219 let self_val_f32 = self.get();
220 let other_val_f32 = other.get();
221 let interpolated = self_val_f32 + ((other_val_f32 - self_val_f32) * t);
222 Self::new(interpolated)
223 }
224}
225
226impl From<f32> for FloatValue {
227 #[inline]
228 fn from(val: f32) -> Self {
229 Self::new(val)
230 }
231}
232
233#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
235#[repr(C)]
236pub enum SizeMetric {
237 Px,
238 Pt,
239 Em,
240 Rem,
241 In,
242 Cm,
243 Mm,
244 Percent,
245 Vw,
247 Vh,
249 Vmin,
251 Vmax,
253}
254
255impl Default for SizeMetric {
256 fn default() -> Self {
257 SizeMetric::Px
258 }
259}
260
261impl fmt::Display for SizeMetric {
262 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
263 use self::SizeMetric::*;
264 match self {
265 Px => write!(f, "px"),
266 Pt => write!(f, "pt"),
267 Em => write!(f, "em"),
268 Rem => write!(f, "rem"),
269 In => write!(f, "in"),
270 Cm => write!(f, "cm"),
271 Mm => write!(f, "mm"),
272 Percent => write!(f, "%"),
273 Vw => write!(f, "vw"),
274 Vh => write!(f, "vh"),
275 Vmin => write!(f, "vmin"),
276 Vmax => write!(f, "vmax"),
277 }
278 }
279}
280
281pub fn parse_float_value(input: &str) -> Result<FloatValue, ParseFloatError> {
282 Ok(FloatValue::new(input.trim().parse::<f32>()?))
283}
284
285#[derive(Clone, PartialEq, Eq)]
286pub enum PercentageParseError {
287 ValueParseErr(ParseFloatError),
288 NoPercentSign,
289 InvalidUnit(AzString),
290}
291
292impl_debug_as_display!(PercentageParseError);
293impl_from!(ParseFloatError, PercentageParseError::ValueParseErr);
294
295impl_display! { PercentageParseError, {
296 ValueParseErr(e) => format!("\"{}\"", e),
297 NoPercentSign => format!("No percent sign after number"),
298 InvalidUnit(u) => format!("Error parsing percentage: invalid unit \"{}\"", u.as_str()),
299}}
300
301#[derive(Debug, Clone, PartialEq, Eq)]
302pub enum PercentageParseErrorOwned {
303 ValueParseErr(ParseFloatError),
304 NoPercentSign,
305 InvalidUnit(String),
306}
307
308impl PercentageParseError {
309 pub fn to_contained(&self) -> PercentageParseErrorOwned {
310 match self {
311 Self::ValueParseErr(e) => PercentageParseErrorOwned::ValueParseErr(e.clone()),
312 Self::NoPercentSign => PercentageParseErrorOwned::NoPercentSign,
313 Self::InvalidUnit(u) => PercentageParseErrorOwned::InvalidUnit(u.as_str().to_string()),
314 }
315 }
316}
317
318impl PercentageParseErrorOwned {
319 pub fn to_shared(&self) -> PercentageParseError {
320 match self {
321 Self::ValueParseErr(e) => PercentageParseError::ValueParseErr(e.clone()),
322 Self::NoPercentSign => PercentageParseError::NoPercentSign,
323 Self::InvalidUnit(u) => PercentageParseError::InvalidUnit(u.clone().into()),
324 }
325 }
326}
327
328pub fn parse_percentage_value(input: &str) -> Result<PercentageValue, PercentageParseError> {
330 let input = input.trim();
331
332 if input.is_empty() {
333 return Err(PercentageParseError::ValueParseErr(
334 "empty string".parse::<f32>().unwrap_err(),
335 ));
336 }
337
338 let mut split_pos = 0;
339 let mut found_numeric = false;
340 for (idx, ch) in input.char_indices() {
341 if ch.is_numeric() || ch == '.' || ch == '-' {
342 split_pos = idx;
343 found_numeric = true;
344 }
345 }
346
347 if !found_numeric {
348 return Err(PercentageParseError::ValueParseErr(
349 "no numeric value".parse::<f32>().unwrap_err(),
350 ));
351 }
352
353 split_pos += 1;
354
355 let unit = input[split_pos..].trim();
356 let mut number = input[..split_pos]
357 .trim()
358 .parse::<f32>()
359 .map_err(|e| PercentageParseError::ValueParseErr(e))?;
360
361 match unit {
362 "" => {
363 number *= 100.0;
364 } "%" => {} other => {
367 return Err(PercentageParseError::InvalidUnit(other.to_string().into()));
368 }
369 }
370
371 Ok(PercentageValue::new(number))
372}
373
374#[cfg(all(test, feature = "parser"))]
375mod tests {
376 use super::*;
377
378 #[test]
379 fn test_parse_float_value() {
380 assert_eq!(parse_float_value("10").unwrap().get(), 10.0);
381 assert_eq!(parse_float_value("2.5").unwrap().get(), 2.5);
382 assert_eq!(parse_float_value("-50.2").unwrap().get(), -50.2);
383 assert_eq!(parse_float_value(" 0 ").unwrap().get(), 0.0);
384 assert!(parse_float_value("10a").is_err());
385 assert!(parse_float_value("").is_err());
386 }
387
388 #[test]
389 fn test_parse_percentage_value() {
390 assert_eq!(parse_percentage_value("50%").unwrap().normalized(), 0.5);
392 assert_eq!(parse_percentage_value("120%").unwrap().normalized(), 1.2);
393 assert_eq!(parse_percentage_value("-25%").unwrap().normalized(), -0.25);
394 assert_eq!(
395 parse_percentage_value(" 75.5% ").unwrap().normalized(),
396 0.755
397 );
398
399 assert!((parse_percentage_value("0.5").unwrap().normalized() - 0.5).abs() < 1e-6);
401 assert!((parse_percentage_value("1.2").unwrap().normalized() - 1.2).abs() < 1e-6);
402 assert!((parse_percentage_value("1").unwrap().normalized() - 1.0).abs() < 1e-6);
403
404 assert!(matches!(
406 parse_percentage_value("50px").err().unwrap(),
407 PercentageParseError::InvalidUnit(_)
408 ));
409 assert!(parse_percentage_value("fifty%").is_err());
410 assert!(parse_percentage_value("").is_err());
411 }
412
413 #[test]
414 fn test_const_new_fractional_single_digit() {
415 let val = FloatValue::const_new_fractional(1, 5);
417 assert_eq!(val.get(), 1.5);
418
419 let val = FloatValue::const_new_fractional(0, 5);
420 assert_eq!(val.get(), 0.5);
421
422 let val = FloatValue::const_new_fractional(2, 3);
423 assert_eq!(val.get(), 2.3);
424
425 let val = FloatValue::const_new_fractional(0, 0);
426 assert_eq!(val.get(), 0.0);
427
428 let val = FloatValue::const_new_fractional(10, 9);
429 assert_eq!(val.get(), 10.9);
430 }
431
432 #[test]
433 fn test_const_new_fractional_two_digits() {
434 let val = FloatValue::const_new_fractional(0, 83);
436 assert!((val.get() - 0.83).abs() < 0.001);
437
438 let val = FloatValue::const_new_fractional(1, 17);
439 assert!((val.get() - 1.17).abs() < 0.001);
440
441 let val = FloatValue::const_new_fractional(1, 52);
442 assert!((val.get() - 1.52).abs() < 0.001);
443
444 let val = FloatValue::const_new_fractional(0, 33);
445 assert!((val.get() - 0.33).abs() < 0.001);
446
447 let val = FloatValue::const_new_fractional(2, 67);
448 assert!((val.get() - 2.67).abs() < 0.001);
449
450 let val = FloatValue::const_new_fractional(0, 10);
451 assert!((val.get() - 0.10).abs() < 0.001);
452
453 let val = FloatValue::const_new_fractional(0, 99);
454 assert!((val.get() - 0.99).abs() < 0.001);
455 }
456
457 #[test]
458 fn test_const_new_fractional_three_digits() {
459 let val = FloatValue::const_new_fractional(1, 523);
461 assert!((val.get() - 1.523).abs() < 0.001);
462
463 let val = FloatValue::const_new_fractional(0, 123);
464 assert!((val.get() - 0.123).abs() < 0.001);
465
466 let val = FloatValue::const_new_fractional(2, 999);
467 assert!((val.get() - 2.999).abs() < 0.001);
468
469 let val = FloatValue::const_new_fractional(0, 100);
470 assert!((val.get() - 0.100).abs() < 0.001);
471
472 let val = FloatValue::const_new_fractional(5, 1);
473 assert!((val.get() - 5.1).abs() < 0.001);
474 }
475
476 #[test]
477 fn test_const_new_fractional_truncation() {
478 let val = FloatValue::const_new_fractional(0, 5234);
482 assert!((val.get() - 0.523).abs() < 0.001);
483
484 let val = FloatValue::const_new_fractional(1, 12345);
486 assert!((val.get() - 1.123).abs() < 0.001);
487
488 let val = FloatValue::const_new_fractional(1, 123456);
490 assert!((val.get() - 1.123).abs() < 0.001);
491
492 let val = FloatValue::const_new_fractional(0, 9876543);
494 assert!((val.get() - 0.987).abs() < 0.001);
495
496 let val = FloatValue::const_new_fractional(2, 1234567890);
498 assert!((val.get() - 2.123).abs() < 0.001);
499 }
500
501 #[test]
502 fn test_const_new_fractional_negative() {
503 let val = FloatValue::const_new_fractional(-1, 5);
505 assert_eq!(val.get(), -1.5);
506
507 let val = FloatValue::const_new_fractional(0, 83);
508 assert!((val.get() - 0.83).abs() < 0.001);
509
510 let val = FloatValue::const_new_fractional(-2, 123);
511 assert!((val.get() - -2.123).abs() < 0.001);
512
513 let val = FloatValue::const_new_fractional(1, -5);
515 assert_eq!(val.get(), 0.5); let val = FloatValue::const_new_fractional(0, -50);
518 assert!((val.get() - -0.5).abs() < 0.001); }
520
521 #[test]
522 fn test_const_new_fractional_edge_cases() {
523 let val = FloatValue::const_new_fractional(0, 0);
525 assert_eq!(val.get(), 0.0);
526
527 let val = FloatValue::const_new_fractional(100, 5);
529 assert_eq!(val.get(), 100.5);
530
531 let val = FloatValue::const_new_fractional(1000, 99);
532 assert!((val.get() - 1000.99).abs() < 0.001);
533
534 let val = FloatValue::const_new_fractional(0, 999);
536 assert!((val.get() - 0.999).abs() < 0.001);
537
538 let val = FloatValue::const_new_fractional(1, 1);
540 assert!((val.get() - 1.1).abs() < 0.001);
541
542 let val = FloatValue::const_new_fractional(1, 10);
543 assert!((val.get() - 1.10).abs() < 0.001);
544 }
545
546 #[test]
547 fn test_const_new_fractional_ua_css_values() {
548 let val = FloatValue::const_new_fractional(2, 0);
552 assert_eq!(val.get(), 2.0);
553
554 let val = FloatValue::const_new_fractional(1, 5);
556 assert_eq!(val.get(), 1.5);
557
558 let val = FloatValue::const_new_fractional(1, 17);
560 assert!((val.get() - 1.17).abs() < 0.001);
561
562 let val = FloatValue::const_new_fractional(1, 0);
564 assert_eq!(val.get(), 1.0);
565
566 let val = FloatValue::const_new_fractional(0, 83);
568 assert!((val.get() - 0.83).abs() < 0.001);
569
570 let val = FloatValue::const_new_fractional(0, 67);
572 assert!((val.get() - 0.67).abs() < 0.001);
573
574 let val = FloatValue::const_new_fractional(0, 67);
576 assert!((val.get() - 0.67).abs() < 0.001);
577
578 let val = FloatValue::const_new_fractional(0, 83);
580 assert!((val.get() - 0.83).abs() < 0.001);
581
582 let val = FloatValue::const_new_fractional(1, 33);
584 assert!((val.get() - 1.33).abs() < 0.001);
585
586 let val = FloatValue::const_new_fractional(1, 67);
588 assert!((val.get() - 1.67).abs() < 0.001);
589
590 let val = FloatValue::const_new_fractional(2, 33);
592 assert!((val.get() - 2.33).abs() < 0.001);
593 }
594
595 #[test]
596 fn test_const_new_fractional_consistency() {
597 let const_val = FloatValue::const_new_fractional(1, 5);
600 let runtime_val = FloatValue::new(1.5);
601 assert_eq!(const_val.get(), runtime_val.get());
602
603 let const_val = FloatValue::const_new_fractional(0, 83);
604 let runtime_val = FloatValue::new(0.83);
605 assert!((const_val.get() - runtime_val.get()).abs() < 0.001);
606
607 let const_val = FloatValue::const_new_fractional(1, 523);
608 let runtime_val = FloatValue::new(1.523);
609 assert!((const_val.get() - runtime_val.get()).abs() < 0.001);
610
611 let const_val = FloatValue::const_new_fractional(2, 99);
612 let runtime_val = FloatValue::new(2.99);
613 assert!((const_val.get() - runtime_val.get()).abs() < 0.001);
614 }
615}