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