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.
This commit is contained in:
Joe Wilm 2016-12-30 00:34:57 -05:00
parent 72ff775b23
commit 44c6171bc0
2 changed files with 394 additions and 112 deletions

25
font/Cargo.lock generated
View File

@ -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)" = "<none>"
"checksum servo-fontconfig-sys 2.11.3 (git+https://github.com/jwilm/libfontconfig)" = "<none>"
"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"

View File

@ -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<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();
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<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)]