font_map_core/raw/
ttf.rs

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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
//! This module contains the TTF parser underlying the crate
//!
//! The parser is designed to be fast, and minimal. Supporting only a subset of the TTF spec
//!
use crate::error::ParseResult;
use crate::reader::{BinaryReader, Parse};

mod post;
pub use post::PostTable;

mod cmap;
pub use cmap::CmapTable;

mod glyf;
pub use glyf::*;

mod name;
pub use name::NameKind;
pub use name::NameTable;

/// The raw data from a TrueType font  
/// Contains only the subset of the table needed for mapping unicode:
/// - Codepoints
/// - Glyph indices
/// - Glyph names
/// - Glyph outlines
#[derive(Debug)]
pub struct TrueTypeFont {
    /// The glyph outlines in the font, indexed by `glyph_id`
    pub glyf_table: Vec<GlyfOutline>,

    /// The CMAP table of the font
    pub cmap_table: CmapTable,

    /// The Post table of the font
    pub post_table: PostTable,

    /// The Name table of the font
    pub name_table: NameTable,
}

impl TrueTypeFont {
    /// Creates a new TrueType font from the given font data
    ///
    /// # Errors
    /// Returns an error if the font data is invalid or cannot be parsed
    pub fn new(font_data: &[u8]) -> ParseResult<Self> {
        Self::from_data(font_data)
    }
}

fn parse_table<T: Parse>(reader: &mut BinaryReader, offset: u32, len: u32) -> ParseResult<T> {
    let table = reader.read_from(offset as usize, len as usize)?;
    let mut table_reader = BinaryReader::new(table);
    T::parse(&mut table_reader)
}

impl Parse for TrueTypeFont {
    fn parse(reader: &mut BinaryReader) -> ParseResult<Self> {
        let mut cmap = None;
        let mut post = None;
        let mut name = None;

        //
        // Offset Table
        reader.skip_u32()?; // Scaler type
        let num_tables = reader.read_u16()?;
        reader.skip_u16()?; // Search range
        reader.skip_u16()?; // Entry selector
        reader.skip_u16()?; // Range shift

        let mut loca_is_long = false;
        let mut glyf_offsets = vec![];
        let mut glyf_table: Vec<_> = vec![];

        //
        // Table directory
        for _ in 0..num_tables {
            let tag = reader.read_string(4)?;
            reader.skip_u32()?; // checksum
            let offset = reader.read_u32()?;
            let length = reader.read_u32()?;

            debug_msg!("Found the {tag} table at {offset} with length {length}");

            match tag.as_str() {
                "cmap" => {
                    cmap = Some(parse_table(reader, offset, length)?);
                }

                "post" => {
                    post = Some(parse_table(reader, offset, length)?);
                }

                "name" => {
                    name = Some(parse_table(reader, offset, length)?);
                }

                "glyf" => {
                    let table = reader.read_from(offset as usize, length as usize)?;
                    glyf_table = table.to_vec();
                }

                "head" => {
                    let table = reader.read_from(offset as usize, length as usize)?;
                    let mut table_reader = BinaryReader::new(table);

                    table_reader.skip_u32()?; // version
                    table_reader.skip_u32()?; // font_revision
                    table_reader.skip_u32()?; // checksum_adjustment
                    table_reader.skip_u32()?; // magic_number
                    table_reader.skip_u16()?; // flags
                    table_reader.skip_u16()?; // units_per_em
                    table_reader.skip_u64()?; // created
                    table_reader.skip_u64()?; // modified
                    table_reader.skip_u64()?; // x_min-ymax
                    table_reader.skip_u16()?; // mac_style
                    table_reader.skip_u16()?; // lowest_rec_ppem
                    table_reader.skip_u16()?; // font_direction_hint

                    loca_is_long = table_reader.read_u16()? != 0;
                    debug_msg!("  loca is long: {loca_is_long}");
                }

                "loca" => {
                    let table = reader.read_from(offset as usize, length as usize)?;
                    let mut table_reader = BinaryReader::new(table);

                    while !table_reader.is_eof() {
                        let offset = if loca_is_long {
                            table_reader.read_u32()?
                        } else {
                            u32::from(table_reader.read_u16()?) * 2
                        };

                        glyf_offsets.push(offset);
                    }

                    debug_msg!("  Found {} glyf offsets", glyf_offsets.len());
                }

                _ => {
                    debug_msg!("  Ignoring table");
                }
            }
        }

        //
        // Grab completed tables
        let cmap = cmap.unwrap_or_default();
        let post = post.unwrap_or_default();
        let name = name.unwrap_or_default();

        //
        // Parse glyf table
        let mut glyphs = vec![];
        let mut glyf_offsets = glyf_offsets.into_iter().peekable();
        while let Some(offset) = glyf_offsets.next() {
            let Some(next_offset) = glyf_offsets.peek().copied().map(|o| o as usize) else {
                break;
            };

            let length = next_offset - offset as usize;
            let data = &glyf_table[offset as usize..next_offset];

            if length > 0 {
                let mut glyf_reader = BinaryReader::new(data);
                let glyph = GlyfOutline::parse(&mut glyf_reader)?;
                glyphs.push(glyph);
            } else {
                debug_msg!("No outline for glyph_id {}", glyphs.len());
                let glyph = GlyfOutline::default();
                glyphs.push(glyph);
            }
        }

        Ok(Self {
            cmap_table: cmap,
            post_table: post,
            glyf_table: glyphs,
            name_table: name,
        })
    }
}

/// The platform types supported by some tables
#[derive(Debug, Clone, Copy, Default)]
#[repr(u16)]
pub enum PlatformType {
    /// Unicode platform
    Unicode = 0,

    /// Macintosh platform
    Macintosh = 1,

    /// ISO platform
    Iso = 2,

    /// Microsoft platform
    Microsoft = 3,

    /// Invalid platform
    #[default]
    Invalid = 0xFFFF,
}
impl From<u16> for PlatformType {
    fn from(value: u16) -> Self {
        match value {
            0 => Self::Unicode,
            1 => Self::Macintosh,
            2 => Self::Iso,
            3 => Self::Microsoft,
            _ => Self::Invalid,
        }
    }
}