]> git.seodisparate.com - mpd_info_screen/commitdiff
Impl fontconfig finding font with given char/str
authorStephen Seo <seo.disparate@gmail.com>
Sun, 31 Jul 2022 12:35:08 +0000 (21:35 +0900)
committerStephen Seo <seo.disparate@gmail.com>
Sun, 31 Jul 2022 12:35:08 +0000 (21:35 +0900)
Should work with non-ascii as well, provided that the system has the
necessary fonts installed.

build.rs
src/unicode_support/fontconfig.rs

index 458302eec423bf0f68ba98ff92182d14d9958133..923299d30efdb230a2468e8b36de270f82363dbe 100644 (file)
--- a/build.rs
+++ b/build.rs
@@ -4,8 +4,7 @@ use std::path::PathBuf;
 use bindgen;
 
 #[cfg(not(feature = "unicode_support"))]
-fn main() {
-}
+fn main() {}
 
 #[cfg(feature = "unicode_support")]
 fn main() {
index 74de4e8dd7ed4ba07236ad34e5320a3cfad1b4ae..613821d125cc5a91617cc7f80008ba93a243528d 100644 (file)
@@ -1,4 +1,8 @@
+use std::{path::PathBuf, str::FromStr};
+
 mod ffi {
+    use std::{ffi::CStr, os::raw::c_int};
+
     mod bindgen {
         #![allow(non_upper_case_globals)]
         #![allow(non_camel_case_types)]
@@ -6,4 +10,337 @@ mod ffi {
 
         include!(concat!(env!("OUT_DIR"), "/unicode_support_bindings.rs"));
     }
+
+    pub struct FcConfigWr {
+        config: *mut bindgen::FcConfig,
+    }
+
+    impl Drop for FcConfigWr {
+        fn drop(&mut self) {
+            if !self.config.is_null() {
+                unsafe {
+                    bindgen::FcConfigDestroy(self.config);
+                }
+            }
+        }
+    }
+
+    impl FcConfigWr {
+        pub fn new() -> Result<Self, String> {
+            let config = unsafe { bindgen::FcInitLoadConfigAndFonts() };
+            if config.is_null() {
+                Err(String::from("Failed to create FcConfig"))
+            } else {
+                Ok(Self { config })
+            }
+        }
+
+        pub fn get(&mut self) -> *mut bindgen::FcConfig {
+            self.config
+        }
+
+        pub fn apply_pattern_to_config(&mut self, pattern: &mut FcPatternWr) -> bool {
+            unsafe {
+                bindgen::FcConfigSubstitute(
+                    self.config,
+                    pattern.get(),
+                    bindgen::_FcMatchKind_FcMatchPattern,
+                ) == bindgen::FcTrue as bindgen::FcBool
+            }
+        }
+
+        pub fn font_match(&mut self, pattern: &mut FcPatternWr) -> Result<FcPatternWr, String> {
+            unsafe {
+                let mut result: bindgen::FcResult = 0 as bindgen::FcResult;
+                let result_pattern = bindgen::FcFontMatch(
+                    self.config,
+                    pattern.get(),
+                    &mut result as *mut bindgen::FcResult,
+                );
+                if result != bindgen::_FcResult_FcResultMatch {
+                    if !result_pattern.is_null() {
+                        bindgen::FcPatternDestroy(result_pattern);
+                        return Err(String::from("Failed to FcFontMatch (FcResult is not FcResultMatch; result_pattern is not null)"));
+                    } else {
+                        return Err(format!(
+                            "Failed to FcFontMatch (FcResult is not FcResultMatch; {:?})",
+                            result
+                        ));
+                    }
+                } else if result_pattern.is_null() {
+                    return Err(String::from(
+                        "Failed to FcFontMatch (result_pattern is null)",
+                    ));
+                }
+
+                Ok(FcPatternWr {
+                    pattern: result_pattern,
+                })
+            }
+        }
+    }
+
+    pub struct FcCharSetWr {
+        charset: *mut bindgen::FcCharSet,
+    }
+
+    impl Drop for FcCharSetWr {
+        fn drop(&mut self) {
+            if !self.charset.is_null() {
+                unsafe {
+                    bindgen::FcCharSetDestroy(self.charset);
+                }
+            }
+        }
+    }
+
+    impl FcCharSetWr {
+        pub fn new_with_str(s: &str) -> Result<Self, String> {
+            let charset;
+            unsafe {
+                let charset_ptr = bindgen::FcCharSetCreate();
+                if charset_ptr.is_null() {
+                    return Err(String::from("Failed to create FcCharSet with str"));
+                }
+                charset = FcCharSetWr {
+                    charset: charset_ptr,
+                };
+                for c in s.chars() {
+                    if bindgen::FcCharSetAddChar(charset.charset, c as u32)
+                        == bindgen::FcFalse as bindgen::FcBool
+                    {
+                        return Err(String::from("Failed to add chars from str into FcCharSet"));
+                    }
+                }
+            }
+
+            Ok(charset)
+        }
+
+        pub fn new_with_char(c: char) -> Result<Self, String> {
+            let charset;
+            unsafe {
+                let charset_ptr = bindgen::FcCharSetCreate();
+                if charset_ptr.is_null() {
+                    return Err(String::from("Failed to create FcCharSet with char"));
+                }
+                charset = FcCharSetWr {
+                    charset: charset_ptr,
+                };
+                if bindgen::FcCharSetAddChar(charset.charset, c as u32)
+                    == bindgen::FcFalse as bindgen::FcBool
+                {
+                    return Err(String::from("Failed to add char to FcCharSet"));
+                }
+            }
+
+            Ok(charset)
+        }
+
+        pub fn get(&mut self) -> *mut bindgen::FcCharSet {
+            self.charset
+        }
+    }
+
+    pub struct FcPatternWr {
+        pattern: *mut bindgen::FcPattern,
+    }
+
+    impl Drop for FcPatternWr {
+        fn drop(&mut self) {
+            if !self.pattern.is_null() {
+                unsafe {
+                    bindgen::FcPatternDestroy(self.pattern);
+                }
+            }
+        }
+    }
+
+    impl FcPatternWr {
+        pub fn new_with_charset(c: &mut FcCharSetWr) -> Result<Self, String> {
+            let pattern;
+            unsafe {
+                let pattern_ptr = bindgen::FcPatternCreate();
+                if pattern_ptr.is_null() {
+                    return Err(String::from("Failed to FcPatternCreate"));
+                }
+                pattern = Self {
+                    pattern: pattern_ptr,
+                };
+                bindgen::FcDefaultSubstitute(pattern.pattern);
+
+                let value = bindgen::FcValue {
+                    type_: bindgen::_FcType_FcTypeCharSet,
+                    u: bindgen::_FcValue__bindgen_ty_1 { c: c.get() },
+                };
+
+                if bindgen::FcPatternAdd(
+                    pattern.pattern,
+                    bindgen::FC_CHARSET as *const _ as *const i8,
+                    value,
+                    bindgen::FcTrue as bindgen::FcBool,
+                ) == bindgen::FcFalse as bindgen::FcBool
+                {
+                    return Err(String::from("Failed to add FcCharSet to new Pattern"));
+                }
+            }
+
+            Ok(pattern)
+        }
+
+        pub fn get(&mut self) -> *mut bindgen::FcPattern {
+            self.pattern
+        }
+
+        pub fn get_count(&self) -> c_int {
+            unsafe { bindgen::FcPatternObjectCount(self.pattern) }
+        }
+
+        pub fn filter_to_filenames(&self) -> Result<Self, String> {
+            let pattern;
+            unsafe {
+                let mut file_object_set_filter = FcObjectSetWr::new_file_object_set()?;
+                let pattern_ptr =
+                    bindgen::FcPatternFilter(self.pattern, file_object_set_filter.get());
+                if pattern_ptr.is_null() {
+                    return Err(String::from("Failed to FcPatternFilter"));
+                }
+                pattern = Self {
+                    pattern: pattern_ptr,
+                };
+            }
+
+            Ok(pattern)
+        }
+
+        pub fn get_filename_contents(&self) -> Result<Vec<String>, String> {
+            let mut vec: Vec<String> = Vec::new();
+            let count = self.get_count();
+            unsafe {
+                let mut value = bindgen::FcValue {
+                    type_: 0,
+                    u: bindgen::_FcValue__bindgen_ty_1 { i: 0 },
+                };
+
+                for i in 0..count {
+                    if bindgen::FcPatternGet(
+                        self.pattern,
+                        bindgen::FC_FILE as *const _ as *const i8,
+                        i,
+                        &mut value as *mut bindgen::FcValue,
+                    ) == bindgen::_FcResult_FcResultMatch
+                    {
+                        if value.type_ == bindgen::_FcType_FcTypeString {
+                            let cs = CStr::from_ptr(value.u.s as *const i8);
+                            vec.push(
+                                cs.to_str()
+                                    .map_err(|_| String::from("Failed to convert CStr to String"))?
+                                    .to_owned(),
+                            );
+                        }
+                    }
+                }
+            }
+
+            Ok(vec)
+        }
+    }
+
+    struct FcObjectSetWr {
+        object_set: *mut bindgen::FcObjectSet,
+    }
+
+    impl Drop for FcObjectSetWr {
+        fn drop(&mut self) {
+            unsafe {
+                if !self.object_set.is_null() {
+                    bindgen::FcObjectSetDestroy(self.object_set);
+                }
+            }
+        }
+    }
+
+    impl FcObjectSetWr {
+        pub fn new_file_object_set() -> Result<Self, String> {
+            let object_set;
+            unsafe {
+                let object_set_ptr = bindgen::FcObjectSetCreate();
+                if object_set_ptr.is_null() {
+                    return Err(String::from("Failed to FcObjectSetCreate"));
+                }
+
+                object_set = Self {
+                    object_set: object_set_ptr,
+                };
+
+                if bindgen::FcObjectSetAdd(
+                    object_set.object_set,
+                    bindgen::FC_FILE as *const _ as *const i8,
+                ) == bindgen::FcFalse as bindgen::FcBool
+                {
+                    return Err(String::from(
+                        "Failed to add \"FC_FILE\" with FcObjectSetAdd",
+                    ));
+                }
+            }
+
+            Ok(object_set)
+        }
+
+        pub fn get(&mut self) -> *mut bindgen::FcObjectSet {
+            self.object_set
+        }
+    }
+}
+
+pub fn get_matching_font_from_str(s: &str) -> Result<PathBuf, String> {
+    let mut config = ffi::FcConfigWr::new()?;
+    let mut charset = ffi::FcCharSetWr::new_with_str(s)?;
+    let mut search_pattern = ffi::FcPatternWr::new_with_charset(&mut charset)?;
+    if !config.apply_pattern_to_config(&mut search_pattern) {
+        return Err(String::from("Failed to apply_pattern_to_config"));
+    }
+    let result_pattern = config.font_match(&mut search_pattern)?;
+    let filtered_pattern = result_pattern.filter_to_filenames()?;
+    let result_vec = filtered_pattern.get_filename_contents()?;
+
+    if result_vec.is_empty() {
+        Err(String::from(
+            "Empty result_vec for get_matching_font_from_str",
+        ))
+    } else {
+        PathBuf::from_str(&result_vec[0]).map_err(|e| e.to_string())
+    }
+}
+
+pub fn get_matching_font_from_char(c: char) -> Result<PathBuf, String> {
+    let mut config = ffi::FcConfigWr::new()?;
+    let mut charset = ffi::FcCharSetWr::new_with_char(c)?;
+    let mut search_pattern = ffi::FcPatternWr::new_with_charset(&mut charset)?;
+    if !config.apply_pattern_to_config(&mut search_pattern) {
+        return Err(String::from("Failed to apply_pattern_to_config"));
+    }
+    let result_pattern = config.font_match(&mut search_pattern)?;
+    let filtered_pattern = result_pattern.filter_to_filenames()?;
+    let result_vec = filtered_pattern.get_filename_contents()?;
+
+    if result_vec.is_empty() {
+        Err(String::from(
+            "Empty result_vec for get_matching_font_from_char",
+        ))
+    } else {
+        PathBuf::from_str(&result_vec[0]).map_err(|e| e.to_string())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_ascii_fetching() {
+        let fetched_path =
+            get_matching_font_from_char('a').expect("Should be able to find match for 'a'");
+        println!("{:?}", fetched_path);
+    }
 }