1use core::cmp::Ordering;
8use core::fmt;
9use core::ops::{Add, Div, Mul, Neg, Sub};
10use std::sync::Arc;
11
12use crate::error::Error;
13use zenith_float::{Consts, ExactNum, Radix, RoundingMode};
14
15pub const DIM_PREC: usize = 256;
17const RM: RoundingMode = RoundingMode::ToEven;
18
19fn consts() -> Consts {
20 Consts::new().expect("zenith-float constants cache")
21}
22
23fn wrap(n: ExactNum) -> Dim {
24 Dim { inner: Arc::new(n) }
25}
26
27#[derive(Clone, Debug)]
42pub struct Dim {
43 inner: Arc<ExactNum>,
44}
45
46impl Dim {
47 #[must_use]
49 pub fn zero() -> Self {
50 wrap(ExactNum::new(DIM_PREC))
51 }
52
53 #[must_use]
55 pub fn one() -> Self {
56 wrap(ExactNum::from_i32(1, DIM_PREC))
57 }
58
59 #[must_use]
61 pub fn from_i64(v: i64) -> Self {
62 wrap(ExactNum::from_i64(v, DIM_PREC))
63 }
64
65 #[must_use]
67 pub fn ratio(num: i64, den: i64) -> Self {
68 Self::from_i64(num) / Self::from_i64(den)
69 }
70
71 #[must_use]
73 pub fn parse(s: &str) -> Self {
74 let mut cc = consts();
75 wrap(ExactNum::parse(s, Radix::Dec, DIM_PREC, RM, &mut cc))
76 }
77
78 #[must_use]
80 pub fn from_font_units(units: i64, units_per_em: u16) -> Self {
81 Self::from_i64(units) / Self::from_i64(i64::from(units_per_em))
82 }
83
84 #[must_use]
86 pub fn mu() -> Self {
87 Self::ratio(1, 18)
88 }
89
90 #[must_use]
92 pub fn to_mu(&self) -> Self {
93 self.clone() * Self::from_i64(18)
94 }
95
96 #[must_use]
98 pub fn from_mu(mu: &Self) -> Self {
99 mu.clone() / Self::from_i64(18)
100 }
101
102 #[must_use]
104 pub fn abs(&self) -> Self {
105 wrap(self.inner.as_ref().abs())
106 }
107
108 #[must_use]
110 pub fn max(&self, other: &Self) -> Self {
111 match self.cmp(other) {
112 Some(Ordering::Less) => other.clone(),
113 _ => self.clone(),
114 }
115 }
116
117 #[must_use]
119 pub fn min(&self, other: &Self) -> Self {
120 match self.cmp(other) {
121 Some(Ordering::Greater) => other.clone(),
122 _ => self.clone(),
123 }
124 }
125
126 #[must_use]
128 pub fn clamp_nonneg(&self) -> Self {
129 self.max(&Self::zero())
130 }
131
132 #[must_use]
134 pub fn is_nan(&self) -> bool {
135 self.inner.is_nan()
136 }
137
138 #[must_use]
140 pub fn is_zero(&self) -> bool {
141 matches!(self.inner.cmp(&ExactNum::new(DIM_PREC)), Some(0))
142 }
143
144 #[must_use]
146 pub fn to_dec_string(&self) -> String {
147 let mut cc = consts();
148 self.inner
149 .format(Radix::Dec, RM, &mut cc)
150 .unwrap_or_else(|_| "NaN".into())
151 }
152
153 #[must_use]
155 pub fn to_svg_string(&self) -> String {
156 self.to_dec_string()
157 }
158
159 #[must_use]
161 pub fn from_ieee32_bits(bits: u32) -> Self {
162 wrap(zenith_float::Ieee32::from_bits(bits).to_exact(DIM_PREC))
163 }
164
165 #[must_use]
170 pub fn to_ieee32_bits(&self) -> u32 {
171 zenith_float::Ieee32::from_exact(self.inner.as_ref()).to_bits()
172 }
173
174 pub fn floor_to_u32(&self) -> Result<u32, Error> {
176 if self.is_nan() {
177 return Err(Error::InvalidOption {
178 what: "dimension is NaN".into(),
179 });
180 }
181 if matches!(self.cmp(&Self::zero()), Some(Ordering::Less)) {
182 return Err(Error::InvalidOption {
183 what: "negative dimension".into(),
184 });
185 }
186 const MAX: u32 = 1 << 20;
187 if matches!(
188 self.cmp(&Self::from_i64(i64::from(MAX))),
189 Some(Ordering::Greater) | Some(Ordering::Equal)
190 ) {
191 return Err(Error::InvalidOption {
192 what: "dimension exceeds raster limit".into(),
193 });
194 }
195 let mut ans = 0u32;
196 let mut bit = 1u32 << 19;
197 while bit > 0 {
198 let cand = ans + bit;
199 if Self::from_i64(i64::from(cand))
200 .cmp(self)
201 .is_some_and(|o| o != Ordering::Greater)
202 {
203 ans = cand;
204 }
205 bit /= 2;
206 }
207 Ok(ans)
208 }
209
210 pub fn ceil_to_u32(&self) -> Result<u32, Error> {
212 let floor = self.floor_to_u32()?;
213 if self.eq_dim(&Self::from_i64(i64::from(floor))) {
214 Ok(floor)
215 } else {
216 floor.checked_add(1).ok_or_else(|| Error::InvalidOption {
217 what: "dimension overflow".into(),
218 })
219 }
220 }
221
222 #[must_use]
227 #[allow(clippy::should_implement_trait)]
228 pub fn cmp(&self, other: &Self) -> Option<Ordering> {
229 match self.inner.cmp(&other.inner) {
230 Some(0) => Some(Ordering::Equal),
231 Some(x) if x < 0 => Some(Ordering::Less),
232 Some(_) => Some(Ordering::Greater),
233 None => None,
234 }
235 }
236
237 #[must_use]
239 pub fn eq_dim(&self, other: &Self) -> bool {
240 matches!(self.cmp(other), Some(Ordering::Equal))
241 }
242}
243
244impl PartialEq for Dim {
245 fn eq(&self, other: &Self) -> bool {
246 self.eq_dim(other)
247 }
248}
249
250impl Eq for Dim {}
251
252impl PartialOrd for Dim {
253 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
254 self.cmp(other)
255 }
256}
257
258impl fmt::Display for Dim {
259 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
260 f.write_str(&self.to_dec_string())
261 }
262}
263
264fn add_e(a: &ExactNum, b: &ExactNum) -> ExactNum {
265 ExactNum::add(a, b, DIM_PREC, RM)
266}
267
268fn sub_e(a: &ExactNum, b: &ExactNum) -> ExactNum {
269 ExactNum::sub(a, b, DIM_PREC, RM)
270}
271
272fn mul_e(a: &ExactNum, b: &ExactNum) -> ExactNum {
273 ExactNum::mul(a, b, DIM_PREC, RM)
274}
275
276fn div_e(a: &ExactNum, b: &ExactNum) -> ExactNum {
277 ExactNum::div(a, b, DIM_PREC, RM)
278}
279
280impl Neg for Dim {
281 type Output = Self;
282
283 fn neg(self) -> Self {
284 wrap(self.inner.as_ref().neg())
285 }
286}
287
288macro_rules! impl_dim_op {
289 ($Trait:ident, $method:ident, $helper:ident) => {
290 impl $Trait for Dim {
291 type Output = Dim;
292 fn $method(self, rhs: Dim) -> Dim {
293 wrap($helper(self.inner.as_ref(), rhs.inner.as_ref()))
294 }
295 }
296 impl $Trait<&Dim> for Dim {
297 type Output = Dim;
298 fn $method(self, rhs: &Dim) -> Dim {
299 wrap($helper(self.inner.as_ref(), rhs.inner.as_ref()))
300 }
301 }
302 impl $Trait<Dim> for &Dim {
303 type Output = Dim;
304 fn $method(self, rhs: Dim) -> Dim {
305 wrap($helper(self.inner.as_ref(), rhs.inner.as_ref()))
306 }
307 }
308 impl $Trait for &Dim {
309 type Output = Dim;
310 fn $method(self, rhs: &Dim) -> Dim {
311 wrap($helper(self.inner.as_ref(), rhs.inner.as_ref()))
312 }
313 }
314 };
315}
316
317impl_dim_op!(Add, add, add_e);
318impl_dim_op!(Sub, sub, sub_e);
319impl_dim_op!(Mul, mul, mul_e);
320impl_dim_op!(Div, div, div_e);