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}