1#![cfg(feature = "font_loading")]
2
3use azul_css::{AzString, U8Vec};
4use rust_fontconfig::{FcFontCache, FontSource};
5
6pub mod loading;
7
8#[cfg(target_os = "windows")]
10const KNOWN_SYSTEM_SERIF_FONTS: &[&str] = &["Times New Roman"];
11#[cfg(target_os = "linux")]
12const KNOWN_SYSTEM_SERIF_FONTS: &[&str] = &[
13 "Times",
15 "Times New Roman",
16 "DejaVu Serif",
17 "Free Serif",
18 "Noto Serif",
19 "Bitstream Vera Serif",
20 "Roman",
21 "Regular",
22];
23#[cfg(target_os = "macos")]
24const KNOWN_SYSTEM_SERIF_FONTS: &[&str] = &["Times", "New York", "Palatino"];
25
26#[cfg(target_os = "windows")]
28const KNOWN_SYSTEM_MONOSPACE_FONTS: &[&str] = &[
29 "Segoe UI Mono",
30 "Courier New",
31 "Cascadia Code",
32 "Cascadia Mono",
33];
34#[cfg(target_os = "linux")]
35const KNOWN_SYSTEM_MONOSPACE_FONTS: &[&str] = &[
36 "Source Code Pro",
38 "Cantarell",
39 "DejaVu Sans Mono",
40 "Roboto Mono",
41 "Ubuntu Monospace",
42 "Droid Sans Mono",
43];
44#[cfg(target_os = "macos")]
45const KNOWN_SYSTEM_MONOSPACE_FONTS: &[&str] = &[
46 "SF Mono",
47 "Menlo",
48 "Monaco",
49 "Oxygen Mono",
50 "Source Code Pro",
51 "Fira Mono",
52];
53
54#[cfg(target_os = "windows")]
56const KNOWN_SYSTEM_SANS_SERIF_FONTS: &[&str] = &[
57 "Segoe UI", "Tahoma", "Microsoft Sans Serif",
60 "MS Sans Serif",
61 "Helv",
62];
63#[cfg(target_os = "linux")]
64const KNOWN_SYSTEM_SANS_SERIF_FONTS: &[&str] = &[
65 "Ubuntu",
67 "Arial",
68 "DejaVu Sans",
69 "Noto Sans",
70 "Liberation Sans",
71];
72#[cfg(target_os = "macos")]
73const KNOWN_SYSTEM_SANS_SERIF_FONTS: &[&str] = &[
74 "San Francisco", "Helvetica Neue", "Lucida Grande", ];
78
79#[cfg(target_family = "wasm")]
80const KNOWN_SYSTEM_MONOSPACE_FONTS: &[&str] = &[];
81#[cfg(target_family = "wasm")]
82const KNOWN_SYSTEM_SANS_SERIF_FONTS: &[&str] = &[];
83#[cfg(target_family = "wasm")]
84const KNOWN_SYSTEM_SERIF_FONTS: &[&str] = &[];
85
86pub fn load_system_font(id: &str, fc_cache: &FcFontCache) -> Option<(U8Vec, i32)> {
90 use rust_fontconfig::{FcFontPath, FcPattern, PatternMatch};
91
92 let mut patterns = Vec::new();
93
94 match id {
95 "monospace" => {
96 #[cfg(target_os = "linux")]
97 {
98 if let Some(gsettings_pref) = linux_get_gsettings_font("monospace-font-name") {
99 patterns.push(FcPattern {
100 name: Some(gsettings_pref),
101 ..FcPattern::default()
102 });
103 }
104 if let Some(fontconfig_pref) = linux_get_fc_match_font("monospace") {
105 patterns.push(FcPattern {
106 name: Some(fontconfig_pref),
107 ..FcPattern::default()
108 });
109 }
110 }
111
112 for monospace_font_name in KNOWN_SYSTEM_MONOSPACE_FONTS.iter() {
113 patterns.push(FcPattern {
114 name: Some(monospace_font_name.to_string()),
115 ..FcPattern::default()
116 });
117 }
118
119 patterns.push(FcPattern {
120 monospace: PatternMatch::True,
121 ..FcPattern::default()
122 });
123 }
124 "fantasy" | "oblique" => {
125 #[cfg(target_os = "linux")]
126 {
127 if let Some(fontconfig_pref) = linux_get_fc_match_font("sans-serif") {
128 patterns.push(FcPattern {
129 name: Some(fontconfig_pref),
130 oblique: PatternMatch::True,
131 ..FcPattern::default()
132 });
133 }
134 }
135 for serif_font in KNOWN_SYSTEM_SERIF_FONTS.iter() {
136 patterns.push(FcPattern {
137 name: Some(serif_font.to_string()),
138 oblique: PatternMatch::True,
139 ..FcPattern::default()
140 });
141 }
142
143 patterns.push(FcPattern {
144 oblique: PatternMatch::True,
145 ..FcPattern::default()
146 });
147 }
148 "italic" => {
149 #[cfg(target_os = "linux")]
150 {
151 if let Some(fontconfig_pref) = linux_get_fc_match_font("italic") {
152 patterns.push(FcPattern {
153 name: Some(fontconfig_pref),
154 italic: PatternMatch::True,
155 ..FcPattern::default()
156 });
157 }
158 }
159 for serif_font in KNOWN_SYSTEM_SERIF_FONTS.iter() {
160 patterns.push(FcPattern {
161 name: Some(serif_font.to_string()),
162 italic: PatternMatch::True,
163 ..FcPattern::default()
164 });
165 }
166
167 patterns.push(FcPattern {
168 italic: PatternMatch::True,
169 ..FcPattern::default()
170 });
171 }
172 "sans-serif" => {
173 #[cfg(target_os = "linux")]
174 {
175 if let Some(gsettings_pref) = linux_get_gsettings_font("font-name") {
176 patterns.push(FcPattern {
177 name: Some(gsettings_pref),
178 ..FcPattern::default()
179 });
180 }
181 if let Some(fontconfig_pref) = linux_get_fc_match_font("sans-serif") {
182 patterns.push(FcPattern {
183 name: Some(fontconfig_pref),
184 ..FcPattern::default()
185 });
186 }
187 }
188
189 for sans_serif_font in KNOWN_SYSTEM_SANS_SERIF_FONTS.iter() {
190 patterns.push(FcPattern {
191 name: Some(sans_serif_font.to_string()),
192 ..FcPattern::default()
193 });
194 }
195 }
196 "serif" => {
197 #[cfg(target_os = "linux")]
198 {
199 if let Some(fontconfig_pref) = linux_get_fc_match_font("serif") {
200 patterns.push(FcPattern {
201 name: Some(fontconfig_pref),
202 ..FcPattern::default()
203 });
204 }
205 }
206
207 for serif_font in KNOWN_SYSTEM_SERIF_FONTS.iter() {
208 patterns.push(FcPattern {
209 name: Some(serif_font.to_string()),
210 ..FcPattern::default()
211 });
212 }
213 }
214 other => {
215 patterns.push(FcPattern {
216 name: Some(other.clone().into()),
217 ..FcPattern::default()
218 });
219
220 patterns.push(FcPattern {
221 family: Some(other.clone().into()),
222 ..FcPattern::default()
223 });
224 }
225 }
226
227 patterns.push(FcPattern::default());
230
231 for pattern in patterns {
232 let font_source = fc_cache
234 .query(&pattern, &mut Vec::new())
235 .and_then(|s| fc_cache.get_font_by_id(&s.id));
236
237 let font_source = match font_source {
238 Some(s) => s,
239 None => continue,
240 };
241
242 match font_source {
243 FontSource::Memory(m) => {
244 return Some((m.bytes.clone().into(), m.font_index as i32));
245 }
246 FontSource::Disk(d) => {
247 use std::{fs, path::Path};
248 if let Ok(bytes) = fs::read(Path::new(&d.path)) {
249 return Some((bytes.into(), d.font_index as i32));
250 }
251 }
252 }
253 }
254
255 None
256}
257
258#[cfg(all(target_os = "linux", feature = "std"))]
259fn linux_get_gsettings_font(font_name: &'static str) -> Option<String> {
260 std::process::Command::new("gsettings")
262 .arg("get")
263 .arg("org.gnome.desktop.interface")
264 .arg(font_name)
265 .output()
266 .ok()
267 .map(|output| output.stdout)
268 .and_then(|stdout_bytes| String::from_utf8(stdout_bytes).ok())
269 .map(|stdout_string| stdout_string.lines().collect::<String>())
270 .map(|s| parse_gsettings_font(&s).to_string())
271}
272
273fn parse_gsettings_font(input: &str) -> &str {
274 use std::char;
275 let input = input.trim();
276 let input = input.trim_matches('\'');
277 let input = input.trim_end_matches(char::is_numeric);
278 let input = input.trim();
279 input
280}
281
282#[cfg(all(target_os = "linux", feature = "std"))]
283fn linux_get_fc_match_font(font_name: &'static str) -> Option<String> {
284 std::process::Command::new("fc-match")
286 .arg(font_name)
287 .output()
288 .ok()
289 .map(|output| output.stdout)
290 .and_then(|stdout_bytes| String::from_utf8(stdout_bytes).ok())
291 .map(|stdout_string| stdout_string.lines().collect::<String>())
292 .and_then(|s| Some(parse_fc_match_font(&s)?.to_string()))
293}
294
295fn parse_fc_match_font(input: &str) -> Option<&str> {
299 let input = input.trim();
300 let mut split_iterator = input.split(":");
301 split_iterator.next()?;
302
303 let fonts_str = split_iterator.next()?; let fonts_str = fonts_str.trim();
305 let mut font_iterator = input.split("\" \"");
306 let first_font = font_iterator.next()?; let first_font = first_font.trim();
309 let first_font = first_font.trim_start_matches('"');
310 let first_font = first_font.trim_end_matches('"');
311 let first_font = first_font.trim();
312
313 Some(first_font)
314}