yakui_widgets/
text_renderer.rs1use 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#[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 texture_size: UVec2::ONE,
107 };
108
109 Self {
110 glyph_cache: Rc::new(RefCell::new(glyph_cache)),
111 }
112 }
113}