use core::fmt;
use std::num::ParseFloatError;
use crate::corety::AzString;
pub const FP_PRECISION_MULTIPLIER: f32 = 1000.0;
pub const FP_PRECISION_MULTIPLIER_CONST: isize = FP_PRECISION_MULTIPLIER as isize;
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub struct PercentageValue {
number: FloatValue,
}
impl_option!(
PercentageValue,
OptionPercentageValue,
[Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
);
impl fmt::Display for PercentageValue {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}%", self.normalized() * 100.0)
}
}
impl PercentageValue {
#[inline]
pub const fn const_new(value: isize) -> Self {
Self {
number: FloatValue::const_new(value),
}
}
#[inline]
pub const fn const_new_fractional(pre_comma: isize, post_comma: isize) -> Self {
Self {
number: FloatValue::const_new_fractional(pre_comma, post_comma),
}
}
#[inline]
pub fn new(value: f32) -> Self {
Self {
number: value.into(),
}
}
#[inline]
pub fn normalized(&self) -> f32 {
self.number.get() / 100.0
}
#[inline]
pub fn interpolate(&self, other: &Self, t: f32) -> Self {
Self {
number: self.number.interpolate(&other.number, t),
}
}
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub struct FloatValue {
pub number: isize,
}
impl fmt::Display for FloatValue {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.get())
}
}
impl ::core::fmt::Debug for FloatValue {
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
write!(f, "{}", self)
}
}
impl Default for FloatValue {
fn default() -> Self {
const DEFAULT_FLV: FloatValue = FloatValue::const_new(0);
DEFAULT_FLV
}
}
impl FloatValue {
#[inline]
pub const fn const_new(value: isize) -> Self {
Self {
number: value * FP_PRECISION_MULTIPLIER_CONST,
}
}
#[inline]
pub const fn const_new_fractional(pre_comma: isize, post_comma: isize) -> Self {
let abs_post = if post_comma < 0 {
-post_comma
} else {
post_comma
};
let (normalized_post, divisor) = if abs_post < 10 {
(abs_post, 10)
} else if abs_post < 100 {
(abs_post, 100)
} else if abs_post < 1000 {
(abs_post, 1000)
} else if abs_post < 10000 {
(abs_post / 10, 1000)
} else if abs_post < 100000 {
(abs_post / 100, 1000)
} else if abs_post < 1000000 {
(abs_post / 1000, 1000)
} else if abs_post < 10000000 {
(abs_post / 10000, 1000)
} else if abs_post < 100000000 {
(abs_post / 100000, 1000)
} else if abs_post < 1000000000 {
(abs_post / 1000000, 1000)
} else {
(abs_post / 10000000, 1000)
};
let fractional_part = normalized_post * (FP_PRECISION_MULTIPLIER_CONST / divisor);
let signed_fractional = if post_comma < 0 {
-fractional_part
} else {
fractional_part
};
let final_fractional = if pre_comma < 0 && post_comma >= 0 {
-signed_fractional
} else {
signed_fractional
};
Self {
number: pre_comma * FP_PRECISION_MULTIPLIER_CONST + final_fractional,
}
}
#[inline]
pub fn new(value: f32) -> Self {
Self {
number: (value * FP_PRECISION_MULTIPLIER) as isize,
}
}
#[inline]
pub fn get(&self) -> f32 {
self.number as f32 / FP_PRECISION_MULTIPLIER
}
#[inline]
pub fn interpolate(&self, other: &Self, t: f32) -> Self {
let self_val_f32 = self.get();
let other_val_f32 = other.get();
let interpolated = self_val_f32 + ((other_val_f32 - self_val_f32) * t);
Self::new(interpolated)
}
}
impl From<f32> for FloatValue {
#[inline]
fn from(val: f32) -> Self {
Self::new(val)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub enum SizeMetric {
Px,
Pt,
Em,
Rem,
In,
Cm,
Mm,
Percent,
Vw,
Vh,
Vmin,
Vmax,
}
impl Default for SizeMetric {
fn default() -> Self {
SizeMetric::Px
}
}
impl fmt::Display for SizeMetric {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::SizeMetric::*;
match self {
Px => write!(f, "px"),
Pt => write!(f, "pt"),
Em => write!(f, "em"),
Rem => write!(f, "rem"),
In => write!(f, "in"),
Cm => write!(f, "cm"),
Mm => write!(f, "mm"),
Percent => write!(f, "%"),
Vw => write!(f, "vw"),
Vh => write!(f, "vh"),
Vmin => write!(f, "vmin"),
Vmax => write!(f, "vmax"),
}
}
}
pub fn parse_float_value(input: &str) -> Result<FloatValue, ParseFloatError> {
Ok(FloatValue::new(input.trim().parse::<f32>()?))
}
#[derive(Clone, PartialEq, Eq)]
pub enum PercentageParseError {
ValueParseErr(ParseFloatError),
NoPercentSign,
InvalidUnit(AzString),
}
impl_debug_as_display!(PercentageParseError);
impl_from!(ParseFloatError, PercentageParseError::ValueParseErr);
impl_display! { PercentageParseError, {
ValueParseErr(e) => format!("\"{}\"", e),
NoPercentSign => format!("No percent sign after number"),
InvalidUnit(u) => format!("Error parsing percentage: invalid unit \"{}\"", u.as_str()),
}}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PercentageParseErrorOwned {
ValueParseErr(ParseFloatError),
NoPercentSign,
InvalidUnit(String),
}
impl PercentageParseError {
pub fn to_contained(&self) -> PercentageParseErrorOwned {
match self {
Self::ValueParseErr(e) => PercentageParseErrorOwned::ValueParseErr(e.clone()),
Self::NoPercentSign => PercentageParseErrorOwned::NoPercentSign,
Self::InvalidUnit(u) => PercentageParseErrorOwned::InvalidUnit(u.as_str().to_string()),
}
}
}
impl PercentageParseErrorOwned {
pub fn to_shared(&self) -> PercentageParseError {
match self {
Self::ValueParseErr(e) => PercentageParseError::ValueParseErr(e.clone()),
Self::NoPercentSign => PercentageParseError::NoPercentSign,
Self::InvalidUnit(u) => PercentageParseError::InvalidUnit(u.clone().into()),
}
}
}
pub fn parse_percentage_value(input: &str) -> Result<PercentageValue, PercentageParseError> {
let input = input.trim();
if input.is_empty() {
return Err(PercentageParseError::ValueParseErr(
"empty string".parse::<f32>().unwrap_err(),
));
}
let mut split_pos = 0;
let mut found_numeric = false;
for (idx, ch) in input.char_indices() {
if ch.is_numeric() || ch == '.' || ch == '-' {
split_pos = idx;
found_numeric = true;
}
}
if !found_numeric {
return Err(PercentageParseError::ValueParseErr(
"no numeric value".parse::<f32>().unwrap_err(),
));
}
split_pos += 1;
let unit = input[split_pos..].trim();
let mut number = input[..split_pos]
.trim()
.parse::<f32>()
.map_err(|e| PercentageParseError::ValueParseErr(e))?;
match unit {
"" => {
number *= 100.0;
} "%" => {} other => {
return Err(PercentageParseError::InvalidUnit(other.to_string().into()));
}
}
Ok(PercentageValue::new(number))
}
#[cfg(all(test, feature = "parser"))]
mod tests {
use super::*;
#[test]
fn test_parse_float_value() {
assert_eq!(parse_float_value("10").unwrap().get(), 10.0);
assert_eq!(parse_float_value("2.5").unwrap().get(), 2.5);
assert_eq!(parse_float_value("-50.2").unwrap().get(), -50.2);
assert_eq!(parse_float_value(" 0 ").unwrap().get(), 0.0);
assert!(parse_float_value("10a").is_err());
assert!(parse_float_value("").is_err());
}
#[test]
fn test_parse_percentage_value() {
assert_eq!(parse_percentage_value("50%").unwrap().normalized(), 0.5);
assert_eq!(parse_percentage_value("120%").unwrap().normalized(), 1.2);
assert_eq!(parse_percentage_value("-25%").unwrap().normalized(), -0.25);
assert_eq!(
parse_percentage_value(" 75.5% ").unwrap().normalized(),
0.755
);
assert!((parse_percentage_value("0.5").unwrap().normalized() - 0.5).abs() < 1e-6);
assert!((parse_percentage_value("1.2").unwrap().normalized() - 1.2).abs() < 1e-6);
assert!((parse_percentage_value("1").unwrap().normalized() - 1.0).abs() < 1e-6);
assert!(matches!(
parse_percentage_value("50px").err().unwrap(),
PercentageParseError::InvalidUnit(_)
));
assert!(parse_percentage_value("fifty%").is_err());
assert!(parse_percentage_value("").is_err());
}
#[test]
fn test_const_new_fractional_single_digit() {
let val = FloatValue::const_new_fractional(1, 5);
assert_eq!(val.get(), 1.5);
let val = FloatValue::const_new_fractional(0, 5);
assert_eq!(val.get(), 0.5);
let val = FloatValue::const_new_fractional(2, 3);
assert_eq!(val.get(), 2.3);
let val = FloatValue::const_new_fractional(0, 0);
assert_eq!(val.get(), 0.0);
let val = FloatValue::const_new_fractional(10, 9);
assert_eq!(val.get(), 10.9);
}
#[test]
fn test_const_new_fractional_two_digits() {
let val = FloatValue::const_new_fractional(0, 83);
assert!((val.get() - 0.83).abs() < 0.001);
let val = FloatValue::const_new_fractional(1, 17);
assert!((val.get() - 1.17).abs() < 0.001);
let val = FloatValue::const_new_fractional(1, 52);
assert!((val.get() - 1.52).abs() < 0.001);
let val = FloatValue::const_new_fractional(0, 33);
assert!((val.get() - 0.33).abs() < 0.001);
let val = FloatValue::const_new_fractional(2, 67);
assert!((val.get() - 2.67).abs() < 0.001);
let val = FloatValue::const_new_fractional(0, 10);
assert!((val.get() - 0.10).abs() < 0.001);
let val = FloatValue::const_new_fractional(0, 99);
assert!((val.get() - 0.99).abs() < 0.001);
}
#[test]
fn test_const_new_fractional_three_digits() {
let val = FloatValue::const_new_fractional(1, 523);
assert!((val.get() - 1.523).abs() < 0.001);
let val = FloatValue::const_new_fractional(0, 123);
assert!((val.get() - 0.123).abs() < 0.001);
let val = FloatValue::const_new_fractional(2, 999);
assert!((val.get() - 2.999).abs() < 0.001);
let val = FloatValue::const_new_fractional(0, 100);
assert!((val.get() - 0.100).abs() < 0.001);
let val = FloatValue::const_new_fractional(5, 1);
assert!((val.get() - 5.1).abs() < 0.001);
}
#[test]
fn test_const_new_fractional_truncation() {
let val = FloatValue::const_new_fractional(0, 5234);
assert!((val.get() - 0.523).abs() < 0.001);
let val = FloatValue::const_new_fractional(1, 12345);
assert!((val.get() - 1.123).abs() < 0.001);
let val = FloatValue::const_new_fractional(1, 123456);
assert!((val.get() - 1.123).abs() < 0.001);
let val = FloatValue::const_new_fractional(0, 9876543);
assert!((val.get() - 0.987).abs() < 0.001);
let val = FloatValue::const_new_fractional(2, 1234567890);
assert!((val.get() - 2.123).abs() < 0.001);
}
#[test]
fn test_const_new_fractional_negative() {
let val = FloatValue::const_new_fractional(-1, 5);
assert_eq!(val.get(), -1.5);
let val = FloatValue::const_new_fractional(0, 83);
assert!((val.get() - 0.83).abs() < 0.001);
let val = FloatValue::const_new_fractional(-2, 123);
assert!((val.get() - -2.123).abs() < 0.001);
let val = FloatValue::const_new_fractional(1, -5);
assert_eq!(val.get(), 0.5);
let val = FloatValue::const_new_fractional(0, -50);
assert!((val.get() - -0.5).abs() < 0.001); }
#[test]
fn test_const_new_fractional_edge_cases() {
let val = FloatValue::const_new_fractional(0, 0);
assert_eq!(val.get(), 0.0);
let val = FloatValue::const_new_fractional(100, 5);
assert_eq!(val.get(), 100.5);
let val = FloatValue::const_new_fractional(1000, 99);
assert!((val.get() - 1000.99).abs() < 0.001);
let val = FloatValue::const_new_fractional(0, 999);
assert!((val.get() - 0.999).abs() < 0.001);
let val = FloatValue::const_new_fractional(1, 1);
assert!((val.get() - 1.1).abs() < 0.001);
let val = FloatValue::const_new_fractional(1, 10);
assert!((val.get() - 1.10).abs() < 0.001);
}
#[test]
fn test_const_new_fractional_ua_css_values() {
let val = FloatValue::const_new_fractional(2, 0);
assert_eq!(val.get(), 2.0);
let val = FloatValue::const_new_fractional(1, 5);
assert_eq!(val.get(), 1.5);
let val = FloatValue::const_new_fractional(1, 17);
assert!((val.get() - 1.17).abs() < 0.001);
let val = FloatValue::const_new_fractional(1, 0);
assert_eq!(val.get(), 1.0);
let val = FloatValue::const_new_fractional(0, 83);
assert!((val.get() - 0.83).abs() < 0.001);
let val = FloatValue::const_new_fractional(0, 67);
assert!((val.get() - 0.67).abs() < 0.001);
let val = FloatValue::const_new_fractional(0, 67);
assert!((val.get() - 0.67).abs() < 0.001);
let val = FloatValue::const_new_fractional(0, 83);
assert!((val.get() - 0.83).abs() < 0.001);
let val = FloatValue::const_new_fractional(1, 33);
assert!((val.get() - 1.33).abs() < 0.001);
let val = FloatValue::const_new_fractional(1, 67);
assert!((val.get() - 1.67).abs() < 0.001);
let val = FloatValue::const_new_fractional(2, 33);
assert!((val.get() - 2.33).abs() < 0.001);
}
#[test]
fn test_const_new_fractional_consistency() {
let const_val = FloatValue::const_new_fractional(1, 5);
let runtime_val = FloatValue::new(1.5);
assert_eq!(const_val.get(), runtime_val.get());
let const_val = FloatValue::const_new_fractional(0, 83);
let runtime_val = FloatValue::new(0.83);
assert!((const_val.get() - runtime_val.get()).abs() < 0.001);
let const_val = FloatValue::const_new_fractional(1, 523);
let runtime_val = FloatValue::new(1.523);
assert!((const_val.get() - runtime_val.get()).abs() < 0.001);
let const_val = FloatValue::const_new_fractional(2, 99);
let runtime_val = FloatValue::new(2.99);
assert!((const_val.get() - runtime_val.get()).abs() < 0.001);
}
}