+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)]
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);
+ }
}