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]
282 pub fn as_ratio(&self) -> Option<(i128, i128)> {
283 (!self.nan).then_some((self.num, self.den))
284 }
285
286 #[must_use]
288 pub fn to_dec_string(&self) -> String {
289 if self.nan {
290 return "NaN".into();
291 }
292 format_rational(self.num, self.den)
293 }
294
295 #[must_use]
297 pub fn to_svg_string(&self) -> String {
298 self.to_dec_string()
299 }
300
301 #[must_use]
303 pub fn from_ieee32_bits(bits: u32) -> Self {
304 let sign = (bits >> 31) != 0;
305 let exp = (bits >> 23) & 0xff;
306 let frac = bits & 0x7f_ffff;
307 if exp == 255 {
308 return Self::nan();
309 }
310 let (num, den, sh) = if exp == 0 {
311 if frac == 0 {
312 return Self::zero();
313 }
314 (i128::from(frac), 1i128, 149i32)
315 } else {
316 (i128::from(frac) + (1i128 << 23), 1i128, 150i32 - exp as i32)
317 };
318 let mut n = num;
319 let mut d = den;
320 if sh >= 0 {
321 for _ in 0..sh {
322 d = match d.checked_mul(2) {
323 Some(v) => v,
324 None => return Self::nan(),
325 };
326 }
327 } else {
328 for _ in 0..(-sh) {
329 n = match n.checked_mul(2) {
330 Some(v) => v,
331 None => return Self::nan(),
332 };
333 }
334 }
335 if sign {
336 n = -n;
337 }
338 Self::raw(n, d)
339 }
340
341 #[must_use]
343 pub fn to_ieee32_bits(&self) -> u32 {
344 if self.nan {
345 return 0x7fc0_0000;
346 }
347 if self.num == 0 {
348 return 0;
349 }
350 let sign = if self.num < 0 { 1u32 << 31 } else { 0 };
351 let n = self.num.unsigned_abs();
352 let d = self.den.unsigned_abs();
353 let mut e: i32 = 0;
355 if n < d {
357 let mut t = n;
358 while t < d {
359 t = match t.checked_mul(2) {
360 Some(v) => v,
361 None => break,
362 };
363 e -= 1;
364 if e < -149 {
365 return sign;
366 }
367 }
368 } else {
369 let mut t = d;
370 while t <= n / 2 {
371 t = match t.checked_mul(2) {
372 Some(v) => v,
373 None => break,
374 };
375 e += 1;
376 if e > 127 {
377 return sign | 0x7f80_0000;
378 }
379 }
380 }
381 let mut shift = 23i32 - e;
383 let mut num = n;
384 let den = d;
385 while shift > 0 {
386 num = match num.checked_mul(2) {
387 Some(v) => v,
388 None => return sign | 0x7f80_0000,
389 };
390 shift -= 1;
391 }
392 while shift < 0 {
393 num /= 2;
394 shift += 1;
395 }
396 let mut mant = num / den;
397 let rem = num % den;
398 if rem * 2 > den || (rem * 2 == den && mant & 1 == 1) {
400 mant += 1;
401 }
402 if mant >= (1u128 << 24) {
403 mant >>= 1;
404 e += 1;
405 }
406 if e > 127 {
407 return sign | 0x7f80_0000;
408 }
409 if e < -126 {
410 let sub_shift = -126 - e;
412 if sub_shift >= 24 {
413 return sign;
414 }
415 mant >>= sub_shift as u32;
416 let frac = (mant as u32) & 0x7f_ffff;
417 return sign | frac;
418 }
419 let biased = (e + 127) as u32;
420 let frac = (mant as u32) & 0x7f_ffff;
421 sign | (biased << 23) | frac
422 }
423
424 pub fn floor_to_u32(&self) -> Result<u32, Error> {
426 if self.is_nan() {
427 return Err(Error::InvalidOption {
428 what: "dimension is NaN".into(),
429 });
430 }
431 if matches!(self.cmp(&Self::zero()), Some(Ordering::Less)) {
432 return Err(Error::InvalidOption {
433 what: "negative dimension".into(),
434 });
435 }
436 const MAX: u32 = 1 << 20;
437 if matches!(
438 self.cmp(&Self::from_i64(i64::from(MAX))),
439 Some(Ordering::Greater) | Some(Ordering::Equal)
440 ) {
441 return Err(Error::InvalidOption {
442 what: "dimension exceeds raster limit".into(),
443 });
444 }
445 let q = (self.num / self.den) as u32;
446 Ok(q)
447 }
448
449 pub fn ceil_to_u32(&self) -> Result<u32, Error> {
451 let floor = self.floor_to_u32()?;
452 if self.eq_dim(&Self::from_i64(i64::from(floor))) {
453 Ok(floor)
454 } else {
455 floor.checked_add(1).ok_or_else(|| Error::InvalidOption {
456 what: "dimension overflow".into(),
457 })
458 }
459 }
460
461 #[must_use]
463 #[allow(clippy::should_implement_trait)]
464 pub fn cmp(&self, other: &Self) -> Option<Ordering> {
465 if self.nan || other.nan {
466 return None;
467 }
468 let left = self.num.checked_mul(other.den)?;
470 let right = other.num.checked_mul(self.den)?;
471 Some(left.cmp(&right))
472 }
473
474 #[must_use]
476 pub fn eq_dim(&self, other: &Self) -> bool {
477 matches!(self.cmp(other), Some(Ordering::Equal))
478 }
479}
480
481fn format_rational(num: i128, den: i128) -> String {
482 if num == 0 {
483 return "0.0".into();
484 }
485 let neg = num < 0;
486 let mut n = num.unsigned_abs();
487 let d = den.unsigned_abs();
488 let ip = n / d;
489 n %= d;
490 let mut s = String::new();
491 if neg {
492 s.push('-');
493 }
494 if n == 0 {
495 s.push_str(&ip.to_string());
496 s.push_str(".0");
497 return s;
498 }
499 s.push_str(&ip.to_string());
500 s.push('.');
501 for _ in 0..24 {
503 if n == 0 {
504 break;
505 }
506 n *= 10;
507 let digit = n / d;
508 s.push(char::from(b'0' + digit as u8));
509 n %= d;
510 }
511 while s.ends_with('0') && !s.ends_with(".0") {
513 s.pop();
514 }
515 s
516}
517
518impl PartialEq for Dim {
519 fn eq(&self, other: &Self) -> bool {
520 self.eq_dim(other)
521 }
522}
523
524impl Eq for Dim {}
525
526impl PartialOrd for Dim {
527 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
528 self.cmp(other)
529 }
530}
531
532impl fmt::Display for Dim {
533 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
534 f.write_str(&self.to_dec_string())
535 }
536}
537
538impl Neg for Dim {
539 type Output = Self;
540
541 fn neg(self) -> Self {
542 if self.nan {
543 return Self::nan();
544 }
545 Self::raw(-self.num, self.den)
546 }
547}
548
549fn add_r(a: i128, b: i128, c: i128, d: i128) -> Option<(i128, i128)> {
550 let ad = a.checked_mul(d)?;
551 let cb = c.checked_mul(b)?;
552 let n = ad.checked_add(cb)?;
553 let den = b.checked_mul(d)?;
554 Some((n, den))
555}
556
557fn sub_r(a: i128, b: i128, c: i128, d: i128) -> Option<(i128, i128)> {
558 let ad = a.checked_mul(d)?;
559 let cb = c.checked_mul(b)?;
560 let n = ad.checked_sub(cb)?;
561 let den = b.checked_mul(d)?;
562 Some((n, den))
563}
564
565fn mul_r(a: i128, b: i128, c: i128, d: i128) -> Option<(i128, i128)> {
566 let n = a.checked_mul(c)?;
567 let den = b.checked_mul(d)?;
568 Some((n, den))
569}
570
571fn div_r(a: i128, b: i128, c: i128, d: i128) -> Option<(i128, i128)> {
572 if c == 0 {
573 return None;
574 }
575 let n = a.checked_mul(d)?;
576 let den = b.checked_mul(c)?;
577 Some((n, den))
578}
579
580macro_rules! impl_dim_op {
581 ($Trait:ident, $method:ident, $helper:ident) => {
582 impl $Trait for Dim {
583 type Output = Dim;
584 fn $method(self, rhs: Dim) -> Dim {
585 Dim::binop(&self, &rhs, $helper)
586 }
587 }
588 impl $Trait<&Dim> for Dim {
589 type Output = Dim;
590 fn $method(self, rhs: &Dim) -> Dim {
591 Dim::binop(&self, rhs, $helper)
592 }
593 }
594 impl $Trait<Dim> for &Dim {
595 type Output = Dim;
596 fn $method(self, rhs: Dim) -> Dim {
597 Dim::binop(self, &rhs, $helper)
598 }
599 }
600 impl $Trait for &Dim {
601 type Output = Dim;
602 fn $method(self, rhs: &Dim) -> Dim {
603 Dim::binop(self, rhs, $helper)
604 }
605 }
606 };
607}
608
609impl_dim_op!(Add, add, add_r);
610impl_dim_op!(Sub, sub, sub_r);
611impl_dim_op!(Mul, mul, mul_r);
612impl_dim_op!(Div, div, div_r);
613
614#[cfg(test)]
615mod tests {
616 use super::Dim;
617
618 #[test]
619 fn half_and_font_units() {
620 assert!(Dim::ratio(1, 2).eq_dim(&(&Dim::one() / &Dim::from_i64(2))));
621 let w = Dim::from_font_units(479, 1000);
622 assert_eq!(w.to_dec_string(), "0.479");
623 assert_eq!((Dim::from_i64(12) * w).to_dec_string(), "5.748");
624 }
625
626 #[test]
627 fn ieee32_one() {
628 let one = Dim::from_ieee32_bits(0x3f80_0000);
629 assert!(one.eq_dim(&Dim::one()));
630 assert_eq!(Dim::one().to_ieee32_bits(), 0x3f80_0000);
631 }
632}