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
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

mod sources;
mod vmo_stream;

use {
    anyhow::{format_err, Error},
    byteorder::{BigEndian, ReadBytesExt},
    char_set::CharSet,
    freetype_ffi::{
        FT_Add_Default_Modules, FT_Done_Face, FT_Done_Library, FT_Err_Ok, FT_Face,
        FT_Get_First_Char, FT_Get_Next_Char, FT_Get_Postscript_Name, FT_Get_Sfnt_Name,
        FT_Get_Sfnt_Name_Count, FT_Library, FT_New_Library, FT_Open_Face, FT_SfntName, FT_MEMORY,
        TT_MS_ID_SYMBOL_CS, TT_MS_ID_UNICODE_CS, TT_MS_LANGID_ENGLISH_UNITED_STATES,
        TT_NAME_ID_FULL_NAME, TT_PLATFORM_MICROSOFT,
    },
    std::{ffi::CStr, io::Cursor, ops::Range, ptr},
};

pub use crate::sources::FTOpenArgs;
pub use crate::sources::FontAssetSource;

/// Contains information parsed from a font file.
#[derive(Default, Clone, Debug, Eq, PartialEq)]
pub struct FontInfo {
    pub char_set: CharSet,
    /// The "Postscript name" of the font face.
    pub postscript_name: Option<String>,
    /// The user-friendly "full name" of the font face.
    pub full_name: Option<String>,
}

/// An object that can load a `FontInfo` from a `FontAssetSource`.
///
/// Separate from the [implementation][`FontInfoLoaderImpl`] to allow easier mocking for tests.
pub trait FontInfoLoader {
    /// Load information about a font from the given file source, located at the given index within
    /// the file.
    ///
    /// `S` is any type for which a conversion is defined into `FT_Open_Args`. In practice,
    /// this must be either a something that contains an `FT_Stream`, or a file path.
    ///
    /// For simplicity, use [crates::sources::FontAssetSource].
    fn load_font_info<S, E>(&self, source: S, index: u32) -> Result<FontInfo, Error>
    where
        S: TryInto<FontAssetSource, Error = E>,
        E: Sync + Send + Into<Error>;
}

/// Reads information from font files using the FreeType library.
///
/// This class is tested in `../tests/tests.rs`.
#[derive(Debug)]
pub struct FontInfoLoaderImpl {
    library: FTLibrary,
}

impl FontInfoLoaderImpl {
    pub fn new() -> Result<FontInfoLoaderImpl, Error> {
        let library = FTLibrary::new()?;
        Ok(Self { library })
    }

    pub fn load_font_info<S, E>(&self, source: S, index: u32) -> Result<FontInfo, Error>
    where
        S: TryInto<FontAssetSource, Error = E>,
        E: Sync + Send + Into<Error>,
    {
        let source: FontAssetSource = source.try_into().map_err(|e| e.into())?;
        let open_args: FTOpenArgs = source.try_into()?;

        let face = self.library.open_face(open_args, index)?;
        let code_points = face.code_points().collect();
        let postscript_name = face.postscript_name().ok().filter(|x| !x.is_empty());
        let full_name = face.full_name()?.filter(|x| !x.is_empty());

        Ok(FontInfo { char_set: CharSet::new(code_points), postscript_name, full_name })
    }
}

impl FontInfoLoader for FontInfoLoaderImpl {
    fn load_font_info<S, E>(&self, source: S, index: u32) -> Result<FontInfo, Error>
    where
        S: TryInto<FontAssetSource, Error = E>,
        E: Sync + Send + Into<Error>,
    {
        FontInfoLoaderImpl::load_font_info(&self, source, index)
    }
}

#[derive(Debug)]
struct FTLibrary {
    ft_library: FT_Library,
}

/// Rusty wrapper around `FT_Library`.
impl FTLibrary {
    pub fn new() -> Result<FTLibrary, Error> {
        // Unsafe to call FreeType FFI. On success, stores the `FT_Library` pointer in `FTLibrary`,
        // which calls `FT_Done_Library` in `drop()`.
        unsafe {
            let mut ft_library = ptr::null_mut();
            if FT_New_Library(&FT_MEMORY, &mut ft_library) != FT_Err_Ok {
                return Err(format_err!("Failed to initialize FreeType library."));
            }
            FT_Add_Default_Modules(ft_library);
            Ok(FTLibrary { ft_library })
        }
    }

    /// Opens the font file with the given `open_args` and reads the face at the given index.
    pub fn open_face(&self, open_args: FTOpenArgs, index: u32) -> Result<FTFace, Error> {
        // Unsafe to call FreeType FFI. On success stores the `FT_Face` pointer in `FTFace`, which
        // calls `FT_Done_Face` in `drop()`.
        unsafe {
            let mut ft_face: FT_Face = ptr::null_mut();
            let err = FT_Open_Face(self.ft_library, open_args.as_ref(), index as i64, &mut ft_face);

            if err != FT_Err_Ok {
                return Err(format_err!(
                    "Failed to parse font {} from {:?}: FreeType error {}",
                    index,
                    open_args.source,
                    err
                ));
            }

            Ok(FTFace { ft_face, open_args })
        }
    }
}

impl std::ops::Drop for FTLibrary {
    fn drop(&mut self) {
        // Unsafe to call FreeType FFI.
        // This is required in order to clean up the FreeType library instance.
        unsafe {
            FT_Done_Library(self.ft_library);
        }
    }
}

/// Wrapper around the native `FT_Face`, providing a more Rusty API and retaining some structs that
/// need to stay alive in memory.
struct FTFace {
    ft_face: FT_Face,

    /// Retains the stream or file path that backs the `FT_Face`.
    #[allow(dead_code)]
    open_args: FTOpenArgs,
}

impl FTFace {
    /// Returns an iterator over the code points (as `u32`s) covered by this font face.
    pub fn code_points<'f>(&'f self) -> CodePointsIterator<'f> {
        CodePointsIterator { face: self, state: CodePointsIteratorState::Uninitialized }
    }

    /// Returns the face's Postscript name.
    pub fn postscript_name(&self) -> Result<String, Error> {
        // Unsafe to call FreeType FFI.
        let postscript_name = unsafe {
            // According to the FT docs, this is supposed to be an ASCII string, so it should
            // trivially convert to UTF-8.
            //
            // Note: Must use c_char, not i8 or u8, because c_char's definition depends on the
            // architecture.
            let postscript_name =
                FT_Get_Postscript_Name(self.ft_face) as *const std::os::raw::c_char;
            let postscript_name = CStr::from_ptr(postscript_name);
            postscript_name
                .to_str()
                .map_err(|e| format_err!("Failed to decode Postscript name. Error: {:?}", e))?
                .to_string()
        };
        Ok(postscript_name)
    }

    /// Returns the face's "full name", if present.
    pub fn full_name(&self) -> Result<Option<String>, Error> {
        self.get_name(TT_NAME_ID_FULL_NAME)
    }

    /// Finds and decodes the first supported name entry that has the given name ID.
    ///
    /// A "name ID" is a record identifier in the `name` TrueType table, which can also contain
    /// items like copyright info, URLs, etc. See
    /// https://docs.microsoft.com/en-us/typography/opentype/spec/name#name-ids.
    fn get_name(&self, name_id: u16) -> Result<Option<String>, Error> {
        for name_entry in self.sfnt_names() {
            let name_entry = name_entry?;
            if let Some(name) = FTFace::decode_name_if_matches(&name_entry, name_id)? {
                return Ok(Some(name));
            }
        }
        Ok(None)
    }

    fn sfnt_name_count(&self) -> u32 {
        unsafe { FT_Get_Sfnt_Name_Count(self.ft_face) }
    }

    fn sfnt_names<'a>(&'a self) -> SfntNamesIterator<'a> {
        SfntNamesIterator { face: self, iter: 0..self.sfnt_name_count() }
    }

    /// If the given `FT_SfntName` contains the requested `name_id` and the encoding is in a
    /// supported format, returns the decoded name string. Otherwise, returns `None`.
    ///
    /// Presently, this only looks at en-US name entries.
    fn decode_name_if_matches(
        name_entry: &FT_SfntName,
        name_id: u16,
    ) -> Result<Option<String>, Error> {
        if name_entry.name_id != name_id {
            return Ok(None);
        }
        match (name_entry.platform_id, name_entry.encoding_id, name_entry.language_id) {
            (TT_PLATFORM_MICROSOFT, TT_MS_ID_SYMBOL_CS, TT_MS_LANGID_ENGLISH_UNITED_STATES)
            | (TT_PLATFORM_MICROSOFT, TT_MS_ID_UNICODE_CS, TT_MS_LANGID_ENGLISH_UNITED_STATES) => {
                let name = FTFace::decode_name_utf16_be(name_entry)?;
                Ok(Some(name))
            }
            _ => Ok(None),
        }
    }

    /// Decodes the the given `FT_SfntName` as a UTF-16 Big Endian string.
    fn decode_name_utf16_be(name_entry: &FT_SfntName) -> Result<String, Error> {
        let as_u8 = unsafe {
            std::slice::from_raw_parts(name_entry.string, name_entry.string_len as usize)
        };
        // FT_SfntName.string_len is in bytes. One UTF-16 code unit is two bytes.
        let num_code_units = (name_entry.string_len / 2) as usize;
        let mut reader = Cursor::new(as_u8);
        let mut as_u16 = Vec::with_capacity(num_code_units);
        for _i in 0..num_code_units {
            let ch = reader.read_u16::<BigEndian>()?;
            as_u16.push(ch);
        }
        Ok(String::from_utf16(as_u16.as_slice())?)
    }
}

impl std::ops::Drop for FTFace {
    fn drop(&mut self) {
        // Unsafe to call FreeType FFI.
        // This is required in order to clean up the FreeType face instance.
        unsafe {
            FT_Done_Face(self.ft_face);
        }
    }
}

/// State of a [`CodePointsIterator`].
#[derive(Debug, Copy, Clone)]
enum CodePointsIteratorState {
    Uninitialized,
    /// Stores the previous code point returned.
    Active(u64),
    /// No more code points forthcoming.
    Fused,
}

/// Iterates over the code points (as `u32`s) in an `FTFace`.
pub struct CodePointsIterator<'f> {
    face: &'f FTFace,
    state: CodePointsIteratorState,
}

impl<'f> CodePointsIterator<'f> {
    fn next_internal(&mut self, prev_code_point: Option<u64>) -> Option<u32> {
        let mut glyph_index: u32 = 0;
        // Unsafe to call FreeType FFI. Enumerate character map with FT_Get_First_Char() and
        // FT_Get_Next_Char().
        let code_point = unsafe {
            match prev_code_point {
                Some(prev_code_point) => {
                    FT_Get_Next_Char(self.face.ft_face, prev_code_point, &mut glyph_index)
                }
                None => FT_Get_First_Char(self.face.ft_face, &mut glyph_index),
            }
        };
        // Per the FreeType docs, the glyph index will always be zero after the last code point in
        // the face is returned.
        if glyph_index > 0 {
            self.state = CodePointsIteratorState::Active(code_point);
            Some(code_point as u32)
        } else {
            self.state = CodePointsIteratorState::Fused;
            None
        }
    }
}

impl<'f> std::iter::Iterator for CodePointsIterator<'f> {
    type Item = u32;

    fn next(&mut self) -> Option<Self::Item> {
        match self.state.clone() {
            CodePointsIteratorState::Uninitialized => self.next_internal(None),
            CodePointsIteratorState::Active(prev_code_point) => {
                self.next_internal(Some(prev_code_point))
            }
            CodePointsIteratorState::Fused => None,
        }
    }
}

impl<'f> std::iter::FusedIterator for CodePointsIterator<'f> {}

/// Iterator over the `name` table in a font face.
pub struct SfntNamesIterator<'f> {
    face: &'f FTFace,
    iter: Range<u32>,
}

impl<'f> SfntNamesIterator<'f> {
    fn get(&self, idx: u32) -> Result<FT_SfntName, Error> {
        let mut entry = FT_SfntName::default();
        let err_code = unsafe { FT_Get_Sfnt_Name(self.face.ft_face, idx, &mut entry) };
        if err_code == FT_Err_Ok {
            Ok(entry)
        } else {
            Err(format_err!("FreeType error while getting name {}: {}", idx, err_code))
        }
    }
}

impl<'f> Iterator for SfntNamesIterator<'f> {
    type Item = Result<FT_SfntName, Error>;

    fn next(&mut self) -> Option<Self::Item> {
        self.iter.next().map(|idx| self.get(idx))
    }
}

impl<'f> ExactSizeIterator for SfntNamesIterator<'f> {
    fn len(&self) -> usize {
        self.iter.len()
    }
}