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)] #![allow(non_snake_case)] #![allow(dead_code)] #![allow(deref_nullptr)] #![allow(clippy::redundant_static_lifetimes)] 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 { let config = unsafe { bindgen::FcInitLoadConfigAndFonts() }; if config.is_null() { Err(String::from("Failed to create FcConfig")) } else { Ok(Self { config }) } } #[allow(dead_code)] 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 { 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 { #[allow(dead_code)] pub fn new_with_str(s: &str) -> Result { 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 { 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 { 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 { 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, String> { let mut vec: Vec = 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 && 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 { 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 } } } #[allow(dead_code)] pub fn get_matching_font_from_str(s: &str) -> Result { 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 { 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); } }