1pub mod colours;
2
3use crate::colours::TextColour;
4use fontdue::layout::GlyphPosition;
5use fontdue::layout::{CoordinateSystem, Layout, LayoutSettings, TextStyle};
6use fontdue::Font;
7use fontdue::FontSettings;
8use std::collections::HashMap;
9use std::sync::Arc;
10use log::debug;
11
12#[derive(Clone)]
16pub struct TextRenderer<T> {
17 pub font: Arc<Font>,
18 pub layout: Arc<Layout>,
19 glyph_caches: HashMap<u16, GlyphCache<T>>,
20}
21
22#[derive(Clone)]
27#[allow(dead_code)] struct GlyphCache<T> {
29 pub size: f32,
30 pub surface_map: HashMap<TextColour, HashMap<char, (Vec<u8>, T)>>,
31}
32
33pub trait DrawableSurface {
36 fn paste(&mut self, x: usize, y: usize, width: usize, height: usize, data: &Self);
42 fn from_raw_mask(width: usize, height: usize, data: &[u8], colour: TextColour) -> Self;
49}
50
51#[derive(Debug, Clone, Copy)]
53pub enum TextRendererError {
54 FontNotFound,
55}
56
57fn cache_glyph<T>(font: Arc<Font>, glyph: GlyphPosition, colour: TextColour, make_t: impl FnOnce(&[u8]) -> T) -> (Vec<u8>, T) {
59 debug!("caching glyph: {:?}", glyph);
60 let (_metrics, mut bitmap) = font.rasterize_config(glyph.key);
61 let mut coloured_pixels = Vec::new();
62 for pixel in bitmap.iter_mut() {
63 coloured_pixels.push(colour.r); coloured_pixels.push(colour.g); coloured_pixels.push(colour.b); coloured_pixels.push(*pixel); }
68 let t = make_t(&coloured_pixels);
70 (coloured_pixels, t)
71}
72
73impl<T> TextRenderer<T> where T: DrawableSurface, T: Clone {
74 pub fn load(font_path: &str) -> Result<Self, TextRendererError> {
78 let font_data = std::fs::read(font_path).map_err(|_| TextRendererError::FontNotFound)?;
79 let font = Font::from_bytes(font_data, FontSettings::default())
80 .map_err(|_| TextRendererError::FontNotFound)?;
81 let layout = Layout::new(CoordinateSystem::PositiveYDown);
82 Ok(TextRenderer {
83 font: Arc::new(font),
84 layout: Arc::new(layout),
85 glyph_caches: HashMap::new(),
86 })
87 }
88
89 pub fn draw_string_monospaced(
93 &mut self,
94 string: &str,
95 x: f32,
96 y: f32,
97 size: f32,
98 colour: TextColour,
99 surface: &mut T
100 ) {
101 let mut layout_settings = LayoutSettings::default();
102 layout_settings.x = x;
103 layout_settings.y = y;
104 let mut layout = Layout::new(CoordinateSystem::PositiveYDown);
105 layout.reset(&layout_settings);
106 layout.append(&[self.font.clone()], &TextStyle::new(string, size, 0));
107 let glyphs = layout.glyphs();
108 for (glyph, i) in glyphs.iter().zip(0..) {
109 let bitmap = self.get_glyph_surface(*glyph, glyph.width, glyph.height, colour);
110 surface.paste(
112 (x + (size / 2.0) * i as f32) as usize,
113 (y + glyph.y) as usize,
114 (size / 2.0) as usize,
115 glyph.height as usize,
116 &bitmap,
117 );
118 }
119 }
120
121 pub fn draw_string(
125 &mut self,
126 string: &str,
127 x: f32,
128 y: f32,
129 size: f32,
130 colour: TextColour,
131 surface: &mut T
132 ) {
133 let mut layout_settings = LayoutSettings::default();
134 layout_settings.x = x;
135 layout_settings.y = y;
136 let mut layout = Layout::new(CoordinateSystem::PositiveYDown);
137 layout.reset(&layout_settings);
138 layout.append(&[self.font.clone()], &TextStyle::new(string, size, 0));
139 let glyphs = layout.glyphs();
140 for glyph in glyphs.iter() {
141 let bitmap = self.get_glyph_surface(*glyph, glyph.width, glyph.height, colour);
142 surface.paste(
144 (x + glyph.x) as usize,
145 (y + glyph.y) as usize,
146 glyph.width as usize,
147 glyph.height as usize,
148 &bitmap,
149 );
150 }
151 }
152
153 fn get_glyph_surface(
155 &mut self,
156 glpyh: GlyphPosition,
157 width: usize,
158 height: usize,
159 colour: TextColour,
160 ) -> T {
161 let size = height as u16;
162 self.glyph_caches.entry(size).or_insert(GlyphCache {
165 size: size as f32,
166 surface_map: HashMap::new(),
167 });
168 let glyph_cache = self.glyph_caches.get_mut(&size).unwrap();
172 glyph_cache.surface_map.entry(colour).or_insert_with(|| HashMap::new());
173 let colour_map = glyph_cache.surface_map.get_mut(&colour).unwrap();
177 if let std::collections::hash_map::Entry::Vacant(e) = colour_map.entry(glpyh.parent) {
178 e.insert(cache_glyph(self.font.clone(), glpyh, colour, |data| T::from_raw_mask(width, height, data, colour)));
179 }
180 let glyph_surface = colour_map.get(&glpyh.parent).unwrap();
182 glyph_surface.1.clone()
184 }
185}
186
187#[cfg(test)]
188mod tests {
189 use std::io::Write;
190 use super::*;
191
192 #[derive(Debug, Clone)]
193 struct TestSurface {
194 width: usize,
195 height: usize,
196 data: Vec<u8>,
197 }
198
199 impl DrawableSurface for TestSurface {
200 fn paste(&mut self, x: usize, y: usize, width: usize, height: usize, data: &Self) {
201 println!("paste: x: {}, y: {}, width: {}, height: {}, data: {:?}", x, y, width, height, data);
202 let data_pitch = data.width as i32 * 4;
204 let pitch = self.width as i32 * 4;
205 let mut data_index = 0i32;
206 let mut index = (y as i32 * pitch) + (x as i32 * 4);
207 for _ in 0..height {
209 for _ in 0..width {
210 if index < 0 || index >= (self.width * self.height * 4) as i32 || data_index < 0 || data_index >= (data.width * data.height * 4) as i32 {
212 data_index += 4;
213 index += 4;
214 continue;
215 }
216 self.data[index as usize] = data.data[data_index as usize];
217 self.data[index as usize + 1] = data.data[data_index as usize + 1];
218 self.data[index as usize + 2] = data.data[data_index as usize + 2];
219 self.data[index as usize + 3] = data.data[data_index as usize + 3];
220 data_index += 4;
221 index += 4;
222 }
223 index += pitch - (width as i32 * 4);
224 data_index += data_pitch - (width as i32 * 4);
225 }
226 }
227 fn from_raw_mask(width: usize, height: usize, data: &[u8], colour: TextColour) -> Self {
229 println!("from_raw_mask");
230 println!("width: {}", width);
231 println!("height: {}", height);
232 println!("data: {:?}", data);
233 TestSurface {
234 width,
235 height,
236 data: data.to_vec(),
237 }
238 }
239 }
240
241 #[test]
242 fn test_text_renderer() {
243 let mut renderer = TextRenderer::load("FreeMono.ttf").unwrap();
244 let mut surface = TestSurface {
245 width: 256,
246 height: 256,
247 data: vec![0; 256 * 256 * 4],
248 };
249 renderer.draw_string_monospaced("hElLo w0r1d!", 0.0, 0.0, 24.0, TextColour::new_rgb(255, 255, 255), &mut surface);
250 renderer.draw_string("hElLo w0r1d!", 0.0, 24.0, 24.0, TextColour::new_rgb(255, 255, 255), &mut surface);
251 let mut rgb_data = Vec::new();
253 for i in 0..(surface.width * surface.height) {
254 if surface.data[i * 4 + 3] == 0 {
256 rgb_data.push(0);
257 rgb_data.push(0);
258 rgb_data.push(0);
259 } else {
260 rgb_data.push(surface.data[i * 4]);
261 rgb_data.push(surface.data[i * 4 + 1]);
262 rgb_data.push(surface.data[i * 4 + 2]);
263 }
264 }
265 let mut file = std::fs::File::create("test.ppm").unwrap();
267 let _ = file.write(format!("P6\n{} {}\n255\n", surface.width, surface.height).as_bytes()).unwrap();
268 let _ = file.write(&rgb_data).unwrap();
269 }
270}