font_kit/sources/
fontconfig.rs

1// font-kit/src/sources/fontconfig.rs
2//
3// Copyright © 2018 The Pathfinder Project Developers.
4//
5// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8// option. This file may not be copied, modified, or distributed
9// except according to those terms.
10
11//! A source that contains the fonts installed on the system, as reported by the Fontconfig
12//! library.
13//!
14//! On macOS and Windows, the Cargo feature `source-fontconfig` can be used to opt into fontconfig
15//! support. To prefer it over the native font source (only if you know what you're doing), use the
16//! `source-fontconfig-default` feature.
17
18use crate::error::SelectionError;
19use crate::family_handle::FamilyHandle;
20use crate::family_name::FamilyName;
21use crate::handle::Handle;
22use crate::properties::Properties;
23use crate::source::Source;
24use std::any::Any;
25
26/// A source that contains the fonts installed on the system, as reported by the Fontconfig
27/// library.
28///
29/// On macOS and Windows, the Cargo feature `source-fontconfig` can be used to opt into fontconfig
30/// support. To prefer it over the native font source (only if you know what you're doing), use the
31/// `source-fontconfig-default` feature.
32#[allow(missing_debug_implementations)]
33pub struct FontconfigSource {
34    config: fc::Config,
35}
36
37impl Default for FontconfigSource {
38    fn default() -> Self {
39        Self::new()
40    }
41}
42
43impl FontconfigSource {
44    /// Initializes Fontconfig and prepares it for queries.
45    pub fn new() -> FontconfigSource {
46        FontconfigSource {
47            config: fc::Config::new(),
48        }
49    }
50
51    /// Returns paths of all fonts installed on the system.
52    pub fn all_fonts(&self) -> Result<Vec<Handle>, SelectionError> {
53        let pattern = fc::Pattern::new();
54
55        // We want the family name.
56        let mut object_set = fc::ObjectSet::new();
57        object_set.push_string(fc::Object::File);
58        object_set.push_string(fc::Object::Index);
59
60        let patterns = pattern
61            .list(&self.config, object_set)
62            .map_err(|_| SelectionError::NotFound)?;
63
64        let mut handles = vec![];
65        for patt in patterns {
66            let path = match patt.get_string(fc::Object::File) {
67                Some(v) => v,
68                None => continue,
69            };
70
71            let index = match patt.get_integer(fc::Object::Index) {
72                Some(v) => v,
73                None => continue,
74            };
75
76            handles.push(Handle::Path {
77                path: path.into(),
78                font_index: index as u32,
79            });
80        }
81
82        if !handles.is_empty() {
83            Ok(handles)
84        } else {
85            Err(SelectionError::NotFound)
86        }
87    }
88
89    /// Returns the names of all families installed on the system.
90    pub fn all_families(&self) -> Result<Vec<String>, SelectionError> {
91        let pattern = fc::Pattern::new();
92
93        // We want the family name.
94        let mut object_set = fc::ObjectSet::new();
95        object_set.push_string(fc::Object::Family);
96
97        let patterns = pattern
98            .list(&self.config, object_set)
99            .map_err(|_| SelectionError::NotFound)?;
100
101        let mut result_families = vec![];
102        for patt in patterns {
103            if let Some(family) = patt.get_string(fc::Object::Family) {
104                result_families.push(family);
105            }
106        }
107
108        result_families.sort();
109        result_families.dedup();
110
111        if !result_families.is_empty() {
112            Ok(result_families)
113        } else {
114            Err(SelectionError::NotFound)
115        }
116    }
117
118    /// Looks up a font family by name and returns the handles of all the fonts in that family.
119    pub fn select_family_by_name(&self, family_name: &str) -> Result<FamilyHandle, SelectionError> {
120        use std::borrow::Cow;
121
122        let family_name = match family_name {
123            "serif" | "sans-serif" | "monospace" | "cursive" | "fantasy" => {
124                Cow::from(self.select_generic_font(family_name)?)
125            }
126            _ => Cow::from(family_name),
127        };
128
129        let pattern = fc::Pattern::from_name(family_name.as_ref());
130
131        let mut object_set = fc::ObjectSet::new();
132        object_set.push_string(fc::Object::File);
133        object_set.push_string(fc::Object::Index);
134
135        let patterns = pattern
136            .list(&self.config, object_set)
137            .map_err(|_| SelectionError::NotFound)?;
138
139        let mut handles = vec![];
140        for patt in patterns {
141            let font_path = patt.get_string(fc::Object::File).unwrap();
142            let font_index = patt.get_integer(fc::Object::Index).unwrap() as u32;
143            let handle = Handle::from_path(std::path::PathBuf::from(font_path), font_index);
144            handles.push(handle);
145        }
146
147        if !handles.is_empty() {
148            Ok(FamilyHandle::from_font_handles(handles.into_iter()))
149        } else {
150            Err(SelectionError::NotFound)
151        }
152    }
153
154    /// Selects a font by a generic name.
155    ///
156    /// Accepts: serif, sans-serif, monospace, cursive and fantasy.
157    fn select_generic_font(&self, name: &str) -> Result<String, SelectionError> {
158        let mut pattern = fc::Pattern::from_name(name);
159        pattern.config_substitute(fc::MatchKind::Pattern);
160        pattern.default_substitute();
161
162        let patterns = pattern
163            .sorted(&self.config)
164            .map_err(|_| SelectionError::NotFound)?;
165
166        if let Some(patt) = patterns.into_iter().next() {
167            if let Some(family) = patt.get_string(fc::Object::Family) {
168                return Ok(family);
169            }
170        }
171
172        Err(SelectionError::NotFound)
173    }
174
175    /// Selects a font by PostScript name, which should be a unique identifier.
176    ///
177    /// The default implementation, which is used by the DirectWrite and the filesystem backends,
178    /// does a brute-force search of installed fonts to find the one that matches.
179    pub fn select_by_postscript_name(
180        &self,
181        postscript_name: &str,
182    ) -> Result<Handle, SelectionError> {
183        let mut pattern = fc::Pattern::new();
184        pattern.push_string(fc::Object::PostScriptName, postscript_name.to_owned());
185
186        // We want the file path and the font index.
187        let mut object_set = fc::ObjectSet::new();
188        object_set.push_string(fc::Object::File);
189        object_set.push_string(fc::Object::Index);
190
191        let patterns = pattern
192            .list(&self.config, object_set)
193            .map_err(|_| SelectionError::NotFound)?;
194
195        if let Some(patt) = patterns.into_iter().next() {
196            let font_path = patt.get_string(fc::Object::File).unwrap();
197            let font_index = patt.get_integer(fc::Object::Index).unwrap() as u32;
198            let handle = Handle::from_path(std::path::PathBuf::from(font_path), font_index);
199            Ok(handle)
200        } else {
201            Err(SelectionError::NotFound)
202        }
203    }
204
205    /// Performs font matching according to the CSS Fonts Level 3 specification and returns the
206    /// handle.
207    #[inline]
208    pub fn select_best_match(
209        &self,
210        family_names: &[FamilyName],
211        properties: &Properties,
212    ) -> Result<Handle, SelectionError> {
213        <Self as Source>::select_best_match(self, family_names, properties)
214    }
215}
216
217impl Source for FontconfigSource {
218    #[inline]
219    fn all_fonts(&self) -> Result<Vec<Handle>, SelectionError> {
220        self.all_fonts()
221    }
222
223    #[inline]
224    fn all_families(&self) -> Result<Vec<String>, SelectionError> {
225        self.all_families()
226    }
227
228    #[inline]
229    fn select_family_by_name(&self, family_name: &str) -> Result<FamilyHandle, SelectionError> {
230        self.select_family_by_name(family_name)
231    }
232
233    #[inline]
234    fn select_by_postscript_name(&self, postscript_name: &str) -> Result<Handle, SelectionError> {
235        self.select_by_postscript_name(postscript_name)
236    }
237
238    #[inline]
239    fn as_any(&self) -> &dyn Any {
240        self
241    }
242
243    #[inline]
244    fn as_mut_any(&mut self) -> &mut dyn Any {
245        self
246    }
247}
248
249// A minimal fontconfig wrapper.
250mod fc {
251    #![allow(dead_code)]
252
253    use fontconfig_sys as ffi;
254    use fontconfig_sys::ffi_dispatch;
255
256    #[cfg(feature = "source-fontconfig-dlopen")]
257    use ffi::statics::LIB;
258    #[cfg(not(feature = "source-fontconfig-dlopen"))]
259    use ffi::*;
260
261    use std::ffi::{CStr, CString};
262    use std::os::raw::{c_char, c_uchar};
263    use std::ptr;
264
265    #[derive(Clone, Copy)]
266    pub enum Error {
267        NoMatch,
268        TypeMismatch,
269        NoId,
270        OutOfMemory,
271    }
272
273    #[derive(Clone, Copy)]
274    pub enum MatchKind {
275        Pattern,
276        Font,
277        Scan,
278    }
279
280    impl MatchKind {
281        fn to_u32(self) -> u32 {
282            match self {
283                MatchKind::Pattern => ffi::FcMatchPattern,
284                MatchKind::Font => ffi::FcMatchFont,
285                MatchKind::Scan => ffi::FcMatchScan,
286            }
287        }
288    }
289
290    // https://www.freedesktop.org/software/fontconfig/fontconfig-devel/x19.html
291    #[derive(Clone, Copy)]
292    pub enum Object {
293        Family,
294        File,
295        Index,
296        PostScriptName,
297    }
298
299    impl Object {
300        fn as_bytes(&self) -> &[u8] {
301            match self {
302                Object::Family => b"family\0",
303                Object::File => b"file\0",
304                Object::Index => b"index\0",
305                Object::PostScriptName => b"postscriptname\0",
306            }
307        }
308
309        fn as_ptr(&self) -> *const libc::c_char {
310            self.as_bytes().as_ptr() as *const libc::c_char
311        }
312    }
313
314    pub struct Config {
315        d: *mut ffi::FcConfig,
316    }
317
318    impl Config {
319        // FcInitLoadConfigAndFonts
320        pub fn new() -> Self {
321            unsafe {
322                Config {
323                    d: ffi_dispatch!(
324                        feature = "source-fontconfig-dlopen",
325                        LIB,
326                        FcInitLoadConfigAndFonts,
327                    ),
328                }
329            }
330        }
331    }
332
333    impl Drop for Config {
334        fn drop(&mut self) {
335            unsafe {
336                ffi_dispatch!(
337                    feature = "source-fontconfig-dlopen",
338                    LIB,
339                    FcConfigDestroy,
340                    self.d
341                );
342            }
343        }
344    }
345
346    pub struct Pattern {
347        d: *mut ffi::FcPattern,
348        c_strings: Vec<CString>,
349    }
350
351    impl Pattern {
352        fn from_ptr(d: *mut ffi::FcPattern) -> Self {
353            Pattern {
354                d,
355                c_strings: vec![],
356            }
357        }
358
359        // FcPatternCreate
360        pub fn new() -> Self {
361            unsafe {
362                Pattern::from_ptr(ffi_dispatch!(
363                    feature = "source-fontconfig-dlopen",
364                    LIB,
365                    FcPatternCreate,
366                ))
367            }
368        }
369
370        // FcNameParse
371        pub fn from_name(name: &str) -> Self {
372            let c_name = CString::new(name).unwrap();
373            unsafe {
374                Pattern::from_ptr(ffi_dispatch!(
375                    feature = "source-fontconfig-dlopen",
376                    LIB,
377                    FcNameParse,
378                    c_name.as_ptr() as *mut c_uchar
379                ))
380            }
381        }
382
383        // FcPatternAddString
384        pub fn push_string(&mut self, object: Object, value: String) {
385            unsafe {
386                let c_string = CString::new(value).unwrap();
387                ffi_dispatch!(
388                    feature = "source-fontconfig-dlopen",
389                    LIB,
390                    FcPatternAddString,
391                    self.d,
392                    object.as_ptr(),
393                    c_string.as_ptr() as *const c_uchar
394                );
395
396                // We have to keep this string, because `FcPattern` has a pointer to it now.
397                self.c_strings.push(c_string)
398            }
399        }
400
401        // FcConfigSubstitute
402        pub fn config_substitute(&mut self, match_kind: MatchKind) {
403            unsafe {
404                ffi_dispatch!(
405                    feature = "source-fontconfig-dlopen",
406                    LIB,
407                    FcConfigSubstitute,
408                    ptr::null_mut(),
409                    self.d,
410                    match_kind.to_u32()
411                );
412            }
413        }
414
415        // FcDefaultSubstitute
416        pub fn default_substitute(&mut self) {
417            unsafe {
418                ffi_dispatch!(
419                    feature = "source-fontconfig-dlopen",
420                    LIB,
421                    FcDefaultSubstitute,
422                    self.d
423                );
424            }
425        }
426
427        // FcFontSort
428        pub fn sorted(&self, config: &Config) -> Result<FontSet, Error> {
429            let mut res = ffi::FcResultMatch;
430            let d = unsafe {
431                ffi_dispatch!(
432                    feature = "source-fontconfig-dlopen",
433                    LIB,
434                    FcFontSort,
435                    config.d,
436                    self.d,
437                    1,
438                    ptr::null_mut(),
439                    &mut res
440                )
441            };
442
443            match res {
444                ffi::FcResultMatch => Ok(FontSet { d, idx: 0 }),
445                ffi::FcResultTypeMismatch => Err(Error::TypeMismatch),
446                ffi::FcResultNoId => Err(Error::NoId),
447                ffi::FcResultOutOfMemory => Err(Error::OutOfMemory),
448                _ => Err(Error::NoMatch),
449            }
450        }
451
452        // FcFontList
453        pub fn list(&self, config: &Config, set: ObjectSet) -> Result<FontSet, Error> {
454            let d = unsafe {
455                ffi_dispatch!(
456                    feature = "source-fontconfig-dlopen",
457                    LIB,
458                    FcFontList,
459                    config.d,
460                    self.d,
461                    set.d
462                )
463            };
464            if !d.is_null() {
465                Ok(FontSet { d, idx: 0 })
466            } else {
467                Err(Error::NoMatch)
468            }
469        }
470    }
471
472    impl Drop for Pattern {
473        #[inline]
474        fn drop(&mut self) {
475            unsafe {
476                ffi_dispatch!(
477                    feature = "source-fontconfig-dlopen",
478                    LIB,
479                    FcPatternDestroy,
480                    self.d
481                )
482            }
483        }
484    }
485
486    // A read-only `FcPattern` without a destructor.
487    pub struct PatternRef {
488        d: *mut ffi::FcPattern,
489    }
490
491    impl PatternRef {
492        // FcPatternGetString
493        pub fn get_string(&self, object: Object) -> Option<String> {
494            unsafe {
495                let mut string = ptr::null_mut();
496                let res = ffi_dispatch!(
497                    feature = "source-fontconfig-dlopen",
498                    LIB,
499                    FcPatternGetString,
500                    self.d,
501                    object.as_ptr(),
502                    0,
503                    &mut string
504                );
505                if res != ffi::FcResultMatch {
506                    return None;
507                }
508
509                if string.is_null() {
510                    return None;
511                }
512
513                CStr::from_ptr(string as *const c_char)
514                    .to_str()
515                    .ok()
516                    .map(|string| string.to_owned())
517            }
518        }
519
520        // FcPatternGetInteger
521        pub fn get_integer(&self, object: Object) -> Option<i32> {
522            unsafe {
523                let mut integer = 0;
524                let res = ffi_dispatch!(
525                    feature = "source-fontconfig-dlopen",
526                    LIB,
527                    FcPatternGetInteger,
528                    self.d,
529                    object.as_ptr(),
530                    0,
531                    &mut integer
532                );
533                if res != ffi::FcResultMatch {
534                    return None;
535                }
536
537                Some(integer)
538            }
539        }
540    }
541
542    pub struct FontSet {
543        d: *mut ffi::FcFontSet,
544        idx: usize,
545    }
546
547    impl FontSet {
548        pub fn is_empty(&self) -> bool {
549            self.len() == 0
550        }
551
552        pub fn len(&self) -> usize {
553            unsafe { (*self.d).nfont as usize }
554        }
555    }
556
557    impl Iterator for FontSet {
558        type Item = PatternRef;
559
560        fn next(&mut self) -> Option<Self::Item> {
561            if self.idx == self.len() {
562                return None;
563            }
564
565            let idx = self.idx;
566            self.idx += 1;
567
568            let d = unsafe { *(*self.d).fonts.add(idx) };
569            Some(PatternRef { d })
570        }
571
572        fn size_hint(&self) -> (usize, Option<usize>) {
573            (0, Some(self.len()))
574        }
575    }
576
577    impl Drop for FontSet {
578        fn drop(&mut self) {
579            unsafe {
580                ffi_dispatch!(
581                    feature = "source-fontconfig-dlopen",
582                    LIB,
583                    FcFontSetDestroy,
584                    self.d
585                )
586            }
587        }
588    }
589
590    pub struct ObjectSet {
591        d: *mut ffi::FcObjectSet,
592    }
593
594    impl ObjectSet {
595        // FcObjectSetCreate
596        pub fn new() -> Self {
597            unsafe {
598                ObjectSet {
599                    d: ffi_dispatch!(feature = "source-fontconfig-dlopen", LIB, FcObjectSetCreate,),
600                }
601            }
602        }
603
604        // FcObjectSetAdd
605        pub fn push_string(&mut self, object: Object) {
606            unsafe {
607                // Returns `false` if the property name cannot be inserted
608                // into the set (due to allocation failure).
609                assert_eq!(
610                    ffi_dispatch!(
611                        feature = "source-fontconfig-dlopen",
612                        LIB,
613                        FcObjectSetAdd,
614                        self.d,
615                        object.as_ptr()
616                    ),
617                    1
618                );
619            }
620        }
621    }
622
623    impl Drop for ObjectSet {
624        fn drop(&mut self) {
625            unsafe {
626                ffi_dispatch!(
627                    feature = "source-fontconfig-dlopen",
628                    LIB,
629                    FcObjectSetDestroy,
630                    self.d
631                )
632            }
633        }
634    }
635}