spitfire_fontdue/
lib.rs

1use bytemuck::Pod;
2use etagere::{AtlasAllocator, euclid::default::Rect, size2};
3use fontdue::{
4    Font,
5    layout::{GlyphPosition, GlyphRasterConfig, Layout},
6};
7use spitfire_core::VertexStream;
8use std::{
9    collections::{HashMap, hash_map::Entry},
10    marker::PhantomData,
11};
12
13pub trait TextVertex<UD: Copy> {
14    fn apply(&mut self, position: [f32; 2], tex_coord: [f32; 3], user_data: UD);
15}
16
17#[derive(Debug, Default, Clone, Copy)]
18pub struct TextRendererGlyph {
19    pub page: usize,
20    pub rectangle: Rect<u32>,
21}
22
23pub struct TextRendererUnpacked<UD: Copy> {
24    pub glyphs: HashMap<GlyphRasterConfig, TextRendererGlyph>,
25    pub atlas_size: [usize; 3],
26    pub image: Vec<u8>,
27    pub renderables: Vec<GlyphPosition<UD>>,
28}
29
30#[derive(Clone)]
31pub struct TextRenderer<UD: Copy = ()> {
32    pub renderables_resize: usize,
33    used_glyphs: HashMap<GlyphRasterConfig, TextRendererGlyph>,
34    atlas_size: [usize; 3],
35    image: Vec<u8>,
36    atlases: Vec<AtlasAllocator>,
37    ready_to_render: Vec<GlyphPosition<UD>>,
38    _phantom: PhantomData<fn() -> UD>,
39}
40
41impl<UD: Copy> Default for TextRenderer<UD> {
42    fn default() -> Self {
43        Self::new(1024, 1024)
44    }
45}
46
47impl<UD: Copy> TextRenderer<UD> {
48    pub fn new(width: usize, height: usize) -> Self {
49        Self {
50            renderables_resize: 1024,
51            used_glyphs: Default::default(),
52            atlas_size: [width, height, 0],
53            image: Default::default(),
54            atlases: Default::default(),
55            ready_to_render: Default::default(),
56            _phantom: Default::default(),
57        }
58    }
59
60    pub fn clear(&mut self) {
61        self.used_glyphs.clear();
62        self.atlas_size[2] = 0;
63        self.image.clear();
64        self.atlases.clear();
65        self.ready_to_render.clear();
66    }
67
68    pub fn measure(layout: &Layout<UD>, fonts: &[Font], compact: bool) -> [f32; 4] {
69        let mut xmin = f32::INFINITY;
70        let mut ymin = f32::INFINITY;
71        let mut xmax = f32::NEG_INFINITY;
72        let mut ymax = f32::NEG_INFINITY;
73        if compact {
74            for glyph in layout.glyphs() {
75                if glyph.char_data.rasterize() {
76                    xmin = xmin.min(glyph.x);
77                    ymin = ymin.min(glyph.y);
78                    xmax = xmax.max(glyph.x + glyph.width as f32);
79                    ymax = ymax.max(glyph.y + glyph.height as f32);
80                }
81            }
82        } else if let Some(lines) = layout.lines() {
83            for line in lines {
84                ymin = ymin.min(line.baseline_y - line.max_ascent);
85                ymax = ymax.max(line.baseline_y - line.max_ascent + line.max_new_line_size + 1.0);
86            }
87            for glyph in layout.glyphs() {
88                if glyph.char_data.rasterize() {
89                    let font = &fonts[glyph.font_index];
90                    let metrics = font.metrics_indexed(glyph.key.glyph_index, glyph.key.px);
91                    xmin = xmin.min(glyph.x);
92                    xmax = xmax.max(glyph.x + metrics.advance_width.ceil() + 1.0);
93                }
94            }
95            xmin = layout.settings().x.min(xmin);
96            ymin = layout.settings().y.min(ymin);
97        }
98        [xmin, ymin, xmax, ymax]
99    }
100
101    pub fn include(&mut self, fonts: &[Font], layout: &Layout<UD>) {
102        for glyph in layout.glyphs() {
103            if glyph.char_data.rasterize() {
104                if self.ready_to_render.len() == self.ready_to_render.capacity() {
105                    self.ready_to_render.reserve(self.renderables_resize);
106                }
107                self.ready_to_render.push(*glyph);
108            }
109            if let Entry::Vacant(entry) = self.used_glyphs.entry(glyph.key) {
110                let font = &fonts[glyph.font_index];
111                let (metrics, coverage) = font.rasterize_config(glyph.key);
112                if glyph.char_data.rasterize() {
113                    let allocation = self
114                        .atlases
115                        .iter_mut()
116                        .enumerate()
117                        .find_map(|(page, atlas)| {
118                            Some((
119                                page,
120                                atlas
121                                    .allocate(size2(
122                                        metrics.width as i32 + 1,
123                                        metrics.height as i32 + 1,
124                                    ))?
125                                    .rectangle
126                                    .to_rect()
127                                    .origin
128                                    .to_u32(),
129                            ))
130                        })
131                        .or_else(|| {
132                            let w = self.atlas_size[0];
133                            let h = self.atlas_size[1];
134                            let mut atlas = AtlasAllocator::new(size2(w as _, h as _));
135                            let page = self.atlases.len();
136                            let origin = atlas
137                                .allocate(size2(
138                                    metrics.width as i32 + 1,
139                                    metrics.height as i32 + 1,
140                                ))?
141                                .rectangle
142                                .to_rect()
143                                .origin
144                                .to_u32();
145                            self.atlases.push(atlas);
146                            self.atlas_size[2] += 1;
147                            let [w, h, d] = self.atlas_size;
148                            self.image.resize(w * h * d, 0);
149                            Some((page, origin))
150                        });
151                    if let Some((page, origin)) = allocation {
152                        let [w, h, _] = self.atlas_size;
153                        for (index, value) in coverage.iter().enumerate() {
154                            let x = origin.x as usize + index % metrics.width;
155                            let y = origin.y as usize + index / metrics.width;
156                            let index = page * w * h + y * w + x;
157                            self.image[index] = *value;
158                        }
159                        entry.insert(TextRendererGlyph {
160                            page,
161                            rectangle: Rect::new(
162                                origin,
163                                [metrics.width as _, metrics.height as _].into(),
164                            ),
165                        });
166                    }
167                }
168            }
169        }
170    }
171
172    pub fn include_consumed(
173        &mut self,
174        fonts: &[Font],
175        layout: &Layout<UD>,
176    ) -> impl Iterator<Item = (GlyphPosition<UD>, TextRendererGlyph)> + '_ {
177        self.include(fonts, layout);
178        self.consume_renderables()
179    }
180
181    pub fn glyph(&self, key: &GlyphRasterConfig) -> Option<TextRendererGlyph> {
182        self.used_glyphs.get(key).copied()
183    }
184
185    pub fn consume_renderables(
186        &mut self,
187    ) -> impl Iterator<Item = (GlyphPosition<UD>, TextRendererGlyph)> + '_ {
188        self.ready_to_render
189            .drain(..)
190            .filter_map(|glyph| Some((glyph, *self.used_glyphs.get(&glyph.key)?)))
191    }
192
193    pub fn image(&self) -> &[u8] {
194        &self.image
195    }
196
197    pub fn atlas_size(&self) -> [usize; 3] {
198        self.atlas_size
199    }
200
201    pub fn into_image(self) -> (Vec<u8>, [usize; 3]) {
202        (self.image, self.atlas_size)
203    }
204
205    pub fn into_inner(self) -> TextRendererUnpacked<UD> {
206        TextRendererUnpacked {
207            glyphs: self.used_glyphs,
208            atlas_size: self.atlas_size,
209            image: self.image,
210            renderables: self.ready_to_render,
211        }
212    }
213
214    pub fn render_to_stream<V, B>(&mut self, stream: &mut VertexStream<V, B>)
215    where
216        V: TextVertex<UD> + Pod + Default,
217    {
218        let [w, h, _] = self.atlas_size;
219        let w = w as f32;
220        let h = h as f32;
221        for glyph in self.ready_to_render.drain(..) {
222            if let Some(data) = self.used_glyphs.get(&glyph.key) {
223                let mut a = V::default();
224                let mut b = V::default();
225                let mut c = V::default();
226                let mut d = V::default();
227                a.apply(
228                    [glyph.x, glyph.y],
229                    [
230                        data.rectangle.min_x() as f32 / w,
231                        data.rectangle.min_y() as f32 / h,
232                        data.page as f32,
233                    ],
234                    glyph.user_data,
235                );
236                b.apply(
237                    [glyph.x + glyph.width as f32, glyph.y],
238                    [
239                        data.rectangle.max_x() as f32 / w,
240                        data.rectangle.min_y() as f32 / h,
241                        data.page as f32,
242                    ],
243                    glyph.user_data,
244                );
245                c.apply(
246                    [glyph.x + glyph.width as f32, glyph.y + glyph.height as f32],
247                    [
248                        data.rectangle.max_x() as f32 / w,
249                        data.rectangle.max_y() as f32 / h,
250                        data.page as f32,
251                    ],
252                    glyph.user_data,
253                );
254                d.apply(
255                    [glyph.x, glyph.y + glyph.height as f32],
256                    [
257                        data.rectangle.min_x() as f32 / w,
258                        data.rectangle.max_y() as f32 / h,
259                        data.page as f32,
260                    ],
261                    glyph.user_data,
262                );
263                stream.quad([a, b, c, d]);
264            }
265        }
266    }
267}
268
269#[cfg(test)]
270mod tests {
271    use crate::TextRenderer;
272    use fontdue::{
273        Font,
274        layout::{CoordinateSystem, Layout, LayoutSettings, TextStyle},
275    };
276    use image::RgbImage;
277
278    #[test]
279    fn test_text_renderer() {
280        let text = include_str!("../../../resources/text.txt");
281        let font = include_bytes!("../../../resources/Roboto-Regular.ttf") as &[_];
282        let font = Font::from_bytes(font, Default::default()).unwrap();
283        let fonts = [font];
284        let mut layout = Layout::new(CoordinateSystem::PositiveYDown);
285        let mut renderer = TextRenderer::new(256, 256);
286
287        for line in text.lines() {
288            layout.append(&fonts, &TextStyle::new(line, 32.0, 0));
289        }
290
291        renderer.include(&fonts, &layout);
292        let (image, [width, height, _]) = renderer.into_image();
293        let image = RgbImage::from_vec(
294            width as _,
295            height as _,
296            image
297                .into_iter()
298                .flat_map(|value| [value, value, value])
299                .collect(),
300        )
301        .unwrap();
302        image.save("../../resources/test.png").unwrap();
303    }
304
305    #[test]
306    fn test_text_measurements() {
307        let font = include_bytes!("../../../resources/Roboto-Regular.ttf") as &[_];
308        let font = Font::from_bytes(font, Default::default()).unwrap();
309        let fonts = [font];
310        let style = TextStyle::new(include_str!("../../../resources/long_text.txt"), 32.0, 0);
311        let mut layout = Layout::new(CoordinateSystem::PositiveYDown);
312        layout.append(&fonts, &style);
313
314        let aabb_non_compact = TextRenderer::measure(&layout, &fonts, false);
315        assert_eq!(aabb_non_compact, [0.0, 0.0, 450.0, 115.0]);
316        let aabb_compact = TextRenderer::measure(&layout, &fonts, true);
317        assert_eq!(aabb_compact, [0.0, 6.0, 448.0, 113.0]);
318
319        layout.reset(&LayoutSettings {
320            max_width: Some(aabb_non_compact[2] - aabb_non_compact[0]),
321            max_height: Some(aabb_non_compact[3] - aabb_non_compact[1]),
322            ..Default::default()
323        });
324        layout.append(&fonts, &style);
325        let aabb = TextRenderer::measure(&layout, &fonts, false);
326        assert_eq!(aabb, aabb_non_compact);
327        let aabb = TextRenderer::measure(&layout, &fonts, true);
328        assert_eq!(aabb, aabb_compact);
329
330        layout.reset(&LayoutSettings {
331            max_width: Some(aabb_compact[2] - aabb_compact[0]),
332            max_height: Some(aabb_compact[3] - aabb_compact[1]),
333            ..Default::default()
334        });
335        layout.append(&fonts, &style);
336        let aabb = TextRenderer::measure(&layout, &fonts, false);
337        assert_ne!(aabb, aabb_non_compact);
338        let aabb = TextRenderer::measure(&layout, &fonts, true);
339        assert_ne!(aabb, aabb_compact);
340    }
341}