#![deny(missing_docs)]
use fontconfig_sys as sys;
use fontconfig_sys::ffi_dispatch;
#[cfg(feature = "dlopen")]
use sys::statics::{LIB, LIB_RESULT};
#[cfg(not(feature = "dlopen"))]
use sys::*;
use std::ffi::{c_int, CStr, CString};
use std::marker::PhantomData;
use std::mem;
use std::os::raw::c_char;
use std::path::PathBuf;
use std::ptr;
use std::str::FromStr;
pub use sys::constants::*;
use sys::{FcBool, FcPattern};
#[allow(non_upper_case_globals)]
const FcTrue: FcBool = 1;
#[allow(non_upper_case_globals, dead_code)]
const FcFalse: FcBool = 0;
pub struct Fontconfig {
_initialised: (),
}
#[derive(Debug)]
pub struct UnknownFontFormat(pub String);
#[derive(Eq, PartialEq)]
#[allow(missing_docs)]
pub enum FontFormat {
TrueType,
Type1,
BDF,
PCF,
Type42,
CIDType1,
CFF,
PFR,
WindowsFNT,
}
impl Fontconfig {
pub fn new() -> Option<Self> {
#[cfg(feature = "dlopen")]
if LIB_RESULT.is_err() {
return None;
}
if unsafe { ffi_dispatch!(LIB, FcInit,) == FcTrue } {
Some(Fontconfig { _initialised: () })
} else {
None
}
}
pub fn find(&self, family: &str, style: Option<&str>) -> Option<Font> {
Font::find(self, family, style)
}
}
pub struct Font {
pub name: String,
pub path: PathBuf,
pub index: Option<i32>,
}
impl Font {
fn find(fc: &Fontconfig, family: &str, style: Option<&str>) -> Option<Font> {
let mut pat = Pattern::new(fc);
let family = CString::new(family).ok()?;
pat.add_string(FC_FAMILY, &family);
if let Some(style) = style {
let style = CString::new(style).ok()?;
pat.add_string(FC_STYLE, &style);
}
let font_match = pat.font_match();
font_match.name().and_then(|name| {
font_match.filename().map(|filename| Font {
name: name.to_owned(),
path: PathBuf::from(filename),
index: font_match.face_index(),
})
})
}
#[allow(dead_code)]
fn print_debug(&self) {
println!(
"Name: {}\nPath: {}\nIndex: {:?}",
self.name,
self.path.display(),
self.index
);
}
}
#[repr(C)]
pub struct Pattern<'fc> {
pat: *mut FcPattern,
fc: &'fc Fontconfig,
}
impl<'fc> Pattern<'fc> {
pub fn new(fc: &Fontconfig) -> Pattern {
let pat = unsafe { ffi_dispatch!(LIB, FcPatternCreate,) };
assert!(!pat.is_null());
Pattern { pat, fc }
}
pub unsafe fn from_pattern(fc: &Fontconfig, pat: *mut FcPattern) -> Pattern {
ffi_dispatch!(LIB, FcPatternReference, pat);
Pattern { pat, fc }
}
pub fn add_string(&mut self, name: &CStr, val: &CStr) {
unsafe {
ffi_dispatch!(
LIB,
FcPatternAddString,
self.pat,
name.as_ptr(),
val.as_ptr() as *const u8
);
}
}
pub fn add_integer(&mut self, name: &CStr, val: c_int) {
unsafe {
ffi_dispatch!(LIB, FcPatternAddInteger, self.pat, name.as_ptr(), val);
}
}
pub fn get_string<'a>(&'a self, name: &'a CStr) -> Option<&'a str> {
unsafe {
let mut ret: *mut sys::FcChar8 = ptr::null_mut();
if ffi_dispatch!(
LIB,
FcPatternGetString,
self.pat,
name.as_ptr(),
0,
&mut ret as *mut _
) == sys::FcResultMatch
{
let cstr = CStr::from_ptr(ret as *const c_char);
Some(cstr.to_str().unwrap())
} else {
None
}
}
}
pub fn get_int(&self, name: &CStr) -> Option<i32> {
unsafe {
let mut ret: i32 = 0;
if ffi_dispatch!(
LIB,
FcPatternGetInteger,
self.pat,
name.as_ptr(),
0,
&mut ret as *mut i32
) == sys::FcResultMatch
{
Some(ret)
} else {
None
}
}
}
pub fn print(&self) {
unsafe {
ffi_dispatch!(LIB, FcPatternPrint, &*self.pat);
}
}
pub fn default_substitute(&mut self) {
unsafe {
ffi_dispatch!(LIB, FcDefaultSubstitute, self.pat);
}
}
pub fn config_substitute(&mut self) {
unsafe {
ffi_dispatch!(
LIB,
FcConfigSubstitute,
ptr::null_mut(),
self.pat,
sys::FcMatchPattern
);
}
}
pub fn font_match(&mut self) -> Pattern {
self.config_substitute();
self.default_substitute();
unsafe {
let mut res = sys::FcResultNoMatch;
Pattern::from_pattern(
self.fc,
ffi_dispatch!(LIB, FcFontMatch, ptr::null_mut(), self.pat, &mut res),
)
}
}
pub fn sort_fonts(&mut self, trim: bool) -> FontSet<'fc> {
self.config_substitute();
self.default_substitute();
let mut res = sys::FcResultNoMatch;
let unicode_coverage = ptr::null_mut();
let config = ptr::null_mut();
unsafe {
let raw_set = ffi_dispatch!(
LIB,
FcFontSort,
config,
self.pat,
trim as FcBool,
unicode_coverage,
&mut res
);
FontSet::from_raw(self.fc, raw_set)
}
}
pub fn name(&self) -> Option<&str> {
self.get_string(FC_FULLNAME)
}
pub fn filename(&self) -> Option<&str> {
self.get_string(FC_FILE)
}
pub fn face_index(&self) -> Option<i32> {
self.get_int(FC_INDEX)
}
pub fn slant(&self) -> Option<i32> {
self.get_int(FC_SLANT)
}
pub fn weight(&self) -> Option<i32> {
self.get_int(FC_WEIGHT)
}
pub fn width(&self) -> Option<i32> {
self.get_int(FC_WIDTH)
}
pub fn format(&self) -> Result<FontFormat, UnknownFontFormat> {
self.get_string(FC_FONTFORMAT)
.ok_or_else(|| UnknownFontFormat(String::new()))
.and_then(|format| format.parse())
}
pub fn as_ptr(&self) -> *const FcPattern {
self.pat
}
pub fn as_mut_ptr(&mut self) -> *mut FcPattern {
self.pat
}
}
impl<'fc> std::fmt::Debug for Pattern<'fc> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let fcstr = unsafe { ffi_dispatch!(LIB, FcNameUnparse, self.pat) };
let fcstr = unsafe { CStr::from_ptr(fcstr as *const c_char) };
let result = write!(f, "{:?}", fcstr);
unsafe { ffi_dispatch!(LIB, FcStrFree, fcstr.as_ptr() as *mut u8) };
result
}
}
impl<'fc> Clone for Pattern<'fc> {
fn clone(&self) -> Self {
let clone = unsafe { ffi_dispatch!(LIB, FcPatternDuplicate, self.pat) };
Pattern {
pat: clone,
fc: self.fc,
}
}
}
impl<'fc> Drop for Pattern<'fc> {
fn drop(&mut self) {
unsafe {
ffi_dispatch!(LIB, FcPatternDestroy, self.pat);
}
}
}
impl Pattern<'_> {
pub fn lang_set(&self) -> Option<StrList<'_>> {
unsafe {
let mut ret: *mut sys::FcLangSet = ptr::null_mut();
if ffi_dispatch!(
LIB,
FcPatternGetLangSet,
self.pat,
FC_LANG.as_ptr(),
0,
&mut ret as *mut _
) == sys::FcResultMatch
{
let ss: *mut sys::FcStrSet = ffi_dispatch!(LIB, FcLangSetGetLangs, ret);
let lang_strs: *mut sys::FcStrList = ffi_dispatch!(LIB, FcStrListCreate, ss);
Some(StrList::from_raw(self.fc, lang_strs))
} else {
None
}
}
}
}
pub struct StrList<'a> {
list: *mut sys::FcStrList,
_life: PhantomData<&'a sys::FcStrList>,
}
impl<'a> StrList<'a> {
unsafe fn from_raw(_: &Fontconfig, raw_list: *mut sys::FcStrSet) -> Self {
Self {
list: raw_list,
_life: PhantomData,
}
}
}
impl<'a> Drop for StrList<'a> {
fn drop(&mut self) {
unsafe { ffi_dispatch!(LIB, FcStrListDone, self.list) };
}
}
impl<'a> Iterator for StrList<'a> {
type Item = &'a str;
fn next(&mut self) -> Option<&'a str> {
let lang_str: *mut sys::FcChar8 = unsafe { ffi_dispatch!(LIB, FcStrListNext, self.list) };
if lang_str.is_null() {
None
} else {
match unsafe { CStr::from_ptr(lang_str as *const c_char) }.to_str() {
Ok(s) => Some(s),
_ => self.next(),
}
}
}
}
pub struct FontSet<'fc> {
fcset: *mut sys::FcFontSet,
fc: &'fc Fontconfig,
}
impl<'fc> FontSet<'fc> {
pub fn new(fc: &Fontconfig) -> FontSet {
let fcset = unsafe { ffi_dispatch!(LIB, FcFontSetCreate,) };
FontSet { fcset, fc }
}
pub unsafe fn from_raw(fc: &Fontconfig, raw_set: *mut sys::FcFontSet) -> FontSet {
FontSet { fcset: raw_set, fc }
}
pub fn add_pattern(&mut self, pat: Pattern) {
unsafe {
ffi_dispatch!(LIB, FcFontSetAdd, self.fcset, pat.pat);
mem::forget(pat);
}
}
pub fn print(&self) {
unsafe { ffi_dispatch!(LIB, FcFontSetPrint, self.fcset) };
}
pub fn iter(&self) -> impl Iterator<Item = Pattern<'_>> {
let patterns = unsafe {
let fontset = self.fcset;
std::slice::from_raw_parts((*fontset).fonts, (*fontset).nfont as usize)
};
patterns
.iter()
.map(move |&pat| unsafe { Pattern::from_pattern(self.fc, pat) })
}
}
impl<'fc> Drop for FontSet<'fc> {
fn drop(&mut self) {
unsafe { ffi_dispatch!(LIB, FcFontSetDestroy, self.fcset) }
}
}
pub fn list_fonts<'fc>(pattern: &Pattern<'fc>, objects: Option<&ObjectSet>) -> FontSet<'fc> {
let os = objects.map(|o| o.fcset).unwrap_or(ptr::null_mut());
unsafe {
let raw_set = ffi_dispatch!(LIB, FcFontList, ptr::null_mut(), pattern.pat, os);
FontSet::from_raw(pattern.fc, raw_set)
}
}
pub struct ObjectSet {
fcset: *mut sys::FcObjectSet,
}
impl ObjectSet {
pub fn new(_: &Fontconfig) -> ObjectSet {
let fcset = unsafe { ffi_dispatch!(LIB, FcObjectSetCreate,) };
assert!(!fcset.is_null());
ObjectSet { fcset }
}
pub unsafe fn from_raw(_: &Fontconfig, raw_set: *mut sys::FcObjectSet) -> ObjectSet {
ObjectSet { fcset: raw_set }
}
pub fn add(&mut self, name: &CStr) {
let res = unsafe { ffi_dispatch!(LIB, FcObjectSetAdd, self.fcset, name.as_ptr()) };
assert_eq!(res, FcTrue);
}
}
impl Drop for ObjectSet {
fn drop(&mut self) {
unsafe { ffi_dispatch!(LIB, FcObjectSetDestroy, self.fcset) }
}
}
impl FromStr for FontFormat {
type Err = UnknownFontFormat;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"TrueType" => Ok(FontFormat::TrueType),
"Type 1" => Ok(FontFormat::Type1),
"BDF" => Ok(FontFormat::BDF),
"PCF" => Ok(FontFormat::PCF),
"Type 42" => Ok(FontFormat::Type42),
"CID Type 1" => Ok(FontFormat::CIDType1),
"CFF" => Ok(FontFormat::CFF),
"PFR" => Ok(FontFormat::PFR),
"Windows FNT" => Ok(FontFormat::WindowsFNT),
_ => Err(UnknownFontFormat(s.to_string())),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
assert!(Fontconfig::new().is_some())
}
#[test]
fn test_find_font() {
let fc = Fontconfig::new().unwrap();
fc.find("dejavu sans", None).unwrap().print_debug();
fc.find("dejavu sans", Some("oblique"))
.unwrap()
.print_debug();
}
#[test]
fn test_iter_and_print() {
let fc = Fontconfig::new().unwrap();
let fontset = list_fonts(&Pattern::new(&fc), None);
for pattern in fontset.iter() {
println!("{:?}", pattern.name());
}
assert!(fontset.iter().count() > 0);
}
#[test]
fn iter_lang_set() {
let fc = Fontconfig::new().unwrap();
let mut pat = Pattern::new(&fc);
let family = CString::new("dejavu sans").unwrap();
pat.add_string(FC_FAMILY, &family);
let pattern = pat.font_match();
for lang in pattern.lang_set().unwrap() {
println!("{:?}", lang);
}
assert!(pattern
.lang_set()
.unwrap()
.find(|&lang| lang == "za")
.is_some());
let langs = pattern.lang_set().unwrap().collect::<Vec<_>>();
assert!(langs.iter().find(|&&l| l == "ie").is_some());
}
#[test]
fn test_sort_fonts() {
let fc = Fontconfig::new().unwrap();
let mut pat = Pattern::new(&fc);
let family = CString::new("dejavu sans").unwrap();
pat.add_string(FC_FAMILY, &family);
let style = CString::new("oblique").unwrap();
pat.add_string(FC_STYLE, &style);
let font_set = pat.sort_fonts(false);
for pattern in font_set.iter() {
println!("{:?}", pattern.name());
}
assert!(font_set.iter().count() > 0);
}
}