beamterm_data/atlas.rs
1use std::fmt::Debug;
2
3use compact_str::CompactString;
4
5use crate::{Deserializer, FontAtlasDeserializationError, Glyph, Serializable, SerializationError};
6
7/// Font atlas data for GPU-accelerated terminal rendering.
8///
9/// Contains a pre-rasterized font atlas stored as a 2D texture array, where each layer
10/// holds 32 glyphs in a 1×32 grid. The atlas includes multiple font styles (normal, bold,
11/// italic, bold+italic) and full Unicode support including emoji.
12#[derive(Clone, PartialEq)]
13pub struct FontAtlasData {
14 /// The name of the font
15 pub font_name: CompactString,
16 /// The font size in points
17 pub font_size: f32,
18 /// The number of single-cell (halfwidth) glyphs per layer, before fullwidth glyphs begin.
19 ///
20 /// Fullwidth glyphs (e.g., CJK characters) are assigned IDs starting from this value,
21 /// aligned to even boundaries. This allows the renderer to distinguish halfwidth from
22 /// fullwidth glyphs by comparing against this threshold.
23 pub max_halfwidth_base_glyph_id: u16,
24 /// Width, height and depth of the texture in pixels
25 pub texture_dimensions: (i32, i32, i32),
26 /// Width and height of each character cell
27 pub cell_size: (i32, i32),
28 /// Underline configuration
29 pub underline: LineDecoration,
30 /// Strikethrough configuration
31 pub strikethrough: LineDecoration,
32 /// The glyphs in the font
33 pub glyphs: Vec<Glyph>,
34 /// The 3d texture data containing the font glyphs
35 pub texture_data: Vec<u8>,
36}
37
38impl Debug for FontAtlasData {
39 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40 f.debug_struct("FontAtlasData")
41 .field("font_name", &self.font_name)
42 .field("font_size", &self.font_size)
43 .field("texture_dimensions", &self.texture_dimensions)
44 .field("cell_size", &self.cell_size)
45 .field("glyphs_count", &self.glyphs.len())
46 .field("texture_data_kb", &(self.texture_data.len() * 4 / 1024))
47 .finish()
48 }
49}
50
51impl FontAtlasData {
52 pub const PADDING: i32 = 1;
53 pub const CELLS_PER_SLICE: i32 = 32;
54
55 /// Deserializes a font atlas from binary format.
56 ///
57 /// # Arguments
58 /// * `serialized` - Binary data containing the serialized font atlas
59 ///
60 /// # Returns
61 /// The deserialized font atlas or an error if deserialization fails
62 pub fn from_binary(serialized: &[u8]) -> Result<Self, FontAtlasDeserializationError> {
63 let mut deserializer = Deserializer::new(serialized);
64 FontAtlasData::deserialize(&mut deserializer).map_err(|e| FontAtlasDeserializationError {
65 message: format!("Failed to deserialize font atlas: {e}"),
66 })
67 }
68
69 /// Serializes the font atlas to binary format.
70 ///
71 /// # Returns
72 /// A byte vector containing the serialized font atlas data, or an error
73 /// if serialization fails (e.g., a string field exceeds 255 bytes)
74 pub fn to_binary(&self) -> Result<Vec<u8>, SerializationError> {
75 self.serialize()
76 }
77
78 /// Calculates how many terminal columns and rows fit in the given viewport dimensions.
79 ///
80 /// # Arguments
81 /// * `viewport_width` - Width of the viewport in pixels
82 /// * `viewport_height` - Height of the viewport in pixels
83 ///
84 /// # Returns
85 /// A tuple of (columns, rows) that fit in the viewport
86 pub fn terminal_size(&self, viewport_width: i32, viewport_height: i32) -> (i32, i32) {
87 (
88 viewport_width / self.cell_size.0,
89 viewport_height / self.cell_size.1,
90 )
91 }
92
93 /// Returns the padded terminal cell size.
94 ///
95 /// The cell size includes padding (1 pixel on each side, 2 pixels total per dimension)
96 /// to prevent texture bleeding artifacts during GPU rendering.
97 ///
98 /// # Returns
99 /// A tuple of (width, height) in pixels for each terminal cell
100 pub fn cell_size(&self) -> (i32, i32) {
101 self.cell_size
102 }
103}
104
105impl Default for FontAtlasData {
106 fn default() -> Self {
107 Self::from_binary(include_bytes!("../atlas/bitmap_font.atlas")).unwrap()
108 }
109}
110
111#[derive(Copy, Clone, Debug, PartialEq)]
112pub struct LineDecoration {
113 /// 0.0 to 1.0, where 0.0 is the top of the text line and 1.0 is the bottom.
114 pub position: f32,
115 /// Thickness of the line as a fraction of the cell height (0.0 to 1.0)
116 pub thickness: f32,
117}
118
119impl LineDecoration {
120 pub fn new(position: f32, thickness: f32) -> Self {
121 Self {
122 position: position.clamp(0.0, 1.0),
123 thickness: thickness.clamp(0.0, 1.0),
124 }
125 }
126}
127
128/// Debug pattern for validating pixel-perfect rendering of cell dimensions.
129///
130/// When enabled, replaces the space glyph with a checkered pattern to help
131/// verify that cell boundaries align correctly with pixel boundaries.
132#[derive(Debug, Clone, Copy, PartialEq, Eq)]
133pub enum DebugSpacePattern {
134 /// 1px alternating checkerboard pattern
135 OnePixel,
136 /// 2x2 pixel checkerboard pattern
137 TwoByTwo,
138}