beamterm_core/gl/
static_atlas.rs1use std::collections::HashMap;
2
3use beamterm_data::{FontAtlasData, FontStyle, Glyph};
4use compact_str::{CompactString, ToCompactString};
5
6use super::{
7 atlas,
8 atlas::{Atlas, GlyphSlot, GlyphTracker},
9};
10use crate::error::Error;
11
12#[derive(Debug)]
25pub struct StaticFontAtlas {
26 texture: crate::gl::texture::Texture,
28 glyph_coords: HashMap<CompactString, u16>,
30 symbol_lookup: HashMap<u16, CompactString>,
32 cell_size: (i32, i32),
34 underline: beamterm_data::LineDecoration,
36 strikethrough: beamterm_data::LineDecoration,
38 glyph_tracker: GlyphTracker,
40 last_halfwidth_base_glyph_id: u16,
42 atlas_data: FontAtlasData,
44}
45
46impl StaticFontAtlas {
47 pub fn load(gl: &glow::Context, config: FontAtlasData) -> Result<Self, Error> {
49 let texture = crate::gl::texture::Texture::from_font_atlas_data(gl, glow::RGBA, &config)?;
50
51 let (cell_width, cell_height) = config.cell_size;
52 let mut layers = HashMap::new();
53 let mut symbol_lookup = HashMap::new();
54
55 config.glyphs.iter()
61 .filter(|g| g.style == FontStyle::Normal) .filter(|g| !g.is_ascii()) .for_each(|g| {
64 symbol_lookup.insert(g.id, g.symbol.clone());
65 layers.insert(g.symbol.clone(), g.id);
66 });
67
68 Ok(Self {
69 texture,
70 glyph_coords: layers,
71 last_halfwidth_base_glyph_id: config.max_halfwidth_base_glyph_id,
72 symbol_lookup,
73 cell_size: (cell_width, cell_height),
74 underline: config.underline,
75 strikethrough: config.strikethrough,
76 glyph_tracker: GlyphTracker::new(),
77 atlas_data: config,
78 })
79 }
80}
81
82impl Atlas for StaticFontAtlas {
83 fn get_glyph_id(&self, key: &str, style_bits: u16) -> Option<u16> {
84 let base_id = self.get_base_glyph_id(key)?;
85 Some(base_id | style_bits)
86 }
87
88 fn get_base_glyph_id(&self, key: &str) -> Option<u16> {
90 if key.len() == 1 {
91 let ch = key.chars().next().unwrap();
92 if ch.is_ascii() {
93 let id = ch as u16;
95 return Some(id);
96 }
97 }
98
99 match self.glyph_coords.get(key) {
100 Some(id) => Some(*id),
101 None => {
102 self.glyph_tracker.record_missing(key);
103 None
104 },
105 }
106 }
107
108 fn cell_size(&self) -> (i32, i32) {
109 let (w, h) = self.cell_size;
110 (
111 w - 2 * FontAtlasData::PADDING,
112 h - 2 * FontAtlasData::PADDING,
113 )
114 }
115
116 fn bind(&self, gl: &glow::Context) {
117 self.texture.bind(gl);
118 }
119
120 fn underline(&self) -> beamterm_data::LineDecoration {
122 self.underline
123 }
124
125 fn strikethrough(&self) -> beamterm_data::LineDecoration {
127 self.strikethrough
128 }
129
130 fn get_symbol(&self, glyph_id: u16) -> Option<CompactString> {
132 let base_glyph_id = if glyph_id & Glyph::EMOJI_FLAG != 0 {
133 glyph_id & Glyph::GLYPH_ID_EMOJI_MASK
134 } else {
135 glyph_id & Glyph::GLYPH_ID_MASK
136 };
137
138 if (0x20..0x80).contains(&base_glyph_id) {
139 let ch = base_glyph_id as u8 as char;
141 Some(ch.to_compact_string())
142 } else {
143 self.symbol_lookup.get(&base_glyph_id).cloned()
144 }
145 }
146
147 fn get_ascii_char(&self, glyph_id: u16) -> Option<char> {
148 if (0x20..0x80).contains(&glyph_id) {
150 Some(glyph_id as u8 as char)
151 } else {
152 None
153 }
154 }
155
156 fn glyph_tracker(&self) -> &GlyphTracker {
157 &self.glyph_tracker
158 }
159
160 fn glyph_count(&self) -> u32 {
161 let ascii_count = 0x80 - 0x20;
163 let non_ascii_count = self.symbol_lookup.len() as u32;
165 ascii_count + non_ascii_count
166 }
167
168 fn flush(&self, _gl: &glow::Context) -> Result<(), Error> {
169 Ok(()) }
171
172 fn recreate_texture(&mut self, gl: &glow::Context) -> Result<(), Error> {
178 self.texture.delete(gl);
180
181 self.texture =
183 crate::gl::texture::Texture::from_font_atlas_data(gl, glow::RGBA, &self.atlas_data)?;
184
185 Ok(())
186 }
187
188 fn for_each_symbol(&self, f: &mut dyn FnMut(u16, &str)) {
189 for code in 0x20u16..0x80 {
191 let ch = code as u8 as char;
192 let mut buf = [0u8; 4];
193 let s = ch.encode_utf8(&mut buf);
194 f(code, s);
195 }
196 for (glyph_id, symbol) in &self.symbol_lookup {
198 f(*glyph_id, symbol.as_str());
199 }
200 }
201
202 fn resolve_glyph_slot(&self, key: &str, style_bits: u16) -> Option<GlyphSlot> {
203 if key.len() == 1 {
204 let ch = key.chars().next().unwrap();
205 if ch.is_ascii() {
206 let id = ch as u16;
208 return Some(GlyphSlot::Normal(id | style_bits));
209 }
210 }
211
212 match self.glyph_coords.get(key) {
213 Some(base_glyph_id) => {
214 let id = base_glyph_id | style_bits;
215 if *base_glyph_id >= self.last_halfwidth_base_glyph_id {
216 Some(GlyphSlot::Wide(id))
217 } else if id & Glyph::EMOJI_FLAG != 0 {
218 Some(GlyphSlot::Emoji(id))
219 } else {
220 Some(GlyphSlot::Normal(id))
221 }
222 },
223 None => {
224 self.glyph_tracker.record_missing(key);
225 None
226 },
227 }
228 }
229
230 fn base_lookup_mask(&self) -> u32 {
235 atlas::STATIC_ATLAS_LOOKUP_MASK
236 }
237
238 fn delete(&self, gl: &glow::Context) {
239 self.texture.delete(gl);
240 }
241
242 fn update_pixel_ratio(&mut self, _gl: &glow::Context, pixel_ratio: f32) -> Result<f32, Error> {
243 Ok(pixel_ratio)
245 }
246
247 fn cell_scale_for_dpr(&self, pixel_ratio: f32) -> f32 {
248 if pixel_ratio <= 0.5 { 0.5 } else { pixel_ratio.round().max(1.0) }
250 }
251
252 fn texture_cell_size(&self) -> (i32, i32) {
253 self.cell_size()
255 }
256}