1use std::{io::Error as IoError, slice::Iter, sync::Mutex};
4
5use async_channel::Sender;
6use bevy_asset::{AssetLoader, LoadContext, io::Reader, prelude::*};
7use bevy_derive::{Deref, DerefMut};
8use bevy_ecs::{
9 prelude::*,
10 system::{
11 SystemParamItem,
12 lifetimeless::{Read, SQuery},
13 },
14};
15use bevy_hierarchy::prelude::*;
16use bevy_math::prelude::*;
17use bevy_reflect::prelude::*;
18use cosmic_text::{
19 Align, Buffer, Metrics, Stretch, Style, Weight, Wrap, fontdb::ID as FontId, ttf_parser::FaceParsingError,
20};
21use fixedbitset::FixedBitSet;
22#[cfg(feature = "locale")]
23use hephae_locale::prelude::*;
24use smallvec::SmallVec;
25use thiserror::Error;
26
27use crate::atlas::FontAtlas;
28
29#[derive(Asset, TypePath, Clone)]
31pub struct Font {
32 pub id: FontId,
34 pub name: String,
36 pub style: Style,
38 pub weight: Weight,
40 pub stretch: Stretch,
42}
43
44#[derive(Error, Debug)]
46pub enum FontError {
47 #[error("the async channel to add fonts to the database was closed")]
49 ChannelClosed,
50 #[error(transparent)]
52 Io(#[from] IoError),
53 #[error(transparent)]
55 Face(#[from] FaceParsingError),
56}
57
58pub struct FontLoader {
60 pub(crate) add_to_database: Sender<(Vec<u8>, Sender<Result<Font, FaceParsingError>>)>,
61}
62
63impl AssetLoader for FontLoader {
64 type Asset = Font;
65 type Settings = ();
66 type Error = FontError;
67
68 #[inline]
69 async fn load(
70 &self,
71 reader: &mut dyn Reader,
72 _: &Self::Settings,
73 _: &mut LoadContext<'_>,
74 ) -> Result<Self::Asset, Self::Error> {
75 let mut bytes = Vec::new();
76 reader.read_to_end(&mut bytes).await?;
77
78 let (sender, receiver) = async_channel::bounded(1);
79 if self.add_to_database.send((bytes, sender)).await.is_err() {
80 return Err(FontError::ChannelClosed);
81 }
82
83 let font = receiver.recv().await.map_err(|_| FontError::ChannelClosed)??;
84 Ok(font)
85 }
86
87 #[inline]
88 fn extensions(&self) -> &[&str] {
89 &["ttf"]
90 }
91}
92
93#[derive(Component, Reflect, Clone, Default, Deref, DerefMut)]
96#[reflect(Component, Default)]
97#[require(TextStructure, TextGlyphs)]
98pub struct Text {
99 #[deref]
101 pub text: String,
102 pub wrap: TextWrap,
104 pub align: TextAlign,
106}
107
108impl Text {
109 #[inline]
111 pub fn new(text: impl ToString) -> Self {
112 Self {
113 text: text.to_string(),
114 ..Self::default()
115 }
116 }
117}
118
119#[cfg(feature = "locale")]
120impl LocaleTarget for Text {
121 #[inline]
122 fn update(&mut self, src: &str) {
123 src.clone_into(self);
124 }
125}
126
127#[derive(Reflect, Eq, PartialEq, Copy, Clone, Default)]
129#[reflect(Default)]
130pub enum TextWrap {
131 #[default]
133 None,
134 Glyph,
136 Word,
138 WordOrGlyph,
140}
141
142impl From<TextWrap> for Wrap {
143 #[inline]
144 fn from(value: TextWrap) -> Self {
145 match value {
146 TextWrap::None => Self::None,
147 TextWrap::Glyph => Self::Glyph,
148 TextWrap::Word => Self::Word,
149 TextWrap::WordOrGlyph => Self::WordOrGlyph,
150 }
151 }
152}
153
154#[derive(Reflect, Default, Eq, PartialEq, Clone, Copy)]
156#[reflect(Default)]
157pub enum TextAlign {
158 #[default]
160 Left,
161 Right,
163 Center,
165 Justified,
167 End,
169}
170
171impl From<TextAlign> for Align {
172 #[inline]
173 fn from(value: TextAlign) -> Self {
174 match value {
175 TextAlign::Left => Self::Left,
176 TextAlign::Right => Self::Right,
177 TextAlign::Center => Self::Center,
178 TextAlign::Justified => Self::Justified,
179 TextAlign::End => Self::End,
180 }
181 }
182}
183
184#[derive(Component, Reflect, Clone)]
186#[reflect(Component, Default)]
187pub struct TextFont {
188 pub font: Handle<Font>,
190 pub font_size: f32,
192 pub line_height: f32,
194 pub antialias: bool,
196}
197
198impl Default for TextFont {
199 #[inline]
200 fn default() -> Self {
201 Self {
202 font: Handle::default(),
203 font_size: 16.,
204 line_height: 1.2,
205 antialias: true,
206 }
207 }
208}
209
210pub type STextQuery = SQuery<(Option<Read<Text>>, Option<Read<TextSpan>>, Option<Read<TextFont>>)>;
212pub type TextQuery<'w, 's> = SystemParamItem<'w, 's, STextQuery>;
214
215#[derive(Component, Clone, Deref, DerefMut, Default)]
218pub struct TextStructure(SmallVec<[(Entity, usize); 1]>);
219impl TextStructure {
220 #[inline]
222 pub fn iter<'w, 's>(&'w self, query: &'w TextQuery<'w, 's>) -> TextStructureIter<'w, 's> {
223 TextStructureIter {
224 inner: self.0.iter(),
225 fonts: SmallVec::new_const(),
226 query,
227 }
228 }
229}
230
231pub struct TextStructureIter<'w, 's: 'w> {
233 inner: Iter<'w, (Entity, usize)>,
234 fonts: SmallVec<[(&'w TextFont, usize); 4]>,
235 query: &'w TextQuery<'w, 's>,
236}
237
238impl<'w> Iterator for TextStructureIter<'w, '_> {
239 type Item = (&'w str, &'w TextFont);
240
241 fn next(&mut self) -> Option<Self::Item> {
242 const DEFAULT_FONT: TextFont = TextFont {
243 font: Handle::Weak(AssetId::Uuid {
244 uuid: AssetId::<Font>::DEFAULT_UUID,
245 }),
246 font_size: 24.,
247 line_height: 1.2,
248 antialias: true,
249 };
250
251 let &(e, depth) = self.inner.next()?;
252 let (text, span, font) = self.query.get(e).ok()?;
253 let str = match (text, span) {
254 (Some(text), ..) => text.as_str(),
255 (None, Some(span)) => span.as_str(),
256 (None, None) => return None,
257 };
258
259 let font = font.unwrap_or_else(|| {
260 loop {
261 let &(last_font, last_depth) = self.fonts.last().unwrap_or(&(&DEFAULT_FONT, 0));
262 if depth > 0 && last_depth >= depth {
263 self.fonts.pop();
264 } else {
265 self.fonts.push((last_font, depth));
266 break last_font;
267 }
268 }
269 });
270
271 Some((str, font))
272 }
273}
274
275#[derive(Component)]
277pub struct TextGlyphs {
278 pub glyphs: Vec<TextGlyph>,
280 pub size: Vec2,
282 pub(crate) buffer: Mutex<Buffer>,
283}
284
285impl Default for TextGlyphs {
286 #[inline]
287 fn default() -> Self {
288 Self {
289 glyphs: Vec::new(),
290 size: Vec2::ZERO,
291 buffer: Mutex::new(Buffer::new_empty(Metrics::new(f32::MIN_POSITIVE, f32::MIN_POSITIVE))),
292 }
293 }
294}
295
296#[derive(Copy, Clone)]
298pub struct TextGlyph {
299 pub origin: Vec2,
301 pub size: Vec2,
303 pub atlas: AssetId<FontAtlas>,
305 pub index: usize,
307}
308
309#[derive(Component, Reflect, Default, Deref, DerefMut)]
311#[reflect(Component, Default)]
312pub struct TextSpan(pub String);
313
314#[cfg(feature = "locale")]
315impl LocaleTarget for TextSpan {
316 #[inline]
317 fn update(&mut self, src: &str) {
318 src.clone_into(self);
319 }
320}
321
322pub fn compute_structure(
325 mut text_query: Query<(Entity, &mut TextStructure, Option<&Children>)>,
326 recurse_query: Query<(Entity, Option<&Children>, &Parent), (With<TextSpan>, Without<Text>)>,
327 changed_query: Query<(
328 Entity,
329 Option<Ref<Text>>,
330 Option<Ref<TextSpan>>,
331 Option<Ref<Parent>>,
332 Option<Ref<Children>>,
333 )>,
334 parent_query: Query<&Parent>,
335 mut removed_span: RemovedComponents<TextSpan>,
336 mut iterated: Local<FixedBitSet>,
337 mut removed: Local<FixedBitSet>,
338 mut old: Local<SmallVec<[(Entity, usize); 1]>>,
339) {
340 iterated.clear();
341 removed.clear();
342
343 for e in removed_span.read() {
344 removed.grow_and_insert(e.index() as usize);
345 }
346
347 'out: for (e, text, span, parent, children) in &changed_query {
348 iterated.grow((e.index() + 1) as usize);
349 if iterated.put(e.index() as usize) {
350 continue 'out;
351 }
352
353 let parent_changed = parent.as_ref().is_some_and(Ref::is_changed);
354 let children_changed = children.is_some_and(|children| children.is_changed());
355
356 if match (text.as_ref(), span) {
357 (Some(text), ..) => text.is_added() || children_changed,
358 (None, Some(span)) => span.is_added() || parent_changed || children_changed,
359 (None, None) => {
360 if removed.contains(e.index() as usize) {
361 true
362 } else {
363 continue 'out;
364 }
365 }
366 } {
367 let Ok((root, mut structure, children)) = (if text.is_some() {
368 text_query.get_mut(e)
369 } else {
370 let Some(mut e) = parent.map(|p| p.get()) else {
371 continue 'out;
372 };
373 loop {
374 iterated.grow((e.index() + 1) as usize);
375 if iterated.put(e.index() as usize) {
376 continue 'out;
377 }
378
379 match text_query.get_mut(e) {
380 Ok(structure) => break Ok(structure),
381 Err(..) => match parent_query.get(e) {
382 Ok(parent) => {
383 e = parent.get();
384 continue;
385 }
386 Err(..) => continue 'out,
387 },
388 }
389 }
390 }) else {
391 continue 'out;
392 };
393
394 let inner = &mut structure.bypass_change_detection().0;
395 old.append(inner);
396
397 fn recurse(
398 structure: &mut SmallVec<[(Entity, usize); 1]>,
399 depth: usize,
400 parent: Entity,
401 children: &[Entity],
402 recurse_query: &Query<(Entity, Option<&Children>, &Parent), (With<TextSpan>, Without<Text>)>,
403 ) {
404 for (e, children, actual_parent) in recurse_query.iter_many(children) {
405 assert_eq!(
406 actual_parent.get(),
407 parent,
408 "Malformed hierarchy. This probably means that your hierarchy has been improperly maintained, or contains a cycle"
409 );
410
411 structure.push((e, depth));
412 if let Some(children) = children {
413 recurse(structure, depth + 1, e, children, recurse_query);
414 }
415 }
416 }
417
418 inner.clear();
419 inner.push((root, 0));
420 if let Some(children) = children {
421 recurse(inner, 1, root, children, &recurse_query);
422 }
423
424 if &*old != inner {
425 structure.set_changed();
426 }
427
428 old.clear();
429 }
430 }
431}
432
433pub fn notify_structure(
436 mut root_query: Query<&mut TextStructure>,
437 changed_query: Query<
438 (Option<Ref<Text>>, Option<Ref<TextSpan>>, Option<Ref<TextFont>>),
439 Or<(With<Text>, With<TextSpan>)>,
440 >,
441) {
442 'out: for mut structure in &mut root_query {
443 let inner = &structure.bypass_change_detection().0;
444 for (text, span, font) in changed_query.iter_many(inner.iter().map(|&(e, ..)| e)) {
445 if text.is_some_and(|text| text.is_changed()) ||
446 span.is_some_and(|span| span.is_changed()) ||
447 font.is_some_and(|font| font.is_changed())
448 {
449 structure.set_changed();
450 continue 'out;
451 }
452 }
453 }
454}