1use harfrust::Shaper;
4use linebender_resource_handle::{Blob, FontData};
5use skrifa::raw::{ReadError, TableProvider as _};
6use skrifa::{metrics::Metrics, prelude::*};
7pub use skrifa;
9#[cfg(feature = "peniko")]
11pub use linebender_resource_handle::FontData as PenikoFont;
12
13use core::fmt;
14
15use alloc::sync::Arc;
16#[cfg(not(feature = "std"))]
17use alloc::vec::Vec;
18
19use self_cell::self_cell;
20
21pub mod fallback;
22pub use fallback::{Fallback, PlatformFallback};
23
24pub use self::system::*;
25mod system;
26
27struct OwnedFaceData {
28 data: Arc<dyn AsRef<[u8]> + Send + Sync>,
29 shaper_data: harfrust::ShaperData,
30 shaper_instance: harfrust::ShaperInstance,
31 metrics: Metrics,
32}
33
34self_cell!(
35 struct OwnedFace {
36 owner: OwnedFaceData,
37
38 #[covariant]
39 dependent: Shaper,
40 }
41);
42
43struct FontMonospaceFallback {
44 monospace_em_width: Option<f32>,
45 scripts: Vec<[u8; 4]>,
46 unicode_codepoints: Vec<u32>,
47}
48
49pub struct Font {
51 #[cfg(feature = "swash")]
52 swash: (u32, swash::CacheKey),
53 harfrust: OwnedFace,
54 data: FontData,
55 id: fontdb::ID,
56 monospace_fallback: Option<FontMonospaceFallback>,
57}
58
59impl fmt::Debug for Font {
60 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61 f.debug_struct("Font")
62 .field("id", &self.id)
63 .finish_non_exhaustive()
64 }
65}
66
67impl Font {
68 pub const fn id(&self) -> fontdb::ID {
69 self.id
70 }
71
72 pub fn monospace_em_width(&self) -> Option<f32> {
73 self.monospace_fallback
74 .as_ref()
75 .and_then(|x| x.monospace_em_width)
76 }
77
78 pub fn scripts(&self) -> &[[u8; 4]] {
79 self.monospace_fallback.as_ref().map_or(&[], |x| &x.scripts)
80 }
81
82 pub fn unicode_codepoints(&self) -> &[u32] {
83 self.monospace_fallback
84 .as_ref()
85 .map_or(&[], |x| &x.unicode_codepoints)
86 }
87
88 pub fn data(&self) -> &[u8] {
89 self.data.data.data()
90 }
91
92 pub fn shaper(&self) -> &harfrust::Shaper<'_> {
93 self.harfrust.borrow_dependent()
94 }
95
96 pub(crate) fn shaper_instance(&self) -> &harfrust::ShaperInstance {
97 &self.harfrust.borrow_owner().shaper_instance
98 }
99
100 pub fn metrics(&self) -> &Metrics {
101 &self.harfrust.borrow_owner().metrics
102 }
103
104 #[cfg(feature = "peniko")]
105 pub fn as_peniko(&self) -> PenikoFont {
106 self.data.clone()
107 }
108
109 #[cfg(feature = "swash")]
110 pub fn as_swash(&self) -> swash::FontRef<'_> {
111 let swash = &self.swash;
112 swash::FontRef {
113 data: self.data(),
114 offset: swash.0,
115 key: swash.1,
116 }
117 }
118}
119
120impl Font {
121 pub fn new(db: &fontdb::Database, id: fontdb::ID, weight: fontdb::Weight) -> Option<Self> {
122 let info = db.face(id)?;
123
124 let data = match &info.source {
125 fontdb::Source::Binary(data) => Arc::clone(data),
126 #[cfg(feature = "std")]
127 fontdb::Source::File(path) => {
128 log::warn!("Unsupported fontdb Source::File('{}')", path.display());
129 return None;
130 }
131 #[cfg(feature = "std")]
132 fontdb::Source::SharedFile(_path, data) => Arc::clone(data),
133 };
134
135 let font_ref = FontRef::from_index((*data).as_ref(), info.index).ok()?;
140 let location = font_ref
141 .axes()
142 .location([(Tag::new(b"wght"), weight.0 as f32)]);
143 let metrics = font_ref.metrics(Size::unscaled(), &location);
144
145 let monospace_fallback = if cfg!(feature = "monospace_fallback") {
146 (|| {
147 let glyph_metrics = font_ref.glyph_metrics(Size::unscaled(), &location);
148 let charmap = font_ref.charmap();
149 let monospace_em_width = info
150 .monospaced
151 .then(|| {
152 let hor_advance = glyph_metrics.advance_width(charmap.map(' ')?)?;
153 let upem = metrics.units_per_em;
154 Some(hor_advance / f32::from(upem))
155 })
156 .flatten();
157
158 if info.monospaced && monospace_em_width.is_none() {
159 None?;
160 }
161
162 let scripts = font_ref
163 .gpos()
164 .ok()?
165 .script_list()
166 .ok()?
167 .script_records()
168 .iter()
169 .chain(
170 font_ref
171 .gsub()
172 .ok()?
173 .script_list()
174 .ok()?
175 .script_records()
176 .iter(),
177 )
178 .map(|script| script.script_tag().into_bytes())
179 .collect();
180
181 let mut unicode_codepoints = Vec::new();
182
183 for (code_point, _) in charmap.mappings() {
184 unicode_codepoints.push(code_point);
185 }
186
187 unicode_codepoints.shrink_to_fit();
188
189 Some(FontMonospaceFallback {
190 monospace_em_width,
191 scripts,
192 unicode_codepoints,
193 })
194 })()
195 } else {
196 None
197 };
198
199 let (shaper_instance, shaper_data) = {
200 (
201 harfrust::ShaperInstance::from_coords(&font_ref, location.coords().iter().copied()),
202 harfrust::ShaperData::new(&font_ref),
203 )
204 };
205
206 Some(Self {
207 id: info.id,
208 monospace_fallback,
209 #[cfg(feature = "swash")]
210 swash: {
211 let swash = swash::FontRef::from_index((*data).as_ref(), info.index as usize)?;
212 (swash.offset, swash.key)
213 },
214 harfrust: OwnedFace::try_new(
215 OwnedFaceData {
216 data: Arc::clone(&data),
217 shaper_data,
218 shaper_instance,
219 metrics,
220 },
221 |OwnedFaceData {
222 data,
223 shaper_data,
224 shaper_instance,
225 ..
226 }| {
227 let font_ref = FontRef::from_index((**data).as_ref(), info.index)?;
228 let shaper = shaper_data
229 .shaper(&font_ref)
230 .instance(Some(shaper_instance))
231 .build();
232 Ok::<_, ReadError>(shaper)
233 },
234 )
235 .ok()?,
236 data: FontData::new(Blob::new(data), info.index),
237 })
238 }
239}
240
241#[cfg(test)]
242mod test {
243 #[test]
244 fn test_fonts_load_time() {
245 use crate::FontSystem;
246 use sys_locale::get_locale;
247
248 #[cfg(not(target_arch = "wasm32"))]
249 let now = std::time::Instant::now();
250
251 let mut db = fontdb::Database::new();
252 let locale = get_locale().expect("Local available");
253 db.load_system_fonts();
254 FontSystem::new_with_locale_and_db(locale, db);
255
256 #[cfg(not(target_arch = "wasm32"))]
257 println!("Fonts load time {}ms.", now.elapsed().as_millis());
258 }
259}