1use core::cmp::Ordering;
7use core::fmt;
8use core::ops::{Add, Div, Mul, Neg, Sub};
9
10use crate::error::Error;
11
12pub const DIM_PREC: usize = 256;
14
15fn gcd_u(mut a: u128, mut b: u128) -> u128 {
16 while b != 0 {
17 let t = a % b;
18 a = b;
19 b = t;
20 }
21 a
22}
23
24#[derive(Clone, Debug)]
38pub struct Dim {
39 num: i128,
40 den: i128,
41 nan: bool,
42}
43
44impl Dim {
45 fn raw(num: i128, den: i128) -> Self {
46 if den == 0 {
47 return Self::nan();
48 }
49 if num == 0 {
50 return Self {
51 num: 0,
52 den: 1,
53 nan: false,
54 };
55 }
56 let g = gcd_u(num.unsigned_abs(), den.unsigned_abs());
57 let g = i128::try_from(g).unwrap_or(1);
58 let mut n = num / g;
59 let mut d = den / g;
60 if d < 0 {
61 n = -n;
62 d = -d;
63 }
64 Self {
65 num: n,
66 den: d,
67 nan: false,
68 }
69 }
70
71 fn nan() -> Self {
72 Self {
73 num: 0,
74 den: 1,
75 nan: true,
76 }
77 }
78
79 fn binop(
80 a: &Self,
81 b: &Self,
82 f: impl Fn(i128, i128, i128, i128) -> Option<(i128, i128)>,
83 ) -> Self {
84 if a.nan || b.nan {
85 return Self::nan();
86 }
87 match f(a.num, a.den, b.num, b.den) {
88 Some((n, d)) => Self::raw(n, d),
89 None => Self::nan(),
90 }
91 }
92
93 #[must_use]
95 pub fn zero() -> Self {
96 Self::raw(0, 1)
97 }
98
99 #[must_use]
101 pub fn one() -> Self {
102 Self::raw(1, 1)
103 }
104
105 #[must_use]
107 pub fn from_i64(v: i64) -> Self {
108 Self::raw(i128::from(v), 1)
109 }
110
111 #[must_use]
113 pub fn ratio(num: i64, den: i64) -> Self {
114 Self::raw(i128::from(num), i128::from(den))
115 }
116
117 #[must_use]
119 pub fn parse(s: &str) -> Self {
120 let t = s.trim();
121 if t.is_empty() || t.eq_ignore_ascii_case("nan") {
122 return Self::nan();
123 }
124 let mut rest = t;
125 let neg = if let Some(r) = rest.strip_prefix('-') {
126 rest = r;
127 true
128 } else if let Some(r) = rest.strip_prefix('+') {
129 rest = r;
130 false
131 } else {
132 false
133 };
134 let (mant, exp_s) = match rest.find(['e', 'E']) {
135 Some(i) => (&rest[..i], Some(&rest[i + 1..])),
136 None => (rest, None),
137 };
138 let (ip, fp) = match mant.find('.') {
139 Some(i) => (&mant[..i], &mant[i + 1..]),
140 None => (mant, ""),
141 };
142 if ip.is_empty() && fp.is_empty() {
143 return Self::nan();
144 }
145 if !ip.chars().all(|c| c.is_ascii_digit()) || !fp.chars().all(|c| c.is_ascii_digit()) {
146 return Self::nan();
147 }
148 let mut num: i128 = 0;
149 for c in ip.bytes().chain(fp.bytes()) {
150 let d = i128::from(c - b'0');
151 num = match num.checked_mul(10).and_then(|n| n.checked_add(d)) {
152 Some(n) => n,
153 None => return Self::nan(),
154 };
155 }
156 let mut den = 1i128;
157 for _ in 0..fp.len() {
158 den = match den.checked_mul(10) {
159 Some(d) => d,
160 None => return Self::nan(),
161 };
162 }
163 if let Some(es) = exp_s {
164 let e: i32 = match es.parse() {
165 Ok(v) => v,
166 Err(_) => return Self::nan(),
167 };
168 if e > 0 {
169 for _ in 0..e {
170 num = match num.checked_mul(10) {
171 Some(n) => n,
172 None => return Self::nan(),
173 };
174 }
175 } else {
176 for _ in 0..(-e) {
177 den = match den.checked_mul(10) {
178 Some(d) => d,
179 None => return Self::nan(),
180 };
181 }
182 }
183 }
184 if neg {
185 num = -num;
186 }
187 Self::raw(num, den)
188 }
189
190 #[must_use]
192 pub fn from_font_units(units: i64, units_per_em: u16) -> Self {
193 Self::raw(i128::from(units), i128::from(units_per_em))
194 }
195
196 #[must_use]
198 pub fn mu() -> Self {
199 Self::ratio(1, 18)
200 }
201
202 #[must_use]
204 pub fn to_mu(&self) -> Self {
205 self.clone() * Self::from_i64(18)
206 }
207
208 #[must_use]
210 pub fn from_mu(mu: &Self) -> Self {
211 mu.clone() / Self::from_i64(18)
212 }
213
214 #[must_use]
216 pub fn abs(&self) -> Self {
217 if self.nan {
218 return Self::nan();
219 }
220 if self.num == i128::MIN {
221 return Self::nan();
222 }
223 Self::raw(self.num.abs(), self.den)
224 }
225
226 #[must_use]
228 pub fn max(&self, other: &Self) -> Self {
229 match self.cmp(other) {
230 Some(Ordering::Less) => other.clone(),
231 _ => self.clone(),
232 }
233 }
234
235 #[must_use]
237 pub fn min(&self, other: &Self) -> Self {
238 match self.cmp(other) {
239 Some(Ordering::Greater) => other.clone(),
240 _ => self.clone(),
241 }
242 }
243
244 #[must_use]
246 pub fn clamp_nonneg(&self) -> Self {
247 self.max(&Self::zero())
248 }
249
250 #[must_use]
252 pub fn is_nan(&self) -> bool {
253 self.nan
254 }
255
256 #[must_use]
258 pub fn is_zero(&self) -> bool {
259 !self.nan && self.num == 0
260 }
261
262 #[must_use]
264 pub fn to_dec_string(&self) -> String {
265 if self.nan {
266 return "NaN".into();
267 }
268 format_rational(self.num, self.den)
269 }
270
271 #[must_use]
273 pub fn to_svg_string(&self) -> String {
274 self.to_dec_string()
275 }
276
277 #[must_use]
279 pub fn from_ieee32_bits(bits: u32) -> Self {
280 let sign = (bits >> 31) != 0;
281 let exp = (bits >> 23) & 0xff;
282 let frac = bits & 0x7f_ffff;
283 if exp == 255 {
284 return Self::nan();
285 }
286 let (num, den, sh) = if exp == 0 {
287 if frac == 0 {
288 return Self::zero();
289 }
290 (i128::from(frac), 1i128, 149i32)
291 } else {
292 (i128::from(frac) + (1i128 << 23), 1i128, 150i32 - exp as i32)
293 };
294 let mut n = num;
295 let mut d = den;
296 if sh >= 0 {
297 for _ in 0..sh {
298 d = match d.checked_mul(2) {
299 Some(v) => v,
300 None => return Self::nan(),
301 };
302 }
303 } else {
304 for _ in 0..(-sh) {
305 n = match n.checked_mul(2) {
306 Some(v) => v,
307 None => return Self::nan(),
308 };
309 }
310 }
311 if sign {
312 n = -n;
313 }
314 Self::raw(n, d)
315 }
316
317 #[must_use]
319 pub fn to_ieee32_bits(&self) -> u32 {
320 if self.nan {
321 return 0x7fc0_0000;
322 }
323 if self.num == 0 {
324 return 0;
325 }
326 let sign = if self.num < 0 { 1u32 << 31 } else { 0 };
327 let n = self.num.unsigned_abs();
328 let d = self.den.unsigned_abs();
329 let mut e: i32 = 0;
331 if n < d {
333 let mut t = n;
334 while t < d {
335 t = match t.checked_mul(2) {
336 Some(v) => v,
337 None => break,
338 };
339 e -= 1;
340 if e < -149 {
341 return sign;
342 }
343 }
344 } else {
345 let mut t = d;
346 while t <= n / 2 {
347 t = match t.checked_mul(2) {
348 Some(v) => v,
349 None => break,
350 };
351 e += 1;
352 if e > 127 {
353 return sign | 0x7f80_0000;
354 }
355 }
356 }
357 let mut shift = 23i32 - e;
359 let mut num = n;
360 let den = d;
361 while shift > 0 {
362 num = match num.checked_mul(2) {
363 Some(v) => v,
364 None => return sign | 0x7f80_0000,
365 };
366 shift -= 1;
367 }
368 while shift < 0 {
369 num /= 2;
370 shift += 1;
371 }
372 let mut mant = num / den;
373 let rem = num % den;
374 if rem * 2 > den || (rem * 2 == den && mant & 1 == 1) {
376 mant += 1;
377 }
378 if mant >= (1u128 << 24) {
379 mant >>= 1;
380 e += 1;
381 }
382 if e > 127 {
383 return sign | 0x7f80_0000;
384 }
385 if e < -126 {
386 let sub_shift = -126 - e;
388 if sub_shift >= 24 {
389 return sign;
390 }
391 mant >>= sub_shift as u32;
392 let frac = (mant as u32) & 0x7f_ffff;
393 return sign | frac;
394 }
395 let biased = (e + 127) as u32;
396 let frac = (mant as u32) & 0x7f_ffff;
397 sign | (biased << 23) | frac
398 }
399
400 pub fn floor_to_u32(&self) -> Result<u32, Error> {
402 if self.is_nan() {
403 return Err(Error::InvalidOption {
404 what: "dimension is NaN".into(),
405 });
406 }
407 if matches!(self.cmp(&Self::zero()), Some(Ordering::Less)) {
408 return Err(Error::InvalidOption {
409 what: "negative dimension".into(),
410 });
411 }
412 const MAX: u32 = 1 << 20;
413 if matches!(
414 self.cmp(&Self::from_i64(i64::from(MAX))),
415 Some(Ordering::Greater) | Some(Ordering::Equal)
416 ) {
417 return Err(Error::InvalidOption {
418 what: "dimension exceeds raster limit".into(),
419 });
420 }
421 let q = (self.num / self.den) as u32;
422 Ok(q)
423 }
424
425 pub fn ceil_to_u32(&self) -> Result<u32, Error> {
427 let floor = self.floor_to_u32()?;
428 if self.eq_dim(&Self::from_i64(i64::from(floor))) {
429 Ok(floor)
430 } else {
431 floor.checked_add(1).ok_or_else(|| Error::InvalidOption {
432 what: "dimension overflow".into(),
433 })
434 }
435 }
436
437 #[must_use]
439 #[allow(clippy::should_implement_trait)]
440 pub fn cmp(&self, other: &Self) -> Option<Ordering> {
441 if self.nan || other.nan {
442 return None;
443 }
444 let left = self.num.checked_mul(other.den)?;
446 let right = other.num.checked_mul(self.den)?;
447 Some(left.cmp(&right))
448 }
449
450 #[must_use]
452 pub fn eq_dim(&self, other: &Self) -> bool {
453 matches!(self.cmp(other), Some(Ordering::Equal))
454 }
455}
456
457fn format_rational(num: i128, den: i128) -> String {
458 if num == 0 {
459 return "0.0".into();
460 }
461 let neg = num < 0;
462 let mut n = num.unsigned_abs();
463 let d = den.unsigned_abs();
464 let ip = n / d;
465 n %= d;
466 let mut s = String::new();
467 if neg {
468 s.push('-');
469 }
470 if n == 0 {
471 s.push_str(&ip.to_string());
472 s.push_str(".0");
473 return s;
474 }
475 s.push_str(&ip.to_string());
476 s.push('.');
477 for _ in 0..24 {
479 if n == 0 {
480 break;
481 }
482 n *= 10;
483 let digit = n / d;
484 s.push(char::from(b'0' + digit as u8));
485 n %= d;
486 }
487 while s.ends_with('0') && !s.ends_with(".0") {
489 s.pop();
490 }
491 s
492}
493
494impl PartialEq for Dim {
495 fn eq(&self, other: &Self) -> bool {
496 self.eq_dim(other)
497 }
498}
499
500impl Eq for Dim {}
501
502impl PartialOrd for Dim {
503 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
504 self.cmp(other)
505 }
506}
507
508impl fmt::Display for Dim {
509 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
510 f.write_str(&self.to_dec_string())
511 }
512}
513
514impl Neg for Dim {
515 type Output = Self;
516
517 fn neg(self) -> Self {
518 if self.nan {
519 return Self::nan();
520 }
521 Self::raw(-self.num, self.den)
522 }
523}
524
525fn add_r(a: i128, b: i128, c: i128, d: i128) -> Option<(i128, i128)> {
526 let ad = a.checked_mul(d)?;
527 let cb = c.checked_mul(b)?;
528 let n = ad.checked_add(cb)?;
529 let den = b.checked_mul(d)?;
530 Some((n, den))
531}
532
533fn sub_r(a: i128, b: i128, c: i128, d: i128) -> Option<(i128, i128)> {
534 let ad = a.checked_mul(d)?;
535 let cb = c.checked_mul(b)?;
536 let n = ad.checked_sub(cb)?;
537 let den = b.checked_mul(d)?;
538 Some((n, den))
539}
540
541fn mul_r(a: i128, b: i128, c: i128, d: i128) -> Option<(i128, i128)> {
542 let n = a.checked_mul(c)?;
543 let den = b.checked_mul(d)?;
544 Some((n, den))
545}
546
547fn div_r(a: i128, b: i128, c: i128, d: i128) -> Option<(i128, i128)> {
548 if c == 0 {
549 return None;
550 }
551 let n = a.checked_mul(d)?;
552 let den = b.checked_mul(c)?;
553 Some((n, den))
554}
555
556macro_rules! impl_dim_op {
557 ($Trait:ident, $method:ident, $helper:ident) => {
558 impl $Trait for Dim {
559 type Output = Dim;
560 fn $method(self, rhs: Dim) -> Dim {
561 Dim::binop(&self, &rhs, $helper)
562 }
563 }
564 impl $Trait<&Dim> for Dim {
565 type Output = Dim;
566 fn $method(self, rhs: &Dim) -> Dim {
567 Dim::binop(&self, rhs, $helper)
568 }
569 }
570 impl $Trait<Dim> for &Dim {
571 type Output = Dim;
572 fn $method(self, rhs: Dim) -> Dim {
573 Dim::binop(self, &rhs, $helper)
574 }
575 }
576 impl $Trait for &Dim {
577 type Output = Dim;
578 fn $method(self, rhs: &Dim) -> Dim {
579 Dim::binop(self, rhs, $helper)
580 }
581 }
582 };
583}
584
585impl_dim_op!(Add, add, add_r);
586impl_dim_op!(Sub, sub, sub_r);
587impl_dim_op!(Mul, mul, mul_r);
588impl_dim_op!(Div, div, div_r);
589
590#[cfg(test)]
591mod tests {
592 use super::Dim;
593
594 #[test]
595 fn half_and_font_units() {
596 assert!(Dim::ratio(1, 2).eq_dim(&(&Dim::one() / &Dim::from_i64(2))));
597 let w = Dim::from_font_units(479, 1000);
598 assert_eq!(w.to_dec_string(), "0.479");
599 assert_eq!((Dim::from_i64(12) * w).to_dec_string(), "5.748");
600 }
601
602 #[test]
603 fn ieee32_one() {
604 let one = Dim::from_ieee32_bits(0x3f80_0000);
605 assert!(one.eq_dim(&Dim::one()));
606 assert_eq!(Dim::one().to_ieee32_bits(), 0x3f80_0000);
607 }
608}