use std::fmt::{Display, Formatter};
use std::str::FromStr;
use num_traits::{AsPrimitive, Float};
use crate::Length;
#[derive(thiserror::Error, Debug, PartialEq)]
pub enum UnitError {
#[error("Unrecognised unit '{0}'")]
UnrecognisedError(String),
}
#[derive(Clone, Copy, Default, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum Unit {
#[default]
Px,
In,
Ft,
Yd,
Mi,
Mm,
Cm,
M,
Km,
Pc,
Pt,
}
pub const UNITS: &[Unit] = &[
Unit::Px,
Unit::In,
Unit::Ft,
Unit::Yd,
Unit::Mi,
Unit::Mm,
Unit::Cm,
Unit::M,
Unit::Km,
Unit::Pc,
Unit::Pt,
];
pub const SMALL_UNITS: &[Unit] = &[
Unit::Px,
Unit::In,
Unit::Ft,
Unit::Mm,
Unit::Cm,
Unit::M,
Unit::Pc,
Unit::Pt,
];
impl Unit {
#[must_use]
#[allow(clippy::missing_panics_doc)]
pub fn to_px<F: Float>(&self) -> F {
match &self {
Self::Px => F::one(),
Self::In => F::from(96.0).unwrap(),
Self::Ft => F::from(12.0 * 96.0).unwrap(),
Self::Yd => F::from(36.0 * 96.0).unwrap(),
Self::Mi => F::from(1760.0 * 36.0 * 96.0).unwrap(),
Self::Mm => F::from(96.0 / 25.4).unwrap(),
Self::Cm => F::from(96.0 / 2.54).unwrap(),
Self::M => F::from(100.0 * 96.0 / 2.54).unwrap(),
Self::Km => F::from(100_000.0 * 96.0 / 2.54).unwrap(),
Self::Pc => F::from(16.0).unwrap(),
Self::Pt => F::from(96.0 / 72.0).unwrap(),
}
}
#[must_use]
#[inline]
pub fn convert<F: Float>(&self, pixel_value: F) -> F {
self.convert_from(&Unit::Px, pixel_value)
}
#[must_use]
#[inline]
pub fn convert_from<F: Float>(&self, from_unit: &Unit, value: F) -> F {
value * from_unit.to_px() / self.to_px()
}
#[must_use]
#[inline]
pub fn convert_to<F: Float>(&self, to_unit: &Unit, value: F) -> F {
value * self.to_px() / to_unit.to_px()
}
#[must_use]
pub const fn to_str(&self) -> &'static str {
match &self {
Self::Px => "px",
Self::In => "in",
Self::Ft => "ft",
Self::Yd => "yd",
Self::Mi => "mi",
Self::Mm => "mm",
Self::Cm => "cm",
Self::M => "m",
Self::Km => "km",
Self::Pc => "pc",
Self::Pt => "pt",
}
}
}
impl FromStr for Unit {
type Err = UnitError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
match value.to_lowercase().as_str() {
"px" | "pixel" => Ok(Unit::Px),
"in" | "inch" => Ok(Unit::In),
"ft" | "feet" => Ok(Unit::Ft),
"yd" | "yard" => Ok(Unit::Yd),
"mi" | "mile" | "miles" => Ok(Unit::Mi),
"mm" | "millimeter" | "millimetre" => Ok(Unit::Mm),
"cm" | "centimeter" | "centimetre" => Ok(Unit::Cm),
"m" | "meter" | "metre" => Ok(Unit::M),
"km" | "kilometer" | "kilometre" => Ok(Unit::Km),
"pc" | "pica" => Ok(Unit::Pc),
"pt" | "point" | "points" => Ok(Unit::Pt),
_ => Err(UnitError::UnrecognisedError(value.to_owned())),
}
}
}
impl Display for Unit {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.to_str().fmt(f)
}
}
impl<F: Float + AsPrimitive<f64>> std::ops::Mul<F> for Unit {
type Output = Length;
fn mul(self, rhs: F) -> Self::Output {
Self::Output::new(rhs, self)
}
}
impl<F: Float + AsPrimitive<f64>> std::ops::Mul<F> for &'_ Unit {
type Output = Length;
fn mul(self, rhs: F) -> Self::Output {
Self::Output::new(rhs, *self)
}
}
macro_rules! unit_trait_impl {
($t:ty) => {
impl From<Unit> for $t {
fn from(value: Unit) -> $t {
value.to_px()
}
}
impl From<&'_ Unit> for $t {
fn from(value: &'_ Unit) -> $t {
value.to_px()
}
}
impl std::ops::Mul<Unit> for $t {
type Output = Length;
fn mul(self, rhs: Unit) -> Self::Output {
Self::Output::new(self, rhs)
}
}
impl std::ops::Mul<&'_ Unit> for $t {
type Output = Length;
fn mul(self, rhs: &'_ Unit) -> Self::Output {
Self::Output::new(self, *rhs)
}
}
};
}
unit_trait_impl!(f32);
unit_trait_impl!(f64);