From 44c6171bc0ce461697ef568a0b1134f02cb4c9aa Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Fri, 30 Dec 2016 00:34:57 -0500 Subject: [PATCH] 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. --- font/Cargo.lock | 25 ++ font/src/ft/list_fonts.rs | 481 +++++++++++++++++++++++++++++--------- 2 files changed, 394 insertions(+), 112 deletions(-) diff --git a/font/Cargo.lock b/font/Cargo.lock index 5862234..5dde9e4 100644 --- a/font/Cargo.lock +++ b/font/Cargo.lock @@ -191,3 +191,28 @@ name = "winapi-build" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[metadata] +"checksum bitflags 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "72cd7314bd4ee024071241147222c706e80385a1605ac7d4cd2fcc339da2ae46" +"checksum core-foundation 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "20a6d0448d3a99d977ae4a2aa5a98d886a923e863e81ad9ff814645b6feb3bbd" +"checksum core-foundation-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "05eed248dc504a5391c63794fe4fb64f46f071280afaa1b73308f3c0ce4574c5" +"checksum core-graphics 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0c56c6022ba22aedbaa7d231be545778becbe1c7aceda4c82ba2f2084dd4c723" +"checksum core-text 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "94d4f3fab9e0242a648728764ac50e322b61eeb28c2d26d483721fe392cb2878" +"checksum euclid 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)" = "c7b555729225fcc2aabc1ac951f9346967b35c901f4f03a480c31b6a45824109" +"checksum expat-sys 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4ccf6f838594c1571f176f0afdbeb9cfa9f83b478f269d3f0390939b1df4323e" +"checksum freetype-rs 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1e8c93a141b156862ab58d0206fa44a9b20d899c86c3e6260017ab748029aa42" +"checksum freetype-sys 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "eccfb6d96cac99921f0c2142a91765f6c219868a2c45bdfe7d65a08775f18127" +"checksum gcc 0.3.28 (registry+https://github.com/rust-lang/crates.io-index)" = "3da3a2cbaeb01363c8e3704fd9fd0eb2ceb17c6f27abd4c1ef040fb57d20dc79" +"checksum heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "abb306abb8d398e053cfb1b3e7b72c2f580be048b85745c52652954f8ad1439c" +"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +"checksum libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "c96061f0c8a2dc27482e394d82e23073569de41d73cd736672ccd3e5c7471bfd" +"checksum libz-sys 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "c9795a8a0498b3abab873f8f063816fcc2e002388e89df87da065628dd5a8ed2" +"checksum log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ab83497bf8bf4ed2a74259c1c802351fcd67a65baa86394b6ba73c36f4838054" +"checksum make-cmd 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a8ca8afbe8af1785e09636acb5a41e08a765f5f0340568716c18a8700ba3c0d3" +"checksum num-traits 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "51eab148f171aefad295f8cece636fc488b9b392ef544da31ea4b8ef6b9e9c39" +"checksum pkg-config 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8cee804ecc7eaf201a4a207241472cc870e825206f6c031e3ee2a72fa425f2fa" +"checksum rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)" = "6159e4e6e559c81bd706afe9c8fd68f547d3e851ce12e76b1de7914bab61691b" +"checksum serde 0.7.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b76133a8a02f1c6ebd3fb9a2ecaab3d54302565a51320e80931adba571aadb1b" +"checksum servo-fontconfig 0.2.0 (git+https://github.com/jwilm/rust-fontconfig)" = "" +"checksum servo-fontconfig-sys 2.11.3 (git+https://github.com/jwilm/libfontconfig)" = "" +"checksum winapi 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "3969e500d618a5e974917ddefd0ba152e4bcaae5eb5d9b8c1fbc008e9e28c24e" +"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" diff --git a/font/src/ft/list_fonts.rs b/font/src/ft/list_fonts.rs index 4479450..86ee970 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 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}; + use libc::{c_char, c_int}; + use fontconfig::fontconfig as ffi; -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() + 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, + } + + 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 _) + } + } + } + )* + } + } + + /// FcPattern + pub struct Pattern(*mut FcPattern); + + /// 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() + }) + } + } + + + 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"); + } + + #[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 { + 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 { + 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 { + 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 { 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); + 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(); + let config = fc::Config::get_current(); + let font_set = config.get_fonts(fc::SetName::System); - unsafe { - let config = FcConfigGetCurrent(); // *mut FcConfig - let mut font_set = FcConfigGetFonts(config, FcSetSystem); // *mut FcFontSet + let mut pattern = fc::Pattern::new(); + pattern.add_family(&family); - let pattern = FcPatternCreate(); - let family_name = CString::new(&family[..]).unwrap(); - let family_name = family_name.as_ptr(); + let mut objects = fc::ObjectSet::new(); + objects.add_file(); + objects.add_index(); + objects.add_style(); - // 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 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 { - 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)]