1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4use std::cell::RefCell;
5use std::sync::Arc;
6
7pub use fontdb;
8pub use ttf_parser;
9
10#[derive(derive_more::Deref)]
11pub struct FontDatabase {
12 // This is in a Arc because usvg takes the database in a Arc
13 #[deref]
14 db: Arc<fontdb::Database>,
15 #[cfg(not(any(
16 target_family = "windows",
17 target_os = "macos",
18 target_os = "ios",
19 target_arch = "wasm32",
20 target_os = "android",
21 )))]
22 pub fontconfig_fallback_families: Vec<String>,
23 // Default font families to use instead of SansSerif when SLINT_DEFAULT_FONT env var is set.
24 pub default_font_family_ids: Vec<fontdb::ID>,
25 // Same as default_font_families but reduced to unique family names
26 default_font_family_names: Vec<String>,
27}
28
29impl FontDatabase {
30 pub fn query_with_family(
31 &self,
32 query: fontdb::Query<'_>,
33 family: Option<&'_ str>,
34 ) -> Option<fontdb::ID> {
35 let mut query = query;
36 if let Some(specified_family) = family {
37 let single_family = [fontdb::Family::Name(specified_family)];
38 query.families = &single_family;
39 self.db.query(&query)
40 } else if self.default_font_family_ids.is_empty() {
41 query.families = &[fontdb::Family::SansSerif];
42 self.db.query(&query)
43 } else {
44 let family_storage = self
45 .default_font_family_names
46 .iter()
47 .map(|name| fontdb::Family::Name(name))
48 .collect::<Vec<_>>();
49 query.families = &family_storage;
50 self.db.query(&query)
51 }
52 }
53
54 pub fn make_mut(&mut self) -> &mut fontdb::Database {
55 Arc::make_mut(&mut self.db)
56 }
57}
58
59thread_local! {
60 pub static FONT_DB: RefCell<FontDatabase> = RefCell::new(init_fontdb())
61}
62
63#[cfg(not(any(
64 target_family = "windows",
65 target_os = "macos",
66 target_os = "ios",
67 target_arch = "wasm32",
68 target_os = "android",
69)))]
70mod fontconfig;
71
72fn init_fontdb() -> FontDatabase {
73 let mut font_db = fontdb::Database::new();
74
75 #[cfg(not(target_arch = "wasm32"))]
76 let (default_font_family_ids, default_font_family_names) =
77 std::env::var_os("SLINT_DEFAULT_FONT")
78 .and_then(|maybe_font_path| {
79 let path = std::path::Path::new(&maybe_font_path);
80 match if path.extension().is_some() {
81 font_db.load_font_file(path)
82 } else {
83 font_db.load_fonts_dir(path);
84 Ok(())
85 } {
86 Ok(_) => {
87 let mut family_ids = Vec::new();
88 let mut family_names = Vec::new();
89
90 for face_info in font_db.faces() {
91 family_ids.push(face_info.id);
92
93 let family_name = &face_info.families[0].0;
94 if let Err(insert_pos) = family_names.binary_search(family_name) {
95 family_names.insert(insert_pos, family_name.clone());
96 }
97 }
98
99 Some((family_ids, family_names))
100 }
101 Err(err) => {
102 eprintln!(
103 "Could not load the font set via `SLINT_DEFAULT_FONT`: {}: {}",
104 path.display(),
105 err,
106 );
107 None
108 }
109 }
110 })
111 .unwrap_or_default();
112
113 #[cfg(target_arch = "wasm32")]
114 let (default_font_family_ids, default_font_family_names) =
115 (Default::default(), Default::default());
116
117 #[cfg(not(any(
118 target_family = "windows",
119 target_os = "macos",
120 target_os = "ios",
121 target_arch = "wasm32",
122 target_os = "android",
123 )))]
124 let mut fontconfig_fallback_families = Vec::new();
125
126 #[cfg(target_arch = "wasm32")]
127 {
128 let data = include_bytes!("sharedfontdb/DejaVuSans.ttf");
129 font_db.load_font_data(data.to_vec());
130 font_db.set_sans_serif_family("DejaVu Sans");
131 }
132 #[cfg(target_os = "android")]
133 {
134 font_db.load_fonts_dir("/system/fonts");
135 font_db.set_sans_serif_family("Roboto");
136 }
137 #[cfg(not(any(target_arch = "wasm32", target_os = "android")))]
138 {
139 font_db.load_system_fonts();
140 cfg_if::cfg_if! {
141 if #[cfg(not(any(
142 target_family = "windows",
143 target_os = "macos",
144 target_os = "ios",
145 target_arch = "wasm32",
146 target_os = "android",
147 )))] {
148 match fontconfig::find_families("sans-serif") {
149 Ok(mut fallback_families) => {
150 if !fallback_families.is_empty() {
151 let default_sans_serif_family = fallback_families.remove(0);
152 font_db.set_sans_serif_family(default_sans_serif_family);
153 }
154 fontconfig_fallback_families = fallback_families;
155 }
156 Err(e) => {
157 eprintln!("Error opening libfontconfig.so.1: {e}");
158 }
159 }
160 }
161 }
162 if font_db
163 .query(&fontdb::Query { families: &[fontdb::Family::SansSerif], ..Default::default() })
164 .is_none()
165 {
166 panic!(
167 "Unable to determine default font. Failed to locate font for family {}",
168 font_db.family_name(&fontdb::Family::SansSerif)
169 )
170 }
171 }
172
173 FontDatabase {
174 db: Arc::new(font_db),
175 #[cfg(not(any(
176 target_family = "windows",
177 target_os = "macos",
178 target_os = "ios",
179 target_arch = "wasm32",
180 target_os = "android",
181 )))]
182 fontconfig_fallback_families,
183 default_font_family_ids,
184 default_font_family_names,
185 }
186}
187
188/// This function can be used to register a custom TrueType font with Slint,
189/// for use with the `font-family` property. The provided slice must be a valid TrueType
190/// font.
191pub fn register_font_from_memory(data: &'static [u8]) -> Result<(), Box<dyn std::error::Error>> {
192 FONT_DB.with_borrow_mut(|db: &mut FontDatabase| {
193 db.make_mut().load_font_source(fontdb::Source::Binary(std::sync::Arc::new(data)))
194 });
195 Ok(())
196}
197
198#[cfg(not(target_arch = "wasm32"))]
199pub fn register_font_from_path(path: &std::path::Path) -> Result<(), Box<dyn std::error::Error>> {
200 let requested_path: PathBuf = path.canonicalize().unwrap_or_else(|_| path.to_owned());
201 FONT_DB.with_borrow_mut(|db: &mut FontDatabase| {
202 for face_info in db.faces() {
203 match &face_info.source {
204 fontdb::Source::Binary(_) => {}
205 fontdb::Source::File(loaded_path: &PathBuf) | fontdb::Source::SharedFile(loaded_path: &PathBuf, ..) => {
206 if *loaded_path == requested_path {
207 return Ok(());
208 }
209 }
210 }
211 }
212 db.make_mut().load_font_file(requested_path).map_err(|e: Error| e.into())
213 })
214}
215
216#[cfg(target_arch = "wasm32")]
217pub fn register_font_from_path(_path: &std::path::Path) -> Result<(), Box<dyn std::error::Error>> {
218 return Err(std::io::Error::new(
219 std::io::ErrorKind::Other,
220 "Registering fonts from paths is not supported in WASM builds",
221 )
222 .into());
223}
224
225/// Font metrics in design space. Scale with desired pixel size and divided by units_per_em
226/// to obtain pixel metrics.
227#[derive(Clone)]
228pub struct DesignFontMetrics {
229 pub ascent: f32,
230 pub descent: f32,
231 pub x_height: f32,
232 pub cap_height: f32,
233 pub units_per_em: f32,
234}
235
236impl DesignFontMetrics {
237 pub fn new(face: ttf_parser::Face<'_>) -> Self {
238 Self {
239 ascent: face.ascender() as f32,
240 descent: face.descender() as f32,
241 x_height: face.x_height().unwrap_or_default() as f32,
242 cap_height: face.capital_height().unwrap_or_default() as f32,
243 units_per_em: face.units_per_em() as f32,
244 }
245 }
246}
247