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