1use std::collections::HashMap;
4
5use crate::{
6 color::Color,
7 get_context, get_quad_context,
8 math::{vec3, Rect},
9 texture::{Image, TextureHandle},
10 Error,
11};
12
13use crate::color::WHITE;
14use glam::vec2;
15
16use std::sync::{Arc, Mutex};
17pub(crate) mod atlas;
18
19use atlas::{Atlas, SpriteKey};
20
21#[derive(Debug, Clone)]
22pub(crate) struct CharacterInfo {
23 pub offset_x: i32,
24 pub offset_y: i32,
25 pub advance: f32,
26 pub sprite: SpriteKey,
27}
28
29#[derive(Clone)]
31pub struct Font {
32 font: Arc<fontdue::Font>,
33 atlas: Arc<Mutex<Atlas>>,
34 characters: Arc<Mutex<HashMap<(char, u16), CharacterInfo>>>,
35}
36
37#[derive(Debug, Default, Clone, Copy)]
39pub struct TextDimensions {
40 pub width: f32,
42 pub height: f32,
44 pub offset_y: f32,
48}
49
50#[allow(dead_code)]
51fn require_fn_to_be_send() {
52 fn require_send<T: Send>() {}
53 require_send::<Font>();
54}
55
56impl std::fmt::Debug for Font {
57 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58 f.debug_struct("Font")
59 .field("font", &"fontdue::Font")
60 .finish()
61 }
62}
63
64impl Font {
65 pub(crate) fn load_from_bytes(atlas: Arc<Mutex<Atlas>>, bytes: &[u8]) -> Result<Font, Error> {
66 Ok(Font {
67 font: Arc::new(fontdue::Font::from_bytes(
68 bytes,
69 fontdue::FontSettings::default(),
70 )?),
71 characters: Arc::new(Mutex::new(HashMap::new())),
72 atlas,
73 })
74 }
75
76 pub(crate) fn set_atlas(&mut self, atlas: Arc<Mutex<Atlas>>) {
77 self.atlas = atlas;
78 }
79
80 pub(crate) fn set_characters(
81 &mut self,
82 characters: Arc<Mutex<HashMap<(char, u16), CharacterInfo>>>,
83 ) {
84 self.characters = characters;
85 }
86
87 pub(crate) fn ascent(&self, font_size: f32) -> f32 {
88 self.font.horizontal_line_metrics(font_size).unwrap().ascent
89 }
90
91 pub(crate) fn descent(&self, font_size: f32) -> f32 {
92 self.font
93 .horizontal_line_metrics(font_size)
94 .unwrap()
95 .descent
96 }
97
98 pub(crate) fn cache_glyph(&self, character: char, size: u16) {
99 if self.contains(character, size) {
100 return;
101 }
102
103 let (metrics, bitmap) = self.font.rasterize(character, size as f32);
104
105 let (width, height) = (metrics.width as u16, metrics.height as u16);
106
107 let sprite = self.atlas.lock().unwrap().new_unique_id();
108 self.atlas.lock().unwrap().cache_sprite(
109 sprite,
110 Image {
111 bytes: bitmap
112 .iter()
113 .flat_map(|coverage| vec![255, 255, 255, *coverage])
114 .collect(),
115 width,
116 height,
117 },
118 );
119 let advance = metrics.advance_width;
120
121 let (offset_x, offset_y) = (metrics.xmin, metrics.ymin);
122
123 let character_info = CharacterInfo {
124 advance,
125 offset_x,
126 offset_y,
127 sprite,
128 };
129
130 self.characters
131 .lock()
132 .unwrap()
133 .insert((character, size), character_info);
134 }
135
136 pub(crate) fn get(&self, character: char, size: u16) -> Option<CharacterInfo> {
137 self.characters
138 .lock()
139 .unwrap()
140 .get(&(character, size))
141 .cloned()
142 }
143 pub(crate) fn contains(&self, character: char, size: u16) -> bool {
145 self.characters
146 .lock()
147 .unwrap()
148 .contains_key(&(character, size))
149 }
150
151 pub(crate) fn measure_text(
152 &self,
153 text: impl AsRef<str>,
154 font_size: u16,
155 font_scale_x: f32,
156 font_scale_y: f32,
157 mut glyph_callback: impl FnMut(f32),
158 ) -> TextDimensions {
159 let text = text.as_ref();
160
161 let dpi_scaling = miniquad::window::dpi_scale();
162 let font_size = (font_size as f32 * dpi_scaling).ceil() as u16;
163
164 let mut width = 0.0;
165 let mut min_y = f32::MAX;
166 let mut max_y = f32::MIN;
167
168 for character in text.chars() {
169 if !self.contains(character, font_size) {
170 self.cache_glyph(character, font_size);
171 }
172
173 let font_data = &self.characters.lock().unwrap()[&(character, font_size)];
174 let offset_y = font_data.offset_y as f32 * font_scale_y;
175
176 let atlas = self.atlas.lock().unwrap();
177 let glyph = atlas.get(font_data.sprite).unwrap().rect;
178 let advance = font_data.advance * font_scale_x;
179 glyph_callback(advance);
180 width += advance;
181 min_y = min_y.min(offset_y);
182 max_y = max_y.max(glyph.h * font_scale_y + offset_y);
183 }
184
185 TextDimensions {
186 width: width / dpi_scaling,
187 height: (max_y - min_y) / dpi_scaling,
188 offset_y: max_y / dpi_scaling,
189 }
190 }
191}
192
193impl Font {
194 pub fn ascii_character_list() -> Vec<char> {
196 (0..255).filter_map(::std::char::from_u32).collect()
197 }
198
199 pub fn latin_character_list() -> Vec<char> {
201 "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890!@#$%^&*(){}[].,:"
202 .chars()
203 .collect()
204 }
205
206 pub fn populate_font_cache(&self, characters: &[char], size: u16) {
207 for character in characters {
208 self.cache_glyph(*character, size);
209 }
210 }
211
212 pub fn set_filter(&mut self, filter_mode: miniquad::FilterMode) {
226 self.atlas.lock().unwrap().set_filter(filter_mode);
227 }
228
229 }
235
236impl Default for Font {
237 fn default() -> Self {
238 get_default_font()
239 }
240}
241
242#[derive(Debug, Clone)]
244pub struct TextParams<'a> {
245 pub font: Option<&'a Font>,
246 pub font_size: u16,
248 pub font_scale: f32,
251 pub font_scale_aspect: f32,
255 pub rotation: f32,
258 pub color: Color,
259}
260
261impl<'a> Default for TextParams<'a> {
262 fn default() -> TextParams<'a> {
263 TextParams {
264 font: None,
265 font_size: 20,
266 font_scale: 1.0,
267 font_scale_aspect: 1.0,
268 color: WHITE,
269 rotation: 0.0,
270 }
271 }
272}
273
274pub async fn load_ttf_font(path: &str) -> Result<Font, Error> {
276 let bytes = crate::file::load_file(path)
277 .await
278 .map_err(|_| Error::FontError("The Font file couldn't be loaded"))?;
279
280 load_ttf_font_from_bytes(&bytes[..])
281}
282
283pub fn load_ttf_font_from_bytes(bytes: &[u8]) -> Result<Font, Error> {
288 let atlas = Arc::new(Mutex::new(Atlas::new(
289 get_quad_context(),
290 miniquad::FilterMode::Linear,
291 )));
292
293 let mut font = Font::load_from_bytes(atlas.clone(), bytes)?;
294
295 font.populate_font_cache(&Font::ascii_character_list(), 15);
296
297 let ctx = get_context();
298
299 font.set_filter(ctx.default_filter_mode);
300
301 Ok(font)
302}
303
304pub fn draw_text(
307 text: impl AsRef<str>,
308 x: f32,
309 y: f32,
310 font_size: f32,
311 color: Color,
312) -> TextDimensions {
313 draw_text_ex(
314 text,
315 x,
316 y,
317 TextParams {
318 font_size: font_size as u16,
319 font_scale: 1.0,
320 color,
321 ..Default::default()
322 },
323 )
324}
325
326pub fn draw_text_ex(text: impl AsRef<str>, x: f32, y: f32, params: TextParams) -> TextDimensions {
329 let text = text.as_ref();
330
331 if text.is_empty() {
332 return TextDimensions::default();
333 }
334
335 let font = params
336 .font
337 .unwrap_or(&get_context().fonts_storage.default_font);
338
339 let dpi_scaling = miniquad::window::dpi_scale();
340
341 let rot = params.rotation;
342 let font_scale_x = params.font_scale * params.font_scale_aspect;
343 let font_scale_y = params.font_scale;
344 let font_size = (params.font_size as f32 * dpi_scaling).ceil() as u16;
345
346 let mut total_width = 0.0;
347 let mut max_offset_y = f32::MIN;
348 let mut min_offset_y = f32::MAX;
349
350 for character in text.chars() {
351 if !font.contains(character, font_size) {
352 font.cache_glyph(character, font_size);
353 }
354
355 let char_data = &font.characters.lock().unwrap()[&(character, font_size)];
356 let offset_x = char_data.offset_x as f32 * font_scale_x;
357 let offset_y = char_data.offset_y as f32 * font_scale_y;
358
359 let mut atlas = font.atlas.lock().unwrap();
360 let glyph = atlas.get(char_data.sprite).unwrap().rect;
361 let glyph_scaled_h = glyph.h * font_scale_y;
362
363 min_offset_y = min_offset_y.min(offset_y);
364 max_offset_y = max_offset_y.max(glyph_scaled_h + offset_y);
365
366 let rot_cos = rot.cos();
367 let rot_sin = rot.sin();
368 let dest_x = (offset_x + total_width) * rot_cos + (glyph_scaled_h + offset_y) * rot_sin;
369 let dest_y = (offset_x + total_width) * rot_sin + (-glyph_scaled_h - offset_y) * rot_cos;
370
371 let dest = Rect::new(
372 dest_x / dpi_scaling + x,
373 dest_y / dpi_scaling + y,
374 glyph.w / dpi_scaling * font_scale_x,
375 glyph.h / dpi_scaling * font_scale_y,
376 );
377
378 total_width += char_data.advance * font_scale_x;
379
380 crate::texture::draw_texture_ex(
381 &crate::texture::Texture2D {
382 texture: TextureHandle::Unmanaged(atlas.texture()),
383 },
384 dest.x,
385 dest.y,
386 params.color,
387 crate::texture::DrawTextureParams {
388 dest_size: Some(vec2(dest.w, dest.h)),
389 source: Some(glyph),
390 rotation: rot,
391 pivot: Some(vec2(dest.x, dest.y)),
392 ..Default::default()
393 },
394 );
395 }
396
397 TextDimensions {
398 width: total_width / dpi_scaling,
399 height: (max_offset_y - min_offset_y) / dpi_scaling,
400 offset_y: max_offset_y / dpi_scaling,
401 }
402}
403
404pub fn draw_multiline_text(
407 text: impl AsRef<str>,
408 x: f32,
409 y: f32,
410 font_size: f32,
411 line_distance_factor: Option<f32>,
412 color: Color,
413) -> TextDimensions {
414 draw_multiline_text_ex(
415 text,
416 x,
417 y,
418 line_distance_factor,
419 TextParams {
420 font_size: font_size as u16,
421 font_scale: 1.0,
422 color,
423 ..Default::default()
424 },
425 )
426}
427
428pub fn draw_multiline_text_ex(
431 text: impl AsRef<str>,
432 mut x: f32,
433 mut y: f32,
434 line_distance_factor: Option<f32>,
435 params: TextParams,
436) -> TextDimensions {
437 let line_distance = match line_distance_factor {
438 Some(distance) => distance,
439 None => {
440 let mut font_line_distance = 0.0;
441 let font = if let Some(font) = params.font {
442 font
443 } else {
444 &get_default_font()
445 };
446 if let Some(metrics) = font.font.horizontal_line_metrics(1.0) {
447 font_line_distance = metrics.new_line_size;
448 }
449
450 font_line_distance
451 }
452 };
453
454 let mut dimensions = TextDimensions::default();
455 let y_step = line_distance * params.font_size as f32 * params.font_scale;
456
457 for line in text.as_ref().lines() {
458 let line_dimensions = draw_text_ex(line, x, y, params.clone());
459 x -= (line_distance * params.font_size as f32 * params.font_scale) * params.rotation.sin();
460 y += (line_distance * params.font_size as f32 * params.font_scale) * params.rotation.cos();
461
462 dimensions.width = f32::max(dimensions.width, line_dimensions.width);
463 dimensions.height += y_step;
464
465 if dimensions.offset_y == 0.0 {
466 dimensions.offset_y = line_dimensions.offset_y;
467 }
468 }
469
470 dimensions
471}
472
473pub fn get_text_center(
475 text: impl AsRef<str>,
476 font: Option<&Font>,
477 font_size: u16,
478 font_scale: f32,
479 rotation: f32,
480) -> crate::Vec2 {
481 let measure = measure_text(text, font, font_size, font_scale);
482
483 let x_center = measure.width / 2.0 * rotation.cos() + measure.height / 2.0 * rotation.sin();
484 let y_center = measure.width / 2.0 * rotation.sin() - measure.height / 2.0 * rotation.cos();
485
486 crate::Vec2::new(x_center, y_center)
487}
488
489pub fn measure_text(
490 text: impl AsRef<str>,
491 font: Option<&Font>,
492 font_size: u16,
493 font_scale: f32,
494) -> TextDimensions {
495 let font = font.unwrap_or_else(|| &get_context().fonts_storage.default_font);
496
497 font.measure_text(text, font_size, font_scale, font_scale, |_| {})
498}
499
500pub fn measure_multiline_text(
501 text: &str,
502 font: Option<&Font>,
503 font_size: u16,
504 font_scale: f32,
505 line_distance_factor: Option<f32>,
506) -> TextDimensions {
507 let font = font.unwrap_or_else(|| &get_context().fonts_storage.default_font);
508 let line_distance = match line_distance_factor {
509 Some(distance) => distance,
510 None => match font.font.horizontal_line_metrics(1.0) {
511 Some(metrics) => metrics.new_line_size,
512 None => 1.0,
513 },
514 };
515
516 let mut dimensions = TextDimensions::default();
517 let y_step = line_distance * font_size as f32 * font_scale;
518
519 for line in text.lines() {
520 let line_dimensions = font.measure_text(line, font_size, font_scale, font_scale, |_| {});
521
522 dimensions.width = f32::max(dimensions.width, line_dimensions.width);
523 dimensions.height += y_step;
524 if dimensions.offset_y == 0.0 {
525 dimensions.offset_y = line_dimensions.offset_y;
526 }
527 }
528
529 dimensions
530}
531
532pub fn wrap_text(
534 text: &str,
535 font: Option<&Font>,
536 font_size: u16,
537 font_scale: f32,
538 maximum_line_length: f32,
539) -> String {
540 let font = font.unwrap_or_else(|| &get_context().fonts_storage.default_font);
541
542 let mut new_text =
544 String::with_capacity(text.len() + text.chars().filter(|c| c.is_whitespace()).count());
545
546 let mut current_word_start = 0usize;
547 let mut current_word_end = 0usize;
548 let mut characters = text.char_indices();
549 let mut total_width = 0.0;
550 let mut word_width = 0.0;
551
552 font.measure_text(text, font_size, font_scale, font_scale, |mut width| {
553 let (idx, c) = characters.next().unwrap();
555 let mut keep_char = true;
556
557 if c.is_whitespace() {
558 new_text.push_str(&text[current_word_start..idx + c.len_utf8()]);
559 current_word_start = idx + c.len_utf8();
560 word_width = 0.0;
561 keep_char = false;
562
563 if total_width + width > maximum_line_length {
565 width = 0.0;
566 }
567 }
568
569 if word_width + width > maximum_line_length {
571 new_text.push_str(&text[current_word_start..current_word_end]);
572 new_text.push('\n');
573 current_word_start = current_word_end;
574 total_width = 0.0;
575 word_width = 0.0;
576 }
577
578 current_word_end = idx + c.len_utf8();
579 if keep_char {
580 word_width += width;
581 }
582
583 if c == '\n' {
584 total_width = 0.0;
585 word_width = 0.0;
586 return;
587 }
588
589 total_width += width;
590
591 if total_width > maximum_line_length {
592 new_text.push('\n');
593 total_width = word_width;
594 }
595 });
596
597 new_text.push_str(&text[current_word_start..current_word_end]);
598
599 new_text
600}
601
602pub(crate) struct FontsStorage {
603 default_font: Font,
604}
605
606impl FontsStorage {
607 pub(crate) fn new(ctx: &mut dyn miniquad::RenderingBackend) -> FontsStorage {
608 let atlas = Arc::new(Mutex::new(Atlas::new(ctx, miniquad::FilterMode::Linear)));
609
610 let default_font = Font::load_from_bytes(atlas, include_bytes!("ProggyClean.ttf")).unwrap();
611 FontsStorage { default_font }
612 }
613}
614
615pub fn get_default_font() -> Font {
617 let context = get_context();
618 context.fonts_storage.default_font.clone()
619}
620
621pub fn set_default_font(font: Font) {
623 let context = get_context();
624 context.fonts_storage.default_font = font;
625}
626
627pub fn camera_font_scale(world_font_size: f32) -> (u16, f32, f32) {
631 let context = get_context();
632 let (scr_w, scr_h) = miniquad::window::screen_size();
633 let cam_space = context
634 .projection_matrix()
635 .inverse()
636 .transform_vector3(vec3(2., 2., 0.));
637 let (cam_w, cam_h) = (cam_space.x.abs(), cam_space.y.abs());
638
639 let screen_font_size = world_font_size * scr_h / cam_h;
640
641 let font_size = screen_font_size as u16;
642
643 (font_size, cam_h / scr_h, scr_h / scr_w * cam_w / cam_h)
644}