bevy_text/
font_atlas.rs

1use bevy_asset::{Assets, Handle, RenderAssetUsages};
2use bevy_image::{prelude::*, ImageSampler, ToExtents};
3use bevy_math::{IVec2, UVec2};
4use bevy_platform::collections::HashMap;
5use wgpu_types::{TextureDimension, TextureFormat};
6
7use crate::{FontSmoothing, GlyphAtlasLocation, TextError};
8
9/// Rasterized glyphs are cached, stored in, and retrieved from, a `FontAtlas`.
10///
11/// A `FontAtlas` contains one or more textures, each of which contains one or more glyphs packed into them.
12///
13/// A [`FontAtlasSet`](crate::FontAtlasSet) contains a `FontAtlas` for each font size in the same font face.
14///
15/// For the same font face and font size, a glyph will be rasterized differently for different subpixel offsets.
16/// In practice, ranges of subpixel offsets are grouped into subpixel bins to limit the number of rasterized glyphs,
17/// providing a trade-off between visual quality and performance.
18///
19/// A [`CacheKey`](cosmic_text::CacheKey) encodes all of the information of a subpixel-offset glyph and is used to
20/// find that glyphs raster in a [`TextureAtlas`] through its corresponding [`GlyphAtlasLocation`].
21pub struct FontAtlas {
22    /// Used to update the [`TextureAtlasLayout`].
23    pub dynamic_texture_atlas_builder: DynamicTextureAtlasBuilder,
24    /// A mapping between subpixel-offset glyphs and their [`GlyphAtlasLocation`].
25    pub glyph_to_atlas_index: HashMap<cosmic_text::CacheKey, GlyphAtlasLocation>,
26    /// The handle to the [`TextureAtlasLayout`] that holds the rasterized glyphs.
27    pub texture_atlas: Handle<TextureAtlasLayout>,
28    /// The texture where this font atlas is located
29    pub texture: Handle<Image>,
30}
31
32impl FontAtlas {
33    /// Create a new [`FontAtlas`] with the given size, adding it to the appropriate asset collections.
34    pub fn new(
35        textures: &mut Assets<Image>,
36        texture_atlases_layout: &mut Assets<TextureAtlasLayout>,
37        size: UVec2,
38        font_smoothing: FontSmoothing,
39    ) -> FontAtlas {
40        let mut image = Image::new_fill(
41            size.to_extents(),
42            TextureDimension::D2,
43            &[0, 0, 0, 0],
44            TextureFormat::Rgba8UnormSrgb,
45            // Need to keep this image CPU persistent in order to add additional glyphs later on
46            RenderAssetUsages::MAIN_WORLD | RenderAssetUsages::RENDER_WORLD,
47        );
48        if font_smoothing == FontSmoothing::None {
49            image.sampler = ImageSampler::nearest();
50        }
51        let texture = textures.add(image);
52        let texture_atlas = texture_atlases_layout.add(TextureAtlasLayout::new_empty(size));
53        Self {
54            texture_atlas,
55            glyph_to_atlas_index: HashMap::default(),
56            dynamic_texture_atlas_builder: DynamicTextureAtlasBuilder::new(size, 1),
57            texture,
58        }
59    }
60
61    /// Get the [`GlyphAtlasLocation`] for a subpixel-offset glyph.
62    pub fn get_glyph_index(&self, cache_key: cosmic_text::CacheKey) -> Option<GlyphAtlasLocation> {
63        self.glyph_to_atlas_index.get(&cache_key).copied()
64    }
65
66    /// Checks if the given subpixel-offset glyph is contained in this [`FontAtlas`].
67    pub fn has_glyph(&self, cache_key: cosmic_text::CacheKey) -> bool {
68        self.glyph_to_atlas_index.contains_key(&cache_key)
69    }
70
71    /// Add a glyph to the atlas, updating both its texture and layout.
72    ///
73    /// The glyph is represented by `glyph`, and its image content is `glyph_texture`.
74    /// This content is copied into the atlas texture, and the atlas layout is updated
75    /// to store the location of that glyph into the atlas.
76    ///
77    /// # Returns
78    ///
79    /// Returns `()` if the glyph is successfully added, or [`TextError::FailedToAddGlyph`] otherwise.
80    /// In that case, neither the atlas texture nor the atlas layout are
81    /// modified.
82    pub fn add_glyph(
83        &mut self,
84        textures: &mut Assets<Image>,
85        atlas_layouts: &mut Assets<TextureAtlasLayout>,
86        cache_key: cosmic_text::CacheKey,
87        texture: &Image,
88        offset: IVec2,
89    ) -> Result<(), TextError> {
90        let atlas_layout = atlas_layouts.get_mut(&self.texture_atlas).unwrap();
91        let atlas_texture = textures.get_mut(&self.texture).unwrap();
92
93        if let Ok(glyph_index) =
94            self.dynamic_texture_atlas_builder
95                .add_texture(atlas_layout, texture, atlas_texture)
96        {
97            self.glyph_to_atlas_index.insert(
98                cache_key,
99                GlyphAtlasLocation {
100                    glyph_index,
101                    offset,
102                },
103            );
104            Ok(())
105        } else {
106            Err(TextError::FailedToAddGlyph(cache_key.glyph_id))
107        }
108    }
109}
110
111impl core::fmt::Debug for FontAtlas {
112    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
113        f.debug_struct("FontAtlas")
114            .field("glyph_to_atlas_index", &self.glyph_to_atlas_index)
115            .field("texture_atlas", &self.texture_atlas)
116            .field("texture", &self.texture)
117            .field("dynamic_texture_atlas_builder", &"[...]")
118            .finish()
119    }
120}