1#![allow(clippy::unnecessary_to_owned)] use crate::core::{
28 algebra::Vector2, rectpack::RectPacker, reflect::prelude::*, uuid::Uuid, uuid_provider,
29 visitor::prelude::*, TypeUuidProvider,
30};
31use crate::font::loader::FontImportOptions;
32use fxhash::FxHashMap;
33use fyrox_core::math::Rect;
34use fyrox_core::{err, uuid};
35use fyrox_resource::manager::ResourceManager;
36use fyrox_resource::state::LoadError;
37use fyrox_resource::untyped::ResourceKind;
38use fyrox_resource::{
39 embedded_data_source, io::ResourceIo, manager::BuiltInResource, untyped::UntypedResource,
40 Resource, ResourceData,
41};
42use std::sync::LazyLock;
43use std::{
44 error::Error,
45 fmt::{Debug, Formatter},
46 hash::{Hash, Hasher},
47 ops::Deref,
48 path::Path,
49};
50
51pub mod loader;
52
53const MAX_FALLBACK_DEPTH: usize = 10;
58
59enum FontError {
60 FallbackNotLoaded,
61 GlyphTooLarge,
62}
63
64#[derive(Debug, Clone)]
67pub struct FontGlyph {
68 pub bitmap_top: f32,
72 pub bitmap_left: f32,
76 pub bitmap_width: f32,
79 pub bitmap_height: f32,
81 pub advance: f32,
83 pub tex_coords: [Vector2<f32>; 4],
86 pub page_index: usize,
88 pub bounds: Rect<f32>,
93}
94
95#[derive(Clone)]
97pub struct Page {
98 pub pixels: Vec<u8>,
102 pub texture: Option<UntypedResource>,
106 pub rect_packer: RectPacker<usize>,
110 pub modified: bool,
113}
114
115impl Debug for Page {
116 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
117 f.debug_struct("Page")
118 .field("Pixels", &self.pixels)
119 .field("Texture", &self.texture)
120 .field("Modified", &self.modified)
121 .finish()
122 }
123}
124
125#[derive(Default, Clone, Debug)]
128pub struct Atlas {
129 pub glyphs: Vec<FontGlyph>,
133 pub char_map: FxHashMap<char, usize>,
135 pub pages: Vec<Page>,
138}
139
140impl Atlas {
141 fn render_glyph(
142 &mut self,
143 font: &'_ fontdue::Font,
144 unicode: char,
145 char_index: u16,
146 height: FontHeight,
147 page_size: usize,
148 ) -> Result<usize, FontError> {
149 let border = 2;
150 let (metrics, glyph_raster) = font.rasterize_indexed(char_index, height.0);
151
152 let mut placement_info =
155 self.pages
156 .iter_mut()
157 .enumerate()
158 .find_map(|(page_index, page)| {
159 page.rect_packer
160 .find_free(metrics.width + border, metrics.height + border)
161 .map(|bounds| (page_index, bounds))
162 });
163
164 if placement_info.is_none() {
166 let mut page = Page {
167 pixels: vec![0; page_size * page_size],
168 texture: None,
169 rect_packer: RectPacker::new(page_size, page_size),
170 modified: true,
171 };
172
173 let page_index = self.pages.len();
174
175 if let Some(bounds) = page
176 .rect_packer
177 .find_free(metrics.width + border, metrics.height + border)
178 {
179 placement_info = Some((page_index, bounds));
180
181 self.pages.push(page);
182 }
183 }
184
185 let Some((page_index, placement_rect)) = placement_info else {
186 err!(
187 "Font error: The atlas page size is too small for a requested glyph at font size {}.\
188 Glyph width: {}, height: {}, atlas page size: {}",
189 height.0,
190 metrics.width + border,
191 metrics.height + border,
192 page_size,
193 );
194 return Err(FontError::GlyphTooLarge);
195 };
196 let page = &mut self.pages[page_index];
197 let glyph_index = self.glyphs.len();
198
199 page.modified = true;
202
203 let mut glyph = FontGlyph {
204 bitmap_left: metrics.xmin as f32,
205 bitmap_top: metrics.ymin as f32,
206 advance: metrics.advance_width,
207 tex_coords: Default::default(),
208 bitmap_width: metrics.width as f32,
209 bitmap_height: metrics.height as f32,
210 bounds: Rect::new(
211 metrics.bounds.xmin,
212 metrics.bounds.ymin,
213 metrics.bounds.width,
214 metrics.bounds.height,
215 ),
216 page_index,
217 };
218
219 let k = 1.0 / page_size as f32;
220
221 let bw = placement_rect.w().saturating_sub(border);
222 let bh = placement_rect.h().saturating_sub(border);
223 let bx = placement_rect.x() + border / 2;
224 let by = placement_rect.y() + border / 2;
225
226 let tw = bw as f32 * k;
227 let th = bh as f32 * k;
228 let tx = bx as f32 * k;
229 let ty = by as f32 * k;
230
231 glyph.tex_coords[0] = Vector2::new(tx, ty);
232 glyph.tex_coords[1] = Vector2::new(tx + tw, ty);
233 glyph.tex_coords[2] = Vector2::new(tx + tw, ty + th);
234 glyph.tex_coords[3] = Vector2::new(tx, ty + th);
235
236 let row_end = by + bh;
237 let col_end = bx + bw;
238
239 for (src_row, row) in (by..row_end).enumerate() {
241 for (src_col, col) in (bx..col_end).enumerate() {
242 page.pixels[row * page_size + col] = glyph_raster[src_row * bw + src_col];
243 }
244 }
245
246 self.glyphs.push(glyph);
247
248 self.char_map.insert(unicode, glyph_index);
250
251 Ok(glyph_index)
252 }
253 fn glyph(
254 &mut self,
255 font: &fontdue::Font,
256 unicode: char,
257 height: FontHeight,
258 page_size: usize,
259 fallbacks: &[Option<FontResource>],
260 ) -> Option<&FontGlyph> {
261 match self.char_map.get(&unicode) {
262 Some(glyph_index) => self.glyphs.get(*glyph_index),
263 None => {
264 let glyph_index = if let Some(char_index) = font.chars().get(&unicode) {
267 self.render_glyph(font, unicode, char_index.get(), height, page_size)
268 .ok()
269 } else {
270 match self.fallback_glyph(
272 MAX_FALLBACK_DEPTH,
273 fallbacks,
274 unicode,
275 height,
276 page_size,
277 ) {
278 Ok(Some(glyph_index)) => Some(glyph_index),
279 Ok(None) | Err(FontError::GlyphTooLarge) => {
280 self.render_glyph(font, unicode, 0, height, page_size).ok()
284 }
285 Err(FontError::FallbackNotLoaded) => {
286 None
289 }
290 }
291 };
292 glyph_index.and_then(|i| self.glyphs.get(i))
293 }
294 }
295 }
296 fn fallback_glyph(
300 &mut self,
301 depth: usize,
302 fonts: &[Option<FontResource>],
303 unicode: char,
304 height: FontHeight,
305 page_size: usize,
306 ) -> Result<Option<usize>, FontError> {
307 let Some(depth) = depth.checked_sub(1) else {
308 return Ok(None);
309 };
310 for font in fonts.iter().flatten() {
311 if !font.is_ok() {
312 return Err(FontError::FallbackNotLoaded);
313 }
314 let font = font.data_ref();
315 let inner = font
316 .inner
317 .as_ref()
318 .expect("Fallback font reader must be initialized!");
319 if let Some(char_index) = inner.chars().get(&unicode) {
320 return self
321 .render_glyph(inner, unicode, char_index.get(), height, page_size)
322 .map(Some);
323 } else if let Some(glyph_index) =
324 self.fallback_glyph(depth, &font.fallbacks, unicode, height, page_size)?
325 {
326 return Ok(Some(glyph_index));
327 }
328 }
329 Ok(None)
330 }
331}
332
333#[derive(Default, Clone, Debug, Reflect, Visit)]
335pub struct Font {
336 #[reflect(hidden)]
338 #[visit(skip)]
339 pub inner: Option<fontdue::Font>,
340 #[reflect(hidden)]
343 #[visit(skip)]
344 pub atlases: FxHashMap<FontHeight, Atlas>,
345 #[reflect(hidden)]
347 #[visit(skip)]
348 pub page_size: usize,
349 #[visit(skip)]
351 pub bold: Option<FontResource>,
352 #[visit(skip)]
354 pub italic: Option<FontResource>,
355 #[visit(skip)]
357 pub bold_italic: Option<FontResource>,
358 #[visit(skip)]
361 pub fallbacks: Vec<Option<FontResource>>,
362}
363
364uuid_provider!(Font = "692fec79-103a-483c-bb0b-9fc3a349cb48");
365
366impl ResourceData for Font {
367 fn type_uuid(&self) -> Uuid {
368 <Self as TypeUuidProvider>::type_uuid()
369 }
370
371 fn save(&mut self, _path: &Path) -> Result<(), Box<dyn Error>> {
372 Ok(())
373 }
374
375 fn can_be_saved(&self) -> bool {
376 false
377 }
378
379 fn try_clone_box(&self) -> Option<Box<dyn ResourceData>> {
380 Some(Box::new(self.clone()))
381 }
382}
383
384#[derive(Copy, Clone, Default, Debug)]
388pub struct FontHeight(pub f32);
389
390impl From<f32> for FontHeight {
391 fn from(value: f32) -> Self {
392 Self(value)
393 }
394}
395
396impl PartialEq for FontHeight {
397 fn eq(&self, other: &Self) -> bool {
398 fyrox_core::value_as_u8_slice(&self.0) == fyrox_core::value_as_u8_slice(&other.0)
399 }
400}
401
402impl Eq for FontHeight {}
403
404impl Hash for FontHeight {
405 fn hash<H: Hasher>(&self, state: &mut H) {
406 fyrox_core::hash_as_bytes(&self.0, state)
409 }
410}
411
412pub type FontResource = Resource<Font>;
414
415pub static BOLD_ITALIC: LazyLock<BuiltInResource<Font>> = LazyLock::new(|| {
417 BuiltInResource::new(
418 "__BOLD_ITALIC__",
419 embedded_data_source!("./bold_italic.ttf"),
420 |data| {
421 FontResource::new_ok(
422 uuid!("f5b02124-9601-452a-9368-3fa2a9703ecd"),
423 ResourceKind::External,
424 Font::from_memory(data.to_vec(), 1024, FontStyles::default(), Vec::default())
425 .unwrap(),
426 )
427 },
428 )
429});
430
431pub static BUILT_IN_ITALIC: LazyLock<BuiltInResource<Font>> = LazyLock::new(|| {
433 BuiltInResource::new(
434 "__BUILT_IN_ITALIC__",
435 embedded_data_source!("./built_in_italic.ttf"),
436 |data| {
437 let bold = Some(BOLD_ITALIC.resource());
438 let styles = FontStyles {
439 bold,
440 ..FontStyles::default()
441 };
442 FontResource::new_ok(
443 uuid!("1cd79487-6c76-4370-91c2-e6e1e728950a"),
444 ResourceKind::External,
445 Font::from_memory(data.to_vec(), 1024, styles, Vec::default()).unwrap(),
446 )
447 },
448 )
449});
450
451pub static BUILT_IN_BOLD: LazyLock<BuiltInResource<Font>> = LazyLock::new(|| {
453 BuiltInResource::new(
454 "__BUILT_IN_BOLD__",
455 embedded_data_source!("./built_in_bold.ttf"),
456 |data| {
457 let italic = Some(BOLD_ITALIC.resource());
458 let styles = FontStyles {
459 italic,
460 ..FontStyles::default()
461 };
462 FontResource::new_ok(
463 uuid!("8a471243-2466-4241-a4cb-c341ce8e844a"),
464 ResourceKind::External,
465 Font::from_memory(data.to_vec(), 1024, styles, Vec::default()).unwrap(),
466 )
467 },
468 )
469});
470
471pub static BUILT_IN_FONT: LazyLock<BuiltInResource<Font>> = LazyLock::new(|| {
473 BuiltInResource::new(
474 "__BUILT_IN_FONT__",
475 embedded_data_source!("./built_in_font.ttf"),
476 |data| {
477 let styles = FontStyles {
478 bold: Some(BUILT_IN_BOLD.resource()),
479 italic: Some(BUILT_IN_ITALIC.resource()),
480 bold_italic: Some(BOLD_ITALIC.resource()),
481 };
482 FontResource::new_ok(
483 uuid!("77260e8e-f6fa-429c-8009-13dda2673925"),
484 ResourceKind::External,
485 Font::from_memory(data.to_vec(), 1024, styles, Vec::default()).unwrap(),
486 )
487 },
488 )
489});
490
491pub async fn wait_for_subfonts(font: FontResource) -> Result<FontResource, LoadError> {
496 let mut stack = Vec::new();
497 let font = font.await?;
498 let bold = font.data_ref().bold.clone();
499 if let Some(bold) = bold {
500 wait_for_fallbacks(bold, &mut stack).await?;
501 stack.clear();
502 }
503 let italic = font.data_ref().italic.clone();
504 if let Some(italic) = italic {
505 wait_for_fallbacks(italic, &mut stack).await?;
506 stack.clear();
507 }
508 let bold_italic = font.data_ref().bold_italic.clone();
509 if let Some(bold_italic) = bold_italic {
510 wait_for_fallbacks(bold_italic, &mut stack).await?;
511 stack.clear();
512 }
513 wait_for_fallbacks(font, &mut stack).await
514}
515
516fn write_font_names<W: std::fmt::Write>(fonts: &[FontResource], out: &mut W) -> std::fmt::Result {
517 fn write_name<W: std::fmt::Write>(font: &FontResource, out: &mut W) -> std::fmt::Result {
518 if font.is_ok() {
519 out.write_str(font.data_ref().name().unwrap_or("unnamed"))
520 } else {
521 out.write_str("unnamed")
522 }
523 }
524 if let Some((first, rest)) = fonts.split_first() {
525 write_name(first, out)?;
526 for font in rest {
527 out.write_str(" > ")?;
528 write_name(font, out)?;
529 }
530 }
531 Ok(())
532}
533
534async fn wait_for_fallbacks(
539 font: FontResource,
540 stack: &mut Vec<FontResource>,
541) -> Result<FontResource, LoadError> {
542 if stack.contains(&font) {
543 let mut err = "Cyclic fallback fonts at: ".to_string();
544 write_font_names(stack, &mut err).unwrap();
545 return Err(LoadError::new(err));
546 }
547 stack.push(font.clone());
548 let font = font.await?;
549 let fallbacks = font
550 .data_ref()
551 .fallbacks
552 .iter()
553 .flatten()
554 .cloned()
555 .collect::<Vec<_>>();
556 for fallback in fallbacks {
557 Box::pin(wait_for_fallbacks(fallback, stack)).await?;
558 }
559 _ = stack.pop();
560 Ok(font)
561}
562
563#[derive(Default, Debug, Clone)]
564pub struct FontStyles {
565 pub bold: Option<FontResource>,
566 pub italic: Option<FontResource>,
567 pub bold_italic: Option<FontResource>,
568}
569
570impl Font {
571 pub fn name(&self) -> Option<&str> {
573 self.inner.as_ref().and_then(|f| f.name())
574 }
575
576 pub fn from_memory(
578 data: impl Deref<Target = [u8]>,
579 page_size: usize,
580 styles: FontStyles,
581 fallbacks: Vec<Option<FontResource>>,
582 ) -> Result<Self, &'static str> {
583 let fontdue_font = fontdue::Font::from_bytes(data, fontdue::FontSettings::default())?;
584 Ok(Font {
585 inner: Some(fontdue_font),
586 atlases: Default::default(),
587 page_size,
588 bold: styles.bold,
589 italic: styles.italic,
590 bold_italic: styles.bold_italic,
591 fallbacks,
592 })
593 }
594
595 pub async fn from_file<P: AsRef<Path>>(
597 path: P,
598 options: FontImportOptions,
599 io: &dyn ResourceIo,
600 resource_manager: &ResourceManager,
601 ) -> Result<Self, LoadError> {
602 if let Ok(file_content) = io.load_file(path.as_ref()).await {
603 let page_size = options.page_size;
604 let mut bold = options.bold;
605 let mut italic = options.italic;
606 let mut bold_italic = options.bold_italic;
607 if let Some(bold) = &mut bold {
608 resource_manager.request_resource(bold);
609 }
610 if let Some(italic) = &mut italic {
611 resource_manager.request_resource(italic);
612 }
613 if let Some(bold_italic) = &mut bold_italic {
614 resource_manager.request_resource(bold_italic);
615 }
616 let mut fallbacks = options.fallbacks;
617 for font in fallbacks.iter_mut().flatten() {
618 resource_manager.request_resource(font);
619 }
620 let styles = FontStyles {
621 bold,
622 italic,
623 bold_italic,
624 };
625 Self::from_memory(file_content, page_size, styles, fallbacks).map_err(LoadError::new)
626 } else {
627 Err(LoadError::new("Unable to read file"))
628 }
629 }
630
631 #[inline]
639 pub fn glyph(&mut self, unicode: char, height: f32) -> Option<&FontGlyph> {
640 if !height.is_finite() || height <= f32::EPSILON {
641 return None;
642 }
643 let height = FontHeight(height);
644 let inner = self
645 .inner
646 .as_ref()
647 .expect("Font reader must be initialized!");
648 self.atlases.entry(height).or_default().glyph(
649 inner,
650 unicode,
651 height,
652 self.page_size,
653 &self.fallbacks,
654 )
655 }
656
657 #[inline]
659 pub fn ascender(&self, height: f32) -> f32 {
660 self.inner
661 .as_ref()
662 .unwrap()
663 .horizontal_line_metrics(height)
664 .map(|m| m.ascent)
665 .unwrap_or_default()
666 }
667
668 #[inline]
670 pub fn descender(&self, height: f32) -> f32 {
671 self.inner
672 .as_ref()
673 .unwrap()
674 .horizontal_line_metrics(height)
675 .map(|m| m.descent)
676 .unwrap_or_default()
677 }
678
679 #[inline]
684 pub fn horizontal_kerning(&self, height: f32, left: char, right: char) -> Option<f32> {
685 self.inner
686 .as_ref()
687 .unwrap()
688 .horizontal_kern(left, right, height)
689 }
690
691 #[inline]
693 pub fn page_size(&self) -> usize {
694 self.page_size
695 }
696
697 #[inline]
700 pub fn glyph_advance(&mut self, unicode: char, height: f32) -> f32 {
701 self.glyph(unicode, height)
702 .map_or(height, |glyph| glyph.advance)
703 }
704}
705
706pub struct FontBuilder {
708 page_size: usize,
710 bold: Option<FontResource>,
711 italic: Option<FontResource>,
712 bold_italic: Option<FontResource>,
713 fallbacks: Vec<Option<FontResource>>,
714}
715
716impl FontBuilder {
717 pub fn new() -> Self {
719 Self {
720 page_size: 1024,
721 bold: None,
722 italic: None,
723 bold_italic: None,
724 fallbacks: Vec::default(),
725 }
726 }
727
728 pub fn with_page_size(mut self, size: usize) -> Self {
730 self.page_size = size;
731 self
732 }
733
734 pub fn with_bold(mut self, font: FontResource) -> Self {
736 self.bold = Some(font);
737 self
738 }
739
740 pub fn with_italic(mut self, font: FontResource) -> Self {
742 self.italic = Some(font);
743 self
744 }
745
746 pub fn with_bold_italic(mut self, font: FontResource) -> Self {
747 self.bold_italic = Some(font);
748 self
749 }
750
751 pub fn with_fallback(mut self, font: FontResource) -> Self {
754 self.fallbacks.push(Some(font));
755 self
756 }
757
758 pub fn with_fallbacks(mut self, fallbacks: Vec<Option<FontResource>>) -> Self {
761 self.fallbacks = fallbacks;
762 self
763 }
764
765 fn into_options(self) -> FontImportOptions {
767 FontImportOptions {
768 page_size: self.page_size,
769 bold: self.bold,
770 italic: self.italic,
771 bold_italic: self.bold_italic,
772 fallbacks: self.fallbacks,
773 }
774 }
775
776 pub async fn build_from_file(
778 self,
779 path: impl AsRef<Path>,
780 io: &dyn ResourceIo,
781 resource_manager: &ResourceManager,
782 ) -> Result<Font, LoadError> {
783 Font::from_file(path, self.into_options(), io, resource_manager).await
784 }
785
786 pub fn build_from_memory(
788 mut self,
789 data: impl Deref<Target = [u8]>,
790 resource_manager: &ResourceManager,
791 ) -> Result<Font, &'static str> {
792 for font in self.fallbacks.iter_mut().flatten() {
793 resource_manager.request_resource(font);
794 }
795 let styles = FontStyles {
796 bold: self.bold,
797 italic: self.italic,
798 bold_italic: self.bold_italic,
799 };
800 Font::from_memory(data, self.page_size, styles, self.fallbacks)
801 }
802}