1use crate::color::Color;
2use crate::geometry::{Mesh, Vertex};
3use crate::material::TextureData;
4use fontdue::{Font, FontSettings};
5use glam::{Vec2, Vec3};
6use std::collections::HashMap;
7
8#[derive(Debug, Clone)]
11pub struct GlyphInfo {
12 pub uv_min: Vec2,
14 pub uv_max: Vec2,
15 pub size: Vec2,
17 pub bearing: Vec2,
19 pub advance: f32,
21}
22
23impl GlyphInfo {
24 fn empty(advance: f32) -> Self {
25 Self {
26 uv_min: Vec2::ZERO,
27 uv_max: Vec2::ZERO,
28 size: Vec2::ZERO,
29 bearing: Vec2::ZERO,
30 advance,
31 }
32 }
33}
34
35pub struct FontAtlas {
38 font: Font,
39 pub texture: TextureData,
40 glyphs: HashMap<(u32, u32), GlyphInfo>, cursor_x: usize,
42 cursor_y: usize,
43 row_height: usize,
44}
45
46impl FontAtlas {
47 pub fn from_bytes(font_data: &[u8], atlas_size: usize) -> Result<Self, String> {
49 let font =
50 Font::from_bytes(font_data, FontSettings::default()).map_err(|e| e.to_string())?;
51 let texture = TextureData::new(atlas_size, atlas_size);
52 Ok(Self {
53 font,
54 texture,
55 glyphs: HashMap::new(),
56 cursor_x: 1,
57 cursor_y: 1,
58 row_height: 0,
59 })
60 }
61
62 pub fn get_or_rasterize(&mut self, c: char, px: f32) -> GlyphInfo {
64 let key = (c as u32, px.to_bits());
65 if let Some(g) = self.glyphs.get(&key) {
66 return g.clone();
67 }
68 self.rasterize_glyph(c, px);
69 self.glyphs
70 .get(&key)
71 .cloned()
72 .unwrap_or_else(|| GlyphInfo::empty(px * 0.5))
73 }
74
75 fn rasterize_glyph(&mut self, c: char, px: f32) {
76 let (metrics, bitmap) = self.font.rasterize(c, px);
77
78 if metrics.width == 0 || metrics.height == 0 {
79 let key = (c as u32, px.to_bits());
80 self.glyphs
81 .insert(key, GlyphInfo::empty(metrics.advance_width));
82 return;
83 }
84
85 let aw = self.texture.width;
86 let ah = self.texture.height;
87
88 if self.cursor_x + metrics.width + 1 > aw {
89 self.cursor_x = 1;
90 self.cursor_y += self.row_height + 1;
91 self.row_height = 0;
92 }
93
94 if self.cursor_y + metrics.height + 1 > ah {
95 let key = (c as u32, px.to_bits());
97 self.glyphs
98 .insert(key, GlyphInfo::empty(metrics.advance_width));
99 return;
100 }
101
102 for gy in 0..metrics.height {
103 for gx in 0..metrics.width {
104 let alpha = bitmap[gy * metrics.width + gx];
105 let dx = self.cursor_x + gx;
106 let dy = self.cursor_y + gy;
107 let idx = (dy * aw + dx) * 4;
108 self.texture.data[idx] = 255;
109 self.texture.data[idx + 1] = 255;
110 self.texture.data[idx + 2] = 255;
111 self.texture.data[idx + 3] = alpha;
112 }
113 }
114
115 let uv_min = Vec2::new(
116 self.cursor_x as f32 / aw as f32,
117 self.cursor_y as f32 / ah as f32,
118 );
119 let uv_max = Vec2::new(
120 (self.cursor_x + metrics.width) as f32 / aw as f32,
121 (self.cursor_y + metrics.height) as f32 / ah as f32,
122 );
123
124 self.row_height = self.row_height.max(metrics.height);
125 self.cursor_x += metrics.width + 1;
126
127 let key = (c as u32, px.to_bits());
128 self.glyphs.insert(
129 key,
130 GlyphInfo {
131 uv_min,
132 uv_max,
133 size: Vec2::new(metrics.width as f32, metrics.height as f32),
134 bearing: Vec2::new(metrics.xmin as f32, metrics.ymin as f32),
135 advance: metrics.advance_width,
136 },
137 );
138 }
139}
140
141pub fn generate_text_mesh(atlas: &mut FontAtlas, text: &str, px: f32, color: Color) -> Mesh {
146 let mut vertices = Vec::new();
147 let mut indices = Vec::new();
148 let mut cursor_x = 0.0f32;
149
150 for ch in text.chars() {
151 let info = atlas.get_or_rasterize(ch, px);
152 if info.size.x > 0.0 && info.size.y > 0.0 {
153 let x0 = cursor_x + info.bearing.x;
154 let y0 = info.bearing.y;
155 let x1 = x0 + info.size.x;
156 let y1 = y0 + info.size.y;
157
158 let base = vertices.len() as u32;
159 vertices.push(Vertex {
160 position: Vec3::new(x0, y0, 0.0),
161 normal: Vec3::Z,
162 uv: info.uv_min,
163 color,
164 tangent: Vec3::X,
165 });
166 vertices.push(Vertex {
167 position: Vec3::new(x1, y0, 0.0),
168 normal: Vec3::Z,
169 uv: Vec2::new(info.uv_max.x, info.uv_min.y),
170 color,
171 tangent: Vec3::X,
172 });
173 vertices.push(Vertex {
174 position: Vec3::new(x1, y1, 0.0),
175 normal: Vec3::Z,
176 uv: info.uv_max,
177 color,
178 tangent: Vec3::X,
179 });
180 vertices.push(Vertex {
181 position: Vec3::new(x0, y1, 0.0),
182 normal: Vec3::Z,
183 uv: Vec2::new(info.uv_min.x, info.uv_max.y),
184 color,
185 tangent: Vec3::X,
186 });
187
188 indices.extend_from_slice(&[base, base + 1, base + 2, base, base + 2, base + 3]);
189 }
190 cursor_x += info.advance;
191 }
192
193 Mesh::new(vertices, indices)
194}
195
196pub fn measure_text(atlas: &mut FontAtlas, text: &str, px: f32) -> f32 {
198 text.chars()
199 .map(|c| atlas.get_or_rasterize(c, px).advance)
200 .sum()
201}
202
203pub struct GlyphFont {
212 font: Font,
213 cache: HashMap<(u32, u32), (fontdue::Metrics, Vec<u8>)>,
215}
216
217impl GlyphFont {
218 pub fn from_bytes(data: &[u8]) -> Result<Self, String> {
220 let font = Font::from_bytes(data, FontSettings::default()).map_err(|e| e.to_string())?;
221 Ok(Self { font, cache: HashMap::new() })
222 }
223
224 pub fn from_path(path: &str) -> Result<Self, String> {
226 let bytes = std::fs::read(path).map_err(|e| format!("{path}: {e}"))?;
227 Self::from_bytes(&bytes)
228 }
229
230 fn glyph(&mut self, c: char, px: f32) -> &(fontdue::Metrics, Vec<u8>) {
231 let key = (c as u32, px.to_bits());
232 self.cache
233 .entry(key)
234 .or_insert_with(|| self.font.rasterize(c, px))
235 }
236
237 pub fn measure(&mut self, text: &str, px: f32) -> f32 {
239 text.chars()
240 .map(|c| self.font.metrics(c, px).advance_width)
241 .sum()
242 }
243
244 #[allow(clippy::too_many_arguments)]
250 pub fn draw_text(
251 &mut self,
252 buf: &mut [u32],
253 w: usize,
254 h: usize,
255 x: f32,
256 y: f32,
257 px: f32,
258 color: u32,
259 text: &str,
260 ) {
261 if w == 0 || h == 0 || px <= 0.0 {
262 return;
263 }
264 let lm = self.font.horizontal_line_metrics(px);
265 let ascent = lm.map(|m| m.ascent).unwrap_or(px * 0.8);
266 let baseline = y + ascent;
267
268 let cr = ((color >> 16) & 0xFF) as f32;
269 let cg = ((color >> 8) & 0xFF) as f32;
270 let cb = (color & 0xFF) as f32;
271
272 let mut pen_x = x;
273 for c in text.chars() {
274 let (metrics, bitmap) = self.glyph(c, px).clone();
275 if metrics.width > 0 && metrics.height > 0 {
276 let gx0 = (pen_x + metrics.xmin as f32).round() as i32;
278 let gy0 = (baseline - metrics.ymin as f32 - metrics.height as f32).round() as i32;
279 for gy in 0..metrics.height {
280 let py = gy0 + gy as i32;
281 if py < 0 || py as usize >= h {
282 continue;
283 }
284 let row = py as usize * w;
285 for gx in 0..metrics.width {
286 let px_ = gx0 + gx as i32;
287 if px_ < 0 || px_ as usize >= w {
288 continue;
289 }
290 let a = bitmap[gy * metrics.width + gx] as f32 / 255.0;
291 if a <= 0.0 {
292 continue;
293 }
294 let idx = row + px_ as usize;
295 let dst = buf[idx];
296 let dr = ((dst >> 16) & 0xFF) as f32;
297 let dg = ((dst >> 8) & 0xFF) as f32;
298 let db = (dst & 0xFF) as f32;
299 let nr = (cr * a + dr * (1.0 - a)).min(255.0) as u32;
300 let ng = (cg * a + dg * (1.0 - a)).min(255.0) as u32;
301 let nb = (cb * a + db * (1.0 - a)).min(255.0) as u32;
302 buf[idx] = (nr << 16) | (ng << 8) | nb;
303 }
304 }
305 }
306 pen_x += metrics.advance_width;
307 }
308 }
309}