ttf_view/types/
fixed_point.rs1use 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 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 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}