use std::cmp::Ordering;
use std::fmt::{Display, Formatter};
use std::str::FromStr;
use num_traits::{AsPrimitive, Float};
use rand::distributions::uniform::{SampleBorrow, SampleUniform, UniformSampler};
use rand::Rng;
use crate::{Unit, UnitError};
#[derive(thiserror::Error, Debug, PartialEq)]
pub enum LengthError {
#[error("Could not parse number: {0}")]
FloatParseError(#[from] std::num::ParseFloatError),
#[error("Could not parse unit: {0}")]
UnitParseError(#[from] UnitError),
}
#[derive(Clone, Copy, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct Length {
pub value: f64,
pub unit: Unit,
}
impl Default for Length {
fn default() -> Self {
Length {
value: 0.0,
unit: Unit::Px,
}
}
}
impl PartialOrd for Length {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.to_px::<f64>().partial_cmp(&other.to_px())
}
}
impl Display for Length {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.value.fmt(f)?;
write!(f, "{}", self.unit)
}
}
macro_rules! length_constructor {
($ctor:ident, $unit:expr) => {
#[doc = concat!("Create a [`Length`] with the given value and a [`", stringify!($unit), "`] unit.")]
#[must_use]
pub fn $ctor<F: Float + AsPrimitive<f64>>(value: F) -> Self {
Self::new(value, $unit)
}
};
}
impl Length {
#[must_use]
pub fn new<F: Float + AsPrimitive<f64>>(value: F, unit: Unit) -> Self {
Self {
value: value.as_(),
unit,
}
}
length_constructor!(pixels, Unit::Px);
length_constructor!(inches, Unit::In);
length_constructor!(feet, Unit::Ft);
length_constructor!(yards, Unit::Yd);
length_constructor!(miles, Unit::Mi);
length_constructor!(mm, Unit::Mm);
length_constructor!(cm, Unit::Cm);
length_constructor!(meters, Unit::M);
length_constructor!(km, Unit::Km);
length_constructor!(picas, Unit::Pc);
length_constructor!(points, Unit::Pt);
#[must_use]
#[allow(clippy::missing_panics_doc)]
pub fn to_px<F: Float>(&self) -> F {
F::from(self.value).unwrap() * self.unit.to_px::<F>()
}
#[must_use]
pub fn convert_to(self, unit: Unit) -> Self {
Self {
value: self.unit.convert_to(&unit, self.value),
unit,
}
}
}
impl FromStr for Length {
type Err = LengthError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.trim();
let number_str = s.trim_end_matches(|c: char| c.is_alphabetic());
let unit_str = &s[number_str.len()..];
let number_str = number_str.trim();
let unit = if unit_str.is_empty() {
Unit::Px
} else {
unit_str.parse::<Unit>()?
};
Ok(Self::new(number_str.parse::<f64>()?, unit))
}
}
impl From<Unit> for Length {
fn from(value: Unit) -> Self {
Self::new(1.0f64, value)
}
}
impl From<&'_ Unit> for Length {
fn from(value: &'_ Unit) -> Self {
Self::new(1.0f64, *value)
}
}
impl<F: Float + AsPrimitive<f64>> From<F> for Length {
fn from(value: F) -> Self {
Self::new(value, Unit::Px)
}
}
impl std::ops::Add<Length> for Length {
type Output = Self;
fn add(self, rhs: Length) -> Self::Output {
Self {
value: self.value + rhs.convert_to(self.unit).value,
unit: self.unit,
}
}
}
impl std::ops::Sub<Length> for Length {
type Output = Self;
fn sub(self, rhs: Length) -> Self::Output {
Self {
value: self.value - rhs.convert_to(self.unit).value,
unit: self.unit,
}
}
}
impl std::ops::Neg for Length {
type Output = Self;
fn neg(self) -> Self::Output {
Self {
value: -self.value,
unit: self.unit,
}
}
}
impl<F: Float + AsPrimitive<f64>> std::ops::Add<F> for Length {
type Output = Self;
fn add(self, rhs: F) -> Self::Output {
Self {
value: self.value + Unit::Px.convert_to(&self.unit, rhs.as_()),
unit: self.unit,
}
}
}
impl<F: Float + AsPrimitive<f64>> std::ops::Sub<F> for Length {
type Output = Self;
fn sub(self, rhs: F) -> Self::Output {
Self {
value: self.value - Unit::Px.convert_to(&self.unit, rhs.as_()),
unit: self.unit,
}
}
}
impl<F: Float + AsPrimitive<f64>> std::ops::Mul<F> for Length {
type Output = Self;
fn mul(self, rhs: F) -> Self::Output {
Self {
value: self.value * rhs.as_(),
unit: self.unit,
}
}
}
impl<F: Float + AsPrimitive<f64>> std::ops::Div<F> for Length {
type Output = Self;
fn div(self, rhs: F) -> Self::Output {
Self {
value: self.value / rhs.as_(),
unit: self.unit,
}
}
}
macro_rules! length_trait_impl {
($t:ty) => {
impl From<Length> for $t {
fn from(length: Length) -> Self {
length.to_px()
}
}
impl From<&'_ Length> for $t {
fn from(length: &'_ Length) -> Self {
length.to_px()
}
}
impl std::ops::Add<Length> for $t {
type Output = Length;
fn add(self, rhs: Length) -> Self::Output {
rhs.add(self)
}
}
impl std::ops::Sub<Length> for $t {
type Output = Length;
fn sub(self, rhs: Length) -> Self::Output {
-rhs.sub(self)
}
}
impl std::ops::Mul<Length> for $t {
type Output = Length;
fn mul(self, rhs: Length) -> Self::Output {
rhs.mul(self)
}
}
};
}
length_trait_impl!(f32);
length_trait_impl!(f64);
pub struct LengthSampler {
sampler: rand::distributions::uniform::UniformFloat<f64>,
unit: Unit,
}
impl UniformSampler for LengthSampler {
type X = Length;
fn new<B1, B2>(low: B1, high: B2) -> Self
where
B1: SampleBorrow<Self::X> + Sized,
B2: SampleBorrow<Self::X> + Sized,
{
let unit = low.borrow().unit;
let low = low.borrow().value;
let high = high.borrow().convert_to(unit).value;
Self {
sampler: rand::distributions::uniform::UniformFloat::new(low, high),
unit,
}
}
fn new_inclusive<B1, B2>(low: B1, high: B2) -> Self
where
B1: SampleBorrow<Self::X> + Sized,
B2: SampleBorrow<Self::X> + Sized,
{
let unit = low.borrow().unit;
let low = low.borrow().value;
let high = high.borrow().convert_to(unit).value;
Self {
sampler: rand::distributions::uniform::UniformFloat::new_inclusive(low, high),
unit,
}
}
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Self::X {
Self::X::new(self.sampler.sample(rng), self.unit)
}
}
impl SampleUniform for Length {
type Sampler = LengthSampler;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_length_from_str() {
assert_eq!("1.0px".parse::<Length>().unwrap(), 1.0 * Unit::Px);
assert_eq!("1.0in".parse::<Length>().unwrap(), 1.0 * Unit::In);
assert_eq!("1.0 ft".parse::<Length>().unwrap(), 1.0 * Unit::Ft);
assert_eq!("1.0yd".parse::<Length>().unwrap(), 1.0 * Unit::Yd);
assert_eq!("1.0mi".parse::<Length>().unwrap(), 1.0 * Unit::Mi);
assert_eq!("1.mm".parse::<Length>().unwrap(), 1.0 * Unit::Mm);
assert_eq!("1.0 cm".parse::<Length>().unwrap(), 1.0 * Unit::Cm);
assert_eq!("1m".parse::<Length>().unwrap(), 1.0 * Unit::M);
assert_eq!("1.0km".parse::<Length>().unwrap(), 1.0 * Unit::Km);
assert_eq!("1.0pc".parse::<Length>().unwrap(), 1.0 * Unit::Pc);
assert_eq!("1.0pt".parse::<Length>().unwrap(), 1.0 * Unit::Pt);
assert_eq!("1.0".parse::<Length>().unwrap(), 1.0 * Unit::Px);
assert_eq!("1.".parse::<Length>().unwrap(), 1.0 * Unit::Px);
assert_eq!(".9e-4m".parse::<Length>().unwrap(), 0.9e-4 * Unit::M);
}
}