Skip to main content

fontconfig/
lib.rs

1#![deny(missing_docs)]
2
3//! A wrapper around [freedesktop.org's fontconfig library][homepage], for locating fonts on a UNIX like systems such as Linux and FreeBSD. Requires fontconfig to be installed.
4//!
5//! See the [fontconfig developer reference][1] for more information.
6//!
7//! [1]: http://www.freedesktop.org/software/fontconfig/fontconfig-devel/t1.html
8//!
9//! Dependencies
10//! ============
11//!
12//! * Arch Linux: `fontconfig`
13//! * Debian-based systems: `libfontconfig1-dev`
14//! * FreeBSD: `fontconfig`
15//! * Void Linux: `fontconfig-devel`
16//!
17//! Usage
18//! =====
19//!
20//! Cargo.toml:
21//!
22//! ```toml
23//! [dependencies]
24//! fontconfig = "0.1.0"
25//! ```
26//!
27//! main.rs:
28//!
29//! ```
30//! use fontconfig::{Font, Fontconfig};
31//!
32//! fn main() {
33//!     let fc = Fontconfig::new().unwrap();
34//!     // `Fontconfig::find()` returns `Option` (will rarely be `None` but still could be)
35//!     let font = fc.find("freeserif", None).unwrap();
36//!     // `name` is a `String`, `path` is a `Path`
37//!     println!("Name: {}\nPath: {}", font.name, font.path.display());
38//! }
39//! ```
40
41extern crate fontconfig_sys;
42
43use crate::fontconfig_sys::fontconfig as sys;
44
45use std::ffi::{CStr, CString};
46use std::mem;
47use std::path::PathBuf;
48use std::ptr;
49use std::str::FromStr;
50
51pub use sys::constants::*;
52use sys::{FcBool, FcPattern};
53
54#[allow(non_upper_case_globals)]
55const FcTrue: FcBool = 1;
56#[allow(non_upper_case_globals, dead_code)]
57const FcFalse: FcBool = 0;
58
59/// Handle obtained after Fontconfig has been initialised.
60pub struct Fontconfig {
61    _initialised: (),
62}
63
64/// Error type returned from Pattern::format.
65///
66/// The error holds the name of the unknown format.
67#[derive(Debug)]
68pub struct UnknownFontFormat(pub String);
69
70/// The format of a font matched by Fontconfig.
71#[derive(Eq, PartialEq)]
72#[allow(missing_docs)]
73pub enum FontFormat {
74    TrueType,
75    Type1,
76    BDF,
77    PCF,
78    Type42,
79    CIDType1,
80    CFF,
81    PFR,
82    WindowsFNT,
83}
84
85impl Fontconfig {
86    /// Initialise Fontconfig and return a handle allowing further interaction with the API.
87    ///
88    /// If Fontconfig fails to initialise, returns `None`.
89    pub fn new() -> Option<Self> {
90        if unsafe { sys::FcInit() == FcTrue } {
91            Some(Fontconfig { _initialised: () })
92        } else {
93            None
94        }
95    }
96
97    /// Find a font of the given `family` (e.g. Dejavu Sans, FreeSerif),
98    /// optionally filtering by `style`. Both fields are case-insensitive.
99    pub fn find(&self, family: &str, style: Option<&str>) -> Option<Font> {
100        Font::find(self, family, style)
101    }
102}
103
104/// A very high-level view of a font, only concerned with the name and its file location.
105///
106/// ##Example
107/// ```rust
108/// use fontconfig::{Font, Fontconfig};
109///
110/// let fc = Fontconfig::new().unwrap();
111/// let font = fc.find("sans-serif", Some("italic")).unwrap();
112/// println!("Name: {}\nPath: {}", font.name, font.path.display());
113/// ```
114pub struct Font {
115    /// The true name of this font
116    pub name: String,
117    /// The location of this font on the filesystem.
118    pub path: PathBuf,
119}
120
121impl Font {
122    fn find(fc: &Fontconfig, family: &str, style: Option<&str>) -> Option<Font> {
123        let mut pat = Pattern::new(fc);
124        let family = CString::new(family).ok()?;
125        pat.add_string(FC_FAMILY.as_cstr(), &family);
126
127        if let Some(style) = style {
128            let style = CString::new(style).ok()?;
129            pat.add_string(FC_STYLE.as_cstr(), &style);
130        }
131
132        let font_match = pat.font_match();
133
134        font_match.name().and_then(|name| {
135            font_match.filename().map(|filename| Font {
136                name: name.to_owned(),
137                path: PathBuf::from(filename),
138            })
139        })
140    }
141
142    #[allow(dead_code)]
143    fn print_debug(&self) {
144        println!("Name: {}\nPath: {}", self.name, self.path.display());
145    }
146}
147
148/// A safe wrapper around fontconfig's `FcPattern`.
149#[repr(C)]
150pub struct Pattern<'fc> {
151    /// Raw pointer to `FcPattern`
152    pub pat: *mut FcPattern,
153    fc: &'fc Fontconfig,
154}
155
156impl<'fc> Pattern<'fc> {
157    /// Create a new `Pattern`.
158    pub fn new(fc: &Fontconfig) -> Pattern {
159        let pat = unsafe { sys::FcPatternCreate() };
160        assert!(!pat.is_null());
161
162        Pattern { pat, fc }
163    }
164
165    /// Create a `Pattern` from a raw fontconfig FcPattern pointer. The pattern is referenced.
166    pub fn from_pattern(fc: &Fontconfig, pat: *mut FcPattern) -> Pattern {
167        unsafe {
168            sys::FcPatternReference(pat);
169        }
170
171        Pattern { pat, fc }
172    }
173
174    /// Add a key-value pair to this pattern.
175    ///
176    /// See useful keys in the [fontconfig reference][1].
177    ///
178    /// [1]: http://www.freedesktop.org/software/fontconfig/fontconfig-devel/x19.html
179    pub fn add_string(&mut self, name: &CStr, val: &CStr) {
180        unsafe {
181            sys::FcPatternAddString(self.pat, name.as_ptr(), val.as_ptr() as *const u8);
182        }
183    }
184
185    /// Get string the value for a key from this pattern.
186    pub fn get_string<'a>(&'a self, name: &'a CStr) -> Option<&'a str> {
187        unsafe {
188            let mut ret: *mut sys::FcChar8 = ptr::null_mut();
189            if sys::FcPatternGetString(self.pat, name.as_ptr(), 0, &mut ret as *mut _)
190                == sys::FcResultMatch
191            {
192                let cstr = CStr::from_ptr(ret as *const i8);
193                Some(cstr.to_str().unwrap())
194            } else {
195                None
196            }
197        }
198    }
199
200    /// Get the integer value for a key from this pattern.
201    pub fn get_int(&self, name: &CStr) -> Option<i32> {
202        unsafe {
203            let mut ret: i32 = 0;
204            if sys::FcPatternGetInteger(self.pat, name.as_ptr(), 0, &mut ret as *mut i32)
205                == sys::FcResultMatch
206            {
207                Some(ret)
208            } else {
209                None
210            }
211        }
212    }
213
214    /// Print this pattern to stdout with all its values.
215    pub fn print(&self) {
216        unsafe {
217            sys::FcPatternPrint(&*self.pat);
218        }
219    }
220
221    fn default_substitute(&mut self) {
222        unsafe {
223            sys::FcDefaultSubstitute(self.pat);
224        }
225    }
226
227    fn config_substitute(&mut self) {
228        unsafe {
229            sys::FcConfigSubstitute(ptr::null_mut(), self.pat, sys::FcMatchPattern);
230        }
231    }
232
233    /// Get the best available match for this pattern, returned as a new pattern.
234    pub fn font_match(&mut self) -> Pattern {
235        self.default_substitute();
236        self.config_substitute();
237
238        unsafe {
239            let mut res = sys::FcResultNoMatch;
240            Pattern::from_pattern(
241                self.fc,
242                sys::FcFontMatch(ptr::null_mut(), self.pat, &mut res),
243            )
244        }
245    }
246
247    /// Get the "fullname" (human-readable name) of this pattern.
248    pub fn name(&self) -> Option<&str> {
249        self.get_string(FC_FULLNAME.as_cstr())
250    }
251
252    /// Get the "file" (path on the filesystem) of this font pattern.
253    pub fn filename(&self) -> Option<&str> {
254        self.get_string(FC_FILE.as_cstr())
255    }
256
257    /// Get the "index" (The index of the font within the file) of this pattern.
258    pub fn face_index(&self) -> Option<i32> {
259        self.get_int(FC_INDEX.as_cstr())
260    }
261
262    /// Get the "slant" (Italic, oblique or roman) of this pattern.
263    pub fn slant(&self) -> Option<i32> {
264        self.get_int(FC_SLANT.as_cstr())
265    }
266
267    /// Get the "weight" (Light, medium, demibold, bold or black) of this pattern.
268    pub fn weight(&self) -> Option<i32> {
269        self.get_int(FC_WEIGHT.as_cstr())
270    }
271
272    /// Get the "width" (Condensed, normal or expanded) of this pattern.
273    pub fn width(&self) -> Option<i32> {
274        self.get_int(FC_WIDTH.as_cstr())
275    }
276
277    /// Get the "fontformat" ("TrueType" "Type 1" "BDF" "PCF" "Type 42" "CID Type 1" "CFF" "PFR" "Windows FNT") of this pattern.
278    pub fn format(&self) -> Result<FontFormat, UnknownFontFormat> {
279        self.get_string(FC_FONTFORMAT.as_cstr())
280            .ok_or_else(|| UnknownFontFormat(String::new()))
281            .and_then(|format| format.parse())
282    }
283}
284
285impl<'fc> std::fmt::Debug for Pattern<'fc> {
286    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
287        let fcstr = unsafe { sys::FcNameUnparse(self.pat) };
288        let fcstr = unsafe { CStr::from_ptr(fcstr as *const i8) };
289        let result = write!(f, "{:?}", fcstr);
290        unsafe { sys::FcStrFree(fcstr.as_ptr() as *mut u8) };
291        result
292    }
293}
294
295impl<'fc> Clone for Pattern<'fc> {
296    fn clone(&self) -> Self {
297        let clone = unsafe { sys::FcPatternDuplicate(self.pat) };
298        Pattern {
299            pat: clone,
300            fc: self.fc,
301        }
302    }
303}
304
305impl<'fc> Drop for Pattern<'fc> {
306    fn drop(&mut self) {
307        unsafe {
308            sys::FcPatternDestroy(self.pat);
309        }
310    }
311}
312
313/// Wrapper around `FcFontSet`.
314pub struct FontSet<'fc> {
315    fcset: *mut sys::FcFontSet,
316    fc: &'fc Fontconfig,
317}
318
319impl<'fc> FontSet<'fc> {
320    /// Create a new, empty `FontSet`.
321    pub fn new(fc: &Fontconfig) -> FontSet {
322        let fcset = unsafe { sys::FcFontSetCreate() };
323        FontSet { fcset, fc }
324    }
325
326    /// Wrap an existing `FcFontSet`.
327    ///
328    /// This returned wrapper assumes ownership of the `FcFontSet`.
329    pub fn from_raw(fc: &Fontconfig, raw_set: *mut sys::FcFontSet) -> FontSet {
330        FontSet { fcset: raw_set, fc }
331    }
332
333    /// Add a `Pattern` to this `FontSet`.
334    pub fn add_pattern(&mut self, pat: Pattern) {
335        unsafe {
336            sys::FcFontSetAdd(self.fcset, pat.pat);
337            mem::forget(pat);
338        }
339    }
340
341    /// Print this `FontSet` to stdout.
342    pub fn print(&self) {
343        unsafe { sys::FcFontSetPrint(self.fcset) };
344    }
345
346    /// Iterate the fonts (as `Patterns`) in this `FontSet`.
347    pub fn iter(&self) -> impl Iterator<Item = Pattern<'_>> {
348        let patterns = unsafe {
349            let fontset = self.fcset;
350            std::slice::from_raw_parts((*fontset).fonts, (*fontset).nfont as usize)
351        };
352        patterns
353            .iter()
354            .map(move |&pat| Pattern::from_pattern(self.fc, pat))
355    }
356}
357
358impl<'fc> Drop for FontSet<'fc> {
359    fn drop(&mut self) {
360        unsafe { sys::FcFontSetDestroy(self.fcset) }
361    }
362}
363
364/// Return a `FontSet` containing Fonts that match the supplied `pattern` and `objects`.
365pub fn list_fonts<'fc>(pattern: &Pattern<'fc>, objects: Option<&ObjectSet>) -> FontSet<'fc> {
366    let os = objects.map(|o| o.fcset).unwrap_or(ptr::null_mut());
367    let ptr = unsafe { sys::FcFontList(ptr::null_mut(), pattern.pat, os) };
368    FontSet::from_raw(pattern.fc, ptr)
369}
370
371/// Wrapper around `FcObjectSet`.
372pub struct ObjectSet {
373    fcset: *mut sys::FcObjectSet,
374}
375
376impl ObjectSet {
377    /// Create a new, empty `ObjectSet`.
378    pub fn new(_: &Fontconfig) -> ObjectSet {
379        let fcset = unsafe { sys::FcObjectSetCreate() };
380        assert!(!fcset.is_null());
381
382        ObjectSet { fcset }
383    }
384
385    /// Wrap an existing `FcObjectSet`.
386    ///
387    /// The `FcObjectSet` must not be null. This method assumes ownership of the `FcObjectSet`.
388    pub fn from_raw(_: &Fontconfig, raw_set: *mut sys::FcObjectSet) -> ObjectSet {
389        assert!(!raw_set.is_null());
390        ObjectSet { fcset: raw_set }
391    }
392
393    /// Add a string to the `ObjectSet`.
394    pub fn add(&mut self, name: &CStr) {
395        let res = unsafe { sys::FcObjectSetAdd(self.fcset, name.as_ptr()) };
396        assert_eq!(res, FcTrue);
397    }
398}
399
400impl Drop for ObjectSet {
401    fn drop(&mut self) {
402        unsafe { sys::FcObjectSetDestroy(self.fcset) }
403    }
404}
405
406impl FromStr for FontFormat {
407    type Err = UnknownFontFormat;
408
409    fn from_str(s: &str) -> Result<Self, Self::Err> {
410        match s {
411            "TrueType" => Ok(FontFormat::TrueType),
412            "Type 1" => Ok(FontFormat::Type1),
413            "BDF" => Ok(FontFormat::BDF),
414            "PCF" => Ok(FontFormat::PCF),
415            "Type 42" => Ok(FontFormat::Type42),
416            "CID Type 1" => Ok(FontFormat::CIDType1),
417            "CFF" => Ok(FontFormat::CFF),
418            "PFR" => Ok(FontFormat::PFR),
419            "Windows FNT" => Ok(FontFormat::WindowsFNT),
420            _ => Err(UnknownFontFormat(s.to_string())),
421        }
422    }
423}
424
425#[cfg(test)]
426mod tests {
427    use super::*;
428
429    #[test]
430    fn it_works() {
431        assert!(Fontconfig::new().is_some())
432    }
433
434    #[test]
435    fn test_find_font() {
436        let fc = Fontconfig::new().unwrap();
437        fc.find("dejavu sans", None).unwrap().print_debug();
438        fc.find("dejavu sans", Some("oblique"))
439            .unwrap()
440            .print_debug();
441    }
442
443    #[test]
444    fn test_iter_and_print() {
445        let fc = Fontconfig::new().unwrap();
446        let fontset = list_fonts(&Pattern::new(&fc), None);
447        for pattern in fontset.iter() {
448            println!("{:?}", pattern.name());
449        }
450
451        // Ensure that the set can be iterated again
452        assert!(fontset.iter().count() > 0);
453    }
454}