stereokit_rust/
font.rs

1use crate::{StereoKitError, system::IAsset, tex::TexT};
2use std::{
3    ffi::{CStr, CString, c_char},
4    path::Path,
5    ptr::NonNull,
6};
7
8/// This class represents a text font asset! On the back-end, this asset is composed of a texture with font characters
9/// rendered to it, and a list of data about where, and how large those characters are on the texture.
10///
11/// This asset is used anywhere that text shows up, like in the UI or Text classes!
12/// <https://stereokit.net/Pages/StereoKit/Font.html>
13///
14/// ### Examples
15/// ```
16/// # stereokit_rust::test_init_sk!(); // !!!! Get a proper way to initialize sk !!!!
17/// use stereokit_rust::{ui::Ui, maths::{Vec3, Quat, Pose, Matrix},
18///                      font::Font, system::{Assets, Text}, util::named_colors};
19///
20/// // Load font assets
21/// let emoji_font = if cfg!(windows) {
22///     // TODO: Doesn't work on Windows Github Actions.
23///     // return;
24///     Font::from_file("C:\\Windows\\Fonts\\seguiemj.ttf").unwrap_or_default()
25/// } else {
26///     Font::from_file("fonts/Noto_Emoji/NotoEmoji-VariableFont_wght.ttf").unwrap_or_default()
27/// };
28/// let text_font = if cfg!(windows) {
29///     Font::from_file("C:\\Windows\\Fonts\\Arial.ttf").unwrap_or_default()
30/// } else {
31///     Font::from_file("fonts/Inter/Inter-VariableFont_opsz_wght.ttf").unwrap_or_default()
32/// };
33/// Assets::block_for_priority(i32::MAX);
34/// let emoji_style = Some(Text::make_style(emoji_font, 0.35, named_colors::RED));
35/// let text_style = Text::make_style(text_font, 0.025, named_colors::GREEN);
36/// let mut window_pose = Pose::new(
37///     [0.0, 0.0, 0.90], Some([0.0, 160.0, 0.0].into()));
38///
39/// filename_scr = "screenshots/font.jpeg";
40/// test_screenshot!( // !!!! Get a proper main loop !!!!
41///     Text::add_at(token, "😋 Emojis🤪\n\n  🧐", Matrix::IDENTITY, emoji_style,
42///                  None, None, None, None, None, None);
43///
44///     Ui::window_begin("Default Font", &mut window_pose, None, None, None);
45///     Ui::push_text_style(text_style);
46///     Ui::text("text font", None, None, None, Some(0.14), None, None);
47///     Ui::pop_text_style();
48///     Ui::window_end();
49/// );
50/// ```
51/// <img src="https://raw.githubusercontent.com/mvvvv/StereoKit-rust/refs/heads/master/screenshots/font.jpeg" alt="screenshot" width="200">
52#[repr(C)]
53#[derive(Debug)]
54pub struct Font(pub NonNull<_FontT>);
55impl Drop for Font {
56    fn drop(&mut self) {
57        unsafe { font_release(self.0.as_ptr()) };
58    }
59}
60impl AsRef<Font> for Font {
61    fn as_ref(&self) -> &Font {
62        self
63    }
64}
65/// StereoKit internal type.
66#[repr(C)]
67#[derive(Debug)]
68pub struct _FontT {
69    _unused: [u8; 0],
70}
71/// StereoKit ffi type.
72pub type FontT = *mut _FontT;
73
74unsafe extern "C" {
75    pub fn font_find(id: *const c_char) -> FontT;
76    pub fn font_create(file_utf8: *const c_char) -> FontT;
77    pub fn font_create_files(in_arr_files: *mut *const c_char, file_count: i32) -> FontT;
78    pub fn font_create_family(font_family: *const c_char) -> FontT;
79    pub fn font_set_id(font: FontT, id: *const c_char);
80    pub fn font_get_id(font: FontT) -> *const c_char;
81    pub fn font_addref(font: FontT);
82    pub fn font_release(font: FontT);
83    pub fn font_get_tex(font: FontT) -> TexT;
84}
85
86impl IAsset for Font {
87    // fn id(&mut self, id: impl AsRef<str>) {
88    //     self.id(id);
89    // }
90
91    fn get_id(&self) -> &str {
92        self.get_id()
93    }
94}
95
96impl Default for Font {
97    /// The default font used by StereoKit’s text. This varies from platform to platform, but is typically a sans-serif
98    /// general purpose font, such as Segoe UI.
99    /// <https://stereokit.net/Pages/StereoKit/Font/Default.html>
100    ///
101    /// see also [`font_find`]
102    fn default() -> Self {
103        let c_str = CString::new("default/font").unwrap();
104        Font(NonNull::new(unsafe { font_find(c_str.as_ptr()) }).unwrap())
105    }
106}
107
108impl Font {
109    /// Loads a font and creates a font asset from it. If a glyph is not found you can call unwrap_or_default() to get
110    /// the default font.
111    /// <https://stereokit.net/Pages/StereoKit/Font/FromFile.html>
112    /// * `file_utf8` - The path to the font file to load.
113    ///
114    /// see also [`font_create`]
115    /// ### Examples
116    /// ```
117    /// # stereokit_rust::test_init_sk!(); // !!!! Get a proper way to initialize sk !!!!
118    /// use stereokit_rust::{maths:: Matrix, font::Font, system::Text, util::named_colors};
119    ///
120    /// let text_font = if cfg!(windows) {
121    ///     Font::from_file("C:\\Windows\\Fonts\\Arial.ttf").unwrap_or_default()
122    /// } else {
123    ///     Font::from_file("fonts/Inter/Inter-VariableFont_opsz_wght.ttf").unwrap_or_default()
124    /// };
125    /// let text_style = Some(Text::make_style(&text_font, 0.025, named_colors::GREEN));
126    ///
127    /// test_steps!( // !!!! Get a proper main loop !!!!
128    ///     Text::add_at(token, "My Green Text", Matrix::IDENTITY, text_style,
129    ///                  None, None, None, None, None, None);
130    ///     assert_ne!(text_font.get_id(), "default/font");
131    ///     assert   !(text_font.get_id().starts_with("sk/font/"));
132    /// );
133    /// ```
134    pub fn from_file(file_utf8: impl AsRef<Path>) -> Result<Font, StereoKitError> {
135        let path_buf = file_utf8.as_ref().to_path_buf();
136        let c_str = CString::new(
137            path_buf
138                .clone()
139                .to_str()
140                .ok_or(StereoKitError::FontFile(path_buf.clone(), "CString conversion".to_string()))?,
141        )?;
142        Ok(Font(
143            NonNull::new(unsafe { font_create(c_str.as_ptr()) })
144                .ok_or(StereoKitError::FontFile(path_buf, "font_create failed".to_string()))?,
145        ))
146    }
147
148    /// Loads a font and creates a font asset from it.
149    /// If a glyph is not found, StereoKit will look in the next font file in the list. If None has been found you can
150    /// call unwrap_or_default() to get the default font.
151    /// <https://stereokit.net/Pages/StereoKit/Font/FromFile.html>
152    /// * `files_utf8` - The paths to the font files.
153    ///
154    /// see also [`font_create_files`]
155    /// ### Examples
156    /// ```
157    /// # stereokit_rust::test_init_sk!(); // !!!! Get a proper way to initialize sk !!!!
158    /// use stereokit_rust::{maths:: Matrix, font::Font, system::Text, util::named_colors};
159    ///
160    /// let font_files = if cfg!(windows) {
161    ///     ["C:\\Windows\\Fonts\\Arial.ttf",
162    ///      "C:\\Windows\\Fonts\\Calibri.ttf"]
163    /// } else {
164    ///     ["/usr/share/fonts/truetype/freefont/FreeSans.ttf",
165    ///      "/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf"]
166    /// };
167    ///
168    /// let text_font = Font::from_files(&font_files).unwrap_or_default();
169    /// let text_style = Some(Text::make_style(&text_font, 0.025, named_colors::GREEN));
170    ///
171    /// test_steps!( // !!!! Get a proper main loop !!!!
172    ///     Text::add_at(token, "My Green Text", Matrix::IDENTITY, text_style,
173    ///                  None, None, None, None, None, None);
174    ///     assert_ne!(text_font.get_id(), "default/font");
175    ///     assert!   (text_font.get_id().starts_with("sk/font/"));
176    /// );
177    /// ```
178    pub fn from_files<P: AsRef<Path>>(files_utf8: &[P]) -> Result<Font, StereoKitError> {
179        let mut c_files = Vec::new();
180        for path in files_utf8 {
181            let path = path.as_ref();
182            let c_str = CString::new(path.to_str().ok_or(StereoKitError::FontFiles(
183                path.to_str().unwrap().to_string(),
184                "CString conversion".to_string(),
185            ))?)?;
186            c_files.push(c_str);
187        }
188        let mut c_files_ptr = Vec::new();
189        for str in c_files.iter() {
190            c_files_ptr.push(str.as_ptr());
191        }
192        let in_arr_files_cstr = c_files_ptr.as_mut_slice().as_mut_ptr();
193
194        Ok(Font(NonNull::new(unsafe { font_create_files(in_arr_files_cstr, files_utf8.len() as i32) }).ok_or(
195            StereoKitError::FontFiles("many files".to_owned(), "font_create_files failed".to_string()),
196        )?))
197    }
198
199    /// Doesn't work on Linux
200    /// Loads font from a specified list of font family names.
201    /// Returns a font from the given font family names, Most of the OS provide fallback fonts, hence there will always
202    /// be a set of fonts.
203    /// <https://stereokit.net/Pages/StereoKit/Font/FromFamily.html>
204    /// * `font_family` - List of font family names separated by comma(,) similar to a list of names css allows.
205    ///
206    /// see also [`font_create_family`]
207    /// ### Examples
208    /// ```
209    /// # stereokit_rust::test_init_sk!(); // !!!! Get a proper way to initialize sk !!!!
210    /// use stereokit_rust::{maths:: Matrix, font::Font, system::Text, util::named_colors};
211    ///
212    /// let font_family = if cfg!(windows) {
213    ///     "Arial, Helvetica, Verdana, Geneva, Tahoma, sans-serif;"
214    /// } else {
215    ///     // TODO: Doesn't work on Linux
216    ///     return;
217    ///     "FreeSans, Liberation Sans, Nimbus Sans L, DejaVu Sans, Bitstream Vera Sans, sans-serif;"
218    /// };
219    ///
220    /// let text_font = Font::from_family(&font_family).unwrap_or_default();
221    /// let text_style = Some(Text::make_style(&text_font, 0.025, named_colors::GREEN));
222    ///
223    /// test_steps!( // !!!! Get a proper main loop !!!!
224    ///     Text::add_at(token, "My Green Text", Matrix::IDENTITY, text_style,
225    ///                  None, None, None, None, None, None);
226    ///     assert_ne!(text_font.get_id(), "default/font");
227    ///     assert!   (text_font.get_id().starts_with("sk/font/"));
228    /// );
229    /// ```
230    pub fn from_family(font_family: impl AsRef<str>) -> Result<Font, StereoKitError> {
231        let c_str = CString::new(font_family.as_ref()).map_err(|_| {
232            StereoKitError::FontFamily(font_family.as_ref().into(), "CString conversion error".to_string())
233        })?;
234        Ok(Font(NonNull::new(unsafe { font_create_family(c_str.as_ptr()) }).ok_or(
235            StereoKitError::FontFamily(font_family.as_ref().into(), "font_create_family failed".to_string()),
236        )?))
237    }
238
239    /// Searches the asset list for a font with the given Id.
240    /// <https://stereokit.net/Pages/StereoKit/Font/Find.html>
241    /// * `id` - The Id of the font to find.
242    ///
243    /// see also [`font_find`]
244    /// ### Examples
245    /// ```
246    /// # stereokit_rust::test_init_sk!(); // !!!! Get a proper way to initialize sk !!!!
247    /// use stereokit_rust::{maths:: Matrix, font::Font, system::Text, util::named_colors};
248    ///
249    /// let font_files = if cfg!(windows) {
250    ///     ["C:\\Windows\\Fonts\\Arial.ttf",
251    ///      "C:\\Windows\\Fonts\\Calibri.ttf"]
252    /// } else {
253    ///     ["/usr/share/fonts/truetype/freefont/FreeSans.ttf",
254    ///      "/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf"]
255    /// };
256    ///
257    /// let text_font = Font::from_files(&font_files).unwrap_or_default();
258    /// assert_ne!(text_font.get_id(), "default/font");
259    ///
260    /// let id = text_font.get_id();
261    /// let same_font = Font::find(id).unwrap_or_default();
262    ///
263    /// assert_eq!(text_font.get_id(), same_font.get_id())
264    /// ```
265    pub fn find<S: AsRef<str>>(id: S) -> Result<Font, StereoKitError> {
266        let c_str = CString::new(id.as_ref())
267            .map_err(|_| StereoKitError::FontFind(id.as_ref().into(), "CString conversion error".to_string()))?;
268        Ok(Font(
269            NonNull::new(unsafe { font_find(c_str.as_ptr()) })
270                .ok_or(StereoKitError::FontFind(id.as_ref().into(), "font_find failed".to_string()))?,
271        ))
272    }
273
274    /// Creates a clone of the same reference. Basically, the new variable is the same asset. This is what you get by
275    /// calling find() method.
276    /// <https://stereokit.net/Pages/StereoKit/Font/Find.html>
277    ///
278    /// see also [`font_find`]
279    /// ### Examples
280    /// ```
281    /// # stereokit_rust::test_init_sk!(); // !!!! Get a proper way to initialize sk !!!!
282    /// use stereokit_rust::{maths:: Matrix, font::Font, system::Text, util::named_colors};
283    ///
284    /// let font_files = if cfg!(windows) {
285    ///     ["C:\\Windows\\Fonts\\Arial.ttf",
286    ///      "C:\\Windows\\Fonts\\Calibri.ttf"]
287    /// } else {
288    ///     ["/usr/share/fonts/truetype/freefont/FreeSans.ttf",
289    ///      "/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf"]
290    /// };
291    ///
292    /// let text_font = Font::from_files(&font_files).unwrap_or_default();
293    /// assert_ne!(text_font.get_id(), "default/font");
294    ///
295    /// let same_font = text_font.clone_ref();
296    ///
297    /// assert_eq!(text_font.get_id(), same_font.get_id())
298    /// ```
299    pub fn clone_ref(&self) -> Font {
300        Font(NonNull::new(unsafe { font_find(font_get_id(self.0.as_ptr())) }).expect("<asset>::clone_ref failed!"))
301    }
302
303    /// Gets or sets the unique identifier of this asset resource! This can be helpful for debugging,
304    /// managing your assets, or finding them later on!
305    /// <https://stereokit.net/Pages/StereoKit/Font/Id.html>
306    /// * `id` - Must be a unique identifier of this asset resource!
307    ///
308    /// see also [`font_set_id`]
309    /// ### Examples
310    /// ```
311    /// # stereokit_rust::test_init_sk!(); // !!!! Get a proper way to initialize sk !!!!
312    /// use stereokit_rust::{maths:: Matrix, font::Font, system::Text, util::named_colors};
313    ///
314    /// let font_files = if cfg!(windows) {
315    ///     ["C:\\Windows\\Fonts\\Arial.ttf",
316    ///      "C:\\Windows\\Fonts\\Calibri.ttf"]
317    /// } else {
318    ///     ["/usr/share/fonts/truetype/freefont/FreeSans.ttf",
319    ///      "/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf"]
320    /// };
321    ///
322    /// let mut text_font = Font::from_files(&font_files).unwrap_or_default();
323    /// assert_ne!(text_font.get_id(), "default/font");
324    /// text_font.id("my_font");
325    ///
326    /// let same_font = Font::find("my_font").unwrap_or_default();
327    ///
328    /// assert_eq!(text_font.get_id(), same_font.get_id())
329    /// ```
330    pub fn id<S: AsRef<str>>(&mut self, id: S) -> &mut Self {
331        let c_str = CString::new(id.as_ref()).unwrap();
332        unsafe { font_set_id(self.0.as_ptr(), c_str.as_ptr()) };
333        self
334    }
335
336    /// The id of this font
337    /// <https://stereokit.net/Pages/StereoKit/Font/Id.html>
338    ///
339    /// see also [`font_get_id`]
340    /// see example [`Font::id`]
341    pub fn get_id(&self) -> &str {
342        unsafe { CStr::from_ptr(font_get_id(self.0.as_ptr())) }.to_str().unwrap()
343    }
344}