1use 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#[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 #[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#[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 #[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#[repr(C)]
125#[derive(Clone, Copy, PartialEq, Debug)]
126pub struct ScriptMetrics {
127 pub x_size: i16,
129
130 pub y_size: i16,
132
133 pub x_offset: i16,
135
136 pub y_offset: i16,
138}
139
140
141#[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 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}