tiny_game_framework/graphics/
font.rs

1// https://learnopengl.com/code_viewer_gh.php?code=src/7.in_practice/2.text_rendering/text_rendering.cpp
2use freetype as ft;
3use ft::ffi::{FT_Done_Face, FT_Done_FreeType, FT_Load_Char, FT_Set_Pixel_Sizes, FT_LOAD_RENDER};
4use std::{collections::HashMap, iter::Map, os::raw::c_void};
5
6use gl::{BlendFunc, Enable, GenBuffers, UseProgram, ARRAY_BUFFER, BLEND, CULL_FACE, FALSE, FLOAT, ONE_MINUS_SRC_ALPHA, SRC_ALPHA};
7use glam::{Mat4, Vec2, Vec3};
8use once_cell::sync::Lazy;
9
10use std::ffi::CString;
11
12use gl::types::{GLint, GLsizei};
13
14use crate::{bind_buffer, cstr, Shader};
15
16pub static TEXT_SHADER: Lazy<Shader> = Lazy::new(|| {
17    Shader::new_pipeline(
18            // VS
19        "#version 330 core
20        layout (location = 0) in vec4 vertex; // <vec2 pos, vec2 tex>
21        out vec2 TexCoords;
22        
23        uniform mat4 projection;
24        
25        void main()
26        {
27            gl_Position = projection * vec4(vertex.xy, 0.0, 1.0);
28            TexCoords = vertex.zw;
29        }"
30        ,  // FS
31        "#version 330 core
32        in vec2 TexCoords;
33        out vec4 color;
34        
35        uniform sampler2D text;
36        uniform vec3 textColor;
37        
38        void main()
39        {    
40            vec4 sampled = vec4(1.0, 1.0, 1.0, texture(text, TexCoords).r);
41            color = vec4(textColor, 1.0) * sampled;
42        }"
43    )
44});
45
46struct Character {
47    texture: u32, 
48    size: (i32, i32),
49    bearing: (i32, i32),
50    advance: u32,
51}
52
53pub struct Font {
54    VBO: u32,
55    VAO: u32,
56    characters: HashMap<char, Character>,
57}
58
59impl Font {
60    pub unsafe fn init(width: f32, height: f32, path: &str) -> Self {
61        let (mut VBO, mut VAO) = (0, 0);
62        let mut characters: HashMap<char, Character> = HashMap::new();
63
64        gl::Enable(gl::CULL_FACE);
65        gl::Enable(gl::BLEND);
66        gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA);
67
68        let projection = Mat4::orthographic_rh_gl(0.0, width, 0.0, height, -100.0, 100.0);
69        TEXT_SHADER.use_shader();
70        TEXT_SHADER.uniform_mat4fv(cstr!("projection"), &projection.to_cols_array());
71        gl::UseProgram(0);
72
73        let ft = ft::Library::init().expect("Failed to initialize FreeType library");
74        let mut face = ft.new_face(path, 0).expect("Failed to load font face");
75        FT_Set_Pixel_Sizes(face.raw_mut(), 0, 48);
76        gl::PixelStorei(gl::UNPACK_ALIGNMENT, 1);
77
78        for c in 0..128u8 {
79            if FT_Load_Char(face.raw_mut(), c as u32, FT_LOAD_RENDER) != 0 {
80                eprintln!("Failed to load glyph for character {}", c);
81                continue;
82            }
83            
84            let mut texture = 0;
85            gl::GenTextures(1, &mut texture);
86            gl::BindTexture(gl::TEXTURE_2D, texture);
87            gl::TexImage2D(
88                gl::TEXTURE_2D,
89                0,
90                gl::RED as GLint,
91                face.glyph().bitmap().width() as GLsizei,
92                face.glyph().bitmap().rows() as GLsizei,
93                0,
94                gl::RED,
95                gl::UNSIGNED_BYTE,
96                face.glyph().bitmap().buffer().as_ptr() as *const c_void,
97            );
98            
99            gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as i32);
100            gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as i32);
101            gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as i32);
102            gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as i32);
103            
104            let character = Character {
105                texture,
106                size: (face.glyph().bitmap().width(), face.glyph().bitmap().rows()),
107                bearing: (face.glyph().bitmap_left(), face.glyph().bitmap_top()),
108                advance: face.glyph().advance().x as u32,
109            };
110            characters.insert(c as char, character);
111        }
112        
113        // clean freetype resources (for some reason this causes heap corruption)
114        //FT_Done_Face(face.raw_mut());
115        //FT_Done_FreeType(ft.raw());
116        
117        gl::GenVertexArrays(1, &mut VAO);
118        gl::GenBuffers(1, &mut VBO);
119        gl::BindVertexArray(VAO);
120        gl::BindBuffer(gl::ARRAY_BUFFER, VBO);
121        gl::BufferData(gl::ARRAY_BUFFER, (std::mem::size_of::<f32>() * 6 * 4) as isize, std::ptr::null(), gl::DYNAMIC_DRAW);
122        gl::EnableVertexAttribArray(0);
123        gl::VertexAttribPointer(0, 4, gl::FLOAT, gl::FALSE as gl::types::GLboolean, (4 * std::mem::size_of::<f32>()) as GLsizei, std::ptr::null());
124        gl::BindBuffer(gl::ARRAY_BUFFER, 0);
125        gl::BindVertexArray(0);
126
127        Self {
128            VBO,
129            VAO,
130            characters,
131        }
132    }
133
134    pub fn render_text(&mut self, text: &str, mut x: f32, y: f32, scale: f32, color: Vec3) {
135        unsafe {
136            TEXT_SHADER.use_shader();
137            TEXT_SHADER.uniform_vec3f(cstr!("textColor"), &color);
138            gl::ActiveTexture(gl::TEXTURE0);
139            gl::BindVertexArray(self.VAO);
140            
141            for c in text.chars() {
142                if let Some(ch) = self.characters.get(&c) {
143                    let xpos = x + ch.bearing.0 as f32 * scale;
144                    let ypos = y - (ch.size.1 - ch.bearing.1) as f32 * scale;
145                    
146                    let w = ch.size.0 as f32 * scale;
147                    let h = ch.size.1 as f32 * scale;
148                    
149                    let vertices: [f32; 6 * 4] = [
150                        xpos + w, ypos, 1.0, 1.0,
151                        xpos, ypos, 0.0, 1.0,
152                        xpos, ypos + h, 0.0, 0.0,
153    
154                        xpos + w, ypos + h, 1.0, 0.0,
155                        xpos + w, ypos, 1.0, 1.0,
156                        xpos, ypos + h, 0.0, 0.0,
157                    ];
158                        
159                    gl::BindTexture(gl::TEXTURE_2D, ch.texture);
160                    gl::BindBuffer(gl::ARRAY_BUFFER, self.VBO);
161                    gl::BufferSubData(
162                        gl::ARRAY_BUFFER,
163                        0,
164                        (vertices.len() * std::mem::size_of::<f32>()) as isize,
165                        vertices.as_ptr() as *const gl::types::GLvoid,
166                    );
167                    gl::BindBuffer(gl::ARRAY_BUFFER, 0);
168    
169                    gl::DrawArrays(gl::TRIANGLES, 0, 6);
170    
171                    x += (ch.advance >> 6) as f32 * scale; // Bitshift by 6 to get value in pixels (2^6 = 64)
172                }
173            }
174    
175            gl::BindVertexArray(0);
176            gl::BindTexture(gl::TEXTURE_2D, 0);
177        }
178    }
179}