diff options
Diffstat (limited to 'font/src/ft/list_fonts.rs')
-rw-r--r-- | font/src/ft/list_fonts.rs | 186 |
1 files changed, 186 insertions, 0 deletions
diff --git a/font/src/ft/list_fonts.rs b/font/src/ft/list_fonts.rs new file mode 100644 index 00000000..f171f57e --- /dev/null +++ b/font/src/ft/list_fonts.rs @@ -0,0 +1,186 @@ +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<String> { + 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<String, Variant>, +} + +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<String, Variant> { + &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<String, Family> { + 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()); + } +} |