Skip to main content

astrelis_text/
font.rs

1use cosmic_text::{Attrs, fontdb};
2use std::sync::{Arc, RwLock};
3
4/// A font database that manages available fonts.
5pub struct FontDatabase {
6    inner: fontdb::Database,
7}
8
9impl FontDatabase {
10    /// Create a new font database with system fonts loaded.
11    pub fn new() -> Self {
12        let mut db = fontdb::Database::new();
13        db.load_system_fonts();
14        Self { inner: db }
15    }
16
17    /// Create an empty font database.
18    pub fn empty() -> Self {
19        Self {
20            inner: fontdb::Database::new(),
21        }
22    }
23
24    /// Load a font from bytes.
25    pub fn load_font_data(&mut self, data: Vec<u8>) {
26        self.inner
27            .load_font_source(fontdb::Source::Binary(Arc::new(data)));
28    }
29
30    /// Load a font from a .ttf or .otf file.
31    pub fn load_font_file(&mut self, path: impl AsRef<std::path::Path>) -> std::io::Result<()> {
32        self.inner.load_font_file(path)?;
33        Ok(())
34    }
35
36    /// Load fonts from a directory.
37    pub fn load_fonts_dir(&mut self, path: impl AsRef<std::path::Path>) {
38        self.inner.load_fonts_dir(path);
39    }
40
41    /// Query a font by family name.
42    /// Returns true if the font family is available.
43    pub fn has_family(&self, family: &str) -> bool {
44        self.inner
45            .faces()
46            .any(|face| face.families.iter().any(|(f, _)| f == family))
47    }
48
49    /// List all available font families.
50    pub fn list_families(&self) -> Vec<String> {
51        let mut families = std::collections::HashSet::new();
52        for face in self.inner.faces() {
53            for (family, _) in &face.families {
54                families.insert(family.clone());
55            }
56        }
57        let mut families: Vec<String> = families.into_iter().collect();
58        families.sort();
59        families
60    }
61
62    /// Get the number of fonts loaded.
63    pub fn len(&self) -> usize {
64        self.inner.len()
65    }
66
67    /// Check if the database is empty.
68    pub fn is_empty(&self) -> bool {
69        self.len() == 0
70    }
71}
72
73impl Default for FontDatabase {
74    fn default() -> Self {
75        Self::new()
76    }
77}
78
79/// Font management system.
80pub struct FontSystem {
81    inner: Arc<RwLock<cosmic_text::FontSystem>>,
82}
83
84impl FontSystem {
85    /// Create a new font system with the given font database.
86    pub fn new(db: FontDatabase) -> Self {
87        let cosmic_font_system = cosmic_text::FontSystem::new_with_locale_and_db(
88            sys_locale::get_locale().unwrap_or_else(|| String::from("en-US")),
89            db.inner,
90        );
91        Self {
92            inner: Arc::new(RwLock::new(cosmic_font_system)),
93        }
94    }
95
96    /// Create a new font system with system fonts.
97    pub fn with_system_fonts() -> Self {
98        Self::new(FontDatabase::new())
99    }
100
101    pub(crate) fn inner(&self) -> Arc<RwLock<cosmic_text::FontSystem>> {
102        self.inner.clone()
103    }
104}
105
106impl Default for FontSystem {
107    fn default() -> Self {
108        Self::with_system_fonts()
109    }
110}
111
112/// Font weight.
113#[derive(Debug, Clone, Copy, PartialEq, Eq)]
114pub enum FontWeight {
115    Thin,
116    ExtraLight,
117    Light,
118    Normal,
119    Medium,
120    SemiBold,
121    Bold,
122    ExtraBold,
123    Black,
124}
125
126impl FontWeight {
127    pub(crate) fn to_cosmic(self) -> cosmic_text::Weight {
128        match self {
129            FontWeight::Thin => cosmic_text::Weight::THIN,
130            FontWeight::ExtraLight => cosmic_text::Weight::EXTRA_LIGHT,
131            FontWeight::Light => cosmic_text::Weight::LIGHT,
132            FontWeight::Normal => cosmic_text::Weight::NORMAL,
133            FontWeight::Medium => cosmic_text::Weight::MEDIUM,
134            FontWeight::SemiBold => cosmic_text::Weight::SEMIBOLD,
135            FontWeight::Bold => cosmic_text::Weight::BOLD,
136            FontWeight::ExtraBold => cosmic_text::Weight::EXTRA_BOLD,
137            FontWeight::Black => cosmic_text::Weight::BLACK,
138        }
139    }
140}
141
142/// Font style.
143#[derive(Debug, Clone, Copy, PartialEq, Eq)]
144pub enum FontStyle {
145    Normal,
146    Italic,
147    Oblique,
148}
149
150impl FontStyle {
151    pub(crate) fn to_cosmic(self) -> cosmic_text::Style {
152        match self {
153            FontStyle::Normal => cosmic_text::Style::Normal,
154            FontStyle::Italic => cosmic_text::Style::Italic,
155            FontStyle::Oblique => cosmic_text::Style::Oblique,
156        }
157    }
158}
159
160/// Font stretch.
161#[derive(Debug, Clone, Copy, PartialEq, Eq)]
162pub enum FontStretch {
163    UltraCondensed,
164    ExtraCondensed,
165    Condensed,
166    SemiCondensed,
167    Normal,
168    SemiExpanded,
169    Expanded,
170    ExtraExpanded,
171    UltraExpanded,
172}
173
174impl FontStretch {
175    pub(crate) fn to_cosmic(self) -> cosmic_text::Stretch {
176        match self {
177            FontStretch::UltraCondensed => cosmic_text::Stretch::UltraCondensed,
178            FontStretch::ExtraCondensed => cosmic_text::Stretch::ExtraCondensed,
179            FontStretch::Condensed => cosmic_text::Stretch::Condensed,
180            FontStretch::SemiCondensed => cosmic_text::Stretch::SemiCondensed,
181            FontStretch::Normal => cosmic_text::Stretch::Normal,
182            FontStretch::SemiExpanded => cosmic_text::Stretch::SemiExpanded,
183            FontStretch::Expanded => cosmic_text::Stretch::Expanded,
184            FontStretch::ExtraExpanded => cosmic_text::Stretch::ExtraExpanded,
185            FontStretch::UltraExpanded => cosmic_text::Stretch::UltraExpanded,
186        }
187    }
188}
189
190/// Font attributes.
191#[derive(Debug, Clone)]
192pub struct FontAttributes {
193    pub family: String,
194    pub weight: FontWeight,
195    pub style: FontStyle,
196    pub stretch: FontStretch,
197}
198
199impl FontAttributes {
200    /// Create font attributes with a specific family name.
201    /// Common system fonts:
202    /// - "sans-serif", "serif", "monospace" (generic families)
203    /// - "Arial", "Helvetica", "Times New Roman", "Courier New" (Windows)
204    /// - "San Francisco", "Helvetica Neue" (macOS)
205    /// - "Ubuntu", "Noto Sans", "DejaVu Sans" (Linux)
206    pub fn new(family: impl Into<String>) -> Self {
207        Self {
208            family: family.into(),
209            weight: FontWeight::Normal,
210            style: FontStyle::Normal,
211            stretch: FontStretch::Normal,
212        }
213    }
214
215    /// Create font attributes for a sans-serif font.
216    pub fn sans_serif() -> Self {
217        Self::new("sans-serif")
218    }
219
220    /// Create font attributes for a serif font.
221    pub fn serif() -> Self {
222        Self::new("serif")
223    }
224
225    /// Create font attributes for a monospace font.
226    pub fn monospace() -> Self {
227        Self::new("monospace")
228    }
229
230    pub fn weight(mut self, weight: FontWeight) -> Self {
231        self.weight = weight;
232        self
233    }
234
235    pub fn style(mut self, style: FontStyle) -> Self {
236        self.style = style;
237        self
238    }
239
240    pub fn stretch(mut self, stretch: FontStretch) -> Self {
241        self.stretch = stretch;
242        self
243    }
244
245    pub(crate) fn to_cosmic(&self) -> Attrs<'_> {
246        Attrs::new()
247            .family(cosmic_text::Family::Name(&self.family))
248            .weight(self.weight.to_cosmic())
249            .style(self.style.to_cosmic())
250            .stretch(self.stretch.to_cosmic())
251    }
252}
253
254impl Default for FontAttributes {
255    fn default() -> Self {
256        Self {
257            family: String::from("sans-serif"),
258            weight: FontWeight::Normal,
259            style: FontStyle::Normal,
260            stretch: FontStretch::Normal,
261        }
262    }
263}