macroquad_text/
lib.rs

1//! A lot of stuff was based on macroquads font system,
2//! just removed dpi_scaling and font_scaling and added
3//! fallback font support
4//!
5//! **Example**
6//!
7//! From [examples/render_text.rs](https://github.com/Ricky12Awesome/macroquad-text/blob/main/examples/render_text.rs)
8//!
9//! ```rs
10//! // Include Fonts
11//! const NOTO_SANS: &[u8] = include_bytes!("../assets/fonts/NotoSans-Regular.ttf");
12//! const NOTO_SANS_JP: &[u8] = include_bytes!("../assets/fonts/NotoSansJP-Regular.otf");
13//!
14//! // Window Config for macroquad
15//! fn window_conf() -> Conf { ... }
16//!
17//! #[macroquad::main(window_conf)]
18//! async fn main() {
19//!   // Start by creating a fonts instance to handle all your fonts
20//!   let mut fonts = Fonts::default();
21//!
22//!   // Load fonts, the order you load fonts is the order it uses for lookups
23//!   fonts.load_font_from_bytes("Noto Sans", NOTO_SANS).unwrap();
24//!   fonts.load_font_from_bytes("Noto Sans JP", NOTO_SANS_JP).unwrap();
25//!
26//!   loop {
27//!     // Draw text
28//!     fonts.draw_text("Nice", 20.0, 0.0, 69, Color::from([1.0; 4]));
29//!     fonts.draw_text("良い", 20.0, 89.0, 69, Color::from([1.0; 4]));
30//!     fonts.draw_text("Nice 良い", 20.0, 178.0, 69, Color::from([1.0; 4]));
31//!
32//!     next_frame().await;
33//!   }
34//! }
35//! ```
36//!
37//! ![img.png](https://raw.githubusercontent.com/Ricky12Awesome/macroquad-text/main/examples/render_text_window.png)
38//!
39
40#![deny(unsafe_code)]
41
42use std::{cell::RefCell, collections::HashMap, ops::Deref, path::Path};
43
44use fontdue::{FontResult, FontSettings};
45use macroquad::prelude::{
46  draw_texture_ex, vec2, Color, DrawTextureParams, FilterMode, Image, TextDimensions,
47};
48
49use crate::{
50  atlas::Atlas,
51  misc::{read_file, IoError, IoErrorKind, IoResult},
52};
53
54pub(crate) mod atlas;
55pub(crate) mod misc;
56
57pub type ScalingMode = FilterMode;
58pub type FontdueFont = fontdue::Font;
59
60/// Where to draw from on the screen
61///
62/// **Default** [DrawFrom::TopLeft]
63#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
64pub enum DrawFrom {
65  /// Starts drawing from the bottom left corner
66  BottomLeft,
67  /// Starts drawing from the top left corner
68  ///
69  /// this is the default
70  TopLeft,
71}
72
73impl Default for DrawFrom {
74  fn default() -> Self {
75    Self::TopLeft
76  }
77}
78
79#[derive(Default, Debug, Copy, Clone, PartialEq, PartialOrd)]
80pub(crate) struct CharacterInfo {
81  pub id: u64,
82  pub offset_x: f32,
83  pub offset_y: f32,
84  pub advance: f32,
85}
86
87/// Text parameters for [Fonts::draw_text_ex]
88#[derive(Debug, Copy, Clone, PartialEq)]
89pub struct TextParams<'a> {
90  /// Text to draw to the screen
91  pub text: &'a str,
92  /// x-coordinate of the text
93  pub x: f32,
94  /// y-coordinate of the text
95  pub y: f32,
96  /// The size of the text in pixels
97  pub size: u16,
98  /// The color of the text
99  pub color: Color,
100  /// Where to draw from
101  pub draw: DrawFrom,
102}
103
104impl<'a> Default for TextParams<'a> {
105  fn default() -> Self {
106    Self {
107      text: "",
108      x: 0.0,
109      y: 0.0,
110      size: 22,
111      color: Color::from_rgba(255, 255, 255, 255),
112      draw: DrawFrom::TopLeft,
113    }
114  }
115}
116
117/// Stores font data, also stores caches for much faster rendering times
118#[derive(Debug)]
119pub struct Font<'a> {
120  pub name: &'a str,
121  font: FontdueFont,
122  atlas: RefCell<Atlas>,
123  chars: RefCell<HashMap<(char, u16), CharacterInfo>>,
124}
125
126impl<'a> Deref for Font<'a> {
127  type Target = FontdueFont;
128
129  fn deref(&self) -> &Self::Target {
130    &self.font
131  }
132}
133
134impl<'a> Font<'a> {
135  /// Creates a new font with a given name, [fontdue::Font], and [ScalingMode]
136  fn new(name: &'a str, font: FontdueFont, mode: ScalingMode) -> Self {
137    Self {
138      name,
139      font,
140      atlas: RefCell::new(Atlas::new(mode)),
141      chars: RefCell::default(),
142    }
143  }
144
145  /// Checks if this font contains a given character
146  pub fn contains(&self, c: char) -> bool {
147    self.lookup_glyph_index(c) != 0
148  }
149
150  fn _cache_glyph(&self, c: char, size: u16) -> CharacterInfo {
151    let (matrix, bitmap) = self.rasterize(c, size as f32);
152    let (width, height) = (matrix.width as u16, matrix.height as u16);
153
154    let id = self.atlas.borrow_mut().new_unique_id();
155    let bytes = bitmap
156      .iter()
157      .flat_map(|coverage| vec![255, 255, 255, *coverage])
158      .collect::<Vec<_>>();
159
160    self.atlas.borrow_mut().cache_sprite(
161      id,
162      Image {
163        width,
164        height,
165        bytes,
166      },
167    );
168
169    CharacterInfo {
170      id,
171      offset_x: matrix.xmin as f32,
172      offset_y: matrix.ymin as f32,
173      advance: matrix.advance_width,
174    }
175  }
176
177  /// Caches a glyph for a given character with a given font size
178  ///
179  /// You don't really need to call this function since caching happens automatically
180  pub fn cache_glyph(&self, c: char, size: u16) {
181    if !self.chars.borrow().contains_key(&(c, size)) {
182      let info = self._cache_glyph(c, size);
183
184      self.chars.borrow_mut().insert((c, size), info);
185    }
186  }
187
188  /// Recaches all cached glyphs, this is expensive to call
189  ///
190  /// normally you wouldn't need to call this
191  pub fn recache_glyphs(&self) {
192    for ((c, size), info) in self.chars.borrow_mut().iter_mut() {
193      *info = self._cache_glyph(*c, *size);
194    }
195  }
196}
197
198#[derive(Debug)]
199pub struct Fonts<'a> {
200  fonts: Vec<Font<'a>>,
201  index_by_name: HashMap<&'a str, usize>,
202  default_sm: ScalingMode,
203}
204
205impl<'a> Default for Fonts<'a> {
206  /// Creates a new [Fonts] instance to handle all your font
207  ///
208  /// Same as calling [Fonts::new(ScalingMode::Linear)]
209  fn default() -> Self {
210    Self::new(ScalingMode::Linear)
211  }
212}
213
214impl<'a> Fonts<'a> {
215  /// Creates a new [Fonts] instance to handle all your fonts with a given [ScalingMode]
216  ///
217  /// You can also call [Fonts::default] which defaults to [ScalingMode::Linear]
218  ///
219  /// **Examples**
220  ///
221  /// With nearest mode
222  /// ```rs
223  /// let mut fonts = Fonts::new(ScalingMode::Nearest);
224  /// ```
225  /// With linear mode
226  /// ```rs
227  /// let mut fonts = Fonts::new(ScalingMode::Linear);
228  /// ```
229  pub fn new(default_sm: ScalingMode) -> Self {
230    Self {
231      fonts: Vec::default(),
232      index_by_name: HashMap::default(),
233      default_sm,
234    }
235  }
236
237  /// Returns an immutable reference to the
238  /// list of fonts that are currently loaded
239  pub fn fonts(&self) -> &Vec<Font> {
240    &self.fonts
241  }
242
243  /// Caches a glyph for a given character with a given font size
244  ///
245  /// You don't really need to call this function since caching happens automatically
246  pub fn cache_glyph(&self, c: char, size: u16) {
247    for font in self.fonts.iter() {
248      font.cache_glyph(c, size);
249    }
250  }
251
252  /// Loads font from bytes with a given name and scale
253  ///
254  ///
255  /// What Scale does
256  /// ---------------
257  /// (copied from [FontSettings::scale](FontSettings))
258  ///
259  /// The scale in px the font geometry is optimized for. Fonts rendered at
260  /// the scale defined here will be the most optimal in terms of looks and performance. Glyphs
261  /// rendered smaller than this scale will look the same but perform slightly worse, while
262  /// glyphs rendered larger than this will looks worse but perform slightly better. The units of
263  /// the scale are pixels per Em unit.
264  pub fn load_font_from_bytes_with_scale(
265    &mut self,
266    name: &'a str,
267    bytes: &[u8],
268    scale: f32,
269  ) -> FontResult<()> {
270    let settings = FontSettings {
271      collection_index: 0,
272      scale,
273    };
274    let font = FontdueFont::from_bytes(bytes, settings)?;
275
276    self.index_by_name.insert(name, self.fonts.len());
277    self.fonts.push(Font::new(name, font, self.default_sm));
278
279    Ok(())
280  }
281
282  /// Loads font from bytes with a given name and a default scale of 100.0
283  ///
284  /// **See** [Self::load_font_from_bytes_with_scale]
285  pub fn load_font_from_bytes(&mut self, name: &'a str, bytes: &[u8]) -> FontResult<()> {
286    self.load_font_from_bytes_with_scale(name, bytes, 100.0)
287  }
288
289  /// Loads font from a file with a given name and path and a default scale of 100.0
290  ///
291  /// **See** [Self::load_font_from_bytes_with_scale]
292  pub fn load_font_from_file(&mut self, name: &'a str, path: impl AsRef<Path>) -> IoResult<()> {
293    self.load_font_from_file_with_scale(name, path, 100.0)
294  }
295
296  /// Loads font from a file with a given name, path and scale
297  ///
298  /// **See** [Self::load_font_from_bytes_with_scale]
299  pub fn load_font_from_file_with_scale(
300    &mut self,
301    name: &'a str,
302    path: impl AsRef<Path>,
303    scale: f32,
304  ) -> IoResult<()> {
305    let bytes = read_file(path)?;
306
307    self
308      .load_font_from_bytes_with_scale(name, &bytes, scale)
309      .map_err(|err| IoError::new(IoErrorKind::InvalidData, err))
310  }
311
312  /// Unloads a currently loaded font by its index
313  ///
314  /// This will also re-index all the currently loaded fonts
315  pub fn unload_font_by_index(&mut self, index: usize) {
316    if self.fonts.len() <= index {
317      return;
318    }
319
320    self.fonts.remove(index);
321    self.index_by_name.clear();
322
323    for (index, font) in self.fonts.iter().enumerate() {
324      self.index_by_name.insert(font.name, index);
325    }
326  }
327
328  /// Unloads a currently loaded font by it name
329  ///
330  /// This will also re-index all the currently loaded fonts
331  pub fn unload_font_by_name(&mut self, name: &str) {
332    self.unload_font_by_index(self.get_index_by_name(name).unwrap_or(self.fonts.len()));
333  }
334
335  /// Gets a currently loaded font by its index
336  pub fn get_font_by_index(&self, index: usize) -> Option<&Font> {
337    self.fonts.get(index)
338  }
339
340  /// Gets the first currently loaded font if it contains this character
341  pub fn get_index_by_char(&self, c: char) -> Option<usize> {
342    self.fonts.iter().position(|it| it.contains(c))
343  }
344
345  /// Gets a currently loaded font index by its name
346  pub fn get_index_by_name(&self, name: &str) -> Option<usize> {
347    self.index_by_name.get(name).copied()
348  }
349
350  /// Gets a currently loaded font by its name
351  pub fn get_font_by_name(&self, name: &str) -> Option<&Font> {
352    self.get_font_by_index(self.get_index_by_name(name)?)
353  }
354
355  /// Gets the first currently loaded font if it contains this character
356  pub fn get_font_by_char(&self, c: char) -> Option<&Font> {
357    self.get_font_by_index(self.get_index_by_char(c)?)
358  }
359
360  /// Gets the first currently loaded font if it contains this character,
361  /// if no font that contains this character is found, it will return the first loaded font,
362  /// **if no fonts are loaded then it will panic**
363  pub fn get_font_by_char_or_panic(&self, c: char) -> &Font {
364    self
365      .get_font_by_char(c)
366      .or_else(|| self.fonts.first())
367      .expect("There is no font currently loaded")
368  }
369
370  /// Checks if any fonts supports this character
371  pub fn contains(&self, c: char) -> bool {
372    self.fonts.iter().any(|f| f.contains(c))
373  }
374
375  /// Measures text with a given font size
376  ///
377  /// **Example**
378  /// ```rs
379  /// let dimensions = fonts.measure_text("Some Text", 22);
380  ///
381  /// println!("width: {}, height: {}, offset_y: {}",
382  ///   dimensions.width,
383  ///   dimensions.height,
384  ///   dimensions.offset_y
385  /// )
386  /// ```
387  ///
388  /// **See** [TextDimensions]
389  pub fn measure_text(&self, text: &str, size: u16) -> TextDimensions {
390    let mut width = 0f32;
391    let mut min_y = f32::MAX;
392    let mut max_y = f32::MIN;
393
394    for c in text.chars() {
395      let font = self.get_font_by_char_or_panic(c);
396
397      font.cache_glyph(c, size);
398
399      let info = font.chars.borrow()[&(c, size)];
400      let glyph = font.atlas.borrow().get(info.id).unwrap().rect;
401
402      width += info.advance;
403
404      if min_y > info.offset_y {
405        min_y = info.offset_y;
406      }
407
408      if max_y < glyph.h + info.offset_y {
409        max_y = glyph.h + info.offset_y;
410      }
411    }
412
413    TextDimensions {
414      width,
415      height: max_y - min_y,
416      offset_y: max_y,
417    }
418  }
419
420  /// Draws text with a given font size, draws from TopLeft
421  ///
422  /// **Examples**
423  /// ```rs
424  /// fonts.draw_text("Some Text", 20.0, 20.0, 22, Color::from_rgba(255, 255, 255, 255));
425  /// ```
426  ///
427  /// **See** [Self::draw_text_ex]
428  pub fn draw_text(&self, text: &str, x: f32, y: f32, size: u16, color: Color) -> TextDimensions {
429    self.draw_text_ex(&TextParams {
430      text,
431      x,
432      y,
433      size,
434      color,
435      draw: Default::default(),
436    })
437  }
438
439  /// Draws text with given [TextParams]
440  ///
441  /// **Example**
442  /// ```rs
443  /// fonts.draw_text_ex(&TextParams {
444  ///   text: "Some Text",
445  ///   x: 20.0,
446  ///   y: 20.0,
447  ///   // Default Size
448  ///   size: 22,
449  ///   // Default Color
450  ///   color: Color::from_rgba(255, 255, 255, 255),
451  ///   // Default Draw method
452  ///   draw: DrawFrom::TopLeft
453  /// });
454  ///
455  /// // Does the same as above
456  /// fonts.draw_text_ex(&TextParams {
457  ///   text: "Some Text",
458  ///   x: 20.0,
459  ///   y: 20.0,
460  ///   ..Default::default()
461  /// });
462  /// ```
463  ///
464  /// **See** [Self::draw_text]
465  pub fn draw_text_ex(&self, params: &TextParams) -> TextDimensions {
466    let mut total_width = 0f32;
467
468    for c in params.text.chars() {
469      let font = self.get_font_by_char_or_panic(c);
470      font.cache_glyph(c, params.size);
471    }
472
473    for c in params.text.chars() {
474      let font = self.get_font_by_char_or_panic(c);
475      let mut atlas = font.atlas.borrow_mut();
476      let info = &font.chars.borrow()[&(c, params.size)];
477      let glyph = atlas.get(info.id).unwrap().rect;
478      let mut y = 0.0 - glyph.h - info.offset_y + params.y;
479
480      if let DrawFrom::TopLeft = params.draw {
481        y += params.size as f32;
482      }
483
484      draw_texture_ex(
485        atlas.texture(),
486        info.offset_x + total_width + params.x,
487        y,
488        params.color,
489        DrawTextureParams {
490          dest_size: Some(vec2(glyph.w, glyph.h)),
491          source: Some(glyph),
492          ..Default::default()
493        },
494      );
495
496      total_width += info.advance;
497    }
498
499    self.measure_text(params.text, params.size)
500  }
501}