Skip to main content

ttf_view/types/
fixed_point.rs

1use crate::util::impl_fmt_with;
2
3macro_rules! impl_fixed_point_number {
4    (
5        $(#[$outer:meta])*
6        $vis:vis struct $Name:ident(
7            $int:ty as [u8; $bytes:literal];
8            $integer_bits:literal | $fraction_bits:literal as $fp:ty
9        );
10    ) => {
11        $(#[$outer])*
12        #[derive(Copy, Hash)]
13        #[derive_const(Clone, PartialEq, Eq)]
14        #[repr(transparent)]
15        $vis struct $Name([u8; $bytes]);
16
17        const _: () = {
18            assert!(size_of::<$int>() == $bytes);
19            assert!($integer_bits + $fraction_bits == <$int>::BITS);
20        };
21
22        impl $Name {
23            const STEP: $fp = 1.0 / (1 << $fraction_bits) as $fp;
24            const MIN: $fp = -(1 << $integer_bits) as $fp;
25            const MAX: $fp = ((1 << $integer_bits) - 1) as $fp;
26
27            pub const fn new(num: $fp) -> Option<Self> {
28                if matches!(num, Self::MIN..Self::MAX) {
29                    // TODO: Could this sometimes result in overflow? e.g. 1.999999 wrapping to -2?
30                    Some(unsafe { Self::new_unchecked(num) })
31                } else {
32                    None
33                }
34            }
35            pub const unsafe fn new_unchecked(num: $fp) -> Self {
36                debug_assert!(matches!(num, Self::MIN..Self::MAX));
37                Self(((num / Self::STEP).round() as $int).to_be_bytes())
38            }
39
40            pub const fn from_be_bytes(bytes: [u8; $bytes]) -> Self {
41                Self(bytes)
42            }
43            pub const fn to_be_bytes(self) -> [u8; $bytes] {
44                self.0
45            }
46
47            // Determine how many decimal places the type can accurately represent
48            pub const PRECISION: u32 = {
49                let mut x = Self::STEP;
50                let mut times = 0;
51                while x.round() < 1 as $fp {
52                    times += 1;
53                    x *= 10 as $fp;
54                }
55                times - 1
56            };
57
58            pub const fn frac_num(&self) -> $int {
59                <$int>::from_be_bytes(self.0)
60            }
61            pub const fn get(&self) -> $fp {
62                self.frac_num() as $fp * Self::STEP
63            }
64            pub const fn round_to_precision(&self) -> $fp {
65                const MULT: $fp = 10u32.pow($Name::PRECISION) as $fp;
66                (self.get() * MULT).round() / MULT
67            }
68        }
69
70        impl_fmt_with! {
71            Debug, Display, LowerExp, UpperExp:
72            |x: &$Name, f| {
73                (if f.precision().is_none() { x.round_to_precision() } else { x.get() }).fmt(f)
74            }
75        }
76
77        const impl PartialOrd for $Name {
78            fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
79                Some(self.cmp(other))
80            }
81        }
82        const impl Ord for $Name {
83            fn cmp(&self, other: &Self) -> std::cmp::Ordering {
84                self.frac_num().cmp(&other.frac_num())
85            }
86        }
87
88        const impl PartialEq<$fp> for $Name {
89            fn eq(&self, other: &$fp) -> bool {
90                self.get().eq(other)
91            }
92        }
93        const impl PartialOrd<$fp> for $Name {
94            fn partial_cmp(&self, other: &$fp) -> Option<std::cmp::Ordering> {
95                self.get().partial_cmp(other)
96            }
97        }
98
99        impl std::str::FromStr for $Name {
100            type Err = ();
101            fn from_str(s: &str) -> Result<Self, Self::Err> {
102                <$fp>::from_str(s).or(Err(())).and_then($Name::try_from)
103            }
104        }
105        const impl TryFrom<$fp> for $Name {
106            type Error = ();
107            fn try_from(value: $fp) -> Result<$Name, Self::Error> {
108                Self::new(value).ok_or(())
109            }
110        }
111        const impl From<$Name> for $fp {
112            fn from(value: $Name) -> Self {
113                value.get()
114            }
115        }
116    }
117}
118
119impl_fixed_point_number! {
120    pub struct Fixed(i32 as [u8; 4]; 16|16 as f64);
121}
122impl_fixed_point_number! {
123    pub struct F2DOT14(i16 as [u8; 2]; 2|14 as f32);
124}
125
126#[cfg(test)]
127mod tests {
128    use super::*;
129
130    #[test]
131    fn precision() {
132        assert_eq!(Fixed::PRECISION, 4);
133        assert_eq!(F2DOT14::PRECISION, 3);
134
135        let f = Fixed::from_be_bytes(0x0001999A_u32.to_be_bytes());
136        assert_eq!(format!("{}", f), "1.6");
137        assert_eq!(format!("{:.0}", f), "2");
138        assert_eq!(format!("{:.1}", f), "1.6");
139        assert_eq!(format!("{:.3}", f), "1.600");
140        assert_eq!(format!("{:.7}", f), "1.6000061");
141    }
142
143    #[test]
144    fn fixed() {
145        let nums: [(u32, f64); _] = [
146            (0x7FFF_FFFF, 32767.999985),
147            (0x7FFF_FF00, 32767.996094),
148            (0x7FFF_2000, 32767.125000),
149            (0x7FFF_0000, 32767.000000),
150            (0x0040_0100, 64.003906),
151            (0x0040_0000, 64.000000),
152            (0x0001_0000, 1.000000),
153            (0x0000_0001, 0.000015),
154            (0x0000_0000, 0.000000),
155            (0xFFFF_0000, -1.000000),
156            (0xFFBF_FF00, -64.003906),
157            (0x8000_0000, -32768.000000),
158        ];
159
160        for (raw, fp) in nums {
161            let real = Fixed::new(fp).unwrap().frac_num() as u32;
162            assert_eq!(real, raw, "{real:#X} != {raw:#X} ({fp})");
163
164            let real = Fixed::new(fp).unwrap().get();
165            let diff = (real - fp).abs();
166            assert!(diff <= 0.1 * Fixed::STEP, "{real} != {fp} (Δ={diff})");
167        }
168    }
169
170    #[test]
171    fn f2dot14() {
172        let nums: [(u16, f32); _] = [
173            (0x7FFF, 1.999939),
174            (0x7000, 1.750000),
175            (0x0085, 0.008118),
176            (0x0002, 0.000122),
177            (0x0001, 0.000061),
178            (0x0000, 0.000000),
179            (0xFFFF, -0.000061),
180            (0xFFFE, -0.000122),
181            (0xFF7B, -0.008118),
182            (0x8000, -2.000000),
183        ];
184
185        for (raw, fp) in nums {
186            let real = F2DOT14::new(fp).unwrap().frac_num() as u16;
187            assert_eq!(real, raw, "{real:#X} != {raw:#X} ({fp})");
188
189            let real = F2DOT14::new(fp).unwrap().get();
190            let diff = (real - fp).abs();
191            assert!(diff <= 0.1 * F2DOT14::STEP, "{real} != {fp} (Δ={diff})");
192        }
193    }
194}