leafrender/render/
font.rs

1/// Allows for the caching of fonts.
2use rusttype::Font;
3use rusttype::FontCollection;
4use rusttype::GlyphId;
5use rusttype::Point;
6use rusttype::Scale;
7
8use crate::pos::Position;
9
10use crate::render::Color;
11use crate::render::Dimensions;
12use crate::render::Drawer;
13use crate::render::Texture;
14
15use std::collections::BTreeMap;
16
17#[derive(Clone, Hash, Ord, PartialOrd, Eq, PartialEq)]
18struct CachedGlyph {
19    id: GlyphId,
20    color: Color,
21    size: i32,
22}
23
24pub struct FontCache<'a, T: Sized> {
25    font: Font<'a>,
26    cache: BTreeMap<CachedGlyph, T>,
27}
28
29impl<'a, T: Dimensions> FontCache<'a, T> {
30    /// Returns the width of a specified string.
31    pub fn get_width(&self, text: &str, size: i32) -> i32 {
32        let layout = self
33            .font
34            .layout(text, Scale::uniform(size as f32), Point { x: 0.0, y: 0.0 });
35
36        let mut width = 0;
37        for char in layout {
38            let bb = char.pixel_bounding_box();
39            if let Some(bb) = bb {
40                let pos = char.position().x as i32 + bb.width();
41                if pos > width {
42                    width = pos;
43                }
44            }
45        }
46
47        width
48    }
49
50    /// Draws the specified string to the screen.
51    pub fn draw(
52        &mut self,
53        text: &str,
54        color: &Color,
55        size: i32,
56        pos: &Position,
57        draw: &mut Drawer<NativeTexture = T>,
58    ) {
59        let layout = self.font.layout(
60            text,
61            Scale::uniform(size as f32),
62            Point {
63                x: pos.x as f32,
64                y: pos.y as f32,
65            },
66        );
67
68        for glyph in layout {
69            // Render out texture
70            let bounding_box_opt = glyph.pixel_bounding_box();
71
72            if bounding_box_opt.is_none() {
73                continue;
74            }
75
76            let bounding_box = bounding_box_opt.unwrap();
77
78            // Build hash ID for this glyph
79            let id = CachedGlyph {
80                id: glyph.id(),
81                color: color.clone(),
82                size,
83            };
84
85            if !self.cache.contains_key(&id) {
86                let mut tex = Texture::new(
87                    bounding_box.width() as usize,
88                    bounding_box.height() as usize,
89                );
90
91                {
92                    let render_pos = |x: u32, y: u32, factor: f32| {
93                        tex.draw_pixel(
94                            &color.alpha((factor * 255.0) as u8),
95                            x as usize,
96                            y as usize,
97                        );
98                    };
99
100                    glyph.draw(render_pos);
101                }
102
103                let opengl_tex = draw.convert_native_texture(tex);
104
105                self.cache.insert(id.clone(), opengl_tex);
106            }
107
108            // TODO: Layout text vertically
109            let tex = &self.cache[&id];
110
111            // Setup vertice data
112            draw.draw_texture(
113                tex,
114                &Position::new(bounding_box.min.x as i32, bounding_box.min.y as i32),
115            );
116        }
117    }
118
119    /// Creates a new cache from a .ttf file.
120    pub fn from_bytes(data: &'a [u8]) -> Result<Self, String> {
121        let collection =
122            FontCollection::from_bytes(data).map_err(|x| format!("Failed to read font: {}", x))?;
123
124        // only succeeds if collection consists of one font
125        let font = collection
126            .into_font()
127            .map_err(|x| format!("Failed to read font: {}", x))?;
128
129        Ok(FontCache {
130            font,
131            cache: BTreeMap::new(),
132        })
133    }
134}