ttf_parser/tables/
os2.rs

1//! A [OS/2 and Windows Metrics Table](https://docs.microsoft.com/en-us/typography/opentype/spec/os2)
2//! implementation.
3
4use crate::LineMetrics;
5use crate::parser::Stream;
6
7const WEIGHT_CLASS_OFFSET: usize = 4;
8const 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 TYPO_ASCENDER_OFFSET: usize = 68;
15const TYPO_DESCENDER_OFFSET: usize = 70;
16const TYPO_LINE_GAP_OFFSET: usize = 72;
17const WIN_ASCENT: usize = 74;
18const WIN_DESCENT: usize = 76;
19const X_HEIGHT_OFFSET: usize = 86;
20const CAP_HEIGHT_OFFSET: usize = 88;
21
22/// A face [weight](https://docs.microsoft.com/en-us/typography/opentype/spec/os2#usweightclass).
23#[allow(missing_docs)]
24#[derive(Clone, Copy, Eq, PartialEq, Debug, Hash)]
25pub enum Weight {
26    Thin,
27    ExtraLight,
28    Light,
29    Normal,
30    Medium,
31    SemiBold,
32    Bold,
33    ExtraBold,
34    Black,
35    Other(u16),
36}
37
38impl Weight {
39    /// Returns a numeric representation of a weight.
40    #[inline]
41    pub fn to_number(self) -> u16 {
42        match self {
43            Weight::Thin        => 100,
44            Weight::ExtraLight  => 200,
45            Weight::Light       => 300,
46            Weight::Normal      => 400,
47            Weight::Medium      => 500,
48            Weight::SemiBold    => 600,
49            Weight::Bold        => 700,
50            Weight::ExtraBold   => 800,
51            Weight::Black       => 900,
52            Weight::Other(n)    => n,
53        }
54    }
55}
56
57impl From<u16> for Weight {
58    #[inline]
59    fn from(value: u16) -> Self {
60        match value {
61            100 => Weight::Thin,
62            200 => Weight::ExtraLight,
63            300 => Weight::Light,
64            400 => Weight::Normal,
65            500 => Weight::Medium,
66            600 => Weight::SemiBold,
67            700 => Weight::Bold,
68            800 => Weight::ExtraBold,
69            900 => Weight::Black,
70            _   => Weight::Other(value),
71        }
72    }
73}
74
75impl Default for Weight {
76    #[inline]
77    fn default() -> Self {
78        Weight::Normal
79    }
80}
81
82
83/// A face [width](https://docs.microsoft.com/en-us/typography/opentype/spec/os2#uswidthclass).
84#[allow(missing_docs)]
85#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Debug, Hash)]
86pub enum Width {
87    UltraCondensed,
88    ExtraCondensed,
89    Condensed,
90    SemiCondensed,
91    Normal,
92    SemiExpanded,
93    Expanded,
94    ExtraExpanded,
95    UltraExpanded,
96}
97
98impl Width {
99    /// Returns a numeric representation of a width.
100    #[inline]
101    pub fn to_number(self) -> u16 {
102        match self {
103            Width::UltraCondensed   => 1,
104            Width::ExtraCondensed   => 2,
105            Width::Condensed        => 3,
106            Width::SemiCondensed    => 4,
107            Width::Normal           => 5,
108            Width::SemiExpanded     => 6,
109            Width::Expanded         => 7,
110            Width::ExtraExpanded    => 8,
111            Width::UltraExpanded    => 9,
112        }
113    }
114}
115
116impl Default for Width {
117    #[inline]
118    fn default() -> Self {
119        Width::Normal
120    }
121}
122
123
124/// A face style.
125#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
126pub enum Style {
127    /// A face that is neither italic not obliqued.
128    Normal,
129    /// A form that is generally cursive in nature.
130    Italic,
131    /// A typically-sloped version of the regular face.
132    Oblique,
133}
134
135impl Default for Style {
136    #[inline]
137    fn default() -> Style {
138        Style::Normal
139    }
140}
141
142
143/// A script metrics used by subscript and superscript.
144#[repr(C)]
145#[derive(Clone, Copy, Eq, PartialEq, Debug, Hash)]
146pub struct ScriptMetrics {
147    /// Horizontal face size.
148    pub x_size: i16,
149
150    /// Vertical face size.
151    pub y_size: i16,
152
153    /// X offset.
154    pub x_offset: i16,
155
156    /// Y offset.
157    pub y_offset: i16,
158}
159
160
161// https://docs.microsoft.com/en-us/typography/opentype/spec/os2#fsselection
162#[derive(Clone, Copy)]
163struct SelectionFlags(u16);
164
165impl SelectionFlags {
166    #[inline] fn italic(self) -> bool { self.0 & (1 << 0) != 0 }
167    #[inline] fn bold(self) -> bool { self.0 & (1 << 5) != 0 }
168    // #[inline] fn regular(self) -> bool { self.0 & (1 << 6) != 0 }
169    #[inline] fn use_typo_metrics(self) -> bool { self.0 & (1 << 7) != 0 }
170    #[inline] fn oblique(self) -> bool { self.0 & (1 << 9) != 0 }
171}
172
173
174/// A [OS/2 and Windows Metrics Table](https://docs.microsoft.com/en-us/typography/opentype/spec/os2).
175#[derive(Clone, Copy)]
176pub struct Table<'a> {
177    /// Table version.
178    pub version: u8,
179    data: &'a [u8],
180}
181
182impl<'a> Table<'a> {
183    /// Parses a table from raw data.
184    pub fn parse(data: &'a [u8]) -> Option<Self> {
185        let mut s = Stream::new(data);
186        let version = s.read::<u16>()?;
187
188        let table_len = match version {
189            0 => 78,
190            1 => 86,
191            2 => 96,
192            3 => 96,
193            4 => 96,
194            5 => 100,
195            _ => return None,
196        };
197
198        if data.len() != table_len {
199            return None;
200        }
201
202        Some(Table {
203            version: version as u8,
204            data,
205        })
206    }
207
208    /// Returns weight class.
209    #[inline]
210    pub fn weight(&self) -> Weight {
211        Weight::from(Stream::read_at::<u16>(self.data, WEIGHT_CLASS_OFFSET).unwrap_or(0))
212    }
213
214    /// Returns face width.
215    #[inline]
216    pub fn width(&self) -> Width {
217        match Stream::read_at::<u16>(self.data, WIDTH_CLASS_OFFSET).unwrap_or(0) {
218            1 => Width::UltraCondensed,
219            2 => Width::ExtraCondensed,
220            3 => Width::Condensed,
221            4 => Width::SemiCondensed,
222            5 => Width::Normal,
223            6 => Width::SemiExpanded,
224            7 => Width::Expanded,
225            8 => Width::ExtraExpanded,
226            9 => Width::UltraExpanded,
227            _ => Width::Normal,
228        }
229    }
230
231    /// Returns subscript metrics.
232    #[inline]
233    pub fn subscript_metrics(&self) -> ScriptMetrics {
234        let mut s = Stream::new_at(self.data, Y_SUBSCRIPT_X_SIZE_OFFSET).unwrap_or_default();
235        ScriptMetrics {
236            x_size: s.read::<i16>().unwrap_or(0),
237            y_size: s.read::<i16>().unwrap_or(0),
238            x_offset: s.read::<i16>().unwrap_or(0),
239            y_offset: s.read::<i16>().unwrap_or(0),
240        }
241    }
242
243    /// Returns superscript metrics.
244    #[inline]
245    pub fn superscript_metrics(&self) -> ScriptMetrics {
246        let mut s = Stream::new_at(self.data, Y_SUPERSCRIPT_X_SIZE_OFFSET).unwrap_or_default();
247        ScriptMetrics {
248            x_size: s.read::<i16>().unwrap_or(0),
249            y_size: s.read::<i16>().unwrap_or(0),
250            x_offset: s.read::<i16>().unwrap_or(0),
251            y_offset: s.read::<i16>().unwrap_or(0),
252        }
253    }
254
255    /// Returns strikeout metrics.
256    #[inline]
257    pub fn strikeout_metrics(&self) -> LineMetrics {
258        LineMetrics {
259            thickness: Stream::read_at::<i16>(self.data, Y_STRIKEOUT_SIZE_OFFSET).unwrap_or(0),
260            position: Stream::read_at::<i16>(self.data, Y_STRIKEOUT_POSITION_OFFSET).unwrap_or(0),
261        }
262    }
263
264    #[inline]
265    fn fs_selection(&self) -> u16 {
266        Stream::read_at::<u16>(self.data, FS_SELECTION_OFFSET).unwrap_or(0)
267    }
268
269    /// Returns style.
270    pub fn style(&self) -> Style {
271        let flags = SelectionFlags(self.fs_selection());
272        if flags.italic() {
273            Style::Italic
274        } else if self.version >= 4 && flags.oblique() {
275            Style::Oblique
276        } else {
277            Style::Normal
278        }
279    }
280
281    /// Checks if face is bold.
282    ///
283    /// Do not confuse with [`Weight::Bold`].
284    #[inline]
285    pub fn is_bold(&self) -> bool {
286        SelectionFlags(self.fs_selection()).bold()
287    }
288
289    /// Checks if typographic metrics should be used.
290    #[inline]
291    pub fn use_typographic_metrics(&self) -> bool {
292        if self.version < 4 {
293            false
294        } else {
295            SelectionFlags(self.fs_selection()).use_typo_metrics()
296        }
297    }
298
299    /// Returns typographic ascender.
300    #[inline]
301    pub fn typographic_ascender(&self) -> i16 {
302        Stream::read_at::<i16>(self.data, TYPO_ASCENDER_OFFSET).unwrap_or(0)
303    }
304
305    /// Returns typographic descender.
306    #[inline]
307    pub fn typographic_descender(&self) -> i16 {
308        Stream::read_at::<i16>(self.data, TYPO_DESCENDER_OFFSET).unwrap_or(0)
309    }
310
311    /// Returns typographic line gap.
312    #[inline]
313    pub fn typographic_line_gap(&self) -> i16 {
314        Stream::read_at::<i16>(self.data, TYPO_LINE_GAP_OFFSET).unwrap_or(0)
315    }
316
317    /// Returns Windows ascender.
318    #[inline]
319    pub fn windows_ascender(&self) -> i16 {
320        Stream::read_at::<i16>(self.data, WIN_ASCENT).unwrap_or(0)
321    }
322
323    /// Returns Windows descender.
324    #[inline]
325    pub fn windows_descender(&self) -> i16 {
326        // Should be negated.
327        -Stream::read_at::<i16>(self.data, WIN_DESCENT).unwrap_or(0)
328    }
329
330    /// Returns x height.
331    ///
332    /// Returns `None` version is < 2.
333    #[inline]
334    pub fn x_height(&self) -> Option<i16> {
335        if self.version < 2 {
336            None
337        } else {
338            Stream::read_at::<i16>(self.data, X_HEIGHT_OFFSET)
339        }
340    }
341
342    /// Returns capital height.
343    ///
344    /// Returns `None` version is < 2.
345    #[inline]
346    pub fn capital_height(&self) -> Option<i16> {
347        if self.version < 2 {
348            None
349        } else {
350            Stream::read_at::<i16>(self.data, CAP_HEIGHT_OFFSET)
351        }
352    }
353}
354
355impl core::fmt::Debug for Table<'_> {
356    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
357        write!(f, "Table {{ ... }}")
358    }
359}