leafrender/render/
font.rs1use rusttype::Font;
3use rusttype::FontCollection;
4use rusttype::GlyphId;
5use rusttype::Point;
6use rusttype::Scale;
7
8use crate::pos::Position;
9
10use crate::render::Color;
11use crate::render::Dimensions;
12use crate::render::Drawer;
13use crate::render::Texture;
14
15use std::collections::BTreeMap;
16
17#[derive(Clone, Hash, Ord, PartialOrd, Eq, PartialEq)]
18struct CachedGlyph {
19 id: GlyphId,
20 color: Color,
21 size: i32,
22}
23
24pub struct FontCache<'a, T: Sized> {
25 font: Font<'a>,
26 cache: BTreeMap<CachedGlyph, T>,
27}
28
29impl<'a, T: Dimensions> FontCache<'a, T> {
30 pub fn get_width(&self, text: &str, size: i32) -> i32 {
32 let layout = self
33 .font
34 .layout(text, Scale::uniform(size as f32), Point { x: 0.0, y: 0.0 });
35
36 let mut width = 0;
37 for char in layout {
38 let bb = char.pixel_bounding_box();
39 if let Some(bb) = bb {
40 let pos = char.position().x as i32 + bb.width();
41 if pos > width {
42 width = pos;
43 }
44 }
45 }
46
47 width
48 }
49
50 pub fn draw(
52 &mut self,
53 text: &str,
54 color: &Color,
55 size: i32,
56 pos: &Position,
57 draw: &mut Drawer<NativeTexture = T>,
58 ) {
59 let layout = self.font.layout(
60 text,
61 Scale::uniform(size as f32),
62 Point {
63 x: pos.x as f32,
64 y: pos.y as f32,
65 },
66 );
67
68 for glyph in layout {
69 let bounding_box_opt = glyph.pixel_bounding_box();
71
72 if bounding_box_opt.is_none() {
73 continue;
74 }
75
76 let bounding_box = bounding_box_opt.unwrap();
77
78 let id = CachedGlyph {
80 id: glyph.id(),
81 color: color.clone(),
82 size,
83 };
84
85 if !self.cache.contains_key(&id) {
86 let mut tex = Texture::new(
87 bounding_box.width() as usize,
88 bounding_box.height() as usize,
89 );
90
91 {
92 let render_pos = |x: u32, y: u32, factor: f32| {
93 tex.draw_pixel(
94 &color.alpha((factor * 255.0) as u8),
95 x as usize,
96 y as usize,
97 );
98 };
99
100 glyph.draw(render_pos);
101 }
102
103 let opengl_tex = draw.convert_native_texture(tex);
104
105 self.cache.insert(id.clone(), opengl_tex);
106 }
107
108 let tex = &self.cache[&id];
110
111 draw.draw_texture(
113 tex,
114 &Position::new(bounding_box.min.x as i32, bounding_box.min.y as i32),
115 );
116 }
117 }
118
119 pub fn from_bytes(data: &'a [u8]) -> Result<Self, String> {
121 let collection =
122 FontCollection::from_bytes(data).map_err(|x| format!("Failed to read font: {}", x))?;
123
124 let font = collection
126 .into_font()
127 .map_err(|x| format!("Failed to read font: {}", x))?;
128
129 Ok(FontCache {
130 font,
131 cache: BTreeMap::new(),
132 })
133 }
134}