1use std::any::Any;
15use std::sync::Arc;
16
17#[cfg(not(target_arch = "wasm32"))]
18mod android;
19#[cfg(any(target_os = "macos", target_os = "ios"))]
20mod coretext;
21mod files;
22#[cfg(not(target_arch = "wasm32"))]
23mod scan;
24mod sfnt;
25
26#[cfg(not(target_arch = "wasm32"))]
27pub use android::Android;
28#[cfg(any(target_os = "macos", target_os = "ios"))]
29pub use coretext::CoreText;
30#[cfg(not(target_arch = "wasm32"))]
31pub use scan::Scan;
32pub use sfnt::assemble;
33pub use skrifa::raw::types::Tag;
34
35pub type FontData = Arc<dyn AsRef<[u8]> + Send + Sync>;
37
38#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
40pub enum Slant {
41 #[default]
42 Upright,
43 Italic,
44 Oblique,
45}
46
47#[derive(Clone, Copy, Debug, PartialEq)]
51pub struct Style {
52 pub weight: u16,
54 pub width: f32,
56 pub slant: Slant,
57}
58
59pub const NORMAL_WIDTH: f32 = 100.0;
61
62impl Default for Style {
63 fn default() -> Self {
64 Style {
65 weight: 400,
66 width: NORMAL_WIDTH,
67 slant: Slant::Upright,
68 }
69 }
70}
71
72impl Style {
73 fn distance(self, requested: Style) -> (u32, u8, u16) {
75 let width = ((self.width - requested.width).abs() * 10.0) as u32;
76 let slant = match (requested.slant, self.slant) {
77 (a, b) if a == b => 0,
78 (Slant::Italic, Slant::Oblique) | (Slant::Oblique, Slant::Italic) => 1,
79 _ => 2,
80 };
81 (width, slant, self.weight.abs_diff(requested.weight))
82 }
83}
84
85#[derive(Clone)]
91pub struct Typeface {
92 data: FontData,
93 index: u32,
94 family: String,
95 style: Style,
96}
97
98impl Typeface {
99 pub fn new(data: FontData, index: u32, family: impl Into<String>, style: Style) -> Typeface {
100 Typeface {
101 data,
102 index,
103 family: family.into(),
104 style,
105 }
106 }
107
108 pub fn data(&self) -> &[u8] {
110 (*self.data).as_ref()
111 }
112
113 pub fn shared_data(&self) -> FontData {
115 Arc::clone(&self.data)
116 }
117
118 pub fn index(&self) -> u32 {
120 self.index
121 }
122
123 pub fn family(&self) -> &str {
125 &self.family
126 }
127
128 pub fn style(&self) -> Style {
129 self.style
130 }
131
132 pub fn covers(&self, character: char) -> bool {
134 files::covers(self.data(), self.index, character)
135 }
136
137 pub fn tables(&self) -> Vec<(Tag, &[u8])> {
142 sfnt::served_tables(self.data(), self.index).unwrap_or_default()
143 }
144}
145
146impl std::fmt::Debug for Typeface {
147 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
148 f.debug_struct("Typeface")
149 .field("family", &self.family)
150 .field("index", &self.index)
151 .field("style", &self.style)
152 .finish_non_exhaustive()
153 }
154}
155
156pub trait FontManager: Send {
161 fn family(&mut self, family: &str) -> Vec<Typeface>;
164
165 fn match_family_style(&mut self, family: &str, style: Style) -> Option<Typeface> {
168 nearest(self.family(family), style)
169 }
170
171 fn match_character(
176 &mut self,
177 family: Option<&str>,
178 style: Style,
179 locales: &[&str],
180 character: char,
181 ) -> Option<Typeface>;
182
183 fn system_font(&mut self, size: f32, style: Style) -> Option<Typeface>;
187
188 fn families(&self) -> Vec<String>;
190
191 fn face_count(&self) -> usize;
194
195 fn watch(&mut self, on_change: Box<dyn Fn() + Send + Sync>) -> Option<Watch>;
199}
200
201impl<M: FontManager + ?Sized> FontManager for Box<M> {
205 fn family(&mut self, family: &str) -> Vec<Typeface> {
206 (**self).family(family)
207 }
208
209 fn match_family_style(&mut self, family: &str, style: Style) -> Option<Typeface> {
210 (**self).match_family_style(family, style)
211 }
212
213 fn match_character(
214 &mut self,
215 family: Option<&str>,
216 style: Style,
217 locales: &[&str],
218 character: char,
219 ) -> Option<Typeface> {
220 (**self).match_character(family, style, locales, character)
221 }
222
223 fn system_font(&mut self, size: f32, style: Style) -> Option<Typeface> {
224 (**self).system_font(size, style)
225 }
226
227 fn families(&self) -> Vec<String> {
228 (**self).families()
229 }
230
231 fn face_count(&self) -> usize {
232 (**self).face_count()
233 }
234
235 fn watch(&mut self, on_change: Box<dyn Fn() + Send + Sync>) -> Option<Watch> {
236 (**self).watch(on_change)
237 }
238}
239
240pub struct Watch {
242 _subscription: Box<dyn Any + Send>,
243}
244
245impl Watch {
246 pub fn new(subscription: impl Any + Send) -> Watch {
247 Watch {
248 _subscription: Box::new(subscription),
249 }
250 }
251}
252
253pub fn nearest(faces: Vec<Typeface>, style: Style) -> Option<Typeface> {
256 faces
257 .into_iter()
258 .min_by_key(|face| face.style().distance(style))
259}
260
261#[cfg(not(target_arch = "wasm32"))]
264pub fn platform() -> Box<dyn FontManager> {
265 #[cfg(any(target_os = "macos", target_os = "ios"))]
266 {
267 Box::new(CoreText::new())
268 }
269 #[cfg(target_os = "android")]
270 {
271 Box::new(Android::new())
272 }
273 #[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "android")))]
274 {
275 Box::new(Scan::load())
276 }
277}
278
279#[cfg(test)]
280mod tests {
281 use super::*;
282
283 fn face(family: &str, weight: u16, width: f32, slant: Slant) -> Typeface {
284 Typeface::new(
285 Arc::new(Vec::new()),
286 0,
287 family,
288 Style {
289 weight,
290 width,
291 slant,
292 },
293 )
294 }
295
296 #[test]
297 fn nearest_prefers_width_then_slant_then_weight() {
298 let faces = vec![
299 face("a", 400, 100.0, Slant::Italic),
300 face("b", 700, 100.0, Slant::Upright),
301 face("c", 400, 75.0, Slant::Upright),
302 ];
303 let regular = Style::default();
304 assert_eq!(nearest(faces.clone(), regular).unwrap().family(), "b");
305 let italic = Style {
306 slant: Slant::Italic,
307 ..regular
308 };
309 assert_eq!(nearest(faces.clone(), italic).unwrap().family(), "a");
310 let condensed = Style {
311 width: 75.0,
312 ..regular
313 };
314 assert_eq!(nearest(faces, condensed).unwrap().family(), "c");
315 }
316
317 #[test]
318 fn oblique_stands_in_for_italic_before_upright() {
319 let faces = vec![
320 face("upright", 400, 100.0, Slant::Upright),
321 face("oblique", 400, 100.0, Slant::Oblique),
322 ];
323 let italic = Style {
324 slant: Slant::Italic,
325 ..Style::default()
326 };
327 assert_eq!(nearest(faces, italic).unwrap().family(), "oblique");
328 }
329}