1use bevy_asset::{Asset, AssetEvent, AssetId, Assets};
2use bevy_ecs::{event::EventReader, resource::Resource, system::ResMut};
3use bevy_image::prelude::*;
4use bevy_math::{IVec2, UVec2};
5use bevy_platform::collections::HashMap;
6use bevy_reflect::TypePath;
7use bevy_render::{
8 render_asset::RenderAssetUsages,
9 render_resource::{Extent3d, TextureDimension, TextureFormat},
10};
11
12use crate::{error::TextError, Font, FontAtlas, FontSmoothing, GlyphAtlasInfo};
13
14#[derive(Debug, Default, Resource)]
16pub struct FontAtlasSets {
17 pub(crate) sets: HashMap<AssetId<Font>, FontAtlasSet>,
19}
20
21impl FontAtlasSets {
22 pub fn get(&self, id: impl Into<AssetId<Font>>) -> Option<&FontAtlasSet> {
24 let id: AssetId<Font> = id.into();
25 self.sets.get(&id)
26 }
27 pub fn get_mut(&mut self, id: impl Into<AssetId<Font>>) -> Option<&mut FontAtlasSet> {
29 let id: AssetId<Font> = id.into();
30 self.sets.get_mut(&id)
31 }
32}
33
34pub fn remove_dropped_font_atlas_sets(
36 mut font_atlas_sets: ResMut<FontAtlasSets>,
37 mut font_events: EventReader<AssetEvent<Font>>,
38) {
39 for event in font_events.read() {
40 if let AssetEvent::Removed { id } = event {
41 font_atlas_sets.sets.remove(id);
42 }
43 }
44}
45
46#[derive(Debug, Hash, PartialEq, Eq)]
50pub struct FontAtlasKey(pub u32, pub FontSmoothing);
51
52#[derive(Debug, TypePath, Asset)]
69pub struct FontAtlasSet {
70 font_atlases: HashMap<FontAtlasKey, Vec<FontAtlas>>,
71}
72
73impl Default for FontAtlasSet {
74 fn default() -> Self {
75 FontAtlasSet {
76 font_atlases: HashMap::with_capacity_and_hasher(1, Default::default()),
77 }
78 }
79}
80
81impl FontAtlasSet {
82 pub fn iter(&self) -> impl Iterator<Item = (&FontAtlasKey, &Vec<FontAtlas>)> {
84 self.font_atlases.iter()
85 }
86
87 pub fn has_glyph(&self, cache_key: cosmic_text::CacheKey, font_size: &FontAtlasKey) -> bool {
89 self.font_atlases
90 .get(font_size)
91 .is_some_and(|font_atlas| font_atlas.iter().any(|atlas| atlas.has_glyph(cache_key)))
92 }
93
94 pub fn add_glyph_to_atlas(
96 &mut self,
97 texture_atlases: &mut Assets<TextureAtlasLayout>,
98 textures: &mut Assets<Image>,
99 font_system: &mut cosmic_text::FontSystem,
100 swash_cache: &mut cosmic_text::SwashCache,
101 layout_glyph: &cosmic_text::LayoutGlyph,
102 font_smoothing: FontSmoothing,
103 ) -> Result<GlyphAtlasInfo, TextError> {
104 let physical_glyph = layout_glyph.physical((0., 0.), 1.0);
105
106 let font_atlases = self
107 .font_atlases
108 .entry(FontAtlasKey(
109 physical_glyph.cache_key.font_size_bits,
110 font_smoothing,
111 ))
112 .or_insert_with(|| {
113 vec![FontAtlas::new(
114 textures,
115 texture_atlases,
116 UVec2::splat(512),
117 font_smoothing,
118 )]
119 });
120
121 let (glyph_texture, offset) = Self::get_outlined_glyph_texture(
122 font_system,
123 swash_cache,
124 &physical_glyph,
125 font_smoothing,
126 )?;
127 let mut add_char_to_font_atlas = |atlas: &mut FontAtlas| -> Result<(), TextError> {
128 atlas.add_glyph(
129 textures,
130 texture_atlases,
131 physical_glyph.cache_key,
132 &glyph_texture,
133 offset,
134 )
135 };
136 if !font_atlases
137 .iter_mut()
138 .any(|atlas| add_char_to_font_atlas(atlas).is_ok())
139 {
140 let glyph_max_size: u32 = glyph_texture
142 .texture_descriptor
143 .size
144 .height
145 .max(glyph_texture.width());
146 let containing = (1u32 << (32 - glyph_max_size.leading_zeros())).max(512);
148 font_atlases.push(FontAtlas::new(
149 textures,
150 texture_atlases,
151 UVec2::splat(containing),
152 font_smoothing,
153 ));
154
155 font_atlases.last_mut().unwrap().add_glyph(
156 textures,
157 texture_atlases,
158 physical_glyph.cache_key,
159 &glyph_texture,
160 offset,
161 )?;
162 }
163
164 Ok(self
165 .get_glyph_atlas_info(physical_glyph.cache_key, font_smoothing)
166 .unwrap())
167 }
168
169 pub fn get_glyph_atlas_info(
171 &mut self,
172 cache_key: cosmic_text::CacheKey,
173 font_smoothing: FontSmoothing,
174 ) -> Option<GlyphAtlasInfo> {
175 self.font_atlases
176 .get(&FontAtlasKey(cache_key.font_size_bits, font_smoothing))
177 .and_then(|font_atlases| {
178 font_atlases.iter().find_map(|atlas| {
179 atlas
180 .get_glyph_index(cache_key)
181 .map(|location| GlyphAtlasInfo {
182 location,
183 texture_atlas: atlas.texture_atlas.clone_weak(),
184 texture: atlas.texture.clone_weak(),
185 })
186 })
187 })
188 }
189
190 pub fn len(&self) -> usize {
192 self.font_atlases.len()
193 }
194
195 pub fn is_empty(&self) -> bool {
197 self.font_atlases.len() == 0
198 }
199
200 pub fn get_outlined_glyph_texture(
202 font_system: &mut cosmic_text::FontSystem,
203 swash_cache: &mut cosmic_text::SwashCache,
204 physical_glyph: &cosmic_text::PhysicalGlyph,
205 font_smoothing: FontSmoothing,
206 ) -> Result<(Image, IVec2), TextError> {
207 let image = swash_cache
216 .get_image_uncached(font_system, physical_glyph.cache_key)
217 .ok_or(TextError::FailedToGetGlyphImage(physical_glyph.cache_key))?;
218
219 let cosmic_text::Placement {
220 left,
221 top,
222 width,
223 height,
224 } = image.placement;
225
226 let data = match image.content {
227 cosmic_text::SwashContent::Mask => {
228 if font_smoothing == FontSmoothing::None {
229 image
230 .data
231 .iter()
232 .flat_map(|a| [255, 255, 255, if *a > 127 { 255 } else { 0 }])
234 .collect()
235 } else {
236 image
237 .data
238 .iter()
239 .flat_map(|a| [255, 255, 255, *a])
240 .collect()
241 }
242 }
243 cosmic_text::SwashContent::Color => image.data,
244 cosmic_text::SwashContent::SubpixelMask => {
245 todo!()
247 }
248 };
249
250 Ok((
251 Image::new(
252 Extent3d {
253 width,
254 height,
255 depth_or_array_layers: 1,
256 },
257 TextureDimension::D2,
258 data,
259 TextureFormat::Rgba8UnormSrgb,
260 RenderAssetUsages::MAIN_WORLD,
261 ),
262 IVec2::new(left, top),
263 ))
264 }
265}