1use crate::*;
2
3use etagere::AtlasAllocator;
4use fontdue::{layout::*, *};
5use image::{Rgba, RgbaImage};
6
7#[derive(Debug)]
8pub struct Glyph {
9 pub metrics: fontdue::Metrics,
10 pub bitmap: Vec<u8>,
11 pub rect: IRect,
14}
15
16fn make_layout() -> fontdue::layout::Layout {
17 fontdue::layout::Layout::new(fontdue::layout::CoordinateSystem::PositiveYUp)
18}
19
20pub struct TextRasterizer {
21 pub context: GraphicsContext,
22
23 glyphs: HashMap<(FontHandle, OrderedFloat<f32>, char), Glyph>,
24 atlas: etagere::AtlasAllocator,
25
26 texture: TextureHandle,
27
28 pub atlas_size: u32,
29}
30
31impl TextRasterizer {
32 pub fn new(context: GraphicsContext) -> Self {
33 let glyphs = HashMap::new();
34
35 const TEXT_ATLAS_SIZE: u32 = 4096;
36 let size = uvec2(TEXT_ATLAS_SIZE, TEXT_ATLAS_SIZE);
37
38 let texture = context.texture_creator.borrow_mut().handle_from_size(
39 "Font Atlas",
40 size,
41 TRANSPARENT,
42 );
43
44 Self {
50 context,
51 glyphs,
52 atlas: AtlasAllocator::new(etagere::size2(
53 size.x as i32,
54 size.y as i32,
55 )),
56 texture,
57 atlas_size: TEXT_ATLAS_SIZE,
58 }
59 }
60
61 pub fn calculate_text_layout(
62 &mut self,
63 assets: &Assets,
64 text: TextData,
65 pro_params: ProTextParams,
66 ) -> (fontdue::layout::Layout, Rect, Option<Vec<StyledGlyph>>) {
67 let (clean_text, styled_glyphs) = match text {
68 TextData::Raw(raw_text) => (raw_text, None),
69 TextData::Rich(rich_text) => {
70 (rich_text.clean_text, Some(rich_text.styled_glyphs))
71 }
72 };
73
74 let font_handle = pro_params.font;
75 let font = assets.fonts.get(&font_handle).unwrap();
76
77 let layout = self.layout_text(
78 font,
79 &clean_text,
80 pro_params.font_size,
81 &fontdue::layout::LayoutSettings { ..Default::default() },
82 );
83
84 let mut min_x = f32::INFINITY;
85 let mut min_y = f32::INFINITY;
86 let mut max_x = f32::NEG_INFINITY;
87 let mut max_y = f32::NEG_INFINITY;
88
89 for glyph in layout.glyphs() {
90 let glyph_min_x = glyph.x;
91 let glyph_min_y = glyph.y;
92 let glyph_max_x = glyph.x + glyph.width as f32;
93 let glyph_max_y = glyph.y + glyph.height as f32;
94
95 min_x = min_x.min(glyph_min_x);
96 min_y = min_y.min(glyph_min_y);
97 max_x = max_x.max(glyph_max_x);
98 max_y = max_y.max(glyph_max_y);
99 }
100
101 let layout_rect =
102 Rect::from_min_max(vec2(min_x, min_y), vec2(max_x, max_y));
103
104 (layout, layout_rect, styled_glyphs)
105 }
106
107 pub fn get_glyph(
108 &mut self,
109 font_handle: FontHandle,
110 font: &Font,
111 font_size: f32,
112 c: char,
113 ) -> (TextureHandle, IRect) {
114 let key = (font_handle, OrderedFloat(font_size), c);
115 if !self.glyphs.contains_key(&key) {
116 self.prepare_rasterize(font_handle, font, font_size, c);
117 }
118
119 (self.texture, self.glyphs[&key].rect)
120 }
121
122 pub fn prepare_rasterize(
123 &mut self,
124 font_handle: FontHandle,
125 font: &Font,
126 font_size: f32,
127 c: char,
128 ) {
129 let (metrics, bitmap) = font.rasterize(c, font_size);
130
131 let mut rgba_bitmap = vec![];
136
137 for x in bitmap.iter() {
138 rgba_bitmap.push(*x);
139 rgba_bitmap.push(*x);
140 rgba_bitmap.push(*x);
141 rgba_bitmap.push(*x);
142 }
143
144 if !(metrics.width == 0 || metrics.height == 0) {
150 let mut image =
151 RgbaImage::new(metrics.width as u32, metrics.height as u32);
152
153 for x in 0..metrics.width {
154 for y in 0..metrics.height {
155 let i = y * metrics.width + x;
156
157 let v = bitmap[i];
158 let pixel = Rgba([v, v, v, v]);
159 image.put_pixel(x as u32, y as u32, pixel);
160 }
161 }
162
163 let image = DynamicImage::ImageRgba8(image).flipv().to_rgba8();
164
165 let pad = 2;
166
167 let allocation = self
168 .atlas
169 .allocate(etagere::size2(
170 metrics.width as i32 + 2 * pad,
171 metrics.height as i32 + 2 * pad,
172 ))
173 .unwrap_or_else(|| panic!("FAILED TO FIT GLYPH {}", c));
174
175 if self.atlas.free_space() < self.atlas.allocated_space() {
176 let used = self.atlas.free_space();
177 let total = self.atlas.size().area();
178 info!(
179 "font atlas has {}/{} space ({:.2}%) used",
180 used,
181 total,
182 (1.0 - used as f32 / total as f32) * 100.0
183 );
184 }
185
186 let rect = allocation.rectangle.to_rect();
187 let inset_rect = IRect::new(
188 ivec2(rect.origin.x + pad, rect.origin.y + pad),
189 ivec2(rect.size.width - 2 * pad, rect.size.height - 2 * pad),
190 );
191 self.context.texture_creator.borrow_mut().update_texture_region(
192 self.texture,
193 &image,
194 inset_rect,
195 );
196
197 let glyph = Glyph { metrics, bitmap, rect: inset_rect };
198
199 self.glyphs
200 .insert((font_handle, OrderedFloat(font_size), c), glyph);
201 };
202 }
203
204 pub fn layout_text(
205 &mut self,
206 font: &Font,
207 text: &str,
208 size: f32,
209 layout_settings: &LayoutSettings,
210 ) -> fontdue::layout::Layout {
211 let mut layout = make_layout();
212 layout.reset(layout_settings);
213
214 layout
215 .append(std::slice::from_ref(font), &TextStyle::new(text, size, 0));
216 layout
217 }
218
219 #[allow(dead_code)]
220 pub fn layout_text_demo(&mut self, font: &Font) -> Vec<GlyphPosition> {
221 let mut layout = make_layout();
237
238 layout.reset(&LayoutSettings { ..LayoutSettings::default() });
239
240 let fonts = std::slice::from_ref(font);
241
242 layout.append(fonts, &TextStyle::new("Hello\n", 16.0, 0));
243 layout.append(fonts, &TextStyle::new("\tworld!", 16.0, 0));
244
245 layout.glyphs().clone()
246 }
247}
248
249pub trait EtagereRectExtensions {
250 fn to_irect(&self) -> IRect;
251}
252
253impl EtagereRectExtensions for etagere::Allocation {
254 fn to_irect(&self) -> IRect {
255 let rect = self.rectangle.to_rect();
256
257 let offset = ivec2(rect.origin.x, rect.origin.y);
258 let size = ivec2(rect.size.width, rect.size.height);
259
260 IRect { offset, size }
261 }
262}