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}