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}