cgl_rs/graphics/text.rs
1//! The text module contains the text rendering, font loading functionality of CGL
2
3#![allow(non_camel_case_types)]
4use libc::{c_void, c_int, c_char, size_t, c_uchar};
5
6use crate::graphics::texture::{Texture, CGL_texture};
7use crate::math::Vector2;
8
9// The internal font handle
10#[repr(C)]
11pub(crate) struct CGL_font {
12 _private: c_void
13}
14
15/// The Font Character struct contains all the information about a character in a font
16#[repr(C)] #[derive(Copy, Clone)]
17pub struct FontCharacter {
18 pub size: Vector2,
19 pub normalized_size: Vector2,
20 pub offset: Vector2,
21 pub normalized_offset: Vector2,
22 pub bearing: Vector2,
23 pub bearing_normalized: Vector2,
24 pub(crate) bitmap: *mut c_uchar,
25 pub ch: c_char
26}
27
28
29extern {
30 fn CGL_text_init() -> c_int;
31 fn CGL_text_shutdown() -> c_void;
32 fn CGL_font_load(path: *const c_char) -> *mut CGL_font;
33 fn CGL_font_load_from_memory(data: *const c_char, size: size_t) -> *mut CGL_font;
34 fn CGL_font_destory(font: *mut CGL_font) -> c_void;
35 fn CGL_font_get_atlas(font: *mut CGL_font) -> *mut CGL_texture;
36 fn CGL_font_build_atlas(font: *mut CGL_font, width: size_t, height: size_t, font_size: size_t) -> c_int;
37 fn CGL_font_get_characters(font: *mut CGL_font) -> *mut FontCharacter;
38 fn CGL_text_bake_to_texture(string: *const c_char, string_length: size_t, font: *mut CGL_font, width: *mut size_t, height: *mut size_t) -> *mut CGL_texture;
39}
40
41/// Initialized the text module
42///
43/// Note: This function must be called before any other text functions
44///
45/// # Example
46///
47/// ```
48/// cgl_rs::init();
49/// let mut window = cgl_rs::Window::new("CGL Window", 800, 600).unwrap();
50/// cgl_rs::graphics::init();
51/// cgl_rs::graphics::text::init();
52/// // Do stuff
53/// cgl_rs::graphics::text::shutdown();
54/// cgl_rs::graphics::shutdown();
55/// window.destroy();
56/// cgl_rs::shutdown();
57/// ```
58pub fn init() {
59 unsafe {
60 CGL_text_init();
61 }
62}
63
64/// Shuts down the text module
65///
66/// Note: This function must be called after all other text functions have been called
67///
68/// # Example
69///
70/// ```
71/// cgl_rs::init();
72/// let mut window = cgl_rs::Window::new("CGL Window", 800, 600).unwrap();
73/// cgl_rs::graphics::init();
74/// cgl_rs::graphics::text::init();
75/// // Do stuff
76/// cgl_rs::graphics::text::shutdown();
77/// cgl_rs::graphics::shutdown();
78/// window.destroy();
79/// cgl_rs::shutdown();
80/// ```
81pub fn shutdown() {
82 unsafe {
83 CGL_text_shutdown();
84 }
85}
86
87/// The public font object
88pub struct Font {
89 pub(crate) handle: *mut CGL_font,
90 pub(crate) has_been_destroyed: bool
91}
92
93impl std::ops::Index<u8> for Font {
94 type Output = FontCharacter;
95
96 /// Indexes the font's characters by their ASCII value.
97 ///
98 /// # Arguments
99 ///
100 /// * `index` - An `i8` representing the ASCII value of the character to retrieve.
101 ///
102 /// # Returns
103 ///
104 /// Returns a reference to the `FontCharacter` object corresponding to the given ASCII value.
105 ///
106 /// # Safety
107 ///
108 /// This function is marked as unsafe because it dereferences a raw pointer returned by the `CGL_font_get_characters` function.
109 ///
110 /// # Example
111 ///
112 /// ```no_run
113 /// cgl_rs::init();
114 /// let mut window = cgl_rs::Window::new("CGL Window", 800, 600).unwrap();
115 /// cgl_rs::graphics::init();
116 /// cgl_rs::graphics::text::init();
117 /// let font = cgl_rs::graphics::text::Font::load("path/to/font.ttf").unwrap();
118 /// let character = font[b'c'];
119 /// cgl_rs::graphics::text::shutdown();
120 /// cgl_rs::graphics::shutdown();
121 /// window.destroy();
122 /// cgl_rs::shutdown();
123 /// ```
124 fn index(&self, index: u8) -> &Self::Output {
125 unsafe {
126 &*CGL_font_get_characters(self.handle).offset(index as isize)
127 }
128 }
129}
130
131impl Font {
132
133 /// Loads a font from a file path
134 ///
135 /// # Arguments
136 ///
137 /// * `path` - A string slice that holds the path to the font file
138 ///
139 /// # Returns
140 ///
141 /// Returns a `Result` containing a `Font` object if the font was loaded successfully, or an error message if it failed to load.
142 ///
143 /// # Example
144 ///
145 /// ```no_run
146 /// cgl_rs::init();
147 /// let mut window = cgl_rs::Window::new("CGL Window", 800, 600).unwrap();
148 /// cgl_rs::graphics::init();
149 /// cgl_rs::graphics::text::init();
150 /// {
151 /// let font = cgl_rs::graphics::text::Font::load("path/to/font.ttf").unwrap();
152 /// }
153 /// cgl_rs::graphics::text::shutdown();
154 /// cgl_rs::graphics::shutdown();
155 /// window.destroy();
156 /// cgl_rs::shutdown();
157 /// ```
158 pub fn load(path: &str) -> Result<Font, &'static str> {
159 let c_path = std::ffi::CString::new(path).unwrap();
160 let handle = unsafe { CGL_font_load(c_path.as_ptr()) };
161 if handle.is_null() {
162 Err("Failed to load font")
163 } else {
164 Ok(Font {
165 handle,
166 has_been_destroyed: false
167 })
168 }
169 }
170
171 /// Loads a font from memory
172 ///
173 /// # Arguments
174 ///
175 /// * `data` - A pointer to the font data in memory
176 /// * `size` - The size of the font data in bytes
177 ///
178 /// # Returns
179 ///
180 /// Returns a `Result` containing a `Font` object if the font was loaded successfully, or an error message if it failed to load.
181 ///
182 /// # Example
183 ///
184 /// ```no_run
185 /// cgl_rs::init();
186 /// let mut window = cgl_rs::Window::new("CGL Window", 800, 600).unwrap();
187 /// cgl_rs::graphics::init();
188 /// cgl_rs::graphics::text::init();
189 /// {
190 /// // let font_data = include_bytes!("path/to/font.ttf");
191 /// // let font = cgl_rs::graphics::text::Font::load_from_memory(font_data.as_ptr(), font_data.len()).unwrap();
192 /// }
193 /// cgl_rs::graphics::text::shutdown();
194 /// cgl_rs::graphics::shutdown();
195 /// window.destroy();
196 /// cgl_rs::shutdown();
197 /// ```
198 pub fn load_from_memory(data: *const u8, size: usize) -> Result<Font, &'static str> {
199 let handle = unsafe { CGL_font_load_from_memory(data as *const c_char, size) };
200 if handle.is_null() {
201 Err("Failed to load font")
202 } else {
203 Ok(Font {
204 handle,
205 has_been_destroyed: false
206 })
207 }
208 }
209
210 /// Destroys the font object and frees any resources associated with it.
211 ///
212 /// # Example
213 ///
214 /// ```no_run
215 /// cgl_rs::init();
216 /// let mut window = cgl_rs::Window::new("CGL Window", 800, 600).unwrap();
217 /// cgl_rs::graphics::init();
218 /// cgl_rs::graphics::text::init();
219 /// {
220 /// let mut font = cgl_rs::graphics::text::Font::load("path/to/font.ttf").unwrap();
221 /// // Use the font...
222 /// font.destroy(); // Or, just let the font go out of scope and it will be destroyed automatically.
223 /// }
224 /// cgl_rs::graphics::text::shutdown();
225 /// cgl_rs::graphics::shutdown();
226 /// window.destroy();
227 /// cgl_rs::shutdown();
228 /// ```
229 pub fn destroy(&mut self) {
230 if !self.has_been_destroyed {
231 unsafe {
232 CGL_font_destory(self.handle);
233 }
234 self.has_been_destroyed = true;
235 }
236 }
237
238 /// Returns the texture atlas for the font. This function must be called after `build_atlas` has been called.
239 ///
240 /// Note: This atlas texture is managed by the font object and will be destroyed when the font object is destroyed.
241 ///
242 /// # Returns
243 ///
244 /// Returns a `Texture` object representing the texture atlas for the font.
245 ///
246 /// # Example
247 ///
248 /// ```no_run
249 /// cgl_rs::init();
250 /// let mut window = cgl_rs::Window::new("CGL Window", 800, 600).unwrap();
251 /// cgl_rs::graphics::init();
252 /// cgl_rs::graphics::text::init();
253 /// {
254 /// let font = cgl_rs::graphics::text::Font::load("path/to/font.ttf").unwrap();
255 /// font.build_atlas(512, 512, 32).unwrap();
256 /// let texture = font.get_atlas();
257 /// // Use the texture...
258 /// }
259 /// cgl_rs::graphics::text::shutdown();
260 /// cgl_rs::graphics::shutdown();
261 /// window.destroy();
262 /// cgl_rs::shutdown();
263 /// ```
264 pub fn get_atlas(&self) -> Texture {
265 let handle = unsafe { CGL_font_get_atlas(self.handle) };
266 Texture {
267 handle,
268 has_been_destroyed: true
269 }
270 }
271
272 /// Builds the texture atlas for the font. This function must be called before any text can be rendered with the font.
273 ///
274 /// # Arguments
275 ///
276 /// * `width` - The width of the texture atlas in pixels.
277 /// * `height` - The height of the texture atlas in pixels.
278 /// * `font_size` - The size of the font in pixels.
279 ///
280 /// # Returns
281 ///
282 /// Returns `Ok(())` if the texture atlas was built successfully, otherwise returns an error message.
283 ///
284 /// # Example
285 ///
286 /// ```no_run
287 /// cgl_rs::init();
288 /// let mut window = cgl_rs::Window::new("CGL Window", 800, 600).unwrap();
289 /// cgl_rs::graphics::init();
290 /// cgl_rs::graphics::text::init();
291 /// {
292 /// let font = cgl_rs::graphics::text::Font::load("path/to/font.ttf").unwrap();
293 /// font.build_atlas(512, 512, 32).unwrap();
294 /// // Use the font...
295 /// }
296 /// cgl_rs::graphics::text::shutdown();
297 /// cgl_rs::graphics::shutdown();
298 /// window.destroy();
299 /// cgl_rs::shutdown();
300 /// ```
301 pub fn build_atlas(&self, width: usize, height: usize, font_size: usize) -> Result<(), &'static str> {
302 let result = unsafe { CGL_font_build_atlas(self.handle, width, height, font_size) };
303 if result != 0 {
304 Ok(())
305 } else {
306 Err("Failed to build font atlas")
307 }
308 }
309}
310
311impl Drop for Font {
312 fn drop(&mut self) {
313 self.destroy();
314 }
315}
316
317impl Clone for Font {
318 fn clone(&self) -> Self {
319 Font {
320 handle: self.handle.clone(),
321 has_been_destroyed: true
322 }
323 }
324}
325
326/// Bakes the given text into a texture using the specified font and font size.
327///
328/// Note: This texture is not managed by the font object and the ownership of the texture is passed to the caller.
329///
330/// # Arguments
331///
332/// * `font` - The font to use for rendering the text.
333/// * `text` - The text to render.
334///
335/// # Returns
336///
337/// Returns a tuple containing the resulting `Texture` object, as well as the width and height of the texture in pixels.
338///
339/// # Example
340///
341/// ```no_run
342/// cgl_rs::init();
343/// let mut window = cgl_rs::Window::new("CGL Window", 800, 600).unwrap();
344/// cgl_rs::graphics::init();
345/// cgl_rs::graphics::text::init();
346/// {
347/// let font = cgl_rs::graphics::text::Font::load("path/to/font.ttf").unwrap();
348/// let (texture, width, height) = cgl_rs::graphics::text::bake_to_texture(&font, "Hello, world!").unwrap();
349/// // Use the texture...
350/// }
351/// cgl_rs::graphics::text::shutdown();
352/// cgl_rs::graphics::shutdown();
353/// window.destroy();
354/// cgl_rs::shutdown();
355/// ```
356pub fn bake_to_texture(font: &Font, text: &str) -> Result<(Texture, u32, u32), &'static str> {
357 let mut width_sz: size_t = 0;
358 let mut height_sz: size_t = 0;
359 let width_ptr = &mut width_sz as *mut size_t;
360 let height_ptr = &mut height_sz as *mut size_t;
361 let string_in_c = std::ffi::CString::new(text).unwrap();
362 let handle = unsafe { CGL_text_bake_to_texture(string_in_c.as_ptr(), text.len(), font.handle, width_ptr, height_ptr) };
363 if handle.is_null() {
364 Err("Failed to bake text to texture")
365 } else {
366 Ok((Texture {
367 handle,
368 has_been_destroyed: false
369 }, width_sz as u32, height_sz as u32))
370 }
371}