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}