1use crate::error::{FontError, FontResult};
6use font_kit::family_name::FamilyName;
7use font_kit::properties::{Properties, Style, Weight};
8use font_kit::source::SystemSource;
9use rusttype::{Font as RustTypeFont, Scale};
10use std::path::Path;
11use std::sync::Arc;
12
13#[derive(Debug, Clone)]
15pub struct Font {
16 inner: Arc<RustTypeFont<'static>>,
18 data: Arc<Vec<u8>>,
20}
21
22impl Font {
23 pub fn from_file<P: AsRef<Path>>(path: P) -> FontResult<Self> {
25 let data = std::fs::read(path.as_ref())
26 .map_err(|e| FontError::LoadError(format!("Failed to read font file: {}", e)))?;
27
28 Self::from_bytes(data)
29 }
30
31 pub fn from_bytes(data: Vec<u8>) -> FontResult<Self> {
33 let font = RustTypeFont::try_from_vec(data.clone())
34 .ok_or_else(|| FontError::LoadError("Failed to parse font data".to_string()))?;
35
36 Ok(Self {
37 inner: Arc::new(font),
38 data: Arc::new(data),
39 })
40 }
41
42 pub fn from_system(family_name: &str) -> FontResult<Self> {
44 let source = SystemSource::new();
45
46 let handle = source
47 .select_best_match(
48 &[FamilyName::Title(family_name.to_string())],
49 &Properties::new(),
50 )
51 .map_err(|e| FontError::LoadError(format!("Failed to find system font: {}", e)))?;
52
53 let font_data = handle
54 .load()
55 .map_err(|e| FontError::LoadError(format!("Failed to load system font: {}", e)))?
56 .copy_font_data()
57 .ok_or_else(|| FontError::LoadError("Failed to copy font data".to_string()))?;
58
59 Self::from_bytes(font_data.to_vec())
60 }
61
62 pub fn from_system_with_style(
64 family_name: &str,
65 weight: FontWeight,
66 style: FontStyle,
67 ) -> FontResult<Self> {
68 let source = SystemSource::new();
69
70 let properties = Properties {
71 weight: weight.to_font_kit_weight(),
72 style: style.to_font_kit_style(),
73 ..Properties::default()
74 };
75
76 let handle = source
77 .select_best_match(&[FamilyName::Title(family_name.to_string())], &properties)
78 .map_err(|e| FontError::LoadError(format!("Failed to find system font: {}", e)))?;
79
80 let font_data = handle
81 .load()
82 .map_err(|e| FontError::LoadError(format!("Failed to load system font: {}", e)))?
83 .copy_font_data()
84 .ok_or_else(|| FontError::LoadError("Failed to copy font data".to_string()))?;
85
86 Self::from_bytes(font_data.to_vec())
87 }
88
89 pub fn default_system_font() -> FontResult<Self> {
91 #[cfg(target_os = "macos")]
92 let default_font = "PingFang SC";
93 #[cfg(target_os = "windows")]
94 let default_font = "Arial";
95 #[cfg(target_os = "linux")]
96 #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
97 let default_font = "sans-serif";
98
99 Self::from_system(default_font)
100 }
101
102 pub(crate) fn inner(&self) -> &RustTypeFont<'static> {
104 &self.inner
105 }
106
107 pub fn data(&self) -> &[u8] {
109 &self.data
110 }
111}
112
113#[derive(Debug, Clone, Copy, PartialEq, Eq)]
115pub enum FontWeight {
116 Thin,
117 ExtraLight,
118 Light,
119 Normal,
120 Medium,
121 SemiBold,
122 Bold,
123 ExtraBold,
124 Black,
125}
126
127impl FontWeight {
128 fn to_font_kit_weight(self) -> Weight {
129 match self {
130 FontWeight::Thin => Weight::THIN,
131 FontWeight::ExtraLight => Weight::EXTRA_LIGHT,
132 FontWeight::Light => Weight::LIGHT,
133 FontWeight::Normal => Weight::NORMAL,
134 FontWeight::Medium => Weight::MEDIUM,
135 FontWeight::SemiBold => Weight::SEMIBOLD,
136 FontWeight::Bold => Weight::BOLD,
137 FontWeight::ExtraBold => Weight::EXTRA_BOLD,
138 FontWeight::Black => Weight::BLACK,
139 }
140 }
141}
142
143impl Default for FontWeight {
144 fn default() -> Self {
145 FontWeight::Normal
146 }
147}
148
149#[derive(Debug, Clone, Copy, PartialEq, Eq)]
151pub enum FontStyle {
152 Normal,
153 Italic,
154 Oblique,
155}
156
157impl FontStyle {
158 fn to_font_kit_style(self) -> Style {
159 match self {
160 FontStyle::Normal => Style::Normal,
161 FontStyle::Italic => Style::Italic,
162 FontStyle::Oblique => Style::Oblique,
163 }
164 }
165}
166
167impl Default for FontStyle {
168 fn default() -> Self {
169 FontStyle::Normal
170 }
171}
172
173#[derive(Debug, Clone)]
175pub struct TextStyle {
176 pub font: Font,
178 pub size: f32,
180 pub weight: FontWeight,
182 pub style: FontStyle,
184}
185
186impl TextStyle {
187 pub fn new(font: Font, size: f32) -> Self {
189 Self {
190 font,
191 size,
192 weight: FontWeight::Normal,
193 style: FontStyle::Normal,
194 }
195 }
196
197 pub fn with_size(mut self, size: f32) -> Self {
199 self.size = size;
200 self
201 }
202
203 pub fn with_weight(mut self, weight: FontWeight) -> Self {
205 self.weight = weight;
206 self
207 }
208
209 pub fn with_style(mut self, style: FontStyle) -> Self {
211 self.style = style;
212 self
213 }
214
215 pub(crate) fn scale(&self) -> Scale {
217 Scale::uniform(self.size)
218 }
219
220 pub fn measure_text(&self, text: &str) -> (u32, u32) {
243 let scale = self.scale();
244 let font = self.font.inner();
245
246 let mut width = 0.0;
248 let mut last_glyph_id = None;
249
250 for c in text.chars() {
251 let glyph = font.glyph(c).scaled(scale);
252
253 if let Some(last_id) = last_glyph_id {
255 width += font.pair_kerning(scale, last_id, glyph.id());
256 }
257
258 width += glyph.h_metrics().advance_width;
260 last_glyph_id = Some(glyph.id());
261 }
262
263 let v_metrics = font.v_metrics(scale);
266 let height = (v_metrics.ascent - v_metrics.descent).ceil();
267
268 (width.ceil() as u32, height as u32)
269 }
270}