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}