use core::cmp::Ordering;
use core::fmt;
use core::ops::{Add, Div, Mul, Neg, Sub};
use std::sync::Arc;
use crate::error::Error;
use zenith_float::{Consts, ExactNum, Radix, RoundingMode};
pub const DIM_PREC: usize = 256;
const RM: RoundingMode = RoundingMode::ToEven;
fn consts() -> Consts {
Consts::new().expect("zenith-float constants cache")
}
fn wrap(n: ExactNum) -> Dim {
Dim { inner: Arc::new(n) }
}
#[derive(Clone, Debug)]
pub struct Dim {
inner: Arc<ExactNum>,
}
impl Dim {
#[must_use]
pub fn zero() -> Self {
wrap(ExactNum::new(DIM_PREC))
}
#[must_use]
pub fn one() -> Self {
wrap(ExactNum::from_i32(1, DIM_PREC))
}
#[must_use]
pub fn from_i64(v: i64) -> Self {
wrap(ExactNum::from_i64(v, DIM_PREC))
}
#[must_use]
pub fn ratio(num: i64, den: i64) -> Self {
Self::from_i64(num) / Self::from_i64(den)
}
#[must_use]
pub fn parse(s: &str) -> Self {
let mut cc = consts();
wrap(ExactNum::parse(s, Radix::Dec, DIM_PREC, RM, &mut cc))
}
#[must_use]
pub fn from_font_units(units: i64, units_per_em: u16) -> Self {
Self::from_i64(units) / Self::from_i64(i64::from(units_per_em))
}
#[must_use]
pub fn mu() -> Self {
Self::ratio(1, 18)
}
#[must_use]
pub fn to_mu(&self) -> Self {
self.clone() * Self::from_i64(18)
}
#[must_use]
pub fn from_mu(mu: &Self) -> Self {
mu.clone() / Self::from_i64(18)
}
#[must_use]
pub fn abs(&self) -> Self {
wrap(self.inner.as_ref().abs())
}
#[must_use]
pub fn max(&self, other: &Self) -> Self {
match self.cmp(other) {
Some(Ordering::Less) => other.clone(),
_ => self.clone(),
}
}
#[must_use]
pub fn min(&self, other: &Self) -> Self {
match self.cmp(other) {
Some(Ordering::Greater) => other.clone(),
_ => self.clone(),
}
}
#[must_use]
pub fn clamp_nonneg(&self) -> Self {
self.max(&Self::zero())
}
#[must_use]
pub fn is_nan(&self) -> bool {
self.inner.is_nan()
}
#[must_use]
pub fn is_zero(&self) -> bool {
matches!(self.inner.cmp(&ExactNum::new(DIM_PREC)), Some(0))
}
#[must_use]
pub fn to_dec_string(&self) -> String {
let mut cc = consts();
self.inner
.format(Radix::Dec, RM, &mut cc)
.unwrap_or_else(|_| "NaN".into())
}
#[must_use]
pub fn to_svg_string(&self) -> String {
self.to_dec_string()
}
#[must_use]
pub fn from_ieee32_bits(bits: u32) -> Self {
wrap(zenith_float::Ieee32::from_bits(bits).to_exact(DIM_PREC))
}
#[must_use]
pub fn to_ieee32_bits(&self) -> u32 {
zenith_float::Ieee32::from_exact(self.inner.as_ref()).to_bits()
}
pub fn floor_to_u32(&self) -> Result<u32, Error> {
if self.is_nan() {
return Err(Error::InvalidOption {
what: "dimension is NaN".into(),
});
}
if matches!(self.cmp(&Self::zero()), Some(Ordering::Less)) {
return Err(Error::InvalidOption {
what: "negative dimension".into(),
});
}
const MAX: u32 = 1 << 20;
if matches!(
self.cmp(&Self::from_i64(i64::from(MAX))),
Some(Ordering::Greater) | Some(Ordering::Equal)
) {
return Err(Error::InvalidOption {
what: "dimension exceeds raster limit".into(),
});
}
let mut ans = 0u32;
let mut bit = 1u32 << 19;
while bit > 0 {
let cand = ans + bit;
if Self::from_i64(i64::from(cand))
.cmp(self)
.is_some_and(|o| o != Ordering::Greater)
{
ans = cand;
}
bit /= 2;
}
Ok(ans)
}
pub fn ceil_to_u32(&self) -> Result<u32, Error> {
let floor = self.floor_to_u32()?;
if self.eq_dim(&Self::from_i64(i64::from(floor))) {
Ok(floor)
} else {
floor.checked_add(1).ok_or_else(|| Error::InvalidOption {
what: "dimension overflow".into(),
})
}
}
#[must_use]
#[allow(clippy::should_implement_trait)]
pub fn cmp(&self, other: &Self) -> Option<Ordering> {
match self.inner.cmp(&other.inner) {
Some(0) => Some(Ordering::Equal),
Some(x) if x < 0 => Some(Ordering::Less),
Some(_) => Some(Ordering::Greater),
None => None,
}
}
#[must_use]
pub fn eq_dim(&self, other: &Self) -> bool {
matches!(self.cmp(other), Some(Ordering::Equal))
}
}
impl PartialEq for Dim {
fn eq(&self, other: &Self) -> bool {
self.eq_dim(other)
}
}
impl Eq for Dim {}
impl PartialOrd for Dim {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.cmp(other)
}
}
impl fmt::Display for Dim {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.to_dec_string())
}
}
fn add_e(a: &ExactNum, b: &ExactNum) -> ExactNum {
ExactNum::add(a, b, DIM_PREC, RM)
}
fn sub_e(a: &ExactNum, b: &ExactNum) -> ExactNum {
ExactNum::sub(a, b, DIM_PREC, RM)
}
fn mul_e(a: &ExactNum, b: &ExactNum) -> ExactNum {
ExactNum::mul(a, b, DIM_PREC, RM)
}
fn div_e(a: &ExactNum, b: &ExactNum) -> ExactNum {
ExactNum::div(a, b, DIM_PREC, RM)
}
impl Neg for Dim {
type Output = Self;
fn neg(self) -> Self {
wrap(self.inner.as_ref().neg())
}
}
macro_rules! impl_dim_op {
($Trait:ident, $method:ident, $helper:ident) => {
impl $Trait for Dim {
type Output = Dim;
fn $method(self, rhs: Dim) -> Dim {
wrap($helper(self.inner.as_ref(), rhs.inner.as_ref()))
}
}
impl $Trait<&Dim> for Dim {
type Output = Dim;
fn $method(self, rhs: &Dim) -> Dim {
wrap($helper(self.inner.as_ref(), rhs.inner.as_ref()))
}
}
impl $Trait<Dim> for &Dim {
type Output = Dim;
fn $method(self, rhs: Dim) -> Dim {
wrap($helper(self.inner.as_ref(), rhs.inner.as_ref()))
}
}
impl $Trait for &Dim {
type Output = Dim;
fn $method(self, rhs: &Dim) -> Dim {
wrap($helper(self.inner.as_ref(), rhs.inner.as_ref()))
}
}
};
}
impl_dim_op!(Add, add, add_e);
impl_dim_op!(Sub, sub, sub_e);
impl_dim_op!(Mul, mul, mul_e);
impl_dim_op!(Div, div, div_e);