Skip to main content

fyrox_ui/font/
mod.rs

1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21//! A font resource allows [`FormattedText`](crate::formatted_text::FormattedText)
22//! to render text as a series of glyphs taken from a font file such as a ttf file
23//! or an otf file.
24
25#![allow(clippy::unnecessary_to_owned)] // false-positive
26
27use 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
53/// Arbitrarily chosen limit to the number of levels of recursion,
54/// we will search through fallbacks. In most cases, a limit of 1 should
55/// be sufficient, and if we get to 10, that most likely indicates
56/// a cycle in the fallback fonts.
57const MAX_FALLBACK_DEPTH: usize = 10;
58
59enum FontError {
60    FallbackNotLoaded,
61    GlyphTooLarge,
62}
63
64/// The geometric data specifying where to find a glyph on a font atlas
65/// texture for rendering text.
66#[derive(Debug, Clone)]
67pub struct FontGlyph {
68    /// The vertical position of the glyph relative to other glyphs on the line, measured in font pixels.
69    /// This would be 0 for a glyph with its bottom directly on the baseline, but may be
70    /// negative if the glyph extends below the baseline.
71    pub bitmap_top: f32,
72    /// The horizontal position of the glyph relative to other glyphs on the line, measured in font pixels.
73    /// This would usually be 0, but a negative value would allow a glyph to extend into the space usually reserved
74    /// for the previous glyph on the line.
75    pub bitmap_left: f32,
76    /// The width of the glyph as measured in pixels. This may be more or less than `advance` depending on how much
77    /// this glyph crowds into the space of the glyphs to the left and right of it on the line.
78    pub bitmap_width: f32,
79    /// The height of the glyph as measured in pixels.
80    pub bitmap_height: f32,
81    /// The horizontal distance between the start of this glyph and the start of the next glyph, measured in pixels.
82    pub advance: f32,
83    /// The position of the texture data of this glyph on the atlas page.
84    /// Each corner of the quad containing the glyph is given a UV coordinate.
85    pub tex_coords: [Vector2<f32>; 4],
86    /// The index of the atlas page.
87    pub page_index: usize,
88    /// The position of the glyph measured to sub-pixel precision.
89    /// It is like `bitmap_top`, `bitmap_left`, `bitmap_width`, and `bitmap_height`,
90    /// except that those measure the whole pixels that the font needs for rendering while
91    /// this rect contains the exact outline of the glyph, which is potentially smaller.
92    pub bounds: Rect<f32>,
93}
94
95/// Page is a storage for rasterized glyphs.
96#[derive(Clone)]
97pub struct Page {
98    /// The texture data for rendering some glyphs.
99    /// When new glyphs are required, this data may be modified if space
100    /// is available.
101    pub pixels: Vec<u8>,
102    /// An embedded texture containing a copy of `pixels`.
103    /// This is used by the GPU to render the glyph, and it must be updated
104    /// whenever `pixels` changes.
105    pub texture: Option<UntypedResource>,
106    /// A structure that contains the occupied rectangles within the page texture,
107    /// allowing new glyphs to be added to the texture by finding unoccupied space,
108    /// until the page is full.
109    pub rect_packer: RectPacker<usize>,
110    /// True if one or more glyphs have been added to the page, but
111    /// `texture` has not yet been updated by copying the content of `pixels`.
112    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/// Atlas is a storage for glyphs of a particular size, each atlas could have any number of pages to
126/// store the rasterized glyphs.
127#[derive(Default, Clone, Debug)]
128pub struct Atlas {
129    /// The geometric data used for rendering glyphs from the pages of this atlas,
130    /// such as the size of each glyph, the index of its page, and the UVs of the corners
131    /// of its quad.
132    pub glyphs: Vec<FontGlyph>,
133    /// A map to look up the index of the glyph for a particular char in the `glyphs` array.
134    pub char_map: FxHashMap<char, usize>,
135    /// The list of pages the contain the glyphs of this atlas. Each page is a texture
136    /// that can be used to render glyphs based on the UV data stored in `glyphs`.
137    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        // Find a page, that is capable to fit the new character or create a new
153        // page and put the character there.
154        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        // No space for the character in any of the existing pages, so create a new page.
165        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        // Raise a flag to notify users that the content of the page has changed, and
200        // it should be re-uploaded to GPU (if needed).
201        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        // Copy glyph pixels to the atlas pixels
240        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        // Map the new glyph to its Unicode position.
249        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                // Char might be missing because it wasn't requested earlier. Try to find
265                // it in the inner font and render/pack it.
266                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                    // Otherwise, search the fallback fonts for a glyph to add to the atlas.
271                    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                            // We have failed to find the character in the inner font and the fallbacks.
281                            // Every font's default character is supposed to be at index 0, so add that to the atlas
282                            // in the place of the character.
283                            self.render_glyph(font, unicode, 0, height, page_size).ok()
284                        }
285                        Err(FontError::FallbackNotLoaded) => {
286                            // If a fallback is not loaded successfully, do not write anything to the
287                            // atlas and hope that the fallbacks will be ready next time.
288                            None
289                        }
290                    }
291                };
292                glyph_index.and_then(|i| self.glyphs.get(i))
293            }
294        }
295    }
296    /// Attempt to render and return the index of the given char using the fallback fonts.
297    /// Return the index if the glyph was found and rendered using a fallback font.
298    /// Return None if the glyph was not found in any fallback font.
299    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/// A font resource and the associated data required for rendering glyphs from the font.
334#[derive(Default, Clone, Debug, Reflect, Visit)]
335pub struct Font {
336    /// The source font data, such as might come from a ttf file.
337    #[reflect(hidden)]
338    #[visit(skip)]
339    pub inner: Option<fontdue::Font>,
340    /// The atlases containing textures that allow the GPU to render glyphs from this font,
341    /// or from its fallbacks. Each font size gets its own atlas.
342    #[reflect(hidden)]
343    #[visit(skip)]
344    pub atlases: FxHashMap<FontHeight, Atlas>,
345    /// The size of each atlas page in font pixels. Each page is a square measuring `page_size` x `page_size`.
346    #[reflect(hidden)]
347    #[visit(skip)]
348    pub page_size: usize,
349    /// A font representing the bold version of this font.
350    #[visit(skip)]
351    pub bold: Option<FontResource>,
352    /// A font representing the italic version of this font.
353    #[visit(skip)]
354    pub italic: Option<FontResource>,
355    /// A font representing the bold italic version of this font.
356    #[visit(skip)]
357    pub bold_italic: Option<FontResource>,
358    /// Fallback fonts are used for rendering special characters that do not have glyphs in this
359    /// font.
360    #[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/// The size the text that a font is supposed to render.
385/// Each distinct size gets its own atlas page, and `FontHeight`
386/// allows a f32 to be hashed to look up the page.
387#[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        // Don't care about "genius" Rust decision to make f32 non-hashable. If a user is dumb enough
407        // to put NaN or any other special value as a glyph height, then it is their choice.
408        fyrox_core::hash_as_bytes(&self.0, state)
409    }
410}
411
412/// A resource that allows a font to be loaded.
413pub type FontResource = Resource<Font>;
414
415/// Fyrox's default build-in font for rendering bold italic text when no other font is specified.
416pub 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
431/// Fyrox's default build-in font for rendering italic text when no other font is specified.
432pub 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
451/// Fyrox's default build-in font for rendering bold text when no other font is specified.
452pub 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
471/// Fyrox's default build-in font for rendering text when no other font is specified.
472pub 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
491/// Wait for all subfonts of this font to be completely loaded, including recursively searching
492/// through the fallback fonts, and the fallbacks of the fallbacks, to ensure that this font is
493/// fully ready to be used. In the event that any of the fonts failed to load, or a cycle is found
494/// in the fallbacks, then an error is returned.
495pub 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
534/// Recursively wait for all fallback fonts of the given font to be loaded,
535/// so that this font is fully ready to be rendered, and use the given stack
536/// to prevent infinite recursion due to a fallback cycle. An error is returned
537/// if a cycle is detected.
538async 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    /// The name of the font, if available.
572    pub fn name(&self) -> Option<&str> {
573        self.inner.as_ref().and_then(|f| f.name())
574    }
575
576    /// Create a font from an u8 array of font data such as one might get from a font file.
577    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    /// Asynchronously read font data from the file at the given path to construct a font.
596    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    /// Tries to get a glyph at the given Unicode position of the given height. If there's no rendered
632    /// glyph, this method tries to render the glyph and put into a suitable atlas (see [`Atlas`] docs
633    /// for more info). If the given Unicode position has no representation in the font, [`None`] will
634    /// be returned. If the requested size of the glyph is too big to fit into the page size of the
635    /// font, [`None`] will be returned. Keep in mind that this method is free to create as many atlases
636    /// with any number of pages in them. Each atlas corresponds to a particular glyph size, each glyph
637    /// in the atlas could be rendered at any page in the atlas.
638    #[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    /// The highest point of any glyph of this font above the baseline, usually positive.
658    #[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    /// The lowest point of any glyph of this font below the baseline, usually negative.
669    #[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    /// The horizontal scaled kerning value for the given two characters, if available,
680    /// scaled to the given text height. The kerning value is usually negative, and it is added
681    /// to the advance of the left glyph to bring the right glyph closer when appropriate for some
682    /// pairs of glyphs.
683    #[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    /// The size of each atlas page in font pixels. Each page is a square measuring `page_size` x `page_size`.
692    #[inline]
693    pub fn page_size(&self) -> usize {
694        self.page_size
695    }
696
697    /// The horizontal distance between the start of the glyph for the given character and the start of the next
698    /// glyph, when rendered for the given text height.
699    #[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
706/// Font builder allows you to load fonts in a declarative manner.
707pub struct FontBuilder {
708    /// The size of each atlas page in font pixels. Each page is a square measuring `page_size` x `page_size`.
709    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    /// Creates a default FontBuilder.
718    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    /// The size of each atlas page in font pixels. Each page is a square measuring `page_size` x `page_size`.
729    pub fn with_page_size(mut self, size: usize) -> Self {
730        self.page_size = size;
731        self
732    }
733
734    /// The bold version of this font.
735    pub fn with_bold(mut self, font: FontResource) -> Self {
736        self.bold = Some(font);
737        self
738    }
739
740    /// The italic version of this font.
741    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    /// A fallback font to supply glyphs for special characters that are not represented in
752    /// this font.
753    pub fn with_fallback(mut self, font: FontResource) -> Self {
754        self.fallbacks.push(Some(font));
755        self
756    }
757
758    /// A list of fallback fonts to supply glyphs for special characters that are not represented
759    /// in this font, replacing any existing fallbacks for this font.
760    pub fn with_fallbacks(mut self, fallbacks: Vec<Option<FontResource>>) -> Self {
761        self.fallbacks = fallbacks;
762        self
763    }
764
765    /// Build the options object for this font.
766    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    /// Creates a new font from the data at the specified path.
777    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    /// Creates a new font from bytes in memory.
787    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}