Skip to main content

ttf_view/types/
version16dot16.rs

1use std::{fmt, num::ParseIntError};
2
3/// An [OpenType Version16Dot16][spec] version.
4///
5/// The upper 16 bits comprise a major version number, and the highest-order nibble (4 bits) of the
6/// lower 16 bits comprises a minor version number (0..=9 range). Simply put, the major version is
7/// in `0xFFFF0000`, and the minor version is in `0x0000F000`.
8///
9/// [spec]: https://learn.microsoft.com/en-us/typography/opentype/spec/otff#table-version-numbers
10#[derive(Copy, Hash)]
11#[derive_const(Clone, Default, PartialEq, Eq, PartialOrd, Ord)]
12#[repr(transparent)]
13pub struct Version16Dot16(u32);
14
15impl Version16Dot16 {
16    /// Version 0.5.
17    pub const V0_5: Self = Self::new(0, 5).unwrap();
18    /// Version 1.0.
19    pub const V1_0: Self = Self::new(1, 0).unwrap();
20
21    /// Creates a [`Version16Dot16`] from major and minor version numbers.
22    ///
23    /// # Examples
24    ///
25    /// ```
26    /// use ttf_view::types::Version16Dot16;
27    ///
28    /// assert_eq!(Version16Dot16::new(0, 5).unwrap().tuple(), (0, 5));
29    /// assert_eq!(Version16Dot16::new(15, 9).unwrap().tuple(), (15, 9));
30    /// assert_eq!(Version16Dot16::new(0xFFFF, 9).unwrap().tuple(), (0xFFFF, 9));
31    /// assert_eq!(Version16Dot16::new(15, 10), None);
32    /// assert_eq!(Version16Dot16::new(1, 199), None);
33    /// ```
34    pub const fn new(major: u16, minor: u8) -> Option<Self> {
35        if minor <= 9 { Some(unsafe { Self::new_unchecked(major, minor) }) } else { None }
36    }
37    /// Creates a [`Version16Dot16`] from major and minor version numbers without checks.
38    ///
39    /// # Examples
40    ///
41    /// ```
42    /// use ttf_view::types::Version16Dot16;
43    ///
44    /// assert_eq!(unsafe { Version16Dot16::new_unchecked(0, 5).tuple() }, (0, 5));
45    /// assert_eq!(unsafe { Version16Dot16::new_unchecked(0xFFFF, 9).tuple() }, (0xFFFF, 9));
46    /// ```
47    pub const unsafe fn new_unchecked(major: u16, minor: u8) -> Self {
48        // Note: no debug_assert! here, because we kinda want to support invalid minor bytes too.
49        Self(((major as u32) << 16) | ((minor as u32) << 12))
50    }
51
52    /// Creates a [`Version16Dot16`] from big-endian bytes.
53    ///
54    /// # Examples
55    ///
56    /// ```
57    /// use ttf_view::types::Version16Dot16;
58    ///
59    /// assert_eq!(Version16Dot16::from_be_bytes([0x00, 0x00, 0x50, 0x00]).unwrap().tuple(), (0, 5));
60    /// assert_eq!(Version16Dot16::from_be_bytes([0x00, 0x01, 0x90, 0x00]).unwrap().tuple(), (1, 9));
61    /// assert_eq!(Version16Dot16::from_be_bytes([0x12, 0x34, 0x90, 0x00]).unwrap().tuple(), (0x1234, 9));
62    /// assert_eq!(Version16Dot16::from_be_bytes([0x00, 0x01, 0xA0, 0x00]), None);
63    /// assert_eq!(Version16Dot16::from_be_bytes([0x00, 0x01, 0x90, 0x64]), None);
64    /// ```
65    pub const fn from_be_bytes(bytes: [u8; 4]) -> Option<Self> {
66        match bytes {
67            [_, _, x @ 0x00..=0x90, 0] if (x & 0x0F) == 0 => Some(Self(u32::from_be_bytes(bytes))),
68            _ => None,
69        }
70    }
71    /// Creates a [`Version16Dot16`] from big-endian bytes without checks.
72    ///
73    /// # Examples
74    ///
75    /// ```
76    /// use ttf_view::types::Version16Dot16;
77    ///
78    /// assert_eq!(unsafe { Version16Dot16::from_be_bytes_unchecked([0x00, 0x00, 0x50, 0x00]).tuple() }, (0, 5));
79    /// assert_eq!(unsafe { Version16Dot16::from_be_bytes_unchecked([0x00, 0x01, 0x90, 0x00]).tuple() }, (1, 9));
80    /// assert_eq!(unsafe { Version16Dot16::from_be_bytes_unchecked([0x12, 0x34, 0x90, 0x00]).tuple() }, (0x1234, 9));
81    /// ```
82    pub const unsafe fn from_be_bytes_unchecked(bytes: [u8; 4]) -> Self {
83        // Note: no debug_assert! here, because we kinda want to support invalid minor bytes too.
84        Self(u32::from_be_bytes(bytes))
85    }
86    /// Gets this [`Version16Dot16`]'s big-endian bytes.
87    ///
88    /// # Examples
89    ///
90    /// ```
91    /// use ttf_view::types::Version16Dot16;
92    ///
93    /// assert_eq!(Version16Dot16::new(1, 5).unwrap().to_be_bytes(), [0x00, 0x01, 0x50, 0x00]);
94    /// assert_eq!(Version16Dot16::new(0x1234, 5).unwrap().to_be_bytes(), [0x12, 0x34, 0x50, 0x00]);
95    /// assert_eq!(Version16Dot16::new(0x1234, 9).unwrap().to_be_bytes(), [0x12, 0x34, 0x90, 0x00]);
96    /// ```
97    pub const fn to_be_bytes(self) -> [u8; 4] {
98        self.0.to_be_bytes()
99    }
100
101    /// Gets this [`Version16Dot16`]'s major version number.
102    ///
103    /// # Examples
104    ///
105    /// ```
106    /// use ttf_view::types::Version16Dot16;
107    ///
108    /// assert_eq!(Version16Dot16::new(0, 9).unwrap().major(), 0);
109    /// assert_eq!(Version16Dot16::new(13, 2).unwrap().major(), 13);
110    /// assert_eq!(Version16Dot16::new(0xFFFF, 5).unwrap().major(), 0xFFFF);
111    /// ```
112    pub const fn major(&self) -> u16 {
113        (self.0 >> 16) as u16
114    }
115    /// Gets this [`Version16Dot16`]'s minor version number.
116    ///
117    /// # Examples
118    ///
119    /// ```
120    /// use ttf_view::types::Version16Dot16;
121    ///
122    /// assert_eq!(Version16Dot16::new(0, 9).unwrap().minor(), 9);
123    /// assert_eq!(Version16Dot16::new(13, 2).unwrap().minor(), 2);
124    /// assert_eq!(Version16Dot16::new(0xFFFF, 5).unwrap().minor(), 5);
125    /// ```
126    pub const fn minor(&self) -> u8 {
127        ((self.0 & 0xFFFF) >> 12) as u8
128    }
129    /// Gets this [`Version16Dot16`]'s major and minor version numbers as a tuple.
130    ///
131    /// # Examples
132    ///
133    /// ```
134    /// use ttf_view::types::Version16Dot16;
135    ///
136    /// assert_eq!(Version16Dot16::new(0, 9).unwrap().tuple(), (0, 9));
137    /// assert_eq!(Version16Dot16::new(13, 2).unwrap().tuple(), (13, 2));
138    /// assert_eq!(Version16Dot16::new(0xFFFF, 5).unwrap().tuple(), (0xFFFF, 5));
139    /// ```
140    pub const fn tuple(&self) -> (u16, u8) {
141        (self.major(), self.minor())
142    }
143}
144
145impl fmt::Debug for Version16Dot16 {
146    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
147        fmt::Display::fmt(self, f)
148    }
149}
150impl fmt::Display for Version16Dot16 {
151    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
152        write!(f, "{}.{}", self.major(), self.minor())
153    }
154}
155impl fmt::LowerHex for Version16Dot16 {
156    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
157        u32::from_be_bytes(self.to_be_bytes()).fmt(f)
158    }
159}
160impl fmt::UpperHex for Version16Dot16 {
161    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
162        u32::from_be_bytes(self.to_be_bytes()).fmt(f)
163    }
164}
165
166/// Error type for parsing [`Version16Dot16`].
167// TODO: When ParseIntError's Clone + PartialEq + Eq are constified, make derives const
168#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
169pub enum ParseVersion16Dot16Error {
170    /// The string doesn't contain a dot `.`.
171    #[error("dot not found in string")]
172    NoDot,
173    /// The minor version is not in 0..=9 range.
174    #[error("minor version is not in 0..=9 range")]
175    Invalid,
176    /// Could not parse major/minor version component.
177    #[error("number parsing error: {0}")]
178    Number(ParseIntError),
179}
180
181impl std::str::FromStr for Version16Dot16 {
182    type Err = ParseVersion16Dot16Error;
183    fn from_str(s: &str) -> Result<Self, Self::Err> {
184        let (major, minor) = s.split_once('.').ok_or(ParseVersion16Dot16Error::NoDot)?;
185        Self::new(
186            major.parse().map_err(ParseVersion16Dot16Error::Number)?,
187            minor.parse().map_err(ParseVersion16Dot16Error::Number)?,
188        )
189        .ok_or(ParseVersion16Dot16Error::Invalid)
190    }
191}