1use std::hash::BuildHasher;
2use std::hash::Hasher;
3use std::hash::RandomState;
4
5use ratatui_core::buffer::Cell;
6use ratatui_core::style::Modifier;
7use rustybuzz::Face;
8
9#[derive(Clone)]
11pub struct Font<'a> {
12 font: Face<'a>,
13 advance: f32,
14 id: u64,
15}
16
17impl<'a> Font<'a> {
18 pub fn new(data: &'a [u8]) -> Option<Self> {
21 let mut hasher = RandomState::new().build_hasher();
22 hasher.write(data);
23
24 Face::from_slice(data, 0).map(|font| {
25 let advance = font
26 .glyph_hor_advance(font.glyph_index('m').unwrap_or_default())
27 .unwrap_or_default() as f32;
28 Self {
29 font,
30 advance,
31 id: hasher.finish(),
32 }
33 })
34 }
35}
36
37impl Font<'_> {
38 pub(crate) fn id(&self) -> u64 {
39 self.id
40 }
41
42 pub(crate) fn font(&'_ self) -> &'_ Face<'_> {
43 &self.font
44 }
45
46 pub(crate) fn char_width(
47 &self,
48 height_px: u32,
49 ) -> u32 {
50 let scale = height_px as f32 / self.font.height() as f32;
51 (self.advance * scale) as u32
52 }
53}
54
55pub struct Fonts<'a> {
61 char_width: u32,
62 char_height: u32,
63
64 last_resort: Font<'a>,
65
66 regular: Vec<Font<'a>>,
67 bold: Vec<Font<'a>>,
68 italic: Vec<Font<'a>>,
69 bold_italic: Vec<Font<'a>>,
70}
71
72impl<'a> Fonts<'a> {
73 pub fn new(
81 font: Font<'a>,
82 size_px: u32,
83 ) -> Self {
84 Self {
85 char_width: font.char_width(size_px),
86 char_height: size_px,
87 last_resort: font,
88 regular: vec![],
89 bold: vec![],
90 italic: vec![],
91 bold_italic: vec![],
92 }
93 }
94
95 #[inline]
97 pub fn height_px(&self) -> u32 {
98 self.char_height
99 }
100
101 pub fn set_size_px(
104 &mut self,
105 height_px: u32,
106 ) {
107 self.char_height = height_px;
108
109 self.char_width = std::iter::once(&self.last_resort)
110 .chain(self.regular.iter())
111 .chain(self.bold.iter())
112 .chain(self.italic.iter())
113 .chain(self.bold_italic.iter())
114 .map(|font| font.char_width(height_px))
115 .min()
116 .unwrap_or_default();
117 }
118
119 pub fn add_fonts(
125 &mut self,
126 fonts: impl IntoIterator<Item = Font<'a>>,
127 ) {
128 let bold_italic_len = self.bold_italic.len();
129 let italic_len = self.italic.len();
130 let bold_len = self.bold.len();
131 let regular_len = self.regular.len();
132
133 for font in fonts {
134 if !font.font().is_monospaced() {
135 warn!("Non monospace font used in add_fonts, this may cause unexpected rendering.");
136 }
137
138 self.char_width = self.char_width.min(font.char_width(self.char_height));
139 if font.font().is_italic() && font.font().is_bold() {
140 self.bold_italic.push(font);
141 } else if font.font().is_italic() {
142 self.italic.push(font);
143 } else if font.font().is_bold() {
144 self.bold.push(font);
145 } else {
146 self.regular.push(font);
147 }
148 }
149
150 self.bold_italic[bold_italic_len..].sort_by_key(|font| font.char_width(self.char_height));
151 self.italic[italic_len..].sort_by_key(|font| font.char_width(self.char_height));
152 self.bold[bold_len..].sort_by_key(|font| font.char_width(self.char_height));
153 self.regular[regular_len..].sort_by_key(|font| font.char_width(self.char_height));
154 }
155
156 pub fn add_regular_fonts(
159 &mut self,
160 fonts: impl IntoIterator<Item = Font<'a>>,
161 ) {
162 self.char_width = self.char_width.min(Self::add_fonts_internal(
163 &mut self.regular,
164 fonts,
165 self.char_height,
166 ));
167 }
168
169 pub fn add_bold_fonts(
176 &mut self,
177 fonts: impl IntoIterator<Item = Font<'a>>,
178 ) {
179 self.char_width = self.char_width.min(Self::add_fonts_internal(
180 &mut self.bold,
181 fonts,
182 self.char_height,
183 ));
184 }
185
186 pub fn add_italic_fonts(
194 &mut self,
195 fonts: impl IntoIterator<Item = Font<'a>>,
196 ) {
197 self.char_width = self.char_width.min(Self::add_fonts_internal(
198 &mut self.italic,
199 fonts,
200 self.char_height,
201 ));
202 }
203
204 pub fn add_bold_italic_fonts(
211 &mut self,
212 fonts: impl IntoIterator<Item = Font<'a>>,
213 ) {
214 self.char_width = self.char_width.min(Self::add_fonts_internal(
215 &mut self.bold_italic,
216 fonts,
217 self.char_height,
218 ));
219 }
220}
221
222impl<'a> Fonts<'a> {
223 pub(crate) fn min_width_px(&self) -> u32 {
225 self.char_width
226 }
227
228 pub(crate) fn count(&self) -> usize {
229 1 + self.bold.len() + self.italic.len() + self.bold_italic.len() + self.regular.len()
230 }
231
232 pub(crate) fn font_for_cell(
233 &'_ self,
234 cell: &Cell,
235 ) -> (&'_ Font<'_>, bool, bool) {
236 if cell.modifier.contains(Modifier::BOLD | Modifier::ITALIC) {
237 self.select_font(
238 cell.symbol(),
239 self.bold_italic
240 .iter()
241 .map(|f| (f, false, false))
242 .chain(self.italic.iter().map(|f| (f, true, false)))
243 .chain(self.bold.iter().map(|f| (f, false, true)))
244 .chain(self.regular.iter().map(|f| (f, true, true))),
245 true,
246 true,
247 )
248 } else if cell.modifier.contains(Modifier::BOLD) {
249 self.select_font(
250 cell.symbol(),
251 self.bold
252 .iter()
253 .map(|f| (f, false, false))
254 .chain(self.regular.iter().map(|f| (f, true, false))),
255 true,
256 false,
257 )
258 } else if cell.modifier.contains(Modifier::ITALIC) {
259 self.select_font(
260 cell.symbol(),
261 self.italic
262 .iter()
263 .map(|f| (f, false, false))
264 .chain(self.regular.iter().map(|f| (f, false, true))),
265 false,
266 true,
267 )
268 } else {
269 self.select_font(
270 cell.symbol(),
271 self.regular.iter().map(|f| (f, false, false)),
272 false,
273 false,
274 )
275 }
276 }
277
278 fn select_font<'fonts>(
279 &'fonts self,
280 cluster: &str,
281 fonts: impl IntoIterator<Item = (&'fonts Font<'a>, bool, bool)>,
282 last_resort_fake_bold: bool,
283 last_resort_fake_italic: bool,
284 ) -> (&'fonts Font<'a>, bool, bool) {
285 let mut max = 0;
286 let mut font = None;
287 for (candidate, fake_bold, fake_italic) in fonts.into_iter().chain(std::iter::once((
288 &self.last_resort,
289 last_resort_fake_bold,
290 last_resort_fake_italic,
291 ))) {
292 let (count, last_idx) =
293 cluster
294 .chars()
295 .enumerate()
296 .fold((0, 0), |(mut count, _), (idx, ch)| {
297 count += usize::from(candidate.font().glyph_index(ch).is_some());
298 (count, idx)
299 });
300 if count > max {
301 max = count;
302 font = Some((candidate, fake_bold, fake_italic));
303 }
304
305 if count == last_idx + 1 {
306 break;
307 }
308 }
309
310 *font.get_or_insert((
311 &self.last_resort,
312 last_resort_fake_bold,
313 last_resort_fake_italic,
314 ))
315 }
316
317 fn add_fonts_internal(
318 target: &mut Vec<Font<'a>>,
319 fonts: impl IntoIterator<Item = Font<'a>>,
320 char_height: u32,
321 ) -> u32 {
322 let len = target.len();
323 target.extend(fonts);
324
325 target[len..]
326 .iter()
327 .map(|font| font.char_width(char_height))
328 .min()
329 .unwrap_or(u32::MAX)
330 }
331}