sdl2_unifont/renderer.rs
1use sdl2::pixels::Color;
2use sdl2::pixels::PixelFormatEnum;
3use sdl2::rect::Rect;
4use sdl2::surface::Surface;
5
6use bit_field::BitField;
7
8use std::collections::HashMap;
9use std::slice::IterMut;
10
11use unifont;
12
13/// Number of vertical pixels in each Unifont character.
14const UNIFONT_HEIGHT: u32 = 16;
15
16/// Storage class for rendering settings.
17pub struct SurfaceRenderer {
18 /// The colour to use to draw text.
19 pub fg_color: Color,
20 /// The foreground colour supplied to the constructor
21 fg_orig: Color,
22 /// The colour to use to fill the surface before drawing text.
23 pub bg_color: Color,
24 /// The background colour supplied to the constructor
25 bg_orig: Color,
26
27 /// Integer scale multiplier, since Unifont is a raster font.
28 pub scale: u32,
29 /// Whether or not to make text bold. Uses XTerm-style bolding, where the
30 /// text is just drawn twice on the x-axis, one pixel apart.
31 pub bold: bool,
32 /// Whether or not to make text italicised. Simply shifts pixels to the
33 /// right by one additional pixel, every two vertical pixels.
34 pub italic: bool,
35}
36
37impl SurfaceRenderer {
38 /// Creates a new Unifont renderer which renders text to new SDL surfaces.
39 pub fn new(fg_color: Color, bg_color: Color) -> SurfaceRenderer {
40 SurfaceRenderer {
41 fg_color,
42 fg_orig: fg_color,
43 bg_color,
44 bg_orig: bg_color,
45 scale: 1,
46 bold: false,
47 italic: false,
48 }
49 }
50
51 /// Returns the renderer to the state it was in when it was first created
52 /// (i.e. the foreground and background colours are reset to the values
53 /// given to the constructor, and all other fields are reset).
54 pub fn reset(&mut self) {
55 self.fg_color = self.fg_orig;
56 self.bg_color = self.bg_orig;
57 self.scale = 1;
58 self.bold = false;
59 self.italic = false;
60 }
61
62 /// Draws the supplied text to a new surface, which has been sized to fit
63 /// the text exactly, using the renderer's style settings. Returns an `Err`
64 /// result if a character was found which is not in the font, or the font
65 /// could not be initialised.
66 pub fn draw(&self, text: &str) -> Result<Surface, String> {
67 // Create new surface sized to text
68 let width = self.measure_width(text)?;
69 let mut surf = Surface::new(
70 width,
71 UNIFONT_HEIGHT * self.scale,
72 PixelFormatEnum::RGBA8888,
73 )?;
74
75 // Fill surface with background color
76 surf.fill_rect(None, self.bg_color)?;
77
78 // Obtain raw surface data reference, then draw characters of string
79 // through `draw_raw`
80 if surf.must_lock() {
81 surf.with_lock_mut(|px: &mut [u8]| self.draw_raw(px, &width, text))?
82 } else {
83 self.draw_raw(surf.without_lock_mut().unwrap(), &width, text)?
84 }
85
86 Ok(surf)
87 }
88
89 /// Sums the width of each character in the supplied text, and multiples the
90 /// sum by the renderer's integer scale factor. Takes into consideration
91 /// formatting options' effects on text width.
92 pub fn measure_width(&self, text: &str) -> Result<u32, String> {
93 let mut basic_width = self.scale * count_char_width(text)?;
94
95 if self.bold {
96 basic_width += self.scale;
97 }
98 if self.italic {
99 basic_width += 8 * self.scale;
100 }
101
102 Ok(basic_width)
103 }
104
105 /// May in the future take into consideration newlines and other formatting.
106 /// For now, it just returns `16 * scale`, thus, the result of this method
107 /// can always be safely `unwrap()`ped.
108 pub fn measure_height(&self, _text: &str) -> Result<u32, String> {
109 Ok(self.scale * UNIFONT_HEIGHT)
110 }
111
112 /// Takes an array of pixels and draws the supplied text to it, using the
113 /// specified render options. This function always assumes RGBA8888 pixel
114 /// formatting.
115 fn draw_raw(
116 &self,
117 pixels: &mut [u8],
118 surf_width: &u32,
119 text: &str,
120 ) -> Result<(), String> {
121 let unifont = get_unifont()?;
122
123 // Start position of next character
124 let mut x_offset = 0;
125
126 let iter = text.chars();
127 for c in iter {
128 // Retrieve character description from hashmap
129 let font_char = match unifont.get(&(c as u32)) {
130 None => return Err(gen_missing_char_str(&c)),
131 Some(font_char) => font_char,
132 };
133
134 // Draw rows of character bitmap
135 for row in 0..UNIFONT_HEIGHT as usize {
136 // Draw each pixel for a row
137 for col in (0..font_char.width as usize).rev() {
138 if font_char.bitmap[row].get_bit(col) {
139 // Double character on x axis if we're bolding
140 for x in if self.bold {
141 0..self.scale * 2
142 } else {
143 0..self.scale
144 } {
145 for y in 0..self.scale {
146 // Calculate the byte position of the pixel
147 // (this thing is a mess, to be honest)
148 let px_base = (4
149 * surf_width
150 * (row as u32 * self.scale + y)
151 + 4 * x_offset
152 + 4 * (font_char.width as u32 * self.scale
153 - col as u32 * self.scale
154 - self.scale)
155 + 4 * x)
156 as usize;
157
158 // Insert fg colour into the current pixel
159 // TODO assumes little endian
160 pixels[px_base + 3] = self.fg_color.r;
161 pixels[px_base + 2] = self.fg_color.g;
162 pixels[px_base + 1] = self.fg_color.b;
163 pixels[px_base] = self.fg_color.a;
164 }
165 }
166 }
167 }
168 }
169
170 // Shift next character
171 x_offset += self.scale * font_char.width as u32;
172 }
173
174 // Italicise text
175 if self.italic {
176 let mut offset = (UNIFONT_HEIGHT * self.scale) / 2;
177 for row in 0..UNIFONT_HEIGHT * self.scale as u32 {
178 let row_offset = 4 * row * surf_width;
179 // Shift bytes forward
180 for i in
181 (row_offset..row_offset + 4 * (surf_width - offset)).rev()
182 {
183 pixels[(i + 4 * offset) as usize] = pixels[i as usize];
184 }
185
186 // Clear space behind first character
187 for i in (row_offset as usize
188 ..(row_offset + offset * 4) as usize)
189 .step_by(4)
190 {
191 // TODO assumes little endian
192 pixels[i + 3] = self.bg_color.r;
193 pixels[i + 2] = self.bg_color.g;
194 pixels[i + 1] = self.bg_color.b;
195 pixels[i] = self.bg_color.a;
196 }
197
198 if row % 2 == 1 {
199 offset -= 1;
200 }
201 }
202 }
203
204 Ok(())
205 }
206}
207
208/// Advanced renderer with additional capabilities.
209pub struct FormattedRenderer {
210 /// Stores variables and string literals. The boolean value is set to `true`
211 /// for literals, and `false` for variable name references.
212 text: Vec<(bool, String)>,
213 /// Stores a `SurfaceRenderer` for each entry in the `text` vector.
214 renderers: Vec<SurfaceRenderer>,
215 /// Maps variable names to values.
216 variables: HashMap<String, String>,
217 /// The colour to use behind all text.
218 bg_color: Color,
219 /// The scale to use for all text.
220 scale: u32,
221}
222
223impl FormattedRenderer {
224 /// Creates a new blank `FormattedRenderer`. All segments use the same
225 /// background colour.
226 pub fn new(bg_color: Color) -> FormattedRenderer {
227 FormattedRenderer {
228 text: Vec::new(),
229 renderers: Vec::new(),
230 variables: HashMap::new(),
231 bg_color,
232 scale: 1,
233 }
234 }
235
236 /// Adds a string literal, which cannot be modified once added (i.e. you'll
237 /// need to create a new `FormattedRenderer` instead).
238 pub fn add_text(
239 &mut self,
240 text: &str,
241 color: Color,
242 bold: bool,
243 italic: bool,
244 ) {
245 self.text.push((true, text.to_string()));
246 let mut renderer = SurfaceRenderer::new(color, self.bg_color);
247 renderer.bold = bold;
248 renderer.italic = italic;
249 renderer.scale = self.scale;
250 self.renderers.push(renderer);
251 }
252
253 /// Adds a named variable, which can have its value and changed after
254 /// creation.
255 pub fn add_var(
256 &mut self,
257 name: &str,
258 color: Color,
259 bold: bool,
260 italic: bool,
261 ) {
262 self.text.push((false, name.to_string()));
263 let mut renderer = SurfaceRenderer::new(color, self.bg_color);
264 renderer.bold = bold;
265 renderer.italic = italic;
266 renderer.scale = self.scale;
267 self.renderers.push(renderer);
268 self.variables
269 .insert(name.to_string(), "#UNDEFINED".to_string());
270 }
271
272 /// Sets or modifies the value of an already added variable. If the variable
273 /// referenced does not exist, nothing happens.
274 pub fn set_var(&mut self, name: &str, value: &str) {
275 if self.variables.contains_key(name) {
276 self.variables.insert(name.to_string(), value.to_string());
277 }
278 }
279
280 /// Sets the background color of each component of the formatted output.
281 pub fn set_bg_color(&mut self, bg_color: Color) {
282 self.bg_color = bg_color;
283 for renderer in self.renderers.iter_mut() {
284 renderer.bg_color = bg_color;
285 }
286 }
287
288 /// Returns the default background colour for each rendered section (unless
289 /// it's been changed by modifying a renderer's background colour through
290 /// the `iter_mut` method).
291 pub fn get_bg_color(&self) -> Color {
292 return self.bg_color;
293 }
294
295 /// Sets the scale of each component in the formatted output.
296 pub fn set_scale(&mut self, scale: u32) {
297 self.scale = scale;
298 for renderer in self.renderers.iter_mut() {
299 renderer.scale = scale;
300 }
301 }
302
303 /// Gets the current scale factor used for draw operations.
304 pub fn get_scale(&self) -> u32 {
305 return self.scale;
306 }
307
308 /// Returns an iterator over each renderer, which allows the renderers'
309 /// settings to be modified.
310 pub fn iter_mut(&mut self) -> IterMut<SurfaceRenderer> {
311 self.renderers.iter_mut()
312 }
313
314 /// Sequentially draws each literal and variable, using its associated
315 /// renderer, and linearly appends the output surfaces.
316 pub fn draw<'a>(&self) -> Result<Surface<'a>, String> {
317 // Preflight width sum
318 let width = self.measure_width()?;
319
320 // Create output surface
321 let mut surf = Surface::new(
322 width,
323 UNIFONT_HEIGHT * self.scale,
324 PixelFormatEnum::RGBA8888,
325 )?;
326
327 // Draw text
328 let mut offset: u32 = 0;
329 for (text, renderer) in
330 (&self.text).into_iter().zip((&self.renderers).into_iter())
331 {
332 let text = if text.0 {
333 &text.1
334 } else {
335 match self.variables.get(&text.1) {
336 Some(val) => val,
337 None => return Err("Undefined variable used".to_string()),
338 }
339 };
340
341 renderer.draw(text)?.blit(
342 None,
343 &mut surf,
344 Rect::new(offset as i32, 0, 0, 0),
345 )?;
346 offset += renderer.measure_width(text)?;
347 }
348
349 Ok(surf)
350 }
351
352 /// Measures the width of all of the contained text, including variable
353 /// values, taking into consideration the formatting of each section.
354 pub fn measure_width(&self) -> Result<u32, String> {
355 let mut width = 0;
356 for (text, renderer) in
357 (&self.text).into_iter().zip((&self.renderers).into_iter())
358 {
359 if text.0 {
360 width += renderer.measure_width(&text.1)?;
361 } else {
362 match self.variables.get(&text.1) {
363 Some(val) => width += renderer.measure_width(val)?,
364 None => return Err("Undefined variable used".to_string()),
365 }
366 }
367 }
368
369 Ok(width)
370 }
371
372 /// Returns the height of all content in the formatted string.
373 pub fn measure_height(&self) -> Result<u32, String> {
374 Ok(self.scale * UNIFONT_HEIGHT)
375 }
376}
377
378impl IntoIterator for FormattedRenderer {
379 type Item = SurfaceRenderer;
380 type IntoIter = ::std::vec::IntoIter<SurfaceRenderer>;
381
382 fn into_iter(self) -> Self::IntoIter {
383 self.renderers.into_iter()
384 }
385}
386
387/// Maps `unifont`'s `Result` error type to ours, so that the `?` operator
388/// can be utilised.
389fn get_unifont<'a>() -> Result<&'a unifont::FontChars, String> {
390 match unifont::get_unifont() {
391 Ok(unifont) => Ok(unifont),
392 Err(_) => {
393 return Err("Failed to initialise embedded Unifont".to_string())
394 }
395 }
396}
397
398/// Finds the rendered width of a string, taking into consideration whether each
399/// character is half-width (8px) or full-width (16px). Returns an error result
400/// if a character is not found in the font (i.e. the feature to include it was
401/// probably not enabled).
402fn count_char_width(text: &str) -> Result<u32, String> {
403 let unifont = get_unifont()?;
404
405 let mut width_sum: u32 = 0;
406 let iter = text.chars();
407
408 for c in iter {
409 match unifont.get(&(c as u32)) {
410 None => return Err(gen_missing_char_str(&c)),
411 Some(fc) => width_sum += fc.width as u32,
412 }
413 }
414
415 Ok(width_sum)
416}
417
418fn gen_missing_char_str(c: &char) -> String {
419 format!(
420 "Embedded Unifont does not contain {} (code point: 0x{:x})",
421 c, *c as u32
422 )
423}