spitfire_fontdue/
lib.rs

1use bytemuck::Pod;
2use etagere::{euclid::default::Rect, size2, AtlasAllocator};
3use fontdue::{
4    layout::{GlyphPosition, GlyphRasterConfig, Layout},
5    Font,
6};
7use spitfire_core::VertexStream;
8use std::{
9    collections::{hash_map::Entry, HashMap},
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 include(&mut self, fonts: &[Font], layout: &Layout<UD>) {
69        for glyph in layout.glyphs() {
70            if glyph.char_data.rasterize() {
71                if self.ready_to_render.len() == self.ready_to_render.capacity() {
72                    self.ready_to_render.reserve(self.renderables_resize);
73                }
74                self.ready_to_render.push(*glyph);
75            }
76            if let Entry::Vacant(entry) = self.used_glyphs.entry(glyph.key) {
77                let font = &fonts[glyph.font_index];
78                let (metrics, coverage) = font.rasterize_config(glyph.key);
79                if glyph.char_data.rasterize() {
80                    let allocation = self
81                        .atlases
82                        .iter_mut()
83                        .enumerate()
84                        .find_map(|(page, atlas)| {
85                            Some((
86                                page,
87                                atlas
88                                    .allocate(size2(
89                                        metrics.width as i32 + 1,
90                                        metrics.height as i32 + 1,
91                                    ))?
92                                    .rectangle
93                                    .to_rect()
94                                    .origin
95                                    .to_u32(),
96                            ))
97                        })
98                        .or_else(|| {
99                            let w = self.atlas_size[0];
100                            let h = self.atlas_size[1];
101                            let mut atlas = AtlasAllocator::new(size2(w as _, h as _));
102                            let page = self.atlases.len();
103                            let origin = atlas
104                                .allocate(size2(
105                                    metrics.width as i32 + 1,
106                                    metrics.height as i32 + 1,
107                                ))?
108                                .rectangle
109                                .to_rect()
110                                .origin
111                                .to_u32();
112                            self.atlases.push(atlas);
113                            self.atlas_size[2] += 1;
114                            let [w, h, d] = self.atlas_size;
115                            self.image.resize(w * h * d, 0);
116                            Some((page, origin))
117                        });
118                    if let Some((page, origin)) = allocation {
119                        let [w, h, _] = self.atlas_size;
120                        for (index, value) in coverage.iter().enumerate() {
121                            let x = origin.x as usize + index % metrics.width;
122                            let y = origin.y as usize + index / metrics.width;
123                            let index = page * w * h + y * w + x;
124                            self.image[index] = *value;
125                        }
126                        entry.insert(TextRendererGlyph {
127                            page,
128                            rectangle: Rect::new(
129                                origin,
130                                [metrics.width as _, metrics.height as _].into(),
131                            ),
132                        });
133                    }
134                }
135            }
136        }
137    }
138
139    pub fn include_consumed(
140        &mut self,
141        fonts: &[Font],
142        layout: &Layout<UD>,
143    ) -> impl Iterator<Item = (GlyphPosition<UD>, TextRendererGlyph)> + '_ {
144        self.include(fonts, layout);
145        self.consume_renderables()
146    }
147
148    pub fn glyph(&self, key: &GlyphRasterConfig) -> Option<TextRendererGlyph> {
149        self.used_glyphs.get(key).copied()
150    }
151
152    pub fn consume_renderables(
153        &mut self,
154    ) -> impl Iterator<Item = (GlyphPosition<UD>, TextRendererGlyph)> + '_ {
155        self.ready_to_render
156            .drain(..)
157            .filter_map(|glyph| Some((glyph, *self.used_glyphs.get(&glyph.key)?)))
158    }
159
160    pub fn image(&self) -> &[u8] {
161        &self.image
162    }
163
164    pub fn atlas_size(&self) -> [usize; 3] {
165        self.atlas_size
166    }
167
168    pub fn into_image(self) -> (Vec<u8>, [usize; 3]) {
169        (self.image, self.atlas_size)
170    }
171
172    pub fn into_inner(self) -> TextRendererUnpacked<UD> {
173        TextRendererUnpacked {
174            glyphs: self.used_glyphs,
175            atlas_size: self.atlas_size,
176            image: self.image,
177            renderables: self.ready_to_render,
178        }
179    }
180
181    pub fn render_to_stream<V, B>(&mut self, stream: &mut VertexStream<V, B>)
182    where
183        V: TextVertex<UD> + Pod + Default,
184    {
185        let [w, h, _] = self.atlas_size;
186        let w = w as f32;
187        let h = h as f32;
188        for glyph in self.ready_to_render.drain(..) {
189            if let Some(data) = self.used_glyphs.get(&glyph.key) {
190                let mut a = V::default();
191                let mut b = V::default();
192                let mut c = V::default();
193                let mut d = V::default();
194                a.apply(
195                    [glyph.x, glyph.y],
196                    [
197                        data.rectangle.min_x() as f32 / w,
198                        data.rectangle.min_y() as f32 / h,
199                        data.page as f32,
200                    ],
201                    glyph.user_data,
202                );
203                b.apply(
204                    [glyph.x + glyph.width as f32, glyph.y],
205                    [
206                        data.rectangle.max_x() as f32 / w,
207                        data.rectangle.min_y() as f32 / h,
208                        data.page as f32,
209                    ],
210                    glyph.user_data,
211                );
212                c.apply(
213                    [glyph.x + glyph.width as f32, glyph.y + glyph.height as f32],
214                    [
215                        data.rectangle.max_x() as f32 / w,
216                        data.rectangle.max_y() as f32 / h,
217                        data.page as f32,
218                    ],
219                    glyph.user_data,
220                );
221                d.apply(
222                    [glyph.x, glyph.y + glyph.height as f32],
223                    [
224                        data.rectangle.min_x() as f32 / w,
225                        data.rectangle.max_y() as f32 / h,
226                        data.page as f32,
227                    ],
228                    glyph.user_data,
229                );
230                stream.quad([a, b, c, d]);
231            }
232        }
233    }
234}
235
236#[cfg(test)]
237mod tests {
238    use crate::TextRenderer;
239    use fontdue::{
240        layout::{CoordinateSystem, Layout, TextStyle},
241        Font,
242    };
243    use image::RgbImage;
244
245    #[test]
246    fn test_text_renderer() {
247        let text = include_str!("../../../resources/text.txt");
248        let font = include_bytes!("../../../resources/Roboto-Regular.ttf") as &[_];
249        let font = Font::from_bytes(font, Default::default()).unwrap();
250        let fonts = [font];
251        let mut layout = Layout::new(CoordinateSystem::PositiveYDown);
252        let mut renderer = TextRenderer::new(256, 256);
253
254        for line in text.lines() {
255            layout.append(&fonts, &TextStyle::new(line, 32.0, 0));
256        }
257
258        renderer.include(&fonts, &layout);
259        let (image, [width, height, _]) = renderer.into_image();
260        let image = RgbImage::from_vec(
261            width as _,
262            height as _,
263            image
264                .into_iter()
265                .flat_map(|value| [value, value, value])
266                .collect(),
267        )
268        .unwrap();
269        image.save("../../resources/test.png").unwrap();
270    }
271}