implement list_fonts::list_families

A list of families is returned. Each variant contains the variant's
style, the filepath for the variant, and the index of the variant in the
file. This info should be enough to get freetype to actually load a
font.
This commit is contained in:
Joe Wilm 2016-02-21 17:46:49 -08:00
parent 2a7dc1deb8
commit 32bac94343
1 changed files with 119 additions and 22 deletions

View File

@ -1,16 +1,24 @@
use std::ffi::CStr;
use std::ffi::{CStr, CString};
use std::path::PathBuf;
use std::ptr;
use std::str;
use std::str::from_utf8;
use libc::{c_char, c_int};
use fontconfig::fontconfig::{FcConfigGetCurrent, FcConfigGetFonts, FcSetSystem};
use fontconfig::fontconfig::{FcPatternGetString};
use fontconfig::fontconfig::{FcResultMatch};
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};
pub fn list_font_names() -> Vec<String> {
let mut fonts = Vec::new();
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
@ -22,36 +30,125 @@ pub fn list_font_names() -> Vec<String> {
for i in 0..nfont {
let font = (*font_set).fonts.offset(i); // *mut FcPattern
let id = 0 as c_int;
let mut fullname: *mut FcChar8 = ptr::null_mut();
let mut family: *mut FcChar8 = ptr::null_mut();
let mut format: *mut FcChar8 = ptr::null_mut();
// The second parameter here (fullname) is from the "FONT PROPERTIES" table:
// https://www.freedesktop.org/software/fontconfig/fontconfig-devel/x19.html
let result = FcPatternGetString(*font,
b"fullname\0".as_ptr() as *mut c_char,
b"fontformat\0".as_ptr() as *mut c_char,
id,
&mut fullname);
&mut format);
if result != FcResultMatch {
continue;
}
let s = str::from_utf8(CStr::from_ptr(fullname as *const c_char).to_bytes())
.unwrap().to_owned();
fonts.push(s);
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);
}
}
}
fonts
families.sort();
families.dedup();
families
}
#[derive(Debug)]
pub struct Variant {
style: String,
file: PathBuf,
index: usize,
}
#[derive(Debug)]
pub struct Family {
name: String,
variants: Vec<Variant>,
}
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 usize,
});
}
FcFontSetDestroy(variants);
FcPatternDestroy(pattern);
FcObjectSetDestroy(object_set);
}
Family {
name: family,
variants: members
}
}
pub fn get_font_families() -> Vec<Family> {
list_families().into_iter()
.map(|family| get_family_info(family))
.collect()
}
#[cfg(test)]
mod tests {
use super::list_font_names;
#[test]
fn list_fonts() {
let fonts = list_font_names();
assert!(!fonts.is_empty());
println!("fonts: {:?}", fonts);
fn get_font_families() {
let families = super::get_font_families();
assert!(!families.is_empty());
}
}