Skip to main content

beamterm_data/
serialization.rs

1use compact_str::{CompactString, format_compact};
2
3use crate::{FontAtlasData, FontStyle, Glyph, LineDecoration};
4
5const ATLAS_HEADER: [u8; 4] = [0xBA, 0xB1, 0xF0, 0xA7];
6const ATLAS_VERSION: u8 = 0x03; // dictates the format of the serialized data
7
8#[derive(Debug, thiserror::Error)]
9#[error("{message}")]
10pub struct SerializationError {
11    pub message: CompactString,
12}
13
14impl SerializationError {
15    pub fn new(message: impl Into<CompactString>) -> Self {
16        Self { message: message.into() }
17    }
18}
19
20pub(crate) trait Serializable {
21    fn serialize(&self) -> Result<Vec<u8>, SerializationError>;
22
23    fn deserialize(deser: &mut Deserializer) -> Result<Self, SerializationError>
24    where
25        Self: Sized;
26}
27
28pub(crate) struct Deserializer<'a> {
29    data: &'a [u8],
30    position: usize,
31}
32
33struct Serializer {
34    data: Vec<u8>,
35}
36
37impl Serializer {
38    pub fn new() -> Self {
39        Self { data: Vec::new() }
40    }
41
42    pub fn write_u8(&mut self, value: u8) {
43        self.data.push(value);
44    }
45
46    pub fn write_u16(&mut self, value: u16) {
47        self.data.extend(&value.to_le_bytes());
48    }
49
50    pub fn write_u32(&mut self, value: u32) {
51        self.data.extend(&value.to_le_bytes());
52    }
53
54    pub fn write_f32(&mut self, value: f32) {
55        self.data.extend(&value.to_le_bytes());
56    }
57
58    pub fn write_i32(&mut self, value: i32) {
59        self.data.extend(&value.to_le_bytes());
60    }
61
62    pub fn write_u8_slice(&mut self, value: &[u8]) {
63        self.write_u32(value.len() as u32);
64        self.data.extend_from_slice(value);
65    }
66
67    pub fn write_string(&mut self, value: &str) -> Result<(), SerializationError> {
68        if value.len() > u8::MAX as usize {
69            return Err(SerializationError::new(format!(
70                "String too long: {} bytes (max 255)",
71                value.len()
72            )));
73        }
74
75        let length = value.len() as u8;
76        self.write_u8(length);
77        self.data.extend(value.as_bytes());
78
79        Ok(())
80    }
81}
82
83impl<'a> Deserializer<'a> {
84    pub fn new(data: &'a [u8]) -> Self {
85        Self { data, position: 0 }
86    }
87
88    pub fn read_u8(&mut self) -> Result<u8, SerializationError> {
89        self.verify_offset_in_bounds(1)?;
90
91        let byte = self.data[self.position];
92        self.position += 1;
93
94        Ok(byte)
95    }
96
97    pub fn read_u16(&mut self) -> Result<u16, SerializationError> {
98        self.verify_offset_in_bounds(2)?;
99
100        let bytes = &self.data[self.position..self.position + 2];
101        self.position += 2;
102
103        Ok(u16::from_le_bytes(bytes.try_into().unwrap()))
104    }
105
106    pub fn read_f32(&mut self) -> Result<f32, SerializationError> {
107        self.verify_offset_in_bounds(4)?;
108
109        let bytes = &self.data[self.position..self.position + 4];
110        self.position += 4;
111
112        Ok(f32::from_le_bytes(bytes.try_into().unwrap()))
113    }
114
115    pub fn read_u32(&mut self) -> Result<u32, SerializationError> {
116        self.verify_offset_in_bounds(4)?;
117
118        let bytes = &self.data[self.position..self.position + 4];
119        self.position += 4;
120
121        Ok(u32::from_le_bytes(bytes.try_into().unwrap()))
122    }
123
124    pub fn read_u8_slice(&mut self) -> Result<Vec<u8>, SerializationError> {
125        let length = self.read_u32()? as usize;
126        self.verify_offset_in_bounds(length)?;
127
128        let slice = &self.data[self.position..self.position + length];
129        self.position += length;
130
131        Ok(slice.to_vec())
132    }
133
134    pub fn read_i32(&mut self) -> Result<i32, SerializationError> {
135        self.verify_offset_in_bounds(4)?;
136
137        let bytes = &self.data[self.position..self.position + 4];
138        self.position += 4;
139
140        Ok(i32::from_le_bytes(bytes.try_into().unwrap()))
141    }
142
143    pub fn read_string(&mut self) -> Result<CompactString, SerializationError> {
144        let length = self.read_u8()? as usize;
145        self.verify_offset_in_bounds(length)?;
146
147        let bytes = &self.data[self.position..self.position + length];
148        self.position += length;
149
150        Ok(CompactString::from_utf8_lossy(bytes))
151    }
152
153    fn verify_offset_in_bounds(&self, length: usize) -> Result<(), SerializationError> {
154        if (self.position + length) > self.data.len() {
155            return Err(SerializationError { message: CompactString::from("Out of bounds read") });
156        }
157        Ok(())
158    }
159}
160
161impl Serializable for CompactString {
162    fn serialize(&self) -> Result<Vec<u8>, SerializationError> {
163        let mut ser = Serializer::new();
164        ser.write_string(self)?;
165        Ok(ser.data)
166    }
167
168    fn deserialize(serialized: &mut Deserializer) -> Result<Self, SerializationError> {
169        serialized.read_string()
170    }
171}
172
173impl Serializable for Glyph {
174    fn serialize(&self) -> Result<Vec<u8>, SerializationError> {
175        let mut ser = Serializer::new();
176        ser.write_u16(self.id);
177        ser.write_u8(self.style.ordinal() as u8);
178        ser.write_u8(self.is_emoji as u8);
179        ser.write_i32(self.pixel_coords.0);
180        ser.write_i32(self.pixel_coords.1);
181        ser.write_string(&self.symbol)?;
182        Ok(ser.data)
183    }
184
185    fn deserialize(serialized: &mut Deserializer) -> Result<Self, SerializationError> {
186        let id = serialized.read_u16()?;
187        let style = serialized.read_u8()?;
188        let is_emoji = serialized.read_u8()? != 0;
189        let x = serialized.read_i32()?;
190        let y = serialized.read_i32()?;
191        let symbol = serialized.read_string()?;
192
193        Ok(Glyph {
194            id,
195            style: FontStyle::from_ordinal(style)?,
196            is_emoji,
197            pixel_coords: (x, y),
198            symbol,
199        })
200    }
201}
202
203impl Serializable for FontAtlasData {
204    fn serialize(&self) -> Result<Vec<u8>, SerializationError> {
205        let mut ser = Serializer::new();
206        ser.write_u8(ATLAS_HEADER[0]);
207        ser.write_u8(ATLAS_HEADER[1]);
208        ser.write_u8(ATLAS_HEADER[2]);
209        ser.write_u8(ATLAS_HEADER[3]);
210
211        ser.write_u8(ATLAS_VERSION);
212
213        ser.write_string(&self.font_name)?;
214        ser.write_f32(self.font_size);
215        ser.write_u16(self.max_halfwidth_base_glyph_id);
216
217        ser.write_i32(self.texture_dimensions.0);
218        ser.write_i32(self.texture_dimensions.1);
219        ser.write_i32(self.texture_dimensions.2);
220
221        ser.write_i32(self.cell_size.0);
222        ser.write_i32(self.cell_size.1);
223
224        ser.write_f32(self.underline.position);
225        ser.write_f32(self.underline.thickness);
226        ser.write_f32(self.strikethrough.position);
227        ser.write_f32(self.strikethrough.thickness);
228
229        // serialize the glyphs
230        ser.write_u16(self.glyphs.len() as u16);
231        for glyph in &self.glyphs {
232            ser.data.extend(glyph.serialize()?);
233        }
234
235        // serialize 3d texture data
236        let packed_texture_data = miniz_oxide::deflate::compress_to_vec(&self.texture_data, 9);
237        ser.write_u8_slice(&packed_texture_data);
238
239        Ok(ser.data)
240    }
241
242    fn deserialize(deser: &mut Deserializer) -> Result<Self, SerializationError> {
243        let header = [deser.read_u8()?, deser.read_u8()?, deser.read_u8()?, deser.read_u8()?];
244        if header != ATLAS_HEADER {
245            return Err(SerializationError {
246                message: CompactString::const_new("Invalid font atlas header (wrong file format?)"),
247            });
248        }
249
250        let version = deser.read_u8()?;
251        if version != ATLAS_VERSION {
252            return Err(SerializationError {
253                message: format_compact!(
254                    "Atlas version mismatch: expected v{}, found v{}. \
255                     Please regenerate atlas with current beamterm-atlas version.",
256                    ATLAS_VERSION,
257                    version
258                ),
259            });
260        }
261
262        let font_name = deser.read_string()?;
263        let font_size = deser.read_f32()?;
264        let halfwidth_glyphs_per_layer = deser.read_u16()?;
265
266        let texture_dimensions = (deser.read_i32()?, deser.read_i32()?, deser.read_i32()?);
267        let cell_size = (deser.read_i32()?, deser.read_i32()?);
268
269        let underline = LineDecoration::new(deser.read_f32()?, deser.read_f32()?);
270        let strikethrough = LineDecoration::new(deser.read_f32()?, deser.read_f32()?);
271
272        // deserialize the glyphs
273        let glyph_count = deser.read_u16()? as usize;
274        let mut glyphs = Vec::with_capacity(glyph_count);
275        for _ in 0..glyph_count {
276            glyphs.push(Glyph::deserialize(deser)?);
277        }
278
279        // deserialize texture data
280        let packed_texture_data = deser.read_u8_slice()?;
281        let texture_data =
282            miniz_oxide::inflate::decompress_to_vec(&packed_texture_data).map_err(|_| {
283                SerializationError {
284                    message: CompactString::const_new("Failed to decompress texture data"),
285                }
286            })?;
287
288        Ok(FontAtlasData {
289            font_name,
290            font_size,
291            max_halfwidth_base_glyph_id: halfwidth_glyphs_per_layer,
292            texture_dimensions,
293            cell_size,
294            underline,
295            strikethrough,
296            glyphs,
297            texture_data,
298        })
299    }
300}
301
302#[cfg(test)]
303mod tests {
304    use super::*;
305
306    #[test]
307    fn test_serialized_read_u8() {
308        let data = [42, 100, 255];
309        let mut serialized = Deserializer::new(&data);
310
311        assert_eq!(serialized.read_u8().unwrap(), 42);
312        assert_eq!(serialized.read_u8().unwrap(), 100);
313        assert_eq!(serialized.read_u8().unwrap(), 255);
314    }
315
316    #[test]
317    fn test_serialized_read_u8_bounds_error() {
318        let data = [42];
319        let mut serialized = Deserializer::new(&data);
320
321        // First read should succeed
322        assert_eq!(serialized.read_u8().unwrap(), 42);
323
324        // Second read should fail
325        let result = serialized.read_u8();
326        assert!(result.is_err());
327        assert_eq!(result.unwrap_err().message, "Out of bounds read");
328    }
329
330    #[test]
331    fn test_serialized_read_u16() {
332        // Little endian: 0x1234 = [0x34, 0x12]
333        let data = [0x34, 0x12, 0xFF, 0x00];
334        let mut serialized = Deserializer::new(&data);
335
336        assert_eq!(serialized.read_u16().unwrap(), 0x1234);
337        assert_eq!(serialized.read_u16().unwrap(), 0x00FF);
338    }
339
340    #[test]
341    fn test_serialized_read_u16_bounds_error() {
342        let data = [0x34]; // Only 1 byte, need 2
343        let mut serialized = Deserializer::new(&data);
344
345        let result = serialized.read_u16();
346        assert!(result.is_err());
347        assert_eq!(result.unwrap_err().message, "Out of bounds read");
348    }
349
350    #[test]
351    fn test_serialized_read_u32() {
352        // Little endian: 0x12345678 = [0x78, 0x56, 0x34, 0x12]
353        let data = [0x78, 0x56, 0x34, 0x12, 0xFF, 0xFF, 0xFF, 0xFF];
354        let mut serialized = Deserializer::new(&data);
355
356        assert_eq!(serialized.read_u32().unwrap(), 0x12345678);
357        assert_eq!(serialized.read_u32().unwrap(), 0xFFFFFFFF);
358    }
359
360    #[test]
361    fn test_serialized_read_u32_bounds_error() {
362        let data = [0x78, 0x56, 0x34]; // Only 3 bytes, need 4
363        let mut serialized = Deserializer::new(&data);
364
365        let result = serialized.read_u32();
366        assert!(result.is_err());
367        assert_eq!(result.unwrap_err().message, "Out of bounds read");
368    }
369
370    #[test]
371    fn test_compact_string_serialize_empty() {
372        let s = CompactString::new("");
373        let serialized = s.serialize().unwrap();
374
375        assert_eq!(serialized, vec![0]); // Length 0, no data
376    }
377
378    #[test]
379    fn test_compact_string_serialize_short() {
380        let s = CompactString::from("Hello");
381        let serialized = s.serialize().unwrap();
382
383        let mut expected = vec![5]; // Length 5
384        expected.extend(b"Hello");
385        assert_eq!(serialized, expected);
386    }
387
388    #[test]
389    fn test_compact_string_serialize_max_length() {
390        let s = CompactString::from("A".repeat(255));
391        let serialized = s.serialize().unwrap();
392
393        assert_eq!(serialized[0], 255); // Length byte
394        assert_eq!(serialized.len(), 256); // 1 byte for length + 255 bytes for data
395        assert_eq!(&serialized[1..], "A".repeat(255).as_bytes());
396    }
397
398    #[test]
399    fn test_compact_string_deserialize_empty() {
400        let data = [0]; // Length 0
401        let mut serialized = Deserializer::new(&data);
402
403        let result = CompactString::deserialize(&mut serialized).unwrap();
404        assert_eq!(result, "");
405    }
406
407    #[test]
408    fn test_compact_string_deserialize_short() {
409        let mut data = vec![5]; // Length 5
410        data.extend(b"Hello");
411        let mut serialized = Deserializer::new(&data);
412
413        let result = CompactString::deserialize(&mut serialized).unwrap();
414        assert_eq!(result, "Hello");
415    }
416
417    #[test]
418    fn test_compact_string_round_trip() {
419        let aaaaaa = "A".repeat(255);
420        let test_cases = ["", "Hello", "World!", "🚀 Unicode works! 🎉", aaaaaa.as_str()];
421
422        for original in &test_cases {
423            let compact_str = CompactString::from(*original);
424            let serialized = compact_str.serialize().unwrap();
425            let mut serialized_reader = Deserializer::new(&serialized);
426            let deserialized = CompactString::deserialize(&mut serialized_reader).unwrap();
427
428            assert_eq!(compact_str, deserialized);
429            assert_eq!(*original, deserialized.as_str());
430        }
431    }
432
433    #[test]
434    fn test_serialized_position_tracking() {
435        let data = [1, 2, 3, 4, 5, 6, 7, 8];
436        let mut serialized = Deserializer::new(&data);
437
438        // Read u8 (position: 0 -> 1)
439        assert_eq!(serialized.read_u8().unwrap(), 1);
440
441        // Read u16 (position: 1 -> 3)
442        assert_eq!(serialized.read_u16().unwrap(), 0x0302); // little endian [2,3]
443
444        // Read u32 (position: 3 -> 7)
445        assert_eq!(serialized.read_u32().unwrap(), 0x07060504); // little endian [4,5,6,7]
446
447        // Read final u8 (position: 7 -> 8)
448        assert_eq!(serialized.read_u8().unwrap(), 8);
449
450        // Should be at end now
451        assert!(serialized.read_u8().is_err());
452    }
453
454    #[test]
455    fn test_mixed_serialization() {
456        // Test reading different types in sequence
457        let mut data = Vec::new();
458        data.push(42u8); // u8
459        data.extend(&100u16.to_le_bytes()); // u16
460        data.extend(&0x12345678u32.to_le_bytes()); // u32
461        data.push(5); // string length
462        data.extend(b"Hello"); // string data
463
464        let mut serialized = Deserializer::new(&data);
465
466        assert_eq!(serialized.read_u8().unwrap(), 42);
467        assert_eq!(serialized.read_u16().unwrap(), 100);
468        assert_eq!(serialized.read_u32().unwrap(), 0x12345678);
469        assert_eq!(serialized.read_string().unwrap(), "Hello");
470    }
471
472    #[test]
473    fn test_font_atlas_config_round_trip() {
474        // Create test glyphs
475        let glyphs = vec![
476            Glyph {
477                id: 65, // 'A'
478                style: FontStyle::Normal,
479                symbol: CompactString::from("A"),
480                pixel_coords: (0, 0),
481                is_emoji: false,
482            },
483            Glyph {
484                id: 66, // 'B'
485                style: FontStyle::Normal,
486                symbol: CompactString::from("B"),
487                pixel_coords: (16, 0),
488                is_emoji: false,
489            },
490            Glyph {
491                id: 8364, // '€' (Euro symbol)
492                style: FontStyle::Normal,
493                symbol: CompactString::from("€"),
494                pixel_coords: (32, 0),
495                is_emoji: false,
496            },
497            Glyph {
498                id: 10000, // '🚀' (Rocket emoji)
499                style: FontStyle::Normal,
500                symbol: CompactString::from("🚀"),
501                pixel_coords: (48, 0),
502                is_emoji: true,
503            },
504        ];
505
506        // Create original FontAtlasConfig
507        let original = FontAtlasData {
508            font_name: CompactString::from("TestFont"),
509            font_size: 16.5,
510            max_halfwidth_base_glyph_id: 328,
511            texture_dimensions: (512, 256, 256),
512            cell_size: (12, 18),
513            underline: LineDecoration::new(0.85, 5.0 / 100.0),
514            strikethrough: LineDecoration::new(0.5, 5.0 / 100.0),
515            glyphs,
516            texture_data: Vec::new(),
517        };
518
519        // Serialize
520        let serialized = original.serialize().unwrap();
521
522        // Deserialize
523        let mut deserializer = Deserializer::new(&serialized);
524        let deserialized = FontAtlasData::deserialize(&mut deserializer).unwrap();
525
526        // Assert all fields match
527        assert_eq!(original.font_size, deserialized.font_size);
528        assert_eq!(
529            original.max_halfwidth_base_glyph_id,
530            deserialized.max_halfwidth_base_glyph_id
531        );
532        assert_eq!(original.texture_dimensions, deserialized.texture_dimensions);
533        assert_eq!(original.cell_size, deserialized.cell_size);
534        assert_eq!(original.underline, deserialized.underline);
535        assert_eq!(original.strikethrough, deserialized.strikethrough);
536        assert_eq!(original.glyphs.len(), deserialized.glyphs.len());
537
538        // Assert each glyph matches
539        for (orig_glyph, deser_glyph) in original
540            .glyphs
541            .iter()
542            .zip(deserialized.glyphs.iter())
543        {
544            assert_eq!(orig_glyph.id, deser_glyph.id);
545            assert_eq!(orig_glyph.symbol, deser_glyph.symbol);
546            assert_eq!(orig_glyph.pixel_coords, deser_glyph.pixel_coords);
547        }
548    }
549}