#![deny(missing_docs)]
pub use fontconfig_sys as sys;
use sys::ffi_dispatch;
#[cfg(feature = "dlopen")]
use sys::statics::{LIB, LIB_RESULT};
#[cfg(not(feature = "dlopen"))]
use sys::*;
use std::path::PathBuf;
use std::ptr::{self, NonNull};
use std::str::FromStr;
use std::sync::{Arc, Mutex};
pub use sys::constants::*;
use sys::FcBool;
pub mod blanks;
pub mod charset;
pub mod fontset;
pub mod langset;
pub mod matrix;
pub mod objectset;
pub mod pattern;
pub mod strings;
pub mod stringset;
#[allow(deprecated)]
pub use blanks::Blanks;
pub use charset::{CharSet, OwnedCharSet};
pub use fontset::FontSet;
pub use langset::{LangSet, LangSetCmp};
pub use matrix::Matrix;
pub use objectset::ObjectSet;
pub use pattern::{
properties, properties::Property, properties::PropertyType, OwnedPattern, Pattern,
};
pub use strings::FcStr;
pub use stringset::StringSet;
#[allow(non_upper_case_globals)]
const FcTrue: FcBool = 1;
#[allow(non_upper_case_globals, dead_code)]
const FcFalse: FcBool = 0;
type Result<T> = std::result::Result<T, Error>;
static INITIALIZED: once_cell::sync::Lazy<Arc<Mutex<usize>>> =
once_cell::sync::Lazy::new(|| Arc::new(Mutex::new(0)));
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("Unknown format {0}")]
UnknownFontFormat(String),
#[error("malloc failed")]
OutOfMemory,
#[error("Object exists, but has fewer values than specified")]
NoId,
#[error("Object exists, but the type doesn't match")]
TypeMismatch,
#[error("Object doesn't exist at all")]
NoMatch,
}
trait ToResult {
fn ok(&self) -> Result<()>;
fn opt(&self) -> Option<()> {
self.ok().ok()
}
}
impl ToResult for sys::FcResult {
fn ok(&self) -> Result<()> {
match *self {
sys::FcResultMatch => Ok(()),
sys::FcResultNoMatch => Err(Error::NoMatch),
sys::FcResultTypeMismatch => Err(Error::TypeMismatch),
sys::FcResultNoId => Err(Error::NoId),
_ => unreachable!(),
}
}
}
pub enum MatchKind {
Pattern,
Font,
Scan,
}
#[doc(hidden)]
impl From<sys::FcMatchKind> for MatchKind {
fn from(kind: sys::FcMatchKind) -> Self {
match kind {
sys::FcMatchPattern => MatchKind::Pattern,
sys::FcMatchFont => MatchKind::Font,
sys::FcMatchScan => MatchKind::Scan,
_ => unreachable!(),
}
}
}
#[doc(hidden)]
impl From<MatchKind> for sys::FcMatchKind {
fn from(kind: MatchKind) -> sys::FcMatchKind {
match kind {
MatchKind::Pattern => sys::FcMatchPattern,
MatchKind::Font => sys::FcMatchFont,
MatchKind::Scan => sys::FcMatchScan,
}
}
}
#[derive(Eq, PartialEq)]
#[allow(missing_docs)]
pub enum FontFormat {
TrueType,
Type1,
BDF,
PCF,
Type42,
CIDType1,
CFF,
PFR,
WindowsFNT,
}
#[doc(alias = "FcConfig")]
pub struct FontConfig {
cfg: Option<NonNull<sys::FcConfig>>,
}
impl FontConfig {
pub fn substitute(&mut self, pat: &mut Pattern, kind: MatchKind) {
let ret = unsafe {
ffi_dispatch!(
LIB,
FcConfigSubstitute,
self.as_mut_ptr(),
pat.as_mut_ptr(),
kind.into()
)
};
assert_eq!(ret, FcTrue);
}
#[doc(alias = "FcFontSetMatch")]
pub fn fontset_match(&mut self, sets: &mut [FontSet], pat: &mut Pattern) -> OwnedPattern {
let mut result = sys::FcResultNoMatch;
let pat = unsafe {
ffi_dispatch!(
LIB,
FcFontSetMatch,
self.as_mut_ptr(),
&mut sets.as_mut_ptr().cast(),
sets.len() as i32,
pat.as_mut_ptr(),
&mut result
)
};
result.ok().unwrap();
OwnedPattern {
pat: NonNull::new(pat).unwrap(),
}
}
fn as_mut_ptr(&mut self) -> *mut sys::FcConfig {
if let Some(ref mut cfg) = self.cfg {
cfg.as_ptr()
} else {
ptr::null_mut()
}
}
#[allow(dead_code)]
fn as_ptr(&self) -> *const sys::FcConfig {
if let Some(ref cfg) = self.cfg {
cfg.as_ptr()
} else {
ptr::null_mut()
}
}
pub fn find(&mut self, family: String, style: Option<String>) -> Option<Font> {
let mut pat = OwnedPattern::new();
pat.add(&properties::FC_FAMILY, family);
if let Some(style) = style {
let style = style;
pat.add(&properties::FC_STYLE, style);
}
let font_match = pat.font_match(self);
font_match.name().and_then(|name| {
font_match.filename().map(|filename| Font {
name: name.to_owned(),
path: PathBuf::from(filename),
})
})
}
}
impl Default for FontConfig {
fn default() -> Self {
let mut guard = INITIALIZED.lock().unwrap();
#[cfg(feature = "dlopen")]
if LIB_RESULT.is_err() {
panic!("Failed to load fontconfig library");
}
assert_eq!(FcTrue, unsafe { ffi_dispatch!(LIB, FcInit,) });
*guard += 1;
FontConfig { cfg: None }
}
}
impl Drop for FontConfig {
fn drop(&mut self) {
let guard = INITIALIZED.lock().unwrap();
if guard.checked_sub(1).unwrap_or_default() == 0 {
unsafe { ffi_dispatch!(LIB, FcFini,) };
}
}
}
pub struct Font {
pub name: String,
pub path: PathBuf,
}
impl Font {
#[allow(dead_code)]
fn print_debug(&self) {
println!("Name: {}\nPath: {}", self.name, self.path.display());
}
}
impl FromStr for FontFormat {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
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(Error::UnknownFontFormat(s.to_string())),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Version {
pub major: u32,
pub minor: u32,
pub revision: u32,
}
impl std::fmt::Display for Version {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}.{}.{}", self.major, self.minor, self.revision)
}
}
pub fn version() -> Version {
let version = unsafe { ffi_dispatch!(LIB, FcGetVersion,) as u32 };
let major = version / 10000;
let version = version % 10000;
let minor = version / 100;
let revision = version % 100;
Version {
major,
minor,
revision,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
FontConfig::default();
}
#[test]
fn find_font() {
let mut config = FontConfig::default();
config
.find("dejavu sans".to_string(), None)
.unwrap()
.print_debug();
config
.find("dejavu sans".to_string(), Some("oblique".to_string()))
.unwrap()
.print_debug();
}
#[test]
fn iter_and_print() {
let mut config = FontConfig::default();
let pat = OwnedPattern::new();
let fontset = pat.font_list(&mut config, None);
for pattern in fontset.iter() {
println!("{:?}", pattern.name());
}
assert!(fontset.iter().count() > 0);
}
#[test]
fn iter_lang_set() {
let mut config = FontConfig::default();
let mut pat = OwnedPattern::new();
let family = "dejavu sans".to_string();
pat.add(&properties::FC_FAMILY, family);
let pattern = pat.font_match(&mut config);
for lang in pattern.lang_set().unwrap().langs().iter() {
println!("{:?}", lang);
}
assert!(pattern
.lang_set()
.unwrap()
.langs()
.iter()
.any(|lang| lang == "za"));
let langs = pattern
.lang_set()
.unwrap()
.langs()
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>();
assert!(langs.iter().any(|l| l == "ie"));
}
#[test]
fn iter_font_sort() {
let mut config = FontConfig::default();
let mut pat = OwnedPattern::new();
let family = "dejavu sans".to_owned();
pat.add(&properties::FC_FAMILY, family);
pat.default_substitute();
config.substitute(&mut pat, MatchKind::Pattern);
let font_set = pat.font_sort(&mut config, false).unwrap();
for font in font_set.iter() {
println!("{:?}", font.name());
}
assert!(font_set.iter().count() > 1);
assert!(font_set.iter().next().unwrap().name().unwrap() == "DejaVu Sans");
}
}