Skip to main content

ui/
fonts.rs

1//! The fonts an app paints with, for gpui's text system and for SVG laid out
2//! and drawn with usvg.
3//!
4//! Natively, [`database`] is the system's fonts. In a browser it is the faces
5//! given to [`add`], and a face given to `cx.text_system().add_fonts` directly
6//! paints text and is missing from SVG.
7
8use std::{borrow::Cow, sync::Arc};
9
10use gpui::App;
11use usvg::fontdb;
12
13/// The fonts SVG is laid out and drawn in.
14///
15/// `generation` changes with each [`add`] in a browser; a face's
16/// [`fontdb::ID`] means nothing in another generation's `fonts`.
17#[derive(Clone)]
18pub struct Database {
19    pub fonts: Arc<fontdb::Database>,
20    pub generation: u64,
21}
22
23/// Hand `fonts` to gpui's text system, and in a browser to [`database`].
24pub fn add(cx: &App, fonts: Vec<Cow<'static, [u8]>>) -> gpui::Result<()> {
25    #[cfg(target_family = "wasm")]
26    web::add(&fonts);
27    cx.text_system().add_fonts(fonts)
28}
29
30/// The fonts SVG is drawn in. Natively the first call loads the system's,
31/// which reads every font file's names: a quarter of a second on a Mac.
32pub fn database() -> Database {
33    #[cfg(not(target_family = "wasm"))]
34    {
35        static SYSTEM: std::sync::LazyLock<Database> = std::sync::LazyLock::new(|| {
36            let mut fonts = fontdb::Database::new();
37            fonts.load_system_fonts();
38            Database {
39                fonts: Arc::new(fonts),
40                generation: 0,
41            }
42        });
43        SYSTEM.clone()
44    }
45    #[cfg(target_family = "wasm")]
46    web::database()
47}
48
49#[cfg(target_family = "wasm")]
50mod web {
51    use std::{
52        borrow::Cow,
53        sync::{Arc, Mutex, PoisonError},
54    };
55
56    use usvg::fontdb::{self, Source};
57
58    use super::Database;
59
60    static FONTS: Mutex<Option<Database>> = Mutex::new(None);
61
62    pub(super) fn add(added: &[Cow<'static, [u8]>]) {
63        let mut held = FONTS.lock().unwrap_or_else(PoisonError::into_inner);
64        let mut fonts = held
65            .as_ref()
66            .map_or_else(fontdb::Database::new, |db| (*db.fonts).clone());
67        for font in added {
68            fonts.load_font_source(Source::Binary(Arc::new(font.clone())));
69        }
70        let generation = held.as_ref().map_or(0, |db| db.generation) + 1;
71        *held = Some(Database {
72            fonts: Arc::new(fonts),
73            generation,
74        });
75    }
76
77    pub(super) fn database() -> Database {
78        FONTS
79            .lock()
80            .unwrap_or_else(PoisonError::into_inner)
81            .get_or_insert_with(|| Database {
82                fonts: Arc::new(fontdb::Database::new()),
83                generation: 0,
84            })
85            .clone()
86    }
87}