1use bevy_asset::{RenderAssetUsages, prelude::*};
4use bevy_ecs::prelude::*;
5use bevy_image::prelude::*;
6use bevy_math::{ivec2, prelude::*, uvec2};
7use bevy_reflect::prelude::*;
8use bevy_render::{
9 Extract,
10 render_resource::{Extent3d, TextureDimension, TextureFormat},
11};
12use bevy_utils::HashMap;
13use cosmic_text::{CacheKey, FontSystem, LayoutGlyph, Placement, SwashCache, SwashContent};
14use guillotiere::{AtlasAllocator, size2};
15
16use crate::layout::FontLayoutError;
17
18#[derive(Default)]
19pub(crate) struct FontAtlases(HashMap<FontAtlasKey, Handle<FontAtlas>>);
20impl FontAtlases {
21 #[inline]
22 pub fn atlas_mut<'a>(
23 &mut self,
24 key: FontAtlasKey,
25 atlases: &'a mut Assets<FontAtlas>,
26 ) -> (AssetId<FontAtlas>, &'a mut FontAtlas) {
27 let id = self
28 .0
29 .entry(key)
30 .or_insert_with(|| {
31 atlases.add(FontAtlas {
32 key,
33 alloc: AtlasAllocator::new(size2(512, 512)),
34 image: Handle::Weak(AssetId::invalid()),
35 map: HashMap::new(),
36 nodes: Vec::new(),
37 })
38 })
39 .id();
40
41 (id, atlases.get_mut(id).unwrap())
42 }
43}
44
45#[derive(Copy, Clone, Hash, Eq, PartialEq)]
46pub(crate) struct FontAtlasKey {
47 pub font_size: u32,
48 pub antialias: bool,
49}
50
51#[derive(Asset, TypePath)]
53pub struct FontAtlas {
54 key: FontAtlasKey,
55 alloc: AtlasAllocator,
56 image: Handle<Image>,
57 map: HashMap<CacheKey, usize>,
58 nodes: Vec<(IVec2, URect)>,
59}
60
61impl FontAtlas {
62 #[inline]
64 pub fn image(&self) -> AssetId<Image> {
65 self.image.id()
66 }
67
68 #[inline]
70 pub fn size(&self) -> UVec2 {
71 let size = self.alloc.size().cast::<u32>();
72 uvec2(size.width, size.height)
73 }
74
75 #[inline]
77 pub fn get_info(&self, glyph: &LayoutGlyph) -> Option<(IVec2, URect, usize)> {
78 self.map.get(&glyph.physical((0., 0.), 1.).cache_key).and_then(|&index| {
79 let (offset, rect) = self.get_info_index(index)?;
80 Some((offset, rect, index))
81 })
82 }
83
84 #[inline]
86 pub fn get_info_index(&self, index: usize) -> Option<(IVec2, URect)> {
87 Some(*self.nodes.get(index)?)
88 }
89
90 pub fn get_or_create_info(
92 &mut self,
93 sys: &mut FontSystem,
94 cache: &mut SwashCache,
95 glyph: &LayoutGlyph,
96 images: &mut Assets<Image>,
97 ) -> Result<(IVec2, URect, usize), FontLayoutError> {
98 if let Some(info) = self.get_info(glyph) {
99 return Ok(info);
100 }
101
102 let phys = glyph.physical((0., 0.), 1.);
103 let swash_image = cache
104 .get_image_uncached(sys, phys.cache_key)
105 .ok_or(FontLayoutError::NoGlyphImage(phys.cache_key))?;
106
107 let Placement {
108 left,
109 top,
110 width,
111 height,
112 } = swash_image.placement;
113
114 if width == 0 || height == 0 {
115 self.map.insert(phys.cache_key, self.nodes.len());
116 self.nodes.push((ivec2(left, top), URect::new(0, 0, width, height)));
117
118 Ok((ivec2(left, top), URect::new(0, 0, width, height), self.nodes.len() - 1))
119 } else {
120 loop {
121 match self.alloc.allocate(size2(width as i32 + 2, height as i32 + 2)) {
122 Some(alloc) => {
123 let mut rect = alloc.rectangle.cast::<u32>();
124 rect.min.x += 1;
125 rect.min.y += 1;
126 rect.max.x -= 1;
127 rect.max.y -= 1;
128
129 let alloc_size = self.alloc.size().cast::<u32>();
130
131 self.map.insert(phys.cache_key, self.nodes.len());
132 self.nodes
133 .push((ivec2(left, top), URect::new(rect.min.x, rect.min.y, rect.max.x, rect.max.y)));
134
135 let image = match images.get_mut(&self.image) {
136 Some(image)
137 if {
138 let size = image.texture_descriptor.size;
139 size.width == alloc_size.width && size.height == alloc_size.height
140 } =>
141 {
142 image
143 }
144 _ => {
145 let old = images.remove(&self.image);
146 let new = Image::new(
147 Extent3d {
148 width: alloc_size.width,
149 height: alloc_size.height,
150 depth_or_array_layers: 1,
151 },
152 TextureDimension::D2,
153 match old {
154 Some(old) => {
155 let old_size = old.texture_descriptor.size;
156 let mut data = Vec::with_capacity(
157 alloc_size.width as usize * alloc_size.height as usize * 4,
158 );
159
160 let copy_amount = (old_size.width.min(alloc_size.width) * 4) as usize;
161 let copy_left =
162 alloc_size.width.saturating_sub(old_size.width.min(alloc_size.width));
163
164 for y in 0..old_size.height as usize {
165 data.extend_from_slice(
166 &old.data[(y * copy_amount)..((y + 1) * copy_amount)],
167 );
168 for _ in 0..copy_left {
169 data.extend_from_slice(&[0, 0, 0, 0]);
170 }
171 }
172
173 data
174 }
175 None => vec![0; alloc_size.width as usize * alloc_size.height as usize * 4],
176 },
177 TextureFormat::Rgba8UnormSrgb,
178 RenderAssetUsages::MAIN_WORLD | RenderAssetUsages::RENDER_WORLD,
179 );
180
181 self.image = images.add(new);
182 images.get_mut(&self.image).unwrap()
183 }
184 };
185
186 let row = image.texture_descriptor.size.width as usize * 4;
187 let from_x = rect.min.x as usize;
188 let to_x = rect.max.x as usize;
189 let src_row = to_x - from_x;
190
191 let alpha = |a| match self.key.antialias {
192 false => {
193 if a > 127 {
194 255
195 } else {
196 0
197 }
198 }
199 true => a,
200 };
201
202 for (src_y, dst_y) in (rect.min.y as usize..rect.max.y as usize).enumerate() {
203 for (src_x, dst_x) in (from_x..to_x).enumerate() {
204 image.data[dst_y * row + dst_x * 4..dst_y * row + dst_x * 4 + 4].copy_from_slice(
205 &match swash_image.content {
206 SwashContent::Mask => {
207 [255, 255, 255, alpha(swash_image.data[src_y * src_row + src_x])]
208 }
209 SwashContent::Color => {
210 let data = &swash_image.data
211 [src_y * src_row * 4 + src_x * 4..src_y * src_row * 4 + src_x * 4 + 4];
212 [data[0], data[1], data[2], alpha(data[3])]
213 }
214 SwashContent::SubpixelMask => {
215 unimplemented!("sub-pixel antialiasing is unimplemented")
216 }
217 },
218 );
219 }
220 }
221
222 break Ok((
223 ivec2(left, top),
224 URect::new(rect.min.x, rect.min.y, rect.max.x, rect.max.y),
225 self.nodes.len() - 1,
226 ));
227 }
228 None => self.alloc.grow(self.alloc.size() * 2),
229 }
230 }
231 }
232 }
233}
234
235#[derive(Resource, Default)]
237pub struct ExtractedFontAtlases(HashMap<AssetId<FontAtlas>, ExtractedFontAtlas>);
238impl ExtractedFontAtlases {
239 #[inline]
241 pub fn get(&self, id: impl Into<AssetId<FontAtlas>>) -> Option<&ExtractedFontAtlas> {
242 self.0.get(&id.into())
243 }
244}
245
246#[derive(Default)]
248pub struct ExtractedFontAtlas {
249 image: AssetId<Image>,
250 size: UVec2,
251 nodes: Vec<(IVec2, URect)>,
252}
253
254impl ExtractedFontAtlas {
255 #[inline]
257 pub fn image(&self) -> AssetId<Image> {
258 self.image
259 }
260
261 #[inline]
263 pub fn size(&self) -> UVec2 {
264 self.size
265 }
266
267 #[inline]
269 pub fn get_info_index(&self, index: usize) -> Option<(IVec2, URect)> {
270 Some(*self.nodes.get(index)?)
271 }
272}
273
274pub fn extract_font_atlases(
276 mut extracted: ResMut<ExtractedFontAtlases>,
277 atlases: Extract<Res<Assets<FontAtlas>>>,
278 mut atlas_events: Extract<EventReader<AssetEvent<FontAtlas>>>,
279) {
280 for (id, atlas) in atlases.iter() {
281 let dst = extracted.0.entry(id).or_default();
282 dst.image = atlas.image.id();
283 dst.size = atlas.size();
284 dst.nodes.clear();
285 dst.nodes.extend(&atlas.nodes);
286 }
287
288 for &e in atlas_events.read() {
289 match e {
290 AssetEvent::Added { .. } | AssetEvent::Modified { .. } | AssetEvent::LoadedWithDependencies { .. } => {}
291 AssetEvent::Removed { id } | AssetEvent::Unused { id } => {
292 extracted.0.remove(&id);
293 }
294 }
295 }
296}