summaryrefslogtreecommitdiff
path: root/font/src/ft/list_fonts.rs
diff options
context:
space:
mode:
Diffstat (limited to 'font/src/ft/list_fonts.rs')
-rw-r--r--font/src/ft/list_fonts.rs186
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());
+ }
+}