rsass/value/
numeric.rs

1use super::{Number, Unit, UnitSet};
2use crate::output::{Format, Formatted};
3use std::fmt::{self, Display};
4use std::ops::{Div, Mul, Neg};
5
6/// A Numeric value is a [`Number`] with a [`Unit`] (which may be
7/// `Unit::None`).
8#[derive(Clone)]
9pub struct Numeric {
10    /// The number value of this numeric.
11    pub value: Number,
12    /// The unit of this numeric.
13    pub unit: UnitSet,
14}
15
16impl fmt::Debug for Numeric {
17    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
18        write!(f, "{:?}; {:?}", self.value, self.unit)
19    }
20}
21
22impl Numeric {
23    /// Create a new numeric value.
24    ///
25    /// The value can be given as anything that can be converted into
26    /// a [`Number`], e.g. an [`isize`] or a [`f64`].
27    pub fn new<V: Into<Number>, U: Into<UnitSet>>(value: V, unit: U) -> Self {
28        Self {
29            value: value.into(),
30            unit: unit.into(),
31        }
32    }
33    /// Create a new numeric value with no unit.
34    pub fn scalar(value: impl Into<Number>) -> Self {
35        Self {
36            value: value.into(),
37            unit: UnitSet::scalar(),
38        }
39    }
40    /// Create a new numeric that is a percentage from a number where
41    /// 1 maps to 100%.
42    pub(crate) fn percentage(value: impl Into<Number>) -> Self {
43        Self::new(value.into() * 100, Unit::Percent)
44    }
45
46    /// Convert this numeric value to a given unit, if possible.
47    ///
48    /// # Examples
49    /// ```
50    /// # use rsass::value::{Numeric, Unit};
51    /// let inch = Numeric::new(1, Unit::In);
52    /// assert_eq!(inch.as_unit(Unit::Mm).unwrap() * 5, 127.into());
53    /// assert_eq!(inch.as_unit(Unit::Deg), None);
54    /// ```
55    pub fn as_unit(&self, unit: Unit) -> Option<Number> {
56        self.unit
57            .scale_to_unit(&unit)
58            .map(|scale| &self.value * &Number::from(scale))
59    }
60    /// Convert this numeric value to a given unit, if possible.
61    ///
62    /// # Examples
63    /// ```
64    /// # use rsass::value::{Numeric, Unit};
65    /// let inch = Numeric::new(1, Unit::In);
66    /// assert_eq!(inch.as_unit(Unit::Mm).unwrap() * 5, 127.into());
67    /// assert_eq!(inch.as_unit(Unit::Deg), None);
68    /// ```
69    pub fn as_unitset(&self, unit: &UnitSet) -> Option<Number> {
70        self.unit
71            .scale_to(unit)
72            .map(|scale| &self.value * &Number::from(scale))
73    }
74
75    /// Convert this numeric value to a given unit, if possible.
76    ///
77    /// Like [`as_unit`](Self::as_unit), except a unitless numeric is
78    /// considered to be the expected unit.
79    pub fn as_unit_def(&self, unit: Unit) -> Option<Number> {
80        if self.is_no_unit() {
81            Some(self.value.clone())
82        } else {
83            self.as_unit(unit)
84        }
85    }
86
87    /// Return true if this value has no unit.
88    pub fn is_no_unit(&self) -> bool {
89        self.unit.is_none()
90    }
91
92    /// Get a reference to this `Value` bound to an output format.
93    pub fn format(&self, format: Format) -> Formatted<Self> {
94        Formatted {
95            value: self,
96            format,
97        }
98    }
99}
100
101impl PartialEq for Numeric {
102    fn eq(&self, other: &Self) -> bool {
103        self.partial_cmp(other) == Some(std::cmp::Ordering::Equal)
104    }
105}
106impl Eq for Numeric {}
107
108impl PartialOrd for Numeric {
109    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
110        if self.unit == other.unit {
111            self.value.partial_cmp(&other.value)
112        } else if self.is_no_unit() || other.is_no_unit() {
113            match self.value.partial_cmp(&other.value) {
114                Some(std::cmp::Ordering::Equal) => None,
115                other => other,
116            }
117        } else if let Some(scaled) = other.as_unitset(&self.unit) {
118            self.value.partial_cmp(&scaled)
119        } else {
120            None
121        }
122    }
123}
124
125impl Neg for &Numeric {
126    type Output = Numeric;
127    fn neg(self) -> Self::Output {
128        Numeric {
129            value: -&self.value,
130            unit: self.unit.clone(),
131        }
132    }
133}
134
135impl Div for &Numeric {
136    type Output = Numeric;
137    fn div(self, rhs: Self) -> Self::Output {
138        Numeric {
139            value: &self.value / &rhs.value,
140            unit: &self.unit / &rhs.unit,
141        }
142        .simplify()
143    }
144}
145impl Numeric {
146    fn simplify(mut self) -> Self {
147        let scale = self.unit.simplify();
148        self.value = (f64::from(self.value) * scale).into();
149        self
150    }
151}
152
153impl Mul for &Numeric {
154    type Output = Numeric;
155    fn mul(self, rhs: Self) -> Self::Output {
156        Numeric {
157            value: &self.value * &rhs.value,
158            unit: &self.unit * &rhs.unit,
159        }
160        .simplify()
161    }
162}
163
164impl Display for Formatted<'_, Numeric> {
165    fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result {
166        let t = self.value.clone().simplify();
167        t.value.format(self.format).fmt(out)?;
168        if !t.value.is_finite() && t.unit.is_pos() {
169            out.write_str(" * 1")?;
170        }
171        t.unit.fmt(out)
172    }
173}