1#[cfg(not(any(target_arch = "wasm32", target_os = "android")))]
2pub use desktop::*;
3
4#[cfg(target_arch = "wasm32")]
5pub use wasm::*;
6
7#[cfg(target_os = "android")]
8pub use android::*;
9
10#[cfg(not(any(target_arch = "wasm32", target_os = "android")))]
11mod desktop {
12 use std::{
13 borrow::Cow,
14 path::{Path, PathBuf},
15 sync::Arc,
16 };
17
18 use parking_lot::Mutex;
19 use zng_layout::unit::ByteUnits;
20 use zng_var::ResponseVar;
21
22 use crate::{FontDataRef, FontLoadingError, FontName, FontStretch, FontStyle, FontWeight, GlyphLoadingError};
23
24 static DATA_CACHE: Mutex<Vec<(PathBuf, std::sync::Weak<Vec<u8>>)>> = Mutex::new(vec![]);
25
26 pub fn system_all() -> ResponseVar<Vec<FontName>> {
27 zng_task::wait_respond(|| {
28 font_kit::source::SystemSource::new()
29 .all_families()
30 .unwrap_or_default()
31 .into_iter()
32 .map(FontName::from)
33 .collect()
34 })
35 }
36
37 pub fn best(
38 font_name: &FontName,
39 style: FontStyle,
40 weight: FontWeight,
41 stretch: FontStretch,
42 ) -> Result<Option<(FontDataRef, u32)>, FontLoadingError> {
43 if font_name == "Ubuntu" {
44 if let Ok(Some(h)) = workaround_ubuntu(style, weight, stretch) {
45 return Ok(Some(h));
46 }
47 }
48
49 let family_name = font_kit::family_name::FamilyName::from(font_name.clone());
50 match font_kit::source::SystemSource::new().select_best_match(
51 &[family_name],
52 &font_kit::properties::Properties {
53 style: style.into(),
54 weight: weight.into(),
55 stretch: stretch.into(),
56 },
57 ) {
58 Ok(handle) => {
59 let r = load_handle(&handle)?;
60 Ok(Some(r))
61 }
62 Err(font_kit::error::SelectionError::NotFound) => {
63 tracing::debug!(target: "font_loading", "system font not found\nquery: {:?}", (font_name, style, weight, stretch));
64 Ok(None)
65 }
66 Err(font_kit::error::SelectionError::CannotAccessSource { reason }) => Err(FontLoadingError::Io(Arc::new(
67 std::io::Error::new(std::io::ErrorKind::Other, reason.unwrap_or_default()),
68 ))),
69 }
70 }
71
72 fn workaround_ubuntu(
74 style: FontStyle,
75 weight: FontWeight,
76 stretch: FontStretch,
77 ) -> Result<Option<(FontDataRef, u32)>, FontLoadingError> {
78 let source = font_kit::source::SystemSource::new();
79 let ubuntu = match source.select_family_by_name("Ubuntu") {
80 Ok(u) => u,
81 Err(e) => {
82 return match e {
83 font_kit::error::SelectionError::NotFound => Ok(None),
84 font_kit::error::SelectionError::CannotAccessSource { reason } => Err(FontLoadingError::Io(Arc::new(
85 std::io::Error::new(std::io::ErrorKind::Other, reason.unwrap_or_default()),
86 ))),
87 };
88 }
89 };
90 for handle in ubuntu.fonts() {
91 let font = handle.load()?;
92 let name = match font.postscript_name() {
93 Some(n) => n,
94 None => continue,
95 };
96
97 if (style == FontStyle::Italic) != name.contains("Italic") {
124 continue;
125 }
126
127 if (FontWeight::MEDIUM..FontWeight::SEMIBOLD).contains(&weight) != name.contains("Medium") {
128 continue;
129 }
130 if (weight >= FontWeight::EXTRA_BOLD) != name.contains("ExtraBold") {
131 continue;
132 }
133 if (FontWeight::SEMIBOLD..FontWeight::EXTRA_BOLD).contains(&weight) != name.contains("Bold") {
134 continue;
135 }
136
137 if (FontWeight::EXTRA_LIGHT..FontWeight::LIGHT).contains(&weight) != name.contains("Light") {
138 continue;
139 }
140 if (weight < FontWeight::EXTRA_LIGHT) != name.contains("Thin") {
141 continue;
142 }
143
144 if (stretch <= FontStretch::CONDENSED) != name.contains("Condensed") {
145 continue;
146 }
147
148 return Ok(Some(load_handle(handle)?));
149 }
150 Ok(None)
151 }
152
153 fn load_handle(handle: &font_kit::handle::Handle) -> Result<(FontDataRef, u32), FontLoadingError> {
154 match handle {
155 font_kit::handle::Handle::Path { path, font_index } => {
156 let mut path = Cow::Borrowed(path);
157 if let Ok(base) = path.strip_prefix("/usr/share/fonts/type1/") {
164 if let Some(name) = base.file_name() {
165 if let Some(name) = name.to_str() {
166 if name.ends_with(".t1") {
167 let rep = Path::new("/usr/share/fonts/opentype/").join(base.with_extension("otf"));
168 if rep.exists() {
169 tracing::debug!("replaced `{name}` with .otf of same name");
170 path = Cow::Owned(rep);
171 }
172 }
173 }
174 }
175 }
176
177 for (k, data) in DATA_CACHE.lock().iter() {
178 if *k == *path {
179 if let Some(data) = data.upgrade() {
180 return Ok((FontDataRef(data), *font_index));
181 }
182 }
183 }
184
185 let bytes = std::fs::read(&*path)?;
186 tracing::debug!("read font `{}:{}`, using {}", path.display(), font_index, bytes.capacity().bytes());
187
188 let data = Arc::new(bytes);
189 let mut cache = DATA_CACHE.lock();
190 cache.retain(|(_, v)| v.strong_count() > 0);
191 cache.push((path.to_path_buf(), Arc::downgrade(&data)));
192
193 Ok((FontDataRef(data), *font_index))
194 }
195 font_kit::handle::Handle::Memory { bytes, font_index } => Ok((FontDataRef(bytes.clone()), *font_index)),
196 }
197 }
198
199 impl From<font_kit::error::FontLoadingError> for FontLoadingError {
200 fn from(ve: font_kit::error::FontLoadingError) -> Self {
201 match ve {
202 font_kit::error::FontLoadingError::UnknownFormat => Self::UnknownFormat,
203 font_kit::error::FontLoadingError::NoSuchFontInCollection => Self::NoSuchFontInCollection,
204 font_kit::error::FontLoadingError::Parse => Self::Parse(ttf_parser::FaceParsingError::MalformedFont),
205 font_kit::error::FontLoadingError::NoFilesystem => Self::NoFilesystem,
206 font_kit::error::FontLoadingError::Io(e) => Self::Io(Arc::new(e)),
207 }
208 }
209 }
210
211 impl From<FontName> for font_kit::family_name::FamilyName {
212 fn from(font_name: FontName) -> Self {
213 use font_kit::family_name::FamilyName::*;
214 match font_name.name() {
215 "serif" => Serif,
216 "sans-serif" => SansSerif,
217 "monospace" => Monospace,
218 "cursive" => Cursive,
219 "fantasy" => Fantasy,
220 _ => Title(font_name.txt.into()),
221 }
222 }
223 }
224
225 impl From<FontStretch> for font_kit::properties::Stretch {
226 fn from(value: FontStretch) -> Self {
227 font_kit::properties::Stretch(value.0)
228 }
229 }
230
231 impl From<FontStyle> for font_kit::properties::Style {
232 fn from(value: FontStyle) -> Self {
233 use font_kit::properties::Style::*;
234 match value {
235 FontStyle::Normal => Normal,
236 FontStyle::Italic => Italic,
237 FontStyle::Oblique => Oblique,
238 }
239 }
240 }
241
242 impl From<FontWeight> for font_kit::properties::Weight {
243 fn from(value: FontWeight) -> Self {
244 font_kit::properties::Weight(value.0)
245 }
246 }
247
248 impl From<font_kit::error::GlyphLoadingError> for GlyphLoadingError {
249 fn from(value: font_kit::error::GlyphLoadingError) -> Self {
250 use GlyphLoadingError::*;
251 match value {
252 font_kit::error::GlyphLoadingError::NoSuchGlyph => NoSuchGlyph,
253 font_kit::error::GlyphLoadingError::PlatformError => PlatformError,
254 }
255 }
256 }
257}
258
259#[cfg(target_arch = "wasm32")]
260mod wasm {
261 use zng_var::ResponseVar;
262
263 use crate::{FontDataRef, FontLoadingError, FontName, FontStretch, FontStyle, FontWeight};
264
265 pub fn system_all() -> ResponseVar<Vec<FontName>> {
266 zng_var::response_done_var(vec![])
267 }
268
269 pub fn best(
270 font_name: &FontName,
271 style: FontStyle,
272 weight: FontWeight,
273 stretch: FontStretch,
274 ) -> Result<Option<(FontDataRef, u32)>, FontLoadingError> {
275 let _ = (font_name, style, weight, stretch);
276 Err(FontLoadingError::NoFilesystem)
277 }
278}
279
280#[cfg(target_os = "android")]
281mod android {
282 use std::{borrow::Cow, path::PathBuf, sync::Arc};
286
287 use zng_var::ResponseVar;
288
289 use crate::{FontDataRef, FontLoadingError, FontName, FontStretch, FontStyle, FontWeight};
290
291 pub fn system_all() -> ResponseVar<Vec<FontName>> {
292 zng_task::wait_respond(|| {
293 let mut prev = None;
294 cached_system_all()
295 .iter()
296 .flat_map(|(k, _)| {
297 if prev == Some(k) {
298 None
299 } else {
300 prev = Some(k);
301 Some(k)
302 }
303 })
304 .cloned()
305 .collect()
306 })
307 }
308
309 fn cached_system_all() -> parking_lot::MappedRwLockReadGuard<'static, Vec<(FontName, PathBuf)>> {
310 let lock = SYSTEM_ALL.read();
311 if !lock.is_empty() {
312 return lock;
313 }
314
315 drop(lock);
316 let mut lock = SYSTEM_ALL.write();
317 if lock.is_empty() {
318 for entry in ["/system/fonts/", "/system/font/", "/data/fonts/", "/system/product/fonts/"]
319 .iter()
320 .flat_map(std::fs::read_dir)
321 .flatten()
322 .flatten()
323 {
324 let entry = entry.path();
325 let ext = entry.extension().and_then(|e| e.to_str()).unwrap_or_default().to_ascii_lowercase();
326 if ["ttf", "otf"].contains(&ext.as_str()) && entry.is_file() {
327 if let Ok(bytes) = std::fs::read(&entry) {
328 match crate::FontFace::load(FontDataRef(Arc::new(bytes)), 0) {
329 Ok(f) => {
330 lock.push((f.family_name().clone(), entry));
331 }
332 Err(e) => tracing::error!("error parsing '{}', {e}", entry.display()),
333 }
334 }
335 }
336 }
337 lock.sort_by(|a, b| a.0.cmp(&b.0));
338 }
339 drop(lock);
340 SYSTEM_ALL.read()
341 }
342
343 pub fn best(
344 font_name: &FontName,
345 style: FontStyle,
346 weight: FontWeight,
347 stretch: FontStretch,
348 ) -> Result<Option<(FontDataRef, u32)>, FontLoadingError> {
349 let lock = cached_system_all();
350 let lock = &*lock;
351
352 let font_name = match font_name.name() {
355 "sans-serif" => Cow::Owned(FontName::new("Roboto")),
356 "serif" | "fantasy" => Cow::Owned(FontName::new("Noto Serif")),
357 "cursive" => Cow::Owned(FontName::new("Dancing Script")),
358 "monospace" => Cow::Owned(FontName::new("Droid Sans Mono")),
359 _ => Cow::Borrowed(font_name),
360 };
361 let font_name = &*font_name;
362
363 let mut start_i = match lock.binary_search_by(|a| a.0.cmp(font_name)) {
364 Ok(i) => i,
365 Err(_) => {
366 tracing::debug!(target: "font_loading", "system font not found\nquery: {:?}", (font_name, style, weight, stretch));
367 return Ok(None);
368 }
369 };
370 while start_i > 0 && &lock[start_i - 1].0 == font_name {
371 start_i -= 1
372 }
373 let mut end_i = start_i;
374 while end_i + 1 < lock.len() && &lock[end_i + 1].0 == font_name {
375 end_i += 1
376 }
377
378 let family_len = end_i - start_i;
379 let mut options = Vec::with_capacity(family_len);
380 let mut candidates = Vec::with_capacity(family_len);
381
382 for (_, path) in &lock[start_i..=end_i] {
383 if let Ok(bytes) = std::fs::read(path) {
384 if let Ok(f) = ttf_parser::Face::parse(&bytes, 0) {
385 candidates.push(matching::Properties {
386 style: f.style(),
387 weight: f.weight(),
388 stretch: f.width(),
389 });
390 options.push(bytes);
391 }
392 }
393 }
394
395 match matching::find_best_match(
396 &candidates,
397 &matching::Properties {
398 style: style.into(),
399 weight: weight.into(),
400 stretch: stretch.into(),
401 },
402 ) {
403 Ok(i) => {
404 let bytes = options.swap_remove(i);
405 Ok(Some((FontDataRef(Arc::new(bytes)), 0)))
406 }
407 Err(FontLoadingError::NoSuchFontInCollection) => {
408 tracing::debug!(target: "font_loading", "system font not found\nquery: {:?}", (font_name, style, weight, stretch));
409 Ok(None)
410 }
411 Err(e) => Err(FontLoadingError::Io(Arc::new(std::io::Error::new(std::io::ErrorKind::Other, e)))),
412 }
413 }
414
415 zng_app_context::app_local! {
416 static SYSTEM_ALL: Vec<(FontName, PathBuf)> = vec![];
417 }
418
419 mod matching {
420 use ttf_parser::{Style, Weight, Width as Stretch};
433
434 use crate::FontLoadingError;
435
436 pub struct Properties {
437 pub style: Style,
438 pub weight: Weight,
439 pub stretch: Stretch,
440 }
441
442 pub fn find_best_match(candidates: &[Properties], query: &Properties) -> Result<usize, FontLoadingError> {
446 let mut matching_set: Vec<usize> = (0..candidates.len()).collect();
448 if matching_set.is_empty() {
449 return Err(FontLoadingError::NoSuchFontInCollection);
450 }
451
452 let matching_stretch = if matching_set.iter().any(|&index| candidates[index].stretch == query.stretch) {
454 query.stretch
456 } else if query.stretch <= Stretch::Normal {
457 match matching_set
459 .iter()
460 .filter(|&&index| candidates[index].stretch < query.stretch)
461 .min_by_key(|&&index| query.stretch.to_number() - candidates[index].stretch.to_number())
462 {
463 Some(&matching_index) => candidates[matching_index].stretch,
464 None => {
465 let matching_index = *matching_set
466 .iter()
467 .min_by_key(|&&index| candidates[index].stretch.to_number() - query.stretch.to_number())
468 .unwrap();
469 candidates[matching_index].stretch
470 }
471 }
472 } else {
473 match matching_set
475 .iter()
476 .filter(|&&index| candidates[index].stretch > query.stretch)
477 .min_by_key(|&&index| candidates[index].stretch.to_number() - query.stretch.to_number())
478 {
479 Some(&matching_index) => candidates[matching_index].stretch,
480 None => {
481 let matching_index = *matching_set
482 .iter()
483 .min_by_key(|&&index| query.stretch.to_number() - candidates[index].stretch.to_number())
484 .unwrap();
485 candidates[matching_index].stretch
486 }
487 }
488 };
489 matching_set.retain(|&index| candidates[index].stretch == matching_stretch);
490
491 let style_preference = match query.style {
493 Style::Italic => [Style::Italic, Style::Oblique, Style::Normal],
494 Style::Oblique => [Style::Oblique, Style::Italic, Style::Normal],
495 Style::Normal => [Style::Normal, Style::Oblique, Style::Italic],
496 };
497 let matching_style = *style_preference
498 .iter()
499 .find(|&query_style| matching_set.iter().any(|&index| candidates[index].style == *query_style))
500 .unwrap();
501 matching_set.retain(|&index| candidates[index].style == matching_style);
502
503 let matching_weight = if matching_set.iter().any(|&index| candidates[index].weight == query.weight) {
508 query.weight
509 } else if query.weight.to_number() >= 400
510 && query.weight.to_number() < 450
511 && matching_set.iter().any(|&index| candidates[index].weight == Weight::from(500))
512 {
513 Weight::from(500)
515 } else if query.weight.to_number() >= 450
516 && query.weight.to_number() <= 500
517 && matching_set.iter().any(|&index| candidates[index].weight.to_number() == 400)
518 {
519 Weight::from(400)
521 } else if query.weight.to_number() <= 500 {
522 match matching_set
524 .iter()
525 .filter(|&&index| candidates[index].weight.to_number() <= query.weight.to_number())
526 .min_by_key(|&&index| query.weight.to_number() - candidates[index].weight.to_number())
527 {
528 Some(&matching_index) => candidates[matching_index].weight,
529 None => {
530 let matching_index = *matching_set
531 .iter()
532 .min_by_key(|&&index| candidates[index].weight.to_number() - query.weight.to_number())
533 .unwrap();
534 candidates[matching_index].weight
535 }
536 }
537 } else {
538 match matching_set
540 .iter()
541 .filter(|&&index| candidates[index].weight.to_number() >= query.weight.to_number())
542 .min_by_key(|&&index| candidates[index].weight.to_number() - query.weight.to_number())
543 {
544 Some(&matching_index) => candidates[matching_index].weight,
545 None => {
546 let matching_index = *matching_set
547 .iter()
548 .min_by_key(|&&index| query.weight.to_number() - candidates[index].weight.to_number())
549 .unwrap();
550 candidates[matching_index].weight
551 }
552 }
553 };
554 matching_set.retain(|&index| candidates[index].weight == matching_weight);
555
556 matching_set.into_iter().next().ok_or(FontLoadingError::NoSuchFontInCollection)
560 }
561 }
562}