1use 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#[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 #[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#[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 #[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#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
126pub enum Style {
127 Normal,
129 Italic,
131 Oblique,
133}
134
135impl Default for Style {
136 #[inline]
137 fn default() -> Style {
138 Style::Normal
139 }
140}
141
142
143#[repr(C)]
145#[derive(Clone, Copy, Eq, PartialEq, Debug, Hash)]
146pub struct ScriptMetrics {
147 pub x_size: i16,
149
150 pub y_size: i16,
152
153 pub x_offset: i16,
155
156 pub y_offset: i16,
158}
159
160
161#[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 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#[derive(Clone, Copy)]
176pub struct Table<'a> {
177 pub version: u8,
179 data: &'a [u8],
180}
181
182impl<'a> Table<'a> {
183 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 #[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 #[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 #[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 #[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 #[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 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 #[inline]
285 pub fn is_bold(&self) -> bool {
286 SelectionFlags(self.fs_selection()).bold()
287 }
288
289 #[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 #[inline]
301 pub fn typographic_ascender(&self) -> i16 {
302 Stream::read_at::<i16>(self.data, TYPO_ASCENDER_OFFSET).unwrap_or(0)
303 }
304
305 #[inline]
307 pub fn typographic_descender(&self) -> i16 {
308 Stream::read_at::<i16>(self.data, TYPO_DESCENDER_OFFSET).unwrap_or(0)
309 }
310
311 #[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 #[inline]
319 pub fn windows_ascender(&self) -> i16 {
320 Stream::read_at::<i16>(self.data, WIN_ASCENT).unwrap_or(0)
321 }
322
323 #[inline]
325 pub fn windows_descender(&self) -> i16 {
326 -Stream::read_at::<i16>(self.data, WIN_DESCENT).unwrap_or(0)
328 }
329
330 #[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 #[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}