1pub 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
36pub 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}