bracket_terminal/hal/gl_common/
font.rs

1use super::{gl_error, TextureId};
2use crate::BResult;
3use bracket_color::prelude::RGB;
4use bracket_embedding::prelude::EMBED;
5use glow::HasContext;
6
7#[derive(PartialEq, Clone)]
8/// BTerm's representation of a font or tileset file.
9pub struct Font {
10    pub bitmap_file: String,
11    pub width: u32,
12    pub height: u32,
13
14    pub gl_id: Option<TextureId>,
15
16    pub tile_size: (u32, u32),
17    pub explicit_background: Option<RGB>,
18    pub font_dimensions_glyphs: (u32, u32),
19    pub font_dimensions_texture: (f32, f32),
20}
21
22#[allow(non_snake_case)]
23impl Font {
24    /// Creates an unloaded texture with filename and size parameters provided.
25    pub fn new<S: ToString>(filename: S, width: u32, height: u32, tile_size: (u32, u32)) -> Font {
26        Font {
27            bitmap_file: filename.to_string(),
28            width,
29            height,
30            gl_id: None,
31            tile_size,
32            explicit_background: None,
33            font_dimensions_glyphs: (tile_size.0 / width, tile_size.1 / height),
34            font_dimensions_texture: (
35                tile_size.0 as f32 / width as f32,
36                tile_size.1 as f32 / height as f32,
37            ),
38        }
39    }
40
41    fn load_image(filename: &str) -> image::DynamicImage {
42        let resource = EMBED.lock().get_resource(filename.to_string());
43        match resource {
44            None => image::open(std::path::Path::new(&filename.to_string()))
45                .expect("Failed to load texture"),
46            Some(res) => image::load_from_memory(res).expect("Failed to load texture from memory"),
47        }
48    }
49
50    /// Loads a font file (texture) to obtain the width and height for you
51    pub fn load<S: ToString>(
52        filename: S,
53        tile_size: (u32, u32),
54        explicit_background: Option<RGB>,
55    ) -> Font {
56        let img = Font::load_image(&filename.to_string());
57        Font {
58            bitmap_file: filename.to_string(),
59            width: img.width(),
60            height: img.height(),
61            gl_id: None,
62            tile_size,
63            explicit_background,
64            font_dimensions_glyphs: (img.width() / tile_size.0, img.height() / tile_size.1),
65            font_dimensions_texture: (
66                tile_size.0 as f32 / img.width() as f32,
67                tile_size.1 as f32 / img.height() as f32,
68            ),
69        }
70    }
71
72    /// Load a font, and allocate it as an OpenGL resource. Returns the OpenGL binding number (which is also set in the structure).
73    pub fn setup_gl_texture(&mut self, gl: &glow::Context) -> BResult<TextureId> {
74        let texture;
75
76        unsafe {
77            texture = gl.create_texture().unwrap();
78            gl.bind_texture(glow::TEXTURE_2D, Some(texture));
79            gl.tex_parameter_i32(
80                glow::TEXTURE_2D,
81                glow::TEXTURE_WRAP_S,
82                glow::CLAMP_TO_EDGE as i32,
83            ); // set texture wrapping to gl::REPEAT (default wrapping method)
84            gl.tex_parameter_i32(
85                glow::TEXTURE_2D,
86                glow::TEXTURE_WRAP_T,
87                glow::CLAMP_TO_EDGE as i32,
88            );
89            // set texture filtering parameters
90            gl.tex_parameter_i32(
91                glow::TEXTURE_2D,
92                glow::TEXTURE_MIN_FILTER,
93                glow::NEAREST as i32,
94            );
95            gl.tex_parameter_i32(
96                glow::TEXTURE_2D,
97                glow::TEXTURE_MAG_FILTER,
98                glow::NEAREST as i32,
99            );
100
101            let img_orig = Font::load_image(&self.bitmap_file);
102            let w = img_orig.width() as i32;
103            let h = img_orig.height() as i32;
104            self.width = w as u32;
105            self.height = h as u32;
106            let img_flip = img_orig.flipv();
107            let img = img_flip.to_rgba8();
108            let mut data = img.into_raw();
109            if let Some(bg_rgb) = self.explicit_background {
110                let bg_r = (bg_rgb.r * 255.0) as u8;
111                let bg_g = (bg_rgb.g * 255.0) as u8;
112                let bg_b = (bg_rgb.b * 255.0) as u8;
113                let len = data.len() / 4;
114                for i in 0..len {
115                    let idx = i * 4;
116                    if data[idx] == bg_r && data[idx + 1] == bg_g && data[idx + 2] == bg_b {
117                        data[idx] = 0;
118                        data[idx + 1] = 0;
119                        data[idx + 2] = 0;
120                        data[idx + 3] = 0;
121                    }
122                }
123            }
124            let format = glow::RGBA;
125            gl.tex_image_2d(
126                glow::TEXTURE_2D,
127                0,
128                format as i32,
129                w,
130                h,
131                0,
132                format,
133                glow::UNSIGNED_BYTE,
134                Some(&data),
135            );
136        }
137
138        self.gl_id = Some(texture);
139        gl_error(gl);
140
141        Ok(texture)
142    }
143
144    /// Sets this font file as the active texture
145    pub fn bind_texture(&self, gl: &glow::Context) {
146        unsafe {
147            gl.bind_texture(glow::TEXTURE_2D, self.gl_id);
148            gl_error(gl);
149        }
150    }
151}
152
153// Some unit testing for fonts
154
155#[cfg(test)]
156mod tests {
157    use super::Font;
158
159    #[test]
160    // Tests that we make an RGB triplet at defaults and it is black.
161    fn make_font_minimal() {
162        let f = Font::new("test.png", 1, 2, (8, 8));
163        assert_eq!(f.bitmap_file, "test.png");
164        assert_eq!(f.width, 1);
165        assert_eq!(f.height, 2);
166    }
167
168    #[test]
169    // Tests that we make an RGB triplet at defaults and it is black.
170    fn make_font_from_file() {
171        let f = Font::load("resources/terminal8x8.png", (8, 8), None);
172        assert_eq!(f.width, 128);
173        assert_eq!(f.height, 128);
174    }
175}