Skip to main content

read_fonts/model/font/
format.rs

1//! Font format detection.
2
3use crate::{FileRef, FontRead};
4
5/// Format for a blob of font data.
6#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
7pub enum FontFormat {
8    /// An [SFNT](https://en.wikipedia.org/wiki/SFNT)-based font, which includes
9    /// TrueType and OpenType fonts.
10    ///
11    /// This is the most common format for fonts.
12    ///
13    /// The field contains the number of available fonts. This is always 1 for
14    /// .ttf and .otf files and usually greater than 1 for .ttc and .otc files.
15    Sfnt(u32),
16    /// A Type1 font.
17    Type1,
18    /// A pure CFF font.
19    ///
20    /// The field contains the number of available fonts.
21    Cff(u32),
22}
23
24impl FontFormat {
25    /// Returns the format of the font data in the given buffer.
26    pub fn new(data: &[u8]) -> Option<Self> {
27        if let Ok(file) = FileRef::new(data) {
28            let format = match file {
29                FileRef::Collection(collection) => Self::Sfnt(collection.len()),
30                FileRef::Font(_) => Self::Sfnt(1),
31            };
32            Some(format)
33        } else if check_type1(data) {
34            Some(Self::Type1)
35        } else if let Ok(cff) = crate::ps::cff::v1::Cff::read(data.into()) {
36            Some(Self::Cff(cff.top_dicts().count() as u32))
37        } else {
38            None
39        }
40    }
41
42    /// Returns true if this is an SFNT-based font.
43    pub fn is_sfnt(&self) -> bool {
44        matches!(self, Self::Sfnt(_))
45    }
46
47    /// Returns the number of available fonts.
48    pub fn num_fonts(&self) -> u32 {
49        match self {
50            Self::Sfnt(n) | Self::Cff(n) => *n,
51            _ => 1,
52        }
53    }
54}
55
56fn check_type1(data: &[u8]) -> bool {
57    fn check(data: &[u8]) -> bool {
58        data.starts_with(b"%!PS-AdobeFont") || data.starts_with(b"%!FontType")
59    }
60    check(data) || data.get(6..).map(check).unwrap_or(false)
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66    use crate::{FontRef, TableProvider};
67
68    #[test]
69    fn check_formats() {
70        let pure_cff = FontRef::new(font_test_data::MATERIAL_ICONS_SUBSET)
71            .unwrap()
72            .cff()
73            .unwrap()
74            .offset_data()
75            .as_bytes();
76        use FontFormat::*;
77        #[rustfmt::skip]
78        let pairs = [
79            (font_test_data::CANTARELL_VF_TRIMMED, Sfnt(1)),
80            (font_test_data::TINOS_SUBSET, Sfnt(1)),
81            (pure_cff, Cff(1)),
82            (font_test_data::ttc::TTC, Sfnt(2)),
83            (font_test_data::type1::NOTO_SERIF_REGULAR_SUBSET_PFA, Type1),
84        ];
85        for (data, expected_format) in pairs {
86            assert_eq!(FontFormat::new(data).unwrap(), expected_format);
87        }
88        assert!(FontFormat::new(b"I'm not a font").is_none());
89    }
90}