1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
//! `OS/2` — Windows-specific metrics + classification.
//!
//! Multiple versions exist (0..5). All start with a 78-byte common prefix
//! that contains everything round 1 needs except the typo metrics, which
//! land at offset 68/70/72 in version >= 0.
use crate::parser::{read_i16, read_u16};
use crate::Error;
#[derive(Debug, Clone, Copy)]
pub struct Os2Table {
pub us_weight_class: u16,
pub fs_selection: u16,
pub s_typo_ascender: Option<i16>,
pub s_typo_descender: Option<i16>,
pub s_typo_line_gap: Option<i16>,
pub sx_height: Option<i16>,
pub s_cap_height: Option<i16>,
}
impl Os2Table {
pub fn parse(bytes: &[u8]) -> Result<Self, Error> {
if bytes.len() < 4 {
return Err(Error::UnexpectedEof);
}
let version = read_u16(bytes, 0)?;
// Common-prefix offsets:
// 0 / 2 / version
// 2 / 2 / xAvgCharWidth
// 4 / 2 / usWeightClass
// 6 / 2 / usWidthClass
// 8 / 2 / fsType
// 10 / 10 * 2 / subscript/superscript/strikeout fields
// 30 / 2 / sFamilyClass
// 32 / 10 / panose
// 42 / 16 / ulUnicodeRange (4 * u32)
// 58 / 4 / achVendID
// 62 / 2 / fsSelection
// 64 / 2 / usFirstCharIndex
// 66 / 2 / usLastCharIndex
// 68 / 2 / sTypoAscender
// 70 / 2 / sTypoDescender
// 72 / 2 / sTypoLineGap
// 74 / 2 / usWinAscent
// 76 / 2 / usWinDescent
// Version 1 adds ulCodePageRange1/2 (8 bytes) starting at 78.
// Version 2..5 adds sxHeight, sCapHeight at 86, 88.
if bytes.len() < 78 {
return Err(Error::UnexpectedEof);
}
let us_weight_class = read_u16(bytes, 4)?;
let fs_selection = read_u16(bytes, 62)?;
let s_typo_ascender = Some(read_i16(bytes, 68)?);
let s_typo_descender = Some(read_i16(bytes, 70)?);
let s_typo_line_gap = Some(read_i16(bytes, 72)?);
let mut sx_height = None;
let mut s_cap_height = None;
if version >= 2 && bytes.len() >= 90 {
sx_height = Some(read_i16(bytes, 86)?);
s_cap_height = Some(read_i16(bytes, 88)?);
}
Ok(Self {
us_weight_class,
fs_selection,
s_typo_ascender,
s_typo_descender,
s_typo_line_gap,
sx_height,
s_cap_height,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_v0_minimum() {
let mut b = vec![0u8; 78];
b[0..2].copy_from_slice(&0u16.to_be_bytes()); // version
b[4..6].copy_from_slice(&500u16.to_be_bytes()); // weight
b[62..64].copy_from_slice(&64u16.to_be_bytes()); // fsSelection
b[68..70].copy_from_slice(&(1900i16).to_be_bytes());
b[70..72].copy_from_slice(&(-500i16).to_be_bytes());
b[72..74].copy_from_slice(&(0i16).to_be_bytes());
let t = Os2Table::parse(&b).unwrap();
assert_eq!(t.us_weight_class, 500);
assert_eq!(t.s_typo_ascender, Some(1900));
assert!(t.sx_height.is_none());
}
}