Skip to main content

ttf_view/tables/os_2/
mod.rs

1#![allow(non_camel_case_types)]
2use crate::{
3    tables::{Table, TableDirectory, TableError},
4    types::{FWORD, Tag, UFWORD, int16, tags, uint16, uint32},
5};
6
7mod code_pages;
8mod panose;
9mod unicode_ranges;
10
11pub use code_pages::*;
12pub use panose::*;
13pub use unicode_ranges::*;
14
15#[repr(C)]
16pub struct Os_2Base {
17    _exhaustive_but_dont_instantiate: (),
18    // version any:
19    pub version: uint16,
20    pub x_avg_char_width: FWORD,
21    pub us_weight_class: uint16,
22    pub us_width_class: uint16,
23    pub fs_type: uint16,
24    pub y_subscript_x_size: FWORD,
25    pub y_subscript_y_size: FWORD,
26    pub y_subscript_x_offset: FWORD,
27    pub y_subscript_y_offset: FWORD,
28    pub y_superscript_x_size: FWORD,
29    pub y_superscript_y_size: FWORD,
30    pub y_superscript_x_offset: FWORD,
31    pub y_superscript_y_offset: FWORD,
32    pub y_strikeout_size: FWORD,
33    pub y_strikeout_position: FWORD,
34    pub s_family_class: int16,
35    pub panose: Panose,
36    pub ul_unicode_range_1: uint32,
37    pub ul_unicode_range_2: uint32,
38    pub ul_unicode_range_3: uint32,
39    pub ul_unicode_range_4: uint32,
40    pub ach_vend_id: Tag,
41    pub fs_selection: uint16,
42    pub us_first_char_index: uint16,
43    pub us_last_char_index: uint16,
44}
45#[repr(C)]
46pub struct Os_2V0 {
47    base: Os_2Base,
48    // version ≥ 0:
49    // Apple's docs attribute these 5 fields to v1 instead of v0, so they could be missing in
50    // some malformed fonts. For the purposes of versioning such a table is considered Os_2Base,
51    // and you need to explicitly check that a table is a properly defined v0 table.
52    pub s_typo_ascender: FWORD,
53    pub s_typo_descender: FWORD,
54    pub s_typo_line_gap: FWORD,
55    pub us_win_ascent: UFWORD,
56    pub us_win_descent: UFWORD,
57}
58#[repr(C)]
59pub struct Os_2V1 {
60    v0: Os_2V0,
61    // version ≥ 1:
62    pub ul_code_page_range_1: uint32,
63    pub ul_code_page_range_2: uint32,
64}
65#[repr(C)]
66pub struct Os_2V4 {
67    v1: Os_2V1,
68    // version ≥ 4:
69    pub sx_height: FWORD,
70    pub s_cap_height: FWORD,
71    pub us_default_char: uint16,
72    pub us_break_char: uint16,
73    pub us_max_content: uint16,
74}
75#[repr(C)]
76pub struct Os_2V5 {
77    v4: Os_2V4,
78    // version ≥ 5:
79    pub us_lower_optical_point_size: uint16,
80    pub us_upper_optical_point_size: uint16,
81}
82
83const impl std::ops::Deref for Os_2V0 {
84    type Target = Os_2Base;
85    fn deref(&self) -> &Self::Target {
86        &self.base
87    }
88}
89const impl std::ops::Deref for Os_2V1 {
90    type Target = Os_2V0;
91    fn deref(&self) -> &Self::Target {
92        &self.v0
93    }
94}
95const impl std::ops::Deref for Os_2V4 {
96    type Target = Os_2V1;
97    fn deref(&self) -> &Self::Target {
98        &self.v1
99    }
100}
101const impl std::ops::Deref for Os_2V5 {
102    type Target = Os_2V4;
103    fn deref(&self) -> &Self::Target {
104        &self.v4
105    }
106}
107
108impl<'a> Table<'a> for Os_2<'a> {
109    const TAG: Tag = tags::OS_2;
110    fn new_in(dir: &'a TableDirectory) -> Result<Self, TableError> {
111        let rec = dir.table_record(tags::OS_2).ok_or(TableError::NotFound)?;
112        let base = rec.raw_as::<Os_2Base>().ok_or(TableError::InvalidLen)?;
113
114        let required_len = match base.version.get() {
115            0 => size_of::<Os_2Base>() as u32,
116            1..=3 => size_of::<Os_2V1>() as u32,
117            4 => size_of::<Os_2V4>() as u32,
118            5.. => size_of::<Os_2V5>() as u32,
119        };
120        if rec.length.get() < required_len {
121            return Err(TableError::InvalidLen);
122        }
123
124        Ok(Self { base, len: rec.length.get() })
125    }
126}
127
128#[derive(Copy)]
129#[derive_const(Clone)]
130pub struct Os_2<'a> {
131    base: &'a Os_2Base,
132    len: u32,
133}
134
135const impl<'a> std::ops::Deref for Os_2<'a> {
136    type Target = &'a Os_2Base;
137    fn deref(&self) -> &Self::Target {
138        &self.base
139    }
140}
141
142impl<'a> Os_2<'a> {
143    pub const fn v0(&self) -> Option<&'a Os_2V0> {
144        const V0_SIZE: u32 = size_of::<Os_2V0>() as u32;
145        if self.len >= V0_SIZE {
146            Some(unsafe { std::mem::transmute::<&Os_2Base, &Os_2V0>(self.base) })
147        } else {
148            None
149        }
150    }
151    pub const fn v1(&self) -> Option<&'a Os_2V1> {
152        if self.version.get() >= 1 {
153            Some(unsafe { std::mem::transmute::<&Os_2Base, &Os_2V1>(self.base) })
154        } else {
155            None
156        }
157    }
158    pub const fn v4(&self) -> Option<&'a Os_2V4> {
159        if self.version.get() >= 4 {
160            Some(unsafe { std::mem::transmute::<&Os_2Base, &Os_2V4>(self.base) })
161        } else {
162            None
163        }
164    }
165    pub const fn v5(&self) -> Option<&'a Os_2V5> {
166        if self.version.get() >= 5 {
167            Some(unsafe { std::mem::transmute::<&Os_2Base, &Os_2V5>(self.base) })
168        } else {
169            None
170        }
171    }
172
173    // version any:
174    pub const fn unicode_range(&self) -> UnicodeRanges {
175        UnicodeRanges::from_parts(
176            self.ul_unicode_range_1.get(),
177            self.ul_unicode_range_2.get(),
178            self.ul_unicode_range_3.get(),
179            self.ul_unicode_range_4.get(),
180        )
181    }
182
183    // version ≥ 0:
184    pub const fn s_typo_ascender(&self) -> Option<FWORD> {
185        Some(self.v0()?.s_typo_ascender)
186    }
187    pub const fn s_typo_descender(&self) -> Option<FWORD> {
188        Some(self.v0()?.s_typo_descender)
189    }
190    pub const fn s_typo_line_gap(&self) -> Option<FWORD> {
191        Some(self.v0()?.s_typo_line_gap)
192    }
193    pub const fn us_win_ascent(&self) -> Option<UFWORD> {
194        Some(self.v0()?.us_win_ascent)
195    }
196    pub const fn us_win_descent(&self) -> Option<UFWORD> {
197        Some(self.v0()?.us_win_descent)
198    }
199
200    // version ≥ 1:
201    pub const fn ul_code_page_range_1(&self) -> Option<uint32> {
202        Some(self.v1()?.ul_code_page_range_1)
203    }
204    pub const fn ul_code_page_range_2(&self) -> Option<uint32> {
205        Some(self.v1()?.ul_code_page_range_2)
206    }
207    pub const fn code_page_range(&self) -> Option<CodePages> {
208        let v1 = self.v1()?;
209        Some(CodePages::from_parts(v1.ul_code_page_range_1.get(), v1.ul_code_page_range_2.get()))
210    }
211
212    // version ≥ 4:
213    pub const fn sx_height(&self) -> Option<FWORD> {
214        Some(self.v4()?.sx_height)
215    }
216    pub const fn s_cap_height(&self) -> Option<FWORD> {
217        Some(self.v4()?.s_cap_height)
218    }
219    pub const fn us_default_char(&self) -> Option<uint16> {
220        Some(self.v4()?.us_default_char)
221    }
222    pub const fn us_break_char(&self) -> Option<uint16> {
223        Some(self.v4()?.us_break_char)
224    }
225    pub const fn us_max_content(&self) -> Option<uint16> {
226        Some(self.v4()?.us_max_content)
227    }
228
229    // version ≥ 5:
230    pub const fn us_lower_optical_point_size(&self) -> Option<uint16> {
231        Some(self.v5()?.us_lower_optical_point_size)
232    }
233    pub const fn us_upper_optical_point_size(&self) -> Option<uint16> {
234        Some(self.v5()?.us_upper_optical_point_size)
235    }
236}
237
238impl std::fmt::Debug for Os_2<'_> {
239    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
240        let mut f = f.debug_struct("OS/2");
241
242        f.field("version", &self.version.get())
243            .field("x_avg_char_width", &self.x_avg_char_width.get())
244            .field("us_weight_class", &self.us_weight_class.get())
245            .field("us_width_class", &self.us_width_class.get())
246            .field_with("fs_type", |f| write!(f, "{:#010b}", self.fs_type))
247            // TODO: combine into ScriptMetrics struct (and add getter)
248            .field("y_subscript_x_size", &self.y_subscript_x_size.get())
249            .field("y_subscript_y_size", &self.y_subscript_y_size.get())
250            .field("y_subscript_x_offset", &self.y_subscript_x_offset.get())
251            .field("y_subscript_y_offset", &self.y_subscript_y_offset.get())
252            .field("y_superscript_x_size", &self.y_superscript_x_size.get())
253            .field("y_superscript_y_size", &self.y_superscript_y_size.get())
254            .field("y_superscript_x_offset", &self.y_superscript_x_offset.get())
255            .field("y_superscript_y_offset", &self.y_superscript_y_offset.get())
256            // TODO: combine into StrikeoutMetrics (and add getter)
257            .field("y_strikeout_size", &self.y_strikeout_size.get())
258            .field("y_strikeout_position", &self.y_strikeout_position.get())
259            .field("s_family_class", &self.s_family_class.get())
260            .field("panose", &self.panose)
261            // TODO: impl Debug for UnicodeRanges
262            .field("ul_unicode_range_1", &self.ul_unicode_range_1.get())
263            .field("ul_unicode_range_2", &self.ul_unicode_range_2.get())
264            .field("ul_unicode_range_3", &self.ul_unicode_range_3.get())
265            .field("ul_unicode_range_4", &self.ul_unicode_range_4.get())
266            .field("ach_vend_id", &self.ach_vend_id)
267            .field("fs_selection", &self.fs_selection.get())
268            .field("us_first_char_index", &self.us_first_char_index.get())
269            .field("us_last_char_index", &self.us_last_char_index.get());
270
271        if let Some(v0) = self.v0() {
272            // TODO: combine into TypoMetrics (and add getter) (maybe?)
273            f.field("s_typo_ascender", &v0.s_typo_ascender.get())
274                .field("s_typo_descender", &v0.s_typo_descender.get())
275                .field("s_typo_line_gap", &v0.s_typo_line_gap.get())
276                .field("us_win_ascent", &v0.us_win_ascent.get())
277                .field("us_win_descent", &v0.us_win_descent.get());
278        }
279        if let Some(v1) = self.v1() {
280            // TODO: impl Debug for CodePages
281            f.field("ul_code_page_range_1", &v1.ul_code_page_range_1.get())
282                .field("ul_code_page_range_2", &v1.ul_code_page_range_2.get());
283        }
284        if let Some(v4) = self.v4() {
285            f.field("sx_height", &v4.sx_height.get())
286                .field("s_cap_height", &v4.s_cap_height.get())
287                // TODO: display something like 'X' (U+CODE) (and add getter () -> char)
288                .field("us_default_char", &v4.us_default_char.get())
289                .field("us_break_char", &v4.us_break_char.get())
290                .field("us_max_content", &v4.us_max_content.get());
291        }
292        if let Some(v5) = self.v5() {
293            f.field("us_lower_optical_point_size", &v5.us_lower_optical_point_size.get())
294                .field("us_upper_optical_point_size", &v5.us_upper_optical_point_size.get());
295        }
296
297        f.finish()
298    }
299}
300
301// Monomorphize all fmts to the above fmt for Os_2<'a>
302impl std::fmt::Debug for Os_2Base {
303    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
304        Os_2 { base: self, len: size_of_val(self) as u32 }.fmt(f)
305    }
306}
307impl std::fmt::Debug for Os_2V0 {
308    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
309        Os_2 { base: self, len: size_of_val(self) as u32 }.fmt(f)
310    }
311}
312impl std::fmt::Debug for Os_2V1 {
313    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
314        Os_2 { base: self, len: size_of_val(self) as u32 }.fmt(f)
315    }
316}
317impl std::fmt::Debug for Os_2V4 {
318    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
319        Os_2 { base: self, len: size_of_val(self) as u32 }.fmt(f)
320    }
321}
322impl std::fmt::Debug for Os_2V5 {
323    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
324        Os_2 { base: self, len: size_of_val(self) as u32 }.fmt(f)
325    }
326}