1pub(crate) mod fallback;
3
4pub 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
34pub 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}