git.lucas.co / cce-fonts
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 }