tiny_game_framework/graphics/
font.rs1use 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 "#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 , "#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 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; }
173 }
174
175 gl::BindVertexArray(0);
176 gl::BindTexture(gl::TEXTURE_2D, 0);
177 }
178 }
179}