187 lines
5.7 KiB
Rust
187 lines
5.7 KiB
Rust
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());
|
|
}
|
|
}
|