yakui_widgets/
text_renderer.rs

1use std::cell::RefCell;
2use std::collections::HashMap;
3use std::rc::Rc;
4
5use fontdue::layout::GlyphRasterConfig;
6use fontdue::Font;
7use yakui_core::geometry::{URect, UVec2};
8use yakui_core::paint::{PaintDom, Texture, TextureFormat};
9use yakui_core::ManagedTextureId;
10
11#[cfg(not(target_arch = "wasm32"))]
12const TEXTURE_SIZE: u32 = 4096;
13
14// When targeting the web, limit the texture atlas size to 2048x2048 to fit into
15// WebGL 2's limitations. In the future, we should introduce a way to query for
16// these limits.
17#[cfg(target_arch = "wasm32")]
18const TEXTURE_SIZE: u32 = 2048;
19
20#[derive(Debug, Clone)]
21pub struct TextGlobalState {
22    pub glyph_cache: Rc<RefCell<GlyphCache>>,
23}
24
25#[derive(Debug)]
26pub struct GlyphCache {
27    pub texture: Option<ManagedTextureId>,
28    pub texture_size: UVec2,
29    glyphs: HashMap<GlyphRasterConfig, URect>,
30    next_pos: UVec2,
31    max_height: u32,
32}
33
34impl GlyphCache {
35    pub fn ensure_texture(&mut self, paint: &mut PaintDom) {
36        if self.texture.is_none() {
37            let texture = paint.add_texture(Texture::new(
38                TextureFormat::R8,
39                UVec2::new(TEXTURE_SIZE, TEXTURE_SIZE),
40                vec![0; (TEXTURE_SIZE * TEXTURE_SIZE) as usize],
41            ));
42
43            self.texture = Some(texture);
44            self.texture_size = UVec2::new(TEXTURE_SIZE, TEXTURE_SIZE);
45        }
46    }
47
48    pub fn get_or_insert(
49        &mut self,
50        paint: &mut PaintDom,
51        font: &Font,
52        key: GlyphRasterConfig,
53    ) -> URect {
54        *self.glyphs.entry(key).or_insert_with(|| {
55            paint.mark_texture_modified(self.texture.unwrap());
56            let texture = paint.texture_mut(self.texture.unwrap()).unwrap();
57
58            let (metrics, bitmap) = font.rasterize_indexed(key.glyph_index, key.px);
59            let glyph_size = UVec2::new(metrics.width as u32, metrics.height as u32);
60
61            let glyph_max = self.next_pos + glyph_size;
62            let pos = if glyph_max.x < self.texture_size.x {
63                self.next_pos
64            } else {
65                UVec2::new(0, self.max_height)
66            };
67
68            self.max_height = self.max_height.max(pos.y + glyph_size.y + 1);
69            self.next_pos = pos + UVec2::new(glyph_size.x + 1, 0);
70
71            let size = texture.size();
72            blit(pos, glyph_size, &bitmap, size, texture.data_mut());
73
74            URect::from_pos_size(pos, glyph_size)
75        })
76    }
77}
78
79fn blit(pos: UVec2, src_size: UVec2, src: &[u8], dst_size: UVec2, dst: &mut [u8]) {
80    debug_assert!(dst_size.x >= src_size.x);
81    debug_assert!(dst_size.y >= src_size.y);
82
83    for row in 0..src_size.y {
84        let y1 = row;
85        let s1 = y1 * src_size.x;
86        let e1 = s1 + src_size.x;
87
88        let y2 = row + pos.y;
89        let s2 = y2 * dst_size.x + pos.x;
90        let e2 = s2 + src_size.x;
91
92        dst[s2 as usize..e2 as usize].copy_from_slice(&src[s1 as usize..e1 as usize])
93    }
94}
95
96impl TextGlobalState {
97    pub fn new() -> Self {
98        let glyph_cache = GlyphCache {
99            texture: None,
100            glyphs: HashMap::new(),
101            next_pos: UVec2::ONE,
102            max_height: 0,
103
104            // Not initializing to zero to avoid divide by zero issues if we do
105            // intialize the texture incorrectly.
106            texture_size: UVec2::ONE,
107        };
108
109        Self {
110            glyph_cache: Rc::new(RefCell::new(glyph_cache)),
111        }
112    }
113}