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}