font_enumeration/
lib.rs

1//! This is a cross-platform library for enumerating system fonts.
2//!
3//! # Supported platforms:
4//!
5//! - Unix-like (Fontconfig)
6//! - Windows (DirectWrite; **untested**)
7//! - MacOS (Core Text; **untested**)
8//!
9//! # Features and alternatives
10//!
11//! This library is for very simple uses, where you're only interested in listing installed fonts,
12//! perhaps filtering by family name. The listed fonts include family and font name, file path, and
13//! some limited font attributes (style, weight and stretch). It's unlikely this library will grow
14//! much beyond this feature set, and its dependency tree will remain small.
15//!
16//! ```rust
17//! let font_collection = font_enumeration::Collection::new().unwrap();
18//!
19//! for font in font_collection.by_family("DejaVu Sans") {
20//!     println!("{font:#?}");
21//! }
22//! ```
23
24use std::path::PathBuf;
25
26use thiserror::Error;
27
28mod utils;
29
30#[cfg(not(any(target_os = "macos", windows)))]
31#[path = "./fontconfig.rs"]
32mod system;
33
34#[cfg(target_os = "macos")]
35#[path = "./core_text.rs"]
36mod system;
37
38#[cfg(windows)]
39#[path = "./direct_write.rs"]
40mod system;
41
42#[derive(Debug, Error)]
43pub enum Error {
44    /// Failed to initialize the system font collection.
45    #[error("Could not initialize system collection")]
46    SystemCollection,
47}
48
49/// A system font collection.
50pub struct Collection {
51    // Using a boxed slice rather than Vec saves [Collection] from having to store a capacity
52    all_fonts: Box<[Font]>,
53}
54
55impl Collection {
56    /// Construct a new font collection. This scans and caches the system fonts.
57    pub fn new() -> Result<Self, Error> {
58        let all_fonts = system::all_fonts()?;
59
60        Ok(Self { all_fonts })
61    }
62
63    /// Iterate over fonts in the collection.
64    pub fn all(&self) -> impl Iterator<Item = &'_ Font> {
65        self.all_fonts.iter()
66    }
67
68    /// Iterate over fonts matching the given family name. The matching is case insensitive.
69    pub fn by_family<'c, 'f>(&'c self, family_name: &'f str) -> impl Iterator<Item = &'c Font> + 'f
70    where
71        'c: 'f,
72    {
73        self.all()
74            .filter(|font| utils::case_insensitive_match(&font.family_name, family_name))
75    }
76
77    /// Consume this collection and get owned font data.
78    pub fn take(self) -> Vec<Font> {
79        self.all_fonts.into_vec()
80    }
81}
82
83/// Style of a font.
84#[derive(Clone, Copy, Debug, PartialEq)]
85pub enum Style {
86    /// Upright. Also known as "Roman".
87    Normal,
88    /// Italic style. Usually visually distinct from the normal style, rather than simply angled.
89    Italic,
90    /// Angle of the font in degrees
91    Oblique(Option<f32>),
92}
93
94/// Weight class of a font, usually from 1 to 1000.
95#[derive(Clone, Copy, Debug, PartialEq)]
96pub struct Weight(f32);
97
98impl Weight {
99    /// Weight corresponding to a CSS value of 100.
100    pub const THIN: Self = Weight(100.);
101
102    /// Weight corresponding to a CSS value of 200.
103    pub const EXTRA_LIGHT: Self = Weight(200.);
104
105    /// Weight corresponding to a CSS value of 300.
106    pub const LIGHT: Self = Weight(300.);
107
108    /// Weight corresponding to a CSS value of 350.
109    pub const SEMI_LIGHT: Self = Weight(350.);
110
111    /// Weight corresponding to a CSS value of 400.
112    pub const NORMAL: Self = Weight(400.);
113
114    /// Weight corresponding to a CSS value of 500.
115    pub const MEDIUM: Self = Weight(500.);
116
117    /// Weight corresponding to a CSS value of 600.
118    pub const SEMI_BOLD: Self = Weight(600.);
119
120    /// Weight corresponding to a CSS value of 700.
121    pub const BOLD: Self = Weight(700.);
122
123    /// Weight corresponding to a CSS value of 800.
124    pub const EXTRA_BOLD: Self = Weight(800.);
125
126    /// Weight corresponding to a CSS value of 900.
127    pub const BLACK: Self = Weight(900.);
128
129    /// Weight corresponding to a CSS value of 950.
130    pub const EXTRA_BLACK: Self = Weight(950.);
131
132    // Create the weight corresponding to the given CSS value.
133    pub const fn new(weight: f32) -> Self {
134        Weight(weight)
135    }
136
137    /// Get the corresponding CSS value of this weight.
138    pub const fn value(self) -> f32 {
139        self.0
140    }
141}
142
143/// Stretch of a font.
144#[derive(Clone, Copy, Debug, PartialEq)]
145pub struct Stretch(f32);
146
147impl Stretch {
148    /// Character width 50% of normal.
149    pub const ULTRA_CONDENSED: Self = Stretch(0.5);
150
151    /// Character width 62.5% of normal.
152    pub const EXTRA_CONDENSED: Self = Stretch(0.625);
153
154    /// Character width 75% of normal.
155    pub const CONDENSED: Self = Stretch(0.75);
156
157    /// Character width 87.5% of normal.
158    pub const SEMI_CONDENSED: Self = Stretch(0.875);
159
160    /// Character width 100% of normal.
161    pub const NORMAL: Self = Stretch(1.0);
162
163    /// Character width 112.5% of normal.
164    pub const SEMI_EXPANDED: Self = Stretch(1.125);
165
166    /// Character width 125% of normal.
167    pub const EXPANDED: Self = Stretch(1.25);
168
169    /// Character width 150% of normal.
170    pub const EXTRA_EXPANDED: Self = Stretch(1.5);
171
172    /// Character width 200% of normal.
173    pub const ULTRA_EXPANDED: Self = Stretch(2.);
174
175    /// Create the specified stretch as a factor of normal. 1.0 is normal, less than 1.0 is
176    /// condensed, more than 1.0 is expanded.
177    pub const fn new(stretch: f32) -> Self {
178        Stretch(stretch)
179    }
180
181    /// Get the stretch value as a factor of normal. 1.0 is normal, less than 1.0 is condensed,
182    /// more than 1.0 is expanded.
183    pub const fn value(self) -> f32 {
184        self.0
185    }
186}
187
188/// A font.
189#[derive(Clone, Debug, PartialEq)]
190pub struct Font {
191    /// Name of the family the font is part of.
192    pub family_name: String,
193
194    /// Name of the font.
195    pub font_name: String,
196
197    /// Path at which the font file is located.
198    pub path: PathBuf,
199
200    /// The font's style.
201    pub style: Style,
202
203    /// The font's weight.
204    pub weight: Weight,
205
206    /// The font's stretch.
207    pub stretch: Stretch,
208}
209
210#[cfg(test)]
211mod test {
212    use super::Collection;
213
214    #[test]
215    fn has_fonts() {
216        let collection = Collection::new().unwrap();
217
218        // is this a reasonable assumption?
219        assert!(!collection.take().is_empty());
220    }
221}