1use std::{borrow::Cow, cmp::min, fmt::Debug, ops::Index};
2
3use beamterm_data::{FontAtlasData, FontStyle, Glyph, GlyphEffect};
4use compact_str::{CompactString, CompactStringExt};
5use web_sys::{WebGl2RenderingContext, console};
6
7use crate::{
8 error::Error,
9 gl::{
10 CellIterator, Drawable, FontAtlas, GL, RenderContext, ShaderProgram, buffer_upload_array,
11 selection::SelectionTracker, ubo::UniformBufferObject,
12 },
13 mat4::Mat4,
14};
15
16#[derive(Debug)]
23pub struct TerminalGrid {
24 shader: ShaderProgram,
26 cells: Vec<CellDynamic>,
28 terminal_size: (u16, u16),
30 canvas_size_px: (i32, i32),
32 buffers: TerminalBuffers,
34 ubo_vertex: UniformBufferObject,
36 ubo_fragment: UniformBufferObject,
38 atlas: FontAtlas,
40 sampler_loc: web_sys::WebGlUniformLocation,
42 fallback_glyph: u16,
44 selection: SelectionTracker,
46 cells_pending_flush: bool,
48}
49
50#[derive(Debug)]
51struct TerminalBuffers {
52 vao: web_sys::WebGlVertexArrayObject,
53 vertices: web_sys::WebGlBuffer,
54 instance_pos: web_sys::WebGlBuffer,
55 instance_cell: web_sys::WebGlBuffer,
56 indices: web_sys::WebGlBuffer,
57}
58
59impl TerminalBuffers {
60 fn upload_instance_data<T>(&self, gl: &WebGl2RenderingContext, cell_data: &[T]) {
61 gl.bind_vertex_array(Some(&self.vao));
62 gl.bind_buffer(GL::ARRAY_BUFFER, Some(&self.instance_cell));
63
64 buffer_upload_array(gl, GL::ARRAY_BUFFER, cell_data, GL::DYNAMIC_DRAW);
65
66 gl.bind_vertex_array(None);
67 }
68}
69
70impl TerminalGrid {
71 const FRAGMENT_GLSL: &'static str = include_str!("../shaders/cell.frag");
72 const VERTEX_GLSL: &'static str = include_str!("../shaders/cell.vert");
73
74 pub fn new(
75 gl: &WebGl2RenderingContext,
76 atlas: FontAtlas,
77 screen_size: (i32, i32),
78 ) -> Result<Self, Error> {
79 let vao = create_vao(gl)?;
81 gl.bind_vertex_array(Some(&vao));
82
83 let cell_size = atlas.cell_size();
85 let (cols, rows) = (screen_size.0 / cell_size.0, screen_size.1 / cell_size.1);
86
87 let cell_data = create_terminal_cell_data(cols, rows, &[' ' as u16]);
90 let cell_pos = CellStatic::create_grid(cols, rows);
91 let buffers = setup_buffers(gl, vao, &cell_pos, &cell_data, cell_size)?;
92
93 gl.bind_vertex_array(None);
95
96 let shader = ShaderProgram::create(gl, Self::VERTEX_GLSL, Self::FRAGMENT_GLSL)?;
98 shader.use_program(gl);
99
100 let ubo_vertex = UniformBufferObject::new(gl, CellVertexUbo::BINDING_POINT)?;
101 ubo_vertex.bind_to_shader(gl, &shader, "VertUbo")?;
102 let ubo_fragment = UniformBufferObject::new(gl, CellFragmentUbo::BINDING_POINT)?;
103 ubo_fragment.bind_to_shader(gl, &shader, "FragUbo")?;
104
105 let sampler_loc = gl
106 .get_uniform_location(&shader.program, "u_sampler")
107 .ok_or(Error::uniform_location_failed("u_sampler"))?;
108
109 let (cols, rows) = (screen_size.0 / cell_size.0, screen_size.1 / cell_size.1);
110 let grid = Self {
111 shader,
112 terminal_size: (cols as u16, rows as u16),
113 canvas_size_px: screen_size,
114 cells: cell_data,
115 buffers,
116 ubo_vertex,
117 ubo_fragment,
118 atlas,
119 sampler_loc,
120 fallback_glyph: ' ' as u16,
121 selection: SelectionTracker::new(),
122 cells_pending_flush: false,
123 };
124
125 grid.upload_ubo_data(gl);
126
127 Ok(grid)
128 }
129
130 pub fn set_fallback_glyph(&mut self, fallback: &str) {
132 self.fallback_glyph = self
133 .atlas
134 .get_base_glyph_id(fallback)
135 .unwrap_or(' ' as u16);
136 }
137
138 pub fn atlas(&self) -> &FontAtlas {
140 &self.atlas
141 }
142
143 pub(crate) fn canvas_size(&self) -> (i32, i32) {
145 self.canvas_size_px
146 }
147
148 pub fn cell_size(&self) -> (i32, i32) {
150 self.atlas.cell_size()
151 }
152
153 pub fn terminal_size(&self) -> (u16, u16) {
155 self.terminal_size
156 }
157
158 pub fn cell_data_mut(&mut self, x: u16, y: u16) -> Option<&mut CellDynamic> {
160 let (cols, _) = self.terminal_size;
161 let idx = y as usize * cols as usize + x as usize;
162 self.cells.get_mut(idx)
163 }
164
165 pub(crate) fn selection_tracker(&self) -> SelectionTracker {
167 self.selection.clone()
168 }
169
170 pub(super) fn get_symbols(&self, selection: CellIterator) -> CompactString {
172 let (cols, rows) = self.terminal_size;
173 let mut text = CompactString::new("");
174
175 for (idx, require_newline_after) in selection {
176 text.push_str(&self.get_cell_symbol(idx));
177 if require_newline_after {
178 text.push('\n'); }
180 }
181
182 text
183 }
184
185 fn get_cell_symbol(&self, idx: usize) -> Cow<'_, str> {
186 if idx < self.cells.len() {
187 let glyph_id = self.cells[idx].glyph_id();
188 self.atlas
189 .get_symbol(glyph_id)
190 .unwrap_or_else(|| self.fallback_symbol())
191 } else {
192 self.fallback_symbol()
193 }
194 }
195
196 fn upload_ubo_data(&self, gl: &WebGl2RenderingContext) {
205 let vertex_ubo = CellVertexUbo::new(self.canvas_size_px, self.cell_size());
206 self.ubo_vertex.upload_data(gl, &vertex_ubo);
207
208 let fragment_ubo = CellFragmentUbo::new(&self.atlas);
209 self.ubo_fragment.upload_data(gl, &fragment_ubo);
210 }
211
212 pub fn cell_count(&self) -> usize {
214 self.cells.len()
215 }
216
217 pub fn update_cells<'a>(
231 &mut self,
232 gl: &WebGl2RenderingContext,
233 cells: impl Iterator<Item = CellData<'a>>,
234 ) -> Result<(), Error> {
235 let atlas = &self.atlas;
237
238 let fallback_glyph = self.fallback_glyph;
239
240 let mut pending_cell: Option<CellDynamic> = None;
242 self.cells
243 .iter_mut()
244 .zip(cells)
245 .for_each(|(cell, data)| {
246 let base_glyph_id = atlas
247 .get_base_glyph_id(data.symbol)
248 .unwrap_or(fallback_glyph);
249
250 *cell = if let Some(second_cell) = pending_cell.take() {
251 second_cell
252 } else if base_glyph_id & Glyph::EMOJI_FLAG != 0 {
253 let glyph_id = base_glyph_id;
255 pending_cell = Some(CellDynamic::new(glyph_id + 1, data.fg, data.bg));
257 CellDynamic::new(glyph_id, data.fg, data.bg)
258 } else {
259 let glyph_id = base_glyph_id | data.style_bits;
261 CellDynamic::new(glyph_id, data.fg, data.bg)
262 }
263 });
264
265 self.cells_pending_flush = true;
266 Ok(())
267 }
268
269 pub(crate) fn update_cells_by_position<'a>(
270 &mut self,
271 gl: &WebGl2RenderingContext,
272 cells: impl Iterator<Item = (u16, u16, CellData<'a>)>,
273 ) -> Result<(), Error> {
274 let atlas = &self.atlas;
276
277 let cell_count = self.cells.len();
278 let fallback_glyph = self.fallback_glyph;
279 let w = self.terminal_size.0 as usize;
280
281 let mut skip_idx = None;
286
287 let last_halfwidth = atlas.get_max_halfwidth_base_glyph_id();
288 let is_doublewidth = |glyph_id: u16| {
289 (glyph_id & (Glyph::GLYPH_ID_MASK | Glyph::EMOJI_FLAG)) > last_halfwidth
290 };
291
292 cells
293 .map(|(x, y, cell)| (w * y as usize + x as usize, cell))
294 .filter(|(idx, _)| *idx < cell_count)
295 .for_each(|(idx, cell)| {
296 if skip_idx.take() == Some(idx) {
297 return;
299 }
300
301 let base_glyph_id = atlas
302 .get_base_glyph_id(cell.symbol)
303 .unwrap_or(fallback_glyph);
304
305 if is_doublewidth(base_glyph_id) {
306 let glyph_id = base_glyph_id;
307
308 self.cells[idx] = CellDynamic::new(glyph_id, cell.fg, cell.bg);
310
311 if let Some(c) = self.cells.get_mut(idx + 1) {
313 *c = CellDynamic::new(glyph_id + 1, cell.fg, cell.bg);
314 skip_idx = Some(idx + 1);
315 }
316 } else {
317 let glyph_id = base_glyph_id | cell.style_bits;
318 self.cells[idx] = CellDynamic::new(glyph_id, cell.fg, cell.bg);
319 }
320 });
321
322 self.cells_pending_flush = true;
323
324 Ok(())
325 }
326
327 pub(crate) fn update_cell(&mut self, x: u16, y: u16, cell_data: CellData) {
328 let (cols, _) = self.terminal_size;
329 let idx = y as usize * cols as usize + x as usize;
330 self.update_cell_by_index(idx, cell_data);
331
332 self.cells_pending_flush = true;
333 }
334
335 pub(crate) fn update_cell_by_index(&mut self, idx: usize, cell_data: CellData) {
336 if idx >= self.cells.len() {
337 return;
338 }
339
340 let atlas = &self.atlas;
341 let fallback_glyph = self.fallback_glyph;
342 let base_glyph_id = atlas
343 .get_base_glyph_id(cell_data.symbol)
344 .unwrap_or(fallback_glyph);
345
346 let last_halfwidth = atlas.get_max_halfwidth_base_glyph_id();
347 let is_doublewidth = |glyph_id: u16| {
348 (glyph_id & (Glyph::GLYPH_ID_MASK | Glyph::EMOJI_FLAG)) > last_halfwidth
349 };
350
351 if is_doublewidth(base_glyph_id) {
352 let glyph_id = base_glyph_id;
354 self.cells[idx] = CellDynamic::new(glyph_id, cell_data.fg, cell_data.bg);
355 if let Some(c) = self.cells.get_mut(idx + 1) {
356 *c = CellDynamic::new(glyph_id + 1, cell_data.fg, cell_data.bg);
357 }
358 } else {
359 let glyph_id = base_glyph_id | cell_data.style_bits;
361 self.cells[idx] = CellDynamic::new(glyph_id, cell_data.fg, cell_data.bg);
362 }
363
364 self.cells_pending_flush = true;
365 }
366
367 pub(crate) fn flush_cells(&mut self, gl: &WebGl2RenderingContext) -> Result<(), Error> {
369 if !self.cells_pending_flush {
370 return Ok(()); }
372
373 self.flip_selected_cell_colors();
377
378 self.buffers.upload_instance_data(gl, &self.cells);
379
380 self.flip_selected_cell_colors();
383
384 self.cells_pending_flush = false;
385 Ok(())
386 }
387
388 fn flip_selected_cell_colors(&mut self) {
389 if let Some(iter) = self.selected_cells_iter() {
390 iter.for_each(|(idx, _)| self.cells[idx].flip_colors());
391 }
392 }
393
394 fn selected_cells_iter(&self) -> Option<CellIterator> {
395 self.selection
396 .get_query()
397 .and_then(|query| query.range())
398 .map(|(start, end)| self.cell_iter(start, end, self.selection.mode()))
399 }
400
401 fn flip_cell_colors(&mut self, x: u16, y: u16) {
402 let (cols, _) = self.terminal_size;
403 let idx = y as usize * cols as usize + x as usize;
404 if idx < self.cells.len() {
405 self.cells[idx].flip_colors();
406 }
407 }
408
409 pub fn resize(
425 &mut self,
426 gl: &WebGl2RenderingContext,
427 canvas_size: (i32, i32),
428 ) -> Result<(), Error> {
429 self.canvas_size_px = canvas_size;
430
431 self.upload_ubo_data(gl);
433
434 let cell_size = self.atlas.cell_size();
435 let cols = canvas_size.0 / cell_size.0;
436 let rows = canvas_size.1 / cell_size.1;
437 if self.terminal_size == (cols as u16, rows as u16) {
438 return Ok(()); }
440
441 gl.bind_vertex_array(Some(&self.buffers.vao));
443
444 gl.delete_buffer(Some(&self.buffers.instance_cell));
446 gl.delete_buffer(Some(&self.buffers.instance_pos));
447
448 let current_size = (self.terminal_size.0 as i32, self.terminal_size.1 as i32);
450 let cell_data = resize_cell_grid(&self.cells, current_size, (cols, rows));
451 self.cells = cell_data;
452
453 let cell_pos = CellStatic::create_grid(cols, rows);
454
455 self.buffers.instance_cell = create_dynamic_instance_buffer(gl, &self.cells)?;
457 self.buffers.instance_pos = create_static_instance_buffer(gl, &cell_pos)?;
458
459 gl.bind_vertex_array(None);
461
462 self.terminal_size = (cols as u16, rows as u16);
463
464 Ok(())
465 }
466
467 pub fn base_glyph_id(&self, symbol: &str) -> Option<u16> {
469 self.atlas.get_base_glyph_id(symbol)
470 }
471
472 fn fallback_symbol(&self) -> Cow<'_, str> {
473 self.atlas
474 .get_symbol(self.fallback_glyph)
475 .unwrap_or(Cow::Borrowed(" "))
476 }
477
478 fn fill_glyphs(atlas: &FontAtlas) -> Vec<u16> {
479 [
480 ("🤫", FontStyle::Normal),
481 ("🙌", FontStyle::Normal),
482 ("n", FontStyle::Normal),
483 ("o", FontStyle::Normal),
484 ("r", FontStyle::Normal),
485 ("m", FontStyle::Normal),
486 ("a", FontStyle::Normal),
487 ("l", FontStyle::Normal),
488 ("b", FontStyle::Bold),
489 ("o", FontStyle::Bold),
490 ("l", FontStyle::Bold),
491 ("d", FontStyle::Bold),
492 ("i", FontStyle::Italic),
493 ("t", FontStyle::Italic),
494 ("a", FontStyle::Italic),
495 ("l", FontStyle::Italic),
496 ("i", FontStyle::Italic),
497 ("c", FontStyle::Italic),
498 ("b", FontStyle::BoldItalic),
499 ("-", FontStyle::BoldItalic),
500 ("i", FontStyle::BoldItalic),
501 ("t", FontStyle::BoldItalic),
502 ("a", FontStyle::BoldItalic),
503 ("l", FontStyle::BoldItalic),
504 ("i", FontStyle::BoldItalic),
505 ("c", FontStyle::BoldItalic),
506 ("🤪", FontStyle::Normal),
507 ("🤩", FontStyle::Normal),
508 ]
509 .into_iter()
510 .map(|(symbol, style)| {
511 atlas
512 .get_base_glyph_id(symbol)
513 .map(|g| g | style as u16)
514 })
515 .map(|g| g.unwrap_or(' ' as u16))
516 .collect()
517 }
518}
519
520fn resize_cell_grid(
521 cells: &[CellDynamic],
522 old_size: (i32, i32),
523 new_size: (i32, i32),
524) -> Vec<CellDynamic> {
525 let new_len = new_size.0 * new_size.1;
526
527 let mut new_cells = Vec::with_capacity(new_len as usize);
528 for _ in 0..new_len {
529 new_cells.push(CellDynamic::new(' ' as u16, 0xFFFFFF, 0x000000));
530 }
531
532 for y in 0..min(old_size.1, new_size.1) {
533 for x in 0..min(old_size.0, new_size.0) {
534 let new_idx = (y * new_size.0 + x) as usize;
535 let old_idx = (y * old_size.0 + x) as usize;
536 new_cells[new_idx] = cells[old_idx];
537 }
538 }
539
540 new_cells
541}
542
543fn create_vao(gl: &WebGl2RenderingContext) -> Result<web_sys::WebGlVertexArrayObject, Error> {
544 gl.create_vertex_array()
545 .ok_or(Error::vertex_array_creation_failed())
546}
547
548fn setup_buffers(
549 gl: &WebGl2RenderingContext,
550 vao: web_sys::WebGlVertexArrayObject,
551 cell_pos: &[CellStatic],
552 cell_data: &[CellDynamic],
553 cell_size: (i32, i32),
554) -> Result<TerminalBuffers, Error> {
555 let (w, h) = (cell_size.0 as f32, cell_size.1 as f32);
556
557 let overlap = 0.0; #[rustfmt::skip]
560 let vertices = [
561 w + overlap, -overlap, 1.0, 0.0, -overlap, h + overlap, 0.0, 1.0, w + overlap, h + overlap, 1.0, 1.0, -overlap, -overlap, 0.0, 0.0 ];
567 let indices = [0, 1, 2, 0, 3, 1];
568
569 Ok(TerminalBuffers {
570 vao,
571 vertices: create_buffer_f32(gl, GL::ARRAY_BUFFER, &vertices, GL::STATIC_DRAW)?,
572 instance_pos: create_static_instance_buffer(gl, cell_pos)?,
573 instance_cell: create_dynamic_instance_buffer(gl, cell_data)?,
574 indices: create_buffer_u8(gl, GL::ELEMENT_ARRAY_BUFFER, &indices, GL::STATIC_DRAW)?,
575 })
576}
577
578fn create_buffer_u8(
579 gl: &WebGl2RenderingContext,
580 target: u32,
581 data: &[u8],
582 usage: u32,
583) -> Result<web_sys::WebGlBuffer, Error> {
584 let index_buf = gl
585 .create_buffer()
586 .ok_or(Error::buffer_creation_failed("vbo-u8"))?;
587 gl.bind_buffer(target, Some(&index_buf));
588
589 gl.buffer_data_with_u8_array(target, data, usage);
590
591 Ok(index_buf)
592}
593
594fn create_buffer_f32(
595 gl: &WebGl2RenderingContext,
596 target: u32,
597 data: &[f32],
598 usage: u32,
599) -> Result<web_sys::WebGlBuffer, Error> {
600 let buffer = gl
601 .create_buffer()
602 .ok_or(Error::buffer_creation_failed("vbo-f32"))?;
603
604 gl.bind_buffer(target, Some(&buffer));
605
606 unsafe {
607 let view = js_sys::Float32Array::view(data);
608 gl.buffer_data_with_array_buffer_view(target, &view, usage);
609 }
610
611 const STRIDE: i32 = (2 + 2) * 4; enable_vertex_attrib(gl, attrib::POS, 2, GL::FLOAT, 0, STRIDE);
614 enable_vertex_attrib(gl, attrib::UV, 2, GL::FLOAT, 8, STRIDE);
615
616 Ok(buffer)
617}
618
619fn create_static_instance_buffer(
620 gl: &WebGl2RenderingContext,
621 instance_data: &[CellStatic],
622) -> Result<web_sys::WebGlBuffer, Error> {
623 let instance_buf = gl
624 .create_buffer()
625 .ok_or(Error::buffer_creation_failed("static-instance-buffer"))?;
626
627 gl.bind_buffer(GL::ARRAY_BUFFER, Some(&instance_buf));
628 buffer_upload_array(gl, GL::ARRAY_BUFFER, instance_data, GL::STATIC_DRAW);
629
630 let stride = size_of::<CellStatic>() as i32;
631 enable_vertex_attrib_array(gl, attrib::GRID_XY, 2, GL::UNSIGNED_SHORT, 0, stride);
632
633 Ok(instance_buf)
634}
635
636fn create_dynamic_instance_buffer(
637 gl: &WebGl2RenderingContext,
638 instance_data: &[CellDynamic],
639) -> Result<web_sys::WebGlBuffer, Error> {
640 let instance_buf = gl
641 .create_buffer()
642 .ok_or(Error::buffer_creation_failed("dynamic-instance-buffer"))?;
643
644 gl.bind_buffer(GL::ARRAY_BUFFER, Some(&instance_buf));
645 buffer_upload_array(gl, GL::ARRAY_BUFFER, instance_data, GL::DYNAMIC_DRAW);
646
647 let stride = size_of::<CellDynamic>() as i32;
648
649 enable_vertex_attrib_array(
651 gl,
652 attrib::PACKED_DEPTH_FG_BG,
653 2,
654 GL::UNSIGNED_INT,
655 0,
656 stride,
657 );
658
659 Ok(instance_buf)
660}
661
662fn enable_vertex_attrib_array(
663 gl: &WebGl2RenderingContext,
664 index: u32,
665 size: i32,
666 type_: u32,
667 offset: i32,
668 stride: i32,
669) {
670 enable_vertex_attrib(gl, index, size, type_, offset, stride);
671 gl.vertex_attrib_divisor(index, 1);
672}
673
674fn enable_vertex_attrib(
675 gl: &WebGl2RenderingContext,
676 index: u32,
677 size: i32,
678 type_: u32,
679 offset: i32,
680 stride: i32,
681) {
682 gl.enable_vertex_attrib_array(index);
683 if type_ == GL::FLOAT {
684 gl.vertex_attrib_pointer_with_i32(index, size, type_, false, stride, offset);
685 } else {
686 gl.vertex_attrib_i_pointer_with_i32(index, size, type_, stride, offset);
687 }
688}
689
690impl Drawable for TerminalGrid {
691 fn prepare(&self, context: &mut RenderContext) {
692 let gl = context.gl;
693
694 self.shader.use_program(gl);
695
696 gl.bind_vertex_array(Some(&self.buffers.vao));
697
698 self.atlas.bind(gl, 0);
699 self.ubo_vertex.bind(context.gl);
700 self.ubo_fragment.bind(context.gl);
701 gl.uniform1i(Some(&self.sampler_loc), 0);
702 }
703
704 fn draw(&self, context: &mut RenderContext) {
705 let gl = context.gl;
706 let cell_count = self.cells.len() as i32;
707
708 gl.draw_elements_instanced_with_i32(GL::TRIANGLES, 6, GL::UNSIGNED_BYTE, 0, cell_count);
709 }
710
711 fn cleanup(&self, context: &mut RenderContext) {
712 let gl = context.gl;
713 gl.bind_vertex_array(None);
714 gl.bind_texture(GL::TEXTURE_2D_ARRAY, None);
715
716 self.ubo_vertex.unbind(gl);
717 self.ubo_fragment.unbind(gl);
718 }
719}
720
721#[derive(Debug, Copy, Clone)]
733pub struct CellData<'a> {
734 symbol: &'a str,
735 style_bits: u16,
736 fg: u32,
737 bg: u32,
738}
739
740impl<'a> CellData<'a> {
741 pub fn new(symbol: &'a str, style: FontStyle, effect: GlyphEffect, fg: u32, bg: u32) -> Self {
753 Self::new_with_style_bits(symbol, style.style_mask() | effect as u16, fg, bg)
754 }
755
756 pub fn new_with_style_bits(symbol: &'a str, style_bits: u16, fg: u32, bg: u32) -> Self {
780 debug_assert!(
782 0x81FF & style_bits == 0,
783 "Invalid style bits: {style_bits:#04x}"
784 );
785 Self { symbol, style_bits, fg, bg }
786 }
787}
788
789#[repr(C, align(4))]
810struct CellStatic {
811 pub grid_xy: [u16; 2],
813}
814
815#[derive(Debug, Clone, Copy)]
843#[repr(C, align(4))]
844pub struct CellDynamic {
845 data: [u8; 8], }
858
859impl CellStatic {
860 fn create_grid(cols: i32, rows: i32) -> Vec<Self> {
861 debug_assert!(cols > 0 && cols < u16::MAX as i32, "cols: {cols}");
862 debug_assert!(rows > 0 && rows < u16::MAX as i32, "rows: {rows}");
863
864 (0..rows)
865 .flat_map(|row| (0..cols).map(move |col| (col, row)))
866 .map(|(col, row)| Self { grid_xy: [col as u16, row as u16] })
867 .collect()
868 }
869}
870
871impl CellDynamic {
872 const GLYPH_STYLE_MASK: u16 =
873 Glyph::BOLD_FLAG | Glyph::ITALIC_FLAG | Glyph::UNDERLINE_FLAG | Glyph::STRIKETHROUGH_FLAG;
874
875 #[inline]
876 pub fn new(glyph_id: u16, fg: u32, bg: u32) -> Self {
877 let mut data = [0; 8];
878
879 let glyph_id = glyph_id.to_le_bytes();
881 data[0] = glyph_id[0];
882 data[1] = glyph_id[1];
883
884 let fg = fg.to_le_bytes();
885 data[2] = fg[2]; data[3] = fg[1]; data[4] = fg[0]; let bg = bg.to_le_bytes();
890 data[5] = bg[2]; data[6] = bg[1]; data[7] = bg[0]; Self { data }
895 }
896
897 pub fn style(&mut self, style_bits: u16) {
899 let glyph_id = (self.glyph_id() & !Self::GLYPH_STYLE_MASK) | style_bits;
900 self.data[..2].copy_from_slice(&glyph_id.to_le_bytes());
901 }
902
903 pub fn flip_colors(&mut self) {
905 let fg = [self.data[2], self.data[3], self.data[4]];
907 self.data[2] = self.data[5]; self.data[3] = self.data[6]; self.data[4] = self.data[7]; self.data[5] = fg[0]; self.data[6] = fg[1]; self.data[7] = fg[2]; }
914
915 pub fn fg_color(&mut self, fg: u32) {
917 let fg = fg.to_le_bytes();
918 self.data[2] = fg[2]; self.data[3] = fg[1]; self.data[4] = fg[0]; }
922
923 pub fn bg_color(&mut self, bg: u32) {
925 let bg = bg.to_le_bytes();
926 self.data[5] = bg[2]; self.data[6] = bg[1]; self.data[7] = bg[0]; }
930
931 pub fn get_fg_color(&self) -> u32 {
933 ((self.data[2] as u32) << 16) | ((self.data[3] as u32) << 8) | (self.data[4] as u32)
935 }
936
937 pub fn get_bg_color(&self) -> u32 {
939 ((self.data[5] as u32) << 16) | ((self.data[6] as u32) << 8) | (self.data[7] as u32)
941 }
942
943 pub fn get_style(&self) -> u16 {
945 self.glyph_id() & Self::GLYPH_STYLE_MASK
946 }
947
948 pub fn is_emoji(&self) -> bool {
950 self.glyph_id() & Glyph::EMOJI_FLAG != 0
951 }
952
953 #[inline]
954 fn glyph_id(&self) -> u16 {
955 u16::from_le_bytes([self.data[0], self.data[1]])
956 }
957}
958
959#[repr(C, align(16))] struct CellVertexUbo {
961 pub projection: [f32; 16], pub cell_size: [f32; 2], pub _padding: [f32; 2],
964}
965
966#[repr(C, align(16))] struct CellFragmentUbo {
968 pub padding_frac: [f32; 2], pub underline_pos: f32, pub underline_thickness: f32, pub strikethrough_pos: f32, pub strikethrough_thickness: f32, pub _padding: [f32; 2],
974}
975
976impl CellVertexUbo {
977 pub const BINDING_POINT: u32 = 0;
978
979 fn new(canvas_size: (i32, i32), cell_size: (i32, i32)) -> Self {
980 let projection =
981 Mat4::orthographic_from_size(canvas_size.0 as f32, canvas_size.1 as f32).data;
982 Self {
983 projection,
984 cell_size: [cell_size.0 as f32, cell_size.1 as f32],
985 _padding: [0.0; 2], }
987 }
988}
989
990impl CellFragmentUbo {
991 pub const BINDING_POINT: u32 = 1;
992
993 fn new(atlas: &FontAtlas) -> Self {
994 let cell_size = atlas.cell_size();
995 let underline = atlas.underline();
996 let strikethrough = atlas.strikethrough();
997 Self {
998 padding_frac: [
999 FontAtlasData::PADDING as f32 / cell_size.0 as f32,
1000 FontAtlasData::PADDING as f32 / cell_size.1 as f32,
1001 ],
1002 underline_pos: underline.position,
1003 underline_thickness: underline.thickness,
1004 strikethrough_pos: strikethrough.position,
1005 strikethrough_thickness: strikethrough.thickness,
1006 _padding: [0.0; 2], }
1008 }
1009}
1010
1011fn create_terminal_cell_data(cols: i32, rows: i32, fill_glyph: &[u16]) -> Vec<CellDynamic> {
1012 let glyph_len = fill_glyph.len();
1013 (0..cols * rows)
1014 .map(|i| CellDynamic::new(fill_glyph[i as usize % glyph_len], 0x00ff_ffff, 0x0000_0000))
1015 .collect()
1016}
1017
1018mod attrib {
1019 pub const POS: u32 = 0;
1020 pub const UV: u32 = 1;
1021
1022 pub const GRID_XY: u32 = 2;
1023 pub const PACKED_DEPTH_FG_BG: u32 = 3;
1024}