Skip to main content

fontdb/ttf_parser/
os2.rs

1//! A [OS/2 and Windows Metrics Table](https://docs.microsoft.com/en-us/typography/opentype/spec/os2)
2//! implementation.
3
4use super::parser::Stream;
5
6const WEIGHT_CLASS_OFFSET: usize = 4;
7const WIDTH_CLASS_OFFSET: usize = 6;
8const SELECTION_OFFSET: usize = 62;
9
10/// A face [weight](https://docs.microsoft.com/en-us/typography/opentype/spec/os2#usweightclass).
11#[allow(missing_docs)]
12#[derive(Clone, Copy, Eq, PartialEq, Debug, Hash)]
13pub enum Weight {
14    Thin,
15    ExtraLight,
16    Light,
17    Normal,
18    Medium,
19    SemiBold,
20    Bold,
21    ExtraBold,
22    Black,
23    Other(u16),
24}
25
26impl Weight {
27    /// Returns a numeric representation of a weight.
28    #[inline]
29    pub fn to_number(self) -> u16 {
30        match self {
31            Weight::Thin => 100,
32            Weight::ExtraLight => 200,
33            Weight::Light => 300,
34            Weight::Normal => 400,
35            Weight::Medium => 500,
36            Weight::SemiBold => 600,
37            Weight::Bold => 700,
38            Weight::ExtraBold => 800,
39            Weight::Black => 900,
40            Weight::Other(n) => n,
41        }
42    }
43}
44
45impl From<u16> for Weight {
46    #[inline]
47    fn from(value: u16) -> Self {
48        match value {
49            100 => Weight::Thin,
50            200 => Weight::ExtraLight,
51            300 => Weight::Light,
52            400 => Weight::Normal,
53            500 => Weight::Medium,
54            600 => Weight::SemiBold,
55            700 => Weight::Bold,
56            800 => Weight::ExtraBold,
57            900 => Weight::Black,
58            _ => Weight::Other(value),
59        }
60    }
61}
62
63impl Default for Weight {
64    #[inline]
65    fn default() -> Self {
66        Weight::Normal
67    }
68}
69
70/// A face [width](https://docs.microsoft.com/en-us/typography/opentype/spec/os2#uswidthclass).
71#[allow(missing_docs)]
72#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Debug, Hash)]
73pub enum Width {
74    UltraCondensed,
75    ExtraCondensed,
76    Condensed,
77    SemiCondensed,
78    Normal,
79    SemiExpanded,
80    Expanded,
81    ExtraExpanded,
82    UltraExpanded,
83}
84
85impl Width {
86    /// Returns a numeric representation of a width.
87    #[inline]
88    pub fn to_number(self) -> u16 {
89        match self {
90            Width::UltraCondensed => 1,
91            Width::ExtraCondensed => 2,
92            Width::Condensed => 3,
93            Width::SemiCondensed => 4,
94            Width::Normal => 5,
95            Width::SemiExpanded => 6,
96            Width::Expanded => 7,
97            Width::ExtraExpanded => 8,
98            Width::UltraExpanded => 9,
99        }
100    }
101}
102
103impl Default for Width {
104    #[inline]
105    fn default() -> Self {
106        Width::Normal
107    }
108}
109
110/// A face style.
111#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
112pub enum Style {
113    /// A face that is neither italic not obliqued.
114    Normal,
115    /// A form that is generally cursive in nature.
116    Italic,
117    /// A typically-sloped version of the regular face.
118    Oblique,
119}
120
121impl Default for Style {
122    #[inline]
123    fn default() -> Style {
124        Style::Normal
125    }
126}
127
128// https://docs.microsoft.com/en-us/typography/opentype/spec/os2#fsselection
129#[derive(Clone, Copy)]
130struct SelectionFlags(u16);
131
132#[rustfmt::skip]
133impl SelectionFlags {
134    #[inline] fn italic(self) -> bool { self.0 & (1 << 0) != 0 }
135    #[inline] fn oblique(self) -> bool { self.0 & (1 << 9) != 0 }
136}
137
138/// A [OS/2 and Windows Metrics Table](https://docs.microsoft.com/en-us/typography/opentype/spec/os2).
139#[derive(Clone, Copy)]
140pub struct Table<'a> {
141    /// Table version.
142    pub version: u8,
143    data: &'a [u8],
144}
145
146impl<'a> Table<'a> {
147    /// Parses a table from raw data.
148    pub fn parse(data: &'a [u8]) -> Option<Self> {
149        let mut s = Stream::new(data);
150        let version = s.read::<u16>()?;
151
152        let table_len = match version {
153            0 => 78,
154            1 => 86,
155            2 => 96,
156            3 => 96,
157            4 => 96,
158            5 => 100,
159            _ => return None,
160        };
161
162        // Do not check the exact length, because some fonts include
163        // padding in table's length in table records, which is incorrect.
164        if data.len() < table_len {
165            return None;
166        }
167
168        Some(Table {
169            version: version as u8,
170            data,
171        })
172    }
173
174    /// Returns weight class.
175    #[inline]
176    pub fn weight(&self) -> Weight {
177        Weight::from(Stream::read_at::<u16>(self.data, WEIGHT_CLASS_OFFSET).unwrap_or(0))
178    }
179
180    /// Returns face width.
181    #[inline]
182    pub fn width(&self) -> Width {
183        match Stream::read_at::<u16>(self.data, WIDTH_CLASS_OFFSET).unwrap_or(0) {
184            1 => Width::UltraCondensed,
185            2 => Width::ExtraCondensed,
186            3 => Width::Condensed,
187            4 => Width::SemiCondensed,
188            5 => Width::Normal,
189            6 => Width::SemiExpanded,
190            7 => Width::Expanded,
191            8 => Width::ExtraExpanded,
192            9 => Width::UltraExpanded,
193            _ => Width::Normal,
194        }
195    }
196
197    #[inline]
198    fn fs_selection(&self) -> u16 {
199        Stream::read_at::<u16>(self.data, SELECTION_OFFSET).unwrap_or(0)
200    }
201
202    /// Returns style.
203    pub fn style(&self) -> Style {
204        let flags = SelectionFlags(self.fs_selection());
205        if flags.italic() {
206            Style::Italic
207        } else if self.version >= 4 && flags.oblique() {
208            Style::Oblique
209        } else {
210            Style::Normal
211        }
212    }
213}