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