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