Skip to main content

beamterm_data/
serialization.rs

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