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#[derive(Debug)]
13#[repr(transparent)]
14pub struct Font {
15 pub(crate) raw: ffi::Font,
16}
17
18impl Font {
19 #[inline]
21 pub fn base_size(&self) -> u32 {
22 self.raw.baseSize as _
23 }
24
25 #[inline]
27 pub fn glyph_count(&self) -> usize {
28 self.raw.glyphCount as _
29 }
30
31 #[inline]
33 pub fn glyph_padding(&self) -> u32 {
34 self.raw.glyphPadding as _
35 }
36
37 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[inline]
139 pub fn get_glyph_index(&self, codepoint: char) -> usize {
140 unsafe { ffi::GetGlyphIndex(self.raw.clone(), codepoint as _) as _ }
141 }
142
143 #[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 #[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 #[inline]
168 pub fn as_raw(&self) -> &ffi::Font {
169 &self.raw
170 }
171
172 #[inline]
175 pub fn as_raw_mut(&mut self) -> &mut ffi::Font {
176 &mut self.raw
177 }
178
179 #[inline]
185 pub unsafe fn from_raw(raw: ffi::Font) -> Self {
186 Self { raw }
187 }
188}
189
190impl Default for Font {
191 #[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#[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#[repr(C)]
259#[derive(Clone, Debug)]
260pub struct GlyphInfo {
261 pub value: char,
263 pub offset_x: i32,
265 pub offset_y: i32,
267 pub advance_x: i32,
269 pub image: Image,
271}
272
273impl GlyphInfo {
274 #[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}