hephae_text/
layout.rs

1//! Defines font layout computation systems.
2
3use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
4
5use async_channel::{Receiver, Sender};
6use bevy_asset::prelude::*;
7use bevy_ecs::prelude::*;
8use bevy_image::prelude::*;
9use bevy_math::prelude::*;
10use bevy_tasks::IoTaskPool;
11use bevy_utils::HashMap;
12use cosmic_text::{
13    Attrs, Buffer, CacheKey, Family, FontSystem, Metrics, Shaping, SwashCache,
14    fontdb::{Database, Source},
15    ttf_parser::{Face, FaceParsingError},
16};
17use scopeguard::{Always, ScopeGuard};
18use thiserror::Error;
19
20use crate::{
21    atlas::{FontAtlas, FontAtlasKey, FontAtlases},
22    def::{Font, TextAlign, TextFont, TextGlyph, TextGlyphs, TextWrap},
23};
24
25/// Global handle to the font layout, wrapped in a mutex.
26#[derive(Resource)]
27pub struct FontLayout(pub(crate) Mutex<FontLayoutInner>);
28impl FontLayout {
29    /// Gets a reference to the inner resource. When possible, always prefer [`Self::get_mut`].
30    #[inline]
31    pub fn get(&self) -> MutexGuard<FontLayoutInner> {
32        self.0.lock().unwrap_or_else(PoisonError::into_inner)
33    }
34
35    /// Gets a reference to the inner resource.
36    #[inline]
37    pub fn get_mut(&mut self) -> &mut FontLayoutInner {
38        self.0.get_mut().unwrap_or_else(PoisonError::into_inner)
39    }
40}
41
42/// Handles computations for font glyphs.
43pub struct FontLayoutInner {
44    sys: FontSystem,
45    cache: SwashCache,
46    pending_fonts: Receiver<(Vec<u8>, Sender<Result<Font, FaceParsingError>>)>,
47    font_atlases: HashMap<AssetId<Font>, FontAtlases>,
48    spans: Vec<(&'static str, &'static TextFont)>,
49    glyph_spans: Vec<(AssetId<Font>, FontAtlasKey)>,
50}
51
52impl FontLayoutInner {
53    /// Creates a new font layout pipeline.
54    #[inline]
55    pub(crate) fn new(pending_fonts: Receiver<(Vec<u8>, Sender<Result<Font, FaceParsingError>>)>) -> Self {
56        let locale = sys_locale::get_locale().unwrap_or("en-US".into());
57        Self {
58            sys: FontSystem::new_with_locale_and_db(locale, Database::new()),
59            cache: SwashCache::new(),
60            pending_fonts,
61            font_atlases: HashMap::new(),
62            spans: Vec::new(),
63            glyph_spans: Vec::new(),
64        }
65    }
66}
67
68/// loads bytes sent from [`FontLoader`](crate::def::FontLoader) into a [`Font`] and adds them to
69/// the database.
70pub fn load_fonts_to_database(mut fonts: ResMut<FontLayout>) {
71    let fonts = fonts.get_mut();
72    while let Ok((bytes, sender)) = fonts.pending_fonts.try_recv() {
73        if let Err(e) = Face::parse(&bytes, 0) {
74            IoTaskPool::get().spawn(async move { _ = sender.send(Err(e)).await }).detach();
75            continue
76        }
77
78        // None of these unwraps should fail, as `Face::parse` has already ensured a valid 0th face.
79        let src = Arc::new(bytes.into_boxed_slice());
80        let id = fonts.sys.db_mut().load_font_source(Source::Binary(src))[0];
81
82        let info = fonts.sys.db().face(id).unwrap();
83        let name = info
84            .families
85            .first()
86            .map(|(name, _)| name)
87            .cloned()
88            .unwrap_or("Times New Roman".into());
89        let style = info.style;
90        let weight = info.weight;
91        let stretch = info.stretch;
92
93        IoTaskPool::get()
94            .spawn(async move {
95                _ = sender
96                    .send(Ok(Font {
97                        id,
98                        name,
99                        style,
100                        weight,
101                        stretch,
102                    }))
103                    .await
104            })
105            .detach()
106    }
107}
108
109/// Errors that may arise from font computations.
110#[derive(Error, Debug)]
111pub enum FontLayoutError {
112    /// A font has not been loaded yet.
113    #[error("required font hasn't been loaded yet or has failed loading")]
114    FontNotLoaded(AssetId<Font>),
115    /// Couldn't get an image for a glyph.
116    #[error("couldn't render an image for a glyph")]
117    NoGlyphImage(CacheKey),
118}
119
120impl FontLayoutInner {
121    /// Computes [`TextGlyphs`].
122    pub fn compute_glyphs<'a>(
123        &mut self,
124        glyphs: &mut TextGlyphs,
125        (width, height): (Option<f32>, Option<f32>),
126        wrap: TextWrap,
127        align: TextAlign,
128        scale_factor: f32,
129        fonts: &Assets<Font>,
130        images: &mut Assets<Image>,
131        atlases: &mut Assets<FontAtlas>,
132        spans: impl Iterator<Item = (&'a str, &'a TextFont)>,
133    ) -> Result<(), FontLayoutError> {
134        glyphs.size = Vec2::ZERO;
135        glyphs.glyphs.clear();
136
137        let mut glyph_spans = std::mem::take(&mut self.glyph_spans);
138        let spans = spans.inspect(|&(.., font)| {
139            glyph_spans.push((font.font.id(), FontAtlasKey {
140                font_size: font.font_size.to_bits(),
141                antialias: font.antialias,
142            }))
143        });
144
145        let buffer = glyphs.buffer.get_mut().unwrap_or_else(PoisonError::into_inner);
146        if let Err(e) = self.update_buffer(buffer, (width, height), wrap, align, scale_factor, fonts, spans) {
147            glyph_spans.clear();
148            self.glyph_spans = glyph_spans;
149
150            return Err(e);
151        }
152
153        let buffer_size = buffer_size(buffer);
154        if let Err::<(), FontLayoutError>(e) = buffer
155            .layout_runs()
156            .flat_map(|run| run.glyphs.iter().map(move |glyph| (glyph, run.line_y)))
157            .try_for_each(|(glyph, line)| {
158                let (id, key) = glyph_spans[glyph.metadata];
159
160                let mut tmp;
161                let glyph = if !key.antialias {
162                    tmp = glyph.clone();
163                    tmp.x = tmp.x.round();
164                    tmp.y = tmp.y.round();
165                    tmp.w = tmp.w.round();
166                    tmp.x_offset = tmp.x_offset.round();
167                    tmp.y_offset = tmp.y_offset.round();
168                    tmp.line_height_opt = tmp.line_height_opt.map(f32::round);
169                    &tmp
170                } else {
171                    glyph
172                };
173
174                let atlas_set = self.font_atlases.entry(id).or_default();
175                let phys = glyph.physical((0., 0.), 1.);
176                let (atlas_id, atlas) = atlas_set.atlas_mut(key, atlases);
177
178                let (offset, rect, index) = atlas.get_or_create_info(&mut self.sys, &mut self.cache, glyph, images)?;
179                let size = (rect.max - rect.min).as_vec2();
180                let (top, left) = (offset.y as f32, offset.x as f32);
181
182                let x = left + phys.x as f32;
183                let y = buffer_size.y - (line.round() + phys.y as f32 - (top - size.y));
184
185                glyphs.glyphs.push(TextGlyph {
186                    origin: Vec2::new(x, y),
187                    size,
188                    atlas: atlas_id,
189                    index,
190                });
191
192                Ok(())
193            })
194        {
195            glyphs.glyphs.clear();
196            glyph_spans.clear();
197            self.glyph_spans = glyph_spans;
198
199            return Err(e);
200        }
201
202        glyph_spans.clear();
203        self.glyph_spans = glyph_spans;
204
205        glyphs.size = buffer_size;
206        Ok(())
207    }
208
209    /// Gets the box size of a text.
210    #[inline]
211    pub fn measure_glyphs<'a>(
212        &mut self,
213        glyphs: &TextGlyphs,
214        (width, height): (Option<f32>, Option<f32>),
215        wrap: TextWrap,
216        align: TextAlign,
217        scale_factor: f32,
218        fonts: &Assets<Font>,
219        spans: impl Iterator<Item = (&'a str, &'a TextFont)>,
220    ) -> Result<Vec2, FontLayoutError> {
221        let mut buffer = glyphs.buffer.lock().unwrap_or_else(PoisonError::into_inner);
222        self.update_buffer(&mut buffer, (width, height), wrap, align, scale_factor, fonts, spans)?;
223
224        Ok(buffer_size(&buffer))
225    }
226
227    /// Gets the box size of a precomputed text.
228    #[inline]
229    pub fn size(&self, glyphs: &TextGlyphs) -> Vec2 {
230        buffer_size(&glyphs.buffer.lock().unwrap_or_else(PoisonError::into_inner))
231    }
232
233    fn update_buffer<'a>(
234        &mut self,
235        buffer: &mut Buffer,
236        (width, height): (Option<f32>, Option<f32>),
237        wrap: TextWrap,
238        align: TextAlign,
239        scale_factor: f32,
240        fonts: &Assets<Font>,
241        spans: impl Iterator<Item = (&'a str, &'a TextFont)>,
242    ) -> Result<(), FontLayoutError> {
243        /// Delegates [`std::mem::transmute`] to shrink the vector element's lifetime, but with
244        /// invariant mutable reference lifetime to the vector so it may not be accessed while the
245        /// guard is active.
246        ///
247        /// # Safety:
248        /// - The guard must **not** be passed anywhere else. Ideally, you'd want to immediately
249        ///   dereference it just to make sure.
250        /// - The drop glue of the guard must be called, i.e., [`std::mem::forget`] may not be
251        ///   called. This is to ensure the `'a` lifetime objects are cleared out.
252        #[inline]
253        #[allow(unsafe_op_in_unsafe_fn)]
254        unsafe fn guard<'a, 'this: 'a>(
255            spans: &'this mut Vec<(&'static str, &'static TextFont)>,
256        ) -> ScopeGuard<&'this mut Vec<(&'a str, &'a TextFont)>, fn(&mut Vec<(&'a str, &'a TextFont)>), Always> {
257            // Safety: We only change the lifetime, so the value is valid for both types.
258            ScopeGuard::with_strategy(
259                std::mem::transmute::<
260                    &'this mut Vec<(&'static str, &'static TextFont)>,
261                    &'this mut Vec<(&'a str, &'a TextFont)>,
262                >(spans),
263                Vec::clear,
264            )
265        }
266
267        // Safety: The guard is guaranteed not to be dropped early since it's immediately dereferenced.
268        let spans_vec = &mut **unsafe { guard(&mut self.spans) };
269        let sys = &mut self.sys;
270
271        let mut font_size = f32::MIN_POSITIVE;
272        for (span, font) in spans {
273            if span.is_empty() || font.font_size <= 0. || font.line_height <= 0. {
274                continue;
275            }
276
277            if !fonts.contains(&font.font) {
278                return Err(FontLayoutError::FontNotLoaded(font.font.id()));
279            }
280
281            font_size = font_size.max(font.font_size);
282            spans_vec.push((span, font));
283        }
284
285        let mut buffer = buffer.borrow_with(sys);
286        buffer.lines.clear();
287        buffer.set_metrics_and_size(Metrics::relative(font_size, 1.2).scale(scale_factor), width, height);
288        buffer.set_wrap(wrap.into());
289        buffer.set_rich_text(
290            spans_vec.iter().enumerate().map(|(span_index, &(span, font))| {
291                // The unwrap won't fail because the existence of the fonts have been checked.
292                let info = fonts.get(&font.font).unwrap();
293                (
294                    span,
295                    Attrs::new()
296                        .family(Family::Name(&info.name))
297                        .stretch(info.stretch)
298                        .style(info.style)
299                        .weight(info.weight)
300                        .metadata(span_index)
301                        .metrics(Metrics::relative(font.font_size, font.line_height)),
302                )
303            }),
304            Attrs::new(),
305            Shaping::Advanced,
306            Some(align.into()),
307        );
308
309        buffer.shape_until_scroll(false);
310        Ok(())
311    }
312}
313
314fn buffer_size(buffer: &Buffer) -> Vec2 {
315    let (width, height) = buffer
316        .layout_runs()
317        .map(|run| (run.line_w, run.line_height))
318        .reduce(|(w1, h1), (w2, h2)| (w1.max(w2), h1 + h2))
319        .unwrap_or((0., 0.));
320
321    Vec2::new(width, height).ceil()
322}