font browser
src/pages.rs (2.5K)
1 use std::process::Command;
2
3 #[derive(Debug, Clone)]
4 pub struct FontEntry {
5 pub family: String,
6 pub style: String,
7 pub file: String,
8 /// The face's index within `file` (non-zero only in a .ttc collection).
9 pub index: u32,
10 }
11
12 pub fn fetch_fonts() -> Vec<FontEntry> {
13 let output = match Command::new("fc-list")
14 .arg("--format=%{family}\\t%{style}\\t%{index}\\t%{file}\\n")
15 .output()
16 {
17 Ok(o) => String::from_utf8_lossy(&o.stdout).into_owned(),
18 Err(_) => return Vec::new(),
19 };
20
21 let mut fonts: Vec<FontEntry> = output
22 .lines()
23 .filter_map(|line| {
24 let parts: Vec<&str> = line.splitn(4, '\t').collect();
25 if parts.len() == 4 {
26 let family = parts[0].split(',').next().unwrap_or(parts[0]).trim().to_string();
27 let style = parts[1].split(',').next().unwrap_or(parts[1]).trim().to_string();
28 Some(FontEntry {
29 family,
30 style,
31 index: parts[2].trim().parse().unwrap_or(0),
32 file: parts[3].trim().to_string(),
33 })
34 } else {
35 None
36 }
37 })
38 .collect();
39
40 // Deduped by the caller, which first drops the faces it cannot render.
41 fonts.sort_by(|a, b| a.family.to_lowercase().cmp(&b.family.to_lowercase()));
42 fonts
43 }
44
45 pub fn is_user_font(file: &str) -> bool {
46 file.starts_with("/home/") || file.contains(".local/share/fonts") || file.contains(".fonts")
47 }
48
49 pub fn count_chars(file: &str) -> usize {
50 let output = Command::new("fc-query")
51 .arg("--format=%{charset}")
52 .arg(file)
53 .output()
54 .ok();
55
56 match output {
57 Some(o) => {
58 let s = String::from_utf8_lossy(&o.stdout);
59 let mut count = 0usize;
60 for range in s.split_whitespace() {
61 if let Some((start, end)) = range.split_once('-') {
62 if let (Ok(s_val), Ok(e_val)) = (u32::from_str_radix(start, 16), u32::from_str_radix(end, 16)) {
63 // Guard against malformed/reversed ranges (end < start) — an unsigned
64 // subtraction there panics and crashes the whole app.
65 if e_val >= s_val {
66 count += (e_val - s_val + 1) as usize;
67 }
68 }
69 } else if let Ok(_) = u32::from_str_radix(range, 16) {
70 count += 1;
71 }
72 }
73 count
74 }
75 None => 0,
76 }
77 }