Skip to main content

font_types/
version.rs

1/// A legacy 16/16 version encoding
2/// Packed 32-bit value with major and minor version numbers.
3///
4/// This is a legacy type with an unusual representation. See [the spec][spec] for
5/// additional details.
6///
7/// [spec]: https://learn.microsoft.com/en-us/typography/opentype/spec/otff#table-version-numbers
8#[derive(Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
9#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
10#[cfg_attr(feature = "bytemuck", derive(bytemuck::AnyBitPattern))]
11#[repr(transparent)]
12pub struct Version16Dot16(u32);
13
14/// A type representing a major, minor version pair.
15///
16/// This is not part of [the spec][spec], but versions in the spec are frequently
17/// represented as a `major_version`, `minor_version` pair. This type encodes
18/// those as a single type, which is useful for some of the generated code that
19/// parses out a version.
20///
21/// [spec]: https://learn.microsoft.com/en-us/typography/opentype/spec/otff#table-version-numbers
22#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
23#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
24#[cfg_attr(feature = "bytemuck", derive(bytemuck::AnyBitPattern))]
25#[repr(C)]
26pub struct MajorMinor {
27    /// The major version number
28    pub major: u16,
29    /// The minor version number
30    pub minor: u16,
31}
32
33/// A trait for determining whether versions are compatible.
34pub trait Compatible<Rhs = Self>: Sized {
35    /// return `true` if this version is field-compatible with `other`.
36    ///
37    /// This is kind of poorly defined, but basically means 'same major version,
38    /// greater than or equal minor version'.
39    fn compatible(&self, other: Rhs) -> bool;
40}
41
42impl Version16Dot16 {
43    /// Version 0.5
44    pub const VERSION_0_5: Version16Dot16 = Version16Dot16::new(0, 5);
45    /// Version 1.0
46    pub const VERSION_1_0: Version16Dot16 = Version16Dot16::new(1, 0);
47    /// Version 1.1
48    pub const VERSION_1_1: Version16Dot16 = Version16Dot16::new(1, 1);
49    /// Version 2.0
50    pub const VERSION_2_0: Version16Dot16 = Version16Dot16::new(2, 0);
51    /// Version 2.5
52    pub const VERSION_2_5: Version16Dot16 = Version16Dot16::new(2, 5);
53    /// Version 3.0
54    pub const VERSION_3_0: Version16Dot16 = Version16Dot16::new(3, 0);
55
56    /// Create a new version with the provided major and minor parts.
57    ///
58    /// The minor version must be in the range 0..=9, and will be clamped to
59    /// that range if it is not.
60    pub const fn new(major: u16, minor: u16) -> Self {
61        // Clamp minor to 0..=9, but in a way that works in a const context
62        let minor = if minor > 9 { 9 } else { minor };
63        let version = ((major as u32) << 16) | ((minor as u32) << 12);
64        Version16Dot16(version)
65    }
66
67    /// Return the separate major & minor version numbers.
68    pub const fn to_major_minor(self) -> (u16, u16) {
69        let major = (self.0 >> 16) as u16;
70        let minor = ((self.0 & 0xFFFF) >> 12) as u16;
71        (major, minor)
72    }
73
74    /// The representation of this version as a big-endian byte array.
75    #[inline]
76    pub const fn to_be_bytes(self) -> [u8; 4] {
77        self.0.to_be_bytes()
78    }
79}
80
81crate::newtype_scalar!(Version16Dot16, [u8; 4]);
82
83impl MajorMinor {
84    /// Version 1.0
85    pub const VERSION_1_0: MajorMinor = MajorMinor::new(1, 0);
86    /// Version 1.1
87    pub const VERSION_1_1: MajorMinor = MajorMinor::new(1, 1);
88    /// Version 1.2
89    pub const VERSION_1_2: MajorMinor = MajorMinor::new(1, 2);
90    /// Version 1.3
91    pub const VERSION_1_3: MajorMinor = MajorMinor::new(1, 3);
92    /// Version 2.0
93    pub const VERSION_2_0: MajorMinor = MajorMinor::new(2, 0);
94
95    /// Create a new version with major and minor parts.
96    #[inline]
97    pub const fn new(major: u16, minor: u16) -> Self {
98        MajorMinor { major, minor }
99    }
100
101    /// The representation of this version as a big-endian byte array.
102    #[inline]
103    pub const fn to_be_bytes(self) -> [u8; 4] {
104        let [a, b] = self.major.to_be_bytes();
105        let [c, d] = self.minor.to_be_bytes();
106        [a, b, c, d]
107    }
108}
109
110impl crate::Scalar for MajorMinor {
111    type Raw = [u8; 4];
112
113    fn from_raw(raw: Self::Raw) -> Self {
114        let major = u16::from_be_bytes([raw[0], raw[1]]);
115        let minor = u16::from_be_bytes([raw[2], raw[3]]);
116        Self { major, minor }
117    }
118
119    fn to_raw(self) -> Self::Raw {
120        self.to_be_bytes()
121    }
122}
123
124impl Compatible for Version16Dot16 {
125    #[inline]
126    fn compatible(&self, other: Self) -> bool {
127        let (self_major, self_minor) = self.to_major_minor();
128        let (other_major, other_minor) = other.to_major_minor();
129        self_major == other_major && self_minor >= other_minor
130    }
131}
132
133impl Compatible<(u16, u16)> for Version16Dot16 {
134    fn compatible(&self, other: (u16, u16)) -> bool {
135        self.compatible(Version16Dot16::new(other.0, other.1))
136    }
137}
138
139impl Compatible for MajorMinor {
140    #[inline]
141    fn compatible(&self, other: Self) -> bool {
142        self.major == other.major && self.minor >= other.minor
143    }
144}
145
146impl Compatible<(u16, u16)> for MajorMinor {
147    fn compatible(&self, other: (u16, u16)) -> bool {
148        self.compatible(MajorMinor::new(other.0, other.1))
149    }
150}
151
152impl Compatible for u16 {
153    #[inline]
154    fn compatible(&self, other: Self) -> bool {
155        *self >= other
156    }
157}
158
159impl std::fmt::Debug for Version16Dot16 {
160    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
161        write!(f, "Version16Dot16({:08x})", self.0)
162    }
163}
164
165impl std::fmt::Display for Version16Dot16 {
166    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
167        let (major, minor) = self.to_major_minor();
168        write!(f, "{major}.{minor}")
169    }
170}
171
172impl std::fmt::Display for MajorMinor {
173    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
174        let MajorMinor { major, minor } = self;
175        write!(f, "{major}.{minor}")
176    }
177}
178
179#[cfg(test)]
180mod tests {
181    use super::*;
182    #[test]
183    fn version_smoke_test() {
184        assert_eq!(Version16Dot16(0x00005000).to_major_minor(), (0, 5));
185        assert_eq!(Version16Dot16(0x00011000).to_major_minor(), (1, 1));
186        assert_eq!(Version16Dot16::new(0, 5).0, 0x00005000);
187        assert_eq!(Version16Dot16::new(1, 1).0, 0x00011000);
188    }
189
190    #[test]
191    fn minor_version_clamped_test() {
192        for minor in 0..=9 {
193            assert_eq!(Version16Dot16::new(1, minor).to_major_minor(), (1, minor));
194        }
195        // Minor versions above 9 should be clamped
196        for minor in [10, u16::MAX] {
197            assert_eq!(Version16Dot16::new(1, minor).to_major_minor(), (1, 9));
198        }
199    }
200}