use std::collections::HashMap; use std::ffi::{CStr, CString}; use std::fmt; use std::path::PathBuf; use std::ptr; use std::str::from_utf8; use libc::{c_char, c_int}; use fontconfig::fontconfig::{FcConfigGetCurrent, FcConfigGetFonts, FcSetSystem}; use fontconfig::fontconfig::{FcPatternGetString, FcPatternCreate, FcPatternAddString}; use fontconfig::fontconfig::{FcPatternGetInteger}; use fontconfig::fontconfig::{FcObjectSetCreate, FcObjectSetAdd}; use fontconfig::fontconfig::{FcResultMatch, FcFontSetList}; use fontconfig::fontconfig::{FcChar8}; use fontconfig::fontconfig::{FcFontSetDestroy, FcPatternDestroy, FcObjectSetDestroy}; unsafe fn fc_char8_to_string(fc_str: *mut FcChar8) -> String { from_utf8(CStr::from_ptr(fc_str as *const c_char).to_bytes()).unwrap().to_owned() } fn list_families() -> Vec { let mut families = Vec::new(); unsafe { // https://www.freedesktop.org/software/fontconfig/fontconfig-devel/fcconfiggetcurrent.html let config = FcConfigGetCurrent(); // *mut FcConfig // https://www.freedesktop.org/software/fontconfig/fontconfig-devel/fcconfiggetfonts.html let font_set = FcConfigGetFonts(config, FcSetSystem); // *mut FcFontSet let nfont = (*font_set).nfont as isize; for i in 0..nfont { let font = (*font_set).fonts.offset(i); // *mut FcPattern let id = 0 as c_int; let mut family: *mut FcChar8 = ptr::null_mut(); let mut format: *mut FcChar8 = ptr::null_mut(); let result = FcPatternGetString(*font, b"fontformat\0".as_ptr() as *mut c_char, id, &mut format); if result != FcResultMatch { continue; } let format = fc_char8_to_string(format); if format != "TrueType" && format != "CFF" { continue } let mut id = 0; while FcPatternGetString(*font, b"family\0".as_ptr() as *mut c_char, id, &mut family) == FcResultMatch { let safe_family = fc_char8_to_string(family); id += 1; families.push(safe_family); } } } families.sort(); families.dedup(); families } #[derive(Debug)] pub struct Variant { style: String, file: PathBuf, index: isize, } impl Variant { #[inline] pub fn path(&self) -> &::std::path::Path { self.file.as_path() } #[inline] pub fn index(&self) -> isize { self.index } } #[derive(Debug)] pub struct Family { name: String, variants: HashMap, } impl fmt::Display for Family { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { try!(write!(f, "{}: ", self.name)); for (k, _v) in &self.variants { try!(write!(f, "{}, ", k)); } Ok(()) } } impl Family { #[inline] pub fn variants(&self) -> &HashMap { &self.variants } } static FILE: &'static [u8] = b"file\0"; static FAMILY: &'static [u8] = b"family\0"; static INDEX: &'static [u8] = b"index\0"; static STYLE: &'static [u8] = b"style\0"; pub fn get_family_info(family: String) -> Family { let mut members = Vec::new(); unsafe { let config = FcConfigGetCurrent(); // *mut FcConfig let mut font_set = FcConfigGetFonts(config, FcSetSystem); // *mut FcFontSet let pattern = FcPatternCreate(); let family_name = CString::new(&family[..]).unwrap(); let family_name = family_name.as_ptr(); // Add family name to pattern. Use this for searching. FcPatternAddString(pattern, FAMILY.as_ptr() as *mut c_char, family_name as *mut FcChar8); // Request filename, style, and index for each variant in family let object_set = FcObjectSetCreate(); // *mut FcObjectSet FcObjectSetAdd(object_set, FILE.as_ptr() as *mut c_char); FcObjectSetAdd(object_set, INDEX.as_ptr() as *mut c_char); FcObjectSetAdd(object_set, STYLE.as_ptr() as *mut c_char); let variants = FcFontSetList(config, &mut font_set, 1 /* nsets */, pattern, object_set); let num_variant = (*variants).nfont as isize; for i in 0..num_variant { let font = (*variants).fonts.offset(i); let mut file: *mut FcChar8 = ptr::null_mut(); assert_eq!(FcPatternGetString(*font, FILE.as_ptr() as *mut c_char, 0, &mut file), FcResultMatch); let file = fc_char8_to_string(file); let mut style: *mut FcChar8 = ptr::null_mut(); assert_eq!(FcPatternGetString(*font, STYLE.as_ptr() as *mut c_char, 0, &mut style), FcResultMatch); let style = fc_char8_to_string(style); let mut index = 0 as c_int; assert_eq!(FcPatternGetInteger(*font, INDEX.as_ptr() as *mut c_char, 0, &mut index), FcResultMatch); members.push(Variant { style: style, file: PathBuf::from(file), index: index as isize, }); } FcFontSetDestroy(variants); FcPatternDestroy(pattern); FcObjectSetDestroy(object_set); } Family { name: family, variants: members.into_iter().map(|v| (v.style.clone(), v)).collect() } } pub fn get_font_families() -> HashMap { list_families().into_iter() .map(|family| (family.clone(), get_family_info(family))) .collect() } #[cfg(test)] mod tests { #[test] fn get_font_families() { let families = super::get_font_families(); assert!(!families.is_empty()); } }