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        DENOM = $d_denom:literal;
11        STEP = $d_step:literal;
12        MIN = $d_min:literal;
13        MAX = $d_max:literal;
14        PRECISION = $d_precision:literal;
15    ) => {
16        #[doc = concat!("The [OpenType ", stringify!($Name), "][spec] type, a ")]
17        #[doc = concat!(stringify!($integer_bits), ".", stringify!($fraction_bits), "-bit")]
18        /// signed fixed-point type.
19        ///
20        /// [spec]: https://learn.microsoft.com/en-us/typography/opentype/spec/otff#data-types
21        $(#[$outer])*
22        #[derive(Copy, Hash)]
23        #[derive_const(Clone, PartialEq, Eq)]
24        #[repr(transparent)]
25        $vis struct $Name([u8; $bytes]);
26
27        const _: () = {
28            assert!(size_of::<$int>() == $bytes);
29            assert!($integer_bits + $fraction_bits == <$int>::BITS);
30        };
31
32        impl $Name {
33            const F_STEP: $fp = 1.0 / (1 << $fraction_bits) as $fp;
34            const F_MIN: $fp = -(1 << ($integer_bits - 1)) as $fp;
35            const F_MAX_EXCLUSIVE: $fp = (1 << ($integer_bits - 1)) as $fp;
36            const F_MAX: $fp = Self::F_MAX_EXCLUSIVE - Self::F_STEP;
37
38            #[doc = concat!("The difference between adjacent [`", stringify!($Name), "`] values.")]
39            ///
40            /// # Examples
41            ///
42            /// ```
43            #[doc = concat!("# use ttf_view::types::", stringify!($Name), ";")]
44            #[doc = concat!("assert_eq!(", stringify!($Name), "::STEP, ", stringify!($d_step), ");")]
45            /// ```
46            pub const STEP: Self = Self::new(Self::F_STEP).unwrap();
47
48            /// The smallest value that can be represented by this type.
49            ///
50            /// # Examples
51            ///
52            /// ```
53            #[doc = concat!("# use ttf_view::types::", stringify!($Name), ";")]
54            #[doc = concat!("assert_eq!(", stringify!($Name), "::MIN, ", stringify!($d_min), ");")]
55            /// ```
56            pub const MIN: Self = Self::new(Self::F_MIN).unwrap();
57            /// The largest value that can be represented by this type.
58            ///
59            /// # Examples
60            ///
61            /// ```
62            #[doc = concat!("# use ttf_view::types::", stringify!($Name), ";")]
63            #[doc = concat!("assert_eq!(", stringify!($Name), "::MAX, ", stringify!($d_max), ");")]
64            /// ```
65            pub const MAX: Self = Self::new(Self::F_MAX).unwrap();
66
67            /// The amount of decimal places the type can accurately represent.
68            ///
69            /// # Examples
70            ///
71            /// ```
72            #[doc = concat!("# use ttf_view::types::", stringify!($Name), ";")]
73            #[doc = concat!("assert_eq!(", stringify!($Name), "::PRECISION, ", stringify!($d_precision), ");")]
74            /// ```
75            pub const PRECISION: u32 = (Self::F_STEP.recip() as u32).ilog10();
76
77            #[doc = concat!("Creates a [`", stringify!($Name), "`] from [`", stringify!($fp), "`].")]
78            ///
79            /// # Examples
80            ///
81            /// ```
82            #[doc = concat!("use ttf_view::types::", stringify!($Name), ";")]
83            ///
84            #[doc = concat!("assert_eq!(", stringify!($Name), "::new(-0.125).unwrap(), -0.125);")]
85            #[doc = concat!("assert_eq!(", stringify!($Name), "::new(1.99609375).unwrap(), 1.99609375);")]
86            #[doc = concat!("assert_eq!(", stringify!($Name), "::new(123456.78), None);")]
87            ///
88            #[doc = concat!("// numbers are rounded towards the closest value representable by ", stringify!($Name))]
89            #[doc = concat!("assert_eq!(", stringify!($Name), "::new(0.5156247).unwrap(), 0.515625);")]
90            #[doc = concat!("assert_eq!(", stringify!($Name), "::new(0.5156250).unwrap(), 0.515625);")]
91            #[doc = concat!("assert_eq!(", stringify!($Name), "::new(0.5156254).unwrap(), 0.515625);")]
92            /// ```
93            pub const fn new(num: $fp) -> Option<Self> {
94                if matches!(num, Self::F_MIN..Self::F_MAX_EXCLUSIVE) {
95                    Some(unsafe { Self::new_unchecked(num) })
96                } else {
97                    None
98                }
99            }
100            #[doc = concat!("Creates a [`", stringify!($Name), "`] from [`", stringify!($fp), "`] without checks.")]
101            ///
102            /// TODO: # Examples
103            pub const unsafe fn new_unchecked(num: $fp) -> Self {
104                debug_assert!(matches!(num, Self::F_MIN..Self::F_MAX_EXCLUSIVE));
105
106                // Note: No need to worry about 1.9999999 wrapping to -2, because `as` here
107                // converts in a saturating way (even Infinity becomes 0xFFFF through `as`).
108                Self(((num / Self::F_STEP).round() as $int).to_be_bytes())
109            }
110
111            #[doc = concat!("Creates a [`", stringify!($Name), "`] from big-endian bytes.")]
112            ///
113            /// TODO: # Examples
114            pub const fn from_be_bytes(bytes: [u8; $bytes]) -> Self {
115                Self(bytes)
116            }
117            #[doc = concat!("Gets this [`", stringify!($Name), "`]'s big-endian bytes.")]
118            ///
119            /// TODO: # Examples
120            pub const fn to_be_bytes(self) -> [u8; $bytes] {
121                self.0
122            }
123
124            #[doc = concat!("Returns this [`", stringify!($Name), "`] fraction's numerator")]
125            #[doc = concat!("(`", stringify!($Name), "` represented as <math><mfrac><mi>numerator</mi><mn>", stringify!($d_denom), "</mn></mfrac></math>).")]
126            ///
127            /// TODO: # Examples
128            pub const fn frac_num(&self) -> $int {
129                <$int>::from_be_bytes(self.0)
130            }
131            #[doc = concat!("Returns this [`", stringify!($Name), "`]'s value as [`", stringify!($fp), "`].")]
132            ///
133            /// TODO: # Examples
134            pub const fn get(&self) -> $fp {
135                self.frac_num() as $fp * Self::F_STEP
136            }
137            #[doc = concat!("Rounds this [`", stringify!($Name), "`]'s value to [`PRECISION`][Self::PRECISION] decimal places.")]
138            ///
139            /// TODO: # Examples
140            pub const fn round_to_precision(&self) -> $fp {
141                const SCALE: $fp = 10u32.pow($Name::PRECISION) as $fp;
142                (self.get() * SCALE).round() / SCALE
143            }
144        }
145
146        impl_fmt_with! {
147            Debug, Display, LowerExp, UpperExp:
148            |x: &$Name| x.get()
149        }
150
151        const impl PartialOrd for $Name {
152            fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
153                Some(self.cmp(other))
154            }
155        }
156        const impl Ord for $Name {
157            fn cmp(&self, other: &Self) -> std::cmp::Ordering {
158                self.frac_num().cmp(&other.frac_num())
159            }
160        }
161
162        const impl PartialEq<$fp> for $Name {
163            fn eq(&self, other: &$fp) -> bool {
164                self.get().eq(other)
165            }
166        }
167        const impl PartialOrd<$fp> for $Name {
168            fn partial_cmp(&self, other: &$fp) -> Option<std::cmp::Ordering> {
169                self.get().partial_cmp(other)
170            }
171        }
172
173        impl std::str::FromStr for $Name {
174            type Err = ();
175            fn from_str(s: &str) -> Result<Self, Self::Err> {
176                <$fp>::from_str(s).or(Err(())).and_then($Name::try_from)
177            }
178        }
179        const impl TryFrom<$fp> for $Name {
180            type Error = ();
181            fn try_from(value: $fp) -> Result<$Name, Self::Error> {
182                Self::new(value).ok_or(())
183            }
184        }
185        const impl From<$Name> for $fp {
186            fn from(value: $Name) -> Self {
187                value.get()
188            }
189        }
190    }
191}
192
193// The constants specified here are re-calculated in the macro and then validated in doc-tests.
194impl_fixed_point_number! {
195    pub struct Fixed(i32 as [u8; 4]; 16|16 as f64);
196    DENOM = 65536;
197    STEP = 0.0000152587890625;
198    MIN = -32768.0;
199    MAX = 32767.99998474121;
200    PRECISION = 4;
201}
202impl_fixed_point_number! {
203    pub struct F2DOT14(i16 as [u8; 2]; 2|14 as f32);
204    DENOM = 16384;
205    STEP = 0.000061035156;
206    MIN = -2.0;
207    MAX = 1.999939;
208    PRECISION = 4;
209}
210
211#[cfg(test)]
212mod tests {
213    use super::*;
214
215    #[test]
216    fn precision() {
217        // Precision tests for Fixed (font_revision)
218        let f = Fixed::from_be_bytes(0x0001999A_u32.to_be_bytes());
219        assert_eq!(format!("{}", f), "1.600006103515625");
220        assert_eq!(format!("{}", f.round_to_precision()), "1.6");
221
222        assert_eq!(format!("{:.0}", f), "2");
223        assert_eq!(format!("{:.1}", f), "1.6");
224        assert_eq!(format!("{:.3}", f), "1.600");
225        assert_eq!(format!("{:.7}", f), "1.6000061");
226
227        // Ensure the upper boundary is correctly rounded down
228        assert_eq!(Fixed::new(32767.999999999996).unwrap().to_be_bytes(), [0x7F, 0xFF, 0xFF, 0xFF]);
229        assert_eq!(Fixed::new(32768.0), None);
230        assert_eq!(F2DOT14::new(1.9999999).unwrap().to_be_bytes(), [0x7F, 0xFF]);
231        assert_eq!(F2DOT14::new(2.0), None);
232
233        // Ensure the lower boundary is at an integer
234        assert_eq!(Fixed::new(-32768.0).unwrap().to_be_bytes(), [0x80, 0x00, 0x00, 0x00]);
235        assert_eq!(Fixed::new(-32768.00000000001), None);
236        assert_eq!(F2DOT14::new(-2.0).unwrap().to_be_bytes(), [0x80, 0x00]);
237        assert_eq!(F2DOT14::new(-2.0000002), None);
238    }
239
240    #[test]
241    fn fixed() {
242        // Check f64 bounds used in parameter validation
243        assert_eq!(Fixed::F_MIN, -32768.0);
244        assert_eq!(Fixed::F_MAX_EXCLUSIVE, 32768.0);
245        assert_eq!(Fixed::F_MAX, 32767.99998474121);
246
247        // Test a bunch of sample numbers
248        let nums: [(u32, f64); _] = [
249            (0x7FFF_FFFF, 32767.999985),
250            (0x7FFF_FF00, 32767.996094),
251            (0x7FFF_2000, 32767.125000),
252            (0x7FFF_0000, 32767.000000),
253            (0x0040_0100, 64.003906),
254            (0x0040_0000, 64.000000),
255            (0x0001_0000, 1.000000),
256            (0x0000_0001, 0.000015),
257            (0x0000_0000, 0.000000),
258            (0xFFFF_0000, -1.000000),
259            (0xFFBF_FF00, -64.003906),
260            (0x8000_0000, -32768.000000),
261        ];
262
263        for (raw, fp) in nums {
264            let real = Fixed::new(fp).unwrap().frac_num() as u32;
265            assert_eq!(real, raw, "{real:#X} != {raw:#X} ({fp})");
266
267            let real = Fixed::new(fp).unwrap().get();
268            let diff = (real - fp).abs();
269            assert!(diff <= 0.1 * Fixed::F_STEP, "{real} != {fp} (Δ={diff})");
270        }
271    }
272
273    #[test]
274    fn f2dot14() {
275        // Check f32 bounds used in parameter validation
276        assert_eq!(F2DOT14::F_MIN, -2.0);
277        assert_eq!(F2DOT14::F_MAX_EXCLUSIVE, 2.0);
278        assert_eq!(F2DOT14::F_MAX, 1.999939);
279
280        // Test a bunch of sample numbers
281        let nums: [(u16, f32); _] = [
282            (0x7FFF, 1.999939),
283            (0x7000, 1.750000),
284            (0x0085, 0.008118),
285            (0x0002, 0.000122),
286            (0x0001, 0.000061),
287            (0x0000, 0.000000),
288            (0xFFFF, -0.000061),
289            (0xFFFE, -0.000122),
290            (0xFF7B, -0.008118),
291            (0x8000, -2.000000),
292        ];
293
294        for (raw, fp) in nums {
295            let real = F2DOT14::new(fp).unwrap().frac_num() as u16;
296            assert_eq!(real, raw, "{real:#X} != {raw:#X} ({fp})");
297
298            let real = F2DOT14::new(fp).unwrap().get();
299            let diff = (real - fp).abs();
300            assert!(diff <= 0.1 * F2DOT14::F_STEP, "{real} != {fp} (Δ={diff})");
301        }
302    }
303}