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, $wide:ty
9        );
10        DENOM = $denom:literal;
11        STEP = $step:literal;
12        MIN = $min:literal;
13        MAX = $max:literal;
14    ) => {
15        #[doc = concat!("The [OpenType ", stringify!($Name), "][spec] type, a")]
16        #[doc = concat!($integer_bits, ".", $fraction_bits, "-bit signed fixed-point type.")]
17        ///
18        /// [spec]: https://learn.microsoft.com/en-us/typography/opentype/spec/otff#data-types
19        $(#[$outer])*
20        #[derive(Copy, Hash)]
21        #[derive_const(Clone, Default, PartialEq, Eq, PartialOrd, Ord)]
22        #[repr(transparent)]
23        $vis struct $Name($int);
24
25        const _: () = {
26            assert!(size_of::<$int>() == $bytes);
27            assert!($integer_bits + $fraction_bits == <$int>::BITS);
28        };
29
30        impl $Name {
31            const F_STEP: $fp = 1.0 / (1 << $fraction_bits) as $fp;
32            const F_MIN: $fp = -(1 << ($integer_bits - 1)) as $fp;
33            const F_MAX_EXCLUSIVE: $fp = (1 << ($integer_bits - 1)) as $fp;
34
35            #[doc = concat!("The difference between adjacent [`", stringify!($Name), "`] values.")]
36            ///
37            /// # Examples
38            ///
39            /// ```
40            #[doc = concat!("# use ttf_view::types::", stringify!($Name), ";")]
41            #[doc = concat!("assert_eq!(", stringify!($Name), "::STEP, ", $step, ");")]
42            /// ```
43            pub const STEP: Self = Self(1);
44
45            /// The smallest value that can be represented by this type.
46            ///
47            /// # Examples
48            ///
49            /// ```
50            #[doc = concat!("# use ttf_view::types::", stringify!($Name), ";")]
51            #[doc = concat!("assert_eq!(", stringify!($Name), "::MIN, ", $min, ");")]
52            /// ```
53            pub const MIN: Self = Self(<$int>::MIN);
54            /// The largest value that can be represented by this type.
55            ///
56            /// # Examples
57            ///
58            /// ```
59            #[doc = concat!("# use ttf_view::types::", stringify!($Name), ";")]
60            #[doc = concat!("assert_eq!(", stringify!($Name), "::MAX, ", $max, ");")]
61            /// ```
62            pub const MAX: Self = Self(<$int>::MAX);
63
64            /// The value zero.
65            ///
66            /// # Examples
67            ///
68            /// ```
69            #[doc = concat!("# use ttf_view::types::", stringify!($Name), ";")]
70            #[doc = concat!("assert_eq!(", stringify!($Name), "::ZERO, 0.0);")]
71            /// ```
72            pub const ZERO: Self = Self::new(0.0).unwrap();
73            /// The value one.
74            ///
75            /// # Examples
76            ///
77            /// ```
78            #[doc = concat!("# use ttf_view::types::", stringify!($Name), ";")]
79            #[doc = concat!("assert_eq!(", stringify!($Name), "::ONE, 1.0);")]
80            /// ```
81            pub const ONE: Self = Self::new(1.0).unwrap();
82
83            /// This type's integer fraction's denominator.
84            ///
85            /// # Examples
86            ///
87            /// ```
88            #[doc = concat!("# use ttf_view::types::", stringify!($Name), ";")]
89            #[doc = concat!("assert_eq!(", stringify!($Name), "::DENOM, ", $denom, ");")]
90            /// ```
91            pub const DENOM: $int = Self::ONE.0;
92
93            #[doc = concat!("Creates a [`", stringify!($Name), "`] from [`", stringify!($fp), "`],")]
94            #[doc = concat!("returning `None` if the value is out of `", stringify!($Name), "`'s range.")]
95            ///
96            /// # Examples
97            ///
98            /// ```
99            #[doc = concat!("use ttf_view::types::", stringify!($Name), ";")]
100            ///
101            #[doc = concat!("assert_eq!(", stringify!($Name), "::new(-0.125).unwrap(), -0.125);")]
102            #[doc = concat!("assert_eq!(", stringify!($Name), "::new(1.99609375).unwrap(), 1.99609375);")]
103            #[doc = concat!("assert_eq!(", stringify!($Name), "::new(-34000.0), None);")]
104            #[doc = concat!("assert_eq!(", stringify!($Name), "::new(34000.0), None);")]
105            #[doc = concat!("assert_eq!(", stringify!($Name), "::new(", stringify!($fp), "::NAN), None);")]
106            ///
107            #[doc = concat!("// numbers are rounded towards the closest value representable by ", stringify!($Name))]
108            #[doc = concat!("assert_eq!(", stringify!($Name), "::new(0.5156247).unwrap(), 0.515625);")]
109            #[doc = concat!("assert_eq!(", stringify!($Name), "::new(0.5156250).unwrap(), 0.515625);")]
110            #[doc = concat!("assert_eq!(", stringify!($Name), "::new(0.5156254).unwrap(), 0.515625);")]
111            /// ```
112            pub const fn new(num: $fp) -> Option<Self> {
113                if matches!(num, Self::F_MIN..Self::F_MAX_EXCLUSIVE) {
114                    Some(Self::new_saturating(num))
115                } else {
116                    None
117                }
118            }
119            #[doc = concat!("Creates a [`", stringify!($Name), "`] from [`", stringify!($fp), "`],")]
120            /// saturating at the numeric bounds, and mapping NaN to 0.
121            ///
122            /// # Examples
123            ///
124            /// ```
125            #[doc = concat!("use ttf_view::types::", stringify!($Name), ";")]
126            ///
127            #[doc = concat!("assert_eq!(", stringify!($Name), "::new_saturating(-0.125), -0.125);")]
128            #[doc = concat!("assert_eq!(", stringify!($Name), "::new_saturating(1.99609375), 1.99609375);")]
129            #[doc = concat!("assert_eq!(", stringify!($Name), "::new_saturating(-34000.0), ", $min, ");")]
130            #[doc = concat!("assert_eq!(", stringify!($Name), "::new_saturating(34000.0), ", $max, ");")]
131            #[doc = concat!("assert_eq!(", stringify!($Name), "::new_saturating(", stringify!($fp), "::NAN), 0.0);")]
132            ///
133            #[doc = concat!("// numbers are rounded towards the closest value representable by ", stringify!($Name))]
134            #[doc = concat!("assert_eq!(", stringify!($Name), "::new_saturating(0.5156247), 0.515625);")]
135            #[doc = concat!("assert_eq!(", stringify!($Name), "::new_saturating(0.5156250), 0.515625);")]
136            #[doc = concat!("assert_eq!(", stringify!($Name), "::new_saturating(0.5156254), 0.515625);")]
137            /// ```
138            pub const fn new_saturating(num: $fp) -> Self {
139                // Note: No need to worry about 1.9999999 wrapping to -2, because `as` here
140                // converts in a saturating way (even Infinity becomes 0x7FFF through `as`).
141                Self((num / Self::F_STEP).round() as $int)
142            }
143
144            #[doc = concat!("Creates a [`", stringify!($Name), "`] from big-endian bytes.")]
145            pub const fn from_be_bytes(bytes: [u8; $bytes]) -> Self {
146                Self(<$int>::from_be_bytes(bytes))
147            }
148            #[doc = concat!("Gets this [`", stringify!($Name), "`]'s big-endian bytes.")]
149            pub const fn to_be_bytes(self) -> [u8; $bytes] {
150                self.0.to_be_bytes()
151            }
152
153            #[doc = concat!("Creates a [`", stringify!($Name), "`] from its integer fraction's numerator")]
154            #[doc = concat!("(`", stringify!($Name), "` represented as <span class=\"hidden\">`numerator/", $denom, "`</span><math><mfrac><mi>numerator</mi><mn>", $denom, "</mn></mfrac></math>).")]
155            pub const fn from_frac_num(numerator: $int) -> Self {
156                Self(numerator)
157            }
158            #[doc = concat!("Returns this [`", stringify!($Name), "`]'s integer fraction's numerator")]
159            #[doc = concat!("(`", stringify!($Name), "` represented as <span class=\"hidden\">`numerator/", $denom, "`</span><math><mfrac><mi>numerator</mi><mn>", $denom, "</mn></mfrac></math>).")]
160            pub const fn frac_num(self) -> $int {
161                self.0
162            }
163
164            #[doc = concat!("Returns this [`", stringify!($Name), "`]'s value as [`", stringify!($fp), "`].")]
165            pub const fn get(self) -> $fp {
166                self.frac_num() as $fp * Self::F_STEP
167            }
168
169            /// Widening numerator multiplication: x/d * y/d = xy/dd.
170            ///
171            /// Don't forget to divide the wide numerator by [`DENOM`][Self::DENOM] before casting
172            /// it back to [`Self`] with [`from_frac_num`][Self::from_frac_num].
173            pub(crate) const fn wmul(self, rhs: Self) -> $wide {
174                self.0.widening_mul(rhs.0)
175            }
176
177            pub const fn wrapping_neg(self) -> Self {
178                Self(self.0.wrapping_neg())
179            }
180            pub const fn wrapping_add(self, rhs: Self) -> Self {
181                Self(self.0.wrapping_add(rhs.0))
182            }
183            pub const fn wrapping_sub(self, rhs: Self) -> Self {
184                Self(self.0.wrapping_sub(rhs.0))
185            }
186            pub const fn wrapping_mul(self, rhs: Self) -> Self {
187                Self((self.wmul(rhs) / $denom) as $int)
188            }
189            /// # Panics
190            ///
191            /// This function panics if `rhs == 0`.
192            pub const fn wrapping_div(self, rhs: Self) -> Self {
193                Self((self.wmul(Self::ONE) / rhs.0 as $wide) as $int)
194            }
195            /// # Panics
196            ///
197            /// This function panics if `rhs == 0`.
198            pub const fn wrapping_rem(self, rhs: Self) -> Self {
199                Self(self.0.wrapping_rem(rhs.0))
200            }
201
202            pub const fn saturating_neg(self) -> Self {
203                Self(self.0.saturating_neg())
204            }
205            pub const fn saturating_add(self, rhs: Self) -> Self {
206                Self(self.0.saturating_add(rhs.0))
207            }
208            pub const fn saturating_sub(self, rhs: Self) -> Self {
209                Self(self.0.saturating_sub(rhs.0))
210            }
211            pub const fn saturating_mul(self, rhs: Self) -> Self {
212                Self((self.wmul(rhs) / $denom).saturating_cast())
213            }
214            /// # Panics
215            ///
216            /// This function panics if `rhs == 0`.
217            pub const fn saturating_div(self, rhs: Self) -> Self {
218                Self((self.wmul(Self::ONE) / rhs.0 as $wide).saturating_cast())
219            }
220
221            pub const fn checked_neg(self) -> Option<Self> {
222                self.0.checked_neg().map(Self)
223            }
224            pub const fn checked_add(self, rhs: Self) -> Option<Self> {
225                self.0.checked_add(rhs.0).map(Self)
226            }
227            pub const fn checked_sub(self, rhs: Self) -> Option<Self> {
228                self.0.checked_sub(rhs.0).map(Self)
229            }
230            pub const fn checked_mul(self, rhs: Self) -> Option<Self> {
231                (self.wmul(rhs) / $denom).checked_cast().map(Self)
232            }
233            pub const fn checked_div(self, rhs: Self) -> Option<Self> {
234                (self.wmul(Self::ONE).checked_div(rhs.0 as $wide)?).checked_cast().map(Self)
235            }
236            pub const fn checked_rem(self, rhs: Self) -> Option<Self> {
237                self.0.checked_rem(rhs.0).map(Self)
238            }
239
240            /// Wrapping product sum operation (ab+cd+p)
241            pub(crate) const fn wrapping_maddp(a: Self, b: Self, c: Self, d: Self, p: Self) -> Self {
242                let sum = a.wmul(b).wrapping_add(c.wmul(d)) / $denom;
243                Self(sum.wrapping_add(p.0 as $wide) as $int)
244            }
245            /// Saturating product sum operation (ab+cd+p)
246            pub(crate) const fn saturating_maddp(a: Self, b: Self, c: Self, d: Self, p: Self) -> Self {
247                let sum = a.wmul(b).saturating_add(c.wmul(d)) / $denom;
248                Self(sum.saturating_add(p.0 as $wide).saturating_cast())
249            }
250            /// Checked product sum operation (ab+cd+p)
251            pub(crate) const fn checked_maddp(a: Self, b: Self, c: Self, d: Self, p: Self) -> Option<Self> {
252                let sum = a.wmul(b).checked_add(c.wmul(d))? / $denom;
253                Some(Self(sum.checked_add(p.0 as $wide)?.try_into().ok()?))
254            }
255        }
256
257        /// Performs negation `-` (panics on overflow in debug configuration).
258        const impl std::ops::Neg for $Name {
259            type Output = Self;
260            fn neg(self) -> Self::Output {
261                #[cfg(debug_assertions)]
262                { self.checked_neg().expect("attempt to negate with overflow") }
263                #[cfg(not(debug_assertions))]
264                { self.wrapping_neg() }
265            }
266        }
267        /// Performs addition `+` (panics on overflow in debug configuration).
268        const impl std::ops::Add for $Name {
269            type Output = Self;
270            fn add(self, rhs: Self) -> Self::Output {
271                #[cfg(debug_assertions)]
272                { self.checked_add(rhs).expect("attempt to add with overflow") }
273                #[cfg(not(debug_assertions))]
274                { self.wrapping_add(rhs) }
275            }
276        }
277        /// Performs subtraction `-` (panics on overflow in debug configuration).
278        const impl std::ops::Sub for $Name {
279            type Output = Self;
280            fn sub(self, rhs: Self) -> Self::Output {
281                #[cfg(debug_assertions)]
282                { self.checked_sub(rhs).expect("attempt to subtract with overflow") }
283                #[cfg(not(debug_assertions))]
284                { self.wrapping_sub(rhs) }
285            }
286        }
287        /// Performs multiplication `*` (panics on overflow in debug configuration).
288        const impl std::ops::Mul for $Name {
289            type Output = Self;
290            fn mul(self, rhs: Self) -> Self::Output {
291                #[cfg(debug_assertions)]
292                { self.checked_mul(rhs).expect("attempt to multiply with overflow") }
293                #[cfg(not(debug_assertions))]
294                { self.wrapping_mul(rhs) }
295            }
296        }
297        /// Performs division `/` (panics on overflow in debug configuration).
298        ///
299        /// # Panics
300        ///
301        /// This operation will panic if `rhs == 0`.
302        const impl std::ops::Div for $Name {
303            type Output = Self;
304            fn div(self, rhs: Self) -> Self::Output {
305                #[cfg(debug_assertions)]
306                { self.checked_div(rhs).expect("attempt to divide by zero or with overflow") }
307                #[cfg(not(debug_assertions))]
308                { self.wrapping_div(rhs) }
309            }
310        }
311        /// Performs remainder operation `%` (panics on overflow in debug configuration).
312        ///
313        /// # Panics
314        ///
315        /// This operation will panic if `rhs == 0`.
316        const impl std::ops::Rem for $Name {
317            type Output = Self;
318            fn rem(self, rhs: Self) -> Self::Output {
319                #[cfg(debug_assertions)]
320                { self.checked_rem(rhs).expect("attempt to calculate the remainder with a divisor of zero or with overflow") }
321                #[cfg(not(debug_assertions))]
322                { self.wrapping_rem(rhs) }
323            }
324        }
325
326        impl_fmt_with! { Debug, Display, LowerExp, UpperExp: |x: &$Name| x.get() }
327        impl_fmt_with! { Binary, LowerHex, UpperHex: |x: &$Name| x.0 }
328
329        const impl PartialEq<$fp> for $Name {
330            fn eq(&self, other: &$fp) -> bool {
331                self.get().eq(other)
332            }
333        }
334        const impl PartialOrd<$fp> for $Name {
335            fn partial_cmp(&self, other: &$fp) -> Option<std::cmp::Ordering> {
336                self.get().partial_cmp(other)
337            }
338        }
339
340        const impl std::ops::Mul<$fp> for $Name {
341            type Output = $fp;
342            fn mul(self, other: $fp) -> Self::Output {
343                self.get().mul(other)
344            }
345        }
346        const impl std::ops::Div<$fp> for $Name {
347            type Output = $fp;
348            fn div(self, other: $fp) -> Self::Output {
349                self.get().div(other)
350            }
351        }
352
353        impl std::str::FromStr for $Name {
354            type Err = ();
355            fn from_str(s: &str) -> Result<Self, Self::Err> {
356                <$fp>::from_str(s).or(Err(())).and_then(Self::try_from)
357            }
358        }
359        const impl TryFrom<$fp> for $Name {
360            type Error = ();
361            fn try_from(value: $fp) -> Result<Self, Self::Error> {
362                Self::new(value).ok_or(())
363            }
364        }
365        const impl From<$Name> for $fp {
366            fn from(value: $Name) -> Self {
367                value.get()
368            }
369        }
370    }
371}
372
373// The constants specified here are re-calculated in the macro and then validated in doc-tests.
374impl_fixed_point_number! {
375    pub struct Fixed(i32 as [u8; 4]; 16|16 as f64, i64);
376    DENOM = 65536;
377    STEP = 0.0000152587890625;
378    MIN = -32768.0;
379    MAX = 32767.99998474121;
380}
381impl_fixed_point_number! {
382    pub struct F2Dot14(i16 as [u8; 2]; 2|14 as f32, i32);
383    DENOM = 16384;
384    STEP = 0.000061035156;
385    MIN = -2.0;
386    MAX = 1.999939;
387}
388
389#[cfg(test)]
390mod tests {
391    use super::*;
392
393    #[test]
394    fn fixed() {
395        // Check the rounding at boundaries
396        assert_eq!(Fixed::new(32767.999999999996), Some(Fixed::MAX));
397        assert_eq!(Fixed::new(32768.0), None);
398        assert_eq!(Fixed::new(-32768.0), Some(Fixed::MIN));
399        assert_eq!(Fixed::new(-32768.00000000001), None);
400
401        // Test a bunch of sample numbers
402        let nums: [(u32, f64); _] = [
403            (0x7FFF_FFFF, 32767.999985),
404            (0x7FFF_FF00, 32767.996094),
405            (0x7FFF_2000, 32767.125000),
406            (0x7FFF_0000, 32767.000000),
407            (0x0040_0100, 64.003906),
408            (0x0040_0000, 64.000000),
409            (0x0001_0000, 1.000000),
410            (0x0000_0001, 0.000015),
411            (0x0000_0000, 0.000000),
412            (0xFFFF_0000, -1.000000),
413            (0xFFBF_FF00, -64.003906),
414            (0x8000_0000, -32768.000000),
415        ];
416
417        for (raw, fp) in nums {
418            let real = Fixed::new(fp).unwrap();
419            assert_eq!(real.0 as u32, raw, "{real:#X} != {raw:#X} ({fp})");
420
421            let diff = (real.get() - fp).abs();
422            assert!(diff <= 0.1 * Fixed::F_STEP, "{real} != {fp} (Δ={diff})");
423        }
424    }
425
426    #[test]
427    fn f2dot14() {
428        // Check the rounding at boundaries
429        assert_eq!(F2Dot14::new(1.9999999), Some(F2Dot14::MAX));
430        assert_eq!(F2Dot14::new(2.0), None);
431        assert_eq!(F2Dot14::new(-2.0), Some(F2Dot14::MIN));
432        assert_eq!(F2Dot14::new(-2.0000002), None);
433
434        // Test a bunch of sample numbers
435        let nums: [(u16, f32); _] = [
436            (0x7FFF, 1.999939),
437            (0x7000, 1.750000),
438            (0x0085, 0.008118),
439            (0x0002, 0.000122),
440            (0x0001, 0.000061),
441            (0x0000, 0.000000),
442            (0xFFFF, -0.000061),
443            (0xFFFE, -0.000122),
444            (0xFF7B, -0.008118),
445            (0x8000, -2.000000),
446        ];
447
448        for (raw, fp) in nums {
449            let real = F2Dot14::new(fp).unwrap();
450            assert_eq!(real.0 as u16, raw, "{real:#X} != {raw:#X} ({fp})");
451
452            let diff = (real.get() - fp).abs();
453            assert!(diff <= 0.1 * F2Dot14::F_STEP, "{real} != {fp} (Δ={diff})");
454        }
455    }
456}