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