1use crate::files;
8use crate::{FontData, FontManager, Slant, Style, Typeface, Watch, NORMAL_WIDTH};
9
10pub struct Scan {
14 database: fontdb::Database,
15}
16
17impl Scan {
18 pub fn load() -> Scan {
20 let mut database = fontdb::Database::new();
21 database.load_system_fonts();
22 Scan { database }
23 }
24
25 fn typeface(&mut self, id: fontdb::ID) -> Option<Typeface> {
26 let (family, style) = {
27 let face = self.database.face(id)?;
28 (face.families.first()?.0.clone(), style_of(face))
29 };
30 let (data, index) = unsafe { self.database.make_shared_face_data(id)? };
35 let data: FontData = data;
36 Some(Typeface::new(data, index, family, style))
37 }
38
39 fn candidates(&self, family: Option<&str>, style: Style) -> Vec<fontdb::ID> {
41 let mut keyed: Vec<(Rank, fontdb::ID)> = self
42 .database
43 .faces()
44 .map(|face| {
45 let in_family = family.is_some_and(|name| answers_to(face, name));
46 ((!in_family, style_of(face).distance(style)), face.id)
47 })
48 .collect();
49 keyed.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal));
50 keyed.into_iter().map(|(_, id)| id).collect()
51 }
52}
53
54impl FontManager for Scan {
55 fn family(&mut self, family: &str) -> Vec<Typeface> {
56 let ids: Vec<fontdb::ID> = self
57 .database
58 .faces()
59 .filter(|face| answers_to(face, family))
60 .map(|face| face.id)
61 .collect();
62 ids.into_iter().filter_map(|id| self.typeface(id)).collect()
63 }
64
65 fn match_character(
66 &mut self,
67 family: Option<&str>,
68 style: Style,
69 _locales: &[&str],
70 character: char,
71 ) -> Option<Typeface> {
72 for id in self.candidates(family, style) {
73 let Some(typeface) = self.typeface(id) else {
74 continue;
75 };
76 if files::covers(typeface.data(), typeface.index(), character) {
77 return Some(typeface);
78 }
79 }
80 None
81 }
82
83 fn system_font(&mut self, _size: f32, _style: Style) -> Option<Typeface> {
84 None
85 }
86
87 fn families(&self) -> Vec<String> {
88 let mut names: Vec<String> = self
89 .database
90 .faces()
91 .filter_map(|face| face.families.first().map(|(name, _)| name.clone()))
92 .collect();
93 names.sort();
94 names.dedup();
95 names
96 }
97
98 fn face_count(&self) -> usize {
99 self.database.len()
100 }
101
102 fn watch(&mut self, _on_change: Box<dyn Fn() + Send + Sync>) -> Option<Watch> {
103 None
104 }
105}
106
107type Rank = (bool, (u32, u8, u16));
109
110fn answers_to(face: &fontdb::FaceInfo, name: &str) -> bool {
111 face.families
112 .iter()
113 .any(|(family, _)| family.eq_ignore_ascii_case(name))
114}
115
116fn style_of(face: &fontdb::FaceInfo) -> Style {
117 Style {
118 weight: face.weight.0,
119 width: width_percent(face.stretch),
120 slant: match face.style {
121 fontdb::Style::Normal => Slant::Upright,
122 fontdb::Style::Italic => Slant::Italic,
123 fontdb::Style::Oblique => Slant::Oblique,
124 },
125 }
126}
127
128fn width_percent(stretch: fontdb::Stretch) -> f32 {
130 match stretch {
131 fontdb::Stretch::UltraCondensed => 50.0,
132 fontdb::Stretch::ExtraCondensed => 62.5,
133 fontdb::Stretch::Condensed => 75.0,
134 fontdb::Stretch::SemiCondensed => 87.5,
135 fontdb::Stretch::Normal => NORMAL_WIDTH,
136 fontdb::Stretch::SemiExpanded => 112.5,
137 fontdb::Stretch::Expanded => 125.0,
138 fontdb::Stretch::ExtraExpanded => 150.0,
139 fontdb::Stretch::UltraExpanded => 200.0,
140 }
141}