diff options
author | Joe Wilm <joe@jwilm.com> | 2016-12-30 00:34:57 -0500 |
---|---|---|
committer | Joe Wilm <joe@jwilm.com> | 2016-12-30 00:34:57 -0500 |
commit | 44c6171bc0ce461697ef568a0b1134f02cb4c9aa (patch) | |
tree | 7bd8bde7b617eec573e1895b5f2b464f8ef6dd83 /font/src | |
parent | 72ff775b234644113487f5af8fc358d07bdc3178 (diff) | |
download | r-alacritty-44c6171bc0ce461697ef568a0b1134f02cb4c9aa.tar.gz r-alacritty-44c6171bc0ce461697ef568a0b1134f02cb4c9aa.tar.bz2 r-alacritty-44c6171bc0ce461697ef568a0b1134f02cb4c9aa.zip |
Refactor FontConfig wrappers
There's now a proper wrapper in place for working with the FontConfig
library. This should help significantly with error handling with font
loading; at least, the FontConfig code shouldn't panic. The FreeType
rasterizer still needs to be updated to handle missing fonts, and a more
sensible default font should be specified.
Diffstat (limited to 'font/src')
-rw-r--r-- | font/src/ft/list_fonts.rs | 485 |
1 files changed, 371 insertions, 114 deletions
diff --git a/font/src/ft/list_fonts.rs b/font/src/ft/list_fonts.rs index 44794501..86ee9708 100644 --- a/font/src/ft/list_fonts.rs +++ b/font/src/ft/list_fonts.rs @@ -13,66 +13,360 @@ // limitations under the License. // 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}; +mod fc { + use std::ptr; + use std::ffi::{CStr, CString}; + use std::str; + use std::ops::{Deref, DerefMut}; + + use libc::{c_char, c_int}; + use fontconfig::fontconfig as ffi; + + use self::ffi::{FcConfigGetCurrent, FcConfigGetFonts}; + use self::ffi::{FcPatternGetString, FcPatternCreate, FcPatternAddString}; + use self::ffi::{FcPatternGetInteger}; + use self::ffi::{FcObjectSetCreate, FcObjectSetAdd}; + use self::ffi::{FcResultMatch, FcFontSetList}; + use self::ffi::{FcChar8, FcConfig, FcPattern, FcFontSet, FcObjectSet}; + use self::ffi::{FcFontSetDestroy, FcPatternDestroy, FcObjectSetDestroy, FcConfigDestroy}; + + /// FcConfig - Font Configuration + pub struct Config(*mut FcConfig); + + /// FcFontSet + pub struct FontSet(*mut FcFontSet); + + /// FcFontSet reference + pub struct FontSetRef(*mut FcFontSet); + + /// Iterator over a font set + pub struct FontSetIter<'a> { + font_set: &'a FontSetRef, + num_fonts: usize, + current: usize, + } -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}; + macro_rules! ref_type { + ($($owned:ty => $refty:ty),*) => { + $( + impl Deref for $owned { + type Target = $refty; + fn deref(&self) -> &Self::Target { + unsafe { + &*(self.0 as *mut _) + } + } + } + + impl DerefMut for $owned { + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { + &mut *(self.0 as *mut _) + } + } + } + )* + } + } -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() -} + /// FcPattern + pub struct Pattern(*mut FcPattern); -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; + /// FcObjectSet + pub struct ObjectSet(*mut FcObjectSet); + + /// FcObjectSet reference + pub struct ObjectSetRef(*mut FcObjectSet); + + ref_type! { + ObjectSet => ObjectSetRef, + Pattern => PatternRef, + FontSet => FontSetRef + } + + impl Drop for ObjectSet { + fn drop(&mut self) { + unsafe { + FcObjectSetDestroy(self.0); } + } + } + + impl ObjectSet { + pub fn new() -> ObjectSet { + ObjectSet(unsafe { + FcObjectSetCreate() + }) + } + } - let format = fc_char8_to_string(format); - if format != "TrueType" && format != "CFF" { - continue + impl ObjectSetRef { + fn add(&mut self, property: &[u8]) { + unsafe { + FcObjectSetAdd(self.0, property.as_ptr() as *mut c_char); } + } + + #[inline] + pub fn add_file(&mut self) { + self.add(b"file\0"); + } + + #[inline] + pub fn add_index(&mut self) { + self.add(b"index\0"); + } - 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); + #[inline] + pub fn add_style(&mut self) { + self.add(b"style\0"); + } + } + + macro_rules! pattern_add_string { + ($name:ident => $object:expr) => { + #[inline] + pub fn $name(&mut self, value: &str) -> bool { + unsafe { + self.add_string($object, value) + } + } + } + } + + impl Pattern { + pub fn new() -> Pattern { + Pattern(unsafe { FcPatternCreate() }) + } + } + + impl Drop for Pattern { + fn drop(&mut self) { + unsafe { + FcPatternDestroy(self.0); + } + } + } + + /// FcPattern reference + pub struct PatternRef(*mut FcPattern); + + /// Available font sets + pub enum SetName { + System = 0, + Application = 1, + } + + pub unsafe fn char8_to_string(fc_str: *mut FcChar8) -> String { + str::from_utf8(CStr::from_ptr(fc_str as *const c_char).to_bytes()).unwrap().to_owned() + } + + macro_rules! pattern_get_string { + ($($method:ident() => $property:expr),+) => { + $( + pub fn $method(&self, id: isize) -> Option<String> { + unsafe { + let mut format: *mut FcChar8 = ptr::null_mut(); + + let result = FcPatternGetString( + self.0, + $property.as_ptr() as *mut c_char, + id as c_int, + &mut format + ); + + if result == FcResultMatch { + Some(char8_to_string(format)) + } else { + None + } + } + } + )+ + }; + } + + macro_rules! pattern_get_integer { + ($($method:ident() => $property:expr),+) => { + $( + pub fn $method(&self, id: isize) -> Option<isize> { + let mut index = 0 as c_int; + unsafe { + let result = FcPatternGetInteger( + self.0, + $property.as_ptr() as *mut c_char, + id as c_int, + &mut index + ); + + if result == FcResultMatch { + Some(index as isize) + } else { + None + } + } + } + )+ + }; + } + + impl PatternRef { + /// Add a string value to the pattern + /// + /// If the returned value is `true`, the value is added at the end of + /// any existing list, otherwise it is inserted at the beginning. + /// + /// # Unsafety + /// + /// `object` is not checked to be a valid null-terminated string + unsafe fn add_string(&mut self, object: &[u8], value: &str) -> bool { + let value = CString::new(&value[..]).unwrap(); + let value = value.as_ptr(); + + FcPatternAddString( + self.0, + object.as_ptr() as *mut c_char, + value as *mut FcChar8 + ) == 1 + } + + pattern_add_string! { + add_family => b"family\0" + } + + pattern_get_string! { + fontformat() => b"fontformat\0", + family() => b"family\0", + file() => b"file\0", + style() => b"style\0" + } + + pattern_get_integer! { + index() => b"index\0" + } + } + + impl<'a> IntoIterator for &'a FontSet { + type Item = &'a PatternRef; + type IntoIter = FontSetIter<'a>; + fn into_iter(self) -> FontSetIter<'a> { + let num_fonts = unsafe { + (*self.0).nfont as isize + }; + + FontSetIter { + font_set: unsafe { &*(self.0 as *mut _) }, + num_fonts: num_fonts as _, + current: 0, + } + } + } + + impl<'a> IntoIterator for &'a FontSetRef { + type Item = &'a PatternRef; + type IntoIter = FontSetIter<'a>; + fn into_iter(self) -> FontSetIter<'a> { + let num_fonts = unsafe { + (*self.0).nfont as isize + }; + + FontSetIter { + font_set: self, + num_fonts: num_fonts as _, + current: 0, + } + } + } + + impl<'a> Iterator for FontSetIter<'a> { + type Item = &'a PatternRef; + + fn next(&mut self) -> Option<Self::Item> { + if self.current == self.num_fonts { + None + } else { + let pattern = unsafe { + let ptr = *(*self.font_set.0).fonts.offset(self.current as isize); + &*(ptr as *mut _) + }; + + self.current += 1; + Some(pattern) + } + } + } + + impl FontSet { + pub fn list( + config: &Config, + source: &mut FontSetRef, + pattern: &PatternRef, + objects: &ObjectSetRef + ) -> FontSet { + let raw = unsafe { + FcFontSetList( + config.0, + &mut source.0, + 1 /* nsets */, + pattern.0, + objects.0 + ) + }; + FontSet(raw) + } + } + + impl Config { + /// Get the current configuration + pub fn get_current() -> Config { + Config(unsafe { FcConfigGetCurrent() }) + } + + /// Returns one of the two sets of fonts from the configuration as + /// specified by `set`. + pub fn get_fonts<'a>(&'a self, set: SetName) -> &'a FontSetRef { + unsafe { + let ptr = FcConfigGetFonts(self.0, set as u32); + &*(ptr as *mut _) + } + } + } + + impl Drop for FontSet { + fn drop(&mut self) { + unsafe { + FcFontSetDestroy(self.0) + } + } + } + + impl Drop for Config { + fn drop(&mut self) { + unsafe { + if self.0 != FcConfigGetCurrent() { + FcConfigDestroy(self.0) + } + } + } + } +} + +fn list_families() -> Vec<String> { + let mut families = Vec::new(); + + let config = fc::Config::get_current(); + let font_set = config.get_fonts(fc::SetName::System); + for font in font_set { + if let Some(format) = font.fontformat(0) { + if format == "TrueType" || format == "CFF" { + let id = 0; + while let Some(family) = font.family(id) { + families.push(family); + } } } } @@ -125,71 +419,33 @@ impl Family { } } -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"; - +#[allow(mutable_transmutes)] 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, - }); + let config = fc::Config::get_current(); + let font_set = config.get_fonts(fc::SetName::System); + + let mut pattern = fc::Pattern::new(); + pattern.add_family(&family); + + let mut objects = fc::ObjectSet::new(); + objects.add_file(); + objects.add_index(); + objects.add_style(); + + let variants = fc::FontSet::list(&config, unsafe { ::std::mem::transmute(font_set) }, &pattern, &objects); + for variant in &variants { + if let Some(file) = variant.file(0) { + if let Some(style) = variant.style(0) { + if let Some(index) = variant.index(0) { + members.push(Variant { + style: style, + file: PathBuf::from(file), + index: index as isize, + }); + } + } } - - FcFontSetDestroy(variants); - FcPatternDestroy(pattern); - FcObjectSetDestroy(object_set); } Family { @@ -199,9 +455,10 @@ pub fn get_family_info(family: String) -> Family { } pub fn get_font_families() -> HashMap<String, Family> { - list_families().into_iter() - .map(|family| (family.clone(), get_family_info(family))) - .collect() + list_families() + .into_iter() + .map(|family| (family.clone(), get_family_info(family))) + .collect() } #[cfg(test)] |