beamterm_renderer/gl/
atlas.rs1use std::{borrow::Cow, collections::HashMap};
2
3use beamterm_data::{FontAtlasData, FontStyle, Glyph};
4use compact_str::{CompactString, ToCompactString};
5use web_sys::console;
6
7use crate::{error::Error, gl::GL};
8
9#[derive(Debug)]
22pub struct FontAtlas {
23 texture: crate::gl::texture::Texture,
25 glyph_coords: HashMap<CompactString, u16>,
27 symbol_lookup: HashMap<u16, CompactString>,
29 cell_size: (i32, i32),
31 num_slices: u32,
33 underline: beamterm_data::LineDecoration,
35 strikethrough: beamterm_data::LineDecoration,
37}
38
39impl FontAtlas {
40 pub fn load_default(gl: &web_sys::WebGl2RenderingContext) -> Result<Self, Error> {
42 let config = FontAtlasData::default();
43 Self::load(gl, config)
44 }
45
46 pub fn load(
48 gl: &web_sys::WebGl2RenderingContext,
49 config: FontAtlasData,
50 ) -> Result<Self, Error> {
51 let texture = crate::gl::texture::Texture::from_font_atlas_data(gl, GL::RGBA, &config)?;
52 let num_slices = config.texture_dimensions.2;
53
54 let texture_layers = config.glyphs.iter().map(|g| g.id as i32).max().unwrap_or(0) + 1;
55 console::log_1(
56 &format!("Creating atlas grid with {}/{texture_layers} layers", config.glyphs.len())
57 .into(),
58 );
59
60 let (cell_width, cell_height) = config.cell_size;
61 let mut layers = HashMap::new();
62 let mut symbol_lookup = HashMap::new();
63
64 config.glyphs.iter()
67 .filter(|g| g.style == FontStyle::Normal) .filter(|g| !g.is_ascii()) .for_each(|g| {
70 symbol_lookup.insert(g.id, g.symbol.clone());
71 layers.insert(g.symbol.clone(), g.id);
72 });
73
74 Ok(Self {
75 texture,
76 glyph_coords: layers,
77 symbol_lookup,
78 cell_size: (cell_width, cell_height),
79 num_slices: num_slices as u32,
80 underline: config.underline,
81 strikethrough: config.strikethrough,
82 })
83 }
84
85 pub fn bind(&self, gl: &web_sys::WebGl2RenderingContext, texture_unit: u32) {
87 self.texture.bind(gl, texture_unit);
88 }
89
90 pub fn cell_size(&self) -> (i32, i32) {
91 let (w, h) = self.cell_size;
92 (w - 2 * FontAtlasData::PADDING, h - 2 * FontAtlasData::PADDING)
93 }
94
95 pub fn underline(&self) -> beamterm_data::LineDecoration {
97 self.underline
98 }
99
100 pub fn strikethrough(&self) -> beamterm_data::LineDecoration {
102 self.strikethrough
103 }
104
105 pub fn get_symbol(&self, glyph_id: u16) -> Option<Cow<str>> {
107 let base_glyph_id = glyph_id & (Glyph::GLYPH_ID_MASK | Glyph::EMOJI_FLAG);
108
109 if (0x20..0x80).contains(&base_glyph_id) {
110 let ch = base_glyph_id as u8 as char;
112 Some(Cow::from(ch.to_compact_string()))
113 } else {
114 self.symbol_lookup.get(&base_glyph_id).map(|s| Cow::from(s.as_str()))
115 }
116 }
117
118 pub fn get_base_glyph_id(&self, key: &str) -> Option<u16> {
120 if key.len() == 1 {
121 let ch = key.chars().next().unwrap();
122 if ch.is_ascii() {
123 let id = ch as u16;
125 return Some(id);
126 }
127 }
128
129 self.glyph_coords.get(key).copied()
130 }
131}