1use core::fmt;
5use std::num::ParseFloatError;
6
7use crate::corety::AzString;
8
9pub const FP_PRECISION_MULTIPLIER: f32 = 1000.0;
17const FP_PRECISION_MULTIPLIER_CONST: isize = crate::cast::f32_to_isize(FP_PRECISION_MULTIPLIER);
18
19#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
22#[repr(C)]
23pub struct PercentageValue {
24 number: FloatValue,
25}
26
27impl_option!(
28 PercentageValue,
29 OptionPercentageValue,
30 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
31);
32
33impl fmt::Display for PercentageValue {
34 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35 write!(f, "{}%", self.normalized() * 100.0)
36 }
37}
38
39impl PercentageValue {
40 #[inline]
43 #[must_use] pub const fn const_new(value: isize) -> Self {
44 Self {
45 number: FloatValue::const_new(value),
46 }
47 }
48
49 #[inline]
61 #[must_use] pub const fn const_new_fractional(pre_comma: isize, post_comma: isize) -> Self {
62 Self {
63 number: FloatValue::const_new_fractional(pre_comma, post_comma),
64 }
65 }
66
67 #[inline]
68 #[must_use] pub fn new(value: f32) -> Self {
69 Self {
70 number: value.into(),
71 }
72 }
73
74 #[inline]
77 #[must_use] pub fn normalized(&self) -> f32 {
78 self.number.get() / 100.0
79 }
80
81 #[inline]
82 #[must_use] pub fn interpolate(&self, other: &Self, t: f32) -> Self {
83 Self {
84 number: self.number.interpolate(&other.number, t),
85 }
86 }
87}
88
89#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
92#[repr(C)]
93pub struct FloatValue {
94 pub(crate) number: isize,
95}
96
97impl fmt::Display for FloatValue {
98 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99 write!(f, "{}", self.get())
100 }
101}
102
103impl ::core::fmt::Debug for FloatValue {
104 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
105 write!(f, "{self}")
106 }
107}
108
109impl Default for FloatValue {
110 fn default() -> Self {
111 const DEFAULT_FLV: FloatValue = FloatValue::const_new(0);
112 DEFAULT_FLV
113 }
114}
115
116impl FloatValue {
117 #[inline]
120 #[must_use] pub const fn const_new(value: isize) -> Self {
121 Self {
122 number: value * FP_PRECISION_MULTIPLIER_CONST,
123 }
124 }
125
126 #[inline]
149 #[must_use] pub const fn const_new_fractional(pre_comma: isize, post_comma: isize) -> Self {
150 let abs_post = if post_comma < 0 {
152 -post_comma
153 } else {
154 post_comma
155 };
156
157 let (normalized_post, divisor) = if abs_post < 10 {
160 (abs_post, 10)
162 } else if abs_post < 100 {
163 (abs_post, 100)
165 } else if abs_post < 1000 {
166 (abs_post, 1000)
168 } else if abs_post < 10000 {
169 (abs_post / 10, 1000)
171 } else if abs_post < 100_000 {
172 (abs_post / 100, 1000)
173 } else if abs_post < 1_000_000 {
174 (abs_post / 1000, 1000)
175 } else if abs_post < 10_000_000 {
176 (abs_post / 10000, 1000)
177 } else if abs_post < 100_000_000 {
178 (abs_post / 100_000, 1000)
179 } else if abs_post < 1_000_000_000 {
180 (abs_post / 1_000_000, 1000)
181 } else {
182 (abs_post / 10_000_000, 1000)
185 };
186
187 let fractional_part = normalized_post * (FP_PRECISION_MULTIPLIER_CONST / divisor);
189
190 let signed_fractional = if post_comma < 0 {
192 -fractional_part
193 } else {
194 fractional_part
195 };
196
197 let final_fractional = if pre_comma < 0 && post_comma >= 0 {
200 -signed_fractional
201 } else {
202 signed_fractional
203 };
204
205 Self {
206 number: pre_comma * FP_PRECISION_MULTIPLIER_CONST + final_fractional,
207 }
208 }
209
210 #[inline]
211 #[must_use] pub fn new(value: f32) -> Self {
212 Self {
213 number: crate::cast::f32_to_isize(value * FP_PRECISION_MULTIPLIER),
214 }
215 }
216
217 #[inline]
218 #[must_use] pub fn get(&self) -> f32 {
219 crate::cast::isize_to_f32(self.number) / FP_PRECISION_MULTIPLIER
220 }
221
222 #[inline]
227 #[must_use] pub const fn number(&self) -> isize {
228 self.number
229 }
230
231 #[inline]
232 #[allow(clippy::suboptimal_flops)] #[must_use] pub fn interpolate(&self, other: &Self, t: f32) -> Self {
234 let self_val_f32 = self.get();
235 let other_val_f32 = other.get();
236 let interpolated = self_val_f32 + ((other_val_f32 - self_val_f32) * t);
237 Self::new(interpolated)
238 }
239}
240
241impl From<f32> for FloatValue {
242 #[inline]
243 fn from(val: f32) -> Self {
244 Self::new(val)
245 }
246}
247
248#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
250#[repr(C)]
251#[derive(Default)]
252pub enum SizeMetric {
253 #[default]
254 Px,
255 Pt,
256 Em,
257 Rem,
258 In,
259 Cm,
260 Mm,
261 Percent,
262 Vw,
264 Vh,
266 Vmin,
268 Vmax,
270}
271
272
273impl fmt::Display for SizeMetric {
274 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
275 use self::SizeMetric::{Px, Pt, Em, Rem, In, Cm, Mm, Percent, Vw, Vh, Vmin, Vmax};
276 match self {
277 Px => write!(f, "px"),
278 Pt => write!(f, "pt"),
279 Em => write!(f, "em"),
280 Rem => write!(f, "rem"),
281 In => write!(f, "in"),
282 Cm => write!(f, "cm"),
283 Mm => write!(f, "mm"),
284 Percent => write!(f, "%"),
285 Vw => write!(f, "vw"),
286 Vh => write!(f, "vh"),
287 Vmin => write!(f, "vmin"),
288 Vmax => write!(f, "vmax"),
289 }
290 }
291}
292
293pub fn parse_float_value(input: &str) -> Result<FloatValue, ParseFloatError> {
297 Ok(FloatValue::new(input.trim().parse::<f32>()?))
298}
299#[allow(variant_size_differences)] #[derive(Clone, PartialEq, Eq)]
301#[repr(C, u8)]
302pub enum PercentageParseError {
303 ValueParseErr(crate::props::basic::error::ParseFloatError),
304 NoPercentSign,
305 InvalidUnit(AzString),
306}
307
308impl_debug_as_display!(PercentageParseError);
309
310impl From<ParseFloatError> for PercentageParseError {
311 fn from(e: ParseFloatError) -> Self {
312 Self::ValueParseErr(crate::props::basic::error::ParseFloatError::from(e))
313 }
314}
315
316impl_display! { PercentageParseError, {
317 ValueParseErr(e) => format!("\"{}\"", e),
318 NoPercentSign => format!("No percent sign after number"),
319 InvalidUnit(u) => format!("Error parsing percentage: invalid unit \"{}\"", u.as_str()),
320}}
321#[allow(variant_size_differences)] #[derive(Debug, Clone, PartialEq, Eq)]
323#[repr(C, u8)]
324pub enum PercentageParseErrorOwned {
325 ValueParseErr(crate::props::basic::error::ParseFloatError),
326 NoPercentSign,
327 InvalidUnit(AzString),
328}
329
330impl PercentageParseError {
331 #[must_use] pub fn to_contained(&self) -> PercentageParseErrorOwned {
332 match self {
333 Self::ValueParseErr(e) => PercentageParseErrorOwned::ValueParseErr(*e),
334 Self::NoPercentSign => PercentageParseErrorOwned::NoPercentSign,
335 Self::InvalidUnit(u) => PercentageParseErrorOwned::InvalidUnit(u.clone()),
336 }
337 }
338}
339
340impl PercentageParseErrorOwned {
341 #[must_use] pub fn to_shared(&self) -> PercentageParseError {
342 match self {
343 Self::ValueParseErr(e) => PercentageParseError::ValueParseErr(*e),
344 Self::NoPercentSign => PercentageParseError::NoPercentSign,
345 Self::InvalidUnit(u) => PercentageParseError::InvalidUnit(u.clone()),
346 }
347 }
348}
349
350pub fn parse_percentage_value(input: &str) -> Result<PercentageValue, PercentageParseError> {
355 let input = input.trim();
356
357 if input.is_empty() {
358 return Err(PercentageParseError::ValueParseErr(
359 crate::props::basic::error::ParseFloatError::from("empty string".parse::<f32>().unwrap_err()),
360 ));
361 }
362
363 let mut split_pos = 0;
364 let mut found_numeric = false;
365 for (idx, ch) in input.char_indices() {
366 if ch.is_numeric() || ch == '.' || ch == '-' {
367 split_pos = idx;
368 found_numeric = true;
369 }
370 }
371
372 if !found_numeric {
373 return Err(PercentageParseError::ValueParseErr(
374 crate::props::basic::error::ParseFloatError::from("no numeric value".parse::<f32>().unwrap_err()),
375 ));
376 }
377
378 split_pos += 1;
379
380 let unit = input[split_pos..].trim();
381 let mut number = input[..split_pos]
382 .trim()
383 .parse::<f32>()
384 .map_err(|e| PercentageParseError::ValueParseErr(crate::props::basic::error::ParseFloatError::from(e)))?;
385
386 match unit {
387 "" => {
388 number *= 100.0;
389 } "%" => {} other => {
392 return Err(PercentageParseError::InvalidUnit(other.to_string().into()));
393 }
394 }
395
396 Ok(PercentageValue::new(number))
397}
398
399#[cfg(all(test, feature = "parser"))]
400mod tests {
401 #![allow(clippy::float_cmp)]
403 use super::*;
404
405 #[test]
406 fn test_parse_float_value() {
407 assert_eq!(parse_float_value("10").unwrap().get(), 10.0);
408 assert_eq!(parse_float_value("2.5").unwrap().get(), 2.5);
409 assert_eq!(parse_float_value("-50.2").unwrap().get(), -50.2);
410 assert_eq!(parse_float_value(" 0 ").unwrap().get(), 0.0);
411 assert!(parse_float_value("10a").is_err());
412 assert!(parse_float_value("").is_err());
413 }
414
415 #[test]
416 fn test_parse_percentage_value() {
417 assert_eq!(parse_percentage_value("50%").unwrap().normalized(), 0.5);
419 assert_eq!(parse_percentage_value("120%").unwrap().normalized(), 1.2);
420 assert_eq!(parse_percentage_value("-25%").unwrap().normalized(), -0.25);
421 assert_eq!(
422 parse_percentage_value(" 75.5% ").unwrap().normalized(),
423 0.755
424 );
425
426 assert!((parse_percentage_value("0.5").unwrap().normalized() - 0.5).abs() < 1e-6);
428 assert!((parse_percentage_value("1.2").unwrap().normalized() - 1.2).abs() < 1e-6);
429 assert!((parse_percentage_value("1").unwrap().normalized() - 1.0).abs() < 1e-6);
430
431 assert!(matches!(
433 parse_percentage_value("50px").err().unwrap(),
434 PercentageParseError::InvalidUnit(_)
435 ));
436 assert!(parse_percentage_value("fifty%").is_err());
437 assert!(parse_percentage_value("").is_err());
438 }
439
440 #[test]
441 fn test_const_new_fractional_single_digit() {
442 let val = FloatValue::const_new_fractional(1, 5);
444 assert_eq!(val.get(), 1.5);
445
446 let val = FloatValue::const_new_fractional(0, 5);
447 assert_eq!(val.get(), 0.5);
448
449 let val = FloatValue::const_new_fractional(2, 3);
450 assert_eq!(val.get(), 2.3);
451
452 let val = FloatValue::const_new_fractional(0, 0);
453 assert_eq!(val.get(), 0.0);
454
455 let val = FloatValue::const_new_fractional(10, 9);
456 assert_eq!(val.get(), 10.9);
457 }
458
459 #[test]
460 fn test_const_new_fractional_two_digits() {
461 let val = FloatValue::const_new_fractional(0, 83);
463 assert!((val.get() - 0.83).abs() < 0.001);
464
465 let val = FloatValue::const_new_fractional(1, 17);
466 assert!((val.get() - 1.17).abs() < 0.001);
467
468 let val = FloatValue::const_new_fractional(1, 52);
469 assert!((val.get() - 1.52).abs() < 0.001);
470
471 let val = FloatValue::const_new_fractional(0, 33);
472 assert!((val.get() - 0.33).abs() < 0.001);
473
474 let val = FloatValue::const_new_fractional(2, 67);
475 assert!((val.get() - 2.67).abs() < 0.001);
476
477 let val = FloatValue::const_new_fractional(0, 10);
478 assert!((val.get() - 0.10).abs() < 0.001);
479
480 let val = FloatValue::const_new_fractional(0, 99);
481 assert!((val.get() - 0.99).abs() < 0.001);
482 }
483
484 #[test]
485 fn test_const_new_fractional_three_digits() {
486 let val = FloatValue::const_new_fractional(1, 523);
488 assert!((val.get() - 1.523).abs() < 0.001);
489
490 let val = FloatValue::const_new_fractional(0, 123);
491 assert!((val.get() - 0.123).abs() < 0.001);
492
493 let val = FloatValue::const_new_fractional(2, 999);
494 assert!((val.get() - 2.999).abs() < 0.001);
495
496 let val = FloatValue::const_new_fractional(0, 100);
497 assert!((val.get() - 0.100).abs() < 0.001);
498
499 let val = FloatValue::const_new_fractional(5, 1);
500 assert!((val.get() - 5.1).abs() < 0.001);
501 }
502
503 #[test]
504 fn test_const_new_fractional_truncation() {
505 let val = FloatValue::const_new_fractional(0, 5234);
509 assert!((val.get() - 0.523).abs() < 0.001);
510
511 let val = FloatValue::const_new_fractional(1, 12345);
513 assert!((val.get() - 1.123).abs() < 0.001);
514
515 let val = FloatValue::const_new_fractional(1, 123_456);
517 assert!((val.get() - 1.123).abs() < 0.001);
518
519 let val = FloatValue::const_new_fractional(0, 9_876_543);
521 assert!((val.get() - 0.987).abs() < 0.001);
522
523 let val = FloatValue::const_new_fractional(2, 1_234_567_890);
525 assert!((val.get() - 2.123).abs() < 0.001);
526 }
527
528 #[test]
529 fn test_const_new_fractional_negative() {
530 let val = FloatValue::const_new_fractional(-1, 5);
532 assert_eq!(val.get(), -1.5);
533
534 let val = FloatValue::const_new_fractional(0, 83);
535 assert!((val.get() - 0.83).abs() < 0.001);
536
537 let val = FloatValue::const_new_fractional(-2, 123);
538 assert!((val.get() - -2.123).abs() < 0.001);
539
540 let val = FloatValue::const_new_fractional(1, -5);
542 assert_eq!(val.get(), 0.5); let val = FloatValue::const_new_fractional(0, -50);
545 assert!((val.get() - -0.5).abs() < 0.001); }
547
548 #[test]
549 fn test_const_new_fractional_edge_cases() {
550 let val = FloatValue::const_new_fractional(0, 0);
552 assert_eq!(val.get(), 0.0);
553
554 let val = FloatValue::const_new_fractional(100, 5);
556 assert_eq!(val.get(), 100.5);
557
558 let val = FloatValue::const_new_fractional(1000, 99);
559 assert!((val.get() - 1000.99).abs() < 0.001);
560
561 let val = FloatValue::const_new_fractional(0, 999);
563 assert!((val.get() - 0.999).abs() < 0.001);
564
565 let val = FloatValue::const_new_fractional(1, 1);
567 assert!((val.get() - 1.1).abs() < 0.001);
568
569 let val = FloatValue::const_new_fractional(1, 10);
570 assert!((val.get() - 1.10).abs() < 0.001);
571 }
572
573 #[test]
574 fn test_const_new_fractional_ua_css_values() {
575 let val = FloatValue::const_new_fractional(2, 0);
579 assert_eq!(val.get(), 2.0);
580
581 let val = FloatValue::const_new_fractional(1, 5);
583 assert_eq!(val.get(), 1.5);
584
585 let val = FloatValue::const_new_fractional(1, 17);
587 assert!((val.get() - 1.17).abs() < 0.001);
588
589 let val = FloatValue::const_new_fractional(1, 0);
591 assert_eq!(val.get(), 1.0);
592
593 let val = FloatValue::const_new_fractional(0, 83);
595 assert!((val.get() - 0.83).abs() < 0.001);
596
597 let val = FloatValue::const_new_fractional(0, 67);
599 assert!((val.get() - 0.67).abs() < 0.001);
600
601 let val = FloatValue::const_new_fractional(0, 67);
603 assert!((val.get() - 0.67).abs() < 0.001);
604
605 let val = FloatValue::const_new_fractional(0, 83);
607 assert!((val.get() - 0.83).abs() < 0.001);
608
609 let val = FloatValue::const_new_fractional(1, 33);
611 assert!((val.get() - 1.33).abs() < 0.001);
612
613 let val = FloatValue::const_new_fractional(1, 67);
615 assert!((val.get() - 1.67).abs() < 0.001);
616
617 let val = FloatValue::const_new_fractional(2, 33);
619 assert!((val.get() - 2.33).abs() < 0.001);
620 }
621
622 #[test]
623 fn test_const_new_fractional_consistency() {
624 let const_val = FloatValue::const_new_fractional(1, 5);
627 let runtime_val = FloatValue::new(1.5);
628 assert_eq!(const_val.get(), runtime_val.get());
629
630 let const_val = FloatValue::const_new_fractional(0, 83);
631 let runtime_val = FloatValue::new(0.83);
632 assert!((const_val.get() - runtime_val.get()).abs() < 0.001);
633
634 let const_val = FloatValue::const_new_fractional(1, 523);
635 let runtime_val = FloatValue::new(1.523);
636 assert!((const_val.get() - runtime_val.get()).abs() < 0.001);
637
638 let const_val = FloatValue::const_new_fractional(2, 99);
639 let runtime_val = FloatValue::new(2.99);
640 assert!((const_val.get() - runtime_val.get()).abs() < 0.001);
641 }
642}