figures/
units.rs

1use std::cmp::Ordering;
2use std::fmt;
3use std::num::TryFromIntError;
4use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Rem, RemAssign, Sub, SubAssign};
5
6use intentional::{Cast, CastFrom};
7
8use crate::traits::{
9    Abs, FloatConversion, IntoComponents, IntoSigned, IntoUnsigned, Pow, Roots, Round, ScreenScale,
10    StdNumOps, UnscaledUnit, Zero,
11};
12use crate::Fraction;
13
14pub(crate) const ARBITRARY_SCALE: u16 = 1905;
15const ARBITRARY_SCALE_I32: i32 = ARBITRARY_SCALE as i32;
16const ARBITRARY_SCALE_U32: u32 = ARBITRARY_SCALE as u32;
17#[allow(clippy::cast_precision_loss)]
18const ARBITRARY_SCALE_F32: f32 = ARBITRARY_SCALE as f32;
19
20macro_rules! define_integer_type {
21    ($name:ident, $inner:ty, $docs_file:literal, $scale:literal) => {
22        #[derive(Default, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
23        #[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
24        #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
25        #[doc = include_str!($docs_file)]
26        #[repr(C)]
27        pub struct $name($inner);
28
29        impl $name {
30            /// The maximum value for this type.
31            pub const MAX: Self = Self(<$inner>::MAX);
32            /// The minimum value for this type.
33            pub const MIN: Self = Self(<$inner>::MIN);
34
35            /// Returns a new wrapped value for this unit.
36            #[must_use]
37            pub const fn new(value: $inner) -> Self {
38                Self(value * $scale)
39            }
40
41            /// Returns the contained value, rounded if applicable.
42            #[must_use]
43            pub const fn get(self) -> $inner {
44                if $scale > 1 {
45                    (self.0 + $scale / 2) / $scale
46                } else {
47                    self.0
48                }
49            }
50
51            /// Returns the result of subtracting `other` from `self`. If the
52            /// calculation overflows, the value will be limited to
53            /// [`Self::MIN`]/[`Self::MAX`].
54            #[must_use]
55            pub const fn saturating_sub(self, other: Self) -> Self {
56                Self(self.0.saturating_sub(other.0))
57            }
58
59            /// Returns the result of adding `self` and `other`. If the
60            /// calculation overflows, the value will be limited to
61            /// [`Self::MIN`]/[`Self::MAX`].
62            #[must_use]
63            pub const fn saturating_add(self, other: Self) -> Self {
64                Self(self.0.saturating_add(other.0))
65            }
66
67            /// Returns the result of multiplying `self` and `other`. If the
68            /// calculation overflows, the value will be limited to
69            /// [`Self::MIN`]/[`Self::MAX`].
70            #[must_use]
71            pub const fn saturating_mul(self, other: Self) -> Self {
72                Self(self.0.saturating_mul(other.0) / $scale)
73            }
74
75            /// Returns the result of dividing `self` by `other`. If the
76            /// calculation overflows, the value will be limited to
77            /// [`Self::MIN`]/[`Self::MAX`].
78            #[must_use]
79            pub const fn saturating_div(self, other: Self) -> Self {
80                Self::new(self.0.saturating_div(other.0))
81            }
82        }
83
84        impl FloatConversion for $name {
85            type Float = f32;
86
87            #[allow(clippy::cast_precision_loss)] // precision loss desired to best approximate the value
88            fn into_float(self) -> Self::Float {
89                self.0.cast::<f32>() / $scale.cast::<f32>()
90            }
91
92            fn from_float(float: Self::Float) -> Self {
93                Self((float * $scale.cast::<f32>()).round().cast())
94            }
95        }
96
97        impl From<$name> for f32 {
98            fn from(value: $name) -> Self {
99                value.into_float()
100            }
101        }
102
103        impl From<f32> for $name {
104            fn from(value: f32) -> Self {
105                Self::from_float(value)
106            }
107        }
108
109        impl From<$name> for $inner {
110            fn from(value: $name) -> Self {
111                value.get()
112            }
113        }
114
115        impl From<$inner> for $name {
116            fn from(value: $inner) -> Self {
117                Self::new(value)
118            }
119        }
120
121        impl PartialEq<$inner> for $name {
122            fn eq(&self, other: &$inner) -> bool {
123                self == &Self::new(*other)
124            }
125        }
126
127        impl PartialOrd<$inner> for $name {
128            fn partial_cmp(&self, other: &$inner) -> Option<std::cmp::Ordering> {
129                self.partial_cmp(&Self::new(*other))
130            }
131        }
132
133        impl Div for $name {
134            type Output = Self;
135
136            fn div(self, rhs: Self) -> Self::Output {
137                Self::new(self.0 / rhs.0)
138            }
139        }
140
141        impl Div<$inner> for $name {
142            type Output = Self;
143
144            fn div(self, rhs: $inner) -> Self::Output {
145                Self(self.0 / rhs)
146            }
147        }
148
149        impl Div<f32> for $name {
150            type Output = Self;
151
152            fn div(self, rhs: f32) -> Self::Output {
153                Self::from((self.into_float() / rhs))
154            }
155        }
156
157        impl Div<Fraction> for $name {
158            type Output = Self;
159
160            fn div(self, rhs: Fraction) -> Self::Output {
161                Self::from_unscaled((self.into_unscaled() / rhs))
162            }
163        }
164
165        impl DivAssign for $name {
166            fn div_assign(&mut self, rhs: Self) {
167                *self = *self / rhs
168            }
169        }
170
171        impl DivAssign<$inner> for $name {
172            fn div_assign(&mut self, rhs: $inner) {
173                self.0 /= rhs;
174            }
175        }
176
177        impl Rem for $name {
178            type Output = Self;
179
180            fn rem(self, rhs: Self) -> Self::Output {
181                Self(self.0 % rhs.0)
182            }
183        }
184
185        impl Rem<$inner> for $name {
186            type Output = Self;
187
188            fn rem(self, rhs: $inner) -> Self::Output {
189                Self(self.0 % rhs)
190            }
191        }
192
193        impl RemAssign for $name {
194            fn rem_assign(&mut self, rhs: Self) {
195                self.0 %= rhs.0;
196            }
197        }
198
199        impl RemAssign<$inner> for $name {
200            fn rem_assign(&mut self, rhs: $inner) {
201                self.0 %= rhs;
202            }
203        }
204
205        impl Rem<f32> for $name {
206            type Output = Self;
207
208            fn rem(self, rhs: f32) -> Self::Output {
209                Self::from((self.into_float() % rhs).round())
210            }
211        }
212
213        impl Mul for $name {
214            type Output = Self;
215
216            fn mul(self, rhs: Self) -> Self::Output {
217                Self(self.0 * rhs.0 / $scale)
218            }
219        }
220
221        impl Mul<$inner> for $name {
222            type Output = Self;
223
224            fn mul(self, rhs: $inner) -> Self::Output {
225                Self(self.0 * rhs)
226            }
227        }
228
229        impl Mul<f32> for $name {
230            type Output = Self;
231
232            fn mul(self, rhs: f32) -> Self::Output {
233                Self::from((self.into_float() * rhs))
234            }
235        }
236
237        impl Mul<Fraction> for $name {
238            type Output = Self;
239
240            fn mul(self, rhs: Fraction) -> Self::Output {
241                Self::from_unscaled((self.into_unscaled() * rhs))
242            }
243        }
244
245        impl MulAssign for $name {
246            fn mul_assign(&mut self, rhs: Self) {
247                self.0 = (self.0 * rhs.0) / $scale;
248            }
249        }
250
251        impl MulAssign<$inner> for $name {
252            fn mul_assign(&mut self, rhs: $inner) {
253                self.0 *= rhs;
254            }
255        }
256
257        impl Add for $name {
258            type Output = Self;
259
260            fn add(self, rhs: Self) -> Self::Output {
261                Self(self.0 + rhs.0)
262            }
263        }
264
265        impl Add<$inner> for $name {
266            type Output = Self;
267
268            fn add(self, rhs: $inner) -> Self::Output {
269                self + Self::new(rhs)
270            }
271        }
272
273        impl AddAssign for $name {
274            fn add_assign(&mut self, rhs: Self) {
275                self.0 += rhs.0;
276            }
277        }
278
279        impl AddAssign<$inner> for $name {
280            fn add_assign(&mut self, rhs: $inner) {
281                *self += Self::new(rhs);
282            }
283        }
284
285        impl Sub for $name {
286            type Output = Self;
287
288            fn sub(self, rhs: Self) -> Self::Output {
289                Self(self.0 - rhs.0)
290            }
291        }
292
293        impl Sub<$inner> for $name {
294            type Output = Self;
295
296            fn sub(self, rhs: $inner) -> Self::Output {
297                Self(self.0 - rhs * $scale)
298            }
299        }
300
301        impl SubAssign for $name {
302            fn sub_assign(&mut self, rhs: Self) {
303                self.0 -= rhs.0;
304            }
305        }
306
307        impl SubAssign<$inner> for $name {
308            fn sub_assign(&mut self, rhs: $inner) {
309                *self -= Self::new(rhs);
310            }
311        }
312
313        impl Zero for $name {
314            const ZERO: Self = Self(0);
315
316            fn is_zero(&self) -> bool {
317                self.0 == 0
318            }
319        }
320
321        impl UnscaledUnit for $name {
322            type Representation = $inner;
323
324            fn from_unscaled(unscaled: Self::Representation) -> Self {
325                Self(unscaled)
326            }
327
328            fn into_unscaled(self) -> Self::Representation {
329                self.0
330            }
331        }
332
333        impl Round for $name {
334            fn round(self) -> Self {
335                Self((self.0 + $scale / 2) / $scale * $scale)
336            }
337
338            fn ceil(self) -> Self {
339                Self((self.0 + $scale - 1) / $scale * $scale)
340            }
341
342            fn floor(self) -> Self {
343                Self(self.0 / $scale * $scale)
344            }
345        }
346
347        impl Roots for $name {
348            fn sqrt(self) -> Self {
349                Self(f64::from(self.0).sqrt().cast())
350            }
351
352            fn cbrt(self) -> Self {
353                Self(f64::from(self.0).cbrt().cast())
354            }
355        }
356
357        impl StdNumOps for $name {
358            fn saturating_add(self, other: Self) -> Self {
359                self.saturating_add(other)
360            }
361
362            fn saturating_mul(self, other: Self) -> Self {
363                self.saturating_mul(other)
364            }
365
366            fn saturating_div(self, other: Self) -> Self {
367                self.saturating_div(other)
368            }
369
370            fn saturating_sub(self, other: Self) -> Self {
371                self.saturating_sub(other)
372            }
373        }
374    };
375}
376
377impl CastFrom<f32> for Px {
378    fn from_cast(from: f32) -> Self {
379        Px::from(from)
380    }
381}
382
383impl CastFrom<Px> for f32 {
384    fn from_cast(from: Px) -> Self {
385        from.into_float()
386    }
387}
388
389define_integer_type!(Lp, i32, "docs/lp.md", 1905);
390
391impl IntoComponents<Lp> for i32 {
392    fn into_components(self) -> (Lp, Lp) {
393        (Lp(self), Lp(self))
394    }
395}
396
397impl IntoComponents<Lp> for f32 {
398    fn into_components(self) -> (Lp, Lp) {
399        let value = Lp::from_float(self);
400        (value, value)
401    }
402}
403
404impl ScreenScale for Lp {
405    type Lp = Lp;
406    type Px = Px;
407    type UPx = UPx;
408
409    fn into_px(self, scale: Fraction) -> Self::Px {
410        Px(self.0 * 4 * scale / ARBITRARY_SCALE_I32)
411    }
412
413    fn from_px(px: Self::Px, scale: Fraction) -> Self {
414        px.into_lp(scale)
415    }
416
417    fn into_lp(self, _scale: Fraction) -> Self::Lp {
418        self
419    }
420
421    fn from_lp(lp: Self::Lp, _scale: Fraction) -> Self {
422        lp
423    }
424
425    fn into_upx(self, scale: crate::Fraction) -> Self::UPx {
426        self.into_px(scale).into_unsigned()
427    }
428
429    fn from_upx(px: Self::UPx, scale: crate::Fraction) -> Self {
430        Self::from_px(px.into_signed(), scale)
431    }
432}
433
434impl std::ops::Neg for Lp {
435    type Output = Self;
436
437    fn neg(self) -> Self::Output {
438        Self(-self.0)
439    }
440}
441
442impl TryFrom<u32> for Lp {
443    type Error = TryFromIntError;
444
445    fn try_from(value: u32) -> Result<Self, Self::Error> {
446        value.try_into().map(Self)
447    }
448}
449
450impl Lp {
451    /// Returns a value equivalent to the number of `points` provided. One
452    /// [point](https://en.wikipedia.org/wiki/Point_(typography)) is 1/72 of an
453    /// inch.
454    #[must_use]
455    pub const fn points(points: i32) -> Self {
456        Self(points * ARBITRARY_SCALE_I32 * 4 / 3)
457    }
458
459    /// Returns a value equivalent to the number of `points` provided. One
460    /// [point](https://en.wikipedia.org/wiki/Point_(typography)) is 1/72 of an
461    /// inch.
462    #[must_use]
463    pub fn points_f(points: f32) -> Self {
464        Lp((points * ARBITRARY_SCALE_F32 * 4. / 3.).cast())
465    }
466
467    /// Returns a value equivalent to the number of `centimeters` provided.
468    #[must_use]
469    pub const fn cm(centimeters: i32) -> Self {
470        Self::mm(centimeters * 10)
471    }
472
473    /// Returns a value equivalent to the number of `centimeters` provided.
474    #[must_use]
475    pub fn cm_f(centimeters: f32) -> Self {
476        Lp((centimeters * ARBITRARY_SCALE_F32 * 96. / 2.54).cast())
477    }
478
479    /// Returns a value equivalent to the number of `millimeters` provided.
480    #[must_use]
481    pub const fn mm(millimeters: i32) -> Self {
482        Self(millimeters * ARBITRARY_SCALE_I32 * 960 / 254)
483    }
484
485    /// Returns a value equivalent to the number of `millimeters` provided.
486    #[must_use]
487    pub fn mm_f(millimeters: f32) -> Self {
488        Lp((millimeters * ARBITRARY_SCALE_F32 * 96. / 25.4).cast())
489    }
490
491    /// Returns a value equivalent to the number of `inches` provided.
492    #[must_use]
493    pub const fn inches(inches: i32) -> Self {
494        Self(inches * ARBITRARY_SCALE_I32 * 96)
495    }
496
497    /// Returns a value equivalent to the number of `inches` provided.
498    #[must_use]
499    pub fn inches_f(inches: f32) -> Self {
500        Self((inches * ARBITRARY_SCALE_F32 * 96.).cast())
501    }
502}
503
504impl Pow for Lp {
505    fn pow(&self, exp: u32) -> Self {
506        Self(self.0.saturating_pow(exp))
507    }
508}
509
510impl Abs for Lp {
511    fn abs(&self) -> Self {
512        Self(self.0.saturating_abs())
513    }
514}
515
516impl IntoSigned for Lp {
517    type Signed = Self;
518
519    fn into_signed(self) -> Self::Signed {
520        self
521    }
522}
523
524impl fmt::Debug for Lp {
525    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
526        let fractional = self.0 % ARBITRARY_SCALE_I32;
527        let whole = self.0 / ARBITRARY_SCALE_I32;
528        if fractional == 0 {
529            write!(f, "{whole}lp")
530        } else {
531            let as_float =
532                f64::from(whole) + f64::from(fractional) / f64::from(ARBITRARY_SCALE_F32);
533            write!(f, "{as_float}lp")
534        }
535    }
536}
537
538impl fmt::Display for Lp {
539    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
540        fmt::Debug::fmt(self, f)
541    }
542}
543
544define_integer_type!(Px, i32, "docs/px.md", 4);
545
546impl Pow for Px {
547    fn pow(&self, exp: u32) -> Self {
548        Self(self.0.saturating_pow(exp) / 4_i32.pow(exp.saturating_sub(1)))
549    }
550}
551
552impl Abs for Px {
553    fn abs(&self) -> Self {
554        Self(self.0.saturating_abs())
555    }
556}
557
558impl IntoUnsigned for Px {
559    type Unsigned = UPx;
560
561    fn into_unsigned(self) -> Self::Unsigned {
562        UPx(self.0.into_unsigned())
563    }
564}
565
566impl IntoSigned for Px {
567    type Signed = Self;
568
569    fn into_signed(self) -> Self::Signed {
570        self
571    }
572}
573
574impl ScreenScale for Px {
575    type Lp = Lp;
576    type Px = Self;
577    type UPx = UPx;
578
579    fn into_px(self, _scale: Fraction) -> Self::Px {
580        self
581    }
582
583    fn from_px(px: Self::Px, _scale: Fraction) -> Self {
584        px
585    }
586
587    fn into_lp(self, scale: Fraction) -> Self::Lp {
588        Lp(self.0 * ARBITRARY_SCALE_I32 / scale / 4)
589    }
590
591    fn from_lp(lp: Self::Lp, scale: Fraction) -> Self {
592        lp.into_px(scale)
593    }
594
595    fn into_upx(self, _scale: crate::Fraction) -> Self::UPx {
596        self.into_unsigned()
597    }
598
599    fn from_upx(px: Self::UPx, _scale: crate::Fraction) -> Self {
600        px.into_signed()
601    }
602}
603
604impl IntoComponents<Px> for i32 {
605    fn into_components(self) -> (Px, Px) {
606        (Px::new(self), Px::new(self))
607    }
608}
609
610impl IntoComponents<Px> for f32 {
611    fn into_components(self) -> (Px, Px) {
612        let value = Px::from_float(self);
613        (value, value)
614    }
615}
616
617impl fmt::Debug for Px {
618    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
619        let whole = self.0 >> 2;
620        let remainder = self.0 & 0b11;
621        match remainder {
622            1 => write!(f, "{whole}.25px",),
623            2 => write!(f, "{whole}.5px",),
624            3 => write!(f, "{whole}.75px",),
625            _ => write!(f, "{whole}px",),
626        }
627    }
628}
629
630impl fmt::Display for Px {
631    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
632        fmt::Debug::fmt(self, f)
633    }
634}
635
636impl std::ops::Neg for Px {
637    type Output = Self;
638
639    fn neg(self) -> Self::Output {
640        Self(-self.0)
641    }
642}
643
644impl TryFrom<u32> for Px {
645    type Error = TryFromIntError;
646
647    fn try_from(value: u32) -> Result<Self, Self::Error> {
648        value.try_into().map(Self::new)
649    }
650}
651
652impl PartialEq<UPx> for Px {
653    fn eq(&self, other: &UPx) -> bool {
654        if let Ok(unsigned) = UPx::try_from(*self) {
655            unsigned == *other
656        } else {
657            false
658        }
659    }
660}
661
662impl PartialOrd<UPx> for Px {
663    fn partial_cmp(&self, other: &UPx) -> Option<Ordering> {
664        if let Ok(unsigned) = UPx::try_from(*self) {
665            Some(unsigned.cmp(other))
666        } else {
667            Some(Ordering::Less)
668        }
669    }
670}
671
672define_integer_type!(UPx, u32, "docs/upx.md", 4);
673
674impl Pow for UPx {
675    fn pow(&self, exp: u32) -> Self {
676        Self(self.0.saturating_pow(exp) / 4_u32.pow(exp.saturating_sub(1)))
677    }
678}
679
680impl IntoSigned for UPx {
681    type Signed = Px;
682
683    fn into_signed(self) -> Self::Signed {
684        Px(self.0.into_signed())
685    }
686}
687
688impl IntoUnsigned for UPx {
689    type Unsigned = Self;
690
691    fn into_unsigned(self) -> Self::Unsigned {
692        self
693    }
694}
695
696impl ScreenScale for UPx {
697    type Lp = Lp;
698    type Px = Px;
699    type UPx = Self;
700
701    fn into_px(self, _scale: Fraction) -> Self::Px {
702        Px(i32::try_from(self.0).unwrap_or(i32::MAX))
703    }
704
705    fn from_px(px: Self::Px, _scale: Fraction) -> Self {
706        Self::try_from(px).unwrap_or(Self::MIN)
707    }
708
709    fn into_lp(self, scale: Fraction) -> Self::Lp {
710        (self.0 * ARBITRARY_SCALE_U32 / scale / 4)
711            .try_into()
712            .unwrap_or(Lp::MAX)
713    }
714
715    fn from_lp(lp: Self::Lp, scale: Fraction) -> Self {
716        lp.into_px(scale).try_into().unwrap_or(Self::MIN)
717    }
718
719    fn into_upx(self, _scale: crate::Fraction) -> Self::UPx {
720        self
721    }
722
723    fn from_upx(px: Self::UPx, _scale: crate::Fraction) -> Self {
724        px
725    }
726}
727
728impl IntoComponents<UPx> for u32 {
729    fn into_components(self) -> (UPx, UPx) {
730        (UPx::new(self), UPx::new(self))
731    }
732}
733
734impl IntoComponents<UPx> for f32 {
735    fn into_components(self) -> (UPx, UPx) {
736        let value = UPx::from_float(self);
737        (value, value)
738    }
739}
740
741impl fmt::Debug for UPx {
742    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
743        let whole = self.0 / 4;
744        let remainder = self.0 % 4;
745        match remainder {
746            1 => write!(f, "{whole}.25px",),
747            2 => write!(f, "{whole}.5px",),
748            3 => write!(f, "{whole}.75px",),
749            _ => write!(f, "{whole}px",),
750        }
751    }
752}
753
754impl fmt::Display for UPx {
755    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
756        fmt::Debug::fmt(self, f)
757    }
758}
759
760impl TryFrom<UPx> for i32 {
761    type Error = TryFromIntError;
762
763    fn try_from(value: UPx) -> Result<Self, Self::Error> {
764        value.get().try_into()
765    }
766}
767
768impl TryFrom<i32> for UPx {
769    type Error = TryFromIntError;
770
771    fn try_from(value: i32) -> Result<Self, Self::Error> {
772        value.try_into().map(Self::new)
773    }
774}
775
776impl TryFrom<Px> for UPx {
777    type Error = TryFromIntError;
778
779    fn try_from(value: Px) -> Result<Self, Self::Error> {
780        value.0.try_into().map(Self)
781    }
782}
783
784impl TryFrom<UPx> for Px {
785    type Error = TryFromIntError;
786
787    fn try_from(value: UPx) -> Result<Self, Self::Error> {
788        value.0.try_into().map(Self)
789    }
790}
791
792impl PartialEq<Px> for UPx {
793    fn eq(&self, other: &Px) -> bool {
794        if let Ok(unsigned) = UPx::try_from(*other) {
795            unsigned == *self
796        } else {
797            false
798        }
799    }
800}
801
802impl PartialOrd<Px> for UPx {
803    fn partial_cmp(&self, other: &Px) -> Option<Ordering> {
804        if let Ok(unsigned) = UPx::try_from(*other) {
805            Some(unsigned.cmp(self))
806        } else {
807            Some(Ordering::Less)
808        }
809    }
810}