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