rust_raylib/
text.rs

1use crate::{
2    color::Color,
3    ffi,
4    math::{Rectangle, Vector2},
5    texture::Image,
6};
7use std::ffi::CString;
8
9pub use crate::ffi::FontType;
10
11/// Font, font texture and GlyphInfo array data
12#[derive(Debug)]
13#[repr(transparent)]
14pub struct Font {
15    pub(crate) raw: ffi::Font,
16}
17
18impl Font {
19    /// Base size (default chars height)
20    #[inline]
21    pub fn base_size(&self) -> u32 {
22        self.raw.baseSize as _
23    }
24
25    /// Number of glyph characters
26    #[inline]
27    pub fn glyph_count(&self) -> usize {
28        self.raw.glyphCount as _
29    }
30
31    /// Padding around the glyph characters
32    #[inline]
33    pub fn glyph_padding(&self) -> u32 {
34        self.raw.glyphPadding as _
35    }
36
37    /// Load font from file into GPU memory (VRAM)
38    #[inline]
39    pub fn from_file(file_name: &str) -> Option<Self> {
40        let file_name = CString::new(file_name).unwrap();
41
42        let raw = unsafe { ffi::LoadFont(file_name.as_ptr()) };
43
44        if unsafe { ffi::IsFontReady(raw.clone()) } {
45            Some(Self { raw })
46        } else {
47            None
48        }
49    }
50
51    /// Load font from file with extended parameters
52    #[inline]
53    pub fn from_file_ex(file_name: &str, font_size: u32, chars: &[char]) -> Option<Self> {
54        let file_name = CString::new(file_name).unwrap();
55
56        let raw = unsafe {
57            ffi::LoadFontEx(
58                file_name.as_ptr(),
59                font_size as _,
60                chars.as_ptr() as *mut _,
61                chars.len() as _,
62            )
63        };
64
65        if unsafe { ffi::IsFontReady(raw.clone()) } {
66            Some(Self { raw })
67        } else {
68            None
69        }
70    }
71
72    /// Load font from Image (XNA style)
73    #[inline]
74    pub fn from_image(image: &Image, key_color: Color, first_char: char) -> Option<Self> {
75        let raw =
76            unsafe { ffi::LoadFontFromImage(image.raw.clone(), key_color.into(), first_char as _) };
77
78        if unsafe { ffi::IsFontReady(raw.clone()) } {
79            Some(Self { raw })
80        } else {
81            None
82        }
83    }
84
85    /// Load font from memory buffer, fileType refers to extension: i.e. '.ttf'
86    #[inline]
87    pub fn from_memory(
88        file_type: &str,
89        file_data: &[u8],
90        font_size: u32,
91        chars: &[char],
92    ) -> Option<Self> {
93        let file_type = CString::new(file_type).unwrap();
94
95        let raw = unsafe {
96            ffi::LoadFontFromMemory(
97                file_type.as_ptr(),
98                file_data.as_ptr(),
99                file_data.len() as _,
100                font_size as _,
101                chars.as_ptr() as *mut _,
102                chars.len() as _,
103            )
104        };
105
106        if unsafe { ffi::IsFontReady(raw.clone()) } {
107            Some(Self { raw })
108        } else {
109            None
110        }
111    }
112
113    /// Export font as code file, returns true on success
114    #[inline]
115    pub fn export_as_code(&self, file_name: &str) -> bool {
116        let file_name = CString::new(file_name).unwrap();
117
118        unsafe { ffi::ExportFontAsCode(self.raw.clone(), file_name.as_ptr()) }
119    }
120
121    /// Measure string width for default font
122    #[inline]
123    pub fn measure_text(text: &str, font_size: u32) -> u32 {
124        let text = CString::new(text).unwrap();
125
126        unsafe { ffi::MeasureText(text.as_ptr(), font_size as _) as _ }
127    }
128
129    /// Measure string size for Font
130    #[inline]
131    pub fn measure_text_ex(&self, text: &str, font_size: f32, spacing: f32) -> Vector2 {
132        let text = CString::new(text).unwrap();
133
134        unsafe { ffi::MeasureTextEx(self.raw.clone(), text.as_ptr(), font_size, spacing).into() }
135    }
136
137    /// Get glyph index position in font for a codepoint (unicode character), fallback to '?' if not found
138    #[inline]
139    pub fn get_glyph_index(&self, codepoint: char) -> usize {
140        unsafe { ffi::GetGlyphIndex(self.raw.clone(), codepoint as _) as _ }
141    }
142
143    /// Get glyph rectangle in font atlas for a codepoint (unicode character), fallback to '?' if not found
144    #[inline]
145    pub fn get_glyph_atlas_rect(&self, codepoint: char) -> Rectangle {
146        unsafe { ffi::GetGlyphAtlasRec(self.raw.clone(), codepoint as _).into() }
147    }
148
149    /// Get glyph font info data for a codepoint (unicode character), fallback to '?' if not found
150    #[inline]
151    pub fn get_glyph_info(&self, codepoint: char) -> GlyphInfo {
152        let info = unsafe { ffi::GetGlyphInfo(self.raw.clone(), codepoint as _) };
153
154        GlyphInfo {
155            value: char::from_u32(info.value as _).unwrap(),
156            offset_x: info.offsetX,
157            offset_y: info.offsetY,
158            advance_x: info.advanceX,
159            image: Image {
160                raw: unsafe { ffi::ImageCopy(info.image) },
161            },
162        }
163    }
164
165    /// Get the 'raw' ffi type
166    /// Take caution when cloning so it doesn't outlive the original
167    #[inline]
168    pub fn as_raw(&self) -> &ffi::Font {
169        &self.raw
170    }
171
172    /// Get the 'raw' ffi type
173    /// Take caution when cloning so it doesn't outlive the original
174    #[inline]
175    pub fn as_raw_mut(&mut self) -> &mut ffi::Font {
176        &mut self.raw
177    }
178
179    /// Convert a 'raw' ffi object to a safe wrapper
180    ///
181    /// # Safety
182    /// * The raw object must be correctly initialized
183    /// * The raw object should be unique. Otherwise, make sure its clones don't outlive the newly created object.
184    #[inline]
185    pub unsafe fn from_raw(raw: ffi::Font) -> Self {
186        Self { raw }
187    }
188}
189
190impl Default for Font {
191    /// Get the default Font
192    #[inline]
193    fn default() -> Self {
194        Self {
195            raw: unsafe { ffi::GetFontDefault() },
196        }
197    }
198}
199
200impl Drop for Font {
201    #[inline]
202    fn drop(&mut self) {
203        unsafe { ffi::UnloadFont(self.raw.clone()) }
204    }
205}
206
207/// Generate image font atlas using chars info
208#[inline]
209pub fn gen_image_font_atlas(
210    chars: Vec<GlyphInfo>,
211    font_size: u32,
212    padding: i32,
213    skyline_pack: bool,
214) -> Option<(Image, Vec<Rectangle>)> {
215    assert!(!chars.is_empty());
216
217    let mut recs: *mut ffi::Rectangle = std::ptr::null_mut();
218    let chars_ffi: Vec<_> = chars
219        .iter()
220        .map(|gi| ffi::GlyphInfo {
221            value: gi.value as _,
222            offsetX: gi.offset_x as _,
223            offsetY: gi.offset_y as _,
224            advanceX: gi.advance_x as _,
225            image: gi.image.raw.clone(),
226        })
227        .collect();
228
229    let image = unsafe {
230        ffi::GenImageFontAtlas(
231            chars_ffi.as_ptr(),
232            (&mut recs) as *mut _,
233            chars.len() as _,
234            font_size as _,
235            padding,
236            if skyline_pack { 1 } else { 0 },
237        )
238    };
239
240    if !unsafe { ffi::IsImageReady(image.clone()) } {
241        return None;
242    }
243
244    let mut vec = Vec::new();
245
246    for i in 0..chars.len() {
247        vec.push(unsafe { recs.add(i).read().into() });
248    }
249
250    unsafe {
251        ffi::MemFree(recs as *mut _);
252    }
253
254    Some((Image { raw: image }, vec))
255}
256
257/// GlyphInfo, font characters glyphs info
258#[repr(C)]
259#[derive(Clone, Debug)]
260pub struct GlyphInfo {
261    /// Character value (Unicode)
262    pub value: char,
263    /// Character offset X when drawing
264    pub offset_x: i32,
265    /// Character offset Y when drawing
266    pub offset_y: i32,
267    /// Character advance position X
268    pub advance_x: i32,
269    /// Character image data
270    pub image: Image,
271}
272
273impl GlyphInfo {
274    /// Load font data for further use
275    #[inline]
276    pub fn from_file_data(
277        file_data: &[u8],
278        font_size: u32,
279        font_chars: &[char],
280        font_type: FontType,
281    ) -> Vec<GlyphInfo> {
282        assert!(!font_chars.is_empty());
283        let len = font_chars.len();
284
285        let infos = unsafe {
286            ffi::LoadFontData(
287                file_data.as_ptr(),
288                file_data.len() as _,
289                font_size as _,
290                font_chars.as_ptr() as *mut _,
291                len as _,
292                font_type as _,
293            )
294        };
295
296        let mut vec = Vec::new();
297
298        for i in 0..len {
299            let gi = unsafe { infos.add(i).read() };
300
301            vec.push(GlyphInfo {
302                value: char::from_u32(gi.value as _).unwrap(),
303                offset_x: gi.offsetX,
304                offset_y: gi.offsetY,
305                advance_x: gi.advanceX,
306                image: Image {
307                    raw: unsafe { ffi::ImageCopy(gi.image) },
308                },
309            });
310        }
311
312        unsafe {
313            ffi::UnloadFontData(infos, len as _);
314        }
315
316        vec
317    }
318}