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::{Extent3d, TextureDimension, TextureFormat};
6
7use crate::{FontSmoothing, GlyphAtlasInfo, GlyphAtlasLocation, TextError};
8
9pub struct FontAtlas {
22 pub dynamic_texture_atlas_builder: DynamicTextureAtlasBuilder,
24 pub glyph_to_atlas_index: HashMap<cosmic_text::CacheKey, GlyphAtlasLocation>,
26 pub texture_atlas: Handle<TextureAtlasLayout>,
28 pub texture: Handle<Image>,
30}
31
32impl FontAtlas {
33 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 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 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 pub fn has_glyph(&self, cache_key: cosmic_text::CacheKey) -> bool {
68 self.glyph_to_atlas_index.contains_key(&cache_key)
69 }
70
71 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
91 .get_mut(&self.texture_atlas)
92 .ok_or(TextError::MissingAtlasLayout)?;
93 let atlas_texture = textures
94 .get_mut(&self.texture)
95 .ok_or(TextError::MissingAtlasTexture)?;
96
97 if let Ok(glyph_index) =
98 self.dynamic_texture_atlas_builder
99 .add_texture(atlas_layout, texture, atlas_texture)
100 {
101 self.glyph_to_atlas_index.insert(
102 cache_key,
103 GlyphAtlasLocation {
104 glyph_index,
105 offset,
106 },
107 );
108 Ok(())
109 } else {
110 Err(TextError::FailedToAddGlyph(cache_key.glyph_id))
111 }
112 }
113}
114
115impl core::fmt::Debug for FontAtlas {
116 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
117 f.debug_struct("FontAtlas")
118 .field("glyph_to_atlas_index", &self.glyph_to_atlas_index)
119 .field("texture_atlas", &self.texture_atlas)
120 .field("texture", &self.texture)
121 .field("dynamic_texture_atlas_builder", &"[...]")
122 .finish()
123 }
124}
125
126pub fn add_glyph_to_atlas(
128 font_atlases: &mut Vec<FontAtlas>,
129 texture_atlases: &mut Assets<TextureAtlasLayout>,
130 textures: &mut Assets<Image>,
131 font_system: &mut cosmic_text::FontSystem,
132 swash_cache: &mut cosmic_text::SwashCache,
133 layout_glyph: &cosmic_text::LayoutGlyph,
134 font_smoothing: FontSmoothing,
135) -> Result<GlyphAtlasInfo, TextError> {
136 let physical_glyph = layout_glyph.physical((0., 0.), 1.0);
137
138 let (glyph_texture, offset) =
139 get_outlined_glyph_texture(font_system, swash_cache, &physical_glyph, font_smoothing)?;
140 let mut add_char_to_font_atlas = |atlas: &mut FontAtlas| -> Result<(), TextError> {
141 atlas.add_glyph(
142 textures,
143 texture_atlases,
144 physical_glyph.cache_key,
145 &glyph_texture,
146 offset,
147 )
148 };
149 if !font_atlases
150 .iter_mut()
151 .any(|atlas| add_char_to_font_atlas(atlas).is_ok())
152 {
153 let glyph_max_size: u32 = glyph_texture
155 .texture_descriptor
156 .size
157 .height
158 .max(glyph_texture.width());
159 let containing = (1u32 << (32 - glyph_max_size.leading_zeros())).max(512);
161
162 let mut new_atlas = FontAtlas::new(
163 textures,
164 texture_atlases,
165 UVec2::splat(containing),
166 font_smoothing,
167 );
168
169 new_atlas.add_glyph(
170 textures,
171 texture_atlases,
172 physical_glyph.cache_key,
173 &glyph_texture,
174 offset,
175 )?;
176
177 font_atlases.push(new_atlas);
178 }
179
180 get_glyph_atlas_info(font_atlases, physical_glyph.cache_key)
181 .ok_or(TextError::InconsistentAtlasState)
182}
183
184pub fn get_outlined_glyph_texture(
186 font_system: &mut cosmic_text::FontSystem,
187 swash_cache: &mut cosmic_text::SwashCache,
188 physical_glyph: &cosmic_text::PhysicalGlyph,
189 font_smoothing: FontSmoothing,
190) -> Result<(Image, IVec2), TextError> {
191 let image = swash_cache
200 .get_image_uncached(font_system, physical_glyph.cache_key)
201 .ok_or(TextError::FailedToGetGlyphImage(physical_glyph.cache_key))?;
202
203 let cosmic_text::Placement {
204 left,
205 top,
206 width,
207 height,
208 } = image.placement;
209
210 let data = match image.content {
211 cosmic_text::SwashContent::Mask => {
212 if font_smoothing == FontSmoothing::None {
213 image
214 .data
215 .iter()
216 .flat_map(|a| [255, 255, 255, if *a > 127 { 255 } else { 0 }])
218 .collect()
219 } else {
220 image
221 .data
222 .iter()
223 .flat_map(|a| [255, 255, 255, *a])
224 .collect()
225 }
226 }
227 cosmic_text::SwashContent::Color => image.data,
228 cosmic_text::SwashContent::SubpixelMask => {
229 todo!()
231 }
232 };
233
234 Ok((
235 Image::new(
236 Extent3d {
237 width,
238 height,
239 depth_or_array_layers: 1,
240 },
241 TextureDimension::D2,
242 data,
243 TextureFormat::Rgba8UnormSrgb,
244 RenderAssetUsages::MAIN_WORLD,
245 ),
246 IVec2::new(left, top),
247 ))
248}
249
250pub fn get_glyph_atlas_info(
252 font_atlases: &mut [FontAtlas],
253 cache_key: cosmic_text::CacheKey,
254) -> Option<GlyphAtlasInfo> {
255 font_atlases.iter().find_map(|atlas| {
256 atlas
257 .get_glyph_index(cache_key)
258 .map(|location| GlyphAtlasInfo {
259 location,
260 texture_atlas: atlas.texture_atlas.id(),
261 texture: atlas.texture.id(),
262 })
263 })
264}