cosmic_text/font/
mod.rs

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