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