1use anyhow::Result;
4use fontdb::{Database, Family, Query};
5use std::sync::Arc;
6use swash::FontRef;
7
8use crate::text_shaper::{ShapedRun, ShapingOptions, TextShaper};
9
10#[derive(Clone)]
12pub struct FontData {
13 #[allow(dead_code)]
14 pub data: Arc<Vec<u8>>,
15 pub font_ref: FontRef<'static>,
16}
17
18impl std::fmt::Debug for FontData {
19 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20 f.debug_struct("FontData")
21 .field("data_len", &self.data.len())
22 .finish()
23 }
24}
25
26impl FontData {
27 pub fn new(data: Vec<u8>) -> Option<Self> {
29 let data_arc = Arc::new(data);
30
31 let font_ref = unsafe {
34 let bytes = data_arc.as_slice();
35 let static_bytes: &'static [u8] = std::mem::transmute(bytes);
36 FontRef::from_index(static_bytes, 0)?
37 };
38
39 Some(FontData {
40 data: data_arc,
41 font_ref,
42 })
43 }
44}
45
46#[derive(Debug, Clone)]
48pub struct UnicodeRangeFont {
49 pub start: u32,
51 pub end: u32,
53 pub font: FontData,
55 pub font_index: usize,
57}
58
59pub struct FontManager {
61 primary: FontData,
63
64 bold: Option<FontData>,
66
67 italic: Option<FontData>,
69
70 bold_italic: Option<FontData>,
72
73 range_fonts: Vec<UnicodeRangeFont>,
75
76 fallbacks: Vec<FontData>,
78
79 #[allow(dead_code)]
81 font_db: Database,
82
83 text_shaper: TextShaper,
85}
86
87impl FontManager {
88 pub fn new(
97 primary_family: Option<&str>,
98 bold_family: Option<&str>,
99 italic_family: Option<&str>,
100 bold_italic_family: Option<&str>,
101 font_ranges: &[crate::config::FontRange],
102 ) -> Result<Self> {
103 let mut font_db = Database::new();
104
105 font_db.load_system_fonts();
107
108 log::info!("Loaded {} system fonts", font_db.len());
109
110 let primary = if let Some(family) = primary_family {
112 log::info!("Attempting to load primary font: {}", family);
113 match Self::load_font_from_db(&mut font_db, family) {
114 Some(font_data) => {
115 log::info!("Successfully loaded primary font: {}", family);
116 font_data
117 }
118 None => {
119 log::warn!(
120 "Primary font '{}' not found, using embedded DejaVu Sans Mono",
121 family
122 );
123 Self::load_embedded_font()?
124 }
125 }
126 } else {
127 log::info!("No primary font specified, using embedded DejaVu Sans Mono");
128 Self::load_embedded_font()?
129 };
130
131 let mut fallbacks = Vec::new();
133
134 let fallback_families = [
136 "JetBrainsMono Nerd Font",
138 "JetBrainsMono NF",
139 "FiraCode Nerd Font",
140 "FiraCode NF",
141 "Hack Nerd Font",
142 "Hack NF",
143 "MesloLGS NF",
144 "JetBrains Mono",
146 "Fira Code",
147 "Consolas",
148 "Monaco",
149 "Menlo",
150 "Courier New",
151 "Noto Sans CJK JP",
153 "Noto Sans CJK SC",
154 "Noto Sans CJK TC",
155 "Noto Sans CJK KR",
156 "Microsoft YaHei",
157 "MS Gothic",
158 "SimHei",
159 "Malgun Gothic",
160 "Symbols Nerd Font",
162 "Noto Color Emoji",
163 "Apple Color Emoji",
164 "Segoe UI Emoji",
165 "Segoe UI Symbol",
166 "Symbola",
167 "Arial Unicode MS",
168 "DejaVu Sans",
170 "Arial",
171 "Liberation Sans",
172 ];
173
174 for family_name in fallback_families {
175 if let Some(font_data) = Self::load_font_from_db(&mut font_db, family_name) {
176 log::debug!("Added fallback font: {}", family_name);
177 fallbacks.push(font_data);
178 }
179 }
180
181 log::info!("Loaded {} fallback fonts", fallbacks.len());
182
183 let bold = bold_family.and_then(|family| {
185 log::info!("Attempting to load bold font: {}", family);
186 let font_data = Self::load_font_from_db_with_style(
188 &mut font_db,
189 family,
190 Some(fontdb::Weight::BOLD),
191 None,
192 );
193 if font_data.is_some() {
194 log::info!("Successfully loaded bold font: {}", family);
195 } else {
196 log::warn!("Bold font '{}' not found, will use primary font", family);
197 }
198 font_data
199 });
200
201 let italic = italic_family.and_then(|family| {
203 log::info!("Attempting to load italic font: {}", family);
204 let font_data = Self::load_font_from_db_with_style(
206 &mut font_db,
207 family,
208 None,
209 Some(fontdb::Style::Italic),
210 );
211 if font_data.is_some() {
212 log::info!("Successfully loaded italic font: {}", family);
213 } else {
214 log::warn!("Italic font '{}' not found, will use primary font", family);
215 }
216 font_data
217 });
218
219 let bold_italic = bold_italic_family.and_then(|family| {
221 log::info!("Attempting to load bold italic font: {}", family);
222 let font_data = Self::load_font_from_db_with_style(
224 &mut font_db,
225 family,
226 Some(fontdb::Weight::BOLD),
227 Some(fontdb::Style::Italic),
228 );
229 if font_data.is_some() {
230 log::info!("Successfully loaded bold italic font: {}", family);
231 } else {
232 log::warn!(
233 "Bold italic font '{}' not found, will use primary font",
234 family
235 );
236 }
237 font_data
238 });
239
240 let mut range_fonts = Vec::new();
242 let mut next_font_index = 4; for range in font_ranges {
245 log::info!(
246 "Loading range font for U+{:04X}-U+{:04X}: {}",
247 range.start,
248 range.end,
249 range.font_family
250 );
251
252 if let Some(font_data) = Self::load_font_from_db(&mut font_db, &range.font_family) {
253 range_fonts.push(UnicodeRangeFont {
254 start: range.start,
255 end: range.end,
256 font: font_data,
257 font_index: next_font_index,
258 });
259 log::info!(
260 "Successfully loaded range font: {} (index {})",
261 range.font_family,
262 next_font_index
263 );
264 next_font_index += 1;
265 } else {
266 log::warn!(
267 "Range font '{}' not found for U+{:04X}-U+{:04X}, skipping",
268 range.font_family,
269 range.start,
270 range.end
271 );
272 }
273 }
274
275 Ok(FontManager {
276 primary,
277 bold,
278 italic,
279 bold_italic,
280 range_fonts,
281 fallbacks,
282 font_db,
283 text_shaper: TextShaper::new(),
284 })
285 }
286
287 fn load_embedded_font() -> Result<FontData> {
289 let font_data: &'static [u8] = include_bytes!("../fonts/DejaVuSansMono.ttf");
290 let data = font_data.to_vec();
291
292 FontData::new(data).ok_or_else(|| anyhow::anyhow!("Failed to load embedded font"))
293 }
294
295 fn load_font_from_db(db: &mut Database, family_name: &str) -> Option<FontData> {
297 Self::load_font_from_db_with_style(db, family_name, None, None)
298 }
299
300 fn load_font_from_db_with_style(
301 db: &mut Database,
302 family_name: &str,
303 weight: Option<fontdb::Weight>,
304 style: Option<fontdb::Style>,
305 ) -> Option<FontData> {
306 let query = Query {
308 families: &[Family::Name(family_name)],
309 weight: weight.unwrap_or(fontdb::Weight::NORMAL),
310 style: style.unwrap_or(fontdb::Style::Normal),
311 ..Query::default()
312 };
313
314 let id = db.query(&query)?;
315
316 let (data, _) = unsafe { db.make_shared_face_data(id)? };
319
320 let bytes = data.as_ref().as_ref();
322 FontData::new(bytes.to_vec())
323 }
324
325 fn get_styled_font(&self, bold: bool, italic: bool) -> &FontRef<'static> {
334 match (bold, italic) {
335 (true, true) => self
336 .bold_italic
337 .as_ref()
338 .map(|f| &f.font_ref)
339 .unwrap_or(&self.primary.font_ref),
340 (true, false) => self
341 .bold
342 .as_ref()
343 .map(|f| &f.font_ref)
344 .unwrap_or(&self.primary.font_ref),
345 (false, true) => self
346 .italic
347 .as_ref()
348 .map(|f| &f.font_ref)
349 .unwrap_or(&self.primary.font_ref),
350 (false, false) => &self.primary.font_ref,
351 }
352 }
353
354 pub fn find_glyph(&self, character: char, bold: bool, italic: bool) -> Option<(usize, u16)> {
365 let styled_font = self.get_styled_font(bold, italic);
367 let glyph_id = styled_font.charmap().map(character);
368 if glyph_id != 0 {
369 let font_idx = match (bold, italic) {
371 (true, true) if self.bold_italic.is_some() => 3, (true, false) if self.bold.is_some() => 1, (false, true) if self.italic.is_some() => 2, _ => 0, };
376 return Some((font_idx, glyph_id));
377 }
378
379 let char_code = character as u32;
381 for range_font in &self.range_fonts {
382 if char_code >= range_font.start && char_code <= range_font.end {
383 let glyph_id = range_font.font.font_ref.charmap().map(character);
384 if glyph_id != 0 {
385 log::info!(
386 "✓ Character '{}' (U+{:04X}) found in range font U+{:04X}-U+{:04X} (index {})",
387 character,
388 char_code,
389 range_font.start,
390 range_font.end,
391 range_font.font_index
392 );
393 return Some((range_font.font_index, glyph_id));
394 } else {
395 log::warn!(
396 "✗ Character '{}' (U+{:04X}) in range U+{:04X}-U+{:04X} but glyph_id=0 (not in font)",
397 character,
398 char_code,
399 range_font.start,
400 range_font.end
401 );
402 }
403 }
404 }
405
406 let fallback_start_index = 4 + self.range_fonts.len();
408 for (idx, fallback) in self.fallbacks.iter().enumerate() {
409 let glyph_id = fallback.font_ref.charmap().map(character);
410 if glyph_id != 0 {
411 if !character.is_ascii()
413 || character.is_ascii_punctuation()
414 || character.is_ascii_graphic()
415 {
416 log::debug!(
417 "Character '{}' (U+{:04X}) found in fallback font index {}",
418 character,
419 character as u32,
420 fallback_start_index + idx
421 );
422 }
423 return Some((fallback_start_index + idx, glyph_id));
424 }
425 }
426
427 log::debug!(
429 "Character '{}' (U+{:04X}) not found in any font ({} total fonts)",
430 character,
431 character as u32,
432 self.font_count()
433 );
434 None
435 }
436
437 #[allow(dead_code)]
440 pub fn find_range_font_index(&self, char_code: u32) -> Option<(usize, u16)> {
441 for range_font in &self.range_fonts {
442 if char_code >= range_font.start && char_code <= range_font.end {
443 let character = char::from_u32(char_code)?;
444 let glyph_id = range_font.font.font_ref.charmap().map(character);
445 if glyph_id != 0 {
446 return Some((range_font.font_index, glyph_id));
447 }
448 }
449 }
450 None
451 }
452
453 pub fn get_font(&self, font_index: usize) -> Option<&FontRef<'static>> {
462 match font_index {
463 0 => Some(&self.primary.font_ref),
464 1 => self.bold.as_ref().map(|f| &f.font_ref),
465 2 => self.italic.as_ref().map(|f| &f.font_ref),
466 3 => self.bold_italic.as_ref().map(|f| &f.font_ref),
467 idx if idx >= 4 => {
468 let range_offset = idx - 4;
469 if range_offset < self.range_fonts.len() {
470 Some(&self.range_fonts[range_offset].font.font_ref)
472 } else {
473 let fallback_offset = range_offset - self.range_fonts.len();
475 self.fallbacks.get(fallback_offset).map(|fd| &fd.font_ref)
476 }
477 }
478 _ => None,
479 }
480 }
481
482 #[allow(dead_code)]
484 pub fn primary_font(&self) -> &FontRef<'static> {
485 &self.primary.font_ref
486 }
487
488 pub fn font_count(&self) -> usize {
490 let styled_count = 1 + self.bold.is_some() as usize
492 + self.italic.is_some() as usize
493 + self.bold_italic.is_some() as usize;
494 styled_count + self.range_fonts.len() + self.fallbacks.len()
495 }
496
497 #[allow(dead_code)]
508 #[allow(dead_code)]
509 pub fn get_font_data(&self, font_index: usize) -> Option<&[u8]> {
510 match font_index {
511 0 => Some(self.primary.data.as_slice()),
512 1 => self.bold.as_ref().map(|f| f.data.as_slice()),
513 2 => self.italic.as_ref().map(|f| f.data.as_slice()),
514 3 => self.bold_italic.as_ref().map(|f| f.data.as_slice()),
515 idx if idx >= 4 => {
516 let range_offset = idx - 4;
517 if range_offset < self.range_fonts.len() {
518 Some(self.range_fonts[range_offset].font.data.as_slice())
520 } else {
521 let fallback_offset = range_offset - self.range_fonts.len();
523 self.fallbacks
524 .get(fallback_offset)
525 .map(|fd| fd.data.as_slice())
526 }
527 }
528 _ => None,
529 }
530 }
531
532 #[allow(dead_code)]
546 #[allow(dead_code)]
547 pub fn shape_text(
548 &mut self,
549 text: &str,
550 bold: bool,
551 italic: bool,
552 options: ShapingOptions,
553 ) -> Arc<ShapedRun> {
554 let font_index = self.get_styled_font_index(bold, italic);
556
557 let font_data_arc = match font_index {
560 0 => Arc::clone(&self.primary.data),
561 1 => self
562 .bold
563 .as_ref()
564 .map(|f| Arc::clone(&f.data))
565 .unwrap_or_else(|| Arc::clone(&self.primary.data)),
566 2 => self
567 .italic
568 .as_ref()
569 .map(|f| Arc::clone(&f.data))
570 .unwrap_or_else(|| Arc::clone(&self.primary.data)),
571 3 => self
572 .bold_italic
573 .as_ref()
574 .map(|f| Arc::clone(&f.data))
575 .unwrap_or_else(|| Arc::clone(&self.primary.data)),
576 idx if idx >= 4 => {
577 let range_offset = idx - 4;
578 if range_offset < self.range_fonts.len() {
579 Arc::clone(&self.range_fonts[range_offset].font.data)
580 } else {
581 let fallback_offset = range_offset - self.range_fonts.len();
582 self.fallbacks
583 .get(fallback_offset)
584 .map(|fd| Arc::clone(&fd.data))
585 .unwrap_or_else(|| Arc::clone(&self.primary.data))
586 }
587 }
588 _ => Arc::clone(&self.primary.data),
589 };
590
591 self.text_shaper
593 .shape_text(text, font_data_arc.as_slice(), font_index, options)
594 }
595
596 #[allow(dead_code)]
610 pub fn shape_text_with_font_index(
611 &mut self,
612 text: &str,
613 font_index: usize,
614 options: ShapingOptions,
615 ) -> Arc<ShapedRun> {
616 let font_data_arc = match font_index {
618 0 => Arc::clone(&self.primary.data),
619 1 => self
620 .bold
621 .as_ref()
622 .map(|f| Arc::clone(&f.data))
623 .unwrap_or_else(|| Arc::clone(&self.primary.data)),
624 2 => self
625 .italic
626 .as_ref()
627 .map(|f| Arc::clone(&f.data))
628 .unwrap_or_else(|| Arc::clone(&self.primary.data)),
629 3 => self
630 .bold_italic
631 .as_ref()
632 .map(|f| Arc::clone(&f.data))
633 .unwrap_or_else(|| Arc::clone(&self.primary.data)),
634 idx if idx >= 4 => {
635 let range_offset = idx - 4;
636 if range_offset < self.range_fonts.len() {
637 Arc::clone(&self.range_fonts[range_offset].font.data)
638 } else {
639 let fallback_offset = range_offset - self.range_fonts.len();
640 self.fallbacks
641 .get(fallback_offset)
642 .map(|fd| Arc::clone(&fd.data))
643 .unwrap_or_else(|| Arc::clone(&self.primary.data))
644 }
645 }
646 _ => Arc::clone(&self.primary.data),
647 };
648
649 self.text_shaper
651 .shape_text(text, font_data_arc.as_slice(), font_index, options)
652 }
653
654 #[allow(dead_code)]
658 #[allow(dead_code)]
659 fn get_styled_font_index(&self, bold: bool, italic: bool) -> usize {
660 match (bold, italic) {
661 (true, true) if self.bold_italic.is_some() => 3, (true, false) if self.bold.is_some() => 1, (false, true) if self.italic.is_some() => 2, _ => 0, }
666 }
667
668 #[allow(dead_code)]
672 #[allow(dead_code)]
673 pub fn clear_shape_cache(&mut self) {
674 self.text_shaper.clear_cache();
675 }
676
677 #[allow(dead_code)]
679 #[allow(dead_code)]
680 pub fn shape_cache_size(&self) -> usize {
681 self.text_shaper.cache_size()
682 }
683}