font_map_core/raw/
ttf.rs

1//! This module contains the TTF parser underlying the crate
2//!
3//! The parser is designed to be fast, and minimal. Supporting only a subset of the TTF spec
4//!
5use crate::error::ParseResult;
6use crate::reader::{BinaryReader, Parse};
7
8mod post;
9pub use post::PostTable;
10
11mod cmap;
12pub use cmap::*;
13
14mod glyf;
15pub use glyf::*;
16
17mod name;
18pub use name::NameKind;
19pub use name::NameTable;
20
21/// The raw data from a TrueType font  
22/// Contains only the subset of the table needed for mapping unicode:
23/// - Codepoints
24/// - Glyph indices
25/// - Glyph names
26/// - Glyph outlines
27#[derive(Debug)]
28pub struct TrueTypeFont {
29    /// The glyph outlines in the font, indexed by `glyph_id`
30    pub glyf_table: Vec<GlyfOutline>,
31
32    /// The CMAP table of the font
33    pub cmap_table: CmapTable,
34
35    /// The Post table of the font
36    pub post_table: PostTable,
37
38    /// The Name table of the font
39    pub name_table: NameTable,
40}
41
42impl TrueTypeFont {
43    /// Creates a new TrueType font from the given font data
44    ///
45    /// # Errors
46    /// Returns an error if the font data is invalid or cannot be parsed
47    pub fn new(font_data: &[u8]) -> ParseResult<Self> {
48        Self::from_data(font_data)
49    }
50}
51
52fn parse_table<T: Parse>(reader: &mut BinaryReader, offset: u32, len: u32) -> ParseResult<T> {
53    let table = reader.read_from(offset as usize, len as usize)?;
54    let mut table_reader = BinaryReader::new(table);
55    T::parse(&mut table_reader)
56}
57
58impl Parse for TrueTypeFont {
59    fn parse(reader: &mut BinaryReader) -> ParseResult<Self> {
60        let mut cmap = None;
61        let mut post = None;
62        let mut name = None;
63
64        //
65        // Offset Table
66        reader.skip_u32()?; // Scaler type
67        let num_tables = reader.read_u16()?;
68        reader.skip_u16()?; // Search range
69        reader.skip_u16()?; // Entry selector
70        reader.skip_u16()?; // Range shift
71
72        let mut loca_is_long = false;
73        let mut glyf_offsets = vec![];
74        let mut glyf_table: Vec<_> = vec![];
75
76        //
77        // Table directory
78        for _ in 0..num_tables {
79            let tag = reader.read_string(4)?;
80            reader.skip_u32()?; // checksum
81            let offset = reader.read_u32()?;
82            let length = reader.read_u32()?;
83
84            debug_msg!("Found the {tag} table at {offset} with length {length}");
85
86            match tag.as_str() {
87                "cmap" => {
88                    cmap = Some(parse_table(reader, offset, length)?);
89                }
90
91                "post" => {
92                    post = Some(parse_table(reader, offset, length)?);
93                }
94
95                "name" => {
96                    name = Some(parse_table(reader, offset, length)?);
97                }
98
99                "glyf" => {
100                    let table = reader.read_from(offset as usize, length as usize)?;
101                    glyf_table = table.to_vec();
102                }
103
104                "head" => {
105                    let table = reader.read_from(offset as usize, length as usize)?;
106                    let mut table_reader = BinaryReader::new(table);
107
108                    table_reader.skip_u32()?; // version
109                    table_reader.skip_u32()?; // font_revision
110                    table_reader.skip_u32()?; // checksum_adjustment
111                    table_reader.skip_u32()?; // magic_number
112                    table_reader.skip_u16()?; // flags
113                    table_reader.skip_u16()?; // units_per_em
114                    table_reader.skip_u64()?; // created
115                    table_reader.skip_u64()?; // modified
116                    table_reader.skip_u64()?; // x_min-ymax
117                    table_reader.skip_u16()?; // mac_style
118                    table_reader.skip_u16()?; // lowest_rec_ppem
119                    table_reader.skip_u16()?; // font_direction_hint
120
121                    loca_is_long = table_reader.read_u16()? != 0;
122                    debug_msg!("  loca is long: {loca_is_long}");
123                }
124
125                "loca" => {
126                    let table = reader.read_from(offset as usize, length as usize)?;
127                    let mut table_reader = BinaryReader::new(table);
128
129                    while !table_reader.is_eof() {
130                        let offset = if loca_is_long {
131                            table_reader.read_u32()?
132                        } else {
133                            u32::from(table_reader.read_u16()?) * 2
134                        };
135
136                        glyf_offsets.push(offset);
137                    }
138
139                    debug_msg!("  Found {} glyf offsets", glyf_offsets.len());
140                }
141
142                _ => {
143                    debug_msg!("  Ignoring table");
144                }
145            }
146        }
147
148        //
149        // Grab completed tables
150        let cmap = cmap.unwrap_or_default();
151        let post = post.unwrap_or_default();
152        let name = name.unwrap_or_default();
153
154        //
155        // Parse glyf table
156        let mut glyphs = vec![];
157        let mut glyf_offsets = glyf_offsets.into_iter().peekable();
158        while let Some(offset) = glyf_offsets.next() {
159            let Some(next_offset) = glyf_offsets.peek().copied().map(|o| o as usize) else {
160                break;
161            };
162
163            let length = next_offset - offset as usize;
164            let data = &glyf_table[offset as usize..next_offset];
165
166            if length > 0 {
167                let mut glyf_reader = BinaryReader::new(data);
168                let glyph = GlyfOutline::parse(&mut glyf_reader)?;
169                glyphs.push(glyph);
170            } else {
171                debug_msg!("No outline for glyph_id {}", glyphs.len());
172                let glyph = GlyfOutline::default();
173                glyphs.push(glyph);
174            }
175        }
176
177        Ok(Self {
178            cmap_table: cmap,
179            post_table: post,
180            glyf_table: glyphs,
181            name_table: name,
182        })
183    }
184}
185
186/// The platform types supported by some tables
187#[derive(Debug, Clone, Copy, Default)]
188#[repr(u16)]
189pub enum PlatformType {
190    /// Unicode platform
191    Unicode = 0,
192
193    /// Macintosh platform
194    Macintosh = 1,
195
196    /// ISO platform
197    Iso = 2,
198
199    /// Microsoft platform
200    Microsoft = 3,
201
202    /// Invalid platform
203    #[default]
204    Invalid = 0xFFFF,
205}
206impl From<u16> for PlatformType {
207    fn from(value: u16) -> Self {
208        match value {
209            0 => Self::Unicode,
210            1 => Self::Macintosh,
211            2 => Self::Iso,
212            3 => Self::Microsoft,
213            _ => Self::Invalid,
214        }
215    }
216}