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