font_info/
lib.rs

1// Copyright 2019 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5mod sources;
6mod vmo_stream;
7
8use anyhow::{format_err, Error};
9use byteorder::{BigEndian, ReadBytesExt};
10use char_set::CharSet;
11use freetype_ffi::{
12    FT_Add_Default_Modules, FT_Done_Face, FT_Done_Library, FT_Err_Ok, FT_Face, FT_Get_First_Char,
13    FT_Get_Next_Char, FT_Get_Postscript_Name, FT_Get_Sfnt_Name, FT_Get_Sfnt_Name_Count, FT_Library,
14    FT_New_Library, FT_Open_Face, FT_SfntName, FT_MEMORY, TT_MS_ID_SYMBOL_CS, TT_MS_ID_UNICODE_CS,
15    TT_MS_LANGID_ENGLISH_UNITED_STATES, TT_NAME_ID_FULL_NAME, TT_PLATFORM_MICROSOFT,
16};
17use std::ffi::CStr;
18use std::io::Cursor;
19use std::ops::Range;
20use std::ptr;
21
22pub use crate::sources::{FTOpenArgs, FontAssetSource};
23
24/// Contains information parsed from a font file.
25#[derive(Default, Clone, Debug, Eq, PartialEq)]
26pub struct FontInfo {
27    pub char_set: CharSet,
28    /// The "Postscript name" of the font face.
29    pub postscript_name: Option<String>,
30    /// The user-friendly "full name" of the font face.
31    pub full_name: Option<String>,
32}
33
34/// An object that can load a `FontInfo` from a `FontAssetSource`.
35///
36/// Separate from the [implementation][`FontInfoLoaderImpl`] to allow easier mocking for tests.
37pub trait FontInfoLoader {
38    /// Load information about a font from the given file source, located at the given index within
39    /// the file.
40    ///
41    /// `S` is any type for which a conversion is defined into `FT_Open_Args`. In practice,
42    /// this must be either a something that contains an `FT_Stream`, or a file path.
43    ///
44    /// For simplicity, use [crates::sources::FontAssetSource].
45    fn load_font_info<S, E>(&self, source: S, index: u32) -> Result<FontInfo, Error>
46    where
47        S: TryInto<FontAssetSource, Error = E>,
48        E: Sync + Send + Into<Error>;
49}
50
51/// Reads information from font files using the FreeType library.
52///
53/// This class is tested in `../tests/tests.rs`.
54#[derive(Debug)]
55pub struct FontInfoLoaderImpl {
56    library: FTLibrary,
57}
58
59impl FontInfoLoaderImpl {
60    pub fn new() -> Result<FontInfoLoaderImpl, Error> {
61        let library = FTLibrary::new()?;
62        Ok(Self { library })
63    }
64
65    pub fn load_font_info<S, E>(&self, source: S, index: u32) -> Result<FontInfo, Error>
66    where
67        S: TryInto<FontAssetSource, Error = E>,
68        E: Sync + Send + Into<Error>,
69    {
70        let source: FontAssetSource = source.try_into().map_err(|e| e.into())?;
71        let open_args: FTOpenArgs = source.try_into()?;
72
73        let face = self.library.open_face(open_args, index)?;
74        let code_points = face.code_points().collect();
75        let postscript_name = face.postscript_name().ok().filter(|x| !x.is_empty());
76        let full_name = face.full_name()?.filter(|x| !x.is_empty());
77
78        Ok(FontInfo { char_set: CharSet::new(code_points), postscript_name, full_name })
79    }
80}
81
82impl FontInfoLoader for FontInfoLoaderImpl {
83    fn load_font_info<S, E>(&self, source: S, index: u32) -> Result<FontInfo, Error>
84    where
85        S: TryInto<FontAssetSource, Error = E>,
86        E: Sync + Send + Into<Error>,
87    {
88        FontInfoLoaderImpl::load_font_info(&self, source, index)
89    }
90}
91
92#[derive(Debug)]
93struct FTLibrary {
94    ft_library: FT_Library,
95}
96
97/// Rusty wrapper around `FT_Library`.
98impl FTLibrary {
99    pub fn new() -> Result<FTLibrary, Error> {
100        // Unsafe to call FreeType FFI. On success, stores the `FT_Library` pointer in `FTLibrary`,
101        // which calls `FT_Done_Library` in `drop()`.
102        unsafe {
103            let mut ft_library = ptr::null_mut();
104            if FT_New_Library(&FT_MEMORY, &mut ft_library) != FT_Err_Ok {
105                return Err(format_err!("Failed to initialize FreeType library."));
106            }
107            FT_Add_Default_Modules(ft_library);
108            Ok(FTLibrary { ft_library })
109        }
110    }
111
112    /// Opens the font file with the given `open_args` and reads the face at the given index.
113    pub fn open_face(&self, open_args: FTOpenArgs, index: u32) -> Result<FTFace, Error> {
114        // Unsafe to call FreeType FFI. On success stores the `FT_Face` pointer in `FTFace`, which
115        // calls `FT_Done_Face` in `drop()`.
116        unsafe {
117            let mut ft_face: FT_Face = ptr::null_mut();
118            let err = FT_Open_Face(self.ft_library, open_args.as_ref(), index as i64, &mut ft_face);
119
120            if err != FT_Err_Ok {
121                return Err(format_err!(
122                    "Failed to parse font {} from {:?}: FreeType error {}",
123                    index,
124                    open_args.source,
125                    err
126                ));
127            }
128
129            Ok(FTFace { ft_face, open_args })
130        }
131    }
132}
133
134impl std::ops::Drop for FTLibrary {
135    fn drop(&mut self) {
136        // Unsafe to call FreeType FFI.
137        // This is required in order to clean up the FreeType library instance.
138        unsafe {
139            FT_Done_Library(self.ft_library);
140        }
141    }
142}
143
144/// Wrapper around the native `FT_Face`, providing a more Rusty API and retaining some structs that
145/// need to stay alive in memory.
146struct FTFace {
147    ft_face: FT_Face,
148
149    /// Retains the stream or file path that backs the `FT_Face`.
150    #[allow(dead_code)]
151    open_args: FTOpenArgs,
152}
153
154impl FTFace {
155    /// Returns an iterator over the code points (as `u32`s) covered by this font face.
156    pub fn code_points<'f>(&'f self) -> CodePointsIterator<'f> {
157        CodePointsIterator { face: self, state: CodePointsIteratorState::Uninitialized }
158    }
159
160    /// Returns the face's Postscript name.
161    pub fn postscript_name(&self) -> Result<String, Error> {
162        // Unsafe to call FreeType FFI.
163        let postscript_name = unsafe {
164            // According to the FT docs, this is supposed to be an ASCII string, so it should
165            // trivially convert to UTF-8.
166            //
167            // Note: Must use c_char, not i8 or u8, because c_char's definition depends on the
168            // architecture.
169            let postscript_name =
170                FT_Get_Postscript_Name(self.ft_face) as *const std::os::raw::c_char;
171            let postscript_name = CStr::from_ptr(postscript_name);
172            postscript_name
173                .to_str()
174                .map_err(|e| format_err!("Failed to decode Postscript name. Error: {:?}", e))?
175                .to_string()
176        };
177        Ok(postscript_name)
178    }
179
180    /// Returns the face's "full name", if present.
181    pub fn full_name(&self) -> Result<Option<String>, Error> {
182        self.get_name(TT_NAME_ID_FULL_NAME)
183    }
184
185    /// Finds and decodes the first supported name entry that has the given name ID.
186    ///
187    /// A "name ID" is a record identifier in the `name` TrueType table, which can also contain
188    /// items like copyright info, URLs, etc. See
189    /// https://docs.microsoft.com/en-us/typography/opentype/spec/name#name-ids.
190    fn get_name(&self, name_id: u16) -> Result<Option<String>, Error> {
191        for name_entry in self.sfnt_names() {
192            let name_entry = name_entry?;
193            if let Some(name) = FTFace::decode_name_if_matches(&name_entry, name_id)? {
194                return Ok(Some(name));
195            }
196        }
197        Ok(None)
198    }
199
200    fn sfnt_name_count(&self) -> u32 {
201        unsafe { FT_Get_Sfnt_Name_Count(self.ft_face) }
202    }
203
204    fn sfnt_names<'a>(&'a self) -> SfntNamesIterator<'a> {
205        SfntNamesIterator { face: self, iter: 0..self.sfnt_name_count() }
206    }
207
208    /// If the given `FT_SfntName` contains the requested `name_id` and the encoding is in a
209    /// supported format, returns the decoded name string. Otherwise, returns `None`.
210    ///
211    /// Presently, this only looks at en-US name entries.
212    fn decode_name_if_matches(
213        name_entry: &FT_SfntName,
214        name_id: u16,
215    ) -> Result<Option<String>, Error> {
216        if name_entry.name_id != name_id {
217            return Ok(None);
218        }
219        match (name_entry.platform_id, name_entry.encoding_id, name_entry.language_id) {
220            (TT_PLATFORM_MICROSOFT, TT_MS_ID_SYMBOL_CS, TT_MS_LANGID_ENGLISH_UNITED_STATES)
221            | (TT_PLATFORM_MICROSOFT, TT_MS_ID_UNICODE_CS, TT_MS_LANGID_ENGLISH_UNITED_STATES) => {
222                let name = FTFace::decode_name_utf16_be(name_entry)?;
223                Ok(Some(name))
224            }
225            _ => Ok(None),
226        }
227    }
228
229    /// Decodes the the given `FT_SfntName` as a UTF-16 Big Endian string.
230    fn decode_name_utf16_be(name_entry: &FT_SfntName) -> Result<String, Error> {
231        let as_u8 = unsafe {
232            std::slice::from_raw_parts(name_entry.string, name_entry.string_len as usize)
233        };
234        // FT_SfntName.string_len is in bytes. One UTF-16 code unit is two bytes.
235        let num_code_units = (name_entry.string_len / 2) as usize;
236        let mut reader = Cursor::new(as_u8);
237        let mut as_u16 = Vec::with_capacity(num_code_units);
238        for _i in 0..num_code_units {
239            let ch = reader.read_u16::<BigEndian>()?;
240            as_u16.push(ch);
241        }
242        Ok(String::from_utf16(as_u16.as_slice())?)
243    }
244}
245
246impl std::ops::Drop for FTFace {
247    fn drop(&mut self) {
248        // Unsafe to call FreeType FFI.
249        // This is required in order to clean up the FreeType face instance.
250        unsafe {
251            FT_Done_Face(self.ft_face);
252        }
253    }
254}
255
256/// State of a [`CodePointsIterator`].
257#[derive(Debug, Copy, Clone)]
258enum CodePointsIteratorState {
259    Uninitialized,
260    /// Stores the previous code point returned.
261    Active(u64),
262    /// No more code points forthcoming.
263    Fused,
264}
265
266/// Iterates over the code points (as `u32`s) in an `FTFace`.
267pub struct CodePointsIterator<'f> {
268    face: &'f FTFace,
269    state: CodePointsIteratorState,
270}
271
272impl<'f> CodePointsIterator<'f> {
273    fn next_internal(&mut self, prev_code_point: Option<u64>) -> Option<u32> {
274        let mut glyph_index: u32 = 0;
275        // Unsafe to call FreeType FFI. Enumerate character map with FT_Get_First_Char() and
276        // FT_Get_Next_Char().
277        let code_point = unsafe {
278            match prev_code_point {
279                Some(prev_code_point) => {
280                    FT_Get_Next_Char(self.face.ft_face, prev_code_point, &mut glyph_index)
281                }
282                None => FT_Get_First_Char(self.face.ft_face, &mut glyph_index),
283            }
284        };
285        // Per the FreeType docs, the glyph index will always be zero after the last code point in
286        // the face is returned.
287        if glyph_index > 0 {
288            self.state = CodePointsIteratorState::Active(code_point);
289            Some(code_point as u32)
290        } else {
291            self.state = CodePointsIteratorState::Fused;
292            None
293        }
294    }
295}
296
297impl<'f> std::iter::Iterator for CodePointsIterator<'f> {
298    type Item = u32;
299
300    fn next(&mut self) -> Option<Self::Item> {
301        match self.state.clone() {
302            CodePointsIteratorState::Uninitialized => self.next_internal(None),
303            CodePointsIteratorState::Active(prev_code_point) => {
304                self.next_internal(Some(prev_code_point))
305            }
306            CodePointsIteratorState::Fused => None,
307        }
308    }
309}
310
311impl<'f> std::iter::FusedIterator for CodePointsIterator<'f> {}
312
313/// Iterator over the `name` table in a font face.
314pub struct SfntNamesIterator<'f> {
315    face: &'f FTFace,
316    iter: Range<u32>,
317}
318
319impl<'f> SfntNamesIterator<'f> {
320    fn get(&self, idx: u32) -> Result<FT_SfntName, Error> {
321        let mut entry = FT_SfntName::default();
322        let err_code = unsafe { FT_Get_Sfnt_Name(self.face.ft_face, idx, &mut entry) };
323        if err_code == FT_Err_Ok {
324            Ok(entry)
325        } else {
326            Err(format_err!("FreeType error while getting name {}: {}", idx, err_code))
327        }
328    }
329}
330
331impl<'f> Iterator for SfntNamesIterator<'f> {
332    type Item = Result<FT_SfntName, Error>;
333
334    fn next(&mut self) -> Option<Self::Item> {
335        self.iter.next().map(|idx| self.get(idx))
336    }
337}
338
339impl<'f> ExactSizeIterator for SfntNamesIterator<'f> {
340    fn len(&self) -> usize {
341        self.iter.len()
342    }
343}