ttf_parser/tables/
os2.rs

1// https://docs.microsoft.com/en-us/typography/opentype/spec/os2
2
3use crate::LineMetrics;
4use crate::parser::Stream;
5
6
7const US_WEIGHT_CLASS_OFFSET: usize = 4;
8const US_WIDTH_CLASS_OFFSET: usize = 6;
9const Y_SUBSCRIPT_X_SIZE_OFFSET: usize = 10;
10const Y_SUPERSCRIPT_X_SIZE_OFFSET: usize = 18;
11const Y_STRIKEOUT_SIZE_OFFSET: usize = 26;
12const Y_STRIKEOUT_POSITION_OFFSET: usize = 28;
13const FS_SELECTION_OFFSET: usize = 62;
14const S_TYPO_ASCENDER_OFFSET: usize = 68;
15const S_TYPO_DESCENDER_OFFSET: usize = 70;
16const S_TYPO_LINE_GAP_OFFSET: usize = 72;
17const SX_HEIGHT_OFFSET: usize = 86;
18const S_CAP_HEIGHT_OFFSET: usize = 88;
19
20
21/// A font [weight](https://docs.microsoft.com/en-us/typography/opentype/spec/os2#usweightclass).
22#[derive(Clone, Copy, PartialEq, Debug)]
23#[allow(missing_docs)]
24pub enum Weight {
25    Thin,
26    ExtraLight,
27    Light,
28    Normal,
29    Medium,
30    SemiBold,
31    Bold,
32    ExtraBold,
33    Black,
34    Other(u16),
35}
36
37impl Weight {
38    /// Returns a numeric representation of a weight.
39    #[inline]
40    pub fn to_number(self) -> u16 {
41        match self {
42            Weight::Thin        => 100,
43            Weight::ExtraLight  => 200,
44            Weight::Light       => 300,
45            Weight::Normal      => 400,
46            Weight::Medium      => 500,
47            Weight::SemiBold    => 600,
48            Weight::Bold        => 700,
49            Weight::ExtraBold   => 800,
50            Weight::Black       => 900,
51            Weight::Other(n)    => n,
52        }
53    }
54}
55
56impl From<u16> for Weight {
57    #[inline]
58    fn from(value: u16) -> Self {
59        match value {
60            100 => Weight::Thin,
61            200 => Weight::ExtraLight,
62            300 => Weight::Light,
63            400 => Weight::Normal,
64            500 => Weight::Medium,
65            600 => Weight::SemiBold,
66            700 => Weight::Bold,
67            800 => Weight::ExtraBold,
68            900 => Weight::Black,
69            _   => Weight::Other(value),
70        }
71    }
72}
73
74impl Default for Weight {
75    #[inline]
76    fn default() -> Self {
77        Weight::Normal
78    }
79}
80
81
82/// A font [width](https://docs.microsoft.com/en-us/typography/opentype/spec/os2#uswidthclass).
83#[derive(Clone, Copy, PartialEq, PartialOrd, Debug)]
84#[allow(missing_docs)]
85pub enum Width {
86    UltraCondensed,
87    ExtraCondensed,
88    Condensed,
89    SemiCondensed,
90    Normal,
91    SemiExpanded,
92    Expanded,
93    ExtraExpanded,
94    UltraExpanded,
95}
96
97impl Width {
98    /// Returns a numeric representation of a width.
99    #[inline]
100    pub fn to_number(self) -> u16 {
101        match self {
102            Width::UltraCondensed   => 1,
103            Width::ExtraCondensed   => 2,
104            Width::Condensed        => 3,
105            Width::SemiCondensed    => 4,
106            Width::Normal           => 5,
107            Width::SemiExpanded     => 6,
108            Width::Expanded         => 7,
109            Width::ExtraExpanded    => 8,
110            Width::UltraExpanded    => 9,
111        }
112    }
113}
114
115impl Default for Width {
116    #[inline]
117    fn default() -> Self {
118        Width::Normal
119    }
120}
121
122
123/// A script metrics used by subscript and superscript.
124#[repr(C)]
125#[derive(Clone, Copy, PartialEq, Debug)]
126pub struct ScriptMetrics {
127    /// Horizontal font size.
128    pub x_size: i16,
129
130    /// Vertical font size.
131    pub y_size: i16,
132
133    /// X offset.
134    pub x_offset: i16,
135
136    /// Y offset.
137    pub y_offset: i16,
138}
139
140
141// https://docs.microsoft.com/en-us/typography/opentype/spec/os2#fsselection
142#[derive(Clone, Copy)]
143struct SelectionFlags(u16);
144
145impl SelectionFlags {
146    #[inline] fn italic(self) -> bool { self.0 & (1 << 0) != 0 }
147    #[inline] fn bold(self) -> bool { self.0 & (1 << 5) != 0 }
148    #[inline] fn regular(self) -> bool { self.0 & (1 << 6) != 0 }
149    #[inline] fn use_typo_metrics(self) -> bool { self.0 & (1 << 7) != 0 }
150    #[inline] fn oblique(self) -> bool { self.0 & (1 << 9) != 0 }
151}
152
153
154#[derive(Clone, Copy)]
155pub(crate) struct Table<'a> {
156    version: u8,
157    data: &'a [u8],
158}
159
160impl<'a> Table<'a> {
161    pub fn parse(data: &'a [u8]) -> Option<Self> {
162        let mut s = Stream::new(data);
163        let version: u16 = s.read()?;
164
165        let table_len = match version {
166            0 => 78,
167            1 => 86,
168            2 => 96,
169            3 => 96,
170            4 => 96,
171            5 => 100,
172            _ => return None,
173        };
174
175        if data.len() != table_len {
176            return None;
177        }
178
179        Some(Table {
180            version: version as u8,
181            data,
182        })
183    }
184
185    #[inline]
186    pub fn weight(&self) -> Weight {
187        Weight::from(Stream::read_at::<u16>(self.data, US_WEIGHT_CLASS_OFFSET).unwrap_or(0))
188    }
189
190    #[inline]
191    pub fn width(&self) -> Width {
192        match Stream::read_at::<u16>(self.data, US_WIDTH_CLASS_OFFSET).unwrap_or(0) {
193            1 => Width::UltraCondensed,
194            2 => Width::ExtraCondensed,
195            3 => Width::Condensed,
196            4 => Width::SemiCondensed,
197            5 => Width::Normal,
198            6 => Width::SemiExpanded,
199            7 => Width::Expanded,
200            8 => Width::ExtraExpanded,
201            9 => Width::UltraExpanded,
202            _ => Width::Normal,
203        }
204    }
205
206    #[inline]
207    pub fn is_regular(&self) -> bool {
208        SelectionFlags(self.fs_selection()).regular()
209    }
210
211    #[inline]
212    pub fn is_italic(&self) -> bool {
213        SelectionFlags(self.fs_selection()).italic()
214    }
215
216    #[inline]
217    pub fn is_bold(&self) -> bool {
218        SelectionFlags(self.fs_selection()).bold()
219    }
220
221    #[inline]
222    pub fn is_oblique(&self) -> bool {
223        if self.version < 4 {
224            false
225        } else {
226            SelectionFlags(self.fs_selection()).oblique()
227        }
228    }
229
230    #[inline]
231    pub(crate) fn is_use_typo_metrics(&self) -> bool {
232        if self.version < 4 {
233            false
234        } else {
235            SelectionFlags(self.fs_selection()).use_typo_metrics()
236        }
237    }
238
239    #[inline]
240    pub fn x_height(&self) -> Option<i16> {
241        if self.version < 2 {
242            None
243        } else {
244            // We cannot use SafeStream here, because x height is an optional data.
245            Stream::read_at::<i16>(self.data, SX_HEIGHT_OFFSET)
246        }
247    }
248
249    #[inline]
250    pub fn cap_height(&self) -> Option<i16> {
251        if self.version < 2 {
252            None
253        } else {
254            Stream::read_at::<i16>(self.data, S_CAP_HEIGHT_OFFSET)
255        }
256    }
257
258    #[inline]
259    pub fn strikeout_metrics(&self) -> LineMetrics {
260        LineMetrics {
261            thickness: Stream::read_at::<i16>(self.data, Y_STRIKEOUT_SIZE_OFFSET).unwrap_or(0),
262            position: Stream::read_at::<i16>(self.data, Y_STRIKEOUT_POSITION_OFFSET).unwrap_or(0),
263        }
264    }
265
266    #[inline]
267    pub fn subscript_metrics(&self) -> ScriptMetrics {
268        let mut s = Stream::new_at(self.data, Y_SUBSCRIPT_X_SIZE_OFFSET).unwrap_or_default();
269        ScriptMetrics {
270            x_size: s.read::<i16>().unwrap_or(0),
271            y_size: s.read::<i16>().unwrap_or(0),
272            x_offset: s.read::<i16>().unwrap_or(0),
273            y_offset: s.read::<i16>().unwrap_or(0),
274        }
275    }
276
277    #[inline]
278    pub fn superscript_metrics(&self) -> ScriptMetrics {
279        let mut s = Stream::new_at(self.data, Y_SUPERSCRIPT_X_SIZE_OFFSET).unwrap_or_default();
280        ScriptMetrics {
281            x_size: s.read::<i16>().unwrap_or(0),
282            y_size: s.read::<i16>().unwrap_or(0),
283            x_offset: s.read::<i16>().unwrap_or(0),
284            y_offset: s.read::<i16>().unwrap_or(0),
285        }
286    }
287
288    #[inline]
289    pub fn typo_ascender(&self) -> i16 {
290        Stream::read_at::<i16>(self.data, S_TYPO_ASCENDER_OFFSET).unwrap_or(0)
291    }
292
293    #[inline]
294    pub fn typo_descender(&self) -> i16 {
295        Stream::read_at::<i16>(self.data, S_TYPO_DESCENDER_OFFSET).unwrap_or(0)
296    }
297
298    #[inline]
299    pub fn typo_line_gap(&self) -> i16 {
300        Stream::read_at::<i16>(self.data, S_TYPO_LINE_GAP_OFFSET).unwrap_or(0)
301    }
302
303    #[inline]
304    fn fs_selection(&self) -> u16 {
305        Stream::read_at::<u16>(self.data, FS_SELECTION_OFFSET).unwrap_or(0)
306    }
307}