diff --git a/build.rs b/build.rs index 458302e..923299d 100644 --- 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() { diff --git a/src/unicode_support/fontconfig.rs b/src/unicode_support/fontconfig.rs index 74de4e8..613821d 100644 --- a/src/unicode_support/fontconfig.rs +++ b/src/unicode_support/fontconfig.rs @@ -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 { + 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 { + 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 { + 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 + { + 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 { + 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 { + 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); + } }