cosmic_text/font/
mod.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2pub(crate) mod fallback;
3
4// re-export ttf_parser
5pub use ttf_parser;
6
7use core::fmt;
8
9use alloc::sync::Arc;
10#[cfg(not(feature = "std"))]
11use alloc::vec::Vec;
12
13use rustybuzz::Face as RustybuzzFace;
14use self_cell::self_cell;
15
16pub use self::system::*;
17mod system;
18
19self_cell!(
20    struct OwnedFace {
21        owner: Arc<dyn AsRef<[u8]> + Send + Sync>,
22
23        #[covariant]
24        dependent: RustybuzzFace,
25    }
26);
27
28struct FontMonospaceFallback {
29    monospace_em_width: Option<f32>,
30    scripts: Vec<[u8; 4]>,
31    unicode_codepoints: Vec<u32>,
32}
33
34/// A font
35pub struct Font {
36    #[cfg(feature = "swash")]
37    swash: (u32, swash::CacheKey),
38    rustybuzz: OwnedFace,
39    data: Arc<dyn AsRef<[u8]> + Send + Sync>,
40    id: fontdb::ID,
41    monospace_fallback: Option<FontMonospaceFallback>,
42}
43
44impl fmt::Debug for Font {
45    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46        f.debug_struct("Font")
47            .field("id", &self.id)
48            .finish_non_exhaustive()
49    }
50}
51
52impl Font {
53    pub fn id(&self) -> fontdb::ID {
54        self.id
55    }
56
57    pub fn monospace_em_width(&self) -> Option<f32> {
58        self.monospace_fallback
59            .as_ref()
60            .and_then(|x| x.monospace_em_width)
61    }
62
63    pub fn scripts(&self) -> &[[u8; 4]] {
64        self.monospace_fallback.as_ref().map_or(&[], |x| &x.scripts)
65    }
66
67    pub fn unicode_codepoints(&self) -> &[u32] {
68        self.monospace_fallback
69            .as_ref()
70            .map_or(&[], |x| &x.unicode_codepoints)
71    }
72
73    pub fn data(&self) -> &[u8] {
74        (*self.data).as_ref()
75    }
76
77    pub fn rustybuzz(&self) -> &RustybuzzFace<'_> {
78        self.rustybuzz.borrow_dependent()
79    }
80
81    #[cfg(feature = "swash")]
82    pub fn as_swash(&self) -> swash::FontRef<'_> {
83        let swash = &self.swash;
84        swash::FontRef {
85            data: self.data(),
86            offset: swash.0,
87            key: swash.1,
88        }
89    }
90}
91
92impl Font {
93    pub fn new(db: &fontdb::Database, id: fontdb::ID) -> Option<Self> {
94        let info = db.face(id)?;
95
96        let monospace_fallback = if cfg!(feature = "monospace_fallback") {
97            db.with_face_data(id, |font_data, face_index| {
98                let face = ttf_parser::Face::parse(font_data, face_index).ok()?;
99                let monospace_em_width = info
100                    .monospaced
101                    .then(|| {
102                        let hor_advance = face.glyph_hor_advance(face.glyph_index(' ')?)? as f32;
103                        let upem = face.units_per_em() as f32;
104                        Some(hor_advance / upem)
105                    })
106                    .flatten();
107
108                if info.monospaced && monospace_em_width.is_none() {
109                    None?;
110                }
111
112                let scripts = face
113                    .tables()
114                    .gpos
115                    .into_iter()
116                    .chain(face.tables().gsub)
117                    .flat_map(|table| table.scripts)
118                    .map(|script| script.tag.to_bytes())
119                    .collect();
120
121                let mut unicode_codepoints = Vec::new();
122
123                face.tables()
124                    .cmap?
125                    .subtables
126                    .into_iter()
127                    .filter(|subtable| subtable.is_unicode())
128                    .for_each(|subtable| {
129                        unicode_codepoints.reserve(1024);
130                        subtable.codepoints(|code_point| {
131                            if subtable.glyph_index(code_point).is_some() {
132                                unicode_codepoints.push(code_point);
133                            }
134                        });
135                    });
136
137                unicode_codepoints.shrink_to_fit();
138
139                Some(FontMonospaceFallback {
140                    monospace_em_width,
141                    scripts,
142                    unicode_codepoints,
143                })
144            })?
145        } else {
146            None
147        };
148
149        let data = match &info.source {
150            fontdb::Source::Binary(data) => Arc::clone(data),
151            #[cfg(feature = "std")]
152            fontdb::Source::File(path) => {
153                log::warn!("Unsupported fontdb Source::File('{}')", path.display());
154                return None;
155            }
156            #[cfg(feature = "std")]
157            fontdb::Source::SharedFile(_path, data) => Arc::clone(data),
158        };
159
160        Some(Self {
161            id: info.id,
162            monospace_fallback,
163            #[cfg(feature = "swash")]
164            swash: {
165                let swash = swash::FontRef::from_index((*data).as_ref(), info.index as usize)?;
166                (swash.offset, swash.key)
167            },
168            rustybuzz: OwnedFace::try_new(Arc::clone(&data), |data| {
169                RustybuzzFace::from_slice((**data).as_ref(), info.index).ok_or(())
170            })
171            .ok()?,
172            data,
173        })
174    }
175}
176
177#[cfg(test)]
178mod test {
179    #[test]
180    fn test_fonts_load_time() {
181        use crate::FontSystem;
182        use sys_locale::get_locale;
183
184        #[cfg(not(target_arch = "wasm32"))]
185        let now = std::time::Instant::now();
186
187        let mut db = fontdb::Database::new();
188        let locale = get_locale().expect("Local available");
189        db.load_system_fonts();
190        FontSystem::new_with_locale_and_db(locale, db);
191
192        #[cfg(not(target_arch = "wasm32"))]
193        println!("Fonts load time {}ms.", now.elapsed().as_millis());
194    }
195}